
    ӭj*                    Z   S r SSKJrJr  SSKJr  SSKrSSKrSSKr	SSK
r
SSKrSSKrSSKrSSKrSSKrSSKrSSKrSSKrSSKrSSKrSSKJr  SSKrSSKrSrSrSrS	rSr\" \R@                  " S
5      =(       d    S5      r!Sr"Sr#Sr$\RJ                  " S5      RM                  5       r'0 r(Sr)\RT                  " 5       r+\RJ                  " S5      RM                  5       r,0 r-Sr.Sr/\RT                  " 5       r0\R                  " 5       r1SSK2r3S Hm  r4\4\3Rj                  ;  d  M  \Rj                  Rm                  \Rj                  Ro                  \4S5      5      (       d  MQ  \3Rj                  Rq                  S\45        Mo      SSK9J:r;  Sr<S r>S r?S r@S rAS rBS rC " S S\5      rD\ES :X  av  \R                  " \5         " S! S"\\5      rG\G" S#\4\D5      rH\HR                  R                  \R                  \R                  S$5        \L" S%\ 35        \HR                  5         gg! \= a    Sr< Nf = f)&z
Hermes Diary Web Server with Image Gen Proxy
Serves /opt/data/diary on port 8789
Proxies /api/* and /image_gen/* to image_gen Flask app on port 5011
    )
HTTPServerSimpleHTTPRequestHandler)ThreadingMixInN)BytesParseriU"  z/opt/data/diaryz	127.0.0.1i  LEDGER_PORT18790/ledgerz/opt/data/diary/gba_savesmings   kewi1127ztomica_cars.jsonz /opt/data/diary/garage_cars.jsonz/opt/data/diary/garage_images)z/opt/data/.localz./opt/hermes/.venv/lib/python3.13/site-packagesPIL)ImageTFc                  :    0 S[         R                  " 5       S-  S.$ Nr   @B cars
view_count
updated_attimetime_ns     ./opt/data/diary/ledger/diary_server_patched.py_garage_default_datar   ;       at||~7RSSr   c                    [         R                  R                  U 5      =(       d    Sn[        R                  " USSS9u  p4 [         R
                  " USSS9 n[        R                  " XSS	S
9  S S S 5        [         R                  " X@5        g ! , (       d  f       N%= f! [         aM    [         R                  R                  U5      (       a'  [         R                  " U5        e ! [         a     e f = fe f = f)N.tmp_garage_.jsondirprefixsuffixwutf-8encodingF   ensure_asciiindentospathdirnametempfilemkstempfdopenjsondumpreplace	Exceptionexistsremovefilepathdatadir_namefdtmp_pathfs         r   _garage_atomic_writer@   >       wwx(/CH##wWLBYYr31QIIdE!< 2


8& 21  77>>(##8$ "d #	H   B B/B 
BB .C6C$#C6$
C1.C60C11C6c                      [            [        R                  R                  [        5      (       d&  [        5       n  [        [        U 5        U sS S S 5        $  [        [        SSS9 n[        R                  " U5      sS S S 5        sS S S 5        $ ! [         a     NPf = f! , (       d  f       O= f! [         a    [        R                  SS5      n[        R                  R                  U5      (       aX   [        USSS9 n[        R                  " U5      sS S S 5        s sS S S 5        $ ! , (       d  f       O= f! [         a     Of = f[        5       s sS S S 5        $ f = f S S S 5        g ! , (       d  f       g = fNrr%   r&   r   	.bak.json)garage_db_lockr-   r.   r7   GARAGE_DB_PATHr   r@   r6   openr3   loadr5   )r;   r?   bak_paths      r   _garage_loadrL   K   s   	ww~~n--')D$^T:  
	*ncG<yy| =< 

   =< 	*%--g{CHww~~h''hg>!#yy| ?> 
 ?>  '))' 
	* = 
s   3E/BE/B0&B<	B0
BE/BE/
B-	)B0,E/-B00AE"5D< D+	D<E" E/+
D95D<8E"9D<<
E	E"E		E"E/!E""E//
E=c                  :    0 S[         R                  " 5       S-  S.$ r   r   r   r   r   _tomica_default_datarN   a   r   r   c                    [         R                  R                  U 5      =(       d    Sn[        R                  " USSS9u  p4 [         R
                  " USSS9 n[        R                  " XSS	S
9  S S S 5        [         R                  " X@5        g ! , (       d  f       N%= f! [         aM    [         R                  R                  U5      (       a'  [         R                  " U5        e ! [         a     e f = fe f = f)Nr   tmp_tomica_r   r    r$   r%   r&   Fr(   r)   r,   r9   s         r   _tomica_atomic_writerQ   d   rA   rB   c                  N   [            [        R                  R                  [        5      (       d%  [        5       n [        [        U 5        U sS S S 5        $  [        [        SSS9 n[        R                  " U5      sS S S 5        sS S S 5        $ ! , (       d  f       O= f! [        R                   a    [        R                  SS5      n[        R                  R                  U5      (       at   [        USSS9 n[        R                  " U5      nS S S 5        O! , (       d  f       O= f[        [        W5        Us sS S S 5        $ ! [        R                   a     Of = f[        5       n[        [        U5        Us sS S S 5        $ f = f S S S 5        g ! , (       d  f       g = frD   )tomica_db_lockr-   r.   r7   TOMICA_DB_PATHrN   rQ   rI   r3   rJ   JSONDecodeErrorr5   )initr?   rK   	recoveredfallbacks        r   _tomica_loadrY   q   s-   	ww~~n--')D 6	 

	ncG<yy| =< 
 =<## 	%--g{CHww~~h''hg>!$(IIaL	 ?>>(C$$ 
  ++ +-H :O) 
	 = 
s   AFB%B;	B
B	BFBAF	.E9D	E
D'#E;F	<FEF	EF	=FF		F
F$c                   `  ^  \ rS rSrU 4S jr\R                  " S5      rS rS2S jr	S r
S rS rS	 rS
 rS rS rS rS rS rS rS rS rS rS rS rS rS rS rS rS rS rS r S r!S r"S r#S  r$S! r%S" r&S# r'S$r(S%r)S&r*S' r+S(r,S) r-U 4S* jr.S+ r/S, r0S3S- jr1S. r2S/ r3S0 r4S1r5U =r6$ )4DiaryHandler   c                 2   > [         TU ]  " US[        0UD6  g )N	directory)super__init__	SERVE_DIR)selfargskwargs	__class__s      r   r`   DiaryHandler.__init__   s    $>)>v>r   z^\d+$c                     [         R                  " SU=(       d    S5      (       d  g [        R                  R	                  [
        U5      $ )N^[A-Za-z0-9_-]{1,64}$ )rematchr-   r.   joinGBA_SAVES_DIR)rb   game_ids     r   _gba_game_dirDiaryHandler._gba_game_dir   s0    xx0'-R@@ww||M733r   c                 V   [         R                  " U5      R                  S5      nU R                  U5        U R	                  SS5        U R	                  S[        [        U5      5      5        U R	                  SS5        U R                  5         U R                  R                  U5        g)Nr%   Content-Typeapplication/jsonContent-LengthzAccess-Control-Allow-Origin*T)
r3   dumpsencodesend_responsesend_headerstrlenend_headerswfilewrite)rb   objstatusbodys       r   
_send_jsonDiaryHandler._send_json   s    zz#%%g.6");<)3s4y>:6<

r   c                 ~   U R                   n[        R                  " SU5      nU(       a   U R                  S:X  a  U R	                  5       $ [        R                  " SU5      nU(       a0  U R                  S:X  a   U R                  UR                  S5      5      $ [        R                  " SU5      nU(       a@  U R                  S:X  a0  U R                  UR                  S5      UR                  S5      5      $ U(       a@  U R                  S:X  a0  U R                  UR                  S5      UR                  S5      5      $ g	)
z?Route /api/gba/* to local file system. Returns True if handled.z^/api/gba/save/?$POSTz(^/api/gba/saves/([A-Za-z0-9_-]{1,64})/?$GET   z.^/api/gba/saves/([A-Za-z0-9_-]{1,64})/(\d+)/?$r(   DELETEF)	r.   rj   rk   command	_gba_save	_gba_listgroup	_gba_load_gba_delete)rb   r.   ms      r   _handle_gbaDiaryHandler._handle_gba   s    yyHH)40'>>##HH@$G&>>!''!*--HHFM&>>!''!*aggaj99)##AGGAJ
;;r   c           	          [        U R                  R                  S5      =(       d    S5      nU R                  R	                  U5      n[
        R                  " UR                  S5      5      nUR                  SS5      R                  5       nUR                  SS5      R                  5       S S nUR                  SS5      R                  5       nU(       a  U(       d  U R                  S	S
0S5      $  [        R                  " U5      nU R                  U5      n	U	(       d  U R                  S	S0S5      $ [        R                  " U	SS9  [!        [        ["        R"                  " 5       S-  5      5      n
[%        [        R&                  R)                  XS-   5      S5       nUR+                  U5        S S S 5        [%        [        R&                  R)                  XS-   5      S5       n[
        R,                  " U
U=(       d    S[        ["        R"                  " 5       S-  5      [/        U5      S.U5        S S S 5        U R                  SU
[/        U5      S.5      $ ! [         a!  nU R                  S	SU 30S5      s S nA$ S nAff = f! , (       d  f       N= f! , (       d  f       Nk= f! [         a!  nU R                  S	SU 30S5      s S nA$ S nAff = f)Nrt   r   r%   rn   ri   name@   r;   errorzmissing game_id or data  zinvalid base64: zinvalid game_idTexist_ok  .datwbr   r$   Unnamedidr   mtimesize)okr   r   zsave failed:   )intheadersgetrfilereadr3   loadsdecodestripr   base64	b64decoder6   ro   r-   makedirsrz   r   rI   r.   rl   r~   r4   r{   )rb   clrawpayloadrn   r   data_b64
save_bytesegdirsave_idr?   s               r   r   DiaryHandler._gba_save   sF   	HT\\%%&67<1=B**//"%CjjG!45Gkk)R0668G;;vr*0023B7D{{62.446H(1J'KSQQO#--h7
 %%g.D1B'CSIIKKt,#diikD012Gbggll46)9:DAQ
# Bbggll47):;SAQ		! -I t!34
O	
  B ??$gs:#WXX!  O3CA31G'H#NNO BAAA  	H??G}QC-@#A3GG	Hs   C1J, 5I +J, 7A+J, "J
44J, (AJ6%J, 
J&J<J=J, JJ, 

JJ, 
J)%J, ,
K6KKKc           
      b   U R                  U5      nU(       a$  [        R                  R                  U5      (       d  U R	                  / 5      $ / n[        R
                  " U5       GH  nUR                  S5      (       d  M  US S nU R                  R                  U5      (       d  MC   [        [        R                  R                  X$5      5       n[        R                  " U5      nS S S 5        WR                  SS5      nSUs=:  a  S:  a  O  O[        US-  5      nUR                  UR                  SU5      UR                  SS	5      UUR                  S
S5      S.5        GM     UR#                  S SS9  U R	                  U5      $ ! , (       d  f       N= f! [          a     GMG  f = f)Nr   r   r   l    4Nr   r   r   ri   r   r   c                     U S   $ )Nr   r   )ss    r   <lambda>(DiaryHandler._gba_list.<locals>.<lambda>   s    7r   T)keyreverse)ro   r-   r.   isdirr   listdirendswithGBA_SAVE_ID_RErk   rI   rl   r3   rJ   r   r   appendr6   sort)	rb   rn   r   savesfnr   r?   metar   s	            r   r   DiaryHandler._gba_list   s\   !!'*277==..??2&&**T"B;;w''"gG&&,,W55"'',,t01Q99Q<D 2!, u+|+-E((41 HHVR0" HHVQ/	  #. 	

+T
:u%%# 21  s+   *(FF)A>F
F	F
F.-F.c                    U R                  U5      nU(       a   U R                  R                  U5      (       d  U R                  SS0S5      $ [        R
                  R                  X2S-   5      n[        R
                  R                  X2S-   5      n[        R
                  R                  U5      (       d  U R                  SS0S5      $ [        US5       nUR                  5       nS S S 5        0 n[        R
                  R                  U5      (       a+   [        U5       n[        R                  " U5      nS S S 5        U R                  UUR                  S	S
5      UR                  SS5      [        R                  " W5      R!                  S5      S.5      $ ! , (       d  f       N= f! , (       d  f       Nx= f! [         a     Nf = f)Nr   
invalid idr   r   r   	not found  rbr   ri   r   r   ascii)r   r   r   r;   )ro   r   rk   r   r-   r.   rl   isfilerI   r   r3   rJ   r6   r   r   	b64encoder   )	rb   rn   r   r   dat_path	json_pathr?   r;   r   s	            r   r   DiaryHandler._gba_load   s^   !!'*4..44W==??G\#:C@@77<<&67GGLL'89	ww~~h''??G[#93??(D!Q668D "77>>)$$)_99Q<D % HHVR(XXgq)$$T*11':	 
  	 "!
 %_ s<   FF:  F)7F: 
F&)
F73F: 7F: :
GGc                    U R                  U5      nU(       a   U R                  R                  U5      (       d  U R                  SS0S5      $ S Ha  n[        R
                  R                  X2U-   5      n[        R
                  R                  U5      (       d  MK  [        R                  " U5        Mc     U R                  SS05      $ ! [         a     M  f = f)Nr   r   r   )r   r   r   T)
ro   r   rk   r   r-   r.   rl   r   r8   r6   )rb   rn   r   r   extps         r   r   DiaryHandler._gba_delete
  s    !!'*4..44W==??G\#:C@@$CTS=1Aww~~a  YYq\ %
 d|,, !&$&s   C
CCc                 4   U R                   R                  SS5      n[        R                  " SU5      nU(       d  gUR	                  S5      n[
        R                  U5      nU(       a  U[        R                  " 5       :  a  [
        R                  US5        gg)z,Check session cookie. Returns True if valid.Cookieri   ztomica_sess=([a-f0-9]+)Fr   NT)r   r   rj   searchr   TOMICA_SESSIONSr   poprb   cookier   tokenexpirys        r   _tomica_auth_okDiaryHandler._tomica_auth_ok  sr    !!(B/II0&9
 $$U+$))+-t,r   c                 (    [        U R                  R                  S5      =(       d    S5      nU R                  R	                  U5      n[
        R                  " UR                  S5      5      nUR                  SS5      R                  5       nUR                  SS5      nU[        :X  Ga  [        R                  " UR                  5       5      R                  5       [        :X  a  [        R                   " S5      n["        R"                  " 5       S-   [$        U'   U R'                  S	5        U R)                  S
S5        U R)                  SSU S35        [
        R*                  " SUS.5      R                  5       nU R)                  S[-        [/        U5      5      5        U R1                  5         U R2                  R5                  U5        g U R7                  SS0S5        g ! [8         a'  nU R7                  S[-        U5      0S5         S nAg S nAff = f)Nrt   r   r%   userri   password    '    rr   rs   
Set-Cookieztomica_sess=#; Path=/; Max-Age=2592000; HttpOnlyTr   r   r   zinvalid credentials  r   )r   r   r   r   r   r3   r   r   r   TOMICA_USERhashlibsha256rw   	hexdigestTOMICA_PASS_HASHsecrets	token_hexr   r   rx   ry   rv   rz   r{   r|   r}   r~   r   r6   )	rb   r   r   r   r   pwr   r   r   s	            r   _tomica_loginDiaryHandler._tomica_login#  s   	4T\\%%&67<1=B**//"%CjjG!45G;;vr*002DZ,B{"w~~biik'B'L'L'NRb'b))"-)-z)A&""3'  1CD  eWDg/hizz">?FFH  !13s4y>B  "

  &OOW&;<cB 	4OOWc!f-s33	4s   GG  G   
H*HHc                 (   U R                   R                  SS5      n[        R                  " SU5      nU(       d  gUR	                  S5      n[
        R                  US5      nU[        R                  " 5       :  a  [
        R                  US5        gg)	z3Check garage session cookie. Returns True if valid.r   ri   garage_sess=([a-f0-9]+)Fr   r   NT)r   r   rj   r   r   GARAGE_SESSIONSr   r   r   s        r   _garage_auth_okDiaryHandler._garage_auth_ok:  sr    !!(B/II0&9
 $$UA.DIIKt,r   c                     [        U R                  R                  S5      =(       d    S5      nU R                  R	                  U5      n[
        R                  " UR                  S5      5      nUR                  SS5      n[        R                  " UR                  5       5      R                  5       [        :X  a  [        R                  " S5      n[        R                  " 5       S-   [         U'   U R#                  S5        U R%                  S	S
5        U R%                  SSU S35        [
        R&                  " SUS.5      R                  5       nU R%                  S[)        [+        U5      5      5        U R-                  5         U R.                  R1                  U5        gU R3                  SS0S5        g! [4         a'  nU R3                  S[)        U5      0S5         SnAgSnAff = f)u:   Password-only login. user field is ignored (per spec §3).rt   r   r%   r   ri   r   r   r   rr   rs   r   zgarage_sess=r   Tr   Nr   zinvalid passwordr   r   )r   r   r   r   r   r3   r   r   r   r   rw   r   GARAGE_PASS_HASHr   r   r   r   rx   ry   rv   rz   r{   r|   r}   r~   r   r6   )rb   r   r   r   r   r   r   r   s           r   _garage_loginDiaryHandler._garage_loginG  sr   	4T\\%%&67<1=B**//"%CjjG!45GZ,B~~biik*446:JJ))"-)-z)A&""3'  1CD  eWDg/hizz">?FFH  !13s4y>B  "

  &OOW&893? 	4OOWc!f-s33	4s   FF5  F5 5
G&?G!!G&c                    U R                   R                  SS5      n[        R                  " SU5      nU(       a%  [        R                  UR                  S5      S 5        U R                  S5        U R                  SS5        U R                  SS	5        [        R                  " S
S05      R                  5       nU R                  S[        [        U5      5      5        U R                  5         U R                  R!                  U5        g )Nr   ri   r   r   r   rr   rs   r   z)garage_sess=; Path=/; Max-Age=0; HttpOnlyr   Trt   )r   r   rj   r   r   r   r   rx   ry   r3   rv   rw   rz   r{   r|   r}   r~   )rb   r   r   r   s       r   _garage_logoutDiaryHandler._garage_logout]  s    !!(B/II0&9
D13);<'RSzz4,'..0)3s4y>:

r   c                 x    [        5       n[        UR                  S5      [        5      (       a  [	        UR                  S0 5      5      OSnU R                  S[        [        R                  " 5       [        -
  S-  5      US.5      $ ! [         a'  nU R                  S[        U5      0S5      s S nA$ S nAff = f)Nr   r   Tr   )r   	uptime_ms	car_countr   r   )rL   
isinstancer   dictr{   r   r   r   _garage_start_timer6   rz   )rb   dbr  r   s       r   _garage_healthDiaryHandler._garage_healthk  s    		;B3=bffVnd3S3SBFF62./YZI?? $))+0B"Bd!JK&$  
  	;??GSV#4c::	;s   BB 
B9B4.B94B9c                    U R                  5       (       d  U R                  SS0S5      $  [        5       n[        R                  " 5       S-  [        UR                  S0 5      5      UR                  S0 5      UR                  SS5      S.n[        R                  " US	S
S9R                  S5      nU R                  S5        U R                  SS5        U R                  SS5        U R                  S[        [        U5      5      5        U R                  5         U R                  R                  U5        g! [          a'  nU R                  S[        U5      0S5      s SnA$ SnAff = f)z/P1: dump full DB as JSON attachment (download).r   unauthorizedr   r   r   r   r   )exported_atr  r   r   Fr(   r)   r%   r   rr   zapplication/json; charset=utf-8Content-Dispositionz0attachment; filename="tomica_garage_export.json"rt   Tr   N)r   r   rL   r   r   r{   r   r3   rv   rw   rx   ry   rz   r|   r}   r~   r6   )rb   r	  exportr   r   s        r   _garage_export_json DiaryHandler._garage_export_jsonw  s+   ##%%??G^#<cBB	;B#||~: !34vr* ff\15	F ::f5CJJ7SDs#^-NO24fg-s3t9~>JJT" 	;??GSV#4c::	;s   D	D5 5
E&?E!E&!E&c                    U R                  5       (       d  U R                  SS0S5      $  [        5       n[        UR	                  S5      [
        5      (       a  UR	                  S0 5      O0 n/ SQnSSKnSSKnUR                  5       nUR                  XcSS	9nUR                  5         [        UR                  5       5       H3  n[        X(   5      n	U	R                  S
U5        UR                  U	5        M5     UR                  5       R!                  S5      n
U R#                  S5        U R%                  SS5        U R%                  SS5        U R%                  S['        [)        U
5      5      5        U R+                  5         U R,                  R/                  U
5        g! [0         a'  nU R                  S['        U5      0S5      s SnA$ SnAff = f)zIP1: dump cars as CSV attachment (download). One row per car, flat schema.r   r  r   r   )r   brandmodelnoyearcolorchassis_codeowned	confirmedlow_conf	top_photobottom_photonotes
created_atr   r   Nignore)
fieldnamesextrasactionr   	utf-8-sigr   rr   ztext/csv; charset=utf-8r  z/attachment; filename="tomica_garage_export.csv"rt   Tr   )r   r   rL   r  r   r  csvioStringIO
DictWriterwriteheadersortedkeys
setdefaultwriterowgetvaluerw   rx   ry   rz   r{   r|   r}   r~   r6   )rb   r	  	cars_dictr"  _csv_iobufwritercidrowr   r   s               r   _garage_export_csvDiaryHandler._garage_export_csv  s{   ##%%??G^#<cBB	;B.8.N.Nvr*TVI?J ,,.C__Sh_WF inn./9>*tS)$ 0 <<>((5Ds#^-FG24ef-s3t9~>JJT" 	;??GSV#4c::	;s   E1F 
G'G	G	Gc                     [        U R                  R                  S5      =(       d    S5      nU R                  R	                  U5      n[
        R                  " UR                  S5      5      nUR                  SS5      nUR                  SS5      nU(       d  U R                  SS0S	5      $ S
nSn[        R                  R                  U5      (       a  [        U5       nU Hf  n	U	R                  5       n	U	R                  S5      (       d  M+  U	R                  S5      (       a  MC  U	R                  SS5      S   R                  5       n  O   SSS5        U(       d  U R                  SS0S5      $ Sn
SSSS.SSU0S.SSS.SSU0S.SSS./S.nSSU
S.U/SS S!.n[         R"                  R%                  S"[
        R&                  " U5      R)                  S5      S#U 3S$S%.S&9n [         R"                  R+                  US'S(9 n[
        R                  " UR	                  5       5      nSSS5        W=(       d    0 R                  S)5      =(       d    / n[-        U[.        5      (       a  U(       dd  U=(       d    0 R                  S5      =(       d    0 n[-        U[0        5      (       a  UR                  S*5      OSnU=(       d    S+n[3        S,U 35      e[-        US   [0        5      (       a  US   O0 n[-        UR                  S*5      [0        5      (       a  UR                  S*5      O0 n[-        UR                  S-5      [4        5      (       a  UR                  S-S5      OSnU(       d  [3        S.5      e[6        R8                  " S/SUR                  5       5      n[6        R8                  " S0SU5      n [
        R                  " U5      nU R                  UR                  S3S5      =(       d    SUR                  S4S5      =(       d    S[5        UR                  S5S5      =(       d    S5      UR                  S65      UR                  S7S5      =(       d    SUR                  S80 5      =(       d    0 UR                  S9S5      =(       d    SS:.5      $ ! , (       d  f       GNH= f! , (       d  f       GN= f! [:         a^    [6        R<                  " S1U[6        R>                  5      nU(       a%  [
        R                  " URA                  S5      5      OSSSS0 S2.n GNRf = f! [:         a`  nSSK!nU RE                  S;U S<URG                  5        35        U R                  SS=[I        U5      RJ                   S>U 30S?5      s SnA$ SnAff = f! [:         a`  nSSK!nU RE                  S@U S<URG                  5        35        U R                  SSA[I        U5      RJ                   S>U 30S5      s SnA$ SnAff = f)BzIdentify a Tomica car from top + bottom images via MiniMax vision.
Mirror of _tomica_identify but garage-cookie auth (see _tomica_identify for full logic).rt   r   r%   topri   bottomr   missing top imager   /opt/data/.envNMINIMAX_CN_API_KEY=#=r   z!server missing MINIMAX_CN_API_KEYr   aI  You are a Tomica die-cast car identification expert. Given two photos (top showing the car body, bottom showing the base), return a JSON object with fields: brand, model, no, year, color (optional), confidence (object with brand/model/year/no each 0.0-1.0 float), notes. If unclear, use low confidence (0.3) rather than guessing.r   textzTop photo (vehicle body):)typer@  	image_urlurl)rA  rB  u;   Bottom photo (base engraving showing © TOMY YYYY + No.XX):zPReturn ONLY a JSON object with brand, model, no, year, color, confidence, notes.)rolecontentz
MiniMax-01systemX  g?)r  messages
max_tokenstemperature2https://api.minimaxi.com/v1/text/chatcompletion_v2Bearer rs   Authorizationrr   )r;   r   -   timeoutchoicesmessagezSupstream returned no choices (model unavailable, rate-limited, or content filtered)zAI identify upstream: rE  z+AI identify upstream returned empty contentz^```(?:json)?\s*z
\s*```\s*$z\{.*\})r  r  r  r  
confidencer  r  r  r  r  rT  r  )r  r  r  r  r  rT  r  z garage identify upstream error: 
zidentify upstream failed: :   zgarage identify exception: identify failed: )&r   r   r   r   r   r3   r   r   r   r-   r.   r   rI   r   
startswithspliturllibrequestRequestrv   rw   urlopenr  listr  RuntimeErrorrz   rj   subr6   r   DOTALLr   	tracebacklog_message
format_excrA  __name__)rb   r   r   r   r9  r:  env_pathapi_keyr?   linesystem_promptuser_payloadr   reqresp	resp_datarR  upstreamupstream_msgbase_msgfirstrS  rE  parsedr   upstream_err_tbr   rc  s                                r   _garage_identifyDiaryHandler._garage_identify  sO   k	`T\\%%&67<1=B**//"%CjjG!45G++eR(C[[2.F1D'EsKK (HGww~~h''(^q !#zz|??+@AA$//Z]J^J^&*jja&8&;&A&A&CG!	 !" $ 1T'UWZ[[
M  #-HI(s|D#-jk(vG#-  A	L &%-@  ""D ..((DZZ%,,W5'.wi%8$6 ) C,C^^++C+< $

499; 7I = %?//	:@b!'400 )R44W=CH>HSW>X>X8<<	#:^bL ,  !F  1FH&)?z'JKK&0T&B&B
2<UYYy=QSW2X2X%))I.^`8B7;;yCY[^8_8_'++i4eg&'TUU&&!4b'--/J&&G<C!ZZ0F #ZZ4:#ZZ4:fjjr28b9"JJv.#ZZ4:"(**\2">"D"#ZZ4:(  Y $^\ =<2 ! C		)Wbii@A78TZZ
3UW_ako  @B  ?CFC  C'  #CL>QSTWTbTbTdSe!fg3MdS_N`NiNiMjjlmylz1{'|  B  C  C	C
  	`:1#R	@T@T@V?WXY??G/@aAQAQ@RRTUVTW-X#Y[^__		`s   B+V .3V !*RR''R"V 1A3V %T %R!(F	T 2R3 CT 
RV !
R0+T 3A$TT TT 
V(AV=V>V VV 
W5AW0*W50W5c                    U R                   R                  SS5      nSU;  a  U R                  SS0S5      $ [        U R                   R                  S5      =(       d    S5      nUS	:  a  U R                  SS
0S5      $  U R                  R                  U5      nSU-   S-   R                  S5      U-   n[        5       R                  U5      n[        n[        R                  " USS9  UR                  5        GH^  nUR                  5       S:X  a  M  UR                  5       (       d  M1  UR                  SS9nU(       d  MI  UR                  5       =(       d    Sn	[        R                   R#                  U	5      S   R%                  5       =(       d    Sn
U
S;  a  Sn
['        [(        R*                  " 5       S-  5      n[,        R.                  " S5      nSU-   S-   U-   nUS-   U
-   n[        R                   R1                  Xn5      n[3        US5       nUR5                  U5        SSS5        [7        U5      nUS-   n[        R                   R1                  UU5      nUnSn[8        (       d'  [3        US5       nUR5                  U5        SSS5        O[:        R2                  " [<        R>                  " U5      5      nUR@                  S ;   a  URC                  S!5      nURE                  S"[:        RF                  RH                  5        URK                  US#S$S%S&9  [        R                   RM                  U5      nU R                  UUS'U-   US'U-   UUU
US(.	5      s  $    U R                  SS)0S5      $ ! , (       d  f       GNT= f! , (       d  f       NY= f! [N         aP  nSSK(nU RS                  S*U S+URU                  5        35        U R                  S['        U5      0S,5      s SnA$ SnAff = f)-u/  P1: multipart/form-data image upload. Field name 'file'.
Saves BOTH original (kept for archive) + 800px WebP thumbnail (used by AI identify + UI).
Cap: 8MB. Pillow optional — if missing, thumb is a copy of original.
Returns: {filename, original_url, thumb_url, original_size, thumb_size, original_ext}rr   ri   multipart/form-datar   zexpected multipart/form-datar   rt   r      file too large (max 8MB)  Content-Type: 

r%   Tr   	multipartr   uploadr   .jpgr  z.jpegz.png.webpz.heicz.heifr      v__origr   N_thumb.webpunknown)RGBALAPRGB)   r  WEBPU      )qualitymethod/garage_images/)	filenameoriginal_filenameoriginal_urlthumb_filename	thumb_urloriginal_size
thumb_sizeoriginal_extr   no file found in payloadzupload exception: rU  r   )+r   r   r   r   r   r   rw   r   
parsebytesGARAGE_IMAGES_DIRr-   r   walkget_content_maintypeget_filenameget_payloadr.   splitextlowerrz   r   r   r   r   rl   rI   r~   r{   GARAGE_PIL_OK
_PIL_Imager&  BytesIOmodeconvert	thumbnail
ResamplingLANCZOSsavegetsizer6   rc  rd  re  )rb   content_typer   r   mime_msg_bytesmsgsave_dirpart	file_data	orig_nameorig_exttshex_idbaseorig_filename	orig_pathr?   	orig_sizer  
thumb_pathr  _thumb_statusimgr   rc  s                            r   _garage_post_upload DiaryHandler._garage_post_upload  se   
 ||''; 4??G-K#LcRR!!"238q9??G-G#H#NN<	;::??2&D.=
JRRSZ[^bbN-**>:C(HKK40
,,.+=((** ,,D,9	  --/;8	77++I6q9??AKV#WW%H945 **1-Rx#~. $w 9GGLLA	)T*aGGI& +	N	!%!5WW\\(NC
&
 )$}j$/1	* 0/ %//"**Y*?@Cxx#66!kk%0MM*j.C.C.K.KLHHZAHF!#!<J -)6$5$E&4!2^!C%.",$,%
( 
 
O #d ??G-G#H#NN? +* 0/,  	;1!By7K7K7M6NOP??GSV#4c::	;sX   ?FN M/"AN 5NCN N /
M>	9N 
N	N 
O,AO'!O,'O,c           	        ^  [        U R                  R                  S5      =(       d    S5      nU R                  R	                  U5      n[
        R                  " UR                  S5      5      nUR                  S5      nU(       a  [        U[        5      (       d  U R                  SS0S5      $ [        R                  " SU5      (       d  U R                  SS	0S5      $ UR                  S
5      =(       d    SR                  5       nUR                  S5      =(       d    SR                  5       nU(       d  U(       d  U R                  SS0S5      $ UR                  S5      n[        U[        5      (       d  U R                  SS0S5      $ USL a"  U(       d  U(       d  U R                  SS0S5      $ [        R                   " 5       S-  n["           [%        5       n	[        U	R                  S5      [&        5      (       a  0 U	S'   U	R)                  S0 5      n
U
R                  U5      nUR                  SS5      =(       d    SnU(       a=  UR                  SS5      =(       d    SU:  a  U R                  SUSS.5      sSSS5        $ UR)                  SU5        XS'   UR                  S5      =(       d    0 m[+        U4S jS 5       5      nXS'   S H\  u  pUR                  US5      nU(       d  M   SU;   d  M(  UR                  U5      (       a  M@  UR-                  SS5      S   nUS -   X?'   M^     X:U'   XS'   [.        R0                  R3                  [4        5      (       aB   [7        [4        R9                  S!S"5      [
        R:                  " [=        [4        5      5      5        [7        [4        U	5        SSS5        U R                  SUS#S.5      $ ! [>         a     N9f = f! , (       d  f       N3= f! [>         aP  nSSK nU RC                  S$U S%URE                  5        35        U R                  S[        U5      0S&5      s SnA$ SnAff = f)'zMIncremental save with LWW by updated_at (mirror of _tomica_post_cars_single).rt   r   r%   r   r   missing or invalid idr   rh   invalid id formatr  ri   r  z!must have at least brand or modelr  z5must set confirmed:true or confirmed:false explicitlyTz.must have at least brand or model (final save)r   r   r   
server_wonr   carmergedNr   rT  c              3      >#    U  HE  n[        TR                  U5      [        [        45      =(       a    TR                  U5      S :  v   MG     g7f)g333333?N)r  r   r   float).0kconfs     r   	<genexpr>8DiaryHandler._garage_post_cars_single.<locals>.<genexpr>  sB      E#Ca %TXXa[3,?UDHHQKRUDUU#Cs   AAr  r  r  r  r  ))r  	top_thumb)r  bottom_thumb_orig.r   r  r   rF   
client_wonzgarage save exception: rU  r   )#r   r   r   r   r   r3   r   r   r  rz   r   rj   rk   r   boolr   r   rG   rL   r_  r,  anyrsplitr-   r.   r7   rH   r@   r5   rJ   rI   r6   rc  rd  re  )rb   r   r   r  car_idr  r  confirmed_valnowr	  r   existingclient_updatedloworig_key	thumb_keyorig_urlr  r   rc  r  s                       @r   _garage_post_cars_single%DiaryHandler._garage_post_cars_singlee  s   @	;T\\%%&67<1=B**//"%C**SZZ01CWWT]FFC!8!81H'I3OO884f==1D'EsKKWWW%+224EWWW%+224E1T'UWZ[[GGK0MmT221h'iknoo$U??G5e+fhkll,,.I-C!^bffVnd33!#BvJ}}VR088F+!$q!9!>QlA!>!C!~ U??$xS_+`a   |S1$'L!ww|,2 E#CE E"%J ,j'H"wwx4HxH$8ASAS'x;A>)-)=	 ,j  #V#&< 77>>.11,*227KH IId>&:; %^R8I  J ??$sl#STT % E  L  	;6qcI<P<P<R;STU??GSV#4c::	;s   B-O3 1/O3 !A1O3 9O3 &O3 4O3 B,O">	O3 A'O"3O";O"AO"$AO%O"5O3 
OO"OO""
O0,O3 3
Q=AQQQc           	          [        U R                  R                  S5      =(       d    S5      nU R                  R	                  U5      n[
        R                  " UR                  S5      5      nUR                  S0 5      n[        U[        5      (       d  U R                  SS0S5      $ UR                  SS5      n[        R                  " 5       S	-  nXEUS
.n[           [        R                  R!                  ["        5      (       aB   [%        ["        R'                  SS5      [
        R(                  " [+        ["        5      5      5        [%        ["        U5        SSS5        U R                  S[/        U5      S.5      $ ! [,         a     NAf = f! , (       d  f       N;= f! [,         a'  nU R                  S[1        U5      0S5      s SnA$ SnAff = f)z$Bulk replace entire cars collection.rt   r   r%   r   r   zcars must be an objectr   r   r   r   r   rF   NT)r   r  r   )r   r   r   r   r   r3   r   r   r  r  r   r   r   rG   r-   r.   r7   rH   r@   r5   rJ   rI   r6   r{   rz   )	rb   r   r   r   r   r   r  r	  r   s	            r   _garage_post_cars_replace&DiaryHandler._garage_post_cars_replace  sc   	;T\\%%&67<1=B**//"%CjjG!45G;;vr*DdD))1I'JCPP \15J,,.I-CLB77>>.11,*227KH IId>&:; %^R8   ??$SY#GHH %     	;??GSV#4c::	;sa   B'F  *5F  )F	AE?
F$F  ?
F	FFF
FF   
G*GGGc                    U R                  5       (       d  U R                  SS0S5      $  U R                  R                  SS5      nSnSU;   Ga&  [	        U R                  R                  S5      =(       d    S	5      nUS
:  a  U R                  SS0S5      $ U R
                  R                  U5      nSU-   S-   R                  S5      U-   n[        5       R                  U5      nUR                  5        H|  nUR                  5       S:X  a  M  UR                  5       (       d  M0  UR                  SS9nU(       d  MH  S H  n	 UR                  U	5      n  O   Ub  Mh  U R                  SS0S5      s  $    O[	        U R                  R                  S5      =(       d    S	5      nU R
                  R                  U5      n
[         R"                  " U
R                  S5      5      nUR                  SS5      nU(       a  UR%                  5       (       d  U R                  SS0S5      $ [&        R(                  " [*        R,                  " U5      5      n[/        U5      n[1        5       n[3        UR                  S5      [4        5      (       a)  [7        UR                  S0 5      R9                  5       5      O	[7        5       n/ n/ n[;        U5       GH  u  nnUS-   n/ nUR                  S5      =(       d    SR%                  5       nU(       d  UR=                  S5        O3[>        R@                  " SU5      (       d  UR=                  SUSS  35        UR                  S5      =(       d    SR%                  5       nUR                  S 5      =(       d    SR%                  5       nUR                  S!5      =(       d    SR%                  5       RC                  5       nUS";   a  SnO0US#;   a  S$nO'UR=                  S%UR                  S!5      < S&35        SnUSL a  U(       d  U(       d  UR=                  S'5        UR                  S(5      =(       d    SR%                  5       nSnU(       a   [	        U5      nU(       a  UR=                  UUUUS*.5        GM  UR=                  UUUUUR                  S+5      =(       d    SR%                  5       UUR                  S,5      =(       d    SR%                  5       UR                  S-5      =(       d    SR%                  5       UR                  S.5      =(       d    SR%                  5       RC                  5       S";   UUR                  S/5      =(       d    SR%                  5       UU;   S0.5        GM     U R                  [G        U5      [G        U5      [G        U5      [I        S1 U 5       5      [I        S2 U 5       5      USS3 USS3 S4.5      $ ! [         a     GM  f = f! [D         a    UR=                  S)U 35         GNf = f! [J         aP  nS	SK&nU RO                  S5U S6URQ                  5        35        U R                  S[S        U5      0S75      s SnA$ SnAff = f)8zP1 1.5c: parse CSV, validate rows, return preview. NO write to DB.
Accepts multipart 'file' field OR JSON {csv_text: "..."}.
Validates: id format, confirmed explicit, brand/model present (for confirmed:true).r   r  r   rr   ri   Nry  rt   r   i  @ zCSV too large (max 4MB)r|  r}  r~  r%   r  Tr  )r$  r%   gbkbig5z(CSV decode failed (tried utf-8/gbk/big5)r   csv_textz	empty CSVr   r(   r   z
missing idrh   invalid id format: (   r  r  r  )true1yes)false0r  ri   Fz#confirmed must be true/false (got: )z/confirmed:true requires at least brand or modelr  zinvalid year: )ri  r   errorsr5  r  r  r  r  r  )ri  r   r  r  r  r  r  r  r  r  r  will_overwritec              3   <   #    U  H  oS    (       d  M  Sv   M     g7fr  r   Nr   r  r  s     r   r  ;DiaryHandler._garage_post_import_preview.<locals>.<genexpr>&  s     +Tu!BR@SAAu   	c              3   <   #    U  H  oS    (       a  M  Sv   M     g7fr  r   r  s     r   r  r  '  s      MEq;K9LEr     )	row_countvalid_countinvalid_countwill_overwrite_count	new_countpreviewr  zimport preview exception: rU  r   )*r   r   r   r   r   r   r   rw   r   r  r  r  r  r  r   UnicodeDecodeErrorr3   r   r   r%  
DictReaderr&  r'  r_  rL   r  r  setr+  	enumerater   rj   rk   r  
ValueErrorr{   sumr6   rc  rd  re  rz   )rb   r  r  r   r   r  r  r  r  encr   r   readerrowsr	  existing_idsvalidinvalidir5  line_nor  ridr  r  confirmed_rawr  year_rawr  r   rc  s                                  r   _garage_post_import_preview(DiaryHandler._garage_post_import_preview  st    ##%%??G^#<cBBe	;<<++NB?LH$4))*:;@qA'??G5N+OQTUUzzr*"2\"AJ"N!V!VW^!_bf!f!m..~>HHJD002kA ,,..  $ 0 0 0 =I y#HC)+4+;+;C+@ % $I $+#'??G=g3hjm#nn '" ))*:;@qAjjoob)**SZZ%89";;z268>>#3#3'=sCC ^^BKK$9:F<DB=GvX\=]=]3rvvfb16689cfchLEG#D/3a%wwt}*113MM,/":C@@MM$7CRz"BC)/R668)/R668!$!5!; B B D J J L $88 $I"&>> %IMM$GP[H\G__`"ab $I$U5MM"STGGFO1r88:C"8} NNG3&Y\#]^LL '!!&!&"wwt}299; $"%'''"2"8b!?!?!A),)@)FB(M(M(O"%'''"2"8b!?!?!A!G!G!IMa!a%."%'''"2"8b!?!?!A*-*=" ? *\ ?? Y"5z!$W(++Tu+T(T  ME MM ":!#2,$  G $6 ) ()^ & Cxj&ABC:  	;9!By?S?S?U>VWX??GSV#4c::	;s   A+X B"X =X WX X 3B1X %H;X !W-,E.X 
W*%X )W**X -XX XX 
Y,AY'!Y,'Y,c                    U R                   S:X  a  U R                  5       $ U R                   S:X  a  U R                  5       $ U R                   S:X  a  U R                  5       $ U R                   S:X  aC  U R	                  5       (       d  U R                  SS0S5      $  U R                  [        5       5      $ g	! [         a'  nU R                  S[        U5      0S5      s S nA$ S nAff = f)
Nz/api/garage/healthz/api/garage/export.jsonz/api/garage/export.csv/api/garage/carsr   r  r   r   F)	r.   r
  r  r6  r   r   rL   r6   rz   rb   r   s     r   _garage_handle_getDiaryHandler._garage_handle_get0  s    99,,&&((9911++--9900**,,99**''))'@#FF?|~66   ?Q'8#>>?s   B5 5
C&?C!C&!C&c                 
   U R                   nUS:X  d  UR                  S5      (       a  U R                  5       $ US:X  d  UR                  S5      (       a  U R                  5       $ US:X  d  UR                  S5      (       a9  U R	                  5       (       d  U R                  SS0S	5      $ U R                  5       $ US
:X  a9  U R	                  5       (       d  U R                  SS0S	5      $ U R                  5       $ US:X  a9  U R	                  5       (       d  U R                  SS0S	5      $ U R                  5       $ [        R                  " SU5      nU(       aI  U R	                  5       (       d  U R                  SS0S	5      $ U R                  UR                  S5      5      $ [        R                  " SU5      nU(       aI  U R	                  5       (       d  U R                  SS0S	5      $ U R                  UR                  S5      5      $ US:X  a9  U R	                  5       (       d  U R                  SS0S	5      $ U R                  5       $ US:X  a9  U R	                  5       (       d  U R                  SS0S	5      $ U R                  5       $ g)Nz/api/garage/loginz/api/garage/login?z/api/garage/logoutz/api/garage/logout?z/api/garage/identifyz/api/garage/identify?r   r  r   r  z/api/garage/cars/singlez%^/api/garage/cars/([^/?]+)/confirm/?$r   z%^/api/garage/cars/([^/?]+)/discard/?$z/api/garage/uploadz/api/garage/import/previewF)r.   rY  r   r  r   r   rv  r  r  rj   rk   _garage_post_cars_confirmr   _garage_post_cars_discardr  r  )rb   r.   	m_confirm	m_discards       r   _garage_handle_post DiaryHandler._garage_handle_post@  sB   yy&&$//:N*O*O%%''''4??;P+Q+Q&&(())T__=T-U-U''))'@#FF((**%%''))'@#FF1133,,''))'@#FF0022HHEtL	''))'@#FF11)//!2DEEHHEtL	''))'@#FF11)//!2DEE''''))'@#FF++--//''))'@#FF3355r   c           	         U R                  5       (       d  U R                  SS0S5      $ [        R                  " SU R                  5      nU(       d  U R                  SS0S5      $ UR                  S5      n[        R                  " SU5      (       d  U R                  SS	0S
5      $ [           [        5       n[        UR                  S5      [        5      (       a  0 US'   UR                  S0 5      nX$;  a  U R                  SSS.5      sS S S 5        $ XB	 [        R                  " 5       S-  nXSS'   [        R                  R                  [         5      (       aB   [#        [         R%                  SS5      [&        R(                  " [+        [         5      5      5        [#        [         U5        S S S 5        U R                  SSS.5      $ ! [,         a     N8f = f! , (       d  f       N2= f)Nr   r  r   z^/api/garage/cars/([^/?]+)/?$	Not Foundr   r   rh   r  r   r   TFr   deletedr   r   r   rF   )r   r   rj   rk   r.   r   rG   rL   r  r   r_  r,  r   r   r-   r7   rH   r@   r5   r3   rJ   rI   r6   rb   r   r  r	  r   r  s         r   _garage_handle_delete"DiaryHandler._garage_handle_deleteg  s   ##%%??G^#<cBBHH5tyyA??G[#93??xx0&99??G-@#A3GGB"&&.$//6
==,D!du'EF ^ ,,.I-C"|ww~~n--(&..wD		$~"67 !4% & dt<== ! ! ^9   -AGAGAG	G	
GGGG
G'c           	      0   [         R                  " SU5      (       d  U R                  SS0S5      $ [           [	        5       n[        UR                  S5      [        5      (       a  0 US'   UR                  S0 5      nX;  a  U R                  SS0S5      sSSS5        $ X1   nS	US
'   [        R                  " 5       S-  US'   US   US'   [        R                  R                  [        5      (       aB   [        [        R!                  SS5      ["        R$                  " ['        [        5      5      5        [        [        U5        SSS5        U R                  S	WUR                  SS5      (       + S.5      $ ! [(         a     NNf = f! , (       d  f       NH= f)u   Promote draft (confirmed:false) → confirmed (true). Idempotent.
P0 confirmation gate: identify result is draft until user explicitly confirms.rh   r   r  r   r   r   r   NTr  r   r   r   rF   r  F)r   r  	was_draft)rj   rk   r   rG   rL   r  r   r_  r,  r   r   r-   r.   r7   rH   r@   r5   r3   rJ   rI   r6   )rb   r  r	  r   r  s        r   r  &DiaryHandler._garage_post_cars_confirm  sR    xx0&99??G-@#A3GGB"&&.$//6
==,D!'=sC ^ ,C#C $) ;C"<0B|ww~~n--(&..wD		$~"67 !4' ( d3Q[]bIcEcdee ! # ^s8   AFAF4AE75F7
FFFF
Fc           	         [         R                  " SU5      (       d  U R                  SS0S5      $ [           [	        5       n[        UR                  S5      [        5      (       a  0 US'   UR                  S0 5      nUR                  U5      nSn/ nU(       Gal  S H  nUR                  US5      nU(       d  M  UR                  S	5      (       d  M6  U[        S	5      S
 n	UR                  U	5        SU	;   a+  U	R                  SS5      S   n
UR                  U
S-   5        M  U	R                  S5      (       d  M  U	S
[        S5      *  n
S H  nUR                  U
S-   U-   5        M     M     X1	 Sn[        R                  " 5       S-  US'   [         R"                  R%                  [&        5      (       aB   [)        [&        R+                  SS5      [,        R.                  " [1        [&        5      5      5        [)        [&        U5        S
S
S
5        [        [4        R7                  W5      5      nSnU Hi  n	 [         R"                  R9                  [:        U	5      n[         R"                  R%                  U5      (       a  [         R<                  " U5        US-  nMi  Mk     U R                  SWU[        U5      S.5      $ ! [2         a     Nf = f! , (       d  f       N= f! [2         a     M  f = f)zcDiscard a draft. Delete car entry AND its photos from GARAGE_IMAGES_DIR.
Idempotent on missing car.rh   r   r  r   r   F)r  r  r  r  ri   r  Nr  r   r   r  r  r  Tr   r   r   rF   )r   	discardedphotos_removedfiles_attempted)rj   rk   r   rG   rL   r  r   r_  r,  rY  r{   r   r  r   r   r   r-   r.   r7   rH   r@   r5   r3   rJ   rI   r6   r  fromkeysrl   r  r8   )rb   r  r	  r   r  deleted_carphotos_to_removeurl_keyrC  r   r  r   unique_filesremoved_filesr   s                  r   r  &DiaryHandler._garage_post_cars_discard  s    xx0&99??G-@#A3GGB"&&.$//6
==,D((6"CK!  ZG'''2.Css~~.?@@ %6!7!89(//3#r>#%99Xq#9!#<D,33D=4HI[[77#%&:M(:':#;D'[ 0 7 7w8L M (\  Z L"#'<<>Y#>< 77>>.11,*227KH IId>&:; %^R8E H DMM*:;<BGGLL!2B777>>!$$IIaL!Q&M %  dXez}  K  {L   M  N  	N % A ^X  sX   A?K:KA$K:A:K5AJ<6K4A#K<
K	KK		K
K
K+*K+c                 
  ^^  [        U R                  R                  S5      =(       d    S5      nU R                  R	                  U5      n[
        R                  " UR                  S5      5      nUR                  SS5      nUR                  SS5      nU(       d  U R                  SS0S	5      $ S
nSm[        R                  R                  U5      (       a  [        U5       nU H  nUR                  5       nUR                  S5      (       d  UR                  S5      (       d  MA  UR                  SS5      S   R                  5       R                  S5      R                  S5      n	[!        U	5      S:  d  M  SU	;  d  M  U	m  O   SSS5        T(       d  U R                  SS0S5      $ SmUU4S jn
S nU
" U5      u  pUS:w  a  U R                  SSU SUSS  30S5      $ [
        R                  " U5      nUR                  SUR                  SUR                  SS5      5      5      n[#        U[$        5      (       a  S R'                  S! U 5       5      n0 n [
        R                  " U" U5      5      nU(       Ga  U
" U5      u  nnUS:X  Ga
  [
        R                  " U5      nUR                  SUR                  SUR                  SS5      5      5      n[#        U[$        5      (       a  S R'                  S$ U 5       5      n [
        R                  " U" U5      5      nS% H;  nUR                  U5      (       a  M  UR                  U5      (       d  M3  UU   UU'   M=     UR                  S"5      (       a!  UR                  S"S5      S&-   US"   -   SS# US"'   U R                  UR                  S'S5      =(       d    SUR                  S(UR                  S)S5      5      =(       d    SUR                  S(S5      =(       d    S[+        UR                  S*S5      =(       d    S5      UR                  S+5      UR                  S,S-5      UR                  S.S-5      UR                  S/S-5      UR                  S0S-5      S1.UR                  S"S5      =(       d    SS2.5      $ ! , (       d  f       GN= f! [(         a    S"USS# 0n GNMf = f! [(         a     GN:f = f! [(         aw  nSSKnUR/                  5       nU R1                  S3U S4U 35         U R                  SS5[3        U5      R4                   SU 30S5      s SnA$ ! [(         a      SnAgf = fSnAff = f)6zBIdentify a Tomica car from top + bottom images via MiniMax vision.rt   r   r%   r9  ri   r:  r   r;  r   r<  NzAUXILIARY_VISION_API_KEY=r=  r?  r   "'   ru   zvision API key not configuredr   u  You are identifying a Tomica die-cast toy car from photos. Look at the TOP image (vehicle shape, color, livery) to identify brand and model. Look at the BOTTOM image carefully — Tomica bases are engraved with '© TOMY YYYY' (production year) and sometimes a Tomica model number like 'No.XX'.

Return ONLY a compact JSON object (no markdown, no commentary):
{"brand": "Toyota/Nissan/Honda/Mazda/Subaru/etc OR empty string if unsure", "model": "specific car model name OR empty string", "no": "Tomica number if visible, OR empty string if not legible", "year": YYYY_as_integer_OR_null_if_unsure, "confidence_brand": "high/medium/low", "confidence_model": "high/medium/low", "confidence_year": "high/medium/low (read from base engraving, NOT estimated)", "confidence_no": "high/medium/low", "notes": "1 sentence what you saw (engravings, color, distinguishing marks)"}

CRITICAL RULES:
1. If you cannot clearly read a field, return empty string or null — do NOT guess.
2. Year must come from engraved '© TOMY YYYY' on the base. Never estimate year from model.
3. Tomica number 'No.XX' is hard to read — only return if clearly visible.
4. brand/model can be high confidence even if year/no are unclear.

Reply JSON only.c           	        > SS K nTU S.nUR                  R                  S[        U 5       S35        UR                  R	                  5         [
        R                  R                  S[        R                  " U5      R                  S5      ST 3SS	.S
S9n [
        R                  R                  USS9 nUR                  5       R                  SSS9nUR                  R                  SUR                   S[        U5       S35        UR                  R	                  5         UR                  U4sS S S 5        $ ! , (       d  f       g = f! [
        R                  R                    a  nUR"                  (       a  UR                  5       R                  SSS9OSnUR                  R                  SUR$                   SUS S  S35        UR                  R	                  5         UR$                  U4s S nA$ S nAf[&         av  nUR                  R                  S[)        U5      R*                   SU S35        UR                  R	                  5         SS[)        U5      R*                   SU 34s S nA$ S nAff = f)Nr   )promptrB  z[VLM] calling vlm, image bytes=rU  z+https://api.minimaxi.com/v1/coding_plan/vlmr%   rL  rs   rM  r   r;   r   r  rO  rP  r5   r  z[VLM] OK status=z bytes=ri   z[VLM] HTTPError z body=r   z
[VLM] EXC rV  zclient error: )sysstderrr~   r{   flushr[  r\  r]  r3   rv   rw   r^  r   r   r   r   	HTTPErrorfpcoder6   rA  rf  )	image_data_urlr@  vlm_payloadrl  rm  	body_textr   rh  prompt_texts	          r   call_vlm/DiaryHandler._tomica_identify.<locals>.call_vlm	  s   )4>R

  #B3~CVBWWY!Z[]`]g]g]m]m]onn,,AK077@+27))<(: " - G//R/@D$(IIK$6$6wy$6$Q	

((+;DKK=PST]P^O__a)bceheoeoeueuew#{{I5 A@@ ||-- -NOdd	 JXZIJJ$$'7xviPTQToEVVX%YZ\_\f\f\l\l\n669,,  GJJ$$z$q'2B2B1C2aS%KLcjjN^N^N`tAw/?/?.@1#FFFGsQ   E 5A;D:0	E :
EE E I9)BG60I96I9A+I4.I94I9c                 .   U R                  5       n U R                  S5      (       a  U $ SS KnUR                  SXR                  5      nU(       a  UR                  S5      $ UR                  SXR                  5      nU(       a  UR                  S5      $ U $ )N{r   z```(?:json)?\s*(\{.*?\})\s*```r   z\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\})r   rY  rj   r   rb  r   )r@  _rer   s      r   extract_json3DiaryHandler._tomica_identify.<locals>.extract_json$  su    zz|??3''K JJ@$

S771:%JJA4T771:%r   r   ztop image vision rV  rW  rE  r@  result c              3   8   #    U  H  n[        U5      v   M     g 7fNrz   r  xs     r   r  0DiaryHandler._tomica_identify.<locals>.<genexpr><  s     #=HqCFFH   r  i,  c              3   8   #    U  H  n[        U5      v   M     g 7frT  rU  rV  s     r   r  rX  K  s     .K{!s1vv{rY  )r  r  r  r  z	 | base: r  r  r   r  r  confidence_brandr  confidence_modelconfidence_yearconfidence_nor  )r  r  r   r  r  rT  r  zidentify exception: rU  rX  )r   r   r   r   r   r3   r   r   r   r-   r.   r   rI   r   rY  rZ  r{   r  r_  rl   r6   rz   rc  re  rd  rA  rf  )rb   r   r   r   r9  r:  rg  r?   ri  valrJ  rO  status1body1top_datatop_textr  status2body2bottom_databottom_textbr  r   rc  tbrh  rI  s                             @@r   _tomica_identifyDiaryHandler._tomica_identify  s   U	T\\%%&67<1=B**//"%CjjG!45G++eR(C[[2.F1D'EsKK (HGww~~h''(^q !#zz|??+FGG4??[pKqKq"&**S!"4Q"7"="="?"E"Ec"J"P"PQT"UC"3x"}C*- % !" $ 1P'QSVWW
# .G6" &c]NG#~3DWIRPUVZWZP[}1]'^`cddzz%(H||Ix||FHLLQY[]D^/_`H(D))88#=H#==F3L$:;
 !)&!1c>"&**U"3K"-//)[__VU`UdUdemoqUr=s"tK!+t44&)hh.K{.K&K	 JJ|K'@A!AA#)::a==QUU1XX,-aDq	 "B 55>>/5zz'2/F/TWXY`Wa/acgdg.hF7O ??GR06BGVZZ-CDJ

7B/52&**T2.4"5

6*#ZZ(:EB#ZZ(:EB"JJ'8%@ **_e<	  GR06B$  g $^z  3!8DS>23& % "  	%%'B3A3b=>3DT!WEUEUDVVXYZX[1\']_bcc 	s   B+S 03S #A R'AR9RR"S *8S #A7S R0 7BS 5S  S AS DS 
R-(S 0SS SS 
SS SS 
U#+U,U;U
UUUUUc                    U R                   S:X  aC  U R                  5       (       d  U R                  SS0S5      $  U R                  [        5       5      $ g! [         a'  nU R                  S[        U5      0S5      s S nA$ S nAff = f)N/api/tomica/carsr   r  r   r   F)r.   r   r   rY   r6   rz   r  s     r   _handle_tomica_getDiaryHandler._handle_tomica_getn  s{    99**''))'@#FF?|~66   ?Q'8#>>?s   A 
BB;BBc                    U R                   nUS:X  d  UR                  S5      (       a  U R                  5       $ US:X  d  UR                  S5      (       a9  U R                  5       (       d  U R	                  SS0S5      $ U R                  5       $ US:X  a9  U R                  5       (       d  U R	                  SS0S5      $ U R                  5       $ US	:X  a9  U R                  5       (       d  U R	                  SS0S5      $ U R                  5       $ US
:X  a9  U R                  5       (       d  U R	                  SS0S5      $ U R                  5       $ US:X  a=  U R                  5       (       d  U R	                  SS0S5      $ U R	                  SS0S5      $ g)Nz/api/tomica/loginz/api/tomica/login?z/api/tomica/identifyz/api/tomica/identify?r   r  r   rm  z/api/tomica/cars/singlez/api/tomica/uploadz/api/tomica/view-count/resetzNot implemented (Step 3+)  F)	r.   rY  r   r   r   rj  _tomica_post_cars_replace_tomica_post_cars_single_tomica_post_upload)rb   r.   s     r   _handle_tomica_post DiaryHandler._handle_tomica_postx  s}   yy&&$//:N*O*O%%''))T__=T-U-U''))'@#FF((**%%''))'@#FF1133,,''))'@#FF0022''''))'@#FF++--11''))'@#FF??G-H#I3OOr   c           	         U R                  5       (       d  U R                  SS0S5      $ [        R                  " SU R                  5      nU(       d  U R                  SS0S5      $ UR                  S5      n[        R                  " SU5      (       d  U R                  SS	0S
5      $ [           [        5       n[        UR                  S5      [        5      (       a  0 US'   UR                  S0 5      nX$;  a  U R                  SSS.5      sSSS5        $ XB	 [        R                  " 5       S-  nXSS'   [        R                  R                  [         5      (       aB   [#        [         R%                  SS5      [&        R(                  " [+        [         5      5      5        [#        [         U5        SSS5        U R                  SSS.5      $ ! [,         a     N8f = f! , (       d  f       N2= f)zStep 2D: DELETE /api/tomica/cars/<id>. Idempotent.
RLock + sanitize + atomic + .bak pattern.
No-op if id absent: return deleted:false, no disk IO.r   r  r   z^/api/tomica/cars/([^/?]+)/?$r#  r   r   rh   r  r   r   TFr$  Nr   r   r   rF   )r   r   rj   rk   r.   r   rS   rY   r  r   r_  r,  r   r   r-   r7   rT   rQ   r5   r3   rJ   rI   r6   r&  s         r   _handle_tomica_delete"DiaryHandler._handle_tomica_delete  s    ##%%??G^#<cBBHH5tyyA??G[#93??xx0&99??G-@#A3GGB"&&.$//6
==,D!du'EF ^ ,,.I-C"|ww~~n--(&..wD		$~"67 !4+ , dt<== ! ' ^r)  c           	          [        U R                  R                  S5      =(       d    S5      nU R                  R	                  U5      n[
        R                  " UR                  S5      5      nUR                  S5      nU(       a  [        U[        5      (       d  U R                  SS0S5      $ [        R                  " SU5      (       d  U R                  SS	0S5      $ [        R                  " 5       S
-  n[           [!        5       n[        UR                  S5      ["        5      (       a  0 US'   UR%                  S0 5      nUR                  U5      nUR                  SS5      =(       d    Sn	U(       a=  UR                  SS5      =(       d    SU	:  a  U R                  SUSS.5      sSSS5        $ UR                  S5      (       d  UR                  SU5      US'   X7U'   XVS'   [&        R(                  R+                  [,        5      (       aB   [/        [,        R1                  SS5      [
        R2                  " [5        [,        5      5      5        [/        [,        U5        SSS5        U R                  SUSS.5      $ ! [6         a     N9f = f! , (       d  f       N3= f! [6         a'  n
U R                  S[        U
5      0S5      s Sn
A
$ Sn
A
ff = f)zStep 2A: incremental save with LWW by updated_at.
RLock means caller can safely with-lock then call _tomica_load
(which may also internally with-lock). No deadlock.
Defensive: isinstance(..., list) sanitizes legacy array data.rt   r   r%   r   r   r  r   rh   r  r   r   r   Tr  r  Nr   r   rF   r  r   )r   r   r   r   r   r3   r   r   r  rz   r   rj   rk   r   r   rS   rY   r_  r,  r-   r.   r7   rT   rQ   r5   rJ   rI   r6   )rb   r   r   r  r  r  r	  r   r  r  r   s              r   rs  %DiaryHandler._tomica_post_cars_single  s5   
(	;T\\%%&67<1=B**//"%C**SZZ01CWWT]FFC!8!81H'I3OO884f==1D'EsKK,,.I-C!^bffVnd33!#BvJ}}VR088F+!$q!9!>QlA!>!C!~ U??"8|,    ww|,,(+c(BC%"V#&< 77>>.11,*227KH IId>&:; %^R87  8 ??$sl#STT % 3  :  	;??GSV#4c::	;su   B-J 0/J  J >B,J*	J 4AJAI>J!J >
JJ
JJ
JJ 
K)KKKc           	      H    [        U R                  R                  S5      =(       d    S5      nU R                  R	                  U5      n[
        R                  " UR                  S5      5      nUR                  S5      n[        U[        5      (       d  U R                  SS0S5      $ UR                  5        H}  u  pV[        U[        5      (       a  UR                  S5      U:w  a  U R                  SS	U 30S5      s  $ [        R                  " S
U5      (       a  Mf  U R                  SSU 30S5      s  $    [        R                  " 5       S-  n[            [#        5       n[        UR                  S5      [$        5      (       a  0 US'   SU;   a  [        US   5      US'   XHS'   XxS'   [&        R(                  R+                  [,        5      (       aB   [/        [,        R1                  SS5      [
        R2                  " [5        [,        5      5      5        [/        [,        U5        SSS5        U R                  SU[9        U5      WS   S.5      $ ! [6         a     NFf = f! , (       d  f       N@= f! [6         a'  n	U R                  S[;        U	5      0S5      s Sn	A	$ Sn	A	ff = f)zStep 2B: bulk replace entire cars collection.
Body: {'cars': {id1: carObj, id2: carObj, ...}, 'view_count': int}
Defensive: isinstance(..., list) sanitize; view_count preserved if
omitted from payload (server-side merge with current).rt   r   r%   r   r   z#cars must be an object {id: carObj}r   r   zcar id mismatch at key rh   r  r   r   r   r   rF   NT)r   r   countr   r   )r   r   r   r   r   r3   r   r   r  r  r   itemsrj   rk   r   r   rS   rY   r_  r-   r.   r7   rT   rQ   r5   rJ   rI   r6   r{   rz   )
rb   r   r   r   new_carsr4  r  r  r	  r   s
             r   rr  &DiaryHandler._tomica_post_cars_replace  s,   
)	;T\\%%&67<1=B**//"%CjjG!45G{{6*Hh--1V'WY\]]$NN,!#t,,0D??G7Nse5T+UWZ[[xx 8#>>??G7J3%5P+QSVWW	 -
 ,,.I-C!^bffVnd33!#BvJ7*'*7<+@'AB|$%6
#&< 77>>.11,*227KH IId>&:; %^R8'  ( ??!X .	$   % #  4  	;??GSV#4c::	;su   B&I0 )AI0 I0 I0 8 I0 A;IAII%)I0 
IIII
I-)I0 0
J!:JJ!J!c                    U R                   R                  SS5      nUR                  S5      (       d  U R                  SS0S5      $ [	        U R                   R                  S5      =(       d    S5      nUS::  a  U R                  SS	0S5      $ US
:  a  U R                  SS0S5      $  U R
                  R                  U5      nSU-   S-   R                  S5      U-   n[        5       R                  U5      nSn[        R                  " USS9  UR                  5        H  nUR                  5       S:X  a  M  UR                  5       (       d  M0  UR                  SS9nU(       d  MH  S[!        ["        R$                  " 5       S-  5      -   S-   [&        R(                  " S5      -   S-   n	[        R*                  R-                  Xi5      n
[/        U
S5       nUR1                  U5        SSS5        U R                  U	SU	-   [3        U5      S.5      s  $    U R                  SS0S5      $ ! , (       d  f       NG= f! [4         a'  nU R                  S[!        U5      0S5      s SnA$ SnAff = f) aV  Step 2C: multipart/form-data image upload. Field name 'file'.
Client expected to have transcoded to WebP (spec 5.3).
Ponytail: 8MB cap BEFORE read; 413 early.
Uses email.parser.BytesParser (cgi removed in Py 3.13).
Wrap body with Content-Type header (boundary info) before parse.
No DB lock needed (writes to separate file in tomica_images/).rr   ri   ry  r   zmust be multipart/form-datar   rt   r   zmissing or zero Content-Lengthrz  r{  r|  r}  r~  r%   z/opt/data/diary/tomica_imagesTr   r  r  r  r   r  r  r  r   Nz/tomica_images/)r  rC  r   r  r   )r   r   rY  r   r   r   r   rw   r   r  r-   r   r  r  r  r  rz   r   r   r   r   r.   rl   rI   r~   r{   r6   )rb   r  r   r   r  r  r  r  r  	safe_name	save_pathr?   r   s                r   rt   DiaryHandler._tomica_post_upload  s&    ||'';&&'<==??G-J#KSQQ!!"238q97??G-M#NPSTT??G-G#H#NN	;::??2&D.=
JRRSZ[^bbN-**>:C6HKK40
,,.+=((** ,,D,9	 #dlln	&A"BBSH7K\K\]^K__bii	GGLL=	)T*aGGI& + ),y8	N(   #" ??G-G#H#NN +*  	;??GSV#4c::	;s=   )D)H6 H%$*H6 H6 %
H3	/H6 6
I' I"I'"I'NrK  x   c                    [         R                  b  [         R                  $  [        SSSS9 nU Hm  nUR                  S5      (       d  M  UR                  S5      (       a  M3  UR	                  5       R                  SS5      S   R	                  5       [         l          O   S S S 5        [         R                  $ ! , (       d  f       [         R                  $ = f! [         a     [         R                  $ f = f)	Nr<  rE   r%   r&   r=  r>  r?  r   )r[   _BUREAU_KEYrI   rY  r   rZ  r6   )rb   r?   ri  s      r   _bureau_load_keyDiaryHandler._bureau_load_keyJ  s    ##/+++	&g>!D'<==dooVYFZFZ37::<3E3Ec13Ma3P3V3V3X0  ? ''' ?> '''  	'''	s:   C B:B:$>B:"C :
CC C 
C65C6Fc                    [         R                  (       a  U R                  SSS.S5      $ U R                  5       nU(       d  U R                  SS0S5      $ U R                  R                  S5      nU(       d  U R                  SS	0S
5      $  U R                  R                  [        U5      5      n[        R                  " U5      nSUS'   UR                  SS5        UR                  SS5        UR                  SSS05        [        R                  R                  [         R                   [        R"                  " U5      R%                  S5      SU 3SS.SS9n [        R                  R'                  U[         R(                  S9 n[        R                  " UR                  5       5      nS S S 5         WR                  S"0 /5      S#   R                  S$0 5      n
U
R                  S%5      =(       d    S&nUR                  S"0 /5      S#   R                  S'S&5      nU
R                  S(5      =(       d    S&nUR3                  5       (       a  US):X  a6  U(       a/  [4        R6                  " S*U5      nU(       a  UR9                  S#5      nXS"   S#   S$   S%'   [;        U=(       a    UR3                  5       (       + 5      UR                  S+0 5      S,'   U R                  WS-5      $ ! [         a!  nU R                  SSU 30S
5      s S nA$ S nAff = f! , (       d  f       GNh= f! [        R*                  R,                   aY  nUR                  5       R/                  SSS9S S
 n	U R                  SSUR0                   SU	 30UR0                  5      s S nA$ S nAf[         a!  nU R                  SS U 30S!5      s S nA$ S nAff = f! [         a     GNf = f).Nz8Service temporarily disabled due to upstream instabilitybureau_paused)r   rE  i  r   z!Server missing MINIMAX_CN_API_KEYr   rt   zMissing Content-Lengthr   zBad request body: zMiniMax-M2.7r  rI  rG  rJ  g      ?response_formatrA  json_objectr%   rL  rs   rM  r   r>  rP  r5   r?  zMiniMax HTTP rV  zMiniMax proxy failed: rW  rR  r   rS  rE  ri   finish_reasonreasoning_contentlengthz\{[\s\S]*\}_metareasoning_fallbackr   )r[   _BUREAU_DISABLEDr   r  r   r   r   r   r   r3   r   r6   r,  r[  r\  r]  _BUREAU_URLrv   rw   r^  _BUREAU_TIMEOUTr   rC  r   rE  r   rj   r   r   r  )rb   r   r   r   r   r   rl  rm  raw_dataerr_bodyr  rE  finish	reasoningr   s                  r   _bureau_handleDiaryHandler._bureau_handleY  sg   ((??-g  rA  $B  DG  H  H##%??G-P#QSVWW\\./??G-E#FLL	M::??3r7+Djj&G * 	<-=#.,v}.EFnn$$$$G$++G4(/uoGYZ	 % 
	Q''\5Q5Q'RVZ::diik2 S	,,y2$/266y"ECggi(.BG\\)bT21599/2NF 34:IMMOOv'9yIIni8ggajG;BY"9-i8EI)Jk\c\i\i\kXkElH,-AB x--U  	M??G/A!-E#FLL	M$ SR||%% 	\vvxwyA$3GH??G}QVVHBxj-Q#RTUTZTZ[[ 	Q??G/EaS-I#JCPP	Q"  		s   :K  ,L  %L-L  6DO  
L*L LL
LL  L   O >ANO O N;5O ;O 
OOc                   > U R                   S:X  d  U R                   S:X  a  SU l         U R                   S:X  a  SU l         U R                   S:X  a4  U R                  S5        U R                  SS	5        U R                  5         g U R                   R	                  S
5      (       a  U R                  5       (       a  g U R                   R	                  S5      (       a  U R                  5       (       a  g U R                   R	                  S5      (       a  U R                  5       (       a  g U R                   S:X  d@  U R                   R	                  S	5      (       d   U R                   R	                  S5      (       a  U R                  5         g U R                   R	                  S5      (       d   U R                   R	                  S5      (       a  U R                  5         g [        TU ]-  5       $ )N/z/diaryz/diary_blog.htmlz/image_gen.html
/image_genz/ledger.htmli.  Location/ledger//api/gba/api/garage/api/tomicar	   /ledger?/api)r.   rx   ry   r|   rY  r   r  rn  proxy_to_ledgerproxy_to_image_genr_   do_GET)rb   re   s    r   r  DiaryHandler.do_GET  si   99tyyH4*DI99))$DI99&s#Z499
++!!699..&&((&99..&&((&99	!TYY%9%9*%E%EI]I]^hIiIi  "99--1E1Ef1M1M##%w~r   c                 ^   SS K nUR                  R                  SU R                   S35        UR                  R	                  5         U R                  R                  S5      (       a  U R                  5       (       a  g U R                  R                  S5      (       a  U R                  5       (       a  g U R                  R                  S5      (       a  U R                  5       (       a  g U R                  S:X  a  U R                  5         g U R                  S:X  d@  U R                  R                  S	5      (       d   U R                  R                  S
5      (       a  U R                  5         g U R                  R                  S5      (       a  U R                  5         g U R                  S5        g )Nr   z[do_POST] path=rU  r  r  r  z/api/bureau/chatr	   r  r  r  rq  )r@  rA  r~   r.   rB  rY  r   r   ru  r  r  r  
send_error)rb   r@  s     r   do_POSTDiaryHandler.do_POST  s5   

?499+R893::;K;K;M99
++!!699..''))699..''))699**!99	!TYY%9%9*%E%EI]I]^hIiIi  "99''##%r   c                 .   U R                   R                  S5      (       a  U R                  5       (       a  g U R                   R                  S5      (       a  U R                  5       (       a  g U R                   R                  S5      (       a  U R	                  5       (       a  g U R                   R                  S5      (       a  U R                  5         g U R                   R                  S5      (       a  U R                  5         g U R                  S5        g )Nr  r  r  r	   r  rq  )r.   rY  r   r'  rx  r  r  r  rb   s    r   	do_DELETEDiaryHandler.do_DELETE  s    99
++!!699..))++V99..))++V99	**  "99''##%r   c                 z   U R                   nU(       a-  UR                  U5      (       a  U[        U5      S =(       d    SnSnU R                  R	                  S5      nU(       a%   U R
                  R                  [        U5      5      n0 nS H+  n	U R                  R	                  U	5      n
U
(       d  M'  XU	'   M-     [        R                  R                  XUS9n UR                  U R                  XVUS9  UR                  5       nU R                  UR                   5        UR#                  5        H,  u  pU	R%                  5       S;   a  M  U R'                  X5        M.     U R)                  5         U R*                  R-                  UR                  5       5         UR3                  5         g! [         a    Sn GN2f = f! [         a:  n U R/                  S[1        U5      5         SnANP! [         a      SnAN`f = fSnAff = f! [         a     gf = f!  UR3                  5         f ! [         a     f f = f= f)	zGGeneric reverse-proxy. Strips prefix_strip from path before forwarding.Nr  rt   )rr   rN  Acceptz
X-Passwordr   rP  )r   r   )ztransfer-encoding
connectionrW  )r.   rY  r{   r   r   r   r   r   r6   httpclientHTTPConnectionr\  r   getresponserx   r   
getheadersr  ry   r|   r}   r~   r  rz   close)rb   hostportprefix_striprQ  r.   r   r   hdrsr  r  connrm  r   s                 r   _proxy_to_upstreamDiaryHandler._proxy_to_upstream  s   yyDOOL99L)*+2sD\\./zzs2w/ TA  #AqQ U
 {{))$g)F	LLtLE##%Dt{{+)779 CC  & * JJTYY[)

7  (  	SV,, 	  

 s   &$F- B?G  H -F=<F= 
HG+&H +
G<5G?6H ;G<<G??HH 
HHH:H*)H:*
H74H:6H77H:c                    U R                   nUR                  S5      (       a  U R                  [        [        5      $ UR                  S5      (       Ga%  U[        S5      S  nSU 3n[        R                   R                  U5      (       a  U R                  S5        UR                  SS5      S   R                  5       nSSS	S
S.R                  US5      nU R                  SU5        U R                  S[        R                   R                  U5      5        U R                  5         [        US5       nU R                   R#                  UR%                  5       5        S S S 5        g U R'                  SS5        g UR                  S5      (       a  U R                  [        [        SS9$ U R'                  S5        g ! , (       d  f       g = f)Nr  z/image_gen/z/opt/data/image_gen/r   r   r   z
image/jpegz	image/pngz
image/webp)jpgjpegpngwebpzapplication/octet-streamrr   rt   r   r   zFile not foundr  r  )r.   rY  r  IMAGE_GEN_HOSTIMAGE_GEN_PORTr{   r-   r7   rx   r  r  r   ry   r  r|   rI   r}   r~   r   r  )rb   r.   r  
local_filer   ctyper?   s          r   r  DiaryHandler.proxy_to_image_gen  sv   yy??6""**>>JJ??=))C./0H/z:Jww~~j))""3'ooc1-b1779 ,l;`lmqqru  xR  S  7  !1277??:3NO  "*d+qJJ$$QVVX. ,OOC!12??<((**>>Xd*ee ,+s   3*F::
Gc                 <    U R                  [        [        [        S9$ )Nr  )r  LEDGER_HOSTr   LEDGER_PATH_PREFIXr  s    r   r  DiaryHandler.proxy_to_ledger  s    &&{KN`&aar   c                     SS K n UR                  R                  SU R                  5        SX-   S35        UR                  R	                  5         g ! [
         a     g f = f)Nr   [z] rU  )r@  rA  r~   log_date_time_stringrB  r6   )rb   formatrc   r@  s       r   rd  DiaryHandler.log_message  sY    	JJq!:!:!< =RbQRJJ 		s   AA 
A A )r.   )r   )ri   Z   )7rf  
__module____qualname____firstlineno__r`   rj   compiler   ro   r   r   r   r   r   r   r   r   r   r   r  r
  r  r6  rv  r  r  r  r  r  r   r'  r  r  rj  rn  ru  rx  rs  rr  rt  r  r  r  r  r  r  r  r  r  r  r  r  rd  __static_attributes____classcell__)re   s   @r   r[   r[      s   ? ZZ)N4 H@&>0	-4.4,
;;.;<n``G;RB;H;4k;Z %N><f63NjWr4">H-;^.;`);b KFKO( 6.p <2 (T2b r   r[   __main__c                       \ rS rSrSrSrSrg)ThreadingHTTPServeri*  zPMulti-threaded so identify's blocking vision call doesn't freeze other requests.Tr   N)rf  r  r  r  __doc__daemon_threadsr  r   r   r   r  r  *  s
    ^r   r  z0.0.0.0r   zDiary server running on port )Nr  http.serverr   r   socketserverr   r-   sockethttp.clientr  r3   r   r   rj   r   r   r0   	threadingr%  r&  emailemail.parserr   urllib.requestr[  urllib.errorPORTra   r  r  r  r   getenvr   r  rm   r   r   r   r   r   rT   RLockrS   r   r   rH   r  rG   r  r@  _sys_pr.   r   rl   insertr   r   r  r  ImportErrorr   r@   rL   rN   rQ   rY   r[   rf  chdirr  server
setsockopt
SOL_SOCKETSO_REUSEADDRprintserve_foreverr   r   r   <module>r     s   = ' f f f f f f f  $ #	 "))M*5g6 + >>+.88:  $"
 >>+.88: 33 "YY[  
PB	rww}}RWW\\"e-DEE		B Q'MT*,T.^+ ^@- zHHYnj  !)T!2LAF MMV..0C0CQG	)$
01
 a/  Ms   =H H*)H*