
    9Lj|&                     4   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ZddlZddlZddl	Z
ddlmZ ddiZej                            ej                            ej                            e                    d          Z ej        ed           d	d
dddddddddddddddddddZdIdZdIdZd ZdJdZdKdZddlZddlZi ddddd d!d"d#d$d#d%d&d'd(d)d*d+d,d-d.d/d0d1d2d3d4d5d6d7d8d9d:d;d<d=d>d?Zd@ ZdA ZdB ZdLdDZdE Z dF Z!dG Z"dH Z#dS )Ma  Data sources for the sports predictor.

- OpenLigaDB (keyless): Bundesliga fixtures + full history + live table
- Wikipedia REST (keyless): current-season standings for any top league
- the-odds-api (key via env ODDS_API_KEY): upcoming fixtures + bookmaker odds

All sources return normalized dicts:
  fixture: {league, home, away, kickoff_utc, group}
  result:  {league, home, away, kickoff_utc, hg, ag}
  standings: {team_name: ppg}   (league avg : '_avg')
    N)quotez
User-Agentz4Mozilla/5.0 (X11; Linux x86_64) sports-predictor/1.0z.cacheT)exist_okbl1u   2026–27 Bundesligasoccer_germany_bundesliga)
openligadb
wiki_titleodds_keyu   2026–27 Premier League
soccer_eplu   2026–27 La Ligasoccer_spain_la_ligau   2026–27 Serie Asoccer_italy_serie_au   2026–27 Ligue 1soccer_france_ligue_one)
BundesligazPremier LeaguezLa LigazSerie AzLigue 1   c                     t           j                            | t                    }t           j                            ||          5 }|                                cd d d            S # 1 swxY w Y   d S )N)headers)timeout)urllibrequestRequestUAurlopenread)urlr   reqrs       1/root/.hermes/scripts/sports-predictor/sources.py_getr   8   s    
.
 
 b
 
1
1C			W		5	5 vvxx                 s   A))A-0A-c                 n    t          j        t          | |                              dd                    S )Nutf-8replace)jsonloadsr   decode)r   r   s     r   	_get_jsonr$   >   s,    :d3((//CCDDD    c                 .   t           j                            t          | dz             }t           j                            |          rt          j                    t           j                            |          z
  |k     rT	 t          |          5 }t          j	        |          cd d d            S # 1 swxY w Y   n# t          t          f$ r Y nw xY w || }t          |d          5 }t          j        ||           d d d            n# 1 swxY w Y   |S )Nz.jsonw)ospathjoin	CACHE_DIRexiststimegetmtimeopenr!   load
ValueErrorOSErrordump)keyttl_sfetchargsr)   fdatas          r   _cacher:   B   su   7<<	3=11D	w~~d 	bg.>.>t.D.D Du L L	d $qy||$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $G$ 	 	 	D	5$<D	dC A	$              KsH    B< B0#B< 0B44B< 7B48B< <CC(D

DDc                     dS )N   )shortcuts    r   openligadb_current_seasonr?   Q   s    4r%   r<   c                    t          d|  d|           }g }|D ]}t          |                    dg                     }|                    | |d         d         |d         d         |                    d          |                    d          pi                     d	          t	          |                    d
                    |r|d         nd |r|d         nd d           |S )Nz'https://api.openligadb.de/getmatchdata//matchResultsteam1teamNameteam2matchDateTimeUTCgroupgroupOrderIDmatchIsFinishedr      )leaguehomeawaykickoff_utcrG   finishedhgag)r$   _final_scoregetappendbool)r>   seasonrawoutgress         r   openligadb_matchesr[   U   s    
QhQQQQ
R
RC
C  1554455

gJz*gJz*55!344eeGnn*//??QUU#45566)#a&&T)#a&&T	
 	
 		 		 		 		 Jr%   internazionaleinterzinter milanzac milanmilanzparis sgzparis saint-germainpsgz
