
    Ájd5                       d Z ddlmZ ddlZddlZddlZddlZddlZddl	ZdZ
dZdddd	d
dddd	ddd	ddiddiddd	ddd	dg dddddddgddZdZd!d"dZd#dZd$dZd%d&dZd'd(dZd Zd)dZ	 d*	 	 	 d+dZ	 d,	 	 	 d-d Zy).a,  Backfill sports_teams and match_level from Wikipedia.

The two factors that decide whether a broadcast is a blockbuster -- who played
and what stage it was -- are missing from 60%/70% of the real-match rows in
global_sports, and absent entirely from a raw EPG. They are, however, public
knowledge: a tournament's Wikipedia page lists every fixture with its date,
teams and stage.

Flow, mirroring the one proven in crystalcast/index.php:

    suggest_url(event, season)   -> a best-guess Wikipedia page, pre-filled for
                                    the analyst rather than asked for cold
    fetch_wikitext(url)          -> ?action=raw, not rendered HTML
    compact_wikitext(text)       -> keep section headers + match-template lines
    parse_fixtures(client, ...)  -> Claude turns that into structured fixtures

Nothing is committed without a human looking at it: the caller previews the
fixtures, edits them, and only then confirms. Results are written to the
`crystal` application database -- NEVER to tvviewers, which stays read-only.

The same fixtures serve both directions: historical rows (so the engine can
learn what a marquee fixture is worth) and future schedule rows (so it can
recognise one). Enriching only the forecast side would give the model the
question without ever having taught it the answer.
    )annotationsNz"https://en.wikipedia.org/w/api.phpzRGSIQ-Crystal2/1.0 (broadcast audience estimation; contact denson.joseph@gmail.com)objectstringzHThe tournament as the page names it, e.g. '2026 ICC Men's T20 World Cup')typedescriptionarrayz(ISO date YYYY-MM-DD, or empty if unknownz8Local start time HH:MM if the page gives one, else emptyr   zOne of GROUP, LEAGUE, QUALIFIER, ROUND_OF_32, ROUND_OF_16, QUARTER_FINAL, SEMI_FINAL, FINAL, THIRD_PLACE, SUPER_8, SUPER_4, PLAYOFF, ELIMINATOR, REGULAR, WARMUP, or UNKNOWNz!Match number if given, else empty)
match_date
match_timeteam1team2stagematch_noF)r   
propertiesrequiredadditionalProperties)r   items)event_labelfixturesr   r   a[  Below is the compacted wikitext of a Wikipedia page for a sports tournament.

Extract every individual match/fixture you can find. For each one give:
- match_date  : the calendar date it was played (YYYY-MM-DD). Use the season year {season} if the wikitext gives only a day and month. Empty string if genuinely absent.
- match_time  : the local start time as HH:MM if the page states one, else empty string.
- team1, team2: the two competing teams, as full names (not abbreviations) where possible.
- stage       : which round of the tournament, using the controlled vocabulary in the schema.
- match_no    : the match number if the page numbers them, else empty string.

Also give event_label: the tournament's own name as this page states it.

Rules:
- Only real fixtures between two teams. Skip standings tables, squad lists, venue
  lists, broadcast-rights tables, and prose.
- Do not invent fixtures. If the page has none, return an empty list.
- A match listed with a date range or "TBD" gets an empty match_date rather than a guess.

Event as our EPG names it: {event}
Season: {season}

WIKITEXT:
{wikitext}
c                    t         j                  j                  | dt        i      }t         j                  j	                  ||      5 }|j                         j                  dd      cd d d        S # 1 sw Y   y xY w)Nz
User-Agent)headers)timeoutzutf-8replace)errors)urllibrequestRequestUAurlopenreaddecode)urlr   reqrs       4/var/www/html/crystal/crystal2/engine/wiki_enrich.py_getr%   d   sd    
..
 
 |R.@
 
AC			W		5 :vvxwy9: : :s   	!A44A=c                \   |r| d|  n| }t          dt        j                  j                  |       d}	 t	        j
                  t        |            j                  di       j                  dg       }|sy|r|D ]  }t        |      |d   v s|d   c S  |d   d   S # t        $ r Y yw xY w)	z1Search Wikipedia for the tournament's page title. z#?action=query&list=search&srsearch=z&srlimit=5&format=jsonquerysearchNtitler   )
