a
    k¾€j·8  ã                   @   s„  d Z ddlZddlZddlZddlZddlmZ ddlmZm	Z	 z ej
jdd� ejjdd� W n eyr   Y n0 dZdeiZd	Zed
ƒZed Zed Zed Zed ZdZ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'ZG d(d)„ d)eƒZd*d+„ Zd,d-„ Zd.d/„ Zd0d1„ Zd2d3„ Zd4d5„ Z d6d7„ Z!e"d8e #¡  $d9¡› d:�ƒ zeƒ \Z%Z&W nL eej'f�y¢ Z( z,e"d;e(› �ƒ e"d<ƒ e )d=¡ W Y dZ([(n
dZ([(0 0 e"d>e%› d?e&› d@e&e% › dA�ƒ e%e&k�rîe"dBe%› d?e&› dC�ƒ e )d=¡ e #¡  *¡ Z+e+e	dDdE� Z,e"dFe,› dGe+› dH�ƒ e -e¡Z.e/e.dI  0e1¡ƒZ2e"dJe3e2ƒdK›dL�ƒ e/ƒ Z4e 5¡ �r¤e6eƒ�*Z7e/dMdN„ e7 8¡  9¡ D ƒƒZ4W d  ƒ n1 �sš0    Y  e"dOƒ g Z:e%Z;eƒ Z<dPdQ„ e< =¡ D ƒZ>e>�re"dRe3e>ƒ› dSe› dT�dU ?dVdN„ e@e> =¡ ƒD ƒ¡ ƒ e =¡ D �]V\ZAZBe;ek�r2e"dWƒ  �qjzeeBe, C¡ e+ C¡ e<eA ƒZDW nX e�y¨ Z( z>e"dXeA› dTe(› �ƒ e"dYe3e:ƒ› dZ�ƒ e )d=¡ W Y dZ([(n
dZ([(0 0 e;d[7 Z;eDD ]¤ZEeEd\ d] ZFeFe2v �rÔ�q¶eEd^ d_ ZGeEd^ d` ZHeGdu �s¶eHdu �r�q¶e: IeFeEd\ da ddb… eAeBe<eA eEdc d_ dd eEdc d` dd eGeHdeœ	¡ e2 JeF¡ �q¶e Kdf¡ �qe"dge3e:ƒ› �ƒ e:�rÂe Le:¡ZMejNe.eMgdhdi�Z.e.jOedjdk� e"dle3e.ƒdK›dm�ƒ dndo„ e:D ƒZPe"dpe3ePƒ› dq�ƒ eP�söe"drƒ �nxe. QdI¡ Rds¡ZSe 5¡ �re -e¡ne L¡ ZTg ZUeVePƒD �]ª\ZWZFe;ek�rXe"dteW› du�ƒ  �qÜzŽeeFƒZXe;d[7 Z;eX�r”eFeSv �r”e eFeSeF eXƒZYeU ZeY¡ e6edvƒ� Z7e7 [eF› dH�¡ W d  ƒ n1 �sÆ0    Y  e4 JeF¡ e Kd[¡ W nð ej\j]�y” Z( z�e(jX�re(jXj^ndZ_e_dwk�rbe6edvƒ� Z7e7 [eF› dH�¡ W d  ƒ n1 �sL0    Y  e4 JeF¡ ne"dxeF› dTe(› �ƒ e Kd[¡ W Y dZ([(nLdZ([(0  e`�yÖ Z( z*e"dxeF› dTe(› �ƒ e Kd[¡ W Y dZ([(n
dZ([(0 0 �q.eU�rfe LeU¡Zae3eTƒ�rejNeTeagdhdi�neaZbebjOedjdk� e"dye3ebƒdK›dz�ƒ e"d{ƒ e!ebƒZcecjOedjdk� e"d|e3ecƒdK›d}�ƒ ne"d~ƒ e"de;e% › �ƒ dS )€uÌ   
Weekly API-Football refresh â€” discovers new finished fixtures from the past 7 days,
appends them to fixtures_index.csv, and pulls match stats for any new ones.
Scheduled Wednesdays via Task Scheduler.
é    N)ÚPath)ÚdatetimeÚ	timedeltazutf-8)ÚencodingZ 81b487be73030f5dec980d2a1ef2c406zx-apisports-keyz!https://v3.football.api-sports.iozE:\soccer\raw_apizfixtures_index.csvzmatch_stats.csvzdone_fixtures.txtzmatch_features.csvié  é'   é(   é)   é*   é+   éŒ   é�   é‡   éˆ   éN   éO   é=   é>   éX   é�   é^   éË   éÅ   é³   é´   é·   é¸   )ZE0ZE1ZE2ZE3ZECZSP1ZSP2ZI1ZI2ZD1ZD2ZF1ZF2ZN1ZB1ZP1ZT1ZG1ZSC0ZSC1ZSC2ZSC3iè  c                   @   s   e Zd ZdZdS )ÚQuotaExhaustedz<API-Football refused the call. Not a 'no results' condition.N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© r!   r!   ú&E:\soccer\main\refresh_api_football.pyr   /   s   r   c                 C   s    |   d¡}t|tƒr|r|S dS )z¼API-Football answers 200 with an `errors` body -- quota, bad key, bad
    params. It is a dict when populated and an empty LIST when clean, so a
    truthiness test is the only safe check.ÚerrorsN)ÚgetÚ
isinstanceÚdict)ZpayloadÚer!   r!   r"   Ú_api_errors3   s    
r(   c                  C   s~   t jt› d�tdd�} |  ¡ }t|ƒ}|r8td|› �ƒ‚| d¡pDi  d¡pNi }d|vs`d|vrntd	|› �ƒ‚|d |d fS )
a/  (current, limit_day), or raise.

    This used to be `except: return 0, 7500`, which is the worst possible
    answer to "how much quota is left": when the account was actually OUT, the
    status call failed, the handler reported full quota available, and the run
    proceeded to make calls that every one came back empty. The log then said
    'API quota: 0/7500 used' and 'New fixtures found: 0' and exited 0. Six days
    of fixtures went missing while the task reported success. Never invent an
    optimistic number for a limit you could not read.
    z/statusé
   )ÚheadersÚtimeoutzAPI-Football refused /status: ÚresponseÚrequestsZcurrentZ	limit_dayz$/status returned no request counts: ©r-   r$   ÚAF_BASEÚHEADERSÚjsonr(   r   )ÚrÚdÚerrZreqr!   r!   r"   Úget_requests_used;   s    r5   c              	   C   sR   t jt› d�t| |||ddœdd�}| ¡ }t|ƒ}|rFtd|› �ƒ‚| dg ¡S )ao  Finished fixtures in a window, or raise if the API refused.

    An exhausted quota comes back 200 with results:0 and an `errors` body, which
    is indistinguishable from a genuine "no matches that week" unless the errors
    field is read. It was not read, so a dead account looked exactly like a
    quiet week -- 22 leagues scanned, 0 fixtures found, exit 0.
    z	/fixturesZFT)ZleagueÚseasonÚfromÚtoZstatusé   ©r*   Úparamsr+   z API-Football refused /fixtures: r,   r.   )Ú	league_idÚ	date_fromÚdate_tor6   r2   r3   r4   r!   r!   r"   Úfetch_fixtures_rangeP   s    þýr?   c               
      sf   zddl } |  t¡‰ W n< tyR } z$td|› dt› �ƒ i ‰ W Y d}~n
d}~0 0 ‡ fdd„tD ƒS )zñ{div: season} for the divs we pull, from API-Football's own `current` flag.

    Cached 7d by maps. Any league the resolver cannot answer for falls back to
    AF_SEASON, so a dead API degrades to the old behaviour rather than skipping.
    r   Nz  season resolver unavailable (z) -- falling back to c                    s   i | ]}|ˆ   |¡pt“qS r!   )r$   Ú	AF_SEASON)Ú.0Údiv©Zresolvedr!   r"   Ú
<dictcomp>o   ó    z#resolve_seasons.<locals>.<dictcomp>)ÚmapsZcurrent_seasonsr0   Ú	ExceptionÚprintr@   Ú
LEAGUE_MAP)rF   r'   r!   rC   r"   Úresolve_seasonsc   s    rJ   c                 C   s4   t jt› d�td| idd�}| ¡  | ¡  dg ¡S )Nz/fixtures/statisticsÚfixturer9   r:   r,   )r-   r$   r/   r0   Zraise_for_statusr1   )Ú
fixture_idr2   r!   r!   r"   Úfetch_statsq   s
    ÿrM   c                    sê   g }|D ]Ü}|d d }||d k}dd„ |d D ƒ‰ d ‡ fdd„	}|  | |d	 |d
 |d |t|ƒ|d |d |d |d |dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒ|dƒdœ¡ q|S )!NÚteamÚnameÚ	home_teamc                 S   s   i | ]}|d  |d “qS )ÚtypeÚvaluer!   )rA   Úsr!   r!   r"   rD   |   rE   zparse_stats.<locals>.<dictcomp>Z
