+
    Xj)                     a  0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t
^ RIt^ RIHt ^ RIHtHt ^ RIHtHtHt ^ RIHtHt ^ RIHt ^ RIHt ^ RIt]P8                  P;                  R	R
4      t]P8                  P;                  RR4      t]P8                  P;                  RR4      t ]P8                  P;                  RR4      t!]"! ]P8                  P;                  RR4      4      s#]P8                  P;                  RR
4      t$]P8                  P;                  RR4      PK                  R4      t&]P8                  P;                  RR4      t']P8                  P;                  RR4      t(]P8                  P;                  RR4      R8g  t)R R lt*]+! ]P8                  P;                  RR 4      4      t,]+! ]P8                  P;                  R!R"4      4      t-]"! ]P8                  P;                  R#R$4      4      t.Rs/Rs0R%]1R&&   R' R( lt2R) R* lt3R+ R, lt4ERuR- R. llt5R/ R0 lt6R1 R2 lt7]R3 R4 l4       t8]! R5]8R67      t9]Pt                  ! ]94       R7 R8 lt; ! R9 R:]4      t< ! R; R<]4      t= ! R= R>]4      t>]9P;                  R?4      R@ 4       t?ERvRA RB llt@ERwRC RD lltAREtBERxRF RG lltCRH RI ltDRJ RK ltERL RM ltFERyRO RP lltG]9P                  RQ4      ]! RRR7      3RS RT ll4       tI]9P                  RU4      ]! RRR7      3RV RW ll4       tJ]9P                  RX4      ]! RRR7      3RY RZ ll4       tK0 R[kR\kR]kR^kR_kR`kRakRbkRckRdkRekRfkRgkRhkRikRjkRkkRlkRmkRnkRokRpkRqkRrkRskRtkRukRvkRwkRxkRykRzkRkR{kRkR|kR}kR~kRkRkRkRkRkRkRktLR R ltMR R ltNERzR R lltOR R ltPR R ltQR R ltRR R ltSR R ltT/ RRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRb/ RRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbCtUR R ltVR R ltWR R ltXR R ltY]9P;                  R4      ^
R^RNRRR]! RRR7      3R R ll4       tZ]9P;                  R4      R^(^ RNR]! RRR7      3R R ll4       t[ ! R R]4      t\]9P                  R4      ]! RRR7      3R R ll4       t]]9P;                  R4      RRN]! RRR7      3R R ll4       t^ ! R R]4      t_]9P                  R4      ]! RRR7      3R R ll4       t`]9P;                  R4      R]! RRR7      3R R ll4       ta0 ER{mtbER|tc]P                  ! R4      te]P                  ! R4      tf]P                  ! R]P                  4      th]P                  ! R]P                  4      ti]P                  ! R4      tjR R ltkR R ltlR R ltmERwR R lltn0 ER}mto0 ER~mtp0 ERmtqERtrR R ltsR R lttRtuR R ltvR R ltwR R ltx]9P;                  R4      R]! RRR7      3R R ll4       ty ! R R]4      tz]9P                  R4      ]! RRR7      3R R ll4       t{ ! R R]4      t|]9P                  R4      ]! RRR7      3R R ll4       t} ! R R]4      t~]9P                  R4      ]! RRR7      3R R ll4       t]9P                  ER 4      ]! RRR7      3ER ER ll4       t ! ER ER]4      t]9P                  ER4      ]! RRR7      3ER ER ll4       t]9P;                  ER4      R]! RRR7      3ER ER	 ll4       t ! ER
 ER]4      t]9P                  ER4      ]! RRR7      3ER ER ll4       t ! ER ER]4      t]9P                  ER4      ]! RRR7      3ER ER ll4       t ! ER ER]4      t]9P                  ER4      ]! RRR7      3ER ER ll4       tER ER lt ! ER ER]4      t ! ER ER]4      t]9P                  ER4      ]! RRR7      3ER  ER! ll4       t ! ER" ER#]4      t]9P                  ER$4      ]! RRR7      3ER% ER& ll4       t]9P                  ER'4      ]! RRR7      3ER( ER) ll4       t ! ER* ER+]4      t]9P                  ER,4      ]! RRR7      3ER- ER. ll4       t ! ER/ ER0]4      t]9P                  ER14      ]! RRR7      3ER2 ER3 ll4       t ! ER4 ER5]4      t]9P                  ER64      ]! RRR7      3ER7 ER8 ll4       t]9P;                  ER94      RER:^]! RRR7      3ER; ER< ll4       t]9P;                  ER=4      R^]! RRR7      3ER> ER? ll4       t ! ER@ ERA]4      t]9P                  ERB4      ]! RRR7      3ERC ERD ll4       t ! ERE ERF]4      t]9P                  ERG4      ]! RRR7      3ERH ERI ll4       t ! ERJ ERK]4      t]9P                  ERL4      ]! RRR7      3ERM ERN ll4       t]9P;                  ERO4      R]! RRR7      3ERP ERQ ll4       t ! ERR ERS]4      t]9P                  ERT4      ]! RRR7      3ERU ERV ll4       t ! ERW ERX]4      t]9P;                  ERY4      R]! RRR7      3ERZ ER[ ll4       t]9P                  ER\4      ]! RRR7      3ER] ER^ ll4       t ! ER_ ER`]4      t ! ERa ERb]4      t]9P                  ERc4      ]! RRR7      3ERd ERe ll4       t]9P;                  ERf4      RRN]! RRR7      3ERg ERh ll4       t]9P                  ERi4      ]! RRR7      3ERj ERk ll4       t]9P                  ERl4      ]! RRR7      3ERm ERn ll4       t]9P                  ERo4      ]! RRR7      3ERp ERq ll4       t]9P;                  ERr4      R]! RRR7      3ERs ERt ll4       tR# (  u2  Jarvis memory service — the shared brain (deploy on Railway).

DUMB ON PURPOSE. There is NO model on this server. The intelligence lives in
whatever model is already running on a device (Jarvis's Claude on Mac/Windows
now, a local model on the phone later, future screenshot/transcribe daemons) —
each one decides WHAT to remember and WHEN to recall, and structures the fact
itself. This service only:
  • stores each memory as a node with a LOCAL embedding (FastEmbed, CPU — no
    LLM, no API), and
  • answers searches by MEANING (vector similarity) + returns CONNECTED facts
    (1 graph hop), so a vague query pulls back the linked context.

Backed by FalkorDB (a graph DB with native vectors). Every device/daemon is a
thin client that POSTs facts and GETs searches over HTTPS.

Endpoints
  GET  /health                 — liveness + memory/entity count + embed model + reflection
  POST /remember               — {text, kind?, when?, importance?, raw?, source?,
                                  group?, links?, entities?} → {id, op, auto_linked, entities}
                                  (server-side dedup/supersede WRITE PIPELINE)
  GET  /search?q=&k=&group=&hops=&as_of=&when_from=&when_to= — connected facts by meaning
  GET  /memories?group=&limit=&offset= — newest-first list (HUD viewer)
  GET  /graph?group=           — whole graph {nodes,edges} (Memory+Entity+REL)
  POST /link                   — {from, to, type}
  POST /unlink                 — {from_id, to} cut the edge(s) between two nodes
  POST /backfill_links         — {dry_run?,threshold?} auto-link all existing memories
  GET  /entity?name=          — everything mentioning an entity (facts + relations)
  GET  /profile?name=&group=  — fast RAW one-entity aggregate for a HUD card: entity + summary + read +
                                quick contacts + relations + timeline + facts_by_kind + tasks + counts +
                                has_composed (fuzzy resolver: difflib ratio ≥0.85 beats a substring hit)
  POST /dossier                — {name, group?, force?} an LLM READS the /profile aggregate and COMPOSES
                                an organized, adaptive card {header, sections[text|kv|chips|list]};
                                cached on the entity (24h / fact-count) → {composed, cached}
  POST /entity/update         — {key, group?, etype?, name?} fix an entity's type / display name
  POST /summary                — {name, text} upsert an entity's current-state summary
  POST /retire                 — {id} soft-retire a memory (stale but kept, for dedup)
  POST /attach_entities        — {memory_id, entities} attach entities (backfill hook)
  POST /relation               — {from, to, rtype} entity↔entity edge (resolved ends)
  POST /entities/merge         — {keep_key, merge_keys} fold duplicate entities into one
  POST /entities/dedup_backfill — {dry_run} scan/auto-merge duplicate entities
  POST /reembed                — {batch} re-embed every node with the current model
  GET  /export                 — complete dump (feeds nightly backups)
  POST /reflection_ran         — {status, note} record the reflection engine's last run
  GET  /core_block · POST /core — Ahmed's core context block (singleton)
  POST /insight                — {text,title,itype} reflection finding · GET /insights

RECALL is SALIENCE-ranked: relevance × recency × confidence × reinforcement ×
importance (latest+firm+often-recalled facts surface first; nothing is deleted).
Facts carry a `confidence` (0..1) and an `importance` (1..10, default 5).

WRITE PIPELINE (Mem0-style, server-side): /remember embeds the text, pulls the
top similar valid memories, and — if DEEPSEEK_API_KEY is set — asks ONE cheap
DeepSeek call whether to ADD / UPDATE (supersede a specific memory) / NOOP (a
duplicate), cleans a raw utterance into an atomic third-person fact, extracts the
event date + entities + relations, and marks single-valued facts (srel) so a new
value auto-retires the old. NO key ⇒ graceful degrade (cosine≥0.95 ⇒ noop, else
plain add). An LLM failure NEVER loses a memory — it falls back to a plain add.

BI-TEMPORAL: every memory carries valid_from (event/creation time) and valid_to
(null while current, set on retire/supersede) alongside the legacy `valid` bool +
invalid_at. /search?as_of=<ISO> answers "what was true then".

AUTO-LINKING (Layer 1): every write draws edges to the most similar existing
memories (embedding + shared words) — no LLM. AUTOLINK=0 off.
ENTITIES (Layer 2): entities resolve by exact key / alias / fuzzy (embedding +
difflib) / DeepSeek adjudication into shared :Entity nodes, wired by
(:Memory)-[:MENTIONS]->(:Entity) and (:Entity)-[:REL]->(:Entity).
  POST /supersede              — {old_id, text, ...} mark old fact stale + add new
  POST /forget                 — {id} permanently delete a memory + its links

Env: FALKOR_HOST/FALKOR_PORT, MEMORY_API_KEY (bearer the devices send),
     MEMORY_GROUP (default namespace), EMBED_MODEL, EMBED_DIM,
     DEEPSEEK_API_KEY (optional — enables the LLM write pipeline), WRITE_MODEL,
     DEEPSEEK_URL.
)annotationsN)asynccontextmanagerdatetimetimezone)FastAPIHeaderHTTPException)	BaseModelField)FalkorDB)TextEmbeddingMEMORY_API_KEY MEMORY_GROUPahmedMEMORY_GRAPHjarvisEMBED_MODELz;sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2	EMBED_DIM384DEEPSEEK_API_KEYDEEPSEEK_URLzhttps://api.deepseek.com/WRITE_MODELzdeepseek-v4-flashDOSSIER_MODELAUTOLINK10c                   V ^8  d   QhRR/#    returnfloat )formats   ""claude-voice/memory-service/app.py__annotate__r'   {   s     ! !e !    c                &    R\        V 4      ,
          # )zFalkorDB's db.idx.vector.queryNodes YIELDs cosine DISTANCE (0 = identical,
~1 = unrelated), NOT similarity. Convert at every consumption site so all
thresholds/scores in this file read as plain cosine similarity.      ?)r#   )distances   &r&   _simr,   {   s     x  r(   AUTOLINK_THRESHOLDz0.60INSIGHT_DEDUPz0.78AUTOLINK_MAX5zTextEmbedding | None	_embedderc                    V ^8  d   QhRRRR/# )r!   textstrr"   list[float]r$   )r%   s   "r&   r'   r'      s     = = = =r(   c                |    \        \        P                  V .4      4       Uu. uF  p\        V4      NK  	  up# u upi N)nextr1   embedr#   )r3   xs   & r&   _embedr;      s0    "9??D6#:;<;E!H;<<<s   9c                   V ^8  d   QhRR/# r!   r"   r4   r$   )r%   s   "r&   r'   r'      s     2 2# 2r(   c                 f    \         P                  ! \        P                  4      P	                  4       # r7   )r   nowr   utc	isoformatr$   r(   r&   _now_isorB      s    <<%//11r(   c                   V ^8  d   QhRR/# r=   r$   )r%   s   "r&   r'   r'      s     9 9 9r(   c                     \         P                  ! \        P                  4      P	                  4       P                  4       # r7   )r   r?   r   r@   daterA   r$   r(   r&   _todayrF      s'    <<%**,6688r(   c          
     ,    V ^8  d   QhRRRRRRRRR	R
/# )r!   promptr4   
max_tokensintmodel
str | Nonetemperaturer#   r"   dict | Noner$   )r%   s   "r&   r'   r'      s1      3 C j  %0;r(   c                   \         '       g   \        R4      h\        P                  ! RT;'       g    \        RVRRRVRRR	/R
RRRV /./4      P                  4       p\        P                  P                  \        R,           VRRRR\          2/R7      p\        P                  P                  V^<R7      ;_uu_ 4       p\        P                  ! VP                  4       4      pRRR4       XP                  R4      ;'       g    / .^ ,          P                  R4      ;'       g    / P                  RR4      ;'       g    RP                  4       p\        P                  ! V4      #   + '       g   i     L; i)u  ONE DeepSeek call that must return a JSON OBJECT (response_format json).

Stdlib urllib only (same dependency-light pattern as reflection-service). Any
failure — no key, network, non-JSON — RAISES so callers fall back to a plain
add and never lose a memory. Returns the parsed dict. `model`/`temperature`
are overridable (the dossier composer uses a richer model at temp 0.3); the
defaults keep every existing write-pipeline call unchanged.zno DEEPSEEK_API_KEYrK   rM   streamFrI   response_formattypejson_objectmessagesroleusercontentz/chat/completionszContent-Typezapplication/jsonAuthorizationBearer )dataheaders)timeoutNchoicesmessager   )DEEPSEEK_KEYRuntimeErrorjsondumpsr   encodeurllibrequestRequestDS_URLurlopenloadsreadgetstrip)	rH   rI   rK   rM   bodyreqrorW   s	   &&&&     r&   _deepseek_jsonrq      sG    <011::%%+}k%zFM2ffi89	 
 vx 	 ..
 
 $$4!3 GL>":< ! =C 
		R		0	0AJJqvvx  
1y!))bT1-11)<BBIr") )&(%%' ::g	 
1	0s   >%E%%E5	c                   V ^8  d   QhRR/# r    r$   )r%   s   "r&   r'   r'      s      U r(   c                    V  Uu. uF  p\        V4      NK  	  p pV Uu. uF  p\        V4      NK  	  ppT '       d"   T'       d   \        T 4      \        T4      8w  d   R # \        R \	        Y4       4       4      p\
        P                  ! \        R T  4       4      4      p\
        P                  ! \        R T 4       4      4      pTR 8X  g   TR 8X  d   R # Y4T,          ,          # u upi u upi   \         d     R # i ; i)        c              3  6   "   T F  w  rW,          x  K  	  R # 5ir7   r$   ).0r:   ys   &  r&   	<genexpr>_cosine.<locals>.<genexpr>   s     *	aee	s   c              3  2   "   T F  qV,          x  K  	  R # 5ir7   r$   rv   r:   s   & r&   rx   ry           (a1uua   c              3  2   "   T F  qV,          x  K  	  R # 5ir7   r$   )rv   rw   s   & r&   rx   ry      r|   r}   )r#   	Exceptionlensumzipmathsqrt)abr:   dotnanbs   &&    r&   _cosiner      s     !q!U1Xq! !q!U1Xq! AQ3q6)
*A	*
*C	3(a((	)B	3(a((	)B	SyB#Ir'? "! s+   C1 C'C1 C,C1 '
C1 1D ?D c                   V ^8  d   QhRR/# )r!   r"   Noner$   )r%   s   "r&   r'   r'      s     4P 4PT 4Pr(   c            	     B   \        \        4      s \        \	        R 4      4      p V '       d&   V \
        8w  d   \        R\
         RV  R2RR7       V s\        \        P                  P                  RR	4      \        \        P                  P                  R
R4      4      \        P                  P                  R4      ;'       g    RR7      pTP                  \        4      s \        P!                  R\
         R24        \        P!                  R4        \        P!                  R\
         R24        \        P!                  R\
         R24       R F  p \        P!                  T4       K  	  \        R\         R\
         R2RR7       R#   \         d   p\        RT 2RR7        Rp?EL]Rp?ii ; i  \         d9   pR\#        T4      P%                  4       9  d   \        RT 2RR7        Rp?LRp?ii ; i  \         d:   pR\#        T4      P%                  4       9  d   \        RT 2RR7        Rp?EL'Rp?ii ; i  \         d:   pR\#        T4      P%                  4       9  d   \        RT 2RR7        Rp?ELPRp?ii ; i  \         d:   pR\#        T4      P%                  4       9  d   \        RT 2RR7        Rp?ELyRp?ii ; i  \         dB   pR\#        T4      P%                  4       9  d   \        RT 2RR7        Rp?EK   Rp?EK  Rp?ii ; i)zdimension probez[startup] EMBED_DIM z -> z (from model)Tflushz[startup] dim-probe note: NFALKOR_HOST	localhostFALKOR_PORT6379FALKOR_PASSWORD)hostportpasswordzHCREATE VECTOR INDEX FOR (m:Memory) ON (m.embedding) OPTIONS {dimension: , similarityFunction: 'cosine'}alreadyz[startup] vector index note: z&CREATE INDEX FOR (e:Entity) ON (e.key)z[startup] entity index note: zICREATE VECTOR INDEX FOR (i:Insight) ON (i.embedding) OPTIONS {dimension: z[startup] insight index note: zHCREATE VECTOR INDEX FOR (e:Entity) ON (e.embedding) OPTIONS {dimension: z$[startup] entity vector index note: z[startup] range index note: zmemory service ready (z, dim ))z(CREATE INDEX FOR (m:Memory) ON (m.group)z-CREATE INDEX FOR (m:Memory) ON (m.created_at))r   r   r1   r   r;   r   printr   r   osenvironrk   rJ   select_graph
GRAPH_NAME_graphqueryr4   lower)real_dimedb_stmts       r&   _init_graphr      s   k*I<v/01I-(4zO I 
rzz~~m[A2::>>-@A::>>*;<DD
FB __Z(FC$$-;.NP	QC=>D$$-;.NP	QJ$$-;.NP	QC	FLLC 
";-vi[
B$OQ  <*1#.d;;<  CCFLLN*1!5TBC
  CCFLLN*1!5TBC  DCFLLN*21#6dCD  JCFLLN*8<DIJ  	FA.4QC8EE /	Fs   AF "F7  G= I 4J KF4F//F47G:.G55G:=I.H<<IJ.JJK.K

KL.LLc                   V ^8  d   QhRR/# )r!   appr   r$   )r%   s   "r&   r'   r'      s       r(   c                  "   \        4        \        P                  ! 4       ;_uu_4       GR j  xL
  R 5x  R R R 4      GR j  xL
  R #  L L  + GR j  xL 
 '       g   i     R # ; i5ir7   )r   
mcp_serverlifespan)r   s   &r&   	_lifespanr      s5      M""$$$ %$$$$$sI   ,A+A
A+AA+AA+A+A(	A
A(	 A(	"	A+zJarvis Memory)titler   c                    V ^8  d   QhRRRR/# )r!   authorizationrL   r"   r   r$   )r%   s   "r&   r'   r'   	  s     N N N Nr(   c                X    \         '       d   V R \          28w  d   \        RRR7      hR# R# )rY   i  zbad or missing API keystatus_codedetailN)API_KEYr	   )r   s   &r&   _authr   	  s*    w=ggY$774LMM 8wr(   c                      ] tR tRt$ R]R&   RtR]R&   RtR]R&   ^tR	]R
&   RtR]R&   Rt	R]R&   Rt
R]R&   RtR]R&   RtR]R&   RtR]R&   RtR]R&   RtR]R&   RtR# )Rememberi  r4   r3   factrL   kindNwhen
int | None
importanceFbool | Nonerawr   sourcegrouplist[dict] | Nonelinksentitiesfloat | None
confidencetone
historicalr$   )__name__
__module____qualname____firstlineno____annotations__r   r   r   r   r   r   r   r   r   r   r   __static_attributes__r$   r(   r&   r   r     sw    
ID*D*J
C!FJ!E:#E#"&H&#J#D*#J#r(   r   c                  :    ] tR tRt$ R]R&   R]R&   RtR]R&   RtR	# )
Linki  r4   from_idtorelatedrL   rR   r$   N)r   r   r   r   r   rR   r   r$   r(   r&   r   r     s    LG D* r(   r   c                      ] tR tRt$ R]R&   R]R&   RtR]R&   RtR]R	&   R
tR]R&   RtR]R&   Rt	R]R&   Rt
R]R&   RtR]R&   RtR# )	Supersedei%  r4   old_idr3   r   rL   r   Nr   r   r   r   r   r   r   r   r   r$   )r   r   r   r   r   r   r   r   r   r   r   r   r   r$   r(   r&   r   r   %  sS    K
ID*D*!FJ!E:"&H&#J#D*r(   r   z/healthc                     \         P                  R4      P                  ^ ,          ^ ,          p  \         P                  R4      P                  ^ ,          ^ ,          pRp \         P                  RR\        /4      P                  pV'       d   V^ ,          ^ ,          ;'       g    RMRpRRRV R	VR
VR\
        R\        RV /#   \         d    Rp  Li ; i  \         d    Rp Li ; i  \         d     LKi ; i)u   Liveness — unauthenticated (same as before). Reports memory/entity counts,
