o
    #i/m                     @   s  d Z ddlZddlZddlZddlZddlZddlm  mZ	 ddl
mZ ddlmZmZmZmZ zddlZW n eyI   ed ed Y nw zddlmZ eee jd  W n	 eyf   Y nw ejejd	d
d edZedpeddZee jd Zee jd Z e j!dd ddddddddddddddddd dd!d"dd#d$dd%d&dd'd(dd)d*dd+d,dd-Z"d.d/d0d1d2d0d3d4d0d5d6d0d7d8d0d9d:d0gd;d<d0d=d>d0d?d@d0gdAZ#i dBdCdDgdEdFgdGg dHdIg dJdKdLdMgdNdOdPgdQdRdSgdTdUdVgdWg dXdYdZgd[d\gd]d^d_gd`dagdbdcgdddedfgdgdhgdidjdkgi dldmgdndogdpdqgdrg dsdtg dudvdwdxgdydzgd{d|gd}d~gddgddgddgddgddgdg ddddgdddgi dddgddgddgdddgdddgdddgdddgdddgddgdddgdddgdddgdddgddgddgddgddgi dg dddgddgddgdddgddgdddgdddgddgddgddgdddgddgddgdddgdg ddg di dddgdddgdddgdddgdddgdddgdg ddddgdddgddgdd dgdddgdddgdd	d
gddgddgdddgZ$dddddddddddddZ%g dZ&g dZ'dd Z(dd Z)dd Z*dd Z+dd  Z,d!d" Z-d#d$ Z.d%d& Z/d'd( Z0e1d)kre0  dS dS (*  u  
Pregame Research — Storyline-Driven Parlay Intelligence
========================================================
Scans today's soccer (+ NHL) fixtures, pulls RSS headlines,
and uses Claude Haiku to build narrative-based pregame reports
with a highlighted Parlay of the Day.

100% free data sources:
  - ESPN public API for today's fixtures (no key required)
  - RSS feeds for team news (ESPN, BBC, Sky, Guardian, Goal.com)
  - Claude Haiku for storyline synthesis (~$0.01/day)

Independent of Luke, independent of The Odds API.

Usage:
  python3 pregame_research.py                     # uses .env for Claude key
  ANTHROPIC_API_KEY=xxx python3 pregame_research.py  # explicit key
    N)Path)datetimedate	timedeltatimezonezpip install requests   )load_dotenvz.envz!%(asctime)s [PREGAME] %(message)sz%H:%M:%S)levelformatdatefmtzpregame-researchANTHROPIC_API_KEYCLAUDE_API_KEY zdashboard-data.jsonlogsT)exist_okzEhttps://site.api.espn.com/apis/site/v2/sports/soccer/eng.1/scoreboardEPL)urllabelzEhttps://site.api.espn.com/apis/site/v2/sports/soccer/esp.1/scoreboardLa LigazEhttps://site.api.espn.com/apis/site/v2/sports/soccer/ita.1/scoreboardSerie AzEhttps://site.api.espn.com/apis/site/v2/sports/soccer/ger.1/scoreboard
