
    i*                         S 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	  \R                  " S5      rSrSS\S	\S
\4S jjrSS jrS\S
\4S jrS\S
\4S jrg)u  
Health screenshot parser — uses Claude Vision to extract data from
iHealth app screenshots sent via WhatsApp.

Supported screenshot types:
  1. Scale / body composition (iHealth Nexus Pro)
     → weight, body fat, BMI, muscle mass, body water, etc.
  2. Blood pressure history (iHealth BP monitor)
     → systolic, diastolic, pulse, multiple readings

Bill screenshots his iHealth app and sends to Luke. This module
extracts the numbers and updates the dashboard automatically.
    N)datedatetime)	Anthropic)Configzhealth-visionu  Look at this image. Determine what type of health screenshot it is and extract the data.

TYPE 1 — SCALE / BODY COMPOSITION (shows weight in large text, may show body fat, BMI, etc.)
Return:
{
  "type": "scale",
  "weight_lb": <number>,
  "weight_change": <number or null>,
  "bmi": <number or null>,
  "body_fat_pct": <number or null>,
  "muscle_mass_lb": <number or null>,
  "body_water_pct": <number or null>,
  "lean_body_mass_lb": <number or null>,
  "bone_mass_lb": <number or null>,
  "protein_pct": <number or null>,
  "visceral_fat": <number or null>,
  "bmr_kcal": <number or null>,
  "metabolic_age": <number or null>,
  "date": "<YYYY-MM-DD if visible, else null>",
  "time": "<HH:MM if visible, else null>"
}

TYPE 2 — BLOOD PRESSURE (shows SYS/DIA/Pulse readings, may show history)
Return:
{
  "type": "blood_pressure",
  "readings": [
    {
      "systolic": <number>,
      "diastolic": <number>,
      "pulse": <number>,
      "date": "<YYYY-MM-DD if visible>",
      "time": "<HH:MM if visible>",
      "reading_number": <1st/2nd/3rd label if visible, else null>
    }
  ]
}
For BP, extract ALL visible readings in the image (there may be multiple).
Sort from most recent to oldest.

NOT A HEALTH SCREENSHOT:
Return: {"type": "unknown"}

