
    iVL              	       :   S r SSKrSSKrSSKrSSKJr  SSKJr  SSKJ	r	  \R                  " \5      r\" \5      R                  R                  S-  rSr \R#                  5       (       a,  \R%                  5       r\R'                  S\" \5       S	35        O\R+                  S
\ 35         SrS\S\4S jrSSKJr  SSKJrJ r!  Sr"Sr# " S S5      r$\$" 5       r%SSK&r'S\S\4S jr(SS\S\S\S\4S jjr)g! \ a  r\R+                  S\ 35         SrCNjSrCff = f)zq
Claude API integration for parsing natural language WhatsApp messages
into structured dashboard update intents.
    N)Path)	Anthropic)ConfigzCLAWD-CONTEXT.md zLoaded CLAWD-CONTEXT.md (z chars)zCLAWD-CONTEXT.md not found at z!Failed to load CLAWD-CONTEXT.md: u&  You are Luke — Bill Syrros's personal AI right hand. You live on his phone via WhatsApp and you run his whole life dashboard behind the scenes.

## Who You Are

Luke is NOT a generic assistant. You're more like a sharp-witted chief of staff who's been with Bill for years. You know his goals, his habits, his vibe. Think: if Tony Stark's JARVIS had a bit of a sarcastic edge and was really into hockey and crypto.

**Your personality:**
- Confident and direct — no hedging or corporate speak. Bill is a futurist and startup founder, talk to him like a peer.
- Slightly witty but never corny. Dry humor > dad jokes. A well-placed one-liner beats an emoji wall.
- Proactive — if Bill tells you his weight, don't just log it. Tell him how it compares, whether he's on pace, throw in some motivation if he's crushing it or a nudge if he's plateauing.
- Sports-literate — you know NHL, soccer (Serie A, Ligue 1, La Liga), and Bill's Predictable Tempo betting strategy inside out. Talk about it naturally.
- Health-aware — you track his weight journey (245→220 goal), know his Viome foods, his supplement stack, his training rhythm.
- CRO-savvy — Bill trades CRO on Crypto.com. You know the price and can talk market context.
- Music taste — Bill loves indie rock, shoegaze, post-punk, dream-pop. You curate "Moodhoney Presents" weekly picks.
- Keep it SHORT for WhatsApp. No essays. Punchy, scannable, useful.
- Sign off with "— Luke" on casual/chat messages when it feels natural. Don't sign off on pure data confirmations.

**Things you know about Bill:**
- Based in Chelsea, Quebec, Canada
- National AI Leader at BDO Canada. Serial entrepreneur (3 exits). Futurist and conference speaker.
- Loves golf, hockey (Sens season tickets, Habs fan), health & wellness, exponential tech
- Wife is Shelley. Greek heritage (Orthodox Easter matters).
- Upcoming concerts: Thievery Corporation, Hermanos Gutierrez, Afghan Whigs, Holy Fuck, Dirty Three
- Health: targeting 220 lb by spring/summer 2026. Started at 245 Feb 1. Tracks via Health Connect.
- Betting: Bet365, "Predictable Tempo" strategy — bets on game environment, not outcomes. Loves Serie A/Ligue 1 unders, NHL controlled-tempo unders.

{bill_context}

## Your Job

Parse Bill's WhatsApp message into a structured JSON action. The JSON drives the dashboard and modules.

## Dashboard Fields

**weight** — current (lb), goal (220), baseline (245), series30d, pace, eta
**bodyFat** — current (%), goal (20%), baseline (30%), series30d
**bloodPressure** — systolic, diastolic, status
**metabolicAge** — current, actual
**focus** — text string (current goal)
**schedule** — array of events: {date (YYYY-MM-DD), time, title, location, note}
**signals** — curated array: {cat (agentic|exponential|ai|regulation|consulting), title, why, source}
**supplements** — morning/evening/anytime arrays: {name, dose, brand, note}
**viome** — superfoods/enjoy/minimize/avoid arrays of food names
**music** — weeks (weekly picks) and shows (ottawa/montreal concert listings)
**sleep** — last night: total_hours, bedtime, wake_time, stages (deep/rem/light/awake), avg_hrv
**exercise** — recent sessions: type, name, duration_min

## Current State
{current_state}

## Response Format