the reflection engine's last run, and the active embedding model + dim.z MATCH (m:Memory) RETURN count(m)z MATCH (e:Entity) RETURN count(e)r   z<MATCH (x:Meta {id:'reflection', group:$g}) RETURN x.last_rungokTmemory_countentity_countreflection_last_runembed_modeldimmemories)r   r   
result_setr   GROUP_DEFAULTr   r   )neclastro   s       r&   healthr   1  s    LL;<GGJ1M\\<=HHKAN DLLJ- "",* 	
 #$!Q2 $>2!49j!- -      s@   -C -C  -C( C( %C( CCC%$C%(C65C6c               D    V ^8  d   QhRRRRRRRRRRRR	R
RRRRRRRRR/# )r!   r3   r4   r   r   rL   r   r   embzlist[float] | Noner   r#   r   r   rJ   srelr"   r$   )r%   s   "r&   r'   r'   J  sg      # S 
 C /,6  (2 >Ar(   c
                V   \         P                  ! 4       P                  p
Vf   \        V 4      p\	        4       p\
        P                  RRT
RT RTRTRTRTRT;'       g    R	R
TR\        V4      RT;'       g    R	RVe   \        V4      M^RT;'       g    TRT	;'       g    R	/4       V
# )a(  Create a :Memory node. Bi-temporal: valid_from = the event date (`when`)
if given, else creation time; valid_to is left UNSET (= null = current) and
only set on retire/supersede. `srel` (subject|relation, lowercased) marks a
single-valued fact so a newer value can deterministically supersede it.zCREATE (m:Memory {id:$id, text:$text, kind:$kind, source:$source, group:$grp, created_at:$now, refers_to:$when, valid:true, confidence:$conf, tone:$tone, importance:$imp, valid_from:$vf, srel:$srel}) SET m.embedding = vecf32($emb)idr3   r   r   grpr?   r   r   r   confr   impvfr   )	uuiduuid4hexr;   rB   r   r   r#   rJ   )r3   r   r   r   r   r   r   r   r   r   midr?   s   &&&&&&&&&&  r&   _creater   J  s     **,

C
{Tl
*C
LL	6 
sFD&$&	uc64::2uc	z"FDJJB	:#9Jq	fdjjb		2	3 Jr(   c               $    V ^8  d   QhRRRRRR/# )r!   ids	list[str]superseded_byrL   r"   r   r$   )r%   s   "r&   r'   r'   c  s!     / /y / /t /r(   c                    V '       g   R# \        4       p\        P                  RRV RV/4       V'       d   \        P                  RRV RV/4       R# R# )zBi-temporal retire: set valid=false + invalid_at + valid_to=now (and
superseded_by when a replacement exists). Idempotent; skips already-stale.NzbUNWIND $ids AS rid MATCH (m:Memory {id:rid}) SET m.valid=false, m.invalid_at=$now, m.valid_to=$nowr   r?   DUNWIND $ids AS rid MATCH (m:Memory {id:rid}) SET m.superseded_by=$sbsb)rB   r   r   )r   r   r?   s   && r&   _retire_nodesr   c  sT     
*C
LL	@	UC " RC}-	/ r(   a  You are the write layer of Ahmed's personal memory graph. Decide how to store one new incoming memory relative to what is already stored.

Today's date is {today}.

NEW INPUT (raw_utterance={raw}): {text}

EXISTING SIMILAR MEMORIES (most similar first), "id: text":
{similar}

Choose ONE operation:
- "add": genuinely new information -> store it.
- "update": REPLACES/UPDATES a specific existing memory above (a changed fact or a new current value) -> put that memory's id in target_id.
- "noop": an existing memory above ALREADY states this (a duplicate) -> put its id in target_id; nothing new is stored.

Return ONLY a JSON object with EXACTLY these keys:
{{"op":"add|update|noop",
"target_id":"<existing id for update/noop, else null>",
"text":"<the fact as ONE clean third-person atomic statement about Ahmed's world; MANDATORY rewrite when raw_utterance=true: keep meaning, drop filler and first-person; for noop echo the existing text>",
"when":"<ISO date YYYY-MM-DD the fact REFERS TO if it implies an event date (resolve relative dates like 'yesterday' against today above), else null>",
"sro":{{"subject":"","relation":"","object":"","single_valued":true_or_false}} or null (single_valued=true only when the subject can hold ONE current value for this relation, e.g. lives-in, is-CEO-of, phone-number, so a new value supersedes),
"entities":[{{"name":"","type":"person|company|project|place|product|amount|thing|concept"}}],
"relations":[{{"from":"","to":"","rtype":""}}]}}

JSON:c               (    V ^8  d   QhRRRRRRRR/# )	r!   r   r5   r   r4   r   rJ   r"   
list[dict]r$   )r%   s   "r&   r'   r'     s.     K K+ Kc Kc K* Kr(   c                6    \         P                  RR\        V^4      ^,          ^
,           RV RV/4      P                  R\        V^4       pT Uu. uF)  pRT^ ,          RT^,          R\        T^,          4      /NK+  	  up#   \         d    . u # i ; iu upi )	zuTop-n similar VALID same-group memories by embedding (empty on any error,
e.g. before the vector index has any rows).zCALL db.idx.vector.queryNodes('Memory','embedding',$k,vecf32($emb)) YIELD node, score WHERE node.group=$grp AND node.valid=true RETURN node.id, node.text, scorekr   r   Nr   r3   score)r   r   maxr   r   r,   )r   r   r   rowsro   s   &&&  r&   _similarr    s    ||/ #a)a-"$eSE	
 'JzAq	3 FJJTT1Q41wQqT
;TJJ  	Js   AB /BBBc          
     ,    V ^8  d   QhRRRRRRRRR	R/# )
r!   pdict	orig_textr4   	orig_whenrL   similar_idssetr"   r$   )r%   s   "r&   r'   r'     s1     = =$ =3 =: ==$(=r(   c                   \        V P                  RR4      4      P                  4       P                  4       pVR9  d   RpV P                  R4      pVR9  d   \        V4      P                  4       MRpV'       d	   WS9  d   RpVR9   d   V'       g   Rp\        V P                  R4      ;'       g    R4      P                  4       ;'       g    TpV P                  R4      pVR9  d   \        V4      P                  4       MT;'       g    Rp\	        V P                  R4      \
        4      '       d   V P                  R4      MRpV P                  R	4      ;'       g    .  U	u. uFN  p	\	        V	\
        4      '       g   K  \        V	P                  R
R4      4      P                  4       '       g   KL  V	NKP  	  p
p	V P                  R4      ;'       g    .  Uu. uF  p\	        V\
        4      '       g   K  \        VP                  RR4      4      P                  4       '       g   KL  \        VP                  RR4      4      P                  4       '       g   K}  VNK  	  ppRTRT;'       g    RRVRVRVR	V
RV/# u up	i u upi )u   Validate/normalize the model's plan — never trust its shape or a
hallucinated target_id; degrade update/noop-without-a-real-target to add.opadd	target_idNr   r3   r   sror   name	relationsfromr   )r  updatenoop)Nr   )r  r  )r4   rk   rl   r   
isinstancer  )r  r  r	  r
  r  tidr3   r   r  r   entsro   relss   &&&&         r&   
_norm_planr    s    
QUU4	 	&	&	(	.	.	0B	**
%%
C!3#c(..
C
s%	quuV}""#))+88yD55=D $J 63t9??Y=N=N$D$QUU5\488!%%,dCj)//R/ G/!!T" '*155+<'='C'C'E A/D Gk*00b0 .0!!T" '*155+<'='C'C'E AEE$O$**, A0D . "k3;;$fd3
D+t= =G.s*   #I7 ,I71I7I<3,I<$,I<I<c               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r!   r3   r4   r   r   rL   r   r   boolr"   ztuple[dict, list[float]]r$   )r%   s   "r&   r'   r'     s8      s # Z  7r(   c                   \        V 4      p\        WS^4      pRRRRRV RVRRR. R	. /p\        '       d    R
P                  R V 4       4      ;'       g    Rp\	        \
        P                  \        4       \        \        V4      4      P                  4       V VR7      4      p	\        V	\        4      '       g   \        R4      h\        YY& U
u0 uF  qR,          kK  	  up
4      pW3# V'       d7   V^ ,          R,          R8  d"   RRRV^ ,          R,          RV RVRRR. R	. /pW3# TpW3# u up
i   \         d    Tp Y3# i ; i)u   Build the write PLAN. DeepSeek when keyed (fall back to plain add on any
error — never lose a memory); cosine≥0.95 noop-dedup when not keyed.r  r  r  Nr3   r   r  r   r  
c              3  J   "   T F  qR ,           RVR,           2x  K  	  R# 5i)r   : r3   Nr$   )rv   ss   & r&   rx   _plan_memory.<locals>.<genexpr>  s!     K7aT7)2ai[ 97s   !#(none))todayr   r3   similarzplan not an objectr   r  ffffff?r  )r;   r  r_   joinrq   _WRITE_PROMPTr%   rF   r4   r  r   r  r  
ValueErrorr  r   )r3   r   r   r   r   r   r%  	plain_addsim_txtgotr!  plans   &&&&&       r&   _plan_memoryr.    sl    ,Cs1%Guk4vtj"k2?I|
	iiK7KK    !5!5hCS	N$8$8$: "6 "! "C c4(( !566c/IA$/IJD 9 
WQZ(D0fk71:d+;VTeT:r;L 9 9 0J 	D 9	s*   D/ A8D/ D*D/ *D/ /E Ec                    V ^8  d   QhRRRR/# )r!   r   r4   r"   r   r$   )r%   s   "r&   r'   r'     s     	 	s 	t 	r(   c                   T ;'       g    .  F  p\        T;'       g    / P                  R R4      4      P                  4       p\        T;'       g    / P                  RR4      4      P                  4       p\        T;'       g    / P                  RR4      4      P                  4       ;'       g    RpV'       g   K  V'       g   K   \        W4WQ4       K  	  R#   \         d     K  i ; i)r  r   r   rtyper   N)r4   rk   rl   _add_relationr   )r  r   ro   fntnrts   &&    r&   _apply_relationsr6    s    __"_!''rvr*+113!''rtR()//1!''rw	2399;HHy2""bb0   s   CC)(C)Fc               D    V ^8  d   QhRRRRRRRRRRR	RR
RRRRRRRRR/# )r!   r-  r  r  r4   orig_embr5   r   r   r   r   r#   r   rL   r   rJ   	no_retirer  r"   r$   )r%   s   "r&   r'   r'     s~     CQ CQd CQs CQk CQ CQCQ$'CQ5:CQBLCQCQ  CQ -1CQr(   c                   \        V P                  R4      ;'       g    . 4      \        T	;'       g    . 4      ,           pV R,          pVR8X  dW   V P                  R4      '       d@   \        V R,          W4      p\        V P                  R4      V4       RV R,          RRR^ RV/# V R,          pV P                  R	4      pW8X  d   TM
\	        V4      pV P                  R
4      pRp. p\        V\        4      '       d   VP                  R4      '       d   \        VP                  RR4      4      P                  4       P                  4       p\        VP                  RR4      4      P                  4       P                  4       pV'       d]   V'       dU   VR,           V,           p\        P                  RRVRV/4      P                  pTV Uu. uF  pV^ ,          NK  	  up,          pVR8X  d>   V P                  R4      '       d'   V R,          V9  d   VP                  V R,          4       V'       d   . p\        V4       \        WVWEVWgVVR7
      pV'       d5   \        P                  RRVRV/4       \        P                  RRVRV/4       T
;'       g    .  F  p\        T;'       g    / P                  RR4      4      P                  4       pV'       g   K@  \        P                  RRTRTR\        T;'       g    / P                  R R!4      4      /4       K  	  \         '       d   \#        VVVV4      M. p\        VW4      p\        V P                  R4      V4       V'       d   RMR"pRVRVR\%        V4      RV/# u upi )#u$  Execute a write plan: noop / add / update(+supersede) with bi-temporal
retirement, single-valued (srel) supersession, entities and relations.
`no_retire` (historical imports): keep the srel marker on the new node but
NEVER retire existing memories — old data cannot supersede current truth.r   r  r  r  r  r   auto_linkedr3   r   r  Nsingle_valuedsubjectr   relation|zQMATCH (m:Memory) WHERE m.group=$grp AND m.valid=true AND m.srel=$srel RETURN m.idr   r   r  )r   r   r   r   r   r   r   r   zkUNWIND $ids AS rid MATCH (a:Memory {id:$mid}),(b:Memory {id:rid}) CREATE (a)-[:LINK {type:'replaces'}]->(b)r   r   MMATCH (a:Memory {id:$a}),(b:Memory {id:$b}) CREATE (a)-[:LINK {type:$t}]->(b)r   r   trR   r   r  )listrk   _attach_entitiesr6  r;   r  r  r4   rl   r   r   r   r   appendr   r   r   
_auto_linkr   )r-  r  r8  r   r   r   r   r   r   body_entities
body_linksr9  all_entitiesr  r  r3   r   r   r  r   retiredsubjrelr  ro   r   lkr   autoop_outs   &&&&&&&&&&&&                  r&   _apply_planrO    s    ,223d=;N;NB6OOL	dB	V|--  [ 1<G+.6d;'v}aD" 	" <D88FD'(VD\C
((5/C DG#t!9!93779b)*00288:#''*b)*00288:C#:#D<</vt,. /9j  d+d!d++G	X~$((;//[!0tK()'
$dFs'zC RGT3'	) 	8C(	*
 R"((b)*0022LL4c3CbhhB^^FI-N)OPR   19:c4e,bDC5DTXXk*E2 XeF#tV]CIz4PP? ,s   M>z	/remember)defaultc                    V ^8  d   QhRRRR/# )r!   rm   r   r   rL   r$   )r%   s   "r&   r'   r'   4  s     8 88 8J 8r(   c                D   \        V4       V P                  P                  4       '       g   \        RRR7      hV P                  ;'       g    \
        pV P                  P                  4       pV P                  e   V P                  MRpV P                  f   ^M(\        ^\        ^
\        V P                  4      4      4      p\        Y0P                  ;'       g    RV P                  V\        V P                  4      4      w  rgV P                   '       d!   VP#                  R4      R8w  d   R	VR&   RVR
&   \%        YcYpP                  ;'       g    RV P&                  ;'       g    RVW@P(                  VV P*                  V P,                  \        V P                   4      R7      # )u   Save one memory THROUGH the write pipeline (dedup/supersede/cleanup).
Backward compatible: an old client POSTing just {text} still works — with no
DEEPSEEK_API_KEY it's a plain add (plus cosine≥0.95 duplicate suppression).  z
empty textr   N      ?r   r  r  r  r  r   )	r   r   r   r   r   r   rF  rG  r9  )r   r3   rl   r	   r   r   r   r   r  minrJ   r.  r   r   r  r   r   rk   rO  r   r   r   r   )rm   r   r   r3   r   r   r-  r   s   &&      r&   rememberrV  3  s0   
 
-99??LAA
**
%
%C99??D"oo94??tD&!C3r3t;O3P,QCT99#6#6		3!$((^-ID488D>V3
 T
 [t3YY-@-@&"kk55XS"&YY3%)]]tzz!%doo!6	8 8r(   z/linkc                    V ^8  d   QhRRRR/# )r!   rm   r   r   rL   r$   )r%   s   "r&   r'   r'   P  s      t J r(   c           
         \        V4       \        P                  R RV P                  RV P                  RV P
                  ;'       g    R/4       RR/# )r@  r   r   rA  r   r   T)r   r   r   r   r   rR   rm   r   s   &&r&   linkrZ  O  sL    	-
LL	,	dllC#tyy/E/EIFH $<r(   z
/supersedec                    V ^8  d   QhRRRR/# )r!   rm   r   r   rL   r$   )r%   s   "r&   r'   r'   Z  s     ) )I )j )r(   c                D   \        V4       V P                  ;'       g    \        pV P                  P	                  4       p\        V4      p\        Y0P                  ;'       g    RV P                  V P                  ;'       g    RY$V P                  e   V P                  MRV P                  R7      p\        V P                  .VR7       \        P                  RRVRV P                  /4       \         '       d   \#        WSWB4       \%        WPP&                  V4      pR	VR
V/# )u   A fact changed: mark the old one stale (kept for history — this is the
'temporal' bit) and add the new current one, linked to what it replaced.r   r   rT  )r   r   r   )r   zUMATCH (a:Memory {id:$a}),(b:Memory {id:$b}) CREATE (a)-[:LINK {type:'replaces'}]->(b)r   r   r   r   )r   r   r   r3   rl   r;   r   r   r   r   r   r   r   r   r   r   r   rE  rC  r   )rm   r   r   r3   r   r   r  s   &&     r&   	superseder]  Y  s     
-
**
%
%C99??D
,C
$		++VTYY++))3040KT__QUyy"C 4;;-s3
LL =sC-/ x3c'C4D#z4((r(   whatr   whowherewhichaboutwiththeforandthatthishavehasdiddoeswaswereareyouyourhishershehimtheythemr  intotellknowrV  sirmemyir   anofr   isitdoc                    V ^8  d   QhRRRR/# )r!   r3   r4   r"   r  r$   )r%   s   "r&   r'   r'   v  s      S S r(   c                    ^ RI pTP                  RT ;'       g    RP                  4       4       Uu0 uF  pV\        9  g   K  VkK  	  up# u upi )uG   Content words (names/terms) of a fact — used to spot shared entities.N[a-z0-9]{3,}r   )refindallr   _STOP)r3   _rews   &  r&   _significant_wordsr  v  sM    {{?TZZR4F4F4HI I!~ AI   s   AAc               $    V ^8  d   QhRRRRRR/# )r!   r   r4   r   r"   r  r$   )r%   s   "r&   r'   r'   }  s!      C C D r(   c                    \         P                  R RV RV/4      P                  ^ ,          ^ ,          p\        V4      ^ 8  # )zDMATCH (a:Memory {id:$a})-[l:LINK]-(b:Memory {id:$b}) RETURN count(l)r   r   )r   r   r   rJ   )r   r   r   s   && r&   _edge_existsr  }  sC    N	aa	$*Q	(()	+A q6A:r(   c               8    V ^8  d   QhRRRRRRRRRRR	R
RRRR/# )r!   new_idr4   r3   r   r5   r   	thresholdr#   	max_linksrJ   dry_runr  r"   r   r$   )r%   s   "r&   r'   r'     sR     C Cs C# CK C CC36CC)3Cr(   c                   Vf   \         MTpVf   \        MTp\        V4      p/ p\        P	                  RRV^,          ^,           RVRVRV /4      P
                  p	V	 Fo  w  rp\        T\        T;'       g    R4      ,          4      p\        V4      R\        V^4      ,          ,           pW9  g   WV
