Ë
    ƒ¨j‡-  ã                  óö   — d Z ddlmZ ddlZddlmZmZ ddlmZ ddl	m
Z
 ddlmZmZmZmZmZ g d	¢Zd
„ Zdd„Zddd„Zddd„Zddd„Zdd„Zd d„Zd!d"d„Zd#d$d„Z	 	 d%	 	 	 	 	 	 	 d&d„Zdd'd„Zd(d„Zd)d„Zg d¢Zy)*aè  Calibration statistics: how the engine's suggestion compares with the
danger forecasters actually published.

These numbers are DESCRIPTIVE. Imported history (e.g. the Sierra archive,
2013-2021) predates the current avalanche-problem guidance and was not produced
with this tool, so disagreement is not necessarily an error on either side.
Imported forecasts carry likelihood and size but no sensitivity/distribution,
so they can calibrate the danger table but not the likelihood matrix.
é    )ÚannotationsN)ÚCounterÚdefaultdict)Ú
Connectioné   ©Úfetchall)ÚBANDSÚENGINE_VERSIONÚbands_ofÚ
danger_forÚ
size_class)r   é   é   é   é   c                ó    — | €d S t        | «      S ©N)Úfloat)Úvs    ú,/var/www/avy-guidance/api/app/calibration.pyÚ_fr      s   € Ø�9ˆ4Ð*¤%¨£(Ð*ó    c                óJ  — dddddœ}t         D ]’  }d}| D ]r  }|j                  d«      sŒ|j                  d«      €Œ'|t        |j                  d«      «      v sŒDt        |t	        t        |d   «      t        |d   «      |«      «      }Œt |||<   t        |d   |«      |d<   Œ” |S )zVPer-band suggestion for problems that already carry a likelihood (imported forecasts).r   )ÚupperÚmiddleÚlowerÚoverallÚ
likelihoodÚsize_maxÚ	locationsr   )r
   Úgetr   Úmaxr   Úintr   )ÚproblemsÚconfigÚoutÚbandÚbestÚps         r   Úsuggest_from_recordedr+      s·   € à ¨Q¸1Ñ
=€CÜò 3ˆØˆØò 	aˆAØ�u‰u�\Õ" q§u¡u¨ZÓ'8Ñ'DÈÔQYÐZ[×Z_ÑZ_Ð`kÓZlÓQmÒImÜ˜4¤¬C°°,±Ó,@Ä%ÈÈ*ÉÓBVÐX^Ó!_Ó`‘ð	að ˆˆD‰	Ü˜S ™^¨TÓ2ˆˆIŠð3ð €Jr   c           	     ó&   — t        | d|||||f«      S )NaÐ  SELECT f.id, f.valid_date, f.season, f.guidance_era, f.zone_id, f.source,
                  f.danger_upper, f.danger_middle, f.danger_lower,
                  coalesce(json_agg(json_build_object(
                      'type', p.problem_type, 'rank', p.rank, 'locations', p.locations,
                      'likelihood', p.likelihood, 'size_min', p.size_min, 'size_max', p.size_max)
                      ORDER BY p.rank) FILTER (WHERE p.id IS NOT NULL), '[]') AS problems
           FROM forecasts f LEFT JOIN forecast_problems p ON p.forecast_id = f.id
           WHERE f.center_id = %s AND (%s::text IS NULL OR f.season = %s) AND (%s::text IS NULL OR f.guidance_era = %s)
           GROUP BY f.id ORDER BY f.valid_dater   )ÚconnÚ	center_idÚseasonÚeras       r   Úload_forecastsr1   )   s'   € ÜØð	2ð 
�F˜F C¨Ð-óð r   c                óü   — t        | d|f«      }|D ]J  }|d   }|j                  dk\  r|j                  n|j                  dz
  }|› dt        |dz   «      dd  › �|d<   ŒL |D �cg c]  }|�	|d   |k(  sŒ|‘Œ c}S c c}w )Na7  SELECT id, valid_date, final_upper AS danger_upper, final_middle AS danger_middle, final_lower AS danger_lower,
                  suggested_upper, suggested_middle, suggested_lower, final_problems AS problems, config_id
           FROM tool_runs WHERE center_id = %s AND status = 'finalized' ORDER BY valid_dateÚ
