
    #Kj=L                       d Z ddlmZ ddlZddlmZ ddlmZmZm	Z	 ddl
mZ ddlmZ ddlmZmZ d	d
lmZ d	dlmZmZmZ d	dlmZmZ d	dlmZmZmZmZ d	dlm Z m!Z!m"Z"  edg      Z# G d de      Z$ G d de      Z% G d de      Z&d7dZ'dZ(d8dZ)d9dZ*d:dZ+d;dZ,d<dZ-d=dZ.d>dZ/e#ja                  d        ee       ee      fd?d!       Z1e#je                  d       dd" ee       ee      fd@d#       Z3e#je                  d$       ee       ee      fdAd%       Z4e#jk                  d$       ee       ee      fdBd&       Z6e#jo                  d$       ee       ee      fdAd'       Z8e#ja                  d(       ee       ee      fdCd)       Z9 e: e;ejx                  g d*d+,            Z=d-d.d/d0Z>dDd1Z?dEd2Z@dFd3ZAe#je                  d4      d5 ee       ee      fdGd6       ZBy)Ha  Wizard runs: save drafts, finalize with the forecaster's own ratings, export.

The server always recomputes the engine output from the inputs (never trusting
the browser's numbers) and records which config version and engine version
produced the suggestion.
    )annotationsN)date)	APIRouterDependsHTTPException)PlainTextResponse)
Connection)	BaseModelField   )engine)Usercurrent_userrequire_center)active_configconfig_by_id)Jsonbfetchallfetchoneget_conn)AFP_PROBLEM_NAMESAFP_TO_DRUPAL_PROBLEMLIKELIHOOD_NAMESruns)tagsc                      e Zd ZU ded<   dZded<   ded<   ded	<   i Zd
ed<   dZded<   dZded<   dZded<   dZ	ded<   y)RunInint	center_idN
int | Nonezone_idr   
valid_datedictinputszdict[str, str]justifications
str | Nonenotesbottom_linedict | Noneclient_computeddict[str, int | None] | Nonedraft_ratings)
__name__
__module____qualname____annotations__r!   r%   r'   r(   r*   r,        -/var/www/avy-guidance/api/app/routers/runs.pyr   r      sO    NGZL%'NN'E:"K"#'O['26M/6r2   r   c                  ~    e Zd ZU dZded<   dZded<   dZded<   dZded	<   dZd
ed<   dZ	d
ed<   dZ
ded<   dZded<   y)	RunUpdateNr    r!   zdate | Noner"   r)   r$   zdict[str, str] | Noner%   r&   r'   r(   r*   r+   r,   )r-   r.   r/   r!   r0   r"   r$   r%   r'   r(   r*   r,   r1   r2   r3   r5   r5   '   sT    GZ"J"FK,0N)0E:"K"#'O['26M/6r2   r5   c                      e Zd ZU  edd      Zded<    edd      Zded<    edd      Zded<    eddd	      Zd
ed<   dZ	ded<   dZ
ded<   y)
FinalizeInr      )geler   uppermiddlelowerN)defaultr9   r:   r    overallr&   r(   r'   )r-   r.   r/   r   r;   r0   r<   r=   r?   r(   r'   r1   r2   r3   r7   r7   2   s\    !"E3"1#FC#!"E3"q9GZ9"K"E:r2   r7   c                   | xs i } i }dD ]g  }| j                  |      }|dv rd ||<   	 t        |      }t        j                  |      s||cxk  r|k  sn t	        d| d| d| d|       |||<   i |d   !|d	   |d   |d	   kD  r|d	   |d   c|d<   |d	<   |S # t        t        f$ r}t	        d| d| d      |d }~ww xY w)
N)minmax)N   .z must be a numberz must be between z and rA   rB   )getfloat	TypeError
ValueErrorr   mathisnan)	valuelohilabeloutkvfes	            r3   _rangerU   >   s   KRE
C IIaL
?CF	MaA ::a=a2was2CB4uRD%QRRA 5z#e*"8SZ#e*=T!$USZE
CJJ :& 	Mwas2C%DE1L	Ms   BC,B??Ci  c                    | yt        | t              st        dd|dz    d      t        |       t        kD  rt        dd|dz    dt         d      | S )NrC   rD   Problem    z: discussion must be textz: discussion is longer than z characters)
isinstancestrr   lenMAX_DISCUSSION)rL   is     r3   _discussionr^   U   sc    }eS!C8AE72K!LMM
5zN"C8AE72N~N^^i!jkkLr2   c                   |d   D ch c]  }|d   	 }}|d   D ch c]  }|d   	 }}t        d |d   D              }| j                  d      xs g D cg c]	  }||v s| }}| j                  d      xs g }t        |      |j                  dd      kD  r t        d	d
|j                  dd       d      t	               }	g }
t        |      D ]  \  }}	 t        |j                  d            }||vrt        d	d|dz    d|       ||	v rt        d	d|dz    d      |	j                  |       g }|j                  d      xs g D ]Q  }t        j                  |      }|st        d	d|dz    d|      |d    d|d    }||vsA|j                  |       S |j                  d      }|
j                  ||t        |j                  d      dt        |d         dz
  d| d      t        |j                  d      dt        |d         dz
  d| d      t        |j                  d      d|d| d      t        j                  |      r"dt        |      cxk  rdk  rn nt        |      ndt        |j                  d       |      d!        ||
