clanker / app /relation.py
deucebucket's picture
M3: care + relational memory + Moments + resilience growth
2e57827 verified
Raw
History Blame Contribute Delete
3.71 kB
"""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