
    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 d	d
dddddddddddZdhZg dZdddddZ ej$                  d      Zd!dZd Zd"dZd#d$d Zy)%a  How often past forecasts diverge from the CURRENT Forecast Guidance.

Each check is something the guidance states that can be tested from the
fields a forecast actually stores (problem types, likelihood, size, location,
danger per band, bottom line). Imported forecasts carry no sensitivity or
distribution, so checks that depend on them are marked partial.

These are divergences from today's guidance, not errors: most archived
forecasts were written under earlier conventions.
    )annotationsN)Counterdefaultdict)
Connection   )fetchall)BANDSbands_of)u&   Used “Normal Caution” as a problemu
   FG §1.2.1z'Not one of the nine CMAH problem types.)z3Moderate or higher with no avalanche problem listedu   FG §2.1, §3.4z:Problems should cover the travel advice behind the rating.)z$Listed a discouraged problem pairing
FG Table 1zPStorm+Wind, Wet Loose+Wet Slab, Persistent+Deep Persistent, Dry Loose+Wet Loose.)u;   …and the pair overlapped in location, likelihood and sizer   ud   Table 1: don’t pair unless there is a significant difference in distribution, likelihood and size.)zSize range wider than two sizes   FG §3.4z'Keep the range of sizes to two or less.)z$Deep Persistent Slab smaller than D3r   z9Deep Persistent is for high-consequence (D3+) avalanches.)z+Deep Persistent Slab rated Likely or higherr   zMReserved for low likelihood / high consequence (temporary increases allowed).)z/Storm Slab or Dry Loose with only D1 avalanchesr   um   Partial check: Table 1 also allows a widespread pattern of small avalanches, and distribution isn’t stored.)z)A problem with no aspect/elevation markedr    )z4HIGH/EXTREME with D3+ problems, but not in all bandsu   FG §2.1u?   Allowed with a specific, explainable reason — check the text.)z8A band rated Considerable+ with no problem located thereu   FG §3.2z>Travel advice should be consistent across danger and problems.)z'Bottom line longer than three sentencesu   FG §3.3z!Aim for three sentences or fewer.)normal_cautionrated_without_problemdiscouraged_pairingpairing_overlap
size_range	dps_small
dps_likelysmall_storm_or_looseno_locationhigh_not_all_bandsband_without_problembottom_line_longr   ))      )      )      )r   r   zStorm + Wind SlabzWet Loose + Wet SlabzPersistent + Deep PersistentzDry Loose + Wet Looseu    (?<=[.!?])\s+(?=[A-Z0-9\"'(“])c                    t        j                  dd| xs d      j                         }|syt        d t        j                  |      D              S )Nz\s+ r   r   c              3  Z   K   | ]#  }t        |j                               d k\  s d % yw)r   r   N)lensplit).0parts     &/var/www/avy-guidance/api/app/audit.py	<genexpr>z!sentence_count.<locals>.<genexpr>4   s"     PTTZZ\9Ja9OqPs   !++)resubstripsum_SENTENCE_SPLITr$   )textts     r'   sentence_countr0   0   sC    
vsDJB'--/AP_2215PPP    c                     | d S t        |       S N)float)vs    r'   _numr6   7   s    94*%(*r1   c                   g }| j                  d      xs g }t        D ci c]  }|| j                  d|       xs d c}t        j                               | j                  d      r|j	                  d       dk\  r|s|j	                  d       |D ci c]  }t        |d         | }}t        D cg c]  }|d   |v s|d   |v s| }}|r|j	                  d	       |D ]  \  }}||   ||   }
}	t        t        |	d
         t        |
d
         z        }|	d   duxr  |
d   duxr t        |	d   |
d   z
        dk  }t        |	d         t        |	d         f}t        |
d         t        |
d         f}d||z   vxr |d   |d   k  xr |d   |d   k  }|s|s|s|j	                  d        n |D ]  }t        |d         t        |d         t        |d         }}}||||z
  dkD  r|j	                  d       |dk(  r||dk  r|j	                  d       |dk(  r|d   xs ddk\  r|j	                  d       |dv r||dk  r|j	                  d       |d
   r|j	                  d        t        d |D              }|r8dk\  r3t        fdj                         D              r|j	                  d       t               |D ]  }t        |d
         z   |r*t        fdt        D              r|j	                  d       t        | j                  d            dkD  r|j	                  d        t        t        j!                  |            |fS c c}w c c}w c c}w )!zGCheck ids a forecast diverges on, plus the discouraged pairs it listed.problemsdanger_r   r   r   r   typer   r   	locations
