"""Per-uid relational memory: affinity EMA + intensity-keyed flashbulb. PRIVACY: stores ONLY vectors + mood words + timestamps + hashed uid. NO raw message text, NO usernames, NO PII of any kind. """ from __future__ import annotations import json, sqlite3, time from typing import Callable _DIMS = 7 _COLS = ("av", "aa", "ad", "au", "ag", "aw", "ai") _CREATE = """ CREATE TABLE IF NOT EXISTS relation ( uid TEXT PRIMARY KEY, av REAL, aa REAL, ad REAL, au REAL, ag REAL, aw REAL, ai REAL, n INTEGER, fb_ts REAL, fb_read TEXT, fb_word TEXT, fb_intensity REAL ) """ def _intensity(read: list[int]) -> float: return float(sum(abs(v - 128) for v in read)) class RelationStore: def __init__(self, db_path: str, now: Callable[[], float] = time.time, alpha: float = 0.15): self._db = db_path self._now = now self._alpha = alpha with self._conn() as cx: cx.execute(_CREATE) def _conn(self) -> sqlite3.Connection: return sqlite3.connect(self._db) def observe(self, uid_hash: str, read: list[int], mood_word: str | None = None) -> None: intensity = _intensity(read) ts = self._now() with self._conn() as cx: row = cx.execute("SELECT av,aa,ad,au,ag,aw,ai, n, fb_intensity FROM relation WHERE uid=?", (uid_hash,)).fetchone() if row is None: av, aa, ad, au, ag, aw, ai = (float(v) for v in read) cx.execute( "INSERT INTO relation VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", (uid_hash, av, aa, ad, au, ag, aw, ai, 1, ts, json.dumps(read), mood_word, intensity) ) else: old = list(row[:_DIMS]) n = row[7] old_intensity = row[8] if row[8] is not None else 0.0 a = self._alpha new_ema = [a * read[i] + (1 - a) * old[i] for i in range(_DIMS)] if intensity > old_intensity: fb_ts, fb_read, fb_word, fb_int = ts, json.dumps(read), mood_word, intensity else: fb_ts_old, fb_read_old, fb_word_old = cx.execute( "SELECT fb_ts, fb_read, fb_word FROM relation WHERE uid=?", (uid_hash,)).fetchone() fb_ts, fb_read, fb_word, fb_int = fb_ts_old, fb_read_old, fb_word_old, old_intensity cx.execute( "UPDATE relation SET av=?,aa=?,ad=?,au=?,ag=?,aw=?,ai=?, n=?," " fb_ts=?, fb_read=?, fb_word=?, fb_intensity=? WHERE uid=?", (*new_ema, n + 1, fb_ts, fb_read, fb_word, fb_int, uid_hash) ) def affinity(self, uid_hash: str) -> list[int] | None: with self._conn() as cx: row = cx.execute("SELECT av,aa,ad,au,ag,aw,ai FROM relation WHERE uid=?", (uid_hash,)).fetchone() if row is None: return None return [round(v) for v in row] def flashbulb(self, uid_hash: str) -> dict | None: with self._conn() as cx: row = cx.execute("SELECT fb_ts, fb_read, fb_word FROM relation WHERE uid=?", (uid_hash,)).fetchone() if row is None: return None ts, fb_read, fb_word = row return {"ts": ts, "read": json.loads(fb_read), "mood_word": fb_word} def known(self, uid_hash: str) -> bool: with self._conn() as cx: row = cx.execute("SELECT 1 FROM relation WHERE uid=?", (uid_hash,)).fetchone() return row is not None