statisticsc                    sn   ˆ   | |¡}|d u r|S t|tƒrN| d¡rNzt| d¡ƒW S    | Y S 0 z
t|ƒW S    | Y S 0 d S )Nú%)r$   r%   ÚstrÚendswithÚfloatÚrstrip)ÚkeyÚdefaultÚv©Zstatsr!   r"   r$   }   s    zparse_stats.<locals>.getÚdaterB   r6   Ú	away_teamÚ
home_goalsÚ
away_goalszShots on GoalzShots off GoalzTotal ShotszBlocked ShotszShots insideboxzShots outsideboxZFoulszCorner KicksZOffsideszBall PossessionzYellow Cardsz	Red CardszGoalkeeper SaveszTotal passeszPasses accuratezPasses %)rL   r]   rB   r6   rN   Úis_homerP   r^   r_   r`   Úshots_on_goalÚshots_off_goalÚtotal_shotsÚblocked_shotsÚshots_insideÚshots_outsideÚfoulsÚcornersÚoffsidesÚ
possessionÚyellow_cardsÚ	red_cardsÚsavesÚtotal_passesÚaccurate_passesÚpass_pct)N)ÚappendÚint)rL   Zfixture_rowr,   ZrowsZ	team_dataZ	team_namera   r$   r!   r\   r"   Úparse_statsw   sF    æ
rt   c              	      sª  ˆ   ¡ ‰ t ˆ d ¡ˆ d< ˆ  ddg¡jdd�‰ g d¢}dˆ d< ˆ d d	kˆ d
 ˆ d k@ ˆ d dkˆ d ˆ d
 k@ B }ˆ d
 ˆ d k}dˆ j|df< d	ˆ j|df< ˆ jdd„ d	d�ˆ d< ˆ jdd„ d	d�ˆ d< g d¢‡ fdd„|D ƒ }g }ˆ  d¡D ]t\}}| d¡jdd�}dD ]J}|D ]>}	|	|jv �r||	  	d	¡j
|d	d� ¡ |d|	› d|› �< �q�q| |¡ qøtj|dd�‰g d¢dd„ ˆjD ƒ }
ˆ‡fdd„|
D ƒ S )Nr]   rN   T)Zdrop)rb   rc   rd   re   rf   rg   rh   ri   rj   rk   rl   rm   rn   ro   rp   rq   r   Úptsra   é   r_   r`   é   c                 S   s   | d r| d S | d S )Nra   r_   r`   r!   ©r2   r!   r!   r"   Ú<lambda>±   rE   z build_features.<locals>.<lambda>)ZaxisÚgfc                 S   s   | d r| d S | d S )Nra   r`   r_   r!   rx   r!   r!   r"   ry   ²   rE   Úga)ru   rz   r{   c                    s   g | ]}|ˆ j v r|‘qS r!   ©Úcolumns©rA   Úc)Údfr!   r"   Ú
<listcomp>³   rE   z"build_features.<locals>.<listcomp>)é   r)   )Zmin_periodsÚapi_Z_w©Zignore_index)
rL   r]   rB   r6   rN   ra   rP   r^   r_   r`   c                 S   s   g | ]}|  d ¡r|‘qS )rƒ   )Ú
startswithr~   r!   r!   r"   r�   ¿   rE   c                    s   g | ]}|ˆ j v r|‘qS r!   r|   r~   )Úfeat_dfr!   r"   r�   À   rE   )ÚcopyÚpdZto_datetimeZsort_valuesZreset_indexZlocZapplyÚgroupbyr}   ÚshiftZrollingZmeanrr   Úconcat)r€   Z	stat_colsZwin_maskZ	draw_maskZ	roll_colsZ	feat_rowsrN   ZgrpÚwÚcolZ	feat_colsr!   )r€   r†   r"   Úbuild_features£   s6    ÿ4þrŽ   u$   === API-Football Weekly Refresh â€” z%Y-%m-%d %H:%Mz ===
z)ABORT: cannot read API-Football quota -- z1Nothing was refreshed. Fixture data is now stale.rw   zAPI quota: ú/z used (z remaining)
z&ABORT: daily quota already exhausted (z).é   )ZdayszScanning finished fixtures: u    â†’ Ú
rL   zExisting fixture index: ú,z	 fixturesc                 c   s   | ]}|  ¡ rt|ƒV  qd S )N)Ústriprs   )rA   Úxr!   r!   r"   Ú	<genexpr>â   rE   r•   z)
--- Step 1: Discovering new fixtures ---c                 C   s   i | ]\}}|t kr||“qS r!   )r@   ©rA   r3   rS   r!   r!   r"   rD   ê   rE   rD   z  seasons resolved; z league(s) not on z: z, c                 c   s    | ]\}}|› d |› �V  qdS )ú=Nr!   r–   r!   r!   r"   r•   í   rE   u2   Daily limit reached during discovery â€” stopping.zABORT during discovery on z  zQ fixture(s) found before the refusal were NOT written -- rerun once quota resets.rv   rK   ÚidZgoalsÚhomeZawayr]   r)   ZteamsrO   )	rL   r]   rB   r<   r6   rP   r^   r_   r`   g      à?zNew fixtures found: Tr„   F)Úindexzfixtures_index.csv updated: z total fixturesc                 C   s    g | ]}|d  t vr|d  ‘qS )rL   )Údone)rA   r2   r!   r!   r"   r�     rE   r�   z
--- Step 2: Pulling stats for z new fixtures ---zNothing new to pull.rš   u'   Daily limit reached â€” stopping after z
 fixtures.Úai“  z  Error fixture zmatch_stats.csv updated: z team-match rowsz,
