Ë
    ì¨jè#  ã                  ó²  — d 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
mZ ddlmZmZ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mZmZ  edg¬«      Z G d„ de
«      Z G d„ de
«      Z G d„ de
«      Z G d„ de
«      Z ejC                  d«       ee«       ee«      fd-d„«       Z"ejG                  d«       ee«       ee«      fd.d„«       Z$ejC                  d«       ee«       ee«      fd/d„«       Z%ejG                  d«       ee«       ee«      fd0d„«       Z&ejC                  d«       ee«       ee«      fd/d„«       Z'ejC                  d«       ee«       ee«      fd/d„«       Z(ejC                  d «       ee«       ee«      fd/d!„«       Z)ejC                  d"«       ee«       ee«      fd1d#„«       Z*d2d$„Z+ejG                  d «       ee«       ee«      fd3d%„«       Z,ejG                  d&«       ee«       ee«      fd1d'„«       Z-ejG                  d(«       ee«       ee«      fd4d)„«       Z.ejC                  d*«      d+ ee«       ee«      fd5d,„«       Z/y+)6z5Centers, zones, members and versioned configurations.é    )Úannotations)Ú	APIRouterÚDependsÚHTTPException)Ú
Connection)Ú	BaseModelÚFieldé   )ÚUserÚcurrent_userÚrequire_adminÚrequire_center)Úreplay_config)Úactive_configÚconfig_by_idÚseed_default)ÚJsonbÚfetchallÚfetchoneÚget_connÚcenters)Útagsc                  ó�   — e Zd ZU  ed¬«      Zded<    edd¬«      Zded<   d	Zd
ed<   dZded<   dZ	ded<    edd¬«      Z
ded<   y	)ÚCenterInz^[a-z0-9-]{2,40}$)ÚpatternÚstrÚslugr
   éx   ©Ú
min_lengthÚ
max_lengthÚnameNú
str | NoneÚafp_center_idzAmerica/Los_AngelesÚtimezoneÚenÚlocaleÚNACz^(NAC|SAC)$)Údefaultr   Údanger_scale)Ú__name__Ú
__module__Ú__qualname__r	   r   Ú__annotations__r"   r$   r%   r'   r*   © ó    ú0/var/www/avy-guidance/api/app/routers/centers.pyr   r      sR   … ÙÐ1Ô2€Dˆ#Ó2Ù ¨sÔ3€Dˆ#Ó3Ø $€M�:Ó$Ø)€HˆcÓ)Ø€FˆCÓÙ e°]ÔC€L�#ÔCr0   r   c                  óH   — e Zd ZU  edd¬«      Zded<   dZded<   dZd	ed
<   y)ÚZoneIné   r   r   r   r"   Nr#   Úafp_zone_idúdict[str, str] | NoneÚband_labels)r+   r,   r-   r	   r"   r.   r5   r7   r/   r0   r1   r3   r3      s(   … Ù ¨sÔ3€Dˆ#Ó3Ø"€K�Ó"Ø)-€KÐ&Ô-r0   r3   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<    e
d¬«      Zded<   y)ÚConfigDraftInNz
int | NoneÚ	parent_idzdict[str, list[int]] | NoneÚdanger_tablez!dict[str, list[list[int]]] | NoneÚgradient_tablez dict[str, dict[str, int]] | NoneÚlikelihood_matrixzbool | NoneÚshow_danger_suggestionr6   r7   é
   )r    r   Ú	rationale)r+   r,   r-   r:   r.   r;   r<   r=   r>   r7   r	   r@   r/   r0   r1   r9   r9       sS   … Ø €IˆzÓ à04€LÐ-Ó4Ø8<€NÐ5Ó<Ø:>ÐÐ7Ó>Ø*.Ð˜KÓ.Ø)-€KÐ&Ó-Ù bÔ)€IˆsÔ)r0   r9   c                  ó   — e Zd ZU dZded<   y)Ú	ApproveInNr#   r@   )r+   r,   r-   r@   r.   r/   r0   r1   rB   rB   +   s   … Ø €IˆzÔ r0   rB   z/centersc                ób   — |j                   rt        | d«      S t        | d|j                  f«      S )Nz#SELECT * FROM centers ORDER BY namezoSELECT c.*, m.role FROM centers c JOIN memberships m ON m.center_id = c.id WHERE m.user_id = %s ORDER BY c.name)Úis_adminr   Úid)ÚconnÚusers     r1   Úlist_centersrH   /   s3   € à‡}‚}Ü˜ÐCÓDÐDÜØØyØ	�‰ˆ