d"S c c}w c c}w c c}w # t        t        f$ r}t        d	d|dz    d      |d}~ww xY w)#zGNormalize wizard inputs to the canonical engine shape; 422 on bad data.
conditionsidproblemTypesc              3  &   K   | ]	  }|d      yw)rL   Nr1   ).0ss     r3   	<genexpr>z"validate_inputs.<locals>.<genexpr>c   s     7!1W:7s   sizesproblemsmaxProblems   rD   zAt most z avalanche problemstyperW   rX   z: choose a problem typeNz: unknown problem type z&: each problem type can be listed once	locationsz: invalid location r    likelihoodChoicesensitivityz	problems[z].sensitivitydistributionz].distributionsizez].sizer8   
discussion)rk   rl   ro   rp   rq   rn   rr   )r`   rh   )rB   rF   r[   r   set	enumerater   rH   rI   addr   parse_locationappendrU   is_numr^   )r$   configccondition_idsptype_idsmax_sizer`   rh   seencleanr]   trT   rl   locparsedkeychoices                      r3   validate_inputsr   _   s   &,\&:;QtW;M;!'!78A$8H87vg77H$jj6<"TmAS!TJTzz*%+H
8}vzz-33C8FJJ}a,H+II\!]^^5DE(# 
1	WAEE&M"A HxAw6MaS%QRR9xAw6\%]^^	55%+ 	&C**3/F#C8AE7:McW)UVVAYKq,C)#  %	& )*&%aeeM&:As6-CX?Y\]?]ajkljmmz_{| &quu^'<aVNE[A\_`A`dmnompp~b  !AquuV}aYqc;PQ39==3HQRUV\R]MbabMbCKhl)!%%*=qA
	
'
< %%88O <8T :& 	WxAw6M%NOUVV	Ws.   JJ!	J&!J&J++K:KKc                   | sy	 | j                  d      |d   k7  ry| j                  dg       D cg c]  }|j                  d       c}|d   D cg c]  }|d   	 c}k7  S c c}w c c}w # t        t        f$ r Y yw xY w)NFsuggestionsTrh   
likelihood)rF   AttributeErrorrH   )clientserverr|   s      r3   _differsr      s    ::m$}(==-3ZZ
B-GHl#HflmwfxLyabQ|_LyyyHLyI& s3   A- A- A#		A- A(A- #
A- -A?>A?c                    | syi }t         j                  D ]8  }| j                  |      }t        |t              rd|cxk  rdk  rn n|nd||<   : t        |      S )zPIn-progress danger per band on a draft: {upper, middle, lower} each 0-5 or None.Nr   r8   )r   BANDSrF   rY   r   r   )ratingsrP   brR   s       r3   _clean_ratingsr      sY    
C\\ CKKN C(Q![q[dAC :r2   c                2    | d   }|d   |d   |d   |d   dS )Nr   r;   r<   r=   r?   )susmslsor1   )computedre   s     r3   _suggest_colsr      s+    AG*AhKqz9VVr2   c                \    t        | d|f      }|st        dd      t        ||d          |S )Na:  SELECT r.*, u.name AS user_name, cc.version AS config_version, z.name AS zone_name, c.name AS center_name
           FROM tool_runs r JOIN users u ON u.id = r.user_id JOIN center_configs cc ON cc.id = r.config_id
           JOIN centers c ON c.id = r.center_id LEFT JOIN zones z ON z.id = r.zone_id WHERE r.id = %si  zRun not foundr   )r   r   r   )connrun_iduserruns       r3   	_load_runr      sB    
	i 
	C C114[)*Jr2   c                @    |t        | d||f      st        dd      y y )Nz4SELECT 1 FROM zones WHERE id = %s AND center_id = %srD   z#Zone does not belong to this center)r   r   )r   r   r!   s      r3   _check_zoner      s2    8D2hkrt}j~#C!FGG $@r2   z/runsc                   t        || j                         t        || j                  | j                         t	        || j                        }t        | j                  |d         }t        j                  ||d         }t        |d| j                  | j                  |j                  |d   t        j                  | j                  t        |      t        |      t        | j                        | j                  | j                   t#        | j$                  |      t'        | j(                        dt+        |            }t-        |d   ||      S )Nry   a  INSERT INTO tool_runs (center_id, zone_id, user_id, config_id, engine_version, valid_date, inputs, computed,
               justifications, suggested_upper, suggested_middle, suggested_lower, suggested_overall, bottom_line, notes,
               client_mismatch, draft_ratings)
           VALUES (%(c)s, %(z)s, %(u)s, %(cfg)s, %(ev)s, %(d)s, %(i)s, %(comp)s, %(j)s, %(su)s, %(sm)s, %(sl)s, %(so)s,
               %(bl)s, %(n)s, %(mm)s, %(dr)s) RETURNING idra   )rz   zucfgevdr]   compjblnmmdr)r   r   r   r!   r   r   r$   r   compute_runr   ra   ENGINE_VERSIONr"   r   r%   r(   r'   r   r*   r   r,   r   get_run)bodyr   r   r   r$   r   rows          r3   
