
    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    94*%(*    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    Q1
=C 3 	aAuu\"quuZ'8'DQYZ[Z_Z_`kZlQmIm4C,,@%*BVX^!_`	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 
FFC- 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lO''Q,AFFQJqUQY!4 56(7 G!v~81FA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   7E _ % /44QZs#$$4&r*~';VD 	
 >@
^2j>)bT1-11&9QU 		_A}%E}
	"a'"7,-2-LL4"\2BbffU]N^hi'*47|c%jU\^ _		__" '> 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AvFA!!H
%qA37
%C 5AkNQ'
Q/14/5 qCF D 6;Ah?C,58,,?D?OC#1X -q 	-AQ1A!|+A1s1vay= C1tAw;a(1,,C	--
 !848q3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'     F1Q{^aj01Q6Fs   !#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Err   )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AvT4RVenoo<e<<EFFFFuqy!$FQJ*cEuEEI1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   tY7#E6NwtY<#E62wKPQaq)@16aQ@AAQGQF$G 'q|##A&(##A&' UU#rxr~r~  sA  G  Gjnjkmn  EFs1v9Q<;M;M;O'e%"aSUYdSdA'ee  Gdjkrkxkxkzd{  B  B\`\]_`  @AaYq\-?-?-AWEB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    AvAAA	ME!a%1q5/!U*Ftyya!eq1q5AEAI+>>??%GDS&4-(!,eCVd]4KQ.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 )zP(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,<PRWXYZdXeRfhn1orstzr{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>7GN4F3^\`bkmsuxMyE +G 4E ( 	(A}%EzN0b55&155+<+HQRZ[\[`[`al[mRnMn G  tx/CLA4E&|}F-./qE&BT<UW]1^0_`C#Js5z"a'"	(( C{{} 
V !'!3!3A!6q!9
J3Bb'b'9>qBvzz%+B
Q2j!,m,R0R1=%i

C	
 dS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E9-JGQ!%0GW/67!qw&	"A%7F7G OJJ$6+,;1[>1NN1T7AfIqQWydefqdr)*;!G*N OO )G$w'\4C==  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T9- !"Z."6B
88LMq Xz$'7Qx[RST[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   