BundesligazEhttps://site.api.espn.com/apis/site/v2/sports/soccer/fra.1/scoreboardLigue 1zEhttps://site.api.espn.com/apis/site/v2/sports/soccer/por.1/scoreboardLiga PortugalzEhttps://site.api.espn.com/apis/site/v2/sports/soccer/ned.1/scoreboard
EredivisiezEhttps://site.api.espn.com/apis/site/v2/sports/soccer/usa.1/scoreboardMLSzNhttps://site.api.espn.com/apis/site/v2/sports/soccer/uefa.champions/scoreboardChampions LeaguezKhttps://site.api.espn.com/apis/site/v2/sports/soccer/uefa.europa/scoreboardEuropa LeaguezPhttps://site.api.espn.com/apis/site/v2/sports/soccer/uefa.europa.conf/scoreboardConference LeaguezChttps://site.api.espn.com/apis/site/v2/sports/hockey/nhl/scoreboardNHL)epllaligaseriea
bundesligaligue1liga_portugal
eredivisiemlsucleuropa
conferencenhlzESPN FCz)https://www.espn.com/espn/rss/soccer/news)namer   zBBC Footballz/https://feeds.bbci.co.uk/sport/football/rss.xmlzSky Sports Footballz#https://www.skysports.com/rss/11095zThe Guardian Footballz(https://www.theguardian.com/football/rsszGoal.comz)http://www.goal.com/en/feeds/news?fmt=rssz
TSN Soccerzhttps://www.tsn.ca/rss/soccerzESPN NHLz&https://www.espn.com/espn/rss/nhl/newsz
TSN Hockeyzhttps://www.tsn.ca/rss/hockeyzSportsnet NHLz)https://www.sportsnet.ca/hockey/nhl/feed/)soccerr*   Arsenalarsenalgunners	Liverpool	liverpoolzManchester City)zman cityzmanchester citycityzManchester United)z
man unitedzmanchester unitedzman utdChelseachelseablueszTottenham Hotspur	tottenhamspurszNewcastle United	newcastlemagpieszAston Villazaston villavillaBrighton)brightonzbrighton and hoveseagullszWest Ham Unitedzwest hamFulhamfulhamzCrystal Palacezcrystal palacepalaceBournemouthbournemouth	Brentford	brentfordWolveswolveswolverhamptonEvertonevertonzNottingham Forest
nottinghamforestzLeicester City	leicesterzIpswich TownipswichSouthamptonsouthamptonzAC Milan)milanzac milan	rossonerizInter Milan)interzinter milan
nerazzurriJuventusjuventusjuveNapolinapolizAS RomaromaLaziolazioAtalantaatalanta
Fiorentina
fiorentinaBolognabolognaTorinotorinozReal Madridzreal madrid	Barcelona)	barcelonabarcau   barçazAtletico Madridatleticozatletico madridzReal Sociedadzreal sociedadsociedadzAthletic Bilbaozathletic bilbaobilbao
Villarreal
villarrealSevillasevillaz
Real Betisbetisz
real betiszBayern Munichbayernzbayern munichzBorussia Dortmunddortmundbvbz
RB Leipzigleipzigz
rb leipzigzBayer Leverkusen
leverkusenzbayer leverkusen	Stuttgart	stuttgartzEintracht Frankfurt	frankfurt	eintrachtzParis Saint-Germainpsgzparis saint-germainzOlympique Marseille	marseilleomLyonlyonzolympique lyonnaisMonacomonacoLillelilleBenficabenficaPortoportozSporting CP)sportingzsporting cpzsporting lisbonCelticcelticRangersrangersAjaxajaxPSVpsvzpsv eindhoven	Feyenoord	feyenoordzCF Montrealzcf montrealu   cf montréalz
Toronto FCz
toronto fctfczVancouver Whitecaps	whitecapszInter Miamizinter miamiLAFClafcz	LA Galaxyz	la galaxygalaxyzAtlanta Unitedzatlanta unitedzSeattle SounderssounderszColumbus Crewzcolumbus crewcrewzMontreal Canadiens)	canadienshabsmontrealOttawa Senators)senatorssensottawaToronto Maple Leafszmaple leafsleafsBoston BruinsbruinsbostonzNew York RangersnyrCarolina Hurricanes
hurricanescarolinaFlorida PantherspanthersfloridaEdmonton OilersoilersedmontonColorado Avalanche)	avalancheavscoloradoDallas StarsstarsdallaszWinnipeg JetsjetswinnipegVancouver CanuckscanucksTampa Bay Lightning	lightningtampazVegas Golden Knightszgolden knightsvegasPittsburgh PenguinspenguinspenszWashington CapitalscapitalscapsNew Jersey DevilsdevilsNew York Islanders	islandersMinnesota Wildwild	minnesota      )r   r   r   r   r   r   r   r   r   r   r   r   )r   r   r   zNashville PredatorszLos Angeles Kingsr   r   zColumbus Blue JacketszAnaheim Ducksr   r   zSt. Louis Blues)	r   r   r   r   r   r   r   zBuffalo SabreszDetroit Red Wingsc                     s  t   } t  d}g }t D ]5\} z d }d|i}tj||dddid}|jdkr?t	
| d	|j  W q| }|d
g }|D ]}	z|	di g}
|
r[|
d ni }|dg }t|dk rkW qKd}d}|D ]}|di }|d|dd}|ddkr|}qq|}qq|r|sW qK|	dd}d}|rzt|dd}ttdd}||}|d}W n ty   d}Y nw |	di di dd}|dv rW qK|| d  |||||d!i d"dd#}|| W qK ttfy } zW Y d}~qKd}~ww |r+t fd$d%|D }|r+t	 d   d&| d' W q tyI } zt	| d(|  W Y d}~qd}~ww t	d)t| d* |S )+z?Fetch today's fixtures from ESPN public API across all leagues.z%Y%m%dr   dates
   