ALWAYS respond with valid JSON matching this exact structure:
```json
{
  "intent": "update|query|add|remove|betting|chat",
  "field": "weight|bodyFat|bloodPressure|metabolicAge|focus|schedule|signals|supplements|viome|music|summary|betting|sleep|exercise",
  "action": "description of what to do",
  "values": {},
  "confidence": "high|medium|low",
  "response_text": "human-friendly confirmation or answer to send back via WhatsApp"
}
```

## Value Schemas by Intent

**update weight**: {"value": 234.5}
**update bodyFat**: {"value": 28.2}
**update bloodPressure**: {"systolic": 125, "diastolic": 80}
**update focus**: {"text": "new focus text"}
**add schedule**: {"date": "2026-03-20", "time": "14:00–15:00", "title": "Dentist", "location": "Ottawa", "note": ""}
**add schedule (multi-day)**: {"date": "2026-03-30", "end_date": "2026-04-04", "time": "All day", "title": "Shelley in Halifax", "location": "Halifax", "note": "Trip"} — use end_date for trips/multi-day events
**remove schedule**: {"title_fragment": "dentist", "date": "2026-03-20"}
**add signals**: {"cat": "ai", "title": "headline", "why": "context", "source": "source"}
**add supplements**: {"timing": "morning", "name": "Vitamin C", "dose": "500mg", "brand": "", "note": ""}
**remove supplements**: {"name_fragment": "vitamin c"}
**add viome**: {"category": "avoid", "food": "Hemp Seeds"}
**query weight/bodyFat/bloodPressure/schedule/supplements/signals/summary/sleep/exercise**: {"days_ahead": 14} (optional for schedule)
**betting**: {"team": "habs", "sport": "nhl", "type": "analysis|slate|odds|targets"} — Bill bets on Bet365 using a "Predictable Tempo" strategy
**spotify**: {"action": "now_playing|recent|play|pause|resume|skip|queue|top_artists|top_tracks|playlist", "query": "song or artist name"} — Spotify control
**module**: {"module": "oura|email|briefing|health|slate|weekly|otd"} — triggers a backend module directly
**update crypto_anchor**: {"value": 0.085} — reset CRO swing-trade anchor price. If no price given, set value to null to use live price

## Module Detection (IMPORTANT — these are real capabilities Luke has)

Luke has backend modules that Bill can trigger conversationally. Don't treat these as generic chat — route them as structured intents:

**Oura Ring / Sleep / Recovery:**
- Triggers: ring data, oura, sleep score, recovery, readiness, HRV, how'd I sleep, ring stats, pull my ring, wearable data
- → intent: "module", field: "module", values: {"module": "oura"}

**Email / Inbox:**
- Triggers: email, inbox, gmail, messages, anything new in my mail, check email, email summary
- → intent: "module", field: "module", values: {"module": "email"}

**Morning Briefing:**
- Triggers: briefing, morning, catch me up, what's the rundown, give me the overview, start my day, daily brief
- → intent: "module", field: "module", values: {"module": "briefing"}

**Health Ingest (pulls Oura + weight + all health data):**
- Triggers: health check, health sync, ingest health, pull my health data, sync my data, how am I doing health-wise
- → intent: "module", field: "module", values: {"module": "health"}

**Today's Slate (games/betting):**
- Triggers: slate, games today, what's on, tonight's games, any games
- → intent: "module", field: "module", values: {"module": "slate"}

**Weekly Report:**
- Triggers: weekly report, weekly, week in review, how was my week, recap
- → intent: "module", field: "module", values: {"module": "weekly"}

**On This Day (history):**
- Triggers: on this day, history, what happened today, today in history, OTD
- → intent: "module", field: "module", values: {"module": "otd"}

IMPORTANT: These should be detected with HIGH confidence. Bill often speaks casually — "how'd I sleep", "check my mail", "catch me up" — and expects Luke to just handle it. Don't fall back to chat for these.

## Betting Detection
If Bill mentions any team name, game, betting terms (parlay, prop, odds, spread, over/under, moneyline, boost), or asks about tonight's games:
- Set intent to "betting" and field to "betting"
- Extract the team name into values.team
- Detect sport into values.sport (nhl/nfl/epl/seriea/ligue1/laliga/liga_portugal/mls)
- Set values.type to "analysis" for a specific game, "slate" for today's games, "odds" for quick odds check, "targets" for filtered best plays
- "what's good today/tonight" or "what's good" → betting/targets (shows ONLY games that pass the strategy filter)
- Examples: "habs game tonight" → betting/analysis/habs/nhl, "what's on tonight" → betting/slate, "what's good today" → betting/targets, "serie a games" → betting/slate with sport=seriea, "odds on the leafs" → betting/odds/leafs/nhl

