
    6*Qj                        d Z ddlmZ ddlZddlZddlZdZdZdZ	dZ
eded	e	d
e
d	iZddZddZddZddZ	 	 	 d	 	 	 	 	 	 	 ddZddZddZddZddZddZddZy)u  Structured phone-power storage on top of the memory service.

Phase-2 "Jarvis runs my phone" needs a handful of small, structured, mutable
collections — saved *places*, a *contacts* phonebook, an *SMS watch-list*, and
*routines* — that must survive restarts. Rather than stand up a new database we
persist each item as a **typed memory record in a DEDICATED group namespace**
(``PHONE_STORE_GROUP``, default ``"<MEMORY_GROUP>_phone"``). That gives three
things for free:

  * durability — the memory service is FalkorDB-backed, so records outlive any
    gateway restart or redeploy;
  * isolation — a separate ``group`` means these machine records never pollute
    Ahmed's semantic long-term memory (``memory_search`` / the persona memory
    index both read the ``ahmed`` group, not ``ahmed_phone``);
  * deterministic CRUD — ``GET /memories?group=&kind=`` lists newest-first and
    filters by an exact ``kind``, ``POST /remember`` creates, ``POST /forget``
    deletes by id. No semantic ranking is ever involved, so listing/removing an
    item is exact, not fuzzy.

One record per item. ``kind`` names the collection; the item's fields live as a
compact JSON object in the record ``text``. Numbers are treated as sensitive —
this module never logs a record's contents.

All HTTP funnels through :func:`_mem_request` so tests monkeypatch exactly one
function and never touch the network. This module deliberately does NOT import
``tools`` (which imports us), keeping its own tiny HTTP helper to avoid a
circular import.
    )annotationsNphone_placephone_contactphone_sms_watchphone_routinelabelnamesenderc                 `    t         j                  j                  dd      j                  d      S )NMEMORY_API_URL /)osenvirongetrstrip     A/Users/ahmed/devFolder/Ultron/claude-voice/gateway/phone_store.py_mem_urlr   4   s#    ::>>*B/66s;;r   c                 B    t         j                  j                  dd      S )NMEMORY_API_KEYr   r   r   r   r   r   r   _mem_keyr   8   s    ::>>*B//r   c                 B    t         j                  j                  dd      S )NMEMORY_GROUPahmedr   r   r   r   _base_groupr   <   s    ::>>.'22r   c                 ^    t         j                  j                  d      xs t               dz   S )zThe dedicated namespace for phone-power records (kept OFF the semantic
    memory group so these never surface in recall or the persona index).PHONE_STORE_GROUP_phone)r   r   r   r   r   r   r   phone_groupr"   @   s$     ::>>-.L;=83KLr   c                  K   ddl }t               }|st        d      ||z   }|r%|dt        j                  j                  |      z   z  }ddi}t               }	|	rd|	 |d<   |j                  |	      4 d{   }
|
j                  | ||||nd
       d{   }|j                          |j                  }|rt        j                  |      ni cddd      d{    S 7 k7 L7 	# 1 d{  7  sw Y   yxY ww)z@Call the memory service. Returns parsed JSON ({} on empty body).r   NzMEMORY_API_URL not set?zContent-Typezapplication/jsonzBearer Authorization)timeout)headersjson)httpxr   RuntimeErrorurllibparse	urlencoder   AsyncClientrequestraise_for_statuscontentr(   loads)methodpathparamsbodyr&   r)   baseurlr'   keyclientrespraws                r   _mem_requestr=   G   s     
 :D344
+CsV\\++F33312G
*C
%,SE?    11V^^FC151At $ M Mll"%tzz#2 211M 2111sZ   A6C?8C$9C?<C*C&8C*C?C(C?&C*(C?*C<0C31C<8C?c                X    | xs dj                         }|dk(  r|S |j                         S )zGCase/space-insensitive key for matching. ``"*"`` is preserved verbatim.r   *)striplower)valuevs     r   _normrD   `   s+    	"AS1'aggi'r   c                  K   t        ddt               | dd       d{   }g }|j                  dg       D ]v  }|j                  d      xs |j                  d	      xs d
}	 t        j                  |      }t        |t              sR|j                  d      |d<   |j                  |       x |S 7 # t
        t        f$ r Y w xY ww)zEvery item in a collection, newest-first. Each item is the stored JSON
    payload plus an ``_id`` (the memory-record id, used for deletes).GETz	/memories   )groupkindlimit)r5   Nmemoriesfacttextr   id_id)
