Ë
    çÎj‰&  ã                   óÊ  — U d Z ddlZddlZddlZddlZddlZddlmZ ddlm	Z	 ddl
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 dd	lmZ dd
lmZ ddlmZmZmZmZmZm Z  ddl!m"Z"  ejF                  d«      Z$ e«       Z% ejL                  d«      Z'dg dœZ(e)e*d<   dZ+de,e)   fd„Z-e%j]                  d«      d„ «       Z/ G d„ de«      Z0e%jc                  d«      de0defd„«       Z2y)uI  
Meeconic Portal Backend â€” gÃ©nÃ©ration IA Â« photo magique Â» via storyface.ai.

Routes:
  GET  /api/portal/themes   â†’ thÃ¨mes IA disponibles pour la clÃ© API (cache 10 min)
  POST /api/portal/magic    â†’ gÃ©nÃ¨re la photo magique et l'enregistre dans la galerie famille

Garde-fous (chaque gÃ©nÃ©ration coÃ»te du crÃ©dit storyface) :
  - la famille doit exister, avoir donnÃ© son consentement RGPD et appartenir Ã 
    une Ã©cole active qui a activÃ© l'IA (rÃ©glage Â« ai_enabled Â») ;
  - quota par famille (Â« ai_max_per_family Â» de l'Ã©cole, dÃ©faut 5) ;
  - limite par IP, et au plus 3 gÃ©nÃ©rations simultanÃ©es sur le serveur ;
  - le thÃ¨me doit faire partie de ceux assignÃ©s Ã  la clÃ© API.
La photo gÃ©nÃ©rÃ©e n'est livrÃ©e en HD qu'aprÃ¨s achat d'un format Â« magique Â»
(le portail n'en montre qu'un aperÃ§u filigranÃ©).
é    N)ÚPath)ÚOptional)Ú	APIRouterÚHTTPExceptionÚRequest)Ú	BaseModel)Úconfig)Úget_db)Úenforce)Ú	client_ip)Ú	ai_healthÚalert_serviceÚaudit_serviceÚmedia_serviceÚphoto_serviceÚschool_service)Úwith_retrieszmeeconic.iaé   g        )ÚatÚthemesÚ_themes_cacheiX  Úreturnc               ƒ   óª  K  — t         j                  sg S t        j                  «       } t        d   r| t        d   z
  t
        k  r	t        d   S 	 t        j                  d¬«      4 ƒd {  –—† }|j                  t         j                  › d�dt         j                  iddd	œ¬
«      ƒ d {  –—† }|j                  «        |j                  «       j                  dg «      }|t        d<   | t        d<   |cd d d «      ƒd {  –—†  S 7 Œ™7 Œ[7 Œ	# 1 ƒd {  –—†7  sw Y   y xY w# t        $ r)}t        j                  d|«       t        d   cY d }~S d }~ww xY w­w)Nr   r   g      .@©Útimeoutz/api/v2/themesú	X-API-KeyÚtrueÚ1)ÚextendedÚapp_ids)ÚheadersÚparamsz#Error fetching storyface themes: %s)r	   ÚSTORYFACE_API_KEYÚtimeÚ	monotonicr   Ú_THEMES_TTLÚhttpxÚAsyncClientÚgetÚAUTH_SERVER_URLÚraise_for_statusÚjsonÚ	ExceptionÚlogÚerror)ÚnowÚclientÚrespr   Úes        ú/var/www/photos/backend/ia.pyÚfetch_themesr5   0   s3  è ø€ Ü×#Ò#Øˆ	Ü
�.‰.Ó
€CÜ�XÒ 3¬°tÑ)<Ñ#<¼{Ò#JÜ˜XÑ&Ð&ð'Ü×$Ñ$¨TÔ2÷ 
	ð 
	°fØŸ™Ü×)Ñ)Ð*¨.Ð9Ø$¤f×&>Ñ&>Ð?Ø$*°sÑ;ð $ó ÷ ˆDð
 ×!Ñ!Ô#Ø—Y‘Y“[—_‘_ X¨rÓ2ˆFØ&,ŒM˜(Ñ#Ø"%ŒM˜$ÑØ÷
	÷ 
	ò 
	øðøð
	ø÷ 
	÷ 
	ñ 
	ûô ò 'Ü�	‰	Ð7¸Ô;Ü˜XÑ&Õ&ûð'üs�   ‚AEÁD Á)DÁ*D Á-<D	Â)DÂ*AD	Ã1D Ã=DÃ>D ÄEÄD ÄD	ÄD Ä	DÄDÄDÄD ÄEÄD Ä	EÄ'EÅEÅEÅEÅEz/api/portal/themesc               ƒ   óž  K  — t        «       ƒ d {  –—† } t        t        j                  «      | D �cg c]‹  }|j	                  d«      |j	                  d«      |j	                  d«      xs |j	                  d«      |j	                  d«      xs |j	                  d«      |j	                  d«      |j	                  d«      dœ‘Œ� c}dœS 7 Œµc c}w ­w)	NÚidÚnameÚname_frÚname_esÚdescriptionÚ	thumbnail)r7   r8   r9   r:   r;   r<   )Úenabledr   )r5   Úboolr	   r#   r)   )r   Úts     r4   Úportal_themesr@   G   s®   è ø€ ä“>×!€Fäœ×0Ñ0Ó1ð ö

ð ð  !Ÿu™u T›{Ø Ÿu™u V›}Ø Ÿu™u YÓ/Ò@°1·5±5¸³=Ø Ÿu™u YÓ/Ò@°1·5±5¸³=Ø Ÿu™u ]Ó3Ø Ÿu™u [Ó1óò

ñð ð "úò

ùs!   ‚C�C‘ C±BCÃCÃCc                   ó@   — e Zd ZU eed<   eed<   eed<   dZee   ed<   y)ÚMagicRequestÚfamily_codeÚphoto_filenameÚtheme_idú3:2Úaspect_ratioN)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__rG   r   © ó    r4   rB   rB   \   s"   … ØÓØÓØÓØ$)€L�H˜S‘MÔ)rN   rB   z/api/portal/magicÚbodyÚrequestc           
   ƒ   ó¶
  ‡ ‡‡‡K  — t         j                  st        dd¬«      ‚‰ j                  j	                  «       j                  «       }t        |«      }t        d|› �dd¬«       t        j                  |«      st        dd	¬«      ‚t        «       5 }|j                  d
|t        j                  |«      |f«      j                  «       }d d d «       r
|d   r|d   st        dd	¬«      ‚|d   st        dd¬«      ‚|d   |d   }}t        j                   |«      }t#        |d   «      }	|d   dk7  s|	dk  rt        dd¬«      ‚t%        d„ t        j&                  ||«      D «       «      }
|
|	k\  rt        dd¬«      ‚t        j(                  ||‰ j*                  «      Š‰�‰j,                  j/                  d«      rt        dd¬«      ‚t1        «       ƒ d {  –—† }t3        ˆ fd„|D «       d «      }|€t        dd¬«      ‚t5        j6                  ‰j8                  «      ƒ d {  –—† Š‰j:                  j=                  «       dv rdndŠd t>        fˆ ˆˆˆfd!„}t@        4 ƒd {  –—†  	 tC        |d"|› d#|› �¬$«      ƒ d {  –—† }tG        jP                  |«       d d d «      ƒd {  –—†  d(tR        d tR        fd)„}	 t5        j6                  |«      ƒ d {  –—† }d*}‰jT                  }d‰ jV                  › d,t#        tY        jX                  «       «      › d-|› �}||z  }t5        j6                  |jZ                  |«      ƒ d {  –—†  t        j\                  |«       |j_                  d.«      xs |j_                  d/«      › }	 |d0z  }|ja                  «       r%tc        jd                  |jg                  d1¬2«      «      ni }||ji                  d3i «      |<   |jk                  tc        jl                  |d4d5¬6«      d1¬2«       tq        jr                  d8|d9||t?        ‰ jV                  «      |d:œ¬;«       tL        ju                  d<||‰ jV                  «       d=|jv                  |t        jx                  ||d>«      t        jx                  ||d?«      |d@dAœiS # 1 sw Y   �Œ¼xY w7 �Œ½7 �Œu7 �Œ:7 �Œ!# tD        $ rO}tG        jH                  |tF        jJ                  |«       tL        jO                  d%|||«       t        d&d'¬«      ‚d }~ww xY w7 �ŒZ# 1 ƒd {  –—†7  sw Y   �ŒkxY w7 �ŒD# tD        $ r }d+}Y �ŒPw xY w7 �Œð# tD        $ r!}tL        jo                  d7|«       Y d }~�Œ^d }~ww xY w­w)BNi÷  u    GÃ©nÃ©ration IA non configurÃ©e.©Údetailz	magic-ip:é   i  )Úmax_attemptsÚwindow_secondsi”  zCode introuvable.z¶SELECT f.code, f.school_id, f.consent_at, s.active FROM families f LEFT JOIN schools s ON s.id=f.school_id WHERE f.code=? OR REPLACE(f.code,'-','')=? ORDER BY (f.code=?) DESC LIMIT 1Ú	school_idÚactiveÚ
consent_até“  z Le consentement RGPD est requis.ÚcodeÚai_max_per_familyÚ
ai_enabledr   r   u8   La photo magique n'est pas disponible pour cette Ã©cole.c              3   óX   K  — | ]"  }|j                   j                  d «      sŒd–— Œ$ y­w)Úia_é   N)r8   Ú
startswith)Ú.0Úfs     r4   ú	<genexpr>z!generate_magic.<locals>.<genexpr>~   s"   è ø€ Òg�QÈaÏfÉf×N_ÑN_Ð`eÕNfŒqÑgùs   ‚ *£*z4Quota de photos magiques atteint pour cette famille.r_   z'Photo introuvable pour ce code famille.c              3   ó„   •K  — | ]7  }t        |j                  d «      «      t        ‰j                  «      k(  sŒ4|–— Œ9 y­w)r7   N)rK   r)   rE   )rb   r?   rO   s     €r4   rd   z!generate_magic.<locals>.<genexpr>‡   s.   øè ø€ ÒN˜¤s¨1¯5©5°«;Ó'7¼3¸t¿}¹}Ó;MÓ'M”!ÑNùs
   ƒ5A ¹A i�  u   ThÃ¨me IA inconnu.)z.jpgz.jpegz
image/jpegz	image/pngr   c            
   “   óT  •K  — t        j                  d¬«      4 ƒd {  –—† } | j                  t        j                  › d�d‰j
                  ‰‰fi‰j                  d‰j                  xs dt        j                  dœdt        j                  i¬	«      ƒ d {  –—† }d d d «      ƒd {  –—†  j                  sšt        j                  d
|j                  |j                  d d «       |j                  dv r4t        j                   ddd|j                  › d|j                  d d › �«       t#        dd|j                  › d|j                  d d › �¬«      ‚|j%                  «       j'                  d«      }|st#        dd¬«      ‚t        j                  d¬«      4 ƒd {  –—† } | j'                  |«      ƒ d {  –—† }|j)                  «        d d d «      ƒd {  –—†  |j*                  S 7 �ŒÅ7 �ŒP7 �ŒC# 1 ƒd {  –—†7  sw Y   �ŒTxY w7 Œh7 ŒQ7 Œ3# 1 ƒd {  –—†7  sw Y   j*                  S xY w­w)Ng     €f@r   z/api/v1/picasso-fast-seedÚimager   rF   )rE   Úapp_idrG   Ú
short_coder   )ÚfilesÚdatar!   zStoryface IA error %s: %siô  )i‘  i’  rZ   zstoryface-authu1   Storyface : clÃ© API refusÃ©e ou crÃ©dit Ã©puisÃ©zHTTP z: i,  éö  zStoryface IA failed (HTTP z): éÈ   rR   Ú
result_urlu'   RÃ©ponse IA invalide (sans result_url).g      N@)r'   r(   Úpostr	   r*   r8   rE   rG   ÚSTORYFACE_SHORT_CODEr#   Ú
is_successr.   r/   Ústatus_codeÚtextr   Únotifyr   r,   r)   r+   Úcontent)r1   r2   ÚurlÚrrO   Úimage_bytesÚmimeÚ
photo_paths       €€€€r4   Ú	_generatez!generate_magic.<locals>._generateŽ   s  øè ø€ Ü×$Ñ$¨UÔ3÷ 	ð 	°vØŸ™Ü×)Ñ)Ð*Ð*CÐDØ §¡°+¸tÐ DÐEà$(§M¡MØ$'Ø$(×$5Ñ$5Ò$>¸Ü$*×$?Ñ$?ñ	ð %¤f×&>Ñ&>Ð?ð %ó 
÷ 
ˆD÷	÷ 	ð �ŠÜ�I‰IÐ1°4×3CÑ3CÀTÇYÁYÈtÐPSÀ_ÔUØ×Ñ ?Ñ2Ü×$Ñ$Ð%5Ð7jØ',¨T×-=Ñ-=Ð,>¸bÀÇÁÈ4ÈCÀÐ@QÐ%RôTô   Ð.HÈ×IYÑIYÐHZÐZ]Ð^b×^gÑ^gÐhlÐilÐ^mÐ]nÐ,oÔpÐpØ�i‰i‹k�o‰o˜lÓ+ˆÙÜ Ð,UÔVÐVÜ×$Ñ$¨TÔ2÷ 	!ð 	!°fØ—j‘j “o×%ˆAØ×ÑÔ ÷	!÷ 	!ð �y‰yÐð3	ùð
ùð	ù÷ 	÷ 	ò 	úð,	!øØ%øð	!ø÷ 	!÷ 	!ð 	!ð �y‰yÐüs¬   ƒH(žG#ŸH(¢A4G,ÂG&ÂG,ÂH(Â&G)Â'C2H(ÆHÆH(ÆHÆ2HÆ3HÇH(ÇHÇH(Ç&G,Ç)H(Ç,G?Ç2G5Ç3G?Ç:	H(ÈHÈH(ÈH%ÈHÈH%ÈH(zia ú/)Úlabelz AI generation failed (%s/%s): %srl   u-   La gÃ©nÃ©ration IA a Ã©chouÃ© â€” rÃ©essayez.Úrawc                 óÈ   — ddl m} |j                  t        j                  | «      «      }t        j                  «       }|j                  |ddd¬«       |j                  «       S )Nr   )ÚImageÚWEBPéX   é   )ÚqualityÚmethod)ÚPILr€   ÚopenÚioÚBytesIOÚsaveÚgetvalue)r~   r€   ÚimgÚbufs       r4   Ú_to_webpz generate_magic.<locals>._to_webp³   sF   € ÝØ�j‰jœŸ™ C›Ó)ˆÜ�j‰j‹lˆØ�‰��f b°ˆÔ3Ø�|‰|‹~ÐrN   ÚwebpÚjpgÚ_ú.r9   r8   z	meta.jsonzutf-8)ÚencodingÚphoto_namesFé   )Úensure_asciiÚindentzmeta.json update failed: %szia.generateÚfamily)ÚthemeÚfile)Ú
ip_addressÚresource_typeÚresource_idrW   Údetailsz'Magic photo generated: %s/%s (theme %s)ÚphotoÚfullÚthumbÚai)r7   Úfilenamerv   Ú	thumb_urlÚ
child_nameÚkind)=r	   r#   r   rC   ÚstripÚupperr   Úrate_limit_enforcer   Ú
valid_coder
   Úexecuter   Ú	norm_codeÚfetchoner   Úget_settingsÚintÚsumÚlist_family_filesÚsafe_photo_pathrD   r8   ra   r5   ÚnextÚasyncioÚ	to_threadÚ
read_bytesÚsuffixÚlowerrK   Ú
_gen_slotsr   r-   r   Úrecord_failureÚTASK_IAr.   r/   Úrecord_successÚbytesÚparentrE   r$   Úwrite_bytesÚinvalidate_families_cacher)   Úexistsr,   ÚloadsÚ	read_textÚ
setdefaultÚ
write_textÚdumpsÚwarningr   Ú	log_eventÚinfoÚstemÚpreview_url)rO   rP   Úcode_inÚipÚconnÚfamr[   rW   ÚsettingsÚquotaÚusedr   r™   r{   ru   ÚexcrŽ   Ú	out_bytesÚextÚcode_dirr£   Úout_pathr}   Ú	meta_fileÚmetarx   ry   rz   s   `                        @@@r4   Úgenerate_magicrÚ   c   sþ  ûè ø€ ä×#Ò#Ü˜CÐ(JÔKÐKà×Ñ×$Ñ$Ó&×,Ñ,Ó.€GÜ	�7Ó	€BÜ˜ 2 $Ð'°bÈÕNä×#Ñ# GÔ,Ü˜CÐ(;Ô<Ð<Ü	‹ð q�TØ�l‰lð/à18¼-×:QÑ:QÐRYÓ:ZÐ\cÐ0dóf÷ goÑfnÓfpð 	÷qñ
 �c˜+Ò&¨c°(ªmÜ˜CÐ(;Ô<Ð<Øˆ|ÒÜ˜CÐ(JÔKÐKØ˜&‘k 3 {Ñ#3ˆ)€Dä×*Ñ*¨9Ó5€HÜ�Ð,Ñ-Ó.€EØ�Ñ Ò'¨5°Aª:Ü˜CÐ(bÔcÐcäÑgœ-×9Ñ9¸)ÀTÓJÔgÓg€DØˆu‚}Ü˜CÐ(^Ô_Ð_ä×.Ñ.¨y¸$À×@SÑ@SÓT€JØÐ˜ZŸ_™_×7Ñ7¸Ô>Ü˜CÐ(QÔRÐRä“>×!€FÜÓN˜VÔNÐPTÓU€EØ€}Ü˜CÐ(<Ô=Ð=ä×)Ñ)¨*×*?Ñ*?Ó@×@€KØ%×,Ñ,×2Ñ2Ó4Ð8IÑI‰<È{€DðœS÷ ð ô8 ÷ ,ñ ,ð	]Ü(¨¸CÀ	¸{È!ÈDÈ6Ð:RÔS×SˆGô
 	× Ñ  Ô+÷,÷ ,ð”eð ¤ó ðÜ!×+Ñ+¨H°gÓ>×>ˆ	Øˆð
 × Ñ €HØ�T—]‘]�O 1¤S¬¯©«Ó%5Ð$6°a¸°uÐ=€HØ˜(Ñ"€HÜ
×
Ñ
˜H×0Ñ0°)Ó
<×<Ð<Ü×+Ñ+¨IÔ6à�y‰y˜Ó#Ò8 u§y¡y°Ó'8Ð9€Eð8Ø˜{Ñ*ˆ	ØDM×DTÑDTÔDVŒt�z‰z˜)×-Ñ-°wÐ-Ó?Ô@Ð\^ˆØ7<ˆ�‰˜ rÓ*¨8Ñ4Ø×ÑœTŸZ™Z¨¸5ÈÔKÐV]ÐÔ^ô ×Ñ˜M°bÈÐ^bØ&/Ä3ÀtÇ}Á}ÓCUÐ_gÑ9hõjä‡H�HÐ6¸	À8ÈTÏ]É]Ô[àØ"Ÿ-™-Ø"Ü'×3Ñ3°D¸(ÀFÓKÜ'×3Ñ3°D¸(ÀGÓLØØñ
ð	ð 	÷Kqñ qúð0 "ùð
 Aùð>,ùàSúÜò 	]Ü×$Ñ$ Y´	×0AÑ0AÀ3ÔGÜ�I‰IÐ8¸)ÀTÈ3ÔOÜ Ð,[Ô\Ð\ûð	]úð,ù÷ ,÷ ,ò ,úð" ?úäò Øˆ	Ø‹ðúð =úô ò 8Ü�‰Ð1°3×7Ò7ûð8üs,  †BUÂ7RÃD
UÇRÇA
UÈ RÈ!=UÉRÉUÉ"S<É$RÉ:RÉ;RÉ?S<ÊUÊS9Ê UÊ5T ËTËT ËA$UÌ9T)Ì:>UÍ9A:T, Ï3BUÒRÒ
	UÒUÒUÒRÒ	S6Ò'A
S1Ó1S6Ó6S<Ó9UÓ<TÔTÔTÔ
UÔT ÔT&Ô"UÔ%T&Ô&UÔ,	UÔ5UÕUÕUÕU)3Ú__doc__r´   rˆ   r,   Úloggingr$   Úpathlibr   Útypingr   r'   Úfastapir   r   r   Úpydanticr   Úcorer	   Úcore.dbr
   Úcore.rate_limitr   r©   Úcore.securityr   Úservicesr   r   r   r   r   r   Úutils.retryr   Ú	getLoggerr.   ÚrouterÚ	Semaphorer¹   r   ÚdictrL   r&   Úlistr5   r)   r@   rB   ro   rÚ   rM   rN   r4   ú<module>rì      sí   ðòó" Û 	Û Û Û Ý Ý ã ß 5Ñ 5Ý å Ý Ý 9Ý #ß j× jÝ $à€g×Ñ˜Ó&€á	‹€ØˆW×Ñ˜qÓ!€
ð !¨BÑ/€ˆtÓ /Ø€ð'˜D ™Jó 'ð. ‡�Ð Ó!ñó "ðô(*�9ô *ð ‡�Ð Ó!ðx˜|ð x°gò xó "ñxrN   