,          ^ ,          8  g   Kj  W3W&   Kq  	  \        V4      R,          pV'       d   R	R
P                  R \        \        V4      4       4       4      ,           R,           pRVRV /\        V4       UUu/ uF  w  ppRV 2VbK  	  uppCp\        P	                  VV4      P
                   Fn  w  r\        T\        T;'       g    R4      ,          4      pV^8  g   K3  RR\        V^4      ,          ,           pW9  g   WV
,          ^ ,          8  g   Ki  W3W&   Kp  	  \        VP                  4       R RR7      p. pV F|  w  p
w  ppVV8  d    V# \        V4      V8  d    V# \        W
4      '       d   K9  V'       g   \        P	                  RRV RV
/4       VP!                  RV
RVR\#        V^4      /4       K~  	  V# u uppi )up  LAYER 1 — connect a fact to the memories most like it, automatically.

Candidates come from BOTH vector similarity (meaning) and shared significant
words (so two notes that both name "Altimi" always link, even when the
embeddings are only lukewarm). Best `max_links` above `threshold` get a
:LINK edge of type 'similar' (skipping any pair already connected). No LLM.zCALL db.idx.vector.queryNodes('Memory','embedding',$k,vecf32($emb)) YIELD node, score WHERE node.group=$grp AND node.valid=true AND node.id <> $nid RETURN node.id, node.text, scorer   r   r   nidr   g{Gz?N   NzKMATCH (m:Memory) WHERE m.group=$grp AND m.valid=true AND m.id <> $nid AND ( OR c              3  ,   "   T F
  pR V 2x  K  	  R# 5iztoLower(m.text) CONTAINS $wNr$   rv   r~  s   & r&   rx   _auto_link.<locals>.<genexpr>        7$5q 9<$5   z) RETURN m.id, m.textr  gffffff?c                "    V ^,          ^ ,          # )   r$   )kvs   &r&   <lambda>_auto_link.<locals>.<lambda>  s    Aqr(   TkeyreversezTMATCH (a:Memory {id:$a}),(b:Memory {id:$b}) CREATE (a)-[:LINK {type:'similar'}]->(b)r   r   r   r3   r  )r-   r/   r  r   r   r   r   r,   rU  rB  r'  range	enumeratesorteditemsr  rD  round)r  r3   r   r   r  r  r  my_wordscandvrowscidctextr  sharedcombinedwordskwqr~  r  paramsrankedlinkedscs   &&&&&&&                r&   rE  rE    s    '0&7"YI ) 1yI!$'HD LL	? 
i!mb %eU			
 $ 
 #EX 25;;B ??@ ;FA!66?hc15!)DI # N2E({{ 7$)#e*$57 77 '	'
 v ?,5e,<=,<DAqqWaZ,<=? ,,sF3>>JC$6u{{$CCDF
 {$VQ"77?hc1&=!) 1DI ? DJJL&94HFF"[b%	> M v;)# M $$LL;fc3') 	tS&%%A,GH # M7 >s   6Jc                    V ^8  d   QhRRRR/# )r!   r  r4   r"   r$   )r%   s   "r&   r'   r'     s     2 2s 2s 2r(   c                p    RP                  T ;'       g    RP                  4       P                  4       4      # )z=Canonical key for an entity so the same name reuses one node. r   )r'  r   split)r  s   &r&   _norm_entityr    s)    88TZZR&&(..011r(   c                    V ^8  d   QhRRRR/# )r!   r!  r4   r"   r$   )r%   s   "r&   r'   r'     s     7 7c 7c 7r(   c                    RP                  \        T ;'       g    RP                  4       P                  4       4      4      # )u   Order-independent token string — 'al temimi' and 'temimi al' compare equal
under difflib, so a reversed name still resolves to the same entity.r  r   )r'  r  r   r  )r!  s   &r&   _sorted_tokensr    s.     88FAGG??,224566r(   c               (    V ^8  d   QhRRRRRRRR/# )r!   
entity_keyr4   aliasr   r"   r   r$   )r%   s   "r&   r'   r'     s(      c # c d r(   c                F   V'       d   W8X  d   R#  \         P                  RRV RV/4      P                  pV'       d   V^ ,          ^ ,          MR;'       g    . pW9   d   R# \         P                  RRV RVR\        V4      V.,           /4       R#   \         d     R# i ; i)zAdd `alias` (a canonical key) to an entity's alias list if new. Read-modify
-write in Python (FalkorDB has no APOC list helpers); None-tolerant.Nz6MATCH (e:Entity {key:$k, group:$grp}) RETURN e.aliasesr   r   z7MATCH (e:Entity {key:$k, group:$grp}) SET e.aliases=$alal)r   r   r   rB  r   )r  r  r   r  curs   &&&  r&   _append_aliasr    s     E'||D*eU+--7Z 	 "tAwqzt22<E*eUD$s)ug2EF	H  s"   +B B 	B "-B B B c               (    V ^8  d   QhRRRRRRRR/# )r!   r  r4   etyper   r"   rL   r$   )r%   s   "r&   r'   r'     s.     F F# Fc F# F* Fr(   c                   \        V 4      pV'       g   R# T;'       g    RP                  4       P                  4       ;'       g    Rp\        P	                  RRVRV/4      P
                  pV'       d   V^ ,          ^ ,          # \        V 4      p/ p \        P	                  RR^
RVRVR	V/4      P
                   F  p\        V^,          4      Wg^ ,          &   K   	  \        P	                  R
RVR	V/4      P
                  p\        V 4      p	RRRrp
V F  w  rp\        P                  ! RT	\        T;'       g    T4      4      P                  4       pT;'       g    .  F<  p\        V\        P                  ! RV	\        V4      4      P                  4       4      pK>  	  \        VP                  VR4      V4      pVV8  g   K  TTTrp
K  	  V
'       d   VR8  d   VR8  d   \        WV4       V
# V
'       d   VR8  d   \         '       d    RV,           R,           V ,           R,           V
,           R,           p\#        V^R7      p\%        V\&        4      '       d/   \)        VP                  R4      4      '       d   \        WV4       V
# \        P	                  RRVRVRV R	VR\+        4       RV/4       V#   \         d     ELi ; i  \         d     LKi ; i)ud  ENTITY RESOLUTION — map an extracted name to ONE canonical :Entity, so
'Al Temimi' / 'Al-Tamimi' / a reversed spelling all land on the same node.

Flow: (1) exact canonical-key OR alias match (any type) → reuse it. (2) else
fuzzy over SAME-etype same-group entities, scored max(name-embedding cosine,
difflib token ratio over name+aliases): ≥0.92 → attach + record the incoming
spelling as an alias; 0.80–0.92 AND a DeepSeek key → ONE yes/no adjudication
→ attach+alias or create; (3) else create a fresh entity (with a name
embedding, so the next lookalike can find it). Returns the canonical key.NthingzeMATCH (e:Entity {group:$grp}) WHERE e.key=$key OR $key IN coalesce(e.aliases,[]) RETURN e.key LIMIT 1r   r  zCALL db.idx.vector.queryNodes('Entity','embedding',$k,vecf32($emb)) YIELD node, score WHERE node.group=$grp AND node.etype=$et RETURN node.key, scorer   r   etzHMATCH (e:Entity {group:$grp, etype:$et}) RETURN e.key, e.name, e.aliasesrt   gq=
ףp?333333?皙?zAre these the same z? A: "z"  B: "z5". Reply ONLY JSON {"same": true} or {"same": false}.)rI   samezMERGE (e:Entity {key:$key, group:$grp}) ON CREATE SET e.name=$name, e.etype=$et, e.created_at=$now, e.aliases=[] SET e.embedding = vecf32($emb)r  r?   )r  rl   r   r   r   r   r;   r,   r   r  difflibSequenceMatcherratior  rk   r  r_   rq   r  r  r  rB   )r  r  r   r  r  r   emb_simro   candsqtokbest_key
best_scorebest_tokckeycnamecaliasestokr   r  rH   r,  s   &&&                  r&   _resolve_entityr    s    t
Cg$$&,,.99'E<<	!&uc :< =GJ 	 Awqz
,CG% "eS%e<	> ?Ij	IA
 !1JGaDMI LL	*,15$+FHHR
 
 $D%)3(H!&X%%dD.$2O ((- 	..b.Ac722dN1-//4uw8C ! T3'-
?-12s(H( "' J$&8s?hU+J$&<<		+e3 7 #$&/02:;>KKF !B7C#t$$cggfo)>)>hU3 LL	6 
UE64u	
E3	(	) JO  <  		s&   A
J4 A<K 4KKKKc               (    V ^8  d   QhRRRRRRRR/# )r!   mem_idr4   r   list | Noner   r"   rJ   r$   )r%   s   "r&   r'   r'   8  s(      S K   r(   c           
        ^ pT;'       g    .  F  p\        T;'       g    / P                  RR4      4      P                  4       p\        V4      ^8  d   KH  \        T;'       g    / P                  RR4      4      P                  4       P	                  4       ;'       g    Rp\        WVV4      pV'       g   K  \        P                  RRV RVRV/4       V^,          pK  	  V# )	z|Resolve each extracted entity to a canonical node and wire
(:Memory)-[:MENTIONS]->(:Entity). Returns how many were attached.r  r   rR   r  zWMATCH (m:Memory {id:$mid}),(e:Entity {key:$key, group:$grp}) MERGE (m)-[:MENTIONS]->(e)r   r  r   )r4   rk   rl   r   r   r  r   r   )r  r   r   r   r   r  r  r  s   &&&     r&   rC  rC  8  s     	
A^^^AGG==,-335t9q=agg2]]6734::<BBD   	d51)FE3u5	7 	
Q  Hr(   
located_at
located_inbased_inbased_atis_atis_inatinlocationlives_inworks_at	works_foremployed_atemployed_byemployee_ofworks_inownsowner_offounded
founder_ofrunsceo_of	client_ofcustomer_of
partner_ofpartners_with
works_withcollaborates_withcolleague_ofpart_of	member_of
belongs_tor   
related_toc                   V ^8  d   QhRR/# r=   r$   )r%   s   "r&   r'   r'   c  s     3 3s 3r(   c                    \         P                  ! R R\        T ;'       g    R4      P                  4       P	                  4       4      P                  R4      p\
        P                  Y;'       g    R4      # )z[\s\-]+_r   r   )r  subr4   rl   r   _RTYPE_CANONrk   )r5  r  s   & r&   _norm_rtyper	  c  sT    
&&S#bhhB-"5"5"7"="="?
@
F
Fs
KCC!1!1	22r(   c          
     ,    V ^8  d   QhRRRRRRRRRR/# )r!   	from_namer4   to_namer1  r   r"   r   r$   )r%   s   "r&   r'   r'   h  s/      S 3 s 3 4 r(   c                    \        V RV4      p\        VRV4      pV'       d   V'       d   WE8X  d   R# \        P                  RRVRVRVR\        V4      R\	        4       /4       R# )	a  (:Entity)-[:REL {rtype, created_at}]->(:Entity). BOTH ends go through the
resolver first, so relations attach to the same canonical nodes as mentions;
rtype is canonicalized so phrasing variants share one edge type.
Self-relations (both names resolving to one entity) are skipped.r  NzMATCH (a:Entity {key:$fk, group:$grp}),(b:Entity {key:$tk, group:$grp}) MERGE (a)-[r:REL {rtype:$rt}]->(b) ON CREATE SET r.created_at=$nowfktkr   r5  r?   )r  r   r   r	  rB   )r  r  r1  r   r  r  s   &&&&  r&   r2  r2  h  s`    
 
GU	3B	'5	1BR28
LL	M	r4UE4U1C	
	r(   c                    V ^8  d   QhRRRR/# )r!   fr  r"   r#   r$   )r%   s   "r&   r'   r'   x  s     4 4 4% 4r(   c           	        ^ RI H pHp V P                  RR4      RV P                  R^ 4      ,          ,           pRpV P                  R4      ;'       g    RP                  4       ;'       g    V P                  R4      ;'       g    RpV'       dj    VP	                  VP
                  4      VP                  V4      ,
          P                  4       R	,          pR
R\        VR4      ) R,          ,          ,          pRV P                  RR4      R,
          ,          pV P                  R4      ;'       g    ^ pR\        P                  ! ^\        \        V4      ^ 4      ,           4      ,          p	V P                  R4      p
V
f   ^MT
p
R\        V
4      ^,
          ,          pW4,           V,           V	,           V,           #   \         d     Li ; i)u  Rank facts by relevance + keyword hits + recency + confidence +
reinforcement + importance — the Generative-Agents recency×importance×
relevance idea, plus a use-based boost. Recency anchors on the EVENT date
(refers_to) when present, else creation time. Nothing is ever dropped.r   r  rt   g333333?keyword_hits	refers_tor   
created_atg     @g      ?g       @g      ^@g?r   rT        ?access_count皙?r   g?)r   r   rk   rl   r?   r@   fromisoformattotal_secondsr  r   r   logrJ   )r  _dt_tzbaserecanchorage_daysr   ac	reinforcer   imp_terms   &           r&   	_saliencer%  x  si   
 :55#na(@!@@D
CeeK &&B--/NNAEE,4G4M4M2F	(3+<+<V+DD'-/G4H#3x#5"5"=>?C !%%d+c12D	
~		#	#!Bdhhq3s2w?233I
%%
C{!Cs3x!|$H:y(833  		s   A(F9 9GGc               0    V ^8  d   QhRRRRRRRRRRR	R/# )
r!   r  r4   include_staler  as_ofrL   	when_fromwhen_tor"   r$   )r%   s   "r&   r'   r'     sA     C C# Cd C: C(C3=CBECr(   c                   . pV'       d8   VP                  RV  RV  R24       VP                  RV  RV  RV  RV  R2	4       MV'       g   VP                  V  R24       V'       g	   V'       dE   R	V  R
V  RV  R2pV'       d   VP                  V R24       V'       d   VP                  V R24       V'       d    RRP                  V4      ,           R,           # R# )ui  WHERE-fragment (leading ' AND …') scoping a node alias by validity + the
temporal window. `as_of` selects bi-temporally (what was true then, tolerant
of old nodes missing valid_from/valid_to via coalesce); otherwise the legacy
`valid` bool. when_from/when_to filter the EVENT date (refers_to else
created_at). All values are parameters — never interpolated.z	coalesce(z.valid_from,z.created_at)<=$as_ofz
(coalesce(z
.valid_to,z!.invalid_at) IS NULL OR coalesce(z.invalid_at)>$as_of)z.valid=truez(CASE WHEN z.refers_to<>'' THEN z.refers_to ELSE z.created_at END)z>=$when_fromz
<=$when_toz AND r  )rD  r'  )r  r'  r(  r)  r*  partsr   s   &&&&&  r&   _search_filterr-    s     Ey|E7:NOPz% 1 'E72FH	I wk*+Gw&:5' B 02LLF8<01LLF8:./49Ggll5))C/BsBr(   z/searchc               <    V ^8  d   QhRRRRRRRRRR	R
RRRRRRR/	# )r!   qr4   r   rJ   r   rL   hopsr'  r  r(  r)  r*  r   r$   )r%   s   "r&   r'   r'     s]     v, v,c v,c v,z v, v,v,.8v,v,1;v, $v,r(   c	                b  a! ^ RI p	\        V4       T;'       g    \        p
\        V 4      p\	        RWEWg4      p\	        RWEWg4      p/ pV'       d   W^R&   V'       d   WnR&   V'       d   W~R&   \
        P                  RV,           R,           R	\        V^4      ^,          ^
,           R
VRV
/VC4      P                  R\        V^4       p. p\        4       pV F  o!VP                  S!^ ,          4       TP                  RS!^ ,          RS!^,          RS!^,          RS!^,          RS!^,          R\        \        S!^,          4      ^4      RS!^,          e   \        S!^,          4      MRRS!^,          e
   S!^,          M^RS!^,          ;'       g    ^ RR/
4       K  	  V	P                  RV P!                  4       4       Uu. uF  pV\"        9  g   K  VNK  	  upR,          pV'       Ed   RV,           R,           RP%                  R \'        \)        V4      4       4       4      ,           R,           R,           pRV
/VC\+        V4       UUu/ uF  w  ppR V 2VbK  	  uppCp\
        P                  VV4      P                   F  o!S!^ ,          V9   d   K  \-        V!3R! lV 4       4      pTP                  RS!^ ,          RS!^,          RS!^,          RS!^,          RS!^,          R\        R"R#V,          ,           ^4      RS!^,          e   \        S!^,          4      MRRS!^,          e
   S!^,          M^RS!^,          ;'       g    ^ R$VRR/4       VP                  S!^ ,          4       K  	  V F  p\/        V4      VR%&   K  	  VP1                  R& R'R(7       V Uu. uF  pVR,          NK  	  ppV'       d$    \
        P                  R)R*VR+\3        4       /4       V^ 8  Ed~   V'       Edu   V Uu/ uF  pVR,          VR%,          bK  	  pp\	        R,WEWg4      p\
        P                  R-V,           R.,           R*V/VC4      P                  p/ pV F\  o!S!^ ,          S!^,          ppVV9   d   K  R/VP7                  VR04      ,          p VV9  g   V VV,          ^ ,          8  g   KU  V S!3VV&   K^  	  VP9                  4        F  w  pw  p o!VP                  V4       TP                  RTRS!^,          RS!^,          RS!^,          RS!^,          RS!^,          e   \        S!^,          4      MRR1S!^,          RR'R\        V ^4      R%V /
4       K  	  VP1                  R2 R'R(7       V F  pVP;                  R%R4       K  	  R3VR\        V^4      ^,            /# u upi u uppi u upi   \4         d     ELi ; iu upi )4u  HYBRID recall: vector similarity (meaning) UNIONed with keyword matches
(names/terms). A long detailed memory has a blurry embedding and can lose a
short vague one on pure vector search, so we also surface any fact whose
text literally contains the query's significant words — that's how "Ziyad"
or "Journey Joy" reliably comes back.

TEMPORAL: `as_of=<ISO>` answers "what was true THEN" (bi-temporal — valid_from
<= as_of AND (valid_to null OR > as_of)); `when_from`/`when_to` window the EVENT
date (refers_to else created_at). Old clients omit all three and get today's
valid facts, exactly as before. Primary hits are REINFORCED (access_count++,
last_accessed=now) and their 1-hop linked context is folded in, scored at
0.6× the parent's salience, capped at k+5 total.Nnodemr(  r)  r*  zkCALL db.idx.vector.queryNodes('Memory','embedding',$k,vecf32($emb)) YIELD node, score WHERE node.group=$grpzRETURN node.id, node.text, node.kind, node.refers_to, node.created_at, score, node.confidence, node.importance, node.access_countr   r   r   r   r   r   r  r  r  r   rT  r   r  r  Fr  r  z#MATCH (m:Memory) WHERE m.group=$grpzAND (r  c              3  ,   "   T F
  pR V 2x  K  	  R# 5ir  r$   r  s   & r&   rx   search.<locals>.<genexpr>  s      %O<Mq.qc2<Mr  z:) RETURN m.id, m.text, m.kind, m.refers_to, m.created_at, z*m.confidence, m.importance, m.access_countr  c              3  v   <"   T F.  qS^,          ;'       g    RP                  4       9   g   K*  ^x  K0  	  R# 5ir  r   Nr   )rv   r  ro   s   & r&   rx   r5    s+     E%Q10B0B0D+Dqq%s   99
9r  r  r  _salc                    V R ,          # r9  r$   r  s   &r&   r  search.<locals>.<lambda>  s    QvYr(   Tr  ztUNWIND $ids AS rid MATCH (m:Memory {id:rid}) SET m.access_count = coalesce(m.access_count,0)+1, m.last_accessed=$nowr   r?   r   z7MATCH (m:Memory)-[l:LINK]-(n:Memory) WHERE m.id IN $idszRRETURN m.id, n.id, n.text, n.kind, n.refers_to, n.created_at, n.confidence, l.typer  rt   rZ  c                    V R ,          # r;  r$   r<  s   &r&   r  r=    s    6r(   facts)r  r   r   r;   r-  r   r   r  r   r  r  rD  r  r,   r#   r  r   r  r'  r  r   r  r   r%  sortrB   r   rk   r  pop)"r/  r   r   r0  r'  r(  r)  r*  r   r  r   r   fnodefmtparamsr  r?  seenr  r  kwr~  r  hitsr  prim_ids	sal_by_idr3  neighbestparentr  r  ro   s"   &&&&&&&&&                        @r&   searchrM    s   " 	-

 
 =C
)C6=LE	]9	FBG ($	 <<	249	:		
 
c!Qi!mb %eS 		  Z
Q,D E5D1dAaD&!A$!!1Q4qtuT!A$Z3"1Q43CE!A$K"AaD,<AaD!$adiia5B 	C  OQWWY?  ?1 Q?   "$Eu2R7 %O<A#e*<M%O OO LL <	<
  ? ?,5e,<=,<DAqqWaZ,<=?b&)44Att|E%EEDLL$!fadFAaD%qt\1Q4!5sTz)91#=&qt7GadT&!0@!a(!A$))!($%A B HHQqTN 5 aL&	 	JJ&J5 "''A$H'	LL').%(LN axHH167AQtWai'	7CyJEJ##%*H$@$@B CM* 	 AA$!CFd{y}}VS11B$"tCy|"3GS	  !JJLLC"aHHSMLL$VQqT61Q4%qt\1Q4&qt7GadT !A$$!5Q<	= > ) 	

*D
9	fd U>C1IM*++G  >, (  		
 8s0   :V
V
VV0"V 'V,V)(V)z	/memoriesc               0    V ^8  d   QhRRRRRRRRRRR	R/# )
r!   r   rL   limitrJ   offsetr'  r  r   r   r$   )r%   s   "r&   r'   r'   %  sA     Q QJ Qc Q Q Q0:Q&Qr(   c                z   \        V4       T ;'       g    \        pV'       d   RMRpV'       d   RMRpRVR\        V^ 4      R\        ^\        V^4      4      /p	V'       d   WIR&   \        P                  RV,           V,           R	,           V	4      P                  p
\        P                  RV,           R
,           RV/4      P                  ^ ,          ^ ,          pRTRV
 Uu. uFN  pRV^ ,          RV^,          RV^,          RV^,          RV^,          RV^,          RV^,          ;'       g    R/NKP  	  up/# u upi )u   List memories newest-first — powers the HUD memory VIEWER (browse &
curate, not search). `kind` filters (e.g. feedback/preference) so the
reflection engine can pull just Ahmed's stated preferences to respect.r   AND m.valid=true zAND m.kind=$kind r   offlimr   $MATCH (m:Memory) WHERE m.group=$grp zxRETURN m.id, m.text, m.kind, m.refers_to, m.created_at, m.source, m.tone ORDER BY m.created_at DESC SKIP $off LIMIT $limzRETURN count(m)totalr   r   r   r  r  r   r   )r   r   r  rU  r   r   r   )r   rO  rP  r'  r   r   r   validkfr  r  rV  ro   s   &&&&&&       r&   r   r   $  sG    
-

 
 =CB%8E $	"BS%VQSCsO,.Fv<<.6;	:	: 		 
	 	
 LL.69JJ	 j$$%'E UJJN)PJNQ 
qtVQqT61Q4ad	qtXqtVQqTZZR	AJN)P Q Q )Ps    AD8
,D8
c                  0    ] tR tRt$ R]R&   RtR]R&   RtR# )Forgeti@  r4   r   NrL   r   r$   r   r   r   r   r   r   r   r$   r(   r&   rZ  rZ  @      GE:r(   rZ  z/forgetc                    V ^8  d   QhRRRR/# )r!   rm   rZ  r   rL   r$   )r%   s   "r&   r'   r'   F  s     	 	 	
 	r(   c                    \        V4       V P                  P                  4       '       g   \        RRR7      h\        P                  RRV P                  P                  4       /4       RR/# )u   Permanently delete a memory (and its links) — Ahmed removed a WRONG fact
from the HUD viewer. Unlike /supersede (which keeps the old node as history),
this is gone for good; use it only for genuinely bad/incorrect memories.rS  zno idr   z)MATCH (m:Memory {id:$id}) DETACH DELETE mr   r   T)r   r   rl   r	   r   r   rY  s   &&r&   forgetr_  E  sS    
 
-77==??G<<
LL<(*$<r(   z/graphc               $    V ^8  d   QhRRRRRR/# )r!   r   rL   r'  r  r   r$   )r%   s   "r&   r'   r'   S  s"     ), ), ),4 ),#),r(   c                   \        V4       T ;'       g    \        pV'       d   RMRp\        P                  RV,           R,           RV/4      P                  p\        P                  RV'       d   RMR,           R,           RV/4      P                  p\        P                  R	RV/4      P                  p\        P                  R
V'       d   RMR,           R,           RV/4      P                  p\        P                  RRV/4      P                  p	V U
u. uF)  p
RV
^ ,          RV
^,          RV
^,          RV
^,          /NK+  	  pp
TV U
u. uF:  p
RRV
^ ,          ,           RV
^,          RRV
^,          ;'       g    R,           RR/NK<  	  up
,          pV U
u. uF*  p
RV
^ ,          RV
^,          RV
^,          ;'       g    R/NK,  	  pp
TV U
u. uF   p
RV
^ ,          RRV
^,          ,           RR/NK"  	  up
,          pTV	 U
u. uF?  p
RRV
^ ,          ,           RRV
^,          ,           RRV
^,          ;'       g    R,           /NKA  	  up
,          pRVRV/# u up
i u up
i u up
i u up
i u up
i )u   The WHOLE memory graph for a group — :Memory nodes + :LINK edges, PLUS
Layer-2 :Entity nodes and (:Memory)-[:MENTIONS]->(:Entity) edges — for the
HUD's graph view. Ahmed walks/zooms this to see what's connected to what.r   rR  rU  z)RETURN m.id, m.text, m.kind, m.created_atr   zJMATCH (a:Memory)-[l:LINK]->(b:Memory) WHERE a.group=$grp AND b.group=$grp z"AND a.valid=true AND b.valid=true zRETURN a.id, b.id, l.typezAMATCH (e:Entity) WHERE e.group=$grp RETURN e.key, e.name, e.etypezMMATCH (m:Memory)-[:MENTIONS]->(e:Entity) WHERE m.group=$grp AND e.group=$grp zRETURN m.id, e.keyzeMATCH (a:Entity)-[r:REL]->(b:Entity) WHERE a.group=$grp AND b.group=$grp RETURN a.key, b.key, r.rtyper   r3   r   r  zent:zentity:r  r  r   rR   r   mentionszrel:nodesedgesr   r   r   r   r   )r   r'  r   r   rW  nrowserowsentrowsmentrowsrelrowsro   rc  rd  s   &&&          r&   graphrk  R  s    
-

 
 =CB%8ELL.63	46;S\CCM: 
 LL	/2$H	J 	$	$ ',S\	3 4>:	 
 llK	 j  ||	/"5	7 		  %cl	, -7J	  ll	').6 7Aj 
  AaD&!A$!lAaDI 
 	 ! Vad]FAaDyAaDOOG4lBH   E  adD!A$!0A0A	B 
 	!! qtT6AaD=&*E! !E	>EG>E v!}dFQqTMv1!2!235>EG GEUGU++ !Gs0   /I
/I:I'I ?I &I%6I*9I*c                  :    ] tR tRt$ R]R&   R]R&   RtR]R&   RtR# )	Unlinki  r4   r   r   NrL   r   r$   r[  r$   r(   r&   rm  rm    s    LGE:r(   rm  z/unlinkc                    V ^8  d   QhRRRR/# )r!   rm   rm  r   rL   r$   )r%   s   "r&   r'   r'            
 r(   c                |    \        V4       \        P                  RRV P                  RV P                  /4       RR/# )u   Cut the connection(s) between two memories (either direction) — Ahmed
disconnected two nodes in the graph view. The nodes stay; only the edge goes.z=MATCH (a:Memory {id:$a})-[l:LINK]-(b:Memory {id:$b}) DELETE lr   r   r   T)r   r   r   r   r   rY  s   &&r&   unlinkrq    s;     
-
LLG	dllC)+ $<r(   z/entityc               $    V ^8  d   QhRRRRRR/# r!   r  r4   r   rL   r   r$   )r%   s   "r&   r'   r'     s"     #- #- #-Z #-$#-r(   c                   \        V4       T;'       g    \        p\        V 4      pV'       g   R. /# \        P	                  RRVRV/4      P
                  p/ pV FZ  w  rxrrrTP                  TRTRTRT	RT
;'       g    RR	T;'       g    RR
. R. /4      pVR
,          P                  RVRVRV/4       K\  	  VP                  4        F  w  r. pR F  w  pp\        P	                  RV,           R,           RVRV/4      P
                   FD  pTP                  RV^ ,          RV^,          ;'       g    RR\        V^,          4      RV/4       KF  	  K  	  VVR&   K  	  R\        VP                  4       4      /# )u   Everything hanging off one entity — the 'pull up Al Temimi' view. Matches
by canonical key with light fuzziness (substring either way).r   a  MATCH (e:Entity)<-[:MENTIONS]-(m:Memory) WHERE e.group=$grp AND m.valid=true AND (e.key=$key OR e.key CONTAINS $key OR $key CONTAINS e.key OR $key IN coalesce(e.aliases,[])) RETURN e.key, e.name, e.etype, e.summary, e.read, m.id, m.text, m.created_at ORDER BY e.name, m.created_atr   r  r  rR   summaryr   rj   r?  r  r   r   r  z%MATCH (a:Entity {key:$k, group:$grp})z*(b:Entity) RETURN b.name, b.etype, r.rtyper   otherotyper  r1  	direction)outz
-[r:REL]->)r  z
<-[r:REL]-)r   r   r  r   r   r   
setdefaultrD  r  r	  rB  values)r  r   r   r   r  r  r  ekeyenamer  ru  ereadr   mtextmcreatedr   r  rx  arrowrrs   &&&                 r&   entityr    s   
 
-

 
 =C
t
CB<<	5 
UC " #-* 	 DDH@UUOOD5$vu#,gmmVU[[b#*BR#A B 	
'
4fe\8LM	 EI ::< MIull7%? C2 2$s+- .8Z8 WbeWbe6F6Fw$k"Q%&8()5 6	8 !N +   T[[]+,,r(   z(?<!\d)(?:\+?966|0)5\d{8}(?!\d)z0[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}z(?:https?://|www\.)[^\s<>\"')]+u   (?:(?:SAR|SR|USD|AED|\$|€|£|﷼|ريال|ر\.?\s?س)\s?\d[\d,]*(?:\.\d+)?)|(?:\d[\d,]*(?:\.\d+)?\s?(?:SAR|SR|USD|AED|riyals?|ريال|درهم|ر\.?\s?س))u   (?<=[.!?؟])\sc                    V ^8  d   QhRRRR/# )r!   r3   r4   r"   r$   )r%   s   "r&   r'   r'     s     D D# D# Dr(   c                    T ;'       g    RP                  4       pV'       d-   \        P                  V^R7      ^ ,          P                  4       # R# )zJFirst sentence of a summary (for a relation's other_summary). "" if empty.r   )maxsplit)rl   _SENT_SPLITr  )r3   rA  s   & r&   _first_sentencer    sA    	A:;;Q+A.446CCr(   c                    V ^8  d   QhRRRR/# )r!   r  r4   r"   r$   )r%   s   "r&   r'   r'     s      3 3 r(   c                    T ;'       g    RP                  R4      pVP                  R4      '       d   RVR,          ,           pV# )zINormalize a matched Saudi mobile to the local 05XXXXXXXX form for dedupe.r   +966r   :   NN)lstrip
startswith)r  ds   & r&   _norm_phoner    s9    	
bA||E!B%KHr(   c                   V ^8  d   QhRR/# )r!   r"   rB  r$   )r%   s   "r&   r'   r'     s     4 4D 4r(   c                L    \        \        P                  R V  4       4      4      # )zOrder-preserving dedupe.c              3  8   "   T F  q'       g   K  Vx  K  	  R # 5ir7   r$   r{   s   & r&   rx   _dedupe.<locals>.<genexpr>  s     2Aaas   	
)rB  r  fromkeys)seqs   &r&   _deduper    s    22233r(   c               $    V ^8  d   QhRRRRRR/# )r!   textsrB  
near_termsr  r"   r  r$   )r%   s   "r&   r'   r'     s&      @  @D  @k  @T  @r(   c           
     J  a . . . . 3w  r#rET;'       g    .  Uu. uF   qf'       g   K  \        V4      ^8  g   K  VNK"  	  upoR V3R llpV  EF  pT;'       g    RpV'       g   K  VP                  4       p	\        P                  V4       FY  p
V! WP	                  4       V
P                  4       4      '       g   K0  VP                  \        V
P                  ^ 4      4      4       K[  	  \        P                  V4       F^  p
V! WP	                  4       V
P                  4       4      '       g   K0  VP                  V
P                  ^ 4      P                  4       4       K`  	  T\        P                  V4       U
u. uF  qP                  R4      NK  	  up
,          pT\        P                  V4       U
u. uF"  p
RP                  V
P                  4       4      NK$  	  up
,          pEK  	  R\!        V4      R\!        V4      R\!        V4      R	\!        V4      /# u upi u up
i u up
i )
u  Regex-harvest contacts/amounts from an entity's fact texts (None-tolerant).