Rules:
- All numbers must be numeric, not strings
- weight_change is the small delta (like ↑0.9 means +0.9)
- For BP dates shown as "Feb 23, 2026", convert to "2026-02-23"
- For BP times shown as "7:46 a.m.", convert to "07:46"
- Return ONLY the JSON object, no other text
image_datacontent_typereturnc           
         [        [        R                  S9nSU;   a  SnOSU;   a  SnOSn[        R                  " U 5      R                  S5      n UR                  R                  [        R                  SS	S
SUUS.S.S[        S./S./S9nUR                  S   R                  R                  5       n[        R                  SUSS  35        UR                  S5      (       a6  UR!                  SS5      S   R#                  SS5      S   R                  5       n[$        R&                  " U5      nUR)                  SS5      n[        R                  SU 35        U$ ! [$        R*                   aA  n	[        R-                  SU	 35        [        R-                  SWSS  35        SS0s Sn	A	$ Sn	A	f[.         a/  n	[        R-                  SU	 3SS 9  S![1        U	5      S".s Sn	A	$ Sn	A	ff = f)#zy
Send a health app screenshot to Claude Vision for extraction.
Returns dict with 'type' field indicating what was found.
)api_keypngz	image/pngwebpz
image/webp
image/jpegzutf-8i   userimagebase64)type
media_typedata)r   sourcetext)r   r   )rolecontent)model
max_tokensmessagesr   zHealth vision raw response: Ni,  z```
   r   unknownzHealth vision detected type: z%Health vision: failed to parse JSON: zRaw response: zHealth vision API call failed: T)exc_infoerror)r   r    )r   r   CLAUDE_API_KEYr   	b64encodedecoder   createCLAUDE_MODEL_EXTRACTION_PROMPTr   r   striploggerinfo
startswithsplitrsplitjsonloadsgetJSONDecodeErrorr    	Exceptionstr)
r   r   clientr   	b64_imageresponser   r   	data_typees
             3/Users/bsyrros/clawd/whatsapp-agent/scale_vision.pyparse_health_screenshotr9   K   s   
 v445F 
	<	!
!
  ,33G<I)2??))%% !($,*4$-# !' 2  * 
, "''--/24:,?@ ??5!!::dA&q)00:1=CCEDzz$HHVY/	3I;?@ #<QC@A~d4Cj\23	"" 26qc:TJ#a&112s1   DE G+36F/)G+/G+<$G& G+&G+c                     [        X5      $ )N)r9   )r   r   s     r8   parse_scale_screenshotr;      s    ":<<    r   c                 $  ^ ^^ T (       a  T R                  S5      (       d  g[        R                  " 5       R                  5       m/ mUU U4S jnUR	                  U5        SSR                  S T 5       5      -   n[        R                  U5        U$ )zDApply extracted scale data to the dashboard. Returns summary string.	weight_lb c                 l  > TS   nSU ;  a  0 U S'   U S   nXS'   TUS'   SUS'   UR                  S/ 5      nUR                  U5        [        U5      S:  a  US	S  nX2S'   UR                  S
S5      n[        U5      S:  ah  US   US   -
  n[        US5      US'   US:  a3  X-
  nUS:w  a  U[	        U5      -  OSn[        S[        U5      5      US'   OUR                  SS5      US'   [        UR                  SU5      U-
  S5      US'   TR                  SU S35        TR                  S5      (       a_  SU ;  a  0 U S'   U S   nUR                  STS   5      n	TS   US'   TUS'   [        TS   U	-
  S5      US'   TR                  STS    S35        TR                  S5      (       a"  TS   TS.U S'   TR                  S TS    35        0 n
S! H  nTR                  U5      c  M  TU   X'   M      U
(       a  TU
S'   XS"'   / nTR                  S#5      (       a  UR                  S$TS#    S35        TR                  S%5      b  UR                  S&TS%    35        TR                  S'5      (       a  UR                  S(TS'    35        U(       a   TR                  S)R                  U5      5        TU S'   g )*Nr>   weightcurrentr   ziHealth Nexus Pror   	series30d   igoal      ir   pacer   c   etaWeeks?startlostzWeight: z lbbody_fat_pctbodyFatdeltaVsLastz
Body Fat: %bmi)valuer   zBMI: )muscle_mass_lbbody_water_pctlean_body_mass_lbbone_mass_lbprotein_pctvisceral_fatbmr_kcalmetabolic_agebodyCompositionrU   zMuscle: rZ   zVisceral Fat: r\   zMetabolic Age: z | )r/   appendlenroundabsmaxjoin)	dashboardrA   wseriesrE   week_change	remainingweeksbfold_bf	body_compkeyextraschangesr   todays                r8   updater!apply_scale_data.<locals>.updater   s    k"9$"$Ihh)&	)({B'fv;CD\F+uuVS!v;! *vbz1Kk1-AfIQ"M	8Cq8H	C$44b #AuU| 4* !j# 6*!%%0691=&	&-.88N##	)')	)$9%BVVItN';<F 0ByMBvJ %d>&:V&CQ GB}NNZ^(<'=Q?@88E??)-eeDIeNNU4;-01	&C xx}(!%c		&  %If+4'(Fxx()).>)?(@DExx'3tN/C.DEFxx((_0E/FGHuzz&12!	&r<   zScale data captured:
r   c              3   ,   #    U  H
  nS U 3v   M     g7fu     • N .0cs     r8   	<genexpr>#apply_scale_data.<locals>.<genexpr>   s     2QAVA3<   r/   r   rp   	isoformat_read_and_writerc   r(   r)   )r   dashboard_managerrq   summaryro   rp   s   `   @@r8   apply_scale_datar      sq    txx,,JJL""$EGB"H %%g.&2Q2Q)QQG