create_runr      s   4(dnndll3
dnn
-CT[[#h-8F!!&#h-8H
	> dllTZ`ZoZouV}eHoTYZ^ZmZmTn""8DDXDXZb;c !3!34		
 9Fh8O		
C 3t9dD))r2   2   c                d    t        ||        t        |d| ||t        t        |d      d      f      S )Na  SELECT r.id, r.valid_date, r.status, r.created_at, r.finalized_at, r.zone_id, z.name AS zone_name, u.name AS user_name,
                  r.suggested_upper, r.suggested_middle, r.suggested_lower, r.final_upper, r.final_middle, r.final_lower,
                  cc.version AS config_version,
                  (SELECT array_agg((p->>'type')::int) FROM jsonb_array_elements(r.inputs->'problems') p) AS problem_types
           FROM tool_runs r JOIN users u ON u.id = r.user_id JOIN center_configs cc ON cc.id = r.config_id
           LEFT JOIN zones z ON z.id = r.zone_id
           WHERE r.center_id = %s AND (%s::text IS NULL OR r.status = %s)
           ORDER BY r.valid_date DESC, r.id DESC LIMIT %srX   i  )r   r   rA   rB   )r   statuslimitr   r   s        r3   	list_runsr      s<    4#	= 
FFCE1s$;< r2   z/runs/{run_id}c                    t        || |      }t        j                  |d   |d   |d         |d<   |d   dk(  xr+ |d   |j                  k(  xs |j	                  |d         d	k(  |d
<   |S )Nr$   r   r%   	readinessr   draftuser_idr   center_admincan_edit)r   r   r   ra   role_inr   r   r   r   s       r3   r   r      s    
D&$
'C''Hs:L\H]^C(mw.  CC	Ndgg4M  5BQUQ]Q]^abm^nQo  tB  RBC
OJr2   c                   t        || |      }|d   dk7  rt        dd      |d   |j                  k7  r#|j                  |d         dk7  rt        dd	      |j	                  d
      }d|v rt        ||d   |d          t        ||d         }t        |j                  d|d         |d         }t        j                  ||d         }|j                  d| |j                  d|d         |j                  d|d         t        |      t        |      t        |j                  d|d               |j                  d|d         |j                  d|d         |d   t        j                  t        |j                  d      |      d|v rt        |d         n|d   rt        |d         nd dt!        |             t#        | ||      S )Nr   r     zFinalized runs cannot be editedr   r   r     z3Only the author or a center admin can edit this runT)exclude_unsetr!   r$   ry   a  UPDATE tool_runs SET zone_id = %(z)s, valid_date = %(d)s, inputs = %(i)s, computed = %(comp)s, justifications = %(j)s,
               notes = %(n)s, bottom_line = %(bl)s, config_id = %(cfg)s, engine_version = %(ev)s,
               suggested_upper = %(su)s, suggested_middle = %(sm)s, suggested_lower = %(sl)s, suggested_overall = %(so)s,
               client_mismatch = %(mm)s, draft_ratings = %(dr)s, updated_at = now()
           WHERE id = %(id)sr"   r%   r'   r(   ra   r*   r,   )ra   r   r   r]   r   r   r   r   r   r   r   r   )r   r   ra   r   
model_dumpr   r   r   rF   r   r   executer   r   r   r   r   r   )	r   r   r   r   r   fieldsr   r$   r   s	            r3   
update_runr      s   
D&$
'C
8}C!BCC
9~ T\\#k2B%C~%UC!VWW__4_0FFD#k*F9,=>
c+.
/CVZZ#h-@#h-PF!!&#h-8HLL	  vzz)S^D6::VbdghtduKvvheFJJO_adeuavDw>xGS\2&**]TWXeTf:gt9F$9$9&**UfJgiqAr=LPV=V.!89/2?/Cc/*+	
 H%	
  64&&r2   c                    t        || |      }|d   dk7  rt        dd      |d   |j                  k7  r#|j                  |d         dk7  rt        dd	      |j	                  d
| f       ddiS )Nr   r   r   z"Finalized runs are kept as historyr   r   r   r   z5Only the author or a center admin can delete this runz#DELETE FROM tool_runs WHERE id = %sokT)r   r   ra   r   r   r   s       r3   
delete_runr     sx    
D&$
'C
8}C!EFF
9~ T\\#k2B%C~%UC!XYYLL6	B$<r2   z/runs/{run_id}/finalizec                   t        || |      }|d   dk7  rt        dd      |d   |j                  k7  r#|j                  |d         dk7  rt        dd	      t	        ||d
         d   }t        j                  |d   |      }t        j                  |d   ||d         }|rt        dd|d      |j                  |j                  |j                  d}|j                  |j                  nt        |j                               }	t        j                  |d   ||      |d<   t        |d   d         D 
cg c]@  \  }
}|
dz   |d   |d   |d   |
   d   |d   d   |d   d   |j!                  d      xs ddB }}
}|j#                  dt%        |      |j                  |j                  |j                  |	t%        |      |j&                  |j(                  | f	       t+        | ||      S c c}}
w )Nr   r   r   zAlready finalizedr   r   r   r   z7Only the author or a center admin can finalize this run	config_idry   r$   r%   rD   z Finish the run before finalizing)messageissuesr;   r<   r=   finalChecksrh   rX   rk   rl   r   rq   rA   rB   rr   rC   rankrk   rl   r   size_minsize_maxrr   a?  UPDATE tool_runs SET status = 'finalized', finalized_at = now(), updated_at = now(), computed = %s,
               final_upper = %s, final_middle = %s, final_lower = %s, final_overall = %s, final_problems = %s,
               bottom_line = coalesce(%s, bottom_line), notes = coalesce(%s, notes)
           WHERE id = %s)r   r   ra   r   r   r   r   r   r;   r<   r=   r?   rB   valuesguidance_checksrt   rF   r   r   r(   r'   r   )r   r   r   r   r   ry   r   r   finalr?   r]   r|   final_problemss                r3   finalize_runr     s   
D&$
'C
8}C!455
9~ T\\#k2B%C~%UC!Z[[$K 01(;F!!#h-8Hc(mXs;K7LMFC-O[a!bccjjDKK$**ME"ll6dllC<OG$44S]FERH] c(mJ78 Aq EfI;":.q1,?&	%(&	%(%%-3	
N  	LL	 
x$**dkk4::wnH]_c_o_oquq{q{  ~D  	E 64&&'s   6AG*)NNEESESSWWNWT)strictabovenearbelowr   c                6    | yt        |       }|dk\  ryd|dS )N?   u   D4–5Dg)rG   )rR   rS   s     r3   	_fmt_sizer   C  s+    yaAAvqe9r2   c                    i | D ]=  }|j                         \  }}j                  |g       j                  t        |          ? dj	                  fdt
        j                  D              S )Nz; c              3  z   K   | ]2  }|v sj                  ||       d dj                  |           4 yw): z, N)rF   join)rd   r   band_labelsby_bands     r3   rf   z"_locations_text.<locals>.<genexpr>Q  s?     oQbcgnbn1-.b71:1F0GHos   	;.;)split
setdefaultrw   ABBRr   r   r   )rl   r   r   aspectbandr   s    `   @r3   _locations_textr   L  sc    $&G :yy{4$++DL9: 99oRXR^R^ooor2   c                    | j                  d      r| d   S t        | d   d         D cg c]C  \  }}|dz   |d   |d   | d   d   |   d   |d	   d
   |d	   d   |j                  d      xs ddE c}}S c c}}w )Nr   r$   rh   rX   rk   rl   r   r   rq   rA   rB   rr   rC   r   )rF   rt   )r   r]   r|   s      r3   _problems_for_exportr   T  s    