valid_dateé   r   ú-éþÿÿÿr/   )r	   ÚmonthÚyearÚstr)r-   r.   r/   ÚrowsÚrÚdÚstarts          r   Úload_tool_runsr>   9   sœ   € ÜØð	_ð 
ˆó€Dð ò 7ˆØˆl‰OˆØŸ'™' Qš,�—’¨A¯F©F°Q©JˆØ˜˜q¤ U¨Q¡Y£°°Ð!4Ð 5Ð6ˆˆ(Šð7ð ÖG�!˜v˜~°°8±ÀÓ1FŠAÒGÐGùÒGs   Á"A9Á2A9c                óÜ  — g t        «       }}| D ]Ó  }|rt        D �ci c]  }||d|› �   “Œ c}nt        |d   xs g |«      }|d   r|d   xs i gd   j                  d«      nd}t        D ]u  }|d|› �   }	|	�|	dk(  r|dxx   dz  cc<   Œ ||   s|d	xx   dz  cc<   Œ3|j	                  |d
   |d   |j                  d«      |t        ||   «      t        |	«      |dœ«       Œw ŒÕ ||fS c c}w )zN(suggested, final) per band. Skips unrated bands and bands no problem touches.Ú
suggested_r%   r   ÚtypeNÚdanger_Úunratedr   Úno_problem_in_bandÚidr3   r/   )rE   Údater/   r(   Ú	suggestedÚfinalÚprimary)r   r
   r+   r"   Úappendr$   )
Úitemsr&   Úuse_stored_suggestionÚpairsÚskippedÚitÚbÚsuggrI   rH   s
             r   Ú
band_pairsrR   H   s6  € àœ›ˆ7€EØò _ˆñ %ô /4Ö4¨ˆQ��Z ˜sÐ#Ñ$Ñ$Ó4ä& r¨*¡~Ò';¸¸VÓDð 	ð
 >@À
º^�2�j‘>Ò) b T¨1Ñ-×1Ñ1°&Ô9ÐQUˆÜò 		_ˆAØ˜  �}Ñ%ˆEØˆ} ¨¢
Ø˜	Ó" aÑ'Ó"ØØ˜’7ØÐ,Ó-°Ñ2Ó-ØØ�L‰L  4¡°"°\Ñ2BÈbÏfÉfÐU]ÓN^ÐhiÜ'*¨4°©7£|¼cÀ%»jÐU\ñ^õ _ñ		_ð_ð" �'ˆ>Ðùò 5s   œC)c           	     ó^  ‡‡— t        | «      }|dk(  ryt        t        «      }t        |«      D �cg c]  }dg|z  ‘Œ
 c}Š| D ]  }‰|d   dz
     |d   dz
  xx   dz  cc<   Œ  ‰D �cg c]  }t        |«      ‘Œ }}t        |«      D �‡cg c]   Št        ˆˆfd„t        |«      D «       «      ‘Œ" }}dx}	}
t        |«      D ]E  }t        |«      D ]5  Š|‰z
  dz  |dz
  dz  z  }|	|‰|   ‰   z  z  }	|
|||   z  |‰   z  |z  z  }
Œ7 ŒG |
dk(  rdS t	        d|	|
z  z
  d	«      S c c}w c c}w c c}w )