`near_terms` (the entity's name + aliases, lowercased): when given, a PHONE
or EMAIL only counts if it appears within ~100 chars of one of the terms in
that fact — a shared fact mentioning two people must not donate one person's
email to the other's card (Elie's email showed up on Modern Intelligent
Solution's dossier). urls/amounts stay loose — they're rarely personal.c               (    V ^8  d   QhRRRRRRRR/# )r!   s_lowr4   startrJ   endr"   r  r$   )r%   s   "r&   r'   _harvest.<locals>.__annotate__  s(     , ,S , ,3 ,4 ,r(   c                   <a S'       g   R # \        ^ V^d,
          4      V^d,           rCWV o\        ;QJ d    V3R lS 4       F  '       g   K   R # 	  R# ! V3R lS 4       4      # )Tc              3  ,   <"   T F	  qS9   x  K  	  R # 5ir7   r$   )rv   rA  wins   & r&   rx   *_harvest.<locals>._near.<locals>.<genexpr>  s     +U8Us   F)r  any)r  r  r  lohir  termss   &&&  @r&   _near_harvest.<locals>._near  sQ    Q$cCiBrls+U+ss+s+s+U+++r(   r   z	.,;:)]}'"r  phonesemailsurlsamounts)r   r   	_PHONE_REfinditerr  r  rD  r  r   	_EMAIL_RE_URL_REr  rstrip
_AMOUNT_REr'  r  r  )r  r  r  r  r  r  rA  r  r!  r  r3  r  s   &&         @r&   _harvestr    s    %'BN!FD#))r)@)1aQCFaKQQ)@E, , GG	##A&AUGGIquuw//k!''!*56 ' ##A&AUGGIquuw//aggaj..01 ' 	1CD1CA,'1CDD1C1CA1FG1FACHHQWWY'1FGG  gfoxGDM9gg.>@ @- A( EGs   	HHH8H2(H c                   V ^8  d   QhRR/# r=   r$   )r%   s   "r&   r'   r'     s     3 3c 3r(   c                @    V e   \        V 4      P                  4       # R# )zCCoerce any model-supplied scalar to a trimmed string ('' for None).r   )r4   rl   )r:   s   &r&   _svr    s    ]3q6<<>22r(   c               $    V ^8  d   QhRRRRRR/# )r!   cacher  
nfacts_nowrJ   r"   r  r$   )r%   s   "r&   r'   r'     s!     ) )$ )C )D )r(   c                   T ;'       g    / p V P                  R4      '       g   R# V P                  R4      pVe   \        V4      \        V4      8w  d   R# \        V P                  R4      4      pV'       g   R#  \        P                  ! \
        P                  4      \        P                  ! V4      ,
          P                  4       p^ Tu;8*  ;'       d
    \        8  # u #   \         d     R# i ; i)zA stored dossier is usable when it exists, was built for the SAME fact
count (so a new fact invalidates it), and is < 24h old. None-tolerant.ra   Fnfactsr  )rk   rJ   r  r   r?   r   r@   r  r  r   _DOSSIER_MAX_AGE_S)r  r  r   r  ages   &&   r&   _dossier_freshr    s     KKRE99V		(AyCFc*o-	UYYt_	B||HLL)''+,-:]_ 	 (((((((  s   ;AC" "C10C1u0  You are the dossier composer for Ahmed's personal memory. Ahmed Al-Rajeh runs Reval Studio, a Dammam digital agency; he also works at Alrugaib Furniture. Today is {today}.

You are given EVERYTHING the memory graph knows about ONE entity. READ all of it and COMPOSE a clean, organized profile card for Ahmed. NEVER paste raw memory text and NEVER dump checkpoint blobs — REWRITE every line into short, scannable prose or key/value rows. Arabic fragments may stay in Arabic. Invent nothing.

ENTITY: "{name}"  (stored memory type: {etype})

FACTS (newest first) — "date · kind · text":
{facts}

RELATIONS (this entity → other, and how they connect):
{relations}

QUICK CONTACTS harvested from the facts:
{quick}

OPEN TASKS naming this entity:
{tasks}

STEP 1 — Decide what this entity IS TO AHMED: client / outreach partner / employee-intern / colleague / lead / supplier / friend / his own product / project / company / concept — and WHICH context connects them (Reval Studio, Alrugaib, a named campaign, etc.).

STEP 2 — Choose 4-7 sections that FIT THAT relationship. Do NOT force one template:
  - a client -> the deal, what Reval does for them, THEIR clients/leads, money owed/paid;
  - an employee/intern -> role, what they're working on now, performance notes;
  - an outreach partner/lead -> status, last touch, next follow-up, money discussed;
  - a concept/project -> what it is, current status, next steps;
  - a supplier -> what they supply, terms, how to reach them.
Surface MONEY, DATES, PROMISES and FOLLOW-UPS prominently. OMIT any section with nothing real to say. Max 4-7 sections.

STEP 3 — Write a one-line role_line for the header (e.g. "Your outreach client — Modern Intelligent Solution, pays Reval 3,500 SAR") and 1-3 short lowercase badges (e.g. ["client","paying","active"]).

OUTPUT — return ONLY this JSON object, nothing else. Use EXACTLY these four section "type" values; any other type is dropped:
{{"header": {{"role_line": "<one line>", "badges": ["<=3 short tags>"]}},
 "sections": [
   {{"title": "...", "type": "text",  "body": "<clean prose>"}},
   {{"title": "...", "type": "kv",    "rows": [{{"k": "Deal", "v": "3,500 SAR/mo"}}]}},
   {{"title": "...", "type": "chips", "chips": [{{"kind": "phone|email|link|map|whatsapp", "label": "...", "value": "..."}}]}},
   {{"title": "...", "type": "list",  "rows": [{{"icon": "person|company|event|task|money|note", "title": "...", "sub": "<or null>", "date": "<ISO or null>", "entity": "<other entity name to drill into, or null>"}}]}}
 ]}}

CHIP values MUST be actionable:
  phone    -> "tel:+9665XXXXXXXX"
  whatsapp -> "https://wa.me/9665XXXXXXXX"
  email    -> "mailto:name@example.com"
  map      -> "https://maps.apple.com/?q=<url-escaped address or city>"
  link     -> the raw URL
A list row with "entity" set becomes a tap-through to that entity's card — set it for the people/companies connected to this one.

EXAMPLE (shape only — copy the STRUCTURE, never this content):
{{"header": {{"role_line": "Your outreach client — Modern Intelligent Solution, pays Reval 3,500 SAR", "badges": ["client","paying","active"]}},
 "sections": [
   {{"title": "The deal", "type": "kv", "rows": [{{"k": "Pays Reval", "v": "3,500 SAR/mo"}}, {{"k": "Service", "v": "Lead-gen + ads"}}, {{"k": "Since", "v": "2026-05"}}]}},
   {{"title": "Contact", "type": "chips", "chips": [{{"kind": "whatsapp", "label": "WhatsApp Faisal", "value": "https://wa.me/966501234567"}}, {{"kind": "map", "label": "Riyadh office", "value": "https://maps.apple.com/?q=Riyadh"}}]}},
   {{"title": "Their leads", "type": "list", "rows": [{{"icon": "company", "title": "Bin Dawood", "sub": "warm lead handed off", "date": "2026-06-20", "entity": "Bin Dawood"}}]}},
   {{"title": "Follow-ups", "type": "text", "body": "Promised a campaign report by 2026-07-18; Ahmed still owes them the June numbers."}}
 ]}}

JSON:c                    V ^8  d   QhRRRR/# )r!   ctxr  r"   r4   r$   )r%   s   "r&   r'   r'   r  s     = =t = =r(   c           
        V P                  R4      ;'       g    / pV P                  R4      ;'       g    . pV P                  R4      ;'       g    . pV P                  R4      ;'       g    / pV P                  R4      ;'       g    . pRP                  R VR,           4       4      ;'       g    R	pRP                  R
 V 4       4      ;'       g    R	pR Uu. uFP  pVP                  V4      '       g   K  V R2RP                  VP                  V4      ;'       g    . 4      ,           NKR  	  p	pRP                  V	4      ;'       g    R	p
RP                  R V 4       4      ;'       g    R	p\        P                  \	        4       VP                  R4      ;'       g    RVP                  R4      ;'       g    RVWzVR7      # u upi )zRender the composition prompt from a /profile ctx (facts, relations, quick
contacts, tasks). Includes a compact JSON example (in _DOSSIER_PROMPT).chosenr?  r  quicktasksr  c           	   3     "   T F[  pR VP                  R4      ;'       g    R RVP                  R4      ;'       g    R RVP                  R4      ;'       g    R 2x  K]  	  R# 5i)- rE   ?u    · r   r   r   Nrk   )rv   r  s   & r&   rx   (_build_dossier_prompt.<locals>.<genexpr>z  s`      6*4Q aeeFm""s$Dv)@)@&(A55=B
	!*4s   A%A%A%A%:NP   Nr#  c              3  Z  "   T F  pR VP                  R4      ;'       g    R RVP                  R4      R8X  d   RMR RVP                  R4      ;'       g    R	 R
VP                  R4      ;'       g    R R2	VP                  R4      '       d   RVR,           2MR	,           x  K  	  R# 5i)r  r1  r   r  rx  rz  z->z<-rv  r   z (rw  r  r   other_summaryr   Nr  )rv   ro   s   & r&   rx   r  }  s      
 A	 QUU7^((y)55%.4D
955>R
 155>#<#<W"=Q	@ )*o(>(>R/"#$B	H 	H s   B+6B+B+3B+B+r   , z; c              3     "   T FN  pR VP                  R4      ;'       g    R 2VP                  R4      '       d   RVR,           R2MR,           x  KP  	  R# 5i)r  r3   r   duez (due r   Nr  )rv   rA  s   & r&   rx   r    sY       A QUU6]  b!"#$55<<VAeH:QR	9 	9s   AAAr  r   r  r  )r$  r  r  r?  r  r  r  )r  r  r  r  )rk   r'  _DOSSIER_PROMPTr%   rF   )r  r  r?  r  r  r  	facts_txtrel_txtlblqparts	quick_txt	tasks_txts   &           r&   _build_dossier_promptr  r  s    WWX$$"FGGG""E$**IGGG""EGGG""E		 6*/*6 6 B B9A  ii 
   ( (
  (  A!@#3 ;Rj499UYYs^%9%9r:::@  ! 		&!--XI		    $ $ $  !!hVZZ/552jj!,,WI) " = =!s   G5&GGc                   V ^8  d   QhRR/# )r!   r"   rN   r$   )r%   s   "r&   r'   r'     s     G/ G/{ G/r(   c                	   \        V \        4      '       g   R# \        V P                  R4      \        4      '       d   V P                  R4      M/ pVP                  R4      ;'       g    .  Uu. uF!  p\        V4      '       g   K  \        V4      NK#  	  upR,          pR\        VP                  R4      4      RV/p. pV P                  R4      ;'       g    .  EF  p\	        V4      ^8  d    EM\        V\        4      '       g   K/  \        VP                  R4      4      P                  4       pV\        9  d   Kd  \        VP                  R4      4      pVR	8X  d>   \        VP                  R
4      4      p	V	'       d   VP                  RVRR	R
V	/4       K  K  VR8X  d   . p
VP                  R4      ;'       g    .  F  p\	        V
4      ^8  d    Mv\        V\        4      '       g   K-  \        VP                  R4      4      \        VP                  R4      4      rV'       g   V'       g   Kr  V
P                  RVRV/4       K  	  V
'       d   VP                  RVRRRV
/4       EK  EK  VR8X  Ed   . pVP                  R4      ;'       g    .  F  p\	        V4      ^8  d    M\        V\        4      '       g   K-  \        VP                  R4      4      P                  4       p\        VP                  R4      4      pV\        9  g	   V'       g   K  TP                  RTRTR\        VP                  R4      4      ;'       g    T/4       K  	  V'       d   VP                  RVRRRV/4       EK  EK  VR8X  g   EK  . p
VP                  R4      ;'       g    .  EF  p\	        V
4      ^8  d    M\        V\        4      '       g   K.  \        VP                  R4      4      pV'       g   KR  \        VP                  R4      4      P                  4       pT
P                  RV\        9   d   TMRRTR\        VP                  R4      4      ;'       g    RR\        VP                  R4      4      ;'       g    RR\        VP                  R4      4      ;'       g    R/4       EK  	  V
'       g   EK  VP                  RVRRRV
/4       EK  	  V'       g   R# RVRV/# u upi )a  Validate/normalize the model's output to the strict widget schema the
Swift renderer knows. Drops unknown section types + chip kinds, coerces
icons, caps sections=8 / rows=12 / chips=8. Returns None when nothing
usable survives (so the caller falls back to the raw /profile card).Nheaderbadges:Nr  N	role_linesectionsrR   r   r3   rm   r  r  r   vchipsr   valuelabelrB  iconnoter  rE   r  )
r  r  rk   r  r   r   _WIDGET_TYPESrD  _CHIP_KINDS_LIST_ICONS)objhdrr   r  r  rz  sectypr   rm   r  ro   r   r  r  cr   r  rtitler  s   &                   r&   _normalize_dossierr    s   
 c4  )#''(*;TBB#''(
C"wwx066B6B63q6fc!f6B2FF3sww{34hGFC
#))r)s8q=#t$$#''&/"((*m#CGGG$%&=swwv'D

GUFFFDIJ D[Dggfo+++t9?!!T**155:AEE#J1KKaa 01 , 

GUFD&$GH G^Eggg&,,",u:?!!T**155=)//1AEE'N+{*%fdGU%s155>':'C'CeE F - 

GUFGWeLM F]Dggfo+++t9?!!T**QUU7^,155=)//1VT[-@Tf$f"Ce$5$=$=#Sv%7%?%?4%s155?';'C'Ct	E F , t

GUFFFDIJs *t fj#.. Cs   .S&S&c               $    V ^8  d   QhRRRRRR/# )r!   r  r4   r   r"   ztuple[dict, dict]r$   )r%   s   "r&   r'   r'     s'     l l# lC l,= lr(   c                  a;a<a=a>a?a@ \        V 4      o?RRRRRRRR. R. R	. R
. /RR^ R^ RR//pRRR^ R/ /pS?'       g   W#3# \        P                  RRV/4      P                  pV'       g   W#3# R V?3R llp^ R^R^R^R/pRo;\	        V 4      p. pV EF[  p	V	^ ,          V	^,          ;'       g    . rV! W4      p\
        P                  ! RT\	        V	^,          ;'       g    T
4      4      P                  4       pV F<  p\        V\
        P                  ! RV\	        V4      4      P                  4       4      pK>  	  V^8X  d
   VS;8  d   K  TP                  RT
RV	^,          ;'       g    T
RV	^,          ;'       g    RRTRV	^,          ;'       g    RRV	^,          ;'       g    RRV	^,          ;'       g    RR \        V	^,          ;'       g    ^ 4      R!\        Wl,          V4      /	4       EK^  	  V'       g   W#3# VP                  R" R#7       V^ ,          pVR,          o>VR$,           Uu. uF  pVR,          NK  	  pp\	        VR,          4      o=\        VR,          4      o<R% V;V<V=V>3R& llpV Uu. uF  pV! V4      '       g   K  VNK  	  ppV Uu. uF  pVR,          NK  	  pp\        P                  R'R(VRV/4      P                  pV U	u0 uF  q^ ,          kK  	  pp	\        VR,          P                  4       .VR,           Uu. uF  qP                  4       NK  	  up,           V Uu. uF*  pVR,          S>8w  g   K  VR,          P                  4       NK,  	  up,           4      pV Uu. uF  p\        V4      ^8  g   K  VNK  	  upR),          pV'       Edk   R*P!                  R+ \#        \        V4      4       4       4      pRV/\%        V4       UUu/ uF  w  ppR,V 2VbK  	  uppCpV Uu. uF;  p\&        P(                  ! R-\&        P*                  ! V4      ,           R.,           4      NK=  	  pp\        P                  R/V,           R0,           V4      P                   F  p	V	^,          ;'       g    RP                  4       o@V	^ ,          V9  g   K4  \,        ;QJ d    V@3R1 lV 4       F  '       g   K   R2M	  R3M! V@3R1 lV 4       4      '       g   Kv  VP/                  V	^ ,          4       VP                  V	4       K  	  . . pp/ pV F  w  p p!p"p#p$p%T#;'       g    RP1                  4       ;'       g    T$;'       g    Rp&T";'       g    R4p'TP                  R5T R6T&R7T'R4T!;'       g    RR8T%;'       g    R/4       V'\2        9   d2   VP5                  V'. 4      P                  R5T R4T!;'       g    RR6V&/4       V'RV9   g   K  V&'       g   K  VP                  V&4       K  	  VP                  R9 R2R:7       VP7                  4        F  p(V(P                  R; R2R:7       K  	  \9        V U)u. uF  p)V)R4,          NK  	  up)VR,          P                  4       .VR,           Uu. uF  qP                  4       NK  	  up,           R<7      p*V'       d   \        V4      MRp+\        V4      p,\;        V4      p-VR=,          pR> p. \        P                  R?RS>RV/4      P                  p/V/ U	u. uF2  p	V.! V	^ ,          V	^,          V	^,          V	^,          V	^,          4      NK4  	  p0p	V0 U	u0 uF  qRC,          P                  4       kK  	  p3p	 \        P                  RDR(VRV/4      P                   F[  p	V	^ ,          ;'       g    RP                  4       V39  g   K+  V0P                  V.! V	^ ,          V	^,          REV	^,          R@4      4       K]  	  \        VR,          P                  4       .\;        VR,          4      ,           4      p4V4 Uu. uF  p\        V4      ^8  g   K  VNK  	  p4p. p5V4'       d   R*P!                  RF \#        \        V44      4       4       4      pRV/\%        V44       UUu/ uF  w  ppRGV 2VbK  	  uppCp6\        P                  RHV,           RI,           V64      P                   U	u. uF*  p	R5V	^ ,          RJV	^,          RKV	^,          ;'       g    R/NK,  	  p5p	/ p7 \        P                  RLRMS>RV/4      P                  p8V8'       d3   RNV8^ ,          ^ ,          ROV8^ ,          ^,          RPV8^ ,          ^,          /p7RRS>RTR,          RTR,          RTR,          RTR,          /RTR,          RTR,          RT*RRT,R\        T04      RT+//p9T0'       d   T0T9R&   T'       d   TT9RQ&   T'       d   TT9RR&   T5'       d   T5T9RS&   T'       d   TT9RT&   RS>RUTRT-RT,RT0RT*RST5RT7/p:T9T:3# u upi u upi u upi u up	i u upi u upi u upi u uppi u upi u up)i u upi u up	i   \<         d    . p0RW Fv  w  p1p2\        P                  RAT2,           RB,           RS>RT/4      P                   F:  p	T0P                  T.! T	^ ,          T	^,          T	^,          T	^,          T14      4       K<  	  Kx  	   ELi ; iu up	i   \<         d     EL i ; iu upi u uppi u up	i   \<         d     ELi ; i)Xu  Shared aggregation behind BOTH /profile and /dossier. Resolves the entity
(fuzzy-tolerant), then gathers timeline, durable facts, quick contacts,
relations, co-mentions and open tasks into the /profile response dict. Also
returns a `ctx` carrying the chosen key, the UNCAPPED facts, and the entity's
dossier cache props — so /dossier can compose + cache without re-querying.
entity=None (and ctx["ekey"]=None) when nothing resolves.r  Nru  r   rj   r  r  r  r  r  countsr?  r  
last_touchr}  facts_totalr  zMATCH (e:Entity) WHERE e.group=$grp OPTIONAL MATCH (e)<-[:MENTIONS]-(m:Memory {valid:true}) RETURN e.key, e.name, e.etype, e.aliases, e.created_at, e.summary, e.read, count(m) AS mentionsr   c                   V ^8  d   QhRR/# )r!   r"   rJ   r$   )r%   s   "r&   r'   %_gather_profile.<locals>.__annotate__  s      c r(   c                `   < V S8X  d   ^ # ST;'       g    . 9   d   ^# SV 9   g   V S9   d   ^# ^# )    r$   )ckaliasesr  s   &&r&   _tier_gather_profile.<locals>._tier  s1    97==b!"9c	r(   r*   g?rt   g333333?r  r  r  r  r  r  rb  r  c                V    V R ,          ) V R,          ) V R,          P                  4       3# )r  rb  r  r8  )r  s   &r&   r  !_gather_profile.<locals>.<lambda>  s"    qzkAjM>1V9??;LMr(   )r  :r  NNc                   V ^8  d   QhRR/# )r!   r"   r  r$   )r%   s   "r&   r'   r  &  s     P Pt Pr(   c                   < V R ,          pVS8X  g   VS9   g   SV9   d   R# VS9   g   S\        V R,          4      9   d   R# \        P                  ! RS\        V R,          4      4      P	                  4       S8  # )r  Tr  Nr  )r  r  r  r  r  )r  r  RATIO_FLOORchosen_aliases
chosen_tokr}  s   & r&   _variant!_gather_profile.<locals>._variant&  sp    uX:ttrz43q|+<#<&&*nQvY799>KP 	Pr(   zMATCH (m:Memory)-[:MENTIONS]->(e:Entity) WHERE e.group=$grp AND e.key IN $keys AND m.valid=true RETURN DISTINCT m.id, m.text, m.kind, m.refers_to, m.created_at, m.tonekeysr  r  c              3  ,   "   T F
  pR V 2x  K  	  R# 5i)ztoLower(m.text) CONTAINS $sNr$   r  s   & r&   rx   "_gather_profile.<locals>.<genexpr>A  s      =$;q 9<$;r  r!  (?<![a-z0-9@.])(?![a-z0-9@])z:MATCH (m:Memory) WHERE m.group=$grp AND m.valid=true AND (z@) RETURN m.id, m.text, m.kind, m.refers_to, m.created_at, m.tonec              3  D   <"   T F  qP                  S4      x  K  	  R # 5ir7   )rM  )rv   r   lows   & r&   rx   r
  O  s     +I5aHHSMM5s    TFr   r   rE   r   r   c                    V R ,          # rE   r$   r<  s   &r&   r  r   ^  s    &	r(   r  c                    V R ,          # r  r$   r<  s   &r&   r  r   `  s    qyr(   )r  N<   Nc                R    R T RT;'       g    RR\        V4      RVR\        V4      /# )rv  rw  r  r1  rx  r  )r	  r  )nmotr5  summrx  s   &&&&&r&   _rel_gather_profile.<locals>._relk  s4    WbmmGRY9NP 	Pr(   zMATCH (e:Entity {key:$ekey, group:$grp})-[r:REL]-(b:Entity) RETURN b.name, b.etype, r.rtype, b.summary, CASE WHEN startNode(r).key=$ekey THEN 'out' ELSE 'in' END ORDER BY b.namerz  z(MATCH (a:Entity {key:$ekey, group:$grp})z5(b:Entity) RETURN b.name, b.etype, r.rtype, b.summaryrv  ak  MATCH (e:Entity)<-[:MENTIONS]-(m:Memory {valid:true})-[:MENTIONS]->(o:Entity) WHERE e.group=$grp AND e.key IN $keys AND NOT o.key IN $keys AND o.group=$grp AND o.etype <> 'amount' AND NOT (e)-[:NOT_RELATED]-(o) WITH o, count(DISTINCT m) AS n WHERE n >= 2 OR (o.summary IS NOT NULL AND o.summary <> '') RETURN o.name, o.etype, o.summary, n ORDER BY n DESC LIMIT 12zmentioned-withc              3  ,   "   T F
  pR V 2x  K  	  R# 5i)ztoLower(t.text) CONTAINS $tNr$   r  s   & r&   rx   r
    r  r  rA  z6MATCH (t:Task) WHERE t.grp=$grp AND t.done=false AND (z) RETURN t.id, t.text, t.duer3   r  z[MATCH (e:Entity {key:$k, group:$grp}) RETURN e.dossier_json, e.dossier_at, e.dossier_nfactsr   ra   r  r  timelinefacts_by_kindr  also_matchedr  )event
checkpointry  )r  r   r   r   r  r  r  r  r  rD  rJ   r@  r  r  r   r   r'  r  r  r  compileescaper  r  rl   _DURABLE_KINDSr{  r|  r  rB  r   )Ar  r   empty	empty_ctx	cand_rowsr  _TIER_SCOREr  r  ro   r  r  tierr  r   r  r  r  r  variantsall_keysfrowsseen_idssweep_termsrA  condr~  sparamsboundr  event_datesr  r   r  mkindr  r  r   rE   r   lstr  r  r  r  	facts_allr  rrowsr  rx  r  have_relr  r  rD  r  crowrespr  r  r  r  r}  r  r  sA   &&                                                         @@@@@@r&   _gather_profiler8    s
    t
CtYFBxXr62y"MKL"EGE }a"=I 	' */	6 7Aj	 
   c1c1dAs3KK$DEdQqTZZRGR!''$qtzzr2449EG 	Aw66dN1- //4uw8E  19,eR1Wadoog,!

1VQqTZZR #adiia.s;#4e<	> 	?   
JJMJN1XF%=D',Ry1y!AfIIyL1  v/J	*+NP P !05aHQK5H0"*+(Q%(H+
 LL	R 
5#&	( )3
	 

 $$e!eH$				 vi7H#I7H!GGI7H#II&.
Ch!E(d2B1V9??h
C	DEK *9kSVq[11k9"=K{{{ =$)#k*:$;= =#Q;9O!P9OAAaS'1*9O!PQ &'%Q .1=@PPQ% 	 'H##$+- .8Z8A Q4::2$$&Ct8#+I5+I+I5+I(I(IQqT"Q8 kHM:?6UE9j$R&&(>>Z-=-=2sFD&$"fdjjbB 	C>!$$T2.55sFEKKR>@**ttt$ ;@ MM)4M8##%($7 &2Aaii2!'!5!5!7 8+1)+<=+<a	+<=!>?E &1[!bJe*KXI}HPJ T5#&(
 )3
 	 BGGAT!A$!adAaD!A$7	G -66Iq'
  "IH6' 5#.
0 1;

;A !

!!#83  adAaD2BAaD%!PQ
;" VF^))+,tF94E/FFGE-1Q1QQE-E{{ 7$)#e*$57 7#K59I!J9IAAaS'1*9I!JK ,,M<= )j)*)Q !fadE1Q4<<4@) 	 * E	||D$s#% &0Z 	 T!WQZtAwqztAwqz+E 	5$vF7OYy8I!57 	6)$ffVngu7Kc)n-D %[#Z -_W+^467I+{IwE7E+C 9W 2& 1+ % $J
C9 "Q
'2 3=& H J	 MIu\\:UB F= =T5#.0 1;
;   adAaD!A$!i!HI	; !NJ 7  
 .
 "K*"  s   +g/3g4g4g9g>
h
.h
h
/hhh5Ah0h&h"(h, ,8h'$h, *j<6k k 4k k"k/k6'kk++k# 2k# 'h, ,B	j98j9kk#k21k2z/profilec               $    V ^8  d   QhRRRRRR/# rs  r$   )r%   s   "r&   r'   r'     s"      # j %r(   c                    \        V4       T;'       g    \        p\        W4      w  rE\        VP	                  R4      ;'       g    / VP	                  R^ 4      4      VR&   V# )u  FULL dossier of one entity for a HUD card — the FAST quick-render the HUD
depends on, so this response SHAPE is stable. Resolves the name fuzzily
(exact key → alias → substring → difflib token-sort ratio; a full-name ratio
≥0.85 beats a short substring hit), picks the best match (rest → also_matched),
and returns {entity, summary, read, quick, relations, timeline, facts_by_kind,
tasks, counts, also_matched}. Every section is None-tolerant and empty optional
sections are omitted, so any etype renders gracefully. `has_composed` (NEW,
additive) is true when a fresh LLM-composed dossier is cached and instantly
available from POST /dossier.r  r  has_composed)r   r   r8  r  rk   )r  r   r   r   r7  r  s   &&&   r&   profiler<    s[     
-

 
 =C*ID)#'''*:*@*@b*-''-*CEDKr(   c                  >    ] tR tRt$ R]R&   RtR]R&   RtR]R	&   R
tR# )Dossieri  r4   r  NrL   r   Fr   forcer$   )r   r   r   r   r   r   r?  r   r$   r(   r&   r>  r>    s    
IE:E;r(   r>  z/dossierc                    V ^8  d   QhRRRR/# )r!   rm   r>  r   rL   r$   )r%   s   "r&   r'   r'     s     .3 .3' .3* .3r(   c                   \        V4       V P                  ;'       g    \        p\        V P                  V4      w  r4VP                  R4      pV'       g   RRRR/# \        VP                  R^ 4      4      pVP                  R4      ;'       g    / pV P                  '       g4   \        Wv4      '       d#    R\        P                  ! VR,          4      RR	/#  \        \        V4      R
\        RR7      p\        V4      p	T	f   RRRR/#  \(        P+                  RRTRTR\        P,                  ! T	4      R\/        4       RT/4       RT	RR/#   \         d     Li ; i  \         dK   p
\!        RT: R\#        T
4      P$                   R\'        T
4      R,           2R	R7       RRRR/u Rp
?
# Rp
?
ii ; i  \         d     L|i ; i)u  COMPOSED dossier — an LLM READS the raw /profile aggregate and returns an
organized, ADAPTIVE profile card {header{role_line,badges}, sections[]} whose
sections fit what the entity IS TO AHMED (client vs intern vs concept …). The
four widget types are text / kv / chips / list (validated hard in code).

CACHE: the composed JSON is stored on the entity node (dossier_json/at/nfacts).
A cache hit — SAME fact count, < 24h old, and `force` not set — returns
instantly {composed, cached:true}. Otherwise ONE DeepSeek call (DOSSIER_MODEL,
temp 0.3) composes it; ANY LLM failure (or no key) returns {composed:null} so
the caller falls back to the raw /profile card. On success the result is
validated, cached, and returned {composed, cached:false}.r}  composedNcachedFr  r  ra   Ti  g333333?)rI   rK   rM   zdossier compose FAILED for r   :N   Nr   zbMATCH (e:Entity {key:$k, group:$grp}) SET e.dossier_json=$j, e.dossier_at=$at, e.dossier_nfacts=$nr   r   jr  r   )r   r   r   r8  r  rk   rJ   r?  r  ra   ri   r   rq   r  r   r  r   rR   r   r4   r   r   rb   rB   )rm   r   r   _respr  r}  r  r  r,  rB  r   s   &&         r&   dossierrG    s    
-
**
%
%C C0JE776?DD(E22*+FGGG""E:::.77	

5= 98TJJ3237D#0cC%c*
 D(E22K$sCH)=8:sF,	- (E22-  		  3+D82a!!""SVD\N4;?	AD(E223  sB   7 E 'E <F6 EEF3)?F.(F3.F36GGc                  L    ] tR tRt$ R]R&   RtR]R&   RtR]R&   RtR]R&   R	tR# )
EntityUpdatei  r4   r  NrL   r   r  r  r$   )	r   r   r   r   r   r   r  r  r   r$   r(   r&   rI  rI    s%    	HE:E:D*r(   rI  z/entity/updatec                    V ^8  d   QhRRRR/# )r!   rm   rI  r   rL   r$   )r%   s   "r&   r'   r'     s     %1 %1 %1!+%1r(   c           	        \        V4       V P                  ;'       g    \        p\        V P                  4      pV'       g   \        RRR7      h\        P                  RRVRV/4      P                  pV'       g   \        RRR7      h. RVRV/reV P                  e   V P                  ;'       g    R	P                  4       P                  4       pV\        9  d1   \        RR
RP                  \        \        4      4      ,           R7      hVP                  R4       WvR&   V P                   eO   V P                   P                  4       '       d/   VP                  R4       V P                   P                  4       VR&   V'       d-   \        P                  RRP                  V4      ,           V4       \        P                  RRVRV/4      P                  pRRRTRV'       d   V^ ,          ^ ,          MR	RV'       d   V^ ,          ^,          /# R	/# )a	  Correct one entity's TYPE or DISPLAY NAME (e.g. a person mis-typed as a
thing). Aliases + canonical key are untouched, so all its facts/relations stay
attached. `etype` must be one of person/company/place/product/amount/thing/
concept. Nothing else is mutable here.rS  zkey requiredr   z<MATCH (e:Entity {key:$k, group:$grp}) RETURN e.name, e.etyper   r     entity not foundr   zetype must be one of r  ze.etype=$etr  z
e.name=$nmr  z*MATCH (e:Entity {key:$k, group:$grp}) SET r   Tr  r  r  )r   r   r   r  r  r	   r   r   r   r  rl   r   ETYPESr'  r  rD  r  )	rm   r   r   r  r  setsr  r  rz  s	   &&       r&   entity_updaterP    s    
-
**
%
%C
txx
 CNCC<<F	c5#  *
 	 4FGGS%-&zzjjB%%'--/V.6&>1JJL L 	M"tyy!2!2L!yy(t8499T?J	 ,,F	c5#  *
  $s#SVAY23c!fQi1 1,.1 1r(   c                  :    ] tR tRt$ R]R&   R]R&   RtR]R&   RtR# )	SummaryiF  r4   r  r3   NrL   r   r$   r[  r$   r(   r&   rR  rR  F  s    
I
IE:r(   rR  z/summaryc                    V ^8  d   QhRRRR/# r!   rm   rR  r   rL   r$   )r%   s   "r&   r'   r'   M  s      g j r(   c                   \        V4       V P                  ;'       g    \        p\        V P                  4      pV'       d!   V P
                  P                  4       '       g   \        RRR7      h\        P                  RRVRVRV P                  P                  4       RV P
                  P                  4       R	\        4       /4       R
R/# )u   Upsert a current-state SUMMARY onto an entity — the reflection engine's
rolling 'profile' so 'pull up Al Temimi' returns the gist, not just raw facts.rS  name and text requiredr   zMERGE (e:Entity {key:$key, group:$grp}) ON CREATE SET e.name=$name, e.etype='thing', e.created_at=$now SET e.summary=$text, e.summary_at=$nowr  r   r  r3   r?   r   Tr   r   r   r  r  r3   rl   r	   r   r   rB   rm   r   r   r  s   &&  r&   summary_setrY  L  s     
-
**
%
%C
tyy
!Cdiioo''4LMM
LL	1 
UC):	"E8:	7	8 $<r(   z/readc                    V ^8  d   QhRRRR/# rT  r$   )r%   s   "r&   r'   r'   _  s      7 : r(   c                   \        V4       V P                  ;'       g    \        p\        V P                  4      pV'       d!   V P
                  P                  4       '       g   \        RRR7      h\        P                  RRVRVRV P                  P                  4       RV P
                  P                  4       R	\        4       /4       R
R/# )u   THEORY OF MIND — upsert a psychological 'read' onto a person/company:
what they seem to want, how they decide, the state of the relationship, and
how Ahmed should approach them. Models THEM, for Ahmed. (Reuses the Summary
shape: {name, text}.)rS  rV  r   zMERGE (e:Entity {key:$key, group:$grp}) ON CREATE SET e.name=$name, e.etype='thing', e.created_at=$now SET e.read=$text, e.read_at=$nowr  r   r  r3   r?   r   TrW  rX  s   &&  r&   read_setr\  ^  s     
-
**
%
%C
tyy
!Cdiioo''4LMM
LL	+ 
UC):	"E8:	7	8 $<r(   c                  0    ] tR tRt$ R]R&   RtR]R&   RtR# )Valuesir  r4   r3   NrL   r   r$   r[  r$   r(   r&   r^  r^  r      
IE:r(   r^  z/valuesc                    V ^8  d   QhRRRR/# )r!   rm   r^  r   rL   r$   )r%   s   "r&   r'   r'   x  s      V J r(   c           
     &   \        V4       V P                  ;'       g    \        pV P                  P	                  4       '       g   \        RRR7      h\        P                  RRVRV P                  P	                  4       R\        4       /4       RR	/# )
u   VALUE MODEL — upsert Ahmed's north-star: his priorities, what he chases,
what he avoids. A singleton profile the reflection engine and the brain
consult so advice fits HIM, not generic best-practice.rS  ztext requiredr   zOMERGE (p:Profile {id:'values', group:$grp}) SET p.text=$text, p.updated_at=$nowr   r3   r?   r   T)	r   r   r   r3   rl   r	   r   r   rB   rm   r   r   s   && r&   
values_setrc  w  sv    
 
-
**
%
%C99??ODD
LL	.	VTYY__.xzBD $<r(   c                    V ^8  d   QhRRRR/# r!   r   rL   r   r$   )r%   s   "r&   r'   r'     s     	F 	Fj 	F(	Fr(   c                   \        V4       T ;'       g    \        p\        P                  R RV/4      P                  pV'       g   RRRR/# RV^ ,          ^ ,          ;'       g    RRV^ ,          ^,          ;'       g    R/# )zGMATCH (p:Profile {id:'values', group:$grp}) RETURN p.text, p.updated_atr   r3   r   
updated_atre  r   r   r   r  s   &&  r&   
values_getri    s|     
-

 
 =C<<Q	 j 	 L"--DGAJ$$"lDGAJ4D4D"EEr(   c                  0    ] tR tRt$ R]R&   RtR]R&   RtR# )Retirei  r4   r   NrL   r   r$   r[  r$   r(   r&   rk  rk    r\  r(   rk  z/retirec                    V ^8  d   QhRRRR/# )r!   rm   rk  r   rL   r$   )r%   s   "r&   r'   r'     ro  r(   c                    \        V4       \        P                  RRV P                  P	                  4       R\        4       /4       RR/# )u   Soft-retire a memory — mark it stale (hidden from recall) but KEEP it as
history, unlike /forget which deletes. Used by the consolidation pass to fold
away near-duplicate facts without losing anything.z>MATCH (m:Memory {id:$id}) SET m.valid=false, m.invalid_at=$nowr   r?   r   T)r   r   r   r   rl   rB   rY  s   &&r&   retirern    s=    
 
-
LLQ
;=$<r(   c                  :    ] tR tRt$ R]R&   R]R&   RtR]R&   R	tR# )
AttachEntitiesi  r4   	memory_idr   r   NrL   r   r$   r[  r$   r(   r&   rp  rp    s    NE:r(   rp  z/attach_entitiesc                    V ^8  d   QhRRRR/# )r!   rm   rp  r   rL   r$   )r%   s   "r&   r'   r'     s      ^ &0r(   c                    \        V4       V P                  ;'       g    \        p\        V P                  V P
                  V4      pRV/# )u   Backfill hook — attach extracted entities to an EXISTING memory (used by
the engine-side entity backfill over the memories saved before Layer 2).attached)r   r   r   rC  rq  r   )rm   r   r   r   s   &&  r&   attach_entities_epru    s=    
 