óð r0   c                ó†   — t        |«       t        |d| j                  «       «      }t        ||d   |j                  «       |S )NzÁINSERT INTO centers (slug, name, afp_center_id, timezone, locale, danger_scale)
           VALUES (%(slug)s, %(name)s, %(afp_center_id)s, %(timezone)s, %(locale)s, %(danger_scale)s) RETURNING *rE   )r   r   Ú
model_dumpr   rE   )ÚbodyrF   rG   Úrows       r1   Úcreate_centerrM   :   sB   € ä�$ÔÜ
Øð	uà�‰Óó	€Cô ��s˜4‘y $§'¡'Ô*Ø€Jr0   z/centers/{center_id}c                óž   — t        || «       t        |d| f«      }|st        dd«      ‚t        |d| f«      }i |¥||j	                  | «      dœ¥S )Nz#SELECT * FROM centers WHERE id = %si”  zCenter not foundz6SELECT * FROM zones WHERE center_id = %s ORDER BY name)ÚzonesÚrole)r   r   r   r   Úrole_in)Ú	center_idrF   rG   ÚcenterrO   s        r1   Ú
get_centerrT   G   s\   € ä�4˜Ô#Ü�dÐAÀIÀ<ÓP€FÙÜ˜CÐ!3Ó4Ð4Ü�TÐSÐV_ÐUaÓb€EØFˆfÐF˜u¨d¯l©l¸9Ó.EÒFÐFr0   z/centers/{center_id}/zonesc           
     ó´   — t        || d¬«       t        |d| |j                  |j                  |j                  rt        |j                  «      f«      S d f«      S )NT©ÚadminzaINSERT INTO zones (center_id, name, afp_zone_id, band_labels) VALUES (%s, %s, %s, %s) RETURNING *)r   r   r"   r5   r7   r   )rR   rK   rF   rG   s       r1   Úcreate_zonerX   Q   s^   € ä�4˜¨$Õ/ÜØØkØ	�D—I‘I˜t×/Ñ/ÈD×L\ÒL\´°t×7GÑ7GÓ1HÐgóð ð cgÐgóð r0   z/centers/{center_id}/membersc                ó:   — t        || d¬«       t        |d| f«      S )NTrV   z›SELECT u.id, u.email, u.name, u.last_login_at, m.role FROM memberships m JOIN users u ON u.id = m.user_id
           WHERE m.center_id = %s ORDER BY u.name©r   r   ©rR   rF   rG   s      r1   Úlist_membersr\   [   s(   € ä�4˜¨$Õ/ÜØð	5à	ˆó	ð r0   z/centers/{center_id}/configc                ó2   — t        || «       t        || «      S )N)r   r   r[   s      r1   Úget_active_configr^   f   s   € ä�4˜Ô#Ü˜˜yÓ)Ð)r0   z/centers/{center_id}/configsc                ó6   — t        || «       t        |d| f«      S )Na{  SELECT cc.id, cc.version, cc.status, cc.rationale, cc.parent_id, cc.created_at, cc.proposed_at, cc.approved_at,
                  cu.name AS created_by_name, au.name AS approved_by_name
           FROM center_configs cc
           LEFT JOIN users cu ON cu.id = cc.created_by LEFT JOIN users au ON au.id = cc.approved_by
           WHERE cc.center_id = %s ORDER BY cc.version DESCrZ   r[   s      r1   Úlist_configsr`   l   s(   € ä�4˜Ô#ÜØð	?ð
 
ˆóð r0   z/configs/{config_id}c                ó<   — t        || «      }t        ||d   «       |S )NrR   )r   r   ©Ú	config_idrF   rG   rL   s       r1   Ú
get_configrd   z   s"   € ä
�t˜YÓ
'€CÜ�4˜˜[Ñ)Ô*Ø€Jr0   c           	     ó°  — | d   }t        | d   «      xr t        |j                  dg «      «      }dD ]V  }|j                  |«      }t        |t        «      r!t        |«      |k7  st	        d„ |D «       «      sŒEt        dd|› d|› d	�«      ‚ | d
   j                  «       D ]5  \  }}t	        d„ |j                  «       D «       «      sŒ't        dd|› d�«      ‚ y )NÚdangerTableÚsizesÚ1)rh   Ú2Ú3Ú4Ú5c              3  ód   K  — | ](  }t        |t        «       xs d |cxk  xr dk  nc  –— Œ* y­w)r   é   N©Ú
isinstanceÚint©Ú.0Úvs     r1   ú	<genexpr>z#_validate_tables.<locals>.<genexpr>†   s4   è ø€ Ò?wÐnoÄJÈqÔRUÓDVÐ@VÒ@iÐ^_ÐcdÖ^iÐhiÔ^iÐZiÓ@iÑ?wùó   ‚.0é¦  zDanger table row z must have z levels between 0 and 5.ÚlikelihoodMatrixc              3  ód   K  — | ](  }t        |t        «       xs d |cxk  xr dk  nc  –— Œ* y­w)r4   rn   Nro   rr   s     r1   ru   z#_validate_tables.<locals>.<genexpr>‰   s,   è ø€ ÒO¸Q”:˜a¤Ó%Ð%Ò8¨Q°!®[°q¬[¨Ó8ÑOùrv   zLikelihood matrix row z must contain levels 1-5.)ÚlenÚgetrp   ÚlistÚanyr   ÚitemsÚvalues)ÚcfgÚtableÚcolsÚlkrL   Údists         r1   Ú_validate_tablesr…   �   sà   € Ø�Ñ€EÜˆs�7‰|ÓÒ8¤ U§Y¡Y¨s°BÓ%7Ó!8€DØ'ò hˆØ�i‰i˜‹mˆÜ˜#œtÔ$¬¨C«°DÒ(8¼CÑ?wÐsvÔ?wÕ<wÜ Ð'8¸¸¸KÈÀvÐMeÐ%fÓgÐgðhð Ð+Ñ,×2Ñ2Ó4ò _‰	ˆˆcÜÑOÀ#Ç*Á*Ã,ÔOÕOÜ Ð'=¸d¸VÐC\Ð%]Ó^Ð^ñ_r0   c           
     ó‚  — t        || d¬«       |j                  rt        ||j                  «      nt        || «      }|j                  r|d   | k7  rt	        dd«      ‚t        |d   «      }|j                  �|j                  |d<   |j                  �|j                  |d<   |j                  �|j                  |d	<   |j                  �|j                  |d
<   |j                  �|j                  |d<   t        |«       t        |d| f«      d   }t        |d| |t        |«      |j                  |d   |j                  f«      S )zdCreate a new DRAFT version from a parent (default: the active one). Never changes the active config.TrV   rR   rw   z Parent belongs to another centerÚconfigrf   ÚgradientTablerx   ÚshowDangerSuggestionÚ
bandLabelszRSELECT coalesce(max(version), 0) + 1 AS v FROM center_configs WHERE center_id = %srt   z³INSERT INTO center_configs (center_id, version, status, config, rationale, parent_id, created_by)
           VALUES (%s, %s, 'draft', %s, %s, %s, %s) RETURNING id, version, statusrE   )r   r:   r   r   r   Údictr;   r<   r=   r>   r7   r…   r   r   r@   rE   )rR   rK   rF   rG   Úparentr€   Úversions          r1   Úcreate_draftrŽ   �   sP  € ô �4˜¨$Õ/Ø37·>²>Œ\˜$ §¡Ô/Ä}ÐUYÐ[dÓGe€FØ‡~‚~˜& Ñ-°Ò:Ü˜CÐ!CÓDÐDÜ
ˆv�hÑÓ
 €CØ×ÑÐ$Ø!×.Ñ.ˆˆMÑØ×ÑÐ&Ø#×2Ñ2ˆˆOÑØ×ÑÐ)Ø"&×"8Ñ"8ˆÐÑØ×"Ñ"Ð.Ø&*×&AÑ&AˆÐ"Ñ#Ø×ÑÐ#Ø ×,Ñ,ˆˆLÑÜ�SÔÜ�tÐqÐt}Ðsó  Að  BEñ  F€GÜØð	Uà	�GœU 3›Z¨¯©¸À¹ÀtÇwÁwÐOó	ð r0   z/configs/{config_id}/proposec                ó’   — t        || «      }t        ||d   d¬«       |d   dk7  rt        dd«      ‚|j                  d| f«       d	diS )
NrR   TrV   ÚstatusÚdrafté™  zOnly drafts can be proposedzPUPDATE center_configs SET status = 'proposed', proposed_at = now() WHERE id = %sÚok)r   r   r   Úexecuterb   s       r1   Úproposer•   ©   sV   € ä
�t˜YÓ
'€CÜ�4˜˜[Ñ)°Õ6Ø
ˆ8�}˜ÒÜ˜CÐ!>Ó?Ð?Ø‡L�LÐcÐfoÐeqÔrØ�$ˆ<Ðr0   z/configs/{config_id}/approvec                ó`  — t        || «      }t        ||d   d¬«       |d   dk7  rt        dd«      ‚|d   |j                  k(  r|j                  st        d	d
«      ‚|j                  d|d   f«       |j                  d|j                  |j                  rd|j                  › �nd| f«       ddiS )z^Activate a proposed version. Needs a DIFFERENT center admin (or a site admin) than its author.rR   TrV   r�   Úproposedr’   z&Only proposed versions can be approvedÚ
created_byi“  z2A different center admin must approve this versionzWUPDATE center_configs SET status = 'retired' WHERE center_id = %s AND status = 'active'z¨UPDATE center_configs SET status = 'active', approved_by = %s, approved_at = now(),
               rationale = coalesce(rationale, '') || coalesce(%s, '') WHERE id = %sz

Approval note: Nr“   )r   r   r   rE   rD   r”   r@   )rc   rK   rF   rG   rL   s        r1   Úapprover™   ³   s¿   € ô �t˜YÓ
'€CÜ�4˜˜[Ñ)°Õ6Ø
ˆ8�}˜
Ò"Ü˜CÐ!IÓJÐJØ
ˆ<Ñ˜DŸG™GÒ#¨D¯MªMÜ˜CÐ!UÓVÐVØ‡L�LÐjÐmpÐq|Ñm}Ðlô  AØ‡L�Lð	Xà	�‰¸D¿NºNÐ'¨¯©Ð'7Ñ8ÐPTÐV_Ð`ôð
 �$ˆ<Ðr0   z/configs/{config_id}/replayNc                ó†   — t        || «      }t        ||d   «       t        ||d   «      }t        ||d   |d   |d   |«      S )zjBacktest a config against imported forecasts: agreement vs the active config and which suggestions change.rR   r‡   )r   r   r   r   )rc   Úholdout_seasonrF   rG   rL   Úactives         r1   Úreplayr�   Å   sR   € ô �t˜YÓ