likelihoodNsize_minsize_maxr   r   r   r   r   r   )r   r   g      ?r   r   c              3  b   K   | ]'  }t        |d          duxr t        |d          dk\   ) yw)r>   Nr   )r6   )r%   ps     r'   r(   z"forecast_checks.<locals>.<genexpr>c   s4     ]qd1Z=!-J$q}2E2JJ]s   -/r   c              3  (   K   | ]	  }|k    y wr3    )r%   r5   tops     r'   r(   z"forecast_checks.<locals>.<genexpr>d   s     AACAs   r   c              3  :   K   | ]  }|   d k\  xr |v  yw)r   NrB   )r%   bdangertoucheds     r'   r(   z"forecast_checks.<locals>.<genexpr>i   s&     Kq	Q;1G+;;Ks   r   bottom_liner   )getr	   maxvaluesappendintPAIRSboolr
   absr6   anysetr0   listdictfromkeys)foutr8   rE   r@   by_typepairpairsapapbloclikesasbsizer/   sminsmaxbigrF   rC   rG   s                       @@@r'   forecast_checksrf   ;   s{   CuuZ &BH49:qa}%**:F
fmmo
Cuu

#$
ax

*+*23Qs1V9~q 3G3#QdtAw''9d1g>PTQEQ

() 	1WQZB8B{O,x;/HHI,t+~<0@0L~QTUWXdUehjkwhxUxQy}~Q~2j>"DJ$892j>"DJ$8927"Hr!u1~H"Q%2a5.4DJJ()	  &AfIQz](;T!J-=P4 0TD[1_JJ|$6d&4!8JJ{#6q+!1JJ|$;4+s
JJ-.~JJ}%& ]T\]
]C
saxCAAA

'(eG ,8AkN++,CKUKK

)*aeeM*+a/

%&c"#U**] ; 4Qs    M*M/=M4
M4M4c                   t        | d|||||f      }t               }t               }t        t              }t        t              }dx}	}
|D ]  }t	        |      \  }}||d      }|dxx   dz  cc<   |D ]  }|t
        |   xx   dz  cc<    |D ]K  }||xx   dz  cc<   ||xx   dz  cc<   t        ||         dk  s/||   j                  |d   |d   d	       M |r|	dz  }	|d
xx   dz  cc<   t        |      t        z
  s|
dz  }
|dxx   dz  cc<    t        |      }||	|
t        j                         D cg c]-  \  }\  }}}||||||   |rt        ||   |z  d      nd ||   d/ c}}}}t        |      t        |j                               D ci c]  \  }}|t        |       c}}dS c c}}}}w c c}}w )Na  SELECT f.id, f.valid_date, f.season, f.guidance_era, f.normal_caution, f.bottom_line,
                  f.danger_upper, f.danger_middle, f.danger_lower,
                  coalesce(json_agg(json_build_object('type', p.problem_type, '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.guidance_era = %s) AND (%s::text IS NULL OR f.source = %s)
           GROUP BY f.id ORDER BY f.valid_date DESCr   season	forecastsr   r   id
valid_date)rj   daterQ   substantiver   )rj   labelsourcenotecountshareexamples)ri   rQ   rm   checksrZ   	by_season)r   r   r   rS   rf   
PAIR_NAMESr#   rL   rR   
STYLE_ONLYCHECKSitemsroundrT   sorted)conn	center_ideraro   rowscountspair_countsrs   seasonsany_divany_substantiverV   idsrZ   rh   rY   cidnrn   srcrp   scs                          r'   auditr   p   s   	7 
Cff-D iF"9K +D 1H"-g"6G !!Go '$Q'
U8%{q  	/D
4()Q.)	/ 	OC3K1K3K1K8C=!A%$$AdGQ|_%MN		O
 qLG5MQMs8j q O=!Q&!!'" 	D	A& ,2<<>
 
 ('eS$ #tfUXk34eF3K!OQ/$HUXM[

 k"-3GMMO-DETQaajE 
 Fs   >2F7F?)r.   
str | NonereturnrM   )rV   rT   r   z'tuple[list[str], list[tuple[int, int]]])NN)
r|   r   r}   rM   r~   r   ro   r   r   rT   )__doc__
__future__r   r)   collectionsr   r   psycopgr   dbr   enginer	   r
   rx   rw   rN   rv   compiler-   r0   r6   rf   r   rB   r1   r'   <module>r      s   	 # 	 ,   # z\ f@l E aNP^ gt!
$ !!
()3ISq  |S  T
"**@AQ+2+j/r1   