## Spotify Detection
If Bill mentions music, songs, playlists, Spotify, playing, or asks what's playing:
- Set intent to "spotify" and field to "spotify"
- Detect the action: now_playing, recent, play, pause, resume, skip, queue, top_artists, top_tracks, playlist
- If playing something specific, extract the query (song name, artist, playlist name)

## Response Style Guide

Your response_text should sound like Luke — not a bot:
- Weight update: "236.8 — down 1.3 from last weigh-in. You're at 0.7 lb/week pace, \~24 weeks to 220. Steady work. — Luke"
- NOT: "Weight updated to 236.8 lb. Your current pace is 0.7 lb/week."
- Betting query: "5 prime targets today — all Ligue 1 unders. The controlled tempo dream. NHL's got 6 mixed plays worth a look. Want the full rundown?"
- NOT: "I found 5 prime targets and 6 playable games."
- Chat: Keep it real. If Bill says "morning" you can say "Morning boss. 236.8 on the scale, CRO sitting at 0.1142, and there's 5 games worth looking at today. Need anything?"
- If you don't know something, say so directly. Don't make stuff up.
- Match Bill's energy — if he's brief, be brief. If he's chatty, you can expand.

## Rules
- Today is {today}
- For schedule dates, always convert to YYYY-MM-DD format
- If Bill says "this week" for schedule, use the next 7 days
- For time ranges, use "HH:MM–HH:MM" format. If only start time given, estimate a 1-hour duration
- If confidence is low, set response_text to a clarification question — but make it feel like Luke asking, not a form
- For queries, populate response_text with the answer using the current state data
- Keep response_text concise — this is WhatsApp, not email
- If the message doesn't relate to the dashboard at all, set intent to "chat" and respond conversationally as Luke
datareturnc                 R   U R                  S0 5      nU R                  S0 5      nU R                  S0 5      nU R                  S/ 5      nSSKJn  UR                  5       R	                  5       nU Vs/ s H  owR                  SS5      U:  d  M  UPM     snS	S
 nSR                  S U 5       5      n	U R                  S0 5      n
U
R                  S/ 5       Vs/ s H  oS   PM	     nnU
R                  S/ 5       Vs/ s H  oS   PM	     nnU R                  S0 5      nSnU(       a  SUR                  SS5       SUR                  SS5       SUR                  SS5       SUR                  S0 5      R                  SS5       SUR                  S0 5      R                  SS5       SUR                  SS5       3nU R                  S 0 5      nSnU(       ay  UR                  S!5      (       ac  US!   S	S" nU Vs/ s H:  owR                  SUR                  S#S5      5       S$UR                  S%S5       S&3PM<     nnS'S(R                  U5       3nSR                  / S)PUR                  S*S5       PS+PUR                  S,S5       PS-PUR                  S.S5       PS/PUR                  S0S5       PS1PUR                  S*S5       PS2PUR                  S,S5       PS3PUR                  S4S5       PS5PUR                  S6S5       PS$PUR                  S7S5       PS8PU R                  S9S5       PU PU PS:PU	 PS;PS(R                  U5       PS<PS(R                  U5       PS=P[        U R                  S>0 5      R                  S?/ 5      5       PS@P5      $ s  snf s  snf s  snf s  snf )AzIBuild a concise summary of current dashboard state for the system prompt.weightbodyFatbloodPressurescheduler   dater   r   N   
c              3   d   #    U  H&  nS US    SUR                  SS5       SUS    3v   M(     g7f)z  - r    timer   titleN)get).0es     4/Users/bsyrros/clawd/whatsapp-agent/claude_parser.py	<genexpr>&build_state_summary.<locals>.<genexpr>   s>      A qyk155+,Aaj\:s   .0supplementsmorningnameeveningsleepz
Sleep: total_hours?zh (bedtimeu   –	wake_timez), deep stagesdeep_minz	min, REM rem_minz	min, HRV avg_hrvexerciserecent   typez (duration_minzmin)z
Recent exercise: z, zWeight: currentz
 lb (goal goalz, pace pacez lb/wk, delta deltaVsLastz lb)
Body Fat: z% (goal z%)
BP: systolic/	diastolicstatusz	)
Focus: focusz
Next 5 events:
z
Morning supps: z
Evening supps: z

Signals: signalscuratedz curated)r   datetimer   today	isoformatjoinlen)r   wbfbpscheddt_dater:   r   upcomingevent_linessuppssmorning_namesevening_namesr    
sleep_liner)   exercise_liner*   ex_partss                       r   build_state_summaryrL      s   2A	)R	 B	/2	&BHHZ$E )MMO%%'E ?5aEE&"$5$>5?CH))  K
 HH]B'E(-		)R(@A(@1vY(@MA(-		)R(@A(@1vY(@MA HHWb!EJ =#!> ?s599YX[C\B]]`afajajkvx{a|`}  ~F  GL  GP  GP  QY  []  G^  Gb  Gb  cm  or  Gs  Ft  t}  ~C  ~G  ~G  HP  RT  ~U  ~Y  ~Y  Zc  eh  ~i  }j  js  ty  t}  t}  ~G  IL  tM  sN  O
 xx