KKNr<   c           
      z  ^^^^^	^
^^^^ U R                  S/ 5      m
T
(       d  gT
S   nUR                  S5      mUR                  S5      mUR                  S5      mT(       a  T(       d  g[        R                  " 5       R                  5       mUR                  ST5      mUR                  SS5      m	TS	:  a	  TS
:  a  SmO TS:  a	  TS
:  a  SmOTS:  d  TS:  a  SmOSmSSSSS.m/ mUUUUU	U
UUUU4
S jnUR	                  U5        SSR                  S T 5       5      -   n[        R                  U5        U$ )zMApply extracted blood pressure data to the dashboard. Returns summary string.readingsr?   r   systolic	diastolicpulser   timex   P   normal   elevated   Z   stage1stage2NormalElevatedzStage 1 HypertensionzStage 2 Hypertension)r   r   r   r   c                   >
 U R                  S0 5      nTUS'   T	US'   T
US'   TUS'   TUS'   TUS'   SUS	'   TR                  S
T ST	 STR                  TT5       S35        T
(       a  TR                  ST
 S35        UR                  S/ 5      nTT	T
TTS.nU(       a	  US   U:w  a&  UR                  SU5        [        U5      S:  a  US S nX!S'   TSS   Hv  nUR                  S5      UR                  S5      UR                  S5      UR                  ST5      UR                  SS5      S.nXQS   ;  d  Mb  US   R                  U5        Mx     [	        US   S SS9S S US'   [        T5      S:  aq  [        [        S T 5       5      [        T5      -  5      n[        [        S T 5       5      [        T5      -  5      nTR                  S[        T5       SU SU 35        XS'   TU S'   g )NbloodPressurer   r   r   r   r   statusziHealth BP Monitorr   zBP: /z mmHg ()zPulse: z bpmhistory)r   r   r   r   r   r   rD   r   r?   c                 J    U R                  SS5      U R                  SS5      4$ )Nr   r?   r   )r/   )xs    r8   <lambda>0apply_bp_data.<locals>.updater.<locals>.<lambda>:  s    155,aeeFB.?@r<   T)rm   reversec              3   *   #    U  H	  oS    v   M     g7f)r   Nru   rw   rs     r8   ry   1apply_bp_data.<locals>.updater.<locals>.<genexpr>@  s     @x!*x   c              3   *   #    U  H	  oS    v   M     g7f)r   Nru   r   s     r8   ry   r   A  s     A1+r   zAvg (z readings): )r/   r^   insertr_   sortedr`   sum)rd   bpr   	new_entryr   entryavg_sysavg_diaro   dia_valr   reading_datereading_timer   r   status_labelssys_valrp   s           r8   rq   apply_bp_data.<locals>.updater	  s!    ]]?B/ !:!;7!6
!6
8+8gYay8I8I&RX8Y7ZZ[\]NNWUG401 &&B'   
	 '!*	1NN1i(7|b !#2,9 !"AEE*-UU;/wfl3fb)E yM)9$$U+  yM@
 2	9 x=1C@x@@3x=PQGCAAACMQRGNNU3x=/gYayQR%'/"!	&r<   zBlood pressure captured:
r   c              3   ,   #    U  H
  nS U 3v   M     g7frt   ru   rv   s     r8   ry    apply_bp_data.<locals>.<genexpr>I  s     6UWs|Wr{   r|   )r   r   latestrq   r   ro   r   r   r   r   r   r   r   r   rp   s        @@@@@@@@@@r8   apply_bp_datar      s)   xx
B'H a[Fjj$Gjj%GJJwE'JJL""$E::fe,L::fb)L }2	37R<	3'B, ((	M G<" <"| %%g.*TYY6UW6U-UUG
KKNr<   )r   )__doc__r   r-   loggingr   r   	anthropicr   configr   	getLoggerr(   r&   bytesr2   dictr9   r;   r   r   ru   r<   r8   <module>r      s       #  			?	+00 f92 92S 92TX 92z=P4 Ps Pjh hC hr<   