File size: 4,011 Bytes
3be03dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""
Backend connectivity for the memory system.

Provides:
  - Sentence-transformer embedding helpers (_load_embedder, _embed)
  - Supabase connection management (_get_supabase, _using_supabase)
  - SQLite local fallback connector (_get_sqlite)
"""
import os
import sqlite3
import threading
from functools import lru_cache

# ── Embedding helpers ──────────────────────────────────────────────────────────

@lru_cache(maxsize=1)
def _load_embedder():
    """
    Load sentence-transformers model once, cache it.
    all-MiniLM-L6-v2: 80MB, fast, 384-dim vectors β€” perfect for this.
    Returns None if sentence-transformers not installed.
    """
    try:
        from sentence_transformers import SentenceTransformer
        return SentenceTransformer("all-MiniLM-L6-v2")
    except ImportError:
        return None


def _embed(text: str) -> list[float] | None:
    """
    Convert text to a 384-dimensional vector.
    Returns None if embedding model not available β€” callers handle this.
    """
    model = _load_embedder()
    if model is None:
        return None
    return model.encode(text[:500], normalize_embeddings=True).tolist()


# ── Supabase connection ────────────────────────────────────────────────────────

_supabase_client = None
_supabase_lock   = threading.Lock()


def _get_supabase():
    """
    Return a Supabase client, or None if not configured.
    Uses a module-level singleton β€” creates the connection once.
    Thread-safe via lock.
    """
    global _supabase_client
    with _supabase_lock:
        if _supabase_client is not None:
            return _supabase_client

        url = os.getenv("SUPABASE_URL", "")
        key = os.getenv("SUPABASE_KEY", "")

        if not url or not key:
            return None   # not configured β€” caller falls back to local

        try:
            from supabase import create_client
            _supabase_client = create_client(url, key)
            print("[memory] Connected to Supabase")
            return _supabase_client
        except Exception as e:
            print(f"[memory] Supabase connection failed: {e} β€” using local fallback")
            return None


def _using_supabase() -> bool:
    return _get_supabase() is not None


# ── Local SQLite fallback paths ────────────────────────────────────────────────

_DATA_DIR    = os.path.join(os.path.dirname(__file__), "..", "data")
_SQLITE_PATH = os.path.join(_DATA_DIR, "memory.db")


def _get_sqlite():
    """Return a SQLite connection, creating the DB and tables if needed."""
    os.makedirs(_DATA_DIR, exist_ok=True)
    conn = sqlite3.connect(_SQLITE_PATH, check_same_thread=False)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS episodes (
            id             INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id        TEXT,
            ticker         TEXT,
            recommendation TEXT,
            confidence     INTEGER,
            price_at_time  REAL,
            report         TEXT,
            outcome        TEXT,
            created_at     TEXT
        )
    """)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS stock_analyses (
            id             INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id        TEXT,
            ticker         TEXT,
            recommendation TEXT,
            confidence     INTEGER,
            report         TEXT,
            created_at     TEXT
        )
    """)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS user_preferences (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id    TEXT,
            preference TEXT,
            created_at TEXT
        )
    """)
    conn.commit()
    return conn