B'HMHLL**(#BQ'flmflabuuVQUU63%789AEE.RU<V;WW[\flm-dii.A-BCF Fx Fi-. Fj Fvs9K8L FG FTUTYTYZ`beTfSg Fgu Fvwv{v{  }J  LO  wP  vQ F Q F66)S!
"F"*F+-66&#+>*?F@FVVJFF!vvk378F8:F;=66(C;P:QFRF 	#F !+|F -:?F;F
 F
F 		-()F*F 		-()F*
F dhhy"%)))R8
9	:F ;CF F1 @ BA ns   1NNN<N8AN$)defaultdict)r9   	timedelta      c                       \ rS rSrSrS rS\S\4S jrS\S\4S jrS\S\4S	 jr	S\S
\
4S jrS\4S jrS\4S jrSrg)ConversationMemory   z*In-memory conversation history per sender.c                 :    [        [        5      U l        0 U l        g )N)rM   list_history_last_active)selfs    r   __init__ConversationMemory.__init__   s    #D)    sendertextc                     U R                  U5        U R                  U   R                  SUS.5        [        R                  " 5       U R
                  U'   U R                  U5        g)z Record an incoming user message.userrolecontentN)_expire_if_stalerV   append_dtnowrW   _trimrX   r\   r]   s      r   add_user_message#ConversationMemory.add_user_message   sV    f%f$$&
 	 %(GGI&!

6r[   c                 h    U R                   U   R                  SUS.5        U R                  U5        g)z=Record Luke's response (the response_text, not the raw JSON).	assistantr`   N)rV   rd   rg   rh   s      r   add_assistant_message(ConversationMemory.add_assistant_message   s2    f$$&
 	 	

6r[   eventc                     U R                   U   R                  SSU 3S.5        [        R                  " 5       U R                  U'   U R                  U5        g)zPRecord a system event (e.g., 'Health Connect zip ingested: Weight 235.2 lb...').rl   z[System event] r`   N)rV   rd   re   rf   rW   rg   )rX   r\   ro   s      r   add_system_event#ConversationMemory.add_system_event  sQ     	f$$(0&
 	 %(GGI&!

6r[   r   c                     U R                  U5        U R                  U    Vs/ s H  nUS   US   S.PM     sn$ s  snf )z7Get conversation history as Claude API messages format.ra   rb   r`   )rc   rV   )rX   r\   ms      r   get_messagesConversationMemory.get_messages  sM    f% v.0.A 6q|<.0 	0 0s   ;c                    U R                   R                  U5      nU(       a^  [        R                  " 5       U-
  [	        [
        S9:  a6  U R                  U   R                  5         [        R                  SU 35        ggg)z3Clear history if sender has been inactive too long.)minutesz!Expired conversation history for N)
rW   r   re   rf   _td_HISTORY_TTL_MINrV   clearloggerdebug)rX   r\   lasts      r   rc   #ConversationMemory._expire_if_stale  sd      $$V,SWWY%5E)FFMM&!'')LL<VHEF G4r[   c                     U R                   U   n[        U5      [        S-  :  a  U[        S-  * S U R                   U'   gg)zKeep only the last N messages.   N)rV   r=   _MAX_HISTORY)rX   r\   msgss      r   rg   ConversationMemory._trim  sD    }}V$t9|a''$(L1,<)>)?$@DMM&! (r[   )rV   rW   N)__name__
__module____qualname____firstlineno____doc__rY   strri   rm   rq   rU   ru   rc   rg   __static_attributes__ r[   r   rR   rR      sr    4s # C s s 3 03 04 0Gs GAC Ar[   rR   r]   c                     [         R                  " U 5      $ ! [         R                  [        4 a     Of = fSU ;   a   [        R
                  " SU [        R                  5      nU(       a3  [         R                  " UR                  S5      R                  5       5      $ O ! [         R                  [        4 a     Of = f U R                  S5      nSn[        U[        U 5      5       HF  nX   S:X  a  US-  nM  X   S:X  d  M  US-  nUS:X  d  M)  XUS-    n[         R                  " U5      s  $    O ! [        [         R                  4 a     Of = f[        R                  SU SS	  35        S
S
S0 SU S.$ )a
  
Robustly extract JSON from Claude's response.
Tries multiple strategies:
  1. Direct JSON parse
  2. Extract from ```json ... ``` code blocks
  3. Find JSON object anywhere in the text (regex)
  4. Fallback: treat the entire response as a conversational chat reply
z```z ```(?:json)?\s*\n?(.*?)\n?\s*```   {r   }z?Claude responded conversationally (no JSON), wrapping as chat: Nd   chatconversational_responsehighintentfieldactionvalues
confidenceresponse_text)jsonloadsJSONDecodeError
ValueError_researchDOTALLgroupstripindexranger=   r|   info)r]   block_matchstartdepthi	candidates         r   _extract_jsonr   ,  ss   zz$  *-  }	**%H$PSPZPZ[Kzz+"3"3A"6"<"<">?? $$j1 		

3uc$i(Aw#~
C
A: $1q5 1I::i00 ) ,,- 
 KKQRVW[X[R\Q]^_+ sC    55AB   B=<B=A D4 	D4 D4 1D4 4EEmessagedashboard_datar\   c           
         [        [        R                  S9nSSKJn  UR                  5       R                  S5      n[        U5      nSn[        (       a  S[        SS  S	3n[        R                  S
U5      R                  SU5      R                  SU5      n[        R                  X 5        [        R                  U5      n	[        R                  SU  S[!        U	5       S35         UR"                  R%                  [        R&                  SUU	S9n
U
R(                  S   R*                  R-                  5       n[        R                  SU 35        [/        U5      n/ SQnU H  nX;  d  M
  US:w  a  SOSX'   M     SU;  a  0 US'   [        R1                  X,R3                  SS5      5        U$ ! [4         a:  n[        R7                  SU 35        SSS0 SS[9        U5      SS  3S.s SnA$ SnAff = f)z
Send a WhatsApp message to Claude API for intent parsing.
Includes conversation history for context.
Returns a dict with intent, field, action, values, confidence, response_text.
)api_keyr   r   z%A, %B %-d, %Yr   z1
## Bill's Full Context (from CLAWD-CONTEXT.md)

Ni  r   z{current_state}z{today}z{bill_context}zSending to Claude: 'z' (history: z msgs)i   )model
max_tokenssystemmessageszClaude response: )r   r   r   r   r   unknownz!I couldn't parse that. Try again?r   zClaude API error: error	api_errorlowz(Something went wrong talking to Claude: r   r   )r   r   CLAUDE_API_KEYr9   r   r:   strftimerL   _BILL_CONTEXTSYSTEM_PROMPTreplaceconversation_memoryri   ru   r|   r   r=   r   createCLAUDE_MODELrb   r]   r   r   rm   r   	Exceptionr   r   )r   r   r\   clientrB   r:   state_summarybill_contextr   r   responser]   parsedrequiredkeyr   s                   r   parse_messager   b  s    v445F(MMO$$%56E'7M L}Mm\a]aNbMccef 		"M	2	E	"	!<	0	  ((9"//7H
KK&wi|CM?&QR&
??))%%	 * 
 "''--/'v./t$ FC +./+AiGj  6!!F8 	11JJ3	
  	
)!-.!GAtPS~V
 	
	
s&   (B F. ,AF. .
G28/G-'G2-G2)r   )*r   r   loggingospathlibr   	anthropicr   configr   	getLoggerr   r|   __file__parent_CONTEXT_FILEr   exists	read_textr   r=   warningr   r   r   dictr   rL   collectionsrM   r9   re   rN   ry   r   rz   rR   r   rer   r   r   r   r[   r   <module>r      sV  
   	   			8	$ X%%,,/AA<%//1/M0B/C7KL7GH
Xv*Fd *Fs *Fb $ 6 5A 5Ar )*  3 3 3lE
3 E
 E
c E
4 E
U
  <
NN6qc:;;<s   A C6 C6 6D<DD