User-AgentClawd-Pregame/1.0)paramstimeoutheaders   z: ESPN returned eventscompetitionsr   competitorsr   NteamdisplayNameshortDisplayNamer   homeAwayhomer   Zz+00:00)hoursz%I:%M %p ETstatustyper+   )STATUS_FINALSTATUS_FULL_TIMEr   venuefullName)r   awayleague
league_keytimedate_isor   r   c                    s    g | ]}|d   d kr|qS )r   r    .0fleague_infor   </sessions/lucid-sleepy-lamport/mnt/clawd/pregame_research.py
<listcomp>#  s     z"fetch_fixtures.<locals>.<listcomp>: z games todayu   : fixture fetch failed — zTotal: z fixtures across all leagues)r   today	isoformatstrftimeESPN_LEAGUESitemsrequestsgetstatus_codelogdebugjsonlenr   fromisoformatreplacer   r   
astimezone
ValueErrorappendKeyError
IndexErrorinfo	Exceptionwarning)	today_str	espn_dateall_fixturesr   r   r   respdatar   eventr   compr   	home_team	away_teamc	team_infor+   	game_datetime_strdt	et_offsetdt_etr   fixturee
game_countr   r   r   fetch_fixtures   s   



 r  c           	      C   s   g }z_t | }|dD ]Q}i }|d}|d}|d}|dur.|jr.|j |d< |durG|jrGtdd|j}| dd |d	< |durU|jrU|j |d< d|v r^|| qW |S  t j	yl   Y |S w )
zParse RSS XML.itemtitledescriptionlinkNz<[^>]+>r   r   summary)
ET
fromstringiterfindtextstripresubr  
ParseError)	contentr   rootr  entrytitle_eldesc_ellink_elcleanr   r   r   
_parse_rss0  s0   




r0  c                 C   sR   | sdS |   }t||  g}|D ]}|  |v r dS q|  |v r'dS dS )z+Check if text mentions a team by any alias.FT)lowerTEAM_ALIASESr   )r$  	team_name
text_loweraliasesaliasr   r   r   _matches_teamH  s   r7  c                 C   s  t dd | D }t dd | D }g }g }|r|td  |r(|td  |s,i S |D ]^}z=tj|d ddd	id
}|jdkrlt|j}|D ]}|d |d< qI||d |d t	
d|d  dt| d W q. ty }	 zt	
d|d  d|	  W Y d}	~	q.d}	~	ww i }
t| D ]\}}|d  d|d  }g }|D ]L}|d D ]E}|dd d|dd }t||d }t||d }|s|r|r|rdn|rdnd}||dd|dd|dd|d qqt }g }|D ]}|d  dd }||vr|| || q|jdd  d! |dd" |
|< qtd#d |
 D }t	d$| d%t|
 d& |
S )'z7Fetch RSS headlines and match them to today's fixtures.c                 s   s    | ]	}|d  dkV  qdS r   r*   Nr   r   r   r   r   	<genexpr>Y      z"fetch_headlines.<locals>.<genexpr>c                 s   s    | ]	}|d  dkV  qdS r8  r   r   r   r   r   r9  Z  r:  r,   r*   r      r   r   )r   r   r   r+   source)r+   r   zRSS r   z itemsu   : failed — Nr   @r   r   r  r    r  both)r  r  r<  	relevance2   c                 S   s   | d dkrdS dS )Nr@  r?  r   r   r   )xr   r   r   <lambda>  s    z!fetch_headlines.<locals>.<lambda>key   c                 s   s    | ]}t |V  qd S )N)r   )r   vr   r   r   r9    s    zMatched z headlines across z	 matchups)anyextend	RSS_FEEDSr   r   r   r0  r$  r  r   r   r   r  	enumerater7  setr1  addsortsumvaluesr  )fixtures
has_soccerhas_nhl	all_itemsfeeds_to_fetch	feed_infor
  r   r  r  fixture_headlinesir  rE  matchedfeedcombined
home_match
away_matchr@  seenuniquem	title_keytotal_headlinesr   r   r   fetch_headlinesV  st   

 &





