
    \j#              	          S r SSK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rSSK	r	SSK
r
SSKrSSKJr  \R                  R                  S\" \" \5      R%                  5       R&                  5      5        SSKJr  Sr/ SQrS	rS
rSS0rSrSrS\R:                  S\\   S\\   4S jrS\R@                  S\S\S\!S-  4S jr"S\S\S\S-  S\#S-  4S jr$S\#4S jr%\&S:X  a  \RN                  " \%" 5       5        gg)u  
Tribunale dei tipster — raccoglitore per mondopengwin.it.

Scarica gli articoli di pronostico (pubblici, uno per partita), estrae con
l'LLM la pick in formato strutturato e la salva nella tabella tips con il
NOSTRO timestamp di raccolta (scraped_at): è quello che rende la misurazione
onesta ("che quote c'erano quando potevamo agire sulla pick?").

L'LLM qui fa SOLO estrazione di testo -> JSON (nessuna ricerca web, nessuna
opinione): mercato, selezione, quota consigliata, partita, orario.

Uso:
    python scripts/tipster_pengwin.py                       # tutte le leghe
    python scripts/tipster_pengwin.py --leagues serie-a --max-articles 3
    N)datetime)Path)BeautifulSoup)load_api_keyzhttps://www.mondopengwin.it)zserie-azserie-bzpremier-league