m'gladbachzborussia monchengladbachzreal betis balompiez
real betiszrcd espanyolespanyolz
ca osasunaosasunazrcd mallorcamallorcazdeportivo alavesalavesz
cd leganesleganeszafc bournemouthbournemouthzbrighton & hove albionzbrighton and hove albionzwest ham unitedzwest hamztottenham hotspur	tottenhamznewcastle united	newcastlemanchester citymanchester united)rh   ri   c                 .   t          j        d| pd          }|                    dd                                          }t	          j        dd|                                                                          }t          	                    ||          }|S )NNFKD asciiignorez
[^a-z0-9]+ )
unicodedata	normalizeencoder#   resublowerstrip_TEAM_ALIASESrS   )namess     r   normalize_teamrz      sx    fdjb11A	(##**,,A
}c17799--3355A!QAHr%   c                    t          |           |v rS |D ]}t          |          }|k    r|c S |D ]C}t          |          }t                    dk    rt          |          dk    r|v s|v r|c S Dt          |fd          }|r;t          j        dt          |                                                    dk    r|S dS )z4Return the standings key matching src_name, or None.   c                 n    t          j        d t          |                                                     S )N)difflibSequenceMatcherrz   ratio)ry   ns    r   <lambda>zmatch_team.<locals>.<lambda>   s/    (?a"")$ )$$)EGG r%   )r4   Ng333333?)rz   lenmaxr~   r   r   )src_name	standingsr4   kbestr   s        @r   
match_teamr      s   x  AI~~  366JJJ   3q66Q;;3q66Q;;AFFa1ffJJJy , , , , - - -D 'a1E1EFFLLNNQUUU4r%   c                 ^   | D ]G}|                     d          dk    r,|                     d          |                     d          fc S H| D ]_}|                     d          pd                                }d|v sd|v r,|                     d          |                     d          fc S `d S )	NresultTypeID   pointsTeam1pointsTeam2
resultNamerl   endergebnisfinal)rS   ru   )match_resultsmrrx   s      r   rR   rR      s     @ @66.!!Q&&66-(("&&*?*????? ' @ @|$$*1133D  GtOO66-(("&&*?*????? %44r%   `T  c           
            fd}t          t          dt          j        dd           z   ||                    }d |D             S )zCCurrent standings table from the league's Wikipedia season article.c            
         dt          
d          z   } t          |                               dd          }g }t          j        d|t          j                  D ]}d|vsd|vrt          j        d	|t          j                  }|d
d          D ]}d t          j        d|t          j                  D             }t          |          dk    r|d                                         rt          j	        t          j
        dd|d
                                                             }t          |d                   }t          |d                   }	|dk    r|                    ||	|z  |f           |st          d
           |S )Nz/https://en.wikipedia.org/api/rest_v1/page/html/rl   )safer   r    z<table[^>]*>.*?</table>PldPtsz<tr[^>]*>(.*?)</tr>rJ   c                     g | ]>}t          j        d dt          j        dd|                                                    ?S )z\s+ro   z<[^>]+>)rs   rt   rv   ).0cs     r   
<listcomp>z6wikipedia_standings.<locals>.fetch.<locals>.<listcomp>   sV     U U U RVJQ-G-GHHNNPP U U Ur%   z<t[dh][^>]*>(.*?)</t[dh]>
   r   z\[.*?\]r   	   zno standings table found for )r   r   r#   rs   findallSr   isdigithtmlunescapert   rv   intrT   r1   )r   pagerows_outtrowsr   cellsteampldptsr   s             r   r6   z"wikipedia_standings.<locals>.fetch   s   @z+++,Cyy336bdCC 	@ 	@AA~~a:4a>>D!""X @ @U U"$*-I1bd"S"SU U Uu::##a(8(8(:(:#=
Ba)I)I)O)O)Q)QRRDeAh--Cs58}}Qww sSy#(>???@  	KIZIIJJJr%   wiki_z[^A-Za-z0-9]+_c                      i | ]\  }}}|||fS r=   r=   )r   r   vr   s       r   
<dictcomp>z'wikipedia_standings.<locals>.<dictcomp>   s1       aAAq6  r%   )listr:   rs   rt   )r   r5   r6   r   s   `   r   wikipedia_standingsr      su        * w(8#z!J!JJe% % & &D !%   r%   c                    |                      d          rE| d         }t          |          }t          d| d|           }d |D             }d |D             }n^t          t	          | d                             }d |                                D             }d |                                D             }|r1t          |                                          t          |          z  nd	}||d
<   ||d<   |S )z@Return {team: ppg, '_avg': avg_ppg, '_n': {team: games_played}}.r   z%https://api.openligadb.de/getbltable/rA   c                 r    i | ]4}|                     d d          dk    |d         |d         |d          z  5S )matchesr   rD   pointsrS   r   r   s     r   r   z&standings_with_avg.<locals>.<dictcomp>   sM     7 7 7aeeIq11A55 
mQx[1Y<7555r%   c                 `    i | ]+}|                     d d          dk    |d         |d          ,S )r   r   rD   r   r   s     r   r   z&standings_with_avg.<locals>.<dictcomp>   sD     : : :y!!4!4q!8!8 :)!8!8!8r%   r   c                 &    i | ]\  }}||d          S )rJ   r=   r   r   r   s      r   r   z&standings_with_avg.<locals>.<dictcomp>   s"    000TQAaD000r%   c                 &    i | ]\  }}||d          S )r   r=   r   s      r   r   z&standings_with_avg.<locals>.<dictcomp>   s"    ---$!Qa1---r%         ?_avg_n)	rS   r?   r$   dictr   itemssumvaluesr   )
league_cfgr>   rV   rW   stn_mapavgs          r   standings_with_avgr      s   ~~l## .l+*844SSS6SSTT7 77 7 7: :: : : %j&>??@@00RXXZZ000--"((**---(*
3#biikk

SWW
$
$CBvJBtHIr%   c                  f    t           j                            dd                                          S )NODDS_API_KEYrl   )r(   environrS   rv   r=   r%   r   oddsapi_keyr      s$    :>>."--33555r%   c                 h   t                      }d| d|d}t          |          }g }|D ]}g |                    dg           D ]}|                    dg           D ]}|d         dk    rd |d	         D             }t          |          d
k    s+t	          d |                                D                       r`t          d |                                D                       }	d|	cxk    rdk    rn                     |           d}
r9|                    d          d|                    d          g}fd|D             }
|                    | |                    d          |                    d          |                    d          |
t                    d           |S )z6Raw fetch for a league's h2h odds (called via _cache).z'https://api.the-odds-api.com/v4/sports/z/odds/?apiKey=z*&regions=eu&markets=h2h&oddsFormat=decimal
bookmakersmarketsr4   h2hc                 ,    i | ]}|d          |d         S )rx   pricer=   )r   xs     r   r   z"_oddsapi_fetch.<locals>.<dictcomp>   s"    BBBqQvY'
BBBr%   outcomes   c              3   "   K   | ]
}|d k    V  dS r   Nr=   r   r   s     r   	<genexpr>z!_oddsapi_fetch.<locals>.<genexpr>   s&      %C%C1a3h%C%C%C%C%C%Cr%   c              3       K   | ]	}d |z  V  
dS r   r=   r   s     r   r   z!_oddsapi_fetch.<locals>.<genexpr>   s&      99S1W999999r%   g\(\?g      ?N	home_teamDraw	away_teamc                 T    i | ]#t          j        fd D                       $S )c                 (    g | ]}|v |         S r=   r=   )r   blr   s     r   r   z-_oddsapi_fetch.<locals>.<dictcomp>.<listcomp>   s"    7772qBwwAwwwr%   )
statisticsmedian)r   r   
book_liness    @r   r   z"_oddsapi_fetch.<locals>.<dictcomp>   sV     I I I=> z(77777779 9 I I Ir%   commence_time)rK   rL   rM   rN   oddsn_books)r   r$   rS   r   anyr   r   rT   )r	   r4   r   r9   rX   ebmomarginr   namesr   s               @r   _oddsapi_fetchr      s
   