rc  c           
      C   s   | d }| d }| d }|dkrQ|t v }|t v }|tv }|tv }|r+|r+ddddd	S |r6|r6d
dddd	S |s:|rJ|r>|n|}ddd| dd	S ddddd	S t|d}	|	dkrfdddd| dd	S |	dkrudddd| dd	S dddd| dd	S )z2Classify a fixture for Predictable Tempo strategy.r   r   r   r   
controlledr   primez,Both teams play structured, low-event hockey)tempotier
parlay_fitnotechaoticr   avoidu&   Both teams run-and-gun — skip undersmixedr   playablez anchors the paceneutralzNeither team strongly typed
structuredzTier 1 u    — prime under territoryzTier 2 u    — usable with right matchupopenzTier 3 u*    — high-scoring league, risky for unders)NHL_CONTROLLEDNHL_CHAOTICLEAGUE_TIERSr   )
r  r   r   r   	home_ctrl	away_ctrlhome_chaoticaway_chaoticanchorrg  r   r   r   classify_fixture  sH   


ry  c                 C   s  t std t| |S g }| D ]}|d  d|d  }t|}||g }d|d  d|d  d|d  d	|d
  d|d  d|d  d|d  d|d  }|r|d7 }|dd D ]-}|d|d   d|d  7 }|dr|d|d dd  7 }|d|d  7 }q]n|d7 }|| qt	 
d }	d!|}
d"|	 d#|
 d$}ztjd%t d&d'd(d)d*d+|d,gd-d.d/}|jd0kr7| d1 d2 d3  }z"t|}td4t|d5g  d6|d7i dd8  |W W S  tjy6   td9|}|r"t| }td:t|d5g  d; | Y W S td<|dd0   t| | Y W S w td=|j d>|jdd   t| |W S  tym } ztd?|  t| |W  Y d}~S d}~ww )@zJUse Claude Haiku to synthesize fixtures + headlines into a pregame report.uB   No Claude API key — generating basic report without AI synthesisr   r=  r   z
GAME:  @ z (r   z, r   z	)
TEMPO: rf  z (Tier rg  u   ) — ri  z
PARLAY FIT: rh  z
HEADLINES:N   z
  [r@  z] r  r  z
    d   u    — r<  z.
HEADLINES: No team-specific news found today.z%A, %B %d, %Y
z;You are Bill's personal pregame research analyst. Today is u0  .

Bill's strategy: "Predictable Tempo" — he bets soccer (and NHL) unders in structured, low-scoring environments. He builds parlays combining 2-4 legs that share a common narrative: defensive matchups, cagey form, tactical caution, weather, fatigue, or rivalry tension.

TODAY'S FIXTURES + HEADLINES:
u  

YOUR TASK — produce a JSON response with this exact structure:
{
  "parlayOfTheDay": {
    "title": "Short punchy name for the parlay (max 40 chars)",
    "legs": ["Leg 1 description", "Leg 2 description", "Leg 3 if applicable"],
    "narrative": "2-3 sentence storyline connecting ALL legs. Why do these games share a theme today? What's the common thread? Be specific — reference headlines, form, defensive records, tactical setups.",
    "confidence": 1-10,
    "risk_note": "One honest sentence about what could break this parlay"
  },
  "games": [
    {
      "matchup": "Away @ Home",
      "league": "League Name",
      "time": "Game time",
      "storyline": "2-3 sentences of pregame narrative. Reference specific headlines if available. Focus on WHY this game profile supports or doesn't support an under bet. Mention defensive form, tactical matchup, injuries from headlines, historical rivalry patterns.",
      "parlay_fit": "prime" or "playable" or "avoid",
      "under_line": "Suggested under line (e.g. Under 2.5, Under 5.5 for NHL)",
      "confidence": 1-10,
      "key_factor": "One-line summary of the decisive factor"
    }
  ]
}

RULES:
- ONLY reference facts from the HEADLINES provided. Do not invent injuries, transfers, or lineup info.
- If no headlines exist for a game, base your analysis on league tier and general team style.
- The Parlay of the Day should combine 2-4 legs with the STRONGEST narrative connection.
- Be honest about confidence. Tier 3 leagues = low confidence. No headlines = lower confidence.
- Keep storylines punchy and specific — Bill reads these like a racing form, not a textbook.
- Sort games by parlay_fit (prime first, avoid last).