-
**
%
%C<A?r(   c                  Z    ] tR tRt$ ]! RR7      tR]R&   R]R&   RtR]R	&   R
tR]R&   Rt	R
# )Relationi  r  )r  r4   from_er   r   rL   r1  Nr   r$   )
r   r   r   r   r   rx  r   r1  r   r   r$   r(   r&   rw  rw    s*    f%FC%G!E:!E:r(   rw  z	/relationc                    V ^8  d   QhRRRR/# )r!   rm   rw  r   rL   r$   )r%   s   "r&   r'   r'     s      x  *r(   c                `   \        V4       V P                  ;'       g    \        pV P                  ;'       g    RP	                  4       pV P
                  ;'       g    RP	                  4       pV'       d	   V'       g   \        RRR7      h\        Y4V P                  ;'       g    RV4       RR/# )u   Wire an entity↔entity edge (:Entity)-[:REL {rtype}]->(:Entity). Both the
`from` and `to` names run through the resolver, so the edge attaches to the
same canonical nodes that mentions do.r   rS  zfrom and to requiredr   r   r   T)	r   r   r   rx  rl   r   r	   r2  r1  )rm   r   r   r3  r4  s   &&   r&   relation_addr{    s     
-
**
%
%C
++

	"	"	$B
''--R			 BR4JKK"$**11	37$<r(   c               (    V ^8  d   QhRRRRRRRR/# )r!   keepr4   mkr   r"   r  r$   )r%   s   "r&   r'   r'     s(     : :S :c :# :$ :r(   c                   V'       d   W8X  d   R# \         P                  RRV RV/4      P                  p\         P                  RRVRV/4      P                  pV'       d	   V'       g   R# \         P                  RRVRV RV/4       \        4       p\         P                  RRVRV/4      P                  pV FD  w  rxp	WpV39   d   K  \         P                  R	RT R
TRTRT;'       g    RRT	;'       g    T/4       KF  	  \         P                  RRVRV/4      P                  p
V
 FD  w  rxp	WpV39   d   K  \         P                  RRT R