z8Quadratic-weighted Cohen's kappa over danger levels 1-5.r   NrG   r   rH   c              3  ó.   •K  — | ]  }‰|   ‰   –— Œ y ­wr   © )Ú.0ÚiÚjÚobss     €€r   ú	<genexpr>z!weighted_kappa.<locals>.<genexpr>i   s   øè ø€ Ò,˜a��A‘�q•	Ñ,ùs   ƒç        r   r   )ÚlenÚLEVELSÚrangeÚsumÚround)rM   ÚnÚkÚ_r*   r;   r:   rX   ÚcolsÚnumÚdenrW   ÚwrY   s          `     @r   Úweighted_kapparh   _   sY  ù€ äˆE‹
€AØˆA‚vØÜŒF‹€AÜ! !›HÖ
%�qˆAˆ3�‹7Ò
%€CØò 5ˆØˆAˆk‰N˜QÑÑ  '¡
¨Q¡Ó/°1Ñ4Ô/ð5àÖ �qŒC��FÐ €DÐ Ü6;¸A³h×?°ŒCÔ,¤5¨£8Ô,Õ,Ð?€DÐ?Ø€O€Cˆ#Ü�1‹Xò -ˆÜ�q“ò 	-ˆAØ�Q‘˜1‘  A¡¨!™|Ñ+ˆAØ�1�s˜1‘v˜a‘y‘=Ñ ˆCØ�1�t˜A‘w‘;  a¡Ñ(¨1Ñ,Ñ,‰Cñ	-ð-ð
 ˜!’8ˆ4Ð8¤ q¨3°©9¡}°aÓ!8Ð8ùò &ùò !ùÚ?s   °D Á'D%Â	%D*c           	     óŠ  — t        | «      }t        d«      D �cg c]  }dgdz  ‘Œ
 }}| D ]  }||d      |d   xx   dz  cc<   Œ |dk(  r	dd d d d |dœS t        d„ | D «       «      }t        d„ | D «       «      }|t        ||z  d	«      t        ||z  d	«      t        t        d
„ | D «       «      |z  d	«      t	        | «      |dœS c c}w )Né   r   rG   rH   r   )ra   ÚexactÚ
within_oneÚbiasÚkappaÚ	confusionc              3  ó2   K  — | ]  }|d    |d   k(  –— Œ y­w)rG   rH   NrU   ©rV   r*   s     r   rZ   zsummarize.<locals>.<genexpr>z   s   è ø€ Ò<°��+‘ ! G¡*Õ,Ñ<ùó   ‚c              3  óJ   K  — | ]  }t        |d    |d   z
  «      dk  –— Œ y­w)rG   rH   r   N)Úabsrq   s     r   rZ   zsummarize.<locals>.<genexpr>{   s'   è ø€ ÒF¸1”�Q�{‘^ a¨¡jÑ0Ó1°QÕ6ÑFùs   ‚!#r   c              3  ó2   K  — | ]  }|d    |d   z
  –— Œ y­w)rH   rG   NrU   rq   s     r   rZ   zsummarize.<locals>.<genexpr>€   s   è ø€ ÒE¸!˜!˜G™* q¨¡~Õ5ÑEùrr   )r\   r^   r_   r`   rh   )rM   ra   rc   ro   r*   rk   Úwithins          r   Ú	summarizerw   s   sã   € ÜˆE‹
€AÜ"'¨£(Ö+˜Q�!��q“Ð+€IÐ+Øò 3ˆØ�!�K‘.Ñ! ! G¡*Ó-°Ñ2Ô-ð3àˆA‚vØ °TÀ4ÐRVÐenÑoÐoÜÑ<°eÔ<Ó<€EÜÑFÀÔFÓF€FàÜ�u˜q‘y !Ó$Ü˜F Q™J¨Ó*Ü”cÑE¸uÔEÓEÈÑIÈ1ÓMÜ Ó&Øñð ùò ,s   ™C c                ó¸  — |dk(  rt        | ||«      }t        ||d¬«      \  }}nt        | |||«      }t        ||«      \  }}t        D �	�
ci c]%  }	|	t	        |D �
cg c]  }
|
d   |	k(  sŒ|
‘Œ c}
«      “Œ' }}	}
t        t        «      }t        t        «      }|D ]0  }
||
d      j                  |
«       ||
d      j                  |
«       Œ2 |t        |«      t	        |«      ||j                  «       D ���ci c]E  \  }}|sŒ	t        |«      t	        |«      j                  «       D ��ci c]  \  }}|dk7  sŒ||“Œ c}}“ŒG c}}}t        |j                  «       «      D ���ci c]<  \  }}|sŒ	|t	        |«      j                  «       D ��ci c]  \  }}|dk7  sŒ||“Œ c}}“Œ> c}}}t        |«      t        dœS c c}
w c c}
}	w c c}}w c c}}}w c c}}w c c}}}w )	NÚtoolT)rL   r(   rI   r/   ro   )ÚsourcerK   r   Úby_bandÚby_primary_problemÚ	by_seasonrN   Úengine_version)r>   rR   r1   r
   rw   r   ÚlistrJ   r\   rK   r9   ÚsortedÚdictr   )r-   r.   r&   rz   r/   r0   rK   rM   rN   rP   r*   r{   ÚgroupsÚseasonsrb   r   Úkks                    r   Ú	agreementr…   †   sà  € Ø�ÒÜ˜t Y°Ó7ˆÜ# E¨6ÈÔN‰ˆ‰wä˜t Y°¸Ó<ˆÜ# E¨6Ó2‰ˆˆwÜKP×QÀaˆq”)¨Ö@ 1°°6±¸a³šQÒ@ÓAÑAÐQ€GÑQÜœÓ€FÜœ$Ó€GØò 'ˆØˆq�‰|Ñ×#Ñ# AÔ&Ø��(‘Ñ×#Ñ# AÕ&ð'ð Ü�U“Ü˜UÓ#ØØrx×r~Ñr~ó  sA÷  Gð  GÑjnÐjkÐmnò  EFœs 1›v¼9ÀQ»<×;MÑ;MÓ;O×'e±%°"°aÐSUÐYdÓSd¨¨A©Ó'eÑeô  GÜdjÐkr×kxÑkxÓkzÓd{÷  Bð  BÑ\`Ð\]Ð_`ò  @A�a¬Y°q«\×-?Ñ-?Ó-A×W¡E B¨ÀRÈ;ÓEV˜"˜a™%ÓWÑWô  BÜ˜“=Ü(ñ	ð 	ùò AùÓQùó (fùô  GùÛWùô  Bsf   ÁF<ÁF7Á(F7Á,	F<Ã5