Respond with ONLY the JSON. No markdown fences, no commentary.z%https://api.anthropic.com/v1/messagesz
2023-06-01zapplication/json)z	x-api-keyzanthropic-versionzcontent-typezclaude-haiku-4-5-20251001i  user)roler)  )model
max_tokensmessages   )r   r   r   r   r)  r   r$  zClaude report: gamesz games, parlay: parlayOfTheDaynonez\{[\s\S]*\}zClaude report (extracted): z gameszClaude returned non-JSON: zClaude API error r   zClaude call failed: )r   r   r  _build_basic_reportry  r   upperr  r   r   r   joinr   postr   r   r%  loadsr  r   JSONDecodeErrorr&  searchgroupr$  r  )rQ  	headlinesgames_contextr  rE  rf  news
game_blockhr   
games_textpromptr
  r)  reportmatchr  r   r   r   build_pregame_report  s   

 

(

2
"r  c                    s\  g }g }| D ]}}|d  d|d  }t |}||g }d}|r*d|d d  }|d d	kr2d
nd}	|d  d|d  |d |d |rO|d  d| n|d |d |	dddd|d d|d d}
||
 |d dkr||d  d|d  d|	  qddddd |j fddd d}t|dkrd |dd d!dd"d#}||d$S )%uB   Fallback report without Claude — uses tempo classification only.r   r=  r   r   z Headlines: r   r  r   r   z	Under 5.5z	Under 2.5rz  r   ri  .rh        r   )r   r   r   rg  )matchupr   r   	storylinerh  
under_line
confidence
key_factorre  r>  r   r   )re  rm  rn  rk  c                    s     | d dS )Nrh  r   r   )g	fit_orderr   r   rC  Y  s    z%_build_basic_report.<locals>.<lambda>rD  NzTempo Lock ParlayzEAll legs in Tier 1 structured leagues with controlled tempo profiles.uH   Based on tempo classification only — no headline validation available.)r  legs	narrativer  	risk_noter  r  )ry  r   r  rN  r   )rQ  r  r  
prime_legsr  rE  rf  r  headline_textr  gameparlayr   r  r   r  8  sD   

"

r  c              
   C   s   zVt  rtt  }ni }| d| dg t|t 	 dd|d< t 
tj|dd t| dg }| dp>i dd	}td
| d td|  W dS  tyq } ztd|  W Y d}~dS d}~ww )z0Write the pregame report to dashboard-data.json.r  r  zpregame_research.py)r  r  fixtureCountupdatedr<  pregamer   )indentr  r  zWrote z- game reports + parlay to dashboard-data.jsonzParlay of the Day: zFailed to write dashboard: N)DASHBOARD_JSONexistsr   r  	read_textr   r   r   nowr   
write_textdumpsr   r  r  error)r  rQ  r  r  parlay_titler  r   r   r   write_to_dashboardj  s&   


r  c               	   C   s   t d t d t dt   t d t } | s-t d td g dg  d S t| }t| |}t||  |dg }t	dd |D }t	d	d |D }t	d
d |D }t d| d| d| d t d d S )Nz<============================================================u2   Pregame Research — Storyline-Driven Parlay IntelzDate: u)   No fixtures today — nothing to researchr  r  c                 s   "    | ]}| d dkrdV  qdS )rh  re  r   Nr  r   r  r   r   r   r9         zmain.<locals>.<genexpr>c                 s   r  )rh  rm  r   Nr  r  r   r   r   r9    r  c                 s   r  )rh  rk  r   Nr  r  r   r   r   r9    r  z	Summary: u
    prime · u    playable · z avoidzDone!)
r   r  r   r   r  r  rc  r  r   rO  )rQ  r  r  r  re  rm  rk  r   r   r   main  s$   





r  __main__)2__doc__ossysr   loggingr&  xml.etree.ElementTreeetreeElementTreer   pathlibr   r   r   r   r   r   ImportErrorprintexitdotenvr   __file__resolveparentbasicConfigINFO	getLoggerr   getenvr   r  LOGS_DIRmkdirr   rJ  r2  rs  rq  rr  r  r0  r7  rc  ry  r  r  r  r  __name__r   r   r   r   <module>   s  
		
 "#$%&'()+,-./02345689:<=>?@BCDEFGHIJLMNOPQRSTUVWXYZ[
\
]^d


	YG(s2