--- Step 3: Rebuilding rolling features ---zmatch_features.csv updated: z rowszNo new stats rows.z 
Done. API quota used this run: )dr    r-   Zpandasrˆ   ÚtimeÚsysÚpathlibr   r   r   ÚstdoutÚreconfigureÚstderrÚAttributeErrorZAPI_KEYr0   r/   ZOUT_DIRZFIXTURES_CSVZ	STATS_CSVZ	DONE_FILEZFEATURES_CSVr@   rI   ZDAILY_LIMITÚRuntimeErrorr   r(   r5   r?   rJ   rM   rt   rŽ   rH   ZnowÚstrftimeZusedÚlimitZRequestExceptionr'   Úexitr]   r>   r=   Zread_csvZindex_dfÚsetZastypers   Zexisting_idsÚlenr›   ÚexistsÚopenÚfÚreadÚsplitZnew_index_rowsZrequests_usedZseasonsÚitemsZoff_defaultÚjoinÚsortedrB   r<   Z	isoformatZfixturesZfixZfidr_   r`   rr   ÚaddÚsleepZ	DataFrameZnew_dfr‹   Zto_csvZtodo_idsZ	set_indexZto_dictZfixture_lookupZexisting_statsZnew_rowsÚ	enumerateÚir,   ZparsedÚextendÚwriteÚ
exceptionsZ	HTTPErrorZstatus_codeÚcoderG   ÚchunkZcombinedr†   r!   r!   r!   r"   Ú<module>   s"   

û,!  




:ÿ

ÿ 
÷


0

0$
 