GÄ &GÄ&GÄ4GÄ9GÅ 
GÅ+GÆGÆGÆGÆ7F<ÇGÇGc                ó  — |dk(  ry | |z  }d||z  |z  z   }|||z  d|z  z  z   |z  }|t        j                  |d|z
  z  |z  ||z  d|z  |z  z  z   «      z  |z  }t        t        d||z
  «      d«      t        t	        d||z   «      d«      fS )Nr   r   r   r   r[   g      ð?)ÚmathÚsqrtr`   r#   Úmin)Ú	successesra   Úzr*   ÚdenomÚcentreÚhalfs          r   Úwilsonr�   Ÿ   sª   € ØˆA‚vØØ�A‰€AØ��A‘˜‘	‰M€EØ�!�a‘%˜1˜q™5‘/Ñ! UÑ*€FØŒt�y‰y˜˜a !™e™ q™¨1¨q©5°A¸±E¸A±IÑ+>Ñ>Ó?Ñ?À%ÑG€DÜ”�S˜& 4™-Ó(¨!Ó,¬e´C¸¸VÀd¹]Ó4KÈQÓ.OÐOÐOr   c                óô  ‡— |dk(  rt        | ||«      nt        | |||«      }t        t        «      }	|D ]Ô  }
t        D ]É  }|
d|› �   }|sŒ|
d   xs g D �cg c]B  }|j                  d«      r/|j                  d«      �|t        |j                  d«      «      v r|‘ŒD }}|r|dk(  rt        |«      dk7  rŒxt        |ˆfd	„¬
«      }t        |d   «      › dt        t        |d   «      ‰«      › �}|	|   t        |«      xx   dz  cc<   ŒË ŒÖ i }|	j                  «       D ]À  \  }}t        |j                  «       «      }|j                  d«      d   \  }}|j!                  d«      \  }}t        |«      t        |«      |t#        d«      D �cg c]  }|j                  |d«      ‘Œ c}|t%        ||z  d«      t'        ||«      ‰d   |   t        |«      dz
     ||k  dœ	||<   ŒÂ ||t        |«      ||dœS c c}w c c}w )z™P(final danger | likelihood, size class) from bands with one located problem
    (mode='single') or using each band's dominant problem (mode='dominant').ry   rB   r%   r   r    r!   Úsingler   c                óZ   •— t        t        | d   «      t        | d   «      ‰«      | d    fS )Nr   r    Úrank)r   r$   r   )r*   r&   s    €r   ú<lambda>z!empirical_table.<locals>.<lambda>º   s6   ø€ ´¼CÀÀ,ÁÓ<PÔRWÐXYÐZdÑXeÓRfÐhnÓ1oÐrsÐtzÑr{Ðq{Ð0|€ r   )Úkeyr5   r   rj   r   ÚdangerTable)	r   r   ra   ÚcountsÚmodeÚ
mode_shareÚmode_ciÚcurrentÚsparse)rz   r˜   rK   Úmin_nÚcells)r>   r1   r   r   r
   r"   r   r\   r#   r$   r   r   rK   r_   ÚvaluesÚmost_commonÚsplitr^   r`   r�   )r-   r.   r&   rz   r/   r˜   r0   r�   rK   rž   rO   rP   rH   r*   ÚlocatedÚchosenr•   r'   r—   ra   Ú