TRTRT;'       g    RRT	;'       g    T/4       KF  	  V^ ,          ^ ,          ;'       g    . pV^ ,          ^ ,          ;'       g    . p\        P                  \        V4      \        V4      ,           V.,           4       Uu. uF  qV 8w  g   K  VNK  	  ppV^ ,          ^,          ;'       g    RV^ ,          ^,          ;'       g    RppV^ ,          ^,          ;'       g    RV^ ,          ^,          ;'       g    Rpp\        V4      \        V4      8  d   TMTp\        V4      \        V4      8  d   TMTp\         P                  RRV RVRVRVRV/4       \         P                  RRVRV/4       R# u upi )u   Fold entity `mk` into `keep`: re-point MENTIONS + REL edges, union aliases,
keep the richer (longer) summary/read, then delete `mk`. No APOC — REL edges
are collected in Python and recreated so their rtype/created_at survive.FzGMATCH (e:Entity {key:$k, group:$g}) RETURN e.aliases, e.summary, e.readr   r   zMATCH (m:Memory)-[r:MENTIONS]->(:Entity {key:$mk, group:$g}) MATCH (k:Entity {key:$keep, group:$g}) MERGE (m)-[:MENTIONS]->(k) DELETE rr~  r}  z[MATCH (:Entity {key:$mk, group:$g})-[r:REL]->(b:Entity) RETURN b.key, r.rtype, r.created_atzMATCH (k:Entity {key:$keep, group:$g}),(b:Entity {key:$bk, group:$g}) MERGE (k)-[nr:REL {rtype:$rt}]->(b) ON CREATE SET nr.created_at=$catbkr5  r   catz[MATCH (b:Entity)-[r:REL]->(:Entity {key:$mk, group:$g}) RETURN b.key, r.rtype, r.created_atzMATCH (b:Entity {key:$bk, group:$g}),(k:Entity {key:$keep, group:$g}) MERGE (b)-[nr:REL {rtype:$rt}]->(k) ON CREATE SET nr.created_at=$catr   zTMATCH (k:Entity {key:$keep, group:$g}) SET k.aliases=$al, k.summary=$sum, k.read=$rdr  r   rdz4MATCH (e:Entity {key:$mk, group:$g}) DETACH DELETE eT)r   r   r   rB   r  r  rB  r   )r}  r~  r   krowmrowr?   outsbkeyr5  r  inskalmalr   unionksumkreadmsummreadru  rj   s   &&&                  r&   
_merge_oner    s    <<	.03T3/FHHR
 	 <<	.03Re/DFFPj 	 t