ww #$$ c(mJ78 Aq	 E1V91[>j/*5a8F&	%(ai6FVWV[V[\hViVomo	
  s   AA7z/runs/{run_id}/exporttextc                	   t        || |      t        |d         d   }t              }|j                  d      xs i |d   D ci c]  }|d   |d    c}|dk(  rdj                  d	      xs d
dd   d   d   dg|D cg c]y  }|d   t	        j                  |d         |d   |d   t        j                  |d         |d    t        |d         dt        |d         dgng |j                  d      xs d
d{ c}d   d   d   d   ddS |dk(  r|D cg c]  }t        t        |d            t        |d   xs d
      t        t        d t        d!t        j                  t        |d   xs |d                                 t        t        d t        d!t        j                  t        |d   xs |d                                 |d   D cg c]O  }t        |j                         d        d"t         j"                  j%                  |j                         d#          Q c}d$ c}}j                  d%         j                  d&         j                  d'         j                  d(         d)j                  d*      xs i j'                         D 	cg c]  }	|	s|		 c}	d+S |d,k7  rt)        d-d.      d/d0    d1   rd2d1   z   nd
 d3d    g}
d4   d5k(  r=|
j+                  d6d7j-                  fd8t         j.                  D              z          n|
j+                  d9       |
j+                  d
       |D ]  }t        j                  |d   d:      }|d   |d   k(  rt1        |d         nt1        |d          d;t1        |d          }|