mode_levelÚ
mode_countÚlkÚscÚlevels     `                      r   Úempirical_tabler©   ©   s#  ø€ ð 8>ÀÒ7GŒN˜4 ¨FÔ3Ì^Ð\`ÐbkÐmsÐuxÓMy€EÜ +¬GÓ 4€EØò (ˆÜò 	(ˆAØ˜  �}Ñ%ˆEÙØà˜z™NÒ0¨böØØ—5‘5˜Ô&¨1¯5©5°Ó+<Ð+HÈQÔRZÐ[\×[`Ñ[`ÐalÓ[mÓRnÑMnò ðˆGð ñ ˜t xÒ/´C¸³LÀAÒ4EØÜ˜Ó&|Ô}ˆFÜ˜ Ñ-Ó.Ð/¨q´¼EÀ&ÈÑBTÓ<UÐW]Ó1^Ð0_Ð`ˆCØ�#‰J”s˜5“zÓ" aÑ'Ô"ñ	(ð(ð €CØ—{‘{“}ò 
‰ˆˆVÜ�—‘“Ó ˆØ!'×!3Ñ!3°AÓ!6°qÑ!9Ñˆ
�JØ—‘˜3“‰ˆˆBä˜b›'Ü˜b›'ØÜ9>¸q»ÖB°�v—z‘z %¨Õ+ÒBØÜ 
¨Q¡°Ó2Ü˜j¨!Ó,Ø˜mÑ,¨RÑ0´°R³¸1±Ñ=Ø˜%‘iñ

ˆˆCŠð	
ð  d´S¸³ZÈ%ÐZ]Ñ^Ð^ùò3ùò$ Cs   ÁAG0ÆG5c           
     ó¨  — t        | ||«      }t        ||«      \  }}t        ||«      \  }}	|D �
ci c]  }
|
d   |
d   f|
“Œ }}
g }|D ]T  }
|j                  |
d   |
d   f«      }|sŒ|d   |
d   k7  sŒ+|j                  |
d   |
d   |
d   |d   |
d   |
d   dœ«       ŒV |t	        |«      t	        |«      t        |«      |dd t        |	«      d	œS c c}
w )
zXBacktest a candidate config against imported history, optionally on one held-out season.rE   r(   rG   rF   rH   )Úforecast_idrF   r(   ÚactiveÚ	candidaterH   NéÈ   )Úholdout_seasonr¬   r­   Ú	n_changedÚchangedrN   )r1   rR   r"   rJ   rw   r\   r�   )r-   r.   r­   r¬   r¯   rK   Úpairs_crc   Úpairs_arN   r*   Úby_keyr±   Úas                 r   Úreplay_configr¶   Ð   s  € ä˜4 ¨NÓ;€EÜ˜E 9Ó-�J€GˆQÜ! %¨Ó0Ñ€GˆWØ/6Ö7¨!ˆq�‰w˜˜&™	Ð" AÑ%Ð7€FÐ7Ø€GØò OˆØ�J‰J˜˜$™  6¡Ð+Ó,ˆÚ��;‘ 1 [¡>Ó1Ø�N‰N¨1¨T©7¸A¸f¹IÈqÐQWÉyÐdeÐfqÑdrØ)*¨;©À!ÀGÁ*ñNõ OðOð )Ü˜GÓ$Ü˜wÓ'Ü˜“\Ø˜4˜C�=Ü˜“=ñð ùò 8s   °Cc                óÀ   — d}t        | |«      D ]L  }t        |d   xs g |d   «      }| j                  d|d   |d   t        |d   |d   |d   |d	   f«       |d
z  }ŒN |S )zQStore the engine's suggestion for every imported forecast under a config version.r   r%   r&   a  INSERT INTO forecast_suggestions (forecast_id, config_id, engine_version, suggested_upper, suggested_middle,
                   suggested_lower, suggested_overall)
               VALUES (%s, %s, %s, %s, %s, %s, %s)
               ON CONFLICT (forecast_id, config_id, engine_version) DO UPDATE SET
                   suggested_upper = EXCLUDED.suggested_upper, suggested_middle = EXCLUDED.suggested_middle,
                   suggested_lower = EXCLUDED.suggested_lower, suggested_overall = EXCLUDED.suggested_overallrE   r   r   r   r   r   )r1   r+   Úexecuter   )r-   r.   Ú
config_rowra   rO   Úss         r   Úbackfillr»   æ   sŽ   € à	€AÜ˜T 9Ó-ò ˆÜ! " Z¡.Ò"6°B¸
À8Ñ8LÓMˆØ�‰ðqð �‰X�z $Ñ'¬¸¸7¹ÀQÀxÁ[ÐRSÐT[ÑR\Ð^_Ð`iÑ^jÐkô	
ð 	
ˆQ‰‰ðð €Hr   c                ó   — t        | d|f«      S )uc   How often each problem type was listed per season â€” shows how problem use changed over the years.a  SELECT f.season, p.problem_type, count(*)::int AS n, count(DISTINCT f.id)::int AS forecasts
           FROM forecast_problems p JOIN forecasts f ON f.id = p.forecast_id
           WHERE f.center_id = %s GROUP BY f.season, p.problem_type ORDER BY f.season, p.problem_typer   )r-   r.   s     r   Úproblem_usager½   ø   s   € äØð	ið 
ˆóð r   )r…   r»   r©   r½   r¶   r+   r   )r%   ú
list[dict]r&   r�   Úreturnr�   )NN)
r-   r   r.   r$   r/   ú
str | Noner0   rÀ   r¿   r¾   r   )r-   r   r.   r$   r/   rÀ   r¿   r¾   )F)rK   r¾   r&   zdict | NonerL   Úboolr¿   ztuple[list[dict], Counter])rM   r¾   r¿   zfloat | None)rM   r¾   r¿   r�   )ÚimportedNN)r-   r   r.   r$   r&   r�   rz   r9   r/   rÀ   r0   rÀ   r¿   r�   )g\�Âõ(\ÿ?)rŠ   r$   ra   r$   r‹   r   r¿   ztuple[float, float] | None)rÂ   Nr‘   Né
   )r-   r   r.   r$   r&   r�   rz   r9   r/   rÀ   r˜   r9   r0   rÀ   r�   r$   r¿   r�   )r-   r   r.   r$   r­   r�   r¬   r�   r¯   rÀ   r¿   r�   )r-   r   r.   r$   r¹   r�   r¿   r$   )r-   r   r.   r$   r¿   r¾   ) Ú__doc__Ú
__future__r   r‡   Úcollectionsr   r   Úpsycopgr   Údbr	   Úenginer
   r   r   r   r   r]   r   r+   r1   r>   rR   rh   rw   r…   r�   r©   r¶   r»   r½   Ú__all__rU   r   r   ú<module>rË      s¡   ðñõ #ã ß ,å å ß KÕ Kâ	€ò+ó
ôô Hôó.9ó(ô&ô2Pð txØOQð$_Øð$_Ø/9ð$_ØILð$_ØVZó$_ôNó,ó$ò x�r   