LL	. 
r64e,	. *C<<	.04b#u/EGGQz 	 #":ST4sE4yCJJ3 	!  ,,	.04b#u/EGGQz  #":ST4sE4yCJJ3 	!  q'!*

C
q'!*

Cd3i$s)&;rd&BCQC1DyQQCEQq'!*""DGAJ$4$4"%Dq'!*""DGAJ$4$4"%D$i3t9,d$GJ#e*,5%D
LL	8	sE4wdKM LLGC') Rs   K&Kc                  :    ] tR tRt$ R]R&   R]R&   RtR]R&   RtR# )	EntityUnrelatei	  r4   r   r   NrL   r   r$   r[  r$   r(   r&   r  r  	  s    
F
FE:r(   r  c                  :    ] tR tRt$ R]R&   R]R&   RtR]R&   R	tR# )
EntityAliasesi  r4   r  rB  r  NrL   r   r$   r[  r$   r(   r&   r  r    s    	HME:r(   r  z/entity/aliasesc                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'     s     4 4 4",4r(   c           	        \        V4       V P                  ;'       g    \        p\        V P                  4      p\        V P                  ;'       g    .  Uu. uF3  p\        V4      '       g   K  \        V4      V8w  g   K(  \        V4      NK5  	  up4      p\        P                  RRVRVRV/4      P                  pV'       g   \        RRR7      hRR	R
VRV/# u upi )zREPAIR/curation: REPLACE an entity's alias list outright (dedup, lowered,
self-name removed). Used to strip pollution after the corrupt-vector alias
auto-attach incident; aliases regrow organically through resolution.zDMATCH (e:Entity {key:$k, group:$grp}) SET e.aliases=$al RETURN e.keyr   r   r  rL  rM  r   r   Tr  r  )r   r   r   r  r  r  r  r   r   r   r	   )rm   r   r   r   r   cleanress   &&     r&   entity_aliasesr    s     
-
**
%
%CTXXAt||/A/Ar/A B/A$Q %,8Oq,@ %\!_/A B CE
,,	aT59;;E:  4FGG$q)U33Bs   C0CCc                  0    ] tR tRt$ R]R&   RtR]R&   RtR# )EntityDetachi(  r4   r  NrL   r   r$   r[  r$   r(   r&   r  r  (  s    	HE:r(   r  z/entity/detachc                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   .  s     . . .!+.r(   c           	       a \        V4       V P                  ;'       g    \        p\        V P                  4      p\
        P                  RRVRV/4      P                  pV'       g   RRR^ /# V^ ,          ^,          ;'       g    TP                  4       pV^ ,          ^,          ;'       g    .  Uu. uF  qfP                  4       NK  	  pp\        V.V,           4       Uu. uF  p\        V4      ^8  g   K  VNK  	  p	pV	 Uu. uF;  p\        P                  ! R\        P                  ! V4      ,           R,           4      NK=  	  p
pV Uau. uFN  o\        ;QJ d    V3R	 lV
 4       F  '       g   K   RM	  R
M! V3R	 lV
 4       4      '       d   KE  S^ ,          NKP  	  ppV'       d   \
        P                  RRVRVRV/4       RRR\        V4      /# u upi u upi u upi u upi )zREPAIR: remove MENTIONS edges from an entity to memories whose text does
NOT actually contain the entity's name or an alias (word-boundary). Used to
undo bad merges that hijacked another entity's facts. Returns the count.zeMATCH (e:Entity {key:$k, group:$grp})<-[r:MENTIONS]-(m:Memory) RETURN m.id, m.text, e.name, e.aliasesr   r   r   Tdetachedr  r  c              3     <"   T F4  qP                  S^,          ;'       g    RP                  4       4      x  K6  	  R# 5ir7  )rM  r   )rv   r   ro   s   & r&   rx    entity_detach.<locals>.<genexpr>A  s.     Dehh!

11344es   ??FzcUNWIND $ids AS mid MATCH (e:Entity {key:$k, group:$grp})<-[r:MENTIONS]-(m:Memory {id:mid}) DELETE rr   )r   r   r   r  r  r   r   r   r   r  r   r  r   r!  r  )rm   r   r   r   r  r  r   r  rA  r  r/  ro   bads   &&         ` r&   entity_detachr  -  s    
-
**
%
%CTXXA<<	1365#2FHHR
 	 dJ**GAJOO!""$D#'71:#3#3#35#3Qwwy#3G5 01A11SVq[QQ1EA ZZ*RYYq\9<LLM 
  FA#DeD###DeDD 1Q44C F
:Ca,	. $
CH-- 6AFs1   2G!G8GAGG$G$3G$G$z/entity/unrelatec                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   K  s     * *. *#-*r(   c                  a \        V4       V P                  ;'       g    \        o\        V P                  4      \        V P
                  4      r2V'       d   V'       d   W#8X  d   \        RRR7      hV3R lpV! V4      V! V4      reV'       d   V'       d   WV8X  d   \        RRR7      h\        P                  RRVR	VR
S/4       \        P                  RRVR	VR
SR\        4       /4       RRRVR	V/# )u  HUMAN-CONFIRMED edge removal (the dossier card's 'Not related' action).
Deletes every REL edge between the two entities and marks the pair
NOT_RELATED so co-mention grouping never reconnects them. Deliberately a
human-only action — no LLM is allowed to cut edges autonomously.rS  ztwo distinct names neededr   c                   < \         P                  R RSRV /4      P                  pV'       d   V^ ,          ^ ,          # R# )zMATCH (e:Entity) WHERE e.group=$grp AND (e.key=$k OR $k IN coalesce(e.aliases,[]) OR e.key CONTAINS $k) RETURN e.key ORDER BY size(e.key) LIMIT 1r   r   N)r   r   r   )r   r  r   s   & r&   _resolve!entity_unrelate.<locals>._resolveW  sE    ||8 Ca 	" #-*	 	
 "tAwqz+t+r(   rL  rM  zVMATCH (a:Entity {key:$a, group:$grp})-[r:REL]-(b:Entity {key:$b, group:$grp}) DELETE rr   r   r   zMATCH (a:Entity {key:$a, group:$grp}),(b:Entity {key:$b, group:$grp}) MERGE (a)-[x:NOT_RELATED]->(b) ON CREATE SET x.created_at=$nowr?   r   T)
r   r   r   r  r   r   r	   r   r   rB   )rm   r   kakbr  rarbr   s   &&     @r&   entity_unrelater  J  s     
-
**
%
%C$&&!<#7R284OPP, b\8B<R284FGG
LL	 "%r3E3!?A LL	I	b#r5#uhj9; $Rb))r(   c                  :    ] tR tRt$ R]R&   R]R&   RtR]R&   R	tR# )
EntityMergeik  r4   keep_keyr   
merge_keysNrL   r   r$   r[  r$   r(   r&   r  r  k  s    ME:r(   r  z/entities/mergec                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   r  s     8 8 8",8r(   c                r   \        V4       V P                  ;'       g    \        p\        V P                  4      pV'       g   \        RRR7      h. pV P                  ;'       g    .  FE  p\        V4      pV'       g   K  Wc8w  g   K   \        W6V4      '       g   K4  VP                  V4       KG  	  RRRVRV/# )u   Manually fold one or more duplicate entities into a canonical one — moves
every MENTIONS + REL edge, unions aliases, and keeps the richer profile.rS  zkeep_key requiredr   r   Tr}  merged)	r   r   r   r  r  r	   r  r  rD  )rm   r   r   r}  r  r   r~  s   &&     r&   entities_merger  q  s    
 
-
**
%
%C&D4GHHF%%2%#2"*Dc!:!:MM" & $h77r(   c                  4    ] tR tRt$ RtR]R&   RtR]R&   RtR# )	DedupBackfilli  Tr  r  NrL   r   r$   )r   r   r   r   r  r   r   r   r$   r(   r&   r  r    s    GTE:r(   r  z/entities/dedup_backfillc                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'     s#     @H @H- @H+5@Hr(   c                   \        V4       V P                  ;'       g    \        p\        P	                  RRV/4      P
                  pV Uu. uFN  pRV^ ,          RV^,          ;'       g
    V^ ,          RV^,          ;'       g    RRV^,          ;'       g    . /NKP  	  ppV Uu/ uF  qfR,          \        VR,          4      bK  	  pp/ p\        P	                  RRV/4      P
                   F(  p\        V^,          ;'       g    ^ 4      W^ ,          &   K*  	  / p	V F+  pV	P                  VR,          . 4      P                  V4       K-  	  . p
V	P                  4        EF  w  r\        \        V4      4       EF  p\        V^,           \        V4      4       EF{  pW,          W,          pp\        WR,          ,          VVR,          ,          4      p\        VR,          4      p\        VR,          4      p\        P                   ! R	VV4      P#                  4       p\%        VR,          4      \%        VR,          4      ,            Fj  p\'        V\        P                   ! R	V\        V4      4      P#                  4       \        P                   ! R	V\        V4      4      P#                  4       4      pKl  	  \'        VV4      pVR
8  g   EK2  V
P                  VR,          VR,          \)        \+        V4      ^4      \)        \+        V4      ^4      34       EK~  	  EK  	  EK  	  V
P-                  R RR7       V P.                  '       d5   RRR\        V
4      RV
 UUUUu. uF  w  ppppRVRVRVRV/NK  	  upppp/# \1        4       p. . ppV
 F  w  ppppVV9   g   VV9   d   K  VR8  dy   VR8  dr   VP3                  V^ 4      VP3                  V^ 4      8  d   VV3MVV3w  pp\5        VVV4      '       d+   VP7                  V4       VP                  RVRVRV/4       K  K  VP                  RVRVRVRV/4       K  	  RRRVRV/# u upi u upi u uppppi )u9  Scan same-type entities for lookalike pairs (max of name-embedding cosine
and difflib token ratio). `dry_run` returns every candidate pair (≥0.80) with
its score; a real run AUTO-MERGES the confident ones (≥0.95, keeping whichever
has more mentions) and returns the 0.80–0.95 band as `ambiguous` for review.zFMATCH (e:Entity {group:$grp}) RETURN e.key, e.name, e.etype, e.aliasesr   r  r  r  r  r  zXMATCH (e:Entity {group:$grp}) OPTIONAL MATCH (e)<-[r:MENTIONS]-() RETURN e.key, count(r)Nr  c                    V ^,          # )r!   r$   )r  s   &r&   r  )entities_dedup_backfill.<locals>.<lambda>  s    QqTr(   Tr  r  
candidatespairsr   r   r  r  r&  gffffff?r}  r  F	ambiguous)r   r   r   r   r   r   r;   rJ   r{  rD  r  r  r   r   r  r  r  r  rB  r  r  r#   r@  r  r  rk   r  r  )rm   r   r   r  ro   r  r   embsmcby_typer  r  
group_entsr~  rE  r   r   r  atokbtokr  r  r  r!  rA  goner  r  r}  drops   &&                            r&   entities_dedup_backfillr    s    
-
**
%
%C<<P	 j 	 .23-1 AaD&!A$,,!A$!A$//'!

$-1 	 3156AeHfQvY''D6B\\	%',cl44>J? qtyyq>Q4? G1W:r*11!4 E$]]_s:'A1q5#j/2!}jm1!$x.$qx.A%ai0%ai0--dD$?EEGq|,tAiL/AAABc7#:#:dN2$6$88=// $r(:<<AEGEC B
 #&:LL!E(AeHeE"Iq6I"'c
A"6"8 9 3 ( -" 
JJ>4J0|||4s5z,13,1jaAq 37Aua@,134 	4 DBIF
1a9T	9c#%66!Q<266!Q<#?!QaVJD$$c**vtXtWaHI + c1c1gq%CD  uhYGGk36>3s$   Q	+Q	Q	Q	&"QQc                  L    ] tR tRt$ R]R&   RtR]R&   RtR]R&   R	tR]R
&   RtR	# )Insighti  r4   r3   r   rL   r   observationitypeNr   r$   )	r   r   r   r   r   r   r  r   r   r$   r(   r&   r  r    s%    
IE:%E:%E:r(   r  z/insightc                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'     s     ( (g (j (r(   c                   \        V4       V P                  P                  4       pV'       g   \        R RR7      hV P                  ;'       g    \
        p\        V4      p \        P                  RRVRVRR\        ,
          /4      P                  pV'       d%   \        V^ ,          ^ ,          4      ^ 8  d   RR	R
R/# \        P                  ! 4       P                  p\        P                  RRTRTRT P                   ;'       g    RRT P"                  ;'       g    RRTR\%        4       RT/4       RTRR/#   \         d     Li ; i)rS  zempty insightr   zCALL db.idx.vector.queryNodes('Insight','embedding',3,vecf32($e)) YIELD node, score WHERE node.group=$grp AND score<$thr RETURN count(node)r   r   thrr*   createdFreason	duplicatezCREATE (i:Insight {id:$id, text:$text, title:$title, itype:$itype, group:$grp, created_at:$now, surfaced:false}) SET i.embedding = vecf32($emb)r   r3   r   r   r  r  r?   r   T)r   r3   rl   r	   r   r   r;   r   r   r.   r   rJ   r   r   r   r   r   r  rB   )rm   r   r3   r   r   dupiids   &&     r&   insight_addr    s1   	-99??DODD
**
%
%C
,C	ll!#&UC#(#*=#?@ AK
	 	
 3s1vay>A%uh<< **,

C
LL	) 
sFD'4::+;+;	4::..	
E3	(	) #y$''  s   !AD7 7EEz	/insightsTc               (    V ^8  d   QhRRRRRRRR/# )r!   r   rL   unsurfaced_onlyr  rO  rJ   r   r$   )r%   s   "r&   r'   r'     s0     M M MT MM!+Mr(   c                h   \        V4       T ;'       g    \        pV'       d   R MRp\        P                  RV,           R,           RVR\	        ^\        V^d4      4      /4      P                  pRV Uu. uF2  pRV^ ,          RV^,          R	V^,          R
V^,          RV^,          /NK4  	  up/# u upi )zAND i.surfaced=false r   z%MATCH (i:Insight) WHERE i.group=$grp zYRETURN i.id, i.text, i.title, i.itype, i.created_at ORDER BY i.created_at DESC LIMIT $limr   rT  insightsr   r3   r   r  r  )r   r   r   r   r  rU  r   )r   r  rO  r   r   r-  r  ro   s   &&&&    r&   insights_listr    s     
-

 
 =C&5"2D<</$6	0	0 
UC3uc?34	6 7Aj	 	
 FJLFJ !fadGQqT!1Q4qt=FJL M M Ls   38B/z/insights/searchc               (    V ^8  d   QhRRRRRRRR/# )r!   r/  r4   r   rL   r   rJ   r   r$   )r%   s   "r&   r'   r'   	  s)     )) ))s )): )) ))#-))r(   c                   ^ RI p\        V4       T;'       g    \        p\        V 4      p\	        4       p. p \
        P                  RR\        V^4      ^,          ^,           RVRV/4      P                   F|  p	V	^ ,          V9  g   K  VP                  V	^ ,          4       VP                  RV	^ ,          RV	^,          RV	^,          R	V	^,          R
\        \        V	^,          4      ^4      /4       K~  	  VP                  RV P                  4       4       U
u. uF  p
V
\         9  g   K  V
NK  	  up
R,          pV'       d   RRP#                  R \%        \'        V4      4       4       4      ,           R,           pRV/\)        V4       UU
u/ uF  w  rRV 2V
bK  	  up
pCp \
        P                  W4      P                   Fb  p	V	^ ,          V9  g   K  VP                  V	^ ,          4       VP                  RV	^ ,          RV	^,          RV	^,          R	V	^,          R
R/4       Kd  	  RVR\        V^4       /#   \         d     ELPi ; iu up
i u up
pi   \         d     L;i ; i)u   Unsurfaced insights RELEVANT to a query — used to raise a reflection
contextually (only when Ahmed asks about something related), never on a timer.NzCALL db.idx.vector.queryNodes('Insight','embedding',$k,vecf32($emb)) YIELD node, score WHERE node.group=$grp AND node.surfaced=false AND score < 0.5 RETURN node.id, node.text, node.title, node.itype, scorer   r   r   r   r3   r   r  r  r  r  z?MATCH (i:Insight) WHERE i.group=$grp AND i.surfaced=false AND (r  c              3  ,   "   T F
  pR V 2x  K  	  R# 5i)ztoLower(i.text) CONTAINS $wNr$   )rv   r   s   & r&   rx   "insights_search.<locals>.<genexpr>%	  r  r  z') RETURN i.id, i.text, i.title, i.ityper  r  r  )r  r   r   r;   r  r   r   r  r   r  rD  r  r,   r   r  r   r  r'  r  r   r  )r/  r   r   r   r  r   r   rE  rz  ro   r  r  r  r   r  s   &&&&           r&   insights_searchr  	  sI   
 	-

 
 =C
)CDC s1ay1}q0%&	'
 (2z2A t41

D!A$!gqt#QqT7E$qt*a4HJ K2 OQWWY?  ?1 Q?   "$EP{{ 7$)#e*$57 77 99 J	%8H I8H1QC!8H IJ	\\#.99Q4t#HHQqTNJJadFAaD'1Q4 '1w = > : JSAY(('    !J  		sD   AH8 	A-H8 I
*I
I"-I AI 8III#"I#c                  0    ] tR tRt$ R]R&   RtR]R&   RtR# )InsightSeeni4	  r4   r   NrL   r   r$   r[  r$   r(   r&   r  r  4	  r\  r(   r  z/insight/seenc                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   :	  s      {  *r(   c                d    \        V4       \        P                  R RV P                  /4       RR/# )z.MATCH (i:Insight {id:$id}) SET i.surfaced=truer   r   Tr   r   r   r   rY  s   &&r&   insight_seenr  9	  s0     
-
LLA"$<r(   c                  P    ] tR tRt$ RtR]R&   RtR]R&   RtR]R	&   RtR
]R&   Rt	R# )BackfilliB	  Nr   r  r   r  Fr  r  rL   r   r$   )
r   r   r   r   r  r   r  r  r   r   r$   r(   r&   r  r  B	  s*    "I|" Iz GTE:r(   r  z/backfill_linksc                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   J	  s     K K K",Kr(   c                
   \        V4       V P                  ;'       g    \        pV P                  e   V P                  M\        pV P
                  ;'       g    \        p\        P                  RRV/4      P                  p\        4       p. pV F  w  r\        T	;'       g    R4      p
\        Y;'       g    RWW4V P                  R7       F  p\        WR,          34      pW9   d   K  VP                  V4       \!        V4      ^8  g   KA  TP#                  RT	;'       g    RR,          RVR	,          ;'       g    RR,          R
VR
,          /4       K  	  K  	  R\!        V4      R\!        V4      RVRV P                  RV/# )u   LAYER 1 backfill — run auto-linking over EVERY existing memory once, so
today's loose cloud gets wired up. `dry_run` reports what it WOULD link (with
samples) without touching the graph, so the threshold can be tuned first.zHMATCH (m:Memory) WHERE m.group=$grp AND m.valid=true RETURN m.id, m.textr   r   )r  r   r   r  r   r3   r  rc  linked_pairsr  r  samples)r   r   r   r  r-   r  r/   r   r   r   r  r;   rE  r  	frozensetr  r   rD  )rm   r   r   r  mxrc  r  r  r  ntextr   rL  pairs   &&           r&   backfill_linksr  I	  sO    
-
**
%
%C NN6$..<NC		'	'<BLL	 %s|--7Z 
 EG
U[[b!S++2s%)\\3Bcd8_-D}IIdO7|b ekkr3%7 #bj&6&6B%< 'G 6 73  SZUiy'K Kr(   c                  &    ] tR tRt$ ^tR]R&   RtR# )Reembedik	  r   batchr$   N)r   r   r   r   r  r   r   r$   r(   r&   r  r  k	  s    E:r(   r  z/reembedc                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   p	  s     7" 7"' 7"* 7"r(   c                P   \        V4       RpV F"  w  r4 \        P                  RV RV R24       K$  	  \	        ^\        \        V P                  ;'       g    ^4      R4      4      p/ pR F  p\        P                  RV R24      P                  p^ p\        ^ \        V4      V4       FZ  p	WyW,             FI  w  rV
'       g   K  \        P                  RV R2R	T
R
\        T;'       g    R4      /4       V^,          pKK  	  K\  	  WV&   K  	  \        P                  R4      P                  p^ pV FH  w  rpV'       g   K  \        P                  RRTRTR
\        T;'       g    T4      /4       V^,          pKJ  	  WR&   V F)  w  r4 \        P                  RV RV R\         R24       K+  	  RRR\         R\        RV/#   \         d     EK  i ; i  \         dC   pR\        T4      P                  4       9  d   \        RT RT 2RR7        Rp?K   Rp?K  Rp?ii ; i) u*  Re-embed EVERY Memory + Insight + Entity with the CURRENT model and
recreate the vector indexes at the current dim (drop+create, so a dim change
from an EMBED_MODEL swap is handled). Idempotent — re-running just recomputes
the same vectors. Run once on deploy day after switching the embed model.EntityzDROP VECTOR INDEX FOR (n:z) (n.r   i  z	MATCH (n:z) RETURN n.id, n.textz' {id:$id}) SET n.embedding=vecf32($emb)r   r   r   z.MATCH (e:Entity) RETURN e.key, e.group, e.namez@MATCH (e:Entity {key:$k, group:$g}) SET e.embedding=vecf32($emb)r   r   zCREATE VECTOR INDEX FOR (n:z) ON (n.z) OPTIONS {dimension: r   r   z
[reembed] z index note: Tr   Nr   rK   r   
reembedded))Memory	embedding)r  r  )r  r  )r  r  )r   r   r   r   r  rU  rJ   r  r   r  r   r;   r   r4   r   r   r   )rm   r   labelsr  propr  r  r  r  r~  ridtxtrg  r}  egr~  r   s   &&               r&   reembedr  o	  sA    
-'F
 	LL4UG5aHI 
 3s4::,,-t45EF&||w3466@j 	q#d)U+A 19-w&OP3vciiR'89; Q . , u ' LL8::D* 
	A %N$Ru}}(=>	@ 	
Q ! 8 	HLL-eWHTF C((1{2RTU  $eY&" "Q  		J  	HA.
5'qc:$GG /	Hs)   G#GGGH%#1H  H%z/exportc                    V ^8  d   QhRRRR/# re  r$   )r%   s   "r&   r'   r'   	  s     5< 5<* 5<$5<r(   c                   \        V4       T ;'       g    \        p\        P                  RRV/4      P                   Uu. uF  p/ RV^ ,          bRV^,          bRV^,          bRV^,          bRV^,          bRV^,          bR	V^,          bR
V^,          bRV^,          bRV^	,          bRV^
,          bRV^,          bRV^,          bRV^,          bRV^,          bRV^,          ;'       g    ^ bNK  	  pp\        P                  RRV/4      P                   Uu. uFb  pRV^ ,          RV^,          RV^,          RV^,          ;'       g    . RV^,          ;'       g    RRV^,          ;'       g    RRV^,          /NKd  	  pp\        P                  RRV/4      P                   Uu. uF   pRV^ ,          RV^,          RV^,          /NK"  	  pp\        P                  RRV/4      P                   Uu. uF  pR V^ ,          R!V^,          /NK  	  pp\        P                  R"RV/4      P                   Uu. uF)  pRV^ ,          RV^,          R#V^,          RV^,          /NK+  	  pp\        P                  R$RV/4      P                   Uu. uF;  pRV^ ,          RV^,          R%V^,          R&V^,          RV^,          R'V^,          /NK=  	  p	p\        P                  R(RV/4      P                   Uu. uF;  pRV^ ,          RV^,          R)V^,          R*V^,          RV^,          R+V^,          /NK=  	  p
p\        P                  R,RV/4      P                   Uu. uF   pRV^ ,          RV^,          R-V^,          /NK"  	  pp\        P                  R.RV/4      P                   Uu. uF  pRV^ ,          R-V^,          /NK  	  pp\        P                  R/RV/4      P                   Uu. uF)  pRV^ ,          R0V^,          R1V^,          R2V^,          /NK+  	  ppR3VR4\        4       R5VR6VR7VR8VR9VR:V	R;V
R<VR=VR>V/# u upi u upi u upi u upi u upi u upi u upi u upi u upi u upi )?u   COMPLETE dump of a group for nightly backups — memories (INCLUDING stale),
entities, links, mentions, relations, insights, tasks, profile, core, meta.zMATCH (m:Memory {group:$grp}) RETURN m.id, m.text, m.kind, m.source, m.created_at, m.refers_to, m.valid, m.valid_from, m.valid_to, m.invalid_at, m.superseded_by, m.confidence, m.importance, m.srel, m.tone, m.access_countr   r   r3   r   r   r  r  rW  
valid_fromvalid_to
invalid_atr   r   r   r   r   r  zgMATCH (e:Entity {group:$grp}) RETURN e.key, e.name, e.etype, e.aliases, e.summary, e.read, e.created_atr  r  r  r  ru  r   rj   zYMATCH (a:Memory {group:$grp})-[l:LINK]->(b:Memory {group:$grp}) RETURN a.id, b.id, l.typer  r   rR   zUMATCH (m:Memory {group:$grp})-[:MENTIONS]->(e:Entity {group:$grp}) RETURN m.id, e.keymemoryr  ziMATCH (a:Entity {group:$grp})-[r:REL]->(b:Entity {group:$grp}) RETURN a.key, b.key, r.rtype, r.created_atr1  z^MATCH (i:Insight {group:$grp}) RETURN i.id, i.text, i.title, i.itype, i.created_at, i.surfacedr   r  surfacedzUMATCH (t:Task {grp:$grp}) RETURN t.id, t.text, t.due, t.done, t.created_at, t.done_atr  donedone_atz@MATCH (p:Profile {group:$grp}) RETURN p.id, p.text, p.updated_atrg  z7MATCH (c:Core {group:$grp}) RETURN c.text, c.updated_atzEMATCH (x:Meta {group:$grp}) RETURN x.id, x.last_run, x.status, x.notelast_runstatusr  r   exported_atr   r   r   rb  r  r  r  r<  coremeta)r   r   r   r   r   rB   )r   r   r   ro   r   r   r   rb  r  r  r  r<  r	  r
  s   &&            r&   exportr  	  s   
 
-

 
 =C  	! $)#,	0 1;
	;	<
;AKqt KVQqT K61Q4 K1Q4 KqtK%0!A$K8?1KqtK%/1K7CQqTK !!B%K *6quK ?KAbEK 2	K !'"	K 0>quzz	K
;  	<  	*,13<99CDEDA !fadGQqT9adjjbAaDJJB!

L!A$PD  E
 @F||	$&+S\@33=:@> ? @>!fadD!A$!5 @>E ? ;A,,	$cl;,,6J;7 8 ;7Q1Q41Q40 ;7H 8 !,,	57<clDDNJOPOQ !A$adGQqT<1NO  P
 AG	#%*CLA22<*A=>A=1 qtVQqT7AaD'1Q4qtZ17A=  >
 =CLL	"$)3<=11;=<==<q AaD&!A$qtVQqTAaD)QqT3=< 
 =
 J	 j!"!1 adFAaD,!=!  " 9?A	9 j9! " 9!1VQqT<1. 9!D " \\O	 j!"! 1Q4QqT8QqT61Q4H! 	 " S-Z'5*hJ'5wfd< <Y	<E
?8P>="""sW    B,P=-P=0QQQ2Q)&Q5Q8/QAQ4AQ&Q 'Q%*/Q*c                  B    ] tR tRt$ RtR]R&   RtR]R&   RtR]R&   R	tR# )
ReflectionRani	  r   rL   r  r   r  Nr   r$   )	r   r   r   r   r  r   r  r   r   r$   r(   r&   r  r  	  s     FJD*E:r(   r  z/reflection_ranc                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   	  s     ) ) )",)r(   c                    \        V4       V P                  ;'       g    \        p\        4       p\        P                  RRTRTRV P                  ;'       g    RRV P                  ;'       g    R/4       RRR	V/# )
zThe reflection engine calls this after each run so /health can report when
it last executed (stored on a :Meta {id:'reflection'} singleton per group).z\MERGE (x:Meta {id:'reflection', group:$grp}) SET x.last_run=$now, x.status=$st, x.note=$noter   r?   str   r  r   Tr  )r   r   r   rB   r   r   r  r  )rm   r   r   r?   s   &&  r&   reflection_ranr  	  ss    
 
-
**
%
%C
*C
LL	:	UCt{{':':d	b	"#
 $
C((r(   c                  0    ] tR tRt$ R]R&   RtR]R&   RtR# )Corei	  r4   r3   NrL   r   r$   r[  r$   r(   r&   r  r  	  r_  r(   r  z/core_blockc                    V ^8  d   QhRRRR/# re  r$   )r%   s   "r&   r'   r'   	  s     F Fj F(Fr(   c                   \        V4       T ;'       g    \        p\        P                  RRV/4      P                  pV'       g   RRRR/# RV^ ,          ^ ,          ;'       g    RRV^ ,          ^,          ;'       g    R/# )u   Ahmed's always-on core context block (a :Core singleton per group) — the
identity/preamble every brain loads before recalling anything else.zBMATCH (c:Core {id:'core', group:$grp}) RETURN c.text, c.updated_atr   r3   r   rg  re  rh  s   &&  r&   
core_blockr  	  s|    
 
-

 
 =C<<L	 j 	 L"--DGAJ$$"lDGAJ4D4D"EEr(   z/corec                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   
  s      4 
 r(   c           
         \        V4       V P                  ;'       g    \        p\        P	                  RRTRV P
                  ;'       g    RR\        4       /4       RR/# )z.Upsert Ahmed's core context block (singleton).zJMERGE (c:Core {id:'core', group:$grp}) SET c.text=$text, c.updated_at=$nowr   r3   r   r?   r   T)r   r   r   r   r   r3   rB   rb  s   && r&   core_setr  
  sW     
-
**
%
%C
LL	.	VTYY__"eXZ@B $<r(   c                  L    ] tR tRt$ R]R&   RtR]R&   RtR]R&   RtR]R	&   R
tR# )Taski
  r4   r3   NrL   r  r   r   r   r$   )	r   r   r   r   r   r  r   r   r   r$   r(   r&   r  r  
  s%    
ICE:!FJ!r(   r  c                  0    ] tR tRt$ R]R&   RtR]R&   RtR# )TaskIdi$
  r4   r   NrL   r   r$   r[  r$   r(   r&   r  r  $
  r\  r(   r  z/taskc                    V ^8  d   QhRRRR/# )r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   *
  s      4 
 r(   c                   \        V4       V P                  P                  4       '       g   \        R RR7      h\        P
                  ! 4       P                  p\        P                  RRTRV P                  P                  4       RV P                  ;'       g    RRV P                  ;'       g    R	R
V P                  ;'       g    \        R\        4       /4       RV/# )rS  z
empty taskr   znCREATE (t:Task {id:$id, text:$text, due:$due, done:false, source:$src, grp:$grp, created_at:$now, done_at:''})r   r3   r  r   srcr   r   r?   )r   r3   rl   r	   r   r   r   r   r   r  r   r   r   rB   )rm   r   r  s   && r&   task_addr!  )
  s    	-99??LAA
**,

C
LL	?	sFDIIOO-udhhnn"	''x

0K0Km	
	 #;r(   z/tasksc               $    V ^8  d   QhRRRRRR/# )r!   r   rL   include_doner  r   r$   )r%   s   "r&   r'   r'   9
  s'     @ @Z @d @'@r(   c                   \        V4       T ;'       g    \        p\        P                  R V'       d   RMR,           R,           RV/4      P                  pRV Uu. uFX  pRV^ ,          RV^,          RV^,          ;'       g    R	R
\        V^,          4      RV^,          RV^,          ;'       g    R	/NKZ  	  up/# u upi )z MATCH (t:Task) WHERE t.grp=$grp r   zAND t.done=false z`RETURN t.id, t.text, t.due, t.done, t.created_at, t.done_at ORDER BY t.done, t.due, t.created_atr   r  r   r3   r  Nr  r  r  )r   r   r   r   r   r  )r   r#  r   r   r  ro   s   &&&   r&   	task_listr%  8
  s     