j+                  d<|d    d=t	        j                  |d          d3|j3                          d3|        |
j+                  d>t5        |d                 |j                  d      s|
j+                  d?|d   j7                                  j                  d*      xs i j9                         D ]  \  }}|s	|
j+                  d@| dA|        ! j                  d	      r|
d
dBd	    gz  }
|
d
dCd    dDd    dEgz  }
t;        dFj-                  |
            S c c}w c c}w c c}w c c}}w c c}	w )GNr   ry   
bandLabelsdangerLevelslevelnameafpforecastr(   rC   currentfinal_upperfinal_middlefinal_lower)	valid_dayr;   r<   r=   rk   r   rl   r   r   z.1fr   rr   )avalanche_problem_idr   r   locationr   rq   rr   ra   r"   config_versionengine_version)tool_run_idr"   r
  r  )product_typer(   dangerforecast_avalanche_problemsmetabackdroprX   r8   :r   )rk   r   sizeMinsizeMaxrosesuggested_uppersuggested_middlesuggested_lowersuggested_overall)r   r   r   r?   r%   )rh   r   r'   r   rD   z!fmt must be text, afp or backdropu    Avalanche forecast guidance — center_name	zone_namez / u    — r   	finalizedzDanger: z | c              3  x   K   | ]1  }j                  ||       d j                  d|z      d        3 yw)r   final_r   N)rF   )rd   r   r   danger_namesr   s     r3   rf   zexport_run.<locals>.<genexpr>  sB     vdeKOOAq12"\5E5Ec(UV,FWY\5]4^_vs   7:zDanger: not yet set (draft)r   u   –rW   r   z	  Where: z  zPairing note (z): zBottom line: zTool config vz	, engine u3   . Guidance only — the forecaster sets the rating.
)r   r   r   rF   r   r   rG   rZ   r   rB   rA   rJ   floorceilWIZARD_BANDr   r   ASPECTSindexr   r   rw   r   r   r   
capitalizer   stripitemsr   )r   fmtr   r   ry   rh   r   r|   r   rR   lineslikerq   r   r   r   r  r   s                  @@@r3   
export_runr,  a  s   
D&$
'C$K 01(;F#C(H**\*0bK39.3IJaAgJ&	)JL
e|&77=17R%.]9KWZ[iWjux  zG  vH  I  J ",  -.fI-11!F)<fI !+"2"6"6q"G^_`j^k^wa
m 4S9uQz]?STW>XZ}"#%%"5";, %(IS=Nbefvbw  LO  P`  La  b!
 	