--CCEMXXsssTCS>>D
C  
|R(( 		) 		)AUU9b)) ) )U8u$$BBAjMBBBq66Q;;#%C%C

%C%C%C"C"C;99ahhjj999996))))T)))))%%a((()  	IUU;''{1C1CDEI I I IBGI I ID

EE+&&EE+&&5511:
 
 	 	 	 	 Jr%   c                 V    t                      sg S t          d| z   dt          |           S )u  Upcoming fixtures for one league with bookmaker 1X2 odds.

    Aggregation: keep only books quoting all three h2h outcomes with a sane
    overround (margin 0.98-1.25), then take the MEDIAN price per outcome.
    Never merge best-prices per outcome across books — that fabricates
    impossible lines (e.g. 310.0/450.0/1000.0) and fake >5000% EV.

    Cached 30 min so one run (book EV + World block) uses a single API call.
    odds_i  )r   r:   r   )r	   s    r   oddsapi_fixturesr     s.     == 	'H$dNHEEEr%   )r   )r   )r   r<   )r   )$__doc__r!   r(   rs   r   r-   rp   r~   r   urllib.requestr   urllib.parser   r   r)   r*   dirnameabspath__file__r+   makedirsLEAGUESr   r$   r:   r?   r[   rw   rz   r   rR   r   r   r   r   r   r=   r%   r   <module>r      s6  
 
  				 				                     JKGLL)B)BCCXNN	 I % % % % ,/  0   )*  )*  )- + :   E E E E        ,     g7  %	
 
  , < J ) J  ) } 8 z  !" #$ ),'  .    (     <  *6 6 6     FF F F F Fr%   