bundesligazla-ligazligue-1zchampions-leaguezclaude-opus-4-8Pengwinz
User-AgentzkMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36a  
CREATE TABLE IF NOT EXISTS tipsters (
    id          SMALLINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name        VARCHAR(50)  NOT NULL UNIQUE,
    source_url  VARCHAR(200) NULL,
    notes       VARCHAR(255) NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS tips (
    id             INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tipster_id     SMALLINT UNSIGNED NOT NULL,
    match_id       INT UNSIGNED NULL,
    article_url    VARCHAR(300) NOT NULL,
    published_date DATE     NULL,
    scraped_at     DATETIME NOT NULL,
    home_team      VARCHAR(60) NULL,
    away_team      VARCHAR(60) NULL,
    kickoff        DATETIME NULL,
    market         VARCHAR(30) NULL,
    selection      VARCHAR(60) NULL,
    line           DECIMAL(4,1) NULL,
    odds           DECIMAL(7,3) NULL,
    raw_text       VARCHAR(300) NULL,
    outcome        ENUM('win','loss','void','unsettled') NOT NULL DEFAULT 'unsettled',
    UNIQUE KEY uq_tip (tipster_id, article_url),
    KEY idx_match (match_id),
    CONSTRAINT fk_tips_tipster FOREIGN KEY (tipster_id) REFERENCES tipsters (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
u  Estrai dal seguente articolo di pronostico calcistico i dati
strutturati. Rispondi SOLO con un blocco ```json``` con questo schema:
{
  "home": "nome squadra di casa",
  "away": "nome squadra ospite",
  "kickoff": "YYYY-MM-DD HH:MM" oppure null se non deducibile,
  "published_date": "YYYY-MM-DD" oppure null,
  "tip": {
    "raw": "la frase esatta del pronostico consigliato",
    "market": "1x2" | "double_chance" | "over_under" | "btts" |
              "multigol_total" | "multigol_home" | "multigol_away" | "other",
    "selection": "1"|"X"|"2"|"1X"|"X2"|"12"|"over"|"under"|"gol"|"nogol"|"1-3" ecc.,
    "line": 2.5 (solo per over_under, altrimenti null),
    "odds": 1.30 (la quota consigliata, null se assente)
  }
}
Il pronostico consigliato è tipicamente nella sezione "ANALISI E PRONOSTICO".
Se l'articolo contiene più consigli, estrai SOLO quello principale/finale.
Se non c'è un pronostico chiaro, "tip" deve essere null. Non inventare quote.

ARTICOLO:
sessionslugsreturnc                 $   / nU H  n U R                  [         SU S3[        SS9nUR                  S:w  a  M5  [	        UR
                  S5      nUR                  S5       Ha  nUS   nS	U;   d  M  UR                  S5      (       a	  [        U-   nUR                  UR                  S
5      S   R                  S5      5        Mc     M     [        [        U5      5      $ ! [        R                   a  n[        SU SU 35         S nAGM  S nAff = f)Nz/pronostici/calcio//   headerstimeout   lxmlza[href]hrefzstatistiche-quote-e-pronostico?r   z  [errore indice z] )getBASEHEADERSstatus_coder   textselect
startswithappendsplitrstriprequestsRequestExceptionprintsortedset)	r	   r
   urlsslugrespsoupar   excs	            2C:\wamp64\www\Tigertips\scripts\tipster_pengwin.pyget_article_urlsr,   a   s   D	5;;$':4&B'.  <D3& F3D[[+y3t;s++#d{KK

3 2 9 9# >? ,  #d) (( 	5%dV2cU344	5s#   /C3C1ACD2D

Dclientmodelarticle_textc           	         U R                   R                  USS[        US S -   S./S9nUR                  S:X  a  g SR	                  S UR
                   5       5      n[        R                  " S	U[        R                  5      nU(       a  UR                  S
5      O$XDR                  S5      UR                  S5      S
-    n [        R                  " U5      $ ! [        R                   a     g f = f)Ni  useri.  )rolecontent)r.   
max_tokensmessagesrefusal c              3   \   #    U  H"  oR                   S :X  d  M  UR                  v   M$     g7f)r   N)typer   ).0bs     r+   	<genexpr>extract_tip.<locals>.<genexpr>{   s!     H#3avv7G6166#3s   ,,z```json\s*(\{.*?\})\s*```   {})r5   createEXTRACT_PROMPTstop_reasonjoinr3   researchDOTALLgroupfindrfindjsonloadsJSONDecodeError)r-   r.   r/   responser   mraws          r+   extract_tiprQ   u   s    %%!n|FU?S.STU & WH y(77H8#3#3HHD
		.bii@A!''!*tIIcN4::c?Q3FGCzz# s   C C.-C.homeawaykickoffc                    U(       a  U(       d  gU(       a  SOSnSUSS  S3SUSS  S3/nU(       a  UR                  USS 5        U R                  SU S3XX"/U(       a  USS /O/ -   5        U R                  5       nU(       a  US	   $ S$ )
zDAggancio best-effort alla tabella matches (nomi esatti o contenuti).NzAND m.match_date = %sr7   %   
   a  
        SELECT m.id FROM matches m
        JOIN teams h ON h.id = m.home_team_id
        JOIN teams a ON a.id = m.away_team_id
        WHERE (h.name = %s OR %s LIKE CONCAT('%%', h.name, '%%'))
          AND (a.name = %s OR %s LIKE CONCAT('%%', a.name, '%%'))
          z0
        ORDER BY m.match_date DESC LIMIT 1
    r   )r   executefetchone)currR   rS   rT   date_clauseparamsrows          r+   find_match_idr_      s    T-4)"K$s)A!D"I;a 01Fgcrl#KK  -  d	!wgcrl^B	GI ,,.C3q6"d"    c                  	   [         R                  " SS9n U R                  SS[        S9  U R                  S[        SS9  U R                  S	[
        S
9  [        R                  " U 5        U R                  5       n[        5       nU(       d  [        S5        g[        R                  " US9n[        R                  " UR                  UR                   UR"                  UR$                  SS9nUR'                  5       n[(        R+                  S5       H+  nUR-                  5       (       d  M  UR/                  U5        M-     UR/                  S[0        [2        45        UR5                  5         UR/                  S[0        45        UR7                  5       S   n[8        R:                  " 5       n[=        XR>                  5      n	UR/                  SU45        URA                  5        V
s1 s H  oS   iM	     nn
U	 Vs/ s H  oU;  d  M
  UPM     snS URB                   n[        S[E        U	5       S[E        U5       35        SnU GH7  n URG                  U[H        SS9nURK                  5         [O        URP                  S5      nURS                  SSS9n[U        X1RV                  U5      nU(       a  URG                  S5      (       d#  [        S URY                  S!S5      S"    35        M  US   n[[        UURG                  S#5      URG                  S$5      URG                  S%5      5      nUR/                  S&UUUURG                  S'5      [\        R^                  " 5       URG                  S#5      URG                  S$5      URG                  S%5      URG                  S(5      URG                  S)5      URG                  S*5      URG                  S+5      URG                  S,5      =(       d    S-S S. 45        UR5                  5         US-  nU(       a  S/U 3OS0n[        S1URG                  S#5       S2URG                  S$5       SURG                  S(5       S!URG                  S)5       S3URG                  S+5       S4U S535        GM:     [        S6U 35        URa                  5         gs  sn
f s  snf ! [8        RL                   a  n[        SU SU 35         S nAGM  S nAff = f)7NzRaccolta pick Pengwin)descriptionz	--leagues+)nargsdefaultz--max-articles2   )r9   re   z--model)re   zManca ANTHROPIC_API_KEYr>   )api_key	tigertips)hostportr1   passworddatabase;z>INSERT IGNORE INTO tipsters (name, source_url) VALUES (%s, %s)z'SELECT id FROM tipsters WHERE name = %sr   z2SELECT article_url FROM tips WHERE tipster_id = %szArticoli trovati: z | nuovi da processare: r   r   z  [errore] z: r    T)striptipz  [senza pick] r   rR   rS   rT   a  
            INSERT IGNORE INTO tips
                (tipster_id, match_id, article_url, published_date, scraped_at,
                 home_team, away_team, kickoff, market, selection, line, odds, raw_text)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
        published_datemarket	selectionlineoddsrP   r7   i,  z	match_id=znon agganciataz  [ok] -z @ z ()z
Pick salvate: )1argparseArgumentParseradd_argumentLEAGUE_SLUGSintDEFAULT_MODELconfigadd_db_args
parse_argsr   r"   	anthropic	Anthropicpymysqlconnectri   rj   r1   rk   cursorSCHEMAr   ro   rY   TIPSTER_NAMEr   commitrZ   r    Sessionr,   leaguesfetchallmax_articleslenr   r   raise_for_statusr!   r   r   get_textrQ   r.   rsplitr_   r   nowclose)parserargsrg   r-   connr[   stmt
tipster_idr	   r%   rseenunew_urlsn_savedurlr'   r*   r(   r   datarp   match_idlinkeds                           r+   mainr      s   $$1HIF
3E
(sB?
	=9
vDnG'(  1F??						$(MMKID
++-CS!::<<KK " KKPt$&KKMKK9L?K"J GG\\2DKKDzmT,,.).QaD.D)14aD=412D43D3DEH	s4yk)A#h-
QRG	;;sGR;@D!!# TYY/}}S}-6::t4488E??OCJJsA$6r$:#;<=5k dhhv&68H!%)!46 
 (C2B)Cllndhhv.0@hhy!3778#4cggk6Jggfoswwv1E2t0LN	O 	1+39XJ'9I()488F+;*<B"#1SWW[%9$:#cggfo=NbQWPXXY[ 	\9 > 
WI
&'JJLM *1 (( 	KuBse,-	s*   #R7	R!R!%R&&S:SS__main__)(__doc__ry   rK   rE   sysr   pathlibr   r   r   r   r    bs4r   pathinsertstr__file__resolveparentagent_formazionir   r   r|   r~   r   r   r   rB   r   listr,   r   dictrQ   r}   r_   r   __name__exit r`   r+   <module>r      s8      	 
        3tH~--/667 8 )$:! ] ^
<0h.. tCy T#Y (	++ C s tVZ{ #S # #cDj #S4Z #*Cc CL zHHTV r`   