
    ڒiEF                         S r SSKrSSKrSSKrSSKrSSKJrJr  SSKJr  SSK	J
r
Jr  \R                  " \5      r " S S5      rg)u|   
Dashboard data manager — reads and writes dashboard-data.json
with file locking and atomic writes to prevent corruption.
    N)datetimedate)Path)FileLockTimeoutc                      \ rS rSrS\4S jrS\4S jrS\4S jrS r	S	\
S\4S
 jrS	\
S\4S jrS\S\S\4S jr  S6S\S\S\S\S\S\S\4S jjrS7S\S\S\4S jjrS8S\S\S\S\S\4
S jjrS\S\4S jr S9S\S \S!\S"\S\S\4S# jjrS$\S\4S% jrS&\S'\S\4S( jrS&\S)\S\4S* jrS7S+\
S\4S, jjrS\4S- jrS\4S. jrS\4S/ jrS:S0\S\4S1 jjrS\4S2 jrS\4S3 jrS\4S4 jrS5r g);DashboardManager   	file_pathc                     [        U5      U l        [        U R                  5      S-   U l        [	        U R                  SS9U l        g )Nz.lock
   )timeout)r   r   str	lock_pathr   lock)selfr   s     8/Users/bsyrros/clawd/whatsapp-agent/dashboard_manager.py__init__DashboardManager.__init__   s4    iT^^,w6T^^R8	    returnc                     U R                      [        U R                  SSS9 n[        R                  " U5      sSSS5        sSSS5        $ ! , (       d  f       O= f SSS5        g! , (       d  f       g= f)z(Read and return the full dashboard data.rutf-8encodingN)r   openr   jsonload)r   fs     r   readDashboardManager.read   sI    YYdnncG<yy| =< Y<<< YYs!   A'A	A'
A	A''
A5datac                    U R                   R                  n[        R                  " [	        U5      SS9u  p4 [
        R                  " USSS9 n[        R                  " XSSS9  S	S	S	5        [
        R                  " U[	        U R                   5      5        [        R                  S
5        g	! , (       d  f       NN= f! [         a<    [
        R                  R                  U5      (       a  [
        R                  " U5        e f = f)z.Atomic write: write to temp file, then rename.z	.json.tmp)dirsuffixwr   r      F)indentensure_asciiNz#Dashboard data written successfully)r   parenttempfilemkstempr   osfdopenr   dumpreplaceloggerinfo	Exceptionpathexistsunlink)r   r#   dir_pathfdtmp_pathr    s         r   _writeDashboardManager._write!   s    >>((''CM+N	2sW5		$!%@ 6JJxT^^!45KK=> 65  	ww~~h''		(#	s%   B> B-%AB> -
B;7B> >ADc                    U R                      [        U R                  SSS9 n[        R                  " U5      nSSS5        U" W5      nU R                  U5        X44sSSS5        $ ! , (       d  f       N3= f! , (       d  f       g= f)zDRead data, apply updater function, write back. Returns updated data.r   r   r   N)r   r   r   r   r   r;   )r   updaterr    r#   results        r   _read_and_write DashboardManager._read_and_write/   s]    YYdnncG<yy| =T]FKK< Y<< Ys!   A8A'#A8'
A5	1A88
Bvaluec                 :   ^ U4S jnU R                  U5      u  p4U$ )zHUpdate weight with new value, rotate series, recalculate derived fields.c                 
  > U S   nUS   n[        TU-
  S5      US'   TUS'   UR                  S/ 5      nUR                  T5        [        U5      S:  a  USS  nX1S'   [        U5      S:  a  US	S  OUn[        [	        U5      [        U5      -  S5      US
'   UR                  SS5      nUR                  SS5      n[        SSS5      n[        [
        R                  " 5       U-
  R                  S-  S5      nUT-
  n	[        X-  S5      n
XS'   TU-
  nU
S:  a  [        X-  5      OSUS'   [
        R                  " 5       nUR                  S5      US'   UR                  S5      U S'   ST SU SUS   S SU
 SU SUS    S 3$ )!Nweightcurrent   deltaVsLast	series30d      iavg7dbaselineg     n@goal   i  r(   pacer   i  etaWeeksz%B %-dlastWeighInz%Y-%m-%dr   zWeight updated to z	 lb (was z). Change: +.1fz lb. Pace: z lb/wk. ETA to z: ~z weeks.)
roundgetappendlensumr   maxnowdaysstrftime)r#   r'   oldserieslast7rN   rO   baseline_dateweeks_elapsed
total_lostrQ   	remainingtodayrB   s                r   r>   /DashboardManager.update_weight.<locals>.updater<   s   XAI,C$US[!4Am AiL UU;+FMM% 6{R#kN $'v;!#3F23KEs5zCJ6:AgJ uuZ/H55%D$T1a0M-!? E E I1MM!E)J3Q7DfII7;axE)"23SAjMLLNE$~~h7Am >>*5DL'wiuK-HXY]G^^ijnioo~  @D  E  EH  IJ  KU  IV  HW  W^  _  _r   r@   r   rB   r>   _msgs    `   r   update_weightDashboardManager.update_weight:   s#     	_D %%g.
r   c                 :   ^ U4S jnU R                  U5      u  p4U$ )z*Update body fat percentage, rotate series.c                    > U S   nUS   n[        TU-
  S5      US'   TUS'   UR                  S/ 5      nUR                  T5        [        U5      S:  a  USS  nX1S'   ST S	U S
US   S S3$ )NbodyFatrF   rG   rH   rI   rJ   rK   zBody fat updated to z% (was z%). Change: rT   z%.)rU   rV   rW   rX   )r#   bfr^   r_   rB   s       r   r>   1DashboardManager.update_body_fat.<locals>.updatere   s    iBY-C %eck1 5B}!ByMVVK,FMM% 6{R${O)%uLMIZ[_H``bccr   rg   rh   s    `   r   update_body_fat DashboardManager.update_body_fatc   s"    	d %%g.
r   systolic	diastolicc                 >   ^^ UU4S jnU R                  U5      u  pEU$ )zUpdate blood pressure reading.c                    > U S   nUS   US   p2TUS'   TUS'   [         R                  " 5       R                  5       US'   TS:  a  TS:  a  SUS'   O)TS	:  a  TS:  a  S
US'   OTS:  d  TS:  a  SUS'   OSUS'   ST ST SUS    SU SU S3$ )NbloodPressurert   ru   r   x   P   normalstatus   elevated   Z   zhigh-stage1zhigh-stage2zBP updated to / (z). Was .)r   re   	isoformat)r#   bpold_sold_dru   rt   s       r   r>   7DashboardManager.update_blood_pressure.<locals>.updaterz   s    o&Bj>2k?5%BzN'B{O//1BvJ #~)b.'8CIN)8C9r>,8,8#H:QykBxL>QVPWWXY^X__`aar   rg   )r   rt   ru   r>   ri   rj   s    ``   r   update_blood_pressure&DashboardManager.update_blood_pressurex   s"    	b& %%g.
r   N
event_datetimetitlelocationnoteend_datec                 f   ^^^^^^^
^ SSK Jm
Jm  U
UUUUUUU4S jnU R                  U5      u  pU	$ )z~Add event to schedule. event_date should be YYYY-MM-DD.
If end_date is provided, creates a multi-day event spanning the range.r   )r   	timedeltac                   > T(       a  TT:w  a  TR                  T5      nTR                  T5      nSnUnXB::  a?  UR                  5       TTT	T
S.nU S   R                  U5        US-  nUT" SS9-  nXB::  a  M?  U S   R                  S S9  ST S	T S
T SU ST	=(       d    S S3$ TTTT	T
S.nU S   R                  U5        U S   R                  S S9  ST ST ST ST	=(       d    S S3	$ )Nr   )r   r   r   r   r   schedulerG   )r\   c                 &    U R                  SS5      $ Nr    rV   es    r   <lambda>=DashboardManager.add_event.<locals>.updater.<locals>.<lambda>       AEE&"4Er   )keyzAdded:  from z to r   z days). Location: TBDr   c                 &    U R                  SS5      $ r   r   r   s    r   r   r      r   r   z on z at z. Location: )fromisoformatr   rW   sort)r#   startend
days_addedrF   eventdt_dater   r   r   r   r   r   r   s         r   r>   +DashboardManager.add_event.<locals>.updater   sF   H
2--j9++H5
n ' 1 1 3 $!&$, $E $++E2!OJya00G n Z %%*E%F vj\hZr*Ughphytygzz{|| ' " (  Z ''.Z %%*E%F tJ<tD6hN_Z_M``abbr   )r   r   r   r@   )r   r   r   r   r   r   r   r>   ri   rj   r   r   s    ``````   @@r   	add_eventDashboardManager.add_event   s.    
 	8	c 	c@ %%g.
r   title_fragmentc                 >   ^^ UU4S jnU R                  U5      u  pEU$ )zMRemove event(s) matching title (case-insensitive). Optionally filter by date.c                   > [        U S   5      nTR                  5       nU S    Vs/ s H+  nX#S   R                  5       ;   a  Tc  M  US   T:X  a  M)  UPM-     snU S'   U[        U S   5      -
  nU(       a	  SU ST S3$ ST S3$ s  snf )Nr   r   r   zRemoved z event(s) matching ''.zNo events found matching ')rX   lower)r#   beforetitle_lowerr   removedr   r   s        r   r>   .DashboardManager.remove_event.<locals>.updater   s    j)*F(..0K
+ +aW:#3#3#55#  01yJ/F + D s4
#344G!'*>~>NbQQ//?rBB s   BBBrg   )r   r   r   r>   ri   rj   s    ``   r   remove_eventDashboardManager.remove_event   s"    	C %%g.
r   catwhysourcec                 F   ^^^^ UUUU4S jnU R                  U5      u  pgU$ )z,Add a curated signal to the top of the list.c                    > TTTTS.nU S   S   R                  SU5        [        R                  " 5       R                  5       U S   S'   U S   S   S S U S   S'   ST ST 3$ )	N)r   r   r   r   signalscuratedr   updated   zSignal added: [] )insertr   re   r   )r#   signalr   r   r   r   s     r   r>   ,DashboardManager.add_signal.<locals>.updater   sx     5OFOI&--a8)-)?)?)ADOI&)-i)CCR)HDOI&$SEE733r   rg   )r   r   r   r   r   r>   ri   rj   s    ````   r   
add_signalDashboardManager.add_signal   s&    	4 	4 %%g.
r   textc                 :   ^ U4S jnU R                  U5      u  p4U$ )zUpdate the focus/goal text.c                 *   > U S   nTU S'   ST SU S3$ )NfocuszFocus updated to: 'z	' (was: 'z'). )r#   r^   r   s     r   r>   .DashboardManager.update_focus.<locals>.updater   s*    w-C DM(iuC@@r   rg   )r   r   r>   ri   rj   s    `   r   update_focusDashboardManager.update_focus   s"    	A
 %%g.
r   timingnamedosebrandc                 J   ^^^^^ UUUUU4S jnU R                  U5      u  pxU$ )z?Add a supplement. timing is 'morning', 'evening', or 'anytime'.c                 z   > TTTTS.nTU S   ;  a  / U S   T'   U S   T   R                  U5        ST ST ST S3$ )N)r   r   r   r   supplementszAdded r   z) to z supplements.rW   )r#   suppr   r   r   r   r   s     r   r>   0DashboardManager.add_supplement.<locals>.updater   s`     $MDT-00.0]#F+'..t4D6D6vhmDDr   rg   )	r   r   r   r   r   r   r>   ri   rj   s	    `````   r   add_supplementDashboardManager.add_supplement   s(    	E 	E %%g.
r   name_fragmentc                 :   ^ U4S jnU R                  U5      u  p4U$ )zIRemove supplement matching name (case-insensitive) from any timing group.c                   > TR                  5       n/ nS H  n[        U S   R                  U/ 5      5      nU S   R                  U/ 5       Vs/ s H  nXS   R                  5       ;  d  M  UPM     snU S   U'   U[        U S   U   5      -
  nU(       d  M  UR                  U SU 35        M     U(       a  ST SSR	                  U5       S3$ S	T S
3$ s  snf )Nmorningeveninganytimer   r   r   zRemoved supplement 'z': z, r   zNo supplement found matching 'r   )r   rX   rV   rW   join)r#   
name_lowerr   r   r   sdiffr   s          r   r>   3DashboardManager.remove_supplement.<locals>.updater   s    &,,.JG;T-044VR@A#M266vrB/B!!6):: B/]#F+ D$7$? @@4NNdV6&#:; < -m_C		'@R?SSTUU3M?"EE/s   C,Crg   )r   r   r>   ri   rj   s    `   r   remove_supplement"DashboardManager.remove_supplement   s"    	F  %%g.
r   categoryfoodsc                 >   ^^ UU4S jnU R                  U5      u  pEU$ )zIReplace a viome food category. category: superfoods/enjoy/minimize/avoid.c                 P   > TS;  a  ST S3$ TU S   T'   ST S[        T5       S3$ )N)
superfoodsenjoyminimizeavoidInvalid category 'z+'. Use: superfoods, enjoy, minimize, avoid.viomezViome 'z' updated with z items.)rX   )r#   r   r   s    r   r>   .DashboardManager.update_viome.<locals>.updater  sC    KK+H:5`aa&+DM(#XJoc%j\IIr   rg   )r   r   r   r>   ri   rj   s    ``   r   update_viomeDashboardManager.update_viome  s"    	J %%g.
r   foodc                 >   ^^ UU4S jnU R                  U5      u  pEU$ )z&Add a single food to a viome category.c                    > TU S   ;  a  ST S3$ TU S   T   ;  a   U S   T   R                  T5        ST ST S3$ ST ST S3$ )	Nr   r   r   zAdded 'z' to viome r   'z' is already in viome r   )r#   r   r   s    r   r>   0DashboardManager.add_viome_food.<locals>.updater  sq    tG},+H:R884=22Wh'..t4 k(1==tf28*A>>r   rg   )r   r   r   r>   ri   rj   s    ``   r   add_viome_foodDashboardManager.add_viome_food  s!    	? %%g.
r   pricec                 @  ^ SSK nTc;   UR                  SSSS.SS9nUR                  5         UR                  5       S   S   mU4S jnU R                  U5      u  pgU$ ! [         a4  n[
        R                  S	U 35        S
[        U5      SS  S3s SnA$ SnAff = f)zSReset the CRO swing-trade anchor to the given price (or live price from CoinGecko).r   Nz-https://api.coingecko.com/api/v3/simple/pricezcrypto-com-chainusd)idsvs_currenciesr   )paramsr   zFailed to fetch CRO price: zCouldn't fetch live CRO price: rz   z;. Try again or text 'cro anchor 0.085' with a manual price.c           	      \  > U R                  S0 5      R                  S0 5      R                  S0 5      nUR                  SS5      n[        TS5      US'   UR                  SS5      nUR                  S	S
5      n[        TSU-   -  S5      n[        TSU-
  -  S5      nSTS SUS SU SU S3	$ )NcryptopositionstrategyanchorPricer      sellTriggergQ?
buyTriggerg?rG      zCRO anchor reset to $z.4fz (was $u   ).
New targets — Sell: $z	 | Buy: $z'
Dashboard will update on next refresh.)rV   rU   )r#   strat
old_anchorsell_pctbuy_pctsell_target
buy_targetr   s          r   r>   3DashboardManager.update_cro_anchor.<locals>.updater=  s    HHXr*..z2>BB:rRE=!4J#(?E- yy5Hiid3GX 6:KuG4a8J (c{'*S9I J**5i
| L9:r   )	requestsrV   raise_for_statusr   r4   r2   errorr   r@   )r   r   http_requestsrespr   r>   ri   rj   s    `      r   update_cro_anchor"DashboardManager.update_cro_anchor+  s    (=
B$((C#5N ) 
 %%'		$67>
	  %%g.
+  B:1#>?8Q  FA  B  BBs   :A 
B))BBBc                     U R                  5       nUS   nSUS    SUS    SUS    SUS	    S
US    SUS    SUS    SUS    SUS    3$ )NrE   zWeight: rF   z lb (7-day avg: rM   z)
Goal: rO   z lb | Baseline: rN   z lb (baselineDatez)
Pace: rQ    paceUnitz	 | ETA: ~rR   z weeks
Last weigh-in: rS   r!   )r   r#   r'   s      r   query_weightDashboardManager.query_weightR  s    yy{Nq|n$4QwZL AvYK/*eAnDUCV WvYKq:y: H./1	
r   c           	      d    U R                  5       nUS   nSUS    SUS    SUS    SUS	   S
 S3	$ )Nro   z
Body Fat: rF   z	% (goal: rO   z%)
Baseline: rN   z% | Change vs last: rH   rT   %r  )r   r#   rp   s      r   query_body_fatDashboardManager.query_body_fat\  sW    yy{)_IyF =J((<R=Nt<TTUW	
r   c           	      `    U R                  5       nUS   nSUS    SUS    SUS    SUS	    3$ )
Nrx   zBP: rt   r   ru   r   r|   u   ) — measured r   r  )r   r#   r   s      r   query_blood_pressure%DashboardManager.query_blood_pressured  sL    yy{/"bn%Qr+&7r"X,WYZ`WaVbccr   
days_aheadc           	      ~   U R                  5       n[        R                  " 5       n/ nUS    Hd  n [        R                  " US   5      nX6s=::  a*  UR	                  [        UR                  U-   S5      S9::  a  O  MQ  UR                  U5        Md  Mf     U(       d  SU S3$ SU S3/nUS S	  Hd  nUR                  S
5      (       a  SUS
    3OSnUR                  S5      (       a  SUS    3OSn	UR                  SUS    U SUS    U	 35        Mf     SR                  U5      $ ! [        [        4 a     GM  f = f)Nr   r      )dayzNo events in the next z days.zUpcoming events (next z days):r   r   r  r   r   z @ u   •     — r   
)r!   r   re   r   r1   minr*  rW   
ValueErrorKeyErrorrV   r   )
r   r'  r#   re   upcomingr   edatelinestime_strloc_strs
             r   query_scheduleDashboardManager.query_schedulei  sM   yy{

j!A**1V95 U]]EII
2B7 &3 &  OOA& " +J<v>>)*W=>#2A*+%%--1V9+RH/0uuZ/@/@AjM?+bGLL4&	{8*E!G*gYOP  yy ) s   AD'>D''D<;D<c           	      .   U R                  5       nUS   nS/nS Hg  nUR                  U/ 5      nU(       d  M  UR                  SUR                  5        S35        U H   nUR                  SUS    SUS	    35        M"     Mi     SR	                  U5      $ )
Nr   zSupplements:r   r,  :u     • r   r+  r   )r!   rV   rW   upperr   )r   r#   r   r2  r   itemsitems          r   query_supplements"DashboardManager.query_supplements  s    yy{ 7FEE&"%Eur&,,.!134!DLL6$v,uT&\N!KL "	 8 yyr   c                     U R                  5       nUS   S   S S nSUS   S    S3/nU H   nUR                  SUS    S	US
    35        M"     SR                  U5      $ )Nr   r      zLatest signals (updated r   z):[r   r   r   r,  )r!   rW   r   )r   r#   sigsr2  r   s        r   query_signalsDashboardManager.query_signals  sw    yy{Iy)"1-+DOI,F+GrJKALL1QuXJb756 yyr   c                    U R                  5       nUS   nUS   nUS   n[        US    Vs/ s H>  nUR                  SS5      [        R                  " 5       R                  5       :  d  M<  UPM@     sn5      nSUS    SUS	    S
US    SUS    SUS	    SUS    SUS    SUS    SUS    SUS    SU S[        US   S   5       S3$ s  snf )zFull dashboard summary.rE   ro   rx   r   r   r   zDashboard Summary (z):
Weight: rF   z
 lb (goal rO   z
) | Pace: rQ   z lb/wk
Body Fat: z% (goal z%)
BP: rt   r   ru   r   r|   z	)
Focus: r   z
Upcoming events: z

Signals: r   r   z curated)r!   rX   rV   r   re   r   )r   r#   r'   rp   r   r   sched_counts          r   query_summaryDashboardManager.query_summary  s%   yy{N)_/"J'
'!uuVR DJJL$:$:$<< '
 
 "$v, 0|nJqykAfI; OIx6
| <j>"!B{O#4Br(|n E7m_ %  +} -DOI678B	
	
s   ;C+C)r   r   r   )r   r   N)N)r   )r   r   )   )!__name__
__module____qualname____firstlineno__r   r   dictr!   r;   r@   floatrk   rr   intr   r   r   r   r   r   r   listr   r   r  r  r"  r%  r5  r<  rB  rF  __static_attributes__r   r   r   r	   r	      s   9# 9$d $4  %5 %S %RU s *c c c 6 35"&(C (s (3 ((,/((+.(T3 C 3 ,c # C  c    57S  3 !.1;>s s .	S 	 	# 	s # # #u # #N
c 

 
dc d
   c  0
 3 
  s  
s 
r   r	   )__doc__r   r.   r,   loggingr   r   pathlibr   filelockr   r   	getLoggerrI  r2   r	   r   r   r   <module>rW     s=   
  	   #  &			8	$V
 V
r   