WIKI_APIr   parsequotejsonloadsr%   get	Exceptionstr)eventseasonqr!   hitshs         r$   	find_pager8   j   s    %6(!E75AZ:ll  #$$:<Czz$s)$(("599(BG   	"A6{aj(z!	" 77  s   >B 	B+*B+c                    t        | |      }|s=|r| d|  n| }dt        j                  j                  |j	                  dd            z   S dt        j                  j                  |j	                  dd            z   S )zA pre-filled guess for the analyst to accept or correct.

    Always returns something: an empty box makes the analyst do the search, and
    a wrong guess is cheaper to fix than a blank one is to fill.
    r'   zhttps://en.wikipedia.org/wiki/_)r8   r   r,   r-   r   )r3   r4   r*   r5   s       r$   suggest_urlr;   ~   ss     eV$E#)vhawu/&,,2D2DQYYsTWEX2YYY+fll.@.@sTWAX.YYY    c                D   t        j                  d| t         j                        }|rF|j                  d      t        j
                  j                  |j                  d            }}d| d}n(| j                  d      r	 t        |       d| S t        | }}| dt        j
                  j                  |       d	}	 t        j                  t        |            }|j                  d
i       j                  d      }|r|d| S dS # t        $ r Y yw xY w# t        $ r Y yw xY w)zCRaw wikitext is far smaller and more structured than rendered HTML.z8^https?://([a-z]+)\.(?:m\.)?wikipedia\.org/wiki/([^?#]+)      zhttps://z.wikipedia.org/w/api.phphttpNz?action=parse&page=z6&prop=wikitext&formatversion=2&format=json&redirects=1r,   wikitext)rematchIgroupr   r,   unquote
startswithr%   r1   r+   r-   r.   r/   r0   )	url_or_title	max_charsmlangr*   apir!   datatexts	            r$   fetch_wikitextrO      s   
Ll\^\`\`aAggaj&,,"6"6qwwqz"Be67		 	 	(	%jy11 |UU%fll&8&8&?%@CECzz$s)$ 88GR $$Z0D#4
--  		  s$    D <D 	DD	DDc                .   | syt        j                  dt         j                        }g }| j                  d      D ]  }|j	                         }t        j
                  d|      r2|j                  d|j	                  d      j	                         z          \|j                  |      snt        j
                  d|t         j                        s%t        j
                  d|t         j                        r|j                  d	       t        j                  d
d|t         j                        }|j                  |dd        t        |      |k\  s n dj                  |      }d	|v r|d| S g }| j                  d      D ]  }t        j                  dd|j	                               }dt        |      cxk  rdk  sn ?t        t        j                  d|t         j                        xs t        j                  d|            }t        t        j                  d|t         j                        xs t        j                  d|            }	|r|	r|j                  |       t        |      dk\  s n dj                  |      }t        |      dkD  r|dd S dS )a  Throw away everything that is not a fixture before the model sees it.

    Keeps section headers (they carry the stage: "Semi-finals", "Final"), any
    line declaring a match template, and any line carrying team/date/time/score
    parameters. Falls back to a looser date+versus filter when the page does not
    use match templates at all, so non-template pages still yield something.
     ze^\|\s*(team1|team2|date|time|round|stage|score1|score2|home ?team|away ?team|venue|aet|penalties)\s*=
z^=+\s*(.+?)\s*=+$z	SECTION: z= z^\|\s*team1\s*=z^\|\s*home ?team\s*=z--- match ---z<ref[^>]*>.*?</ref>|<ref[^>]*/>)flagsN   z\s+r'      ,  zb\b(\d{1,2}\s+)?(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\.?\s*\d{0,2}(,?\s*\d{4})?\bz/\d{4}-\d{2}-\d{2}|\d{1,2}[/.]\d{1,2}[/.]\d{2,4}u   \bvs?\.?\b|\bv\b|–|—z\d{1,2}[:.]\d{2}i  (   >  )rB   compilerD   splitstriprC   appendsubSlenjoinboolr)   )
rN   	max_linesrI   WANTEDkeeprawsouthas_date	has_matchs
             r$   compact_wikitextrj      s.     ZZ JKM44QFDzz$ IIK88(!,KKaggdm&9&9&;;<||A88&2440BHH=TVWY[Y]Y]4^KK(FF5r1BDDIAdsGt9	! ))D/C #:I Dzz$ FF63		,SV"s"		 #HIJBDDR \99%WYZ[] #>244H >II&91=?		KKNt9 ))D/Cs8b=3w<0b0r<   c                    ddl } t        j                  j                  d      xs d}|sddlm}  |t              j                         j                  j                  dz  dz  }|j                         r6t        j                  |j                               j                  d      xs d}|st        d	      | j                  |
      S )a-  Anthropic client.

    Key resolution order: the environment first (what a developer shell has),
    then worker/config.json. The config route matters because the worker and the
    enrichment runner are started by Apache/systemd, neither of which inherits an
    interactive shell's environment.
    r   NANTHROPIC_API_KEYrQ   )Pathworkerzconfig.jsonanthropic_api_keyz^No Anthropic API key. Set ANTHROPIC_API_KEY, or add "anthropic_api_key" to worker/config.json.)api_key)	anthropicosenvironr0   pathlibrm   __file__resolveparentexistsr.   r/   	read_textRuntimeError	Anthropic)rq   keyrm   cfg_paths       r$   _clientr~      s     
**..,
-
3C >))+2299HD}T??**X//12667JKQrC9: 	: s++r<   c                   | j                   j                  ddddiddt        ddd	t        j	                  ||xs d
|      dg      }|j
                  dk(  rg dfS t        d |j                  D        d      }	 t        j                  |      }|j                  dg       |j                  dd      fS # t        j                  $ r g dfcY S w xY w)z=Have Claude turn compacted wikitext into structured fixtures.zclaude-opus-5rX   r   adaptivemediumjson_schema)r   schema)effortformatuserunknown)r3   r4   rA   )rolecontent)model
max_tokensthinkingoutput_configmessagesrefusalrQ   c              3  T   K   | ]   }|j                   d k(  s|j                   " yw)rN   N)r   rN   ).0bs     r$   	<genexpr>z!parse_fixtures.<locals>.<genexpr>  s     AA&0@As   ((r   r   )r   createFIXTURE_SCHEMAPROMPTr   stop_reasonnextr   r.   r/   JSONDecodeErrorr0   )clientr3   r4   rA   msgrN   rM   s          r$   parse_fixturesr      s    
//
 
 *%!)*7>!RT!fmm 3)h /< /H I J ! C )#2vAA2FDzz$ 88J#TXXmR%@@@  2vs   <B5 5CCc                Z   |sddg dddS t        |      }|sd|g dddS t        j                  |       t        |      }|sd|g dddS 	 t	        | |||      \  }}|rdnd|||t        |       ddS # t
        $ r-}	d	|g dt        |	      j                   d
|	 dd dcY d}	~	S d}	~	ww xY w)zOne event, one page. Network-and-AI only; no DB writes.

    Returns {status, url, title, fixtures, label, note} so the caller can show
    the analyst exactly what happened for each event it looked up.
    no_urlrQ   zno page given)statusr!   r   labelnoteno_pagezcould not read that pageno_fixturesz"page has no fixture-shaped contenterrorz: NrV   okz fixtures read)	rO   timesleeprj   r   r1   r   __name__r_   )
r   r3   r4   r!   r   wtcompactfxr   excs
             r$   enrich_from_urlr   
  s     "22Tcdd		B#CR"24 	4JJur"G'b<> 	>>"65&'B	E !dmCRs2wi~$>@ @  >!#29--.b6t<> 	>>s   A4 4	B*="B%B*%B*c                    t        ||      }|sg ddfS t        j                  |       t        |      }|sg d|fS t	        | ||t        |            \  }}||rd|fS d|fS )z:Search-then-parse, used by the historical backfill runner.r   zno Wikipedia search hitno_wikitextr   r   )r8   r   r   rO   r   rj   )r   r3   r4   r   r*   r   r   r:   s           r$   enrich_event_seasonr   &  sz     eV$E9777JJu		B=%''65&2B22FGEB5555r<   )   )r!   r2   r   intreturnr2   )r3   r2   r4   
int | Noner   
str | None)r3   r2   r4   r   r   r2   )i )rH   r2   rI   r   r   r   )i	  i`  )rN   r2   rb   r   rI   r   r   r2   )r3   r2   r4   r   rA   r2   r   ztuple[list[dict], str])g333333?)
r3   r2   r4   r   r!   r2   r   floatr   dict)g      ?)r3   r2   r4   r   r   r   r   ztuple[list[dict], str, str])__doc__
__future__r   r.   rr   rB   r   urllib.parser   urllib.requestr+   r   r   r   r%   r8   r;   rO   rj   r~   r   r   r    r<   r$   <module>r      s  2 #  	 	   /Y
  ((HI  +32\#^+32l#n$h/$h/&..WX
 *2Be f `(-#
	6 
+!=B
6:(
Z.271v,,A, $'@ @+/@: (+6$6/J6r<   