-

 
 =C<<*2#6	8	/	/ 

 !j 	 9=?9=A QqT61Q4!tAaDz<1 !A$,,$09=? @ @ ?s   'B=-B=1B=z
/task/donec                    V ^8  d   QhRRRR/# r!   rm   r  r   rL   r$   )r%   s   "r&   r'   r'   I
  s      F : r(   c                x    \        V4       \        P                  R RV P                  R\	        4       /4       RR/# )z7MATCH (t:Task {id:$id}) SET t.done=true, t.done_at=$nowr   r?   r   T)r   r   r   r   rB   rY  s   &&r&   	task_doner)  H
  s5    	-
LLJ
35$<r(   z/task/reopenc                    V ^8  d   QhRRRR/# r'  r$   )r%   s   "r&   r'   r'   Q
  s      f Z r(   c                d    \        V4       \        P                  R RV P                  /4       RR/# )z6MATCH (t:Task {id:$id}) SET t.done=false, t.done_at=''r   r   Tr  rY  s   &&r&   task_reopenr,  P
  s.    	-
LLI"$<r(   z/task/deletec                    V ^8  d   QhRRRR/# r'  r$   )r%   s   "r&   r'   r'   Y
  s      f Z r(   c                d    \        V4       \        P                  R RV P                  /4       RR/# )z MATCH (t:Task {id:$id}) DELETE tr   r   Tr  rY  s   &&r&   task_deleter/  X
  s+    	-
LL3dDGG_E$<r(   z/reminders/duec                    V ^8  d   QhRRRR/# re  r$   )r%   s   "r&   r'   r'   `
  s     L L L!+Lr(   c                L   \        V4       T ;'       g    \        p\        4       p\        P	                  RRVRV/4      P
                  pV F"  p\        P	                  RRV^ ,          /4       K$  	  RV Uu. uF   pRV^ ,          RV^,          RV^,          /NK"  	  up/# u upi )uq   Tasks whose reminder time has passed and aren't done/fired — the
scheduler polls this to know what to announce.zMATCH (t:Task) WHERE t.grp=$grp AND t.done=false AND t.due<>'' AND t.due<=$now AND coalesce(t.fired,false)=false RETURN t.id, t.text, t.duer   r?   z(MATCH (t:Task {id:$id}) SET t.fired=truer   r  r3   )r   r   rB   r   r   r   )r   r   r   r?   r  ro   s   &&    r&   reminders_duer2  _
  s    
 
-

 
 =C
*C<<	%',c5#&>@ AK
 	
 ?$!N TJTT1Q41uad;TJKKJs   7&B!)i   Nr  )NrT  N   Nr7   )r  )F)NNF>   placer  amountpersoncompanyconceptproduct)
preferencer-  pattern	synthesis
convention>   r  rB  r3   r  >   maprZ  emailphonewhatsapp>   r  taskr  moneyr6  r7  iQ )__conditional_annotations____doc__
__future__r   r  ra   r   r   r  urllib.requestrd   r   
contextlibr   r   r   fastapir   r   r	   pydanticr
   r   falkordbr   	fastembedr   r   r   rk   r   r   r   r   rJ   r   r_   r  rg   r   r   r   r,   r#   r-   r.   r/   r   r1   r   r;   rB   rF   rq   r   r   r   r   mountr   r   r   r   r   r   r   r(  r  r  r.  r6  rO  postrV  rZ  r]  r  r  r  rE  r  r  r  r  rC  r  r	  r2  r%  r-  rM  r   rZ  r_  rk  rm  rq  r  rN  r"  r   r  r  Ir  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r8  r<  r>  rG  rI  rP  rR  rY  r\  r^  rc  ri  rk  rn  rp  ru  rw  r{  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r!  r%  r)  r,  r/  r2  )rD  s   @r&   <module>rP     s  JV #    	 	   * ' 2 2 %  # 
**..)2
.

~w7ZZ^^NH5
 jjnnPR

{E23	 zz~~0"5	(B	C	J	J3	Ojjnn],?@ 

0CD
 ::>>*c*c1! 2::>>*>GH  bjjnn_f=>2::>>.#67	"&	 &=29 8 4Pn   Oi8 
   N
$y $"!9 !		 	 - -0 2/ /.	DK K=48	CQ CQL +9?9M 8 86 '171E   ,;A$;O ) )*	M 	M 	M 	M 	M 	M' 	M6 	M5 	M		M	M	M%	M'-	M/4	M6;	M=C	MEJ	M		M	M	M%	M',	M.3	M5:	M<A	MCI	M 
	M 	M  	M "(	M *0	M 2<	M >E	M GL	M 
		M 		M 		M  #		M %(		M *.		M 04		M 6:		M <@		M BF		M HL		MC CZ2
7&FR0, ,l<Fl,3\CJ  '+L ;E	 l	
 

 (
 6C z ,9* 
 F  )26 <H
 F %f  ,[ , !0 , !4\ L y &y 3?	 y '	&3
 42C2 Da %4"&d&,T&:v, v,r !%Ba#(T)/)=Q Q6Y 
 )5;D5I 	 	 "%&,T&:), ),XY  )5;D5I   *.'-d';#- #-^ 
Q N JJ9:	JJJK	
**7
>ZZYDD

 jj*+D4
 @  @L 0;E 3
)(F	R=>G/Tl^ +/(.t(< &i  *7=d7K .3 .3b9  
.4T.B%1 %1Pi  *;A$;O  " '8>t8L   &Y 
 )9?9M    #'+1$+?	F 	F 	FY 
 )5;D5I   Y  
39$3G  y  +-3D-A  :zY I  
/5d/C4 4 4$9 
 
.4T.B. . .8 
06t0D* * *@)  
/5d/C8 8 8"I 
 
$%8>t8L@H @H &@HPi  *;A$;O ( ( (< &*D!.4T.BM M M 	04q06t0D)) )) ))X) 
 /-3D-A  y  
/5d/CK K KBi  *7=d7K 7" 7" 7"t #'-d';5< 5< 5<pI  
/5d/C) ) )9 
 #'+1$+?F F F '5;D5I   "9 "Y 
 '5;D5I    "&U*0*>@ @ @ ,8>t8L    .:@:N    .:@:N    	&*.4T.BL L Lr(   