'€CÜ�4˜˜[Ñ)Ô*Ü˜4  [Ñ!1Ó2€FÜ˜˜s ;Ñ/°°X±ÀÀxÑ@PÐR`ÓaÐar0   )rF   r   rG   r   )rK   r   rF   r   rG   r   )rR   rq   rF   r   rG   r   )rR   rq   rK   r3   rF   r   rG   r   )rc   rq   rF   r   rG   r   )r€   r‹   ÚreturnÚNone)rR   rq   rK   r9   rF   r   rG   r   )rc   rq   rK   rB   rF   r   rG   r   )rc   rq   r›   r#   rF   r   rG   r   )0Ú__doc__Ú
__future__r   Úfastapir   r   r   Úpsycopgr   Úpydanticr   r	   Úauthr   r   r   r   Úcalibrationr   Úconfigsr   r   r   Údbr   r   r   r   Úrouterr   r3   r9   rB   r{   rH   ÚpostrM   rT   rX   r\   r^   r`   rd   r…   rŽ   r•   r™   r�   r/   r0   r1   ú<module>r«      s°  ðÙ ;å "ç 5Ñ 5Ý ß %ç DÓ DÝ 'ß ?Ñ ?ß 4Ó 4á	˜˜Ô	$€ôDˆyô Dô.ˆYô .ô*�Iô *ô!�	ô !ð ‡�ˆJÓÙ$+¨HÓ$5ÁGÈLÓDYó ó ðð ‡�ˆZÓÙ5<¸XÓ5FÑU\Ð]iÓUjó 	ó ð	ð ‡�Ð"Ó#Ù29¸(Ó2CÑRYÐZfÓRgó Gó $ðGð ‡�Ð)Ó*ÙAHÈÓARÑahÐiuÓavó ó +ðð ‡�Ð*Ó+Ù4;¸HÓ4EÑT[Ð\hÓTió ó ,ðð ‡�Ð)Ó*Ù9@ÀÓ9JÑY`ÐamÓYnó *ó +ð*ð
 ‡�Ð*Ó+Ù4;¸HÓ4EÑT[Ð\hÓTió 
ó ,ð
ð ‡�Ð"Ó#Ù29¸(Ó2CÑRYÐZfÓRgó ó $ðó	_ð ‡�Ð+Ó,ÙIPÐQYÓIZÑipÐq}Ói~ó ó -ðð6 ‡�Ð+Ó,Ù/6°xÓ/@ÉwÐWcÓOdó ó -ðð ‡�Ð+Ó,Ù@GÈÓ@QÑ`gÐhtÓ`uó ó -ðð" ‡�Ð)Ó*Ø8<ÑQXÐYaÓQbÑqxð  zFó  rGó bó +ñbr0   