o
    iVL                  
   @   sX  d Z ddlZddlZddlZddlmZ ddlmZ ddlm	Z	 e
eZeejjd ZdZze rDe Zedee d	 ned
e  W n eyg Z zede  W Y dZ[ndZ[ww dZdedefddZddlmZ ddlmZm Z! dZ"dZ#G dd dZ$e$ Z%ddl&Z'dedefddZ(ddedededefddZ)dS ) 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                    s  |  di }|  di }|  di }|  dg }ddlm} |    fdd|D d	d
 }ddd |D }|  di }dd | dg D }	dd | dg D }
|  di }d}|rd| dd d| dd d| dd d| di  dd d| di  d d d!| d"d }|  d#i }d}|r| d$r|d$ d	d% }d&d |D }d'd(| }dg d)| d*d d+| d,d d-| d.d d/| d0d d1| d*d d2| d,d d3| d4d d5| d6d d7| d8d d9|  d:d | | d;| d<d(|	 d=d(|
 d>t|  d?i  d@g  dAS )BzIBuild a concise summary of current dashboard state for the system prompt.weightbodyFatbloodPressurescheduler   datec                    s    g | ]}| d d kr|qS )r   r   get.0etoday claude_parser.py
<listcomp>   s     z'build_state_summary.<locals>.<listcomp>N   
c                 s   s6    | ]}d |d  d| dd d|d  V  qdS )z  - r    timer   titleNr   r   r   r   r   	<genexpr>   s
    $
z&build_state_summary.<locals>.<genexpr>supplementsc                 S      g | ]}|d  qS namer   r   sr   r   r   r          morningc                 S   r   r    r   r"   r   r   r   r      r$   eveningsleepr   z
Sleep: total_hours?zh (bedtimeu   –	wake_timez), deep stagesdeep_minz	min, REM rem_minz	min, HRV avg_hrvexerciserecent   c              	   S   s4   g | ]}| d | dd d| dd dqS )r!   typer)    (duration_minzmin)r   r   r   r   r   r      s   4 z
Recent exercise: z, zWeight: currentz
 lb (goal goalz, pace pacez lb/wk, delta deltaVsLastz lb)
Body Fat: z% (goal z%)
BP: systolic/	diastolicr4   statusz	)
Focus: focusz
Next 5 events:
z
Morning supps: z
Evening supps: z

Signals: signalscuratedz curated)r   datetimer   r   	isoformatjoinlen)r   wbfbpscheddt_dateupcomingevent_linessuppsmorning_namesevening_namesr'   
sleep_liner0   exercise_liner1   ex_partsr   r   r   build_state_summary   s`   
hR





rR   )defaultdict)rA   	timedelta      c                   @   s|   e Zd ZdZdd ZdedefddZdedefdd	Zded
efddZdede	fddZ
defddZdefddZdS )ConversationMemoryz*In-memory conversation history per sender.c                 C   s   t t| _i | _d S )N)rS   list_history_last_active)selfr   r   r   __init__   s   

zConversationMemory.__init__sendertextc                 C   s<   |  | | j| d|d t | j|< | | dS )z Record an incoming user message.userrolecontentN)_expire_if_stalerY   append_dtnowrZ   _trimr[   r]   r^   r   r   r   add_user_message   s   

z#ConversationMemory.add_user_messagec                 C   s$   | j | d|d | | dS )z=Record Luke's response (the response_text, not the raw JSON).	assistantr`   N)rY   rd   rg   rh   r   r   r   add_assistant_message   s
   
z(ConversationMemory.add_assistant_messageeventc                 C   s8   | j | dd| d t | j|< | | dS )zPRecord a system event (e.g., 'Health Connect zip ingested: Weight 235.2 lb...').rj   z[System event] r`   N)rY   rd   re   rf   rZ   rg   )r[   r]   rl   r   r   r   add_system_event  s   
z#ConversationMemory.add_system_eventr   c                 C   s   |  | dd | j| D S )z7Get conversation history as Claude API messages format.c                 S   s   g | ]}|d  |d dqS )ra   rb   r`   r   )r   mr   r   r   r     s    z3ConversationMemory.get_messages.<locals>.<listcomp>)rc   rY   )r[   r]   r   r   r   get_messages  s   
zConversationMemory.get_messagesc                 C   sP   | j |}|r$t | ttdkr&| j|   t	d|  dS dS dS )z3Clear history if sender has been inactive too long.)minutesz!Expired conversation history for N)
rZ   r   re   rf   _td_HISTORY_TTL_MINrY   clearloggerdebug)r[   r]   lastr   r   r   rc     s
   z#ConversationMemory._expire_if_stalec                 C   s:   | j | }t|td kr|td  d | j |< dS dS )zKeep only the last N messages.   N)rY   rD   _MAX_HISTORY)r[   r]   msgsr   r   r   rg     s   
zConversationMemory._trimN)__name__
__module____qualname____doc__r\   strri   rk   rm   rX   ro   rc   rg   r   r   r   r   rW      s    

rW   r^   c              	   C   s2  zt | W S  t jtfy   Y nw d| v r;ztd| tj}|r-t |d W S W n t jtfy:   Y nw z<| 	d}d}t
|t| D ]+}| | dkrW|d7 }qJ| | dkru|d8 }|dkru| ||d  }t |  W S qJW n tt jfy   Y nw td| dd	   d
d
di d| d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rangerD   rt   info)r^   block_matchstartdepthi	candidater   r   r   _extract_json,  sL   


r   messagedashboard_datar]   c                 C   s  t tjd}ddlm} | d}t|}d}tr%dtdd  d	}t	
d
|
d|
d|}t||  t|}	td|  dt|	 d zK|jjtjd||	d}
|
jd j }td|  t|}g d}|D ]}||vr|dkr~dnd||< qrd|vri |d< t||dd |W S  ty } z td|  dddi ddt|dd  dW  Y d}~S d}~ww )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_KEYrA   r   r   strftimerR   _BILL_CONTEXTSYSTEM_PROMPTreplaceconversation_memoryri   ro   rt   r   rD   r   createCLAUDE_MODELrb   r^   r   r   rk   r   	Exceptionr   r~   )r   r   r]   clientrI   r   state_summarybill_contextr   r   responser^   parsedrequiredkeyr   r   r   r   parse_messageb  s^   
r   )r   )*r}   r   loggingospathlibr   	anthropicr   configr   	getLoggerrz   rt   __file__parent_CONTEXT_FILEr   exists	read_textr   rD   warningr   r   r   dictr~   rR   collectionsrS   rA   re   rT   rq   rx   rr   rW   r   rer   r   r   r   r   r   r   <module>   s@    
 19 6