r=   r"   r   r(   r2   
ValueError	TypeError
isinstancedictappend)rI   outitemsmr<   payloads         r   
list_itemsrY   g   s      UK.9mT.1%34 4C EWWZ$eeFm2quuV}2	jjoG gt$UU4[GENLL! % L4 I& 		s:   CB4AC%B6:C*C6CCCCc           	        K   t        |      }|syt        |    }t        |        d{   D ]-  }t        t        |j	                  |d                  |k(  s+|c S  y7 5w)z=Fetch one item by its key field (label/name/sender), or None.Nr   )rD   
_KEY_FIELDrY   strr   )rI   r9   wantfielditems        r   get_itemr`   z   s`     :DtE &&&TXXeR()*d2K '  's   &A A.A A c           	     >  K   t        |      }|syt        |    }d}t        |        d{   D ]e  }t        t        |j	                  |d                  |k(  s+|j	                  d      s=t        dd|d   t               d       d{    |d	z  }g |S 7 n7 w)
u   Delete every item whose key field matches. Returns how many were removed
    (0 if none). Idempotent — used both to remove and to make put_item an
    upsert.r   Nr   rO   POSTz/forget)rN   rH   r6      )rD   r[   rY   r\   r   r=   r"   )rI   r9   r]   r^   removedr_   s         r   delete_itemrf      s      :DtEG &&&TXXeR()*d2txxvy+/;$OQ Q QqLG	 '
 N 'Qs-   (BB.BB,BBBBc           
       K   t         |    }t        |j                  |d            j                         }|st	        |  d| d      |j                         D ci c]  \  }}|dk7  s|| }}}t        | |       d{    t        ddt        j                  |d	      t               | d
dd       d{    |S c c}}w 7 E7 w)zUpsert an item (replace any existing one with the same key), then store
    it. Returns the stored payload (without the transient ``_id``).r   z: missing required ''rO   Nrb   z	/rememberF)ensure_asciiphoneg?)rM   rH   rI   source
confidencerc   )r[   r\   r   r@   rP   rV   rf   r=   r(   dumpsr"   )rI   rX   r^   r9   krC   cleans          r   put_itemrp      s      tE
gkk%$
%
+
+
-CD6!5eWA>??%mmo<odaeQToE<
dC
   
v{%)ZZE%J&1mT'.cCD D D L = Ds6   AC
C )C .C
 C8C
9C:C
C
c                    t        |       }|sy|D ]+  }t        t        |            }|dk(  r y|s!||k(  s||v s+ y y)zDoes ``sender`` match the watch-list? ``"*"`` matches everything; a stored
    entry matches on exact (case-insensitive) equality OR as a substring of the
    sender (so ``"SNB"`` catches ``"SNB-AlAhli"``).Fr?   T)rD   r\   )r
   watchswwns        r   sms_matchesrv      sK     	fA3q6]927bAg  r   )returnr\   )NNg      4@)r3   r\   r4   r\   r5   dict | Noner6   rx   r&   floatrw   rS   )rB   r\   rw   r\   )rI   r\   rw   z
list[dict])rI   r\   r9   r\   rw   rx   )rI   r\   r9   r\   rw   int)rI   r\   rX   rS   rw   rS   )r
   r\   rr   z	list[str]rw   bool)__doc__
__future__r   r(   r   urllib.parser+   
KIND_PLACEKIND_CONTACTKIND_SMS_WATCHKIND_ROUTINEr[   r   r   r   r"   r=   rD   rY   r`   rf   rp   rv   r   r   r   <module>r      s   : #  	  
" &H&	
<03M .2+/(,.*.(. !&. 26.2(&	""r   