& j "     5ai @A"%ao&;"<"3q#aE!J-B`STU_S`<a1b*c#de"3q#a5:A_RST^R_;`1a*b#cdklmxkydg;syy{1~67q9M9MciikZ[n9]8^_ &))#.?*@A$((-?)@A%))#.?*@A'++C0C,DE	 #&''*:";"Ar!I!I!KQAqaQ'
 	
, f}C!DEE/M0B/C`cdo`pECP[L\D\vxCyy~  @C  DP  @Q  R  S  TE
8}#jjvioiuiuvvw	

 	23	LL 9##AlOS9+,Z=AjM+Iy:'R[\]^h\iRjQkknoxyz  |F  zG  pH  oI  PJx&	{"->-B-B1V9-M,NeTXTcTcTeSffklpkqrsy;!M NOP55LL2ao3356789 gg./52<<> :	TLL>#c$89: ww}"c-&8%9:;;	bM#&6"7!8	#FVBWAX  YL  M  N  NETYYu-..M K,0$ Rs3   
S#A>S(
+B:S2%AS-9S27S8?S8-S2)rM   rG   rN   rG   rO   rZ   returnr#   )r]   r   r-  rZ   )r$   r#   ry   r#   r-  r#   )r   r)   r   r#   r-  bool)r   r)   r-  zJsonb | None)r   r#   r-  r#   )r   r	   r   r   r   r   r-  r#   )r   r	   r   r   r!   r    r-  None)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   r5   r   r	   r   r   )r   r   r   r7   r   r	   r   r   )r-  rZ   )rl   z	list[str]r   r#   r-  rZ   )r   r#   r-  z
list[dict])r   r   r)  rZ   r   r	   r   r   )C__doc__
__future__r   rJ   datetimer   fastapir   r   r   fastapi.responsesr   psycopgr	   pydanticr
   r   rC   r   authr   r   r   configsr   r   dbr   r   r   r   mappingr   r   r   routerr   r5   r7   rU   r\   r^   r   r   r   r   r   r   postr   rF   r   r   putr   deleter   r   r#   zipr$  r   r#  r   r   r   r,  r1   r2   r3   <module>r@     s0   #   5 5 /  %  5 5 1 4 4 P P		!	7I 	77	 7 ( )9XW
H W/6x/@wWcOd * *. G37b]dem]n  ~E  FR  ~S    ,3H,=GT`La   @G@Q`ght`u ' '>  /6x/@wWcOd  ! &'CJ8CTcjkwcx !' (!'L C LUYZ[6GDp
 #$'-'(BSbijvbw K/ %K/r2   