+
    *cIj[A                    R   R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	H
t
 ^ RIHtHtHtHtHtHt ]
! R4      t]R,          t]R,          R,          t]R	,          t]R
,          t]R,          tRtRt]R,          ],          R,          R,          ],          t]R,          ],          R,          ],          R,          t]P8                  ! ]4       ]! ]4      ]P<                  9  d#   ]P<                  P?                  ^ ]! ]4      4       ^ RI H!t! ^ RI"H#t# ^ RI$H%t% ^ RI&H't' ^ RI(H)t) ^ RI*H+t+ Rs,R t-R R lt.R R lt/R;R R llt0R R  lt1R;R! R" llt2R# R$ lt3R% R& lt4R' R( lt5R<R) R* llt6R+ R, lt7R=R- R. llt8R>R/ R0 llt9R?R1 R2 llt:R3 R4 lt;R5 R6 lt<R7 R8 lt=R9 R: lt>R# )@u  forest_exp 공용 라이브러리 — '전체 숲' 총괄 파이프라인 실험 (2026-07-04).

실험 전용(scratchpad, 커밋 금지). production 코드 차용(import) + 실데이터 읽기 전용.
- capture scope 미개방 → image_asset/체크포인트 write 0.
- 시나리오 중립: 이 파일의 어떤 템플릿에도 장소/인물 고유명사 없음.
  구체 내용은 전부 cp 데이터 문자열로 주입된다.
)annotationsN)Path)AnyDictListOptionalSequenceTuplez*/Users/manta/Documents/Projects/TheRoad-I1backend
scratchpad
forest_expoutplanszrunlog.jsonlz$b0ad5c18-5140-4022-a2b0-805de0e5a385z$0d30302a-51c8-4ac9-98e2-6cd9180512bfprojectscheckpointsepisodesimages)settings)call_structured)call_gpt_image_bytes)get_next_key)sanitize_for_safety)_gemini_generate_contentc                     \         f6   ^ RIHp  V ! \        P                  \        \        P                  4      R7      s \         # )NOpenAI)api_keytimeout)_openai_clientopenair   r   openai_api_keyfloatllm_timeout_image_genr   s    N/Users/manta/Documents/Projects/TheRoad-I1/scratchpad/forest_exp/forest_lib.pyopenai_clientr$   /   s4    !++(889
     c                    V ^8  d   QhRRRR/# )   eventDict[str, Any]returnNone )formats   "r#   __annotate__r.   <   s     > >. >T >r%   c           	     L   R \         P                  ! R4      /V Cp \        P                  P	                  RRR7       \        P                  R4      ;_uu_ 4       pVP                  \        P                  ! V RR7      R,           4       RRR4       R#   + '       g   i     R# ; i)	tsz%Y-%m-%d %H:%M:%STparentsexist_okaFensure_ascii
N)	timestrftimeRUNLOGparentmkdiropenwritejsondumps)r(   fs   & r#   runlogrB   <   sn    4==!45??E
MMt4	S		Q	

5u5<= 
			s   /BB#	c                    V ^8  d   QhRRRR/# )r'   stepstrr*   r)   r,   )r-   s   "r#   r.   r.   E   s     I IC IN Ir%   c                |    \         P                  ! \        V ,          R ,          P                  4       4      R,          # )zmanifest.jsondata)r?   loadsCP	read_text)rD   s   &r#   	load_steprK   E   s(    ::rDy?2==?@HHr%   c                    V ^8  d   QhRRRR/# )r'   overrideOptional[str]r*   r)   r,   )r-   s   "r#   r.   r.   I   s       . r%   c           	     f   \        R4      R,          p. pV EFF  pVP                  R4      ;'       g    .  Uu. uF,  p\        VP                  R4      \        4      '       g   K*  VNK.  	  ppV'       d   VP                  R4      '       g   Kv  \        ;QJ d    R V 4       F  '       g   K   RM	  RM! R V 4       4      p\        ;QJ d    R	 V 4       F  '       g   K   RM	  RM! R	 V 4       4      p\        R
 V 4       4      pV '       d   VR,          V 8X  d   / VCRVRV/Cu # EK  V'       g   EK   V'       g   EK+  VP                  / VCRVRV/CV34       EKI  	  V '       d   \        RV  24      hV'       g   \        R4      hVP                  R R7       V^ ,          ^ ,          # u upi )u   데이터 기준 그룹 선택: 실내+실외 멤버 공존(mixed) 그룹 중 총 shot_count 최대.

is_indoor 가 bool 이 아닌 멤버는 제외(구조 계약 — 추측 금지).
background_classifybuilding_groupsmembers	is_indoorgroup_idc              3  2   "   T F  qR ,          x  K  	  R# 5irS   Nr,   .0ms   & r#   	<genexpr>pick_group.<locals>.<genexpr>U   s     5W{^^Ws   TFc              3  <   "   T F  qR ,          '       * x  K  	  R# 5irV   r,   rW   s   & r#   rZ   r[   V   s     :'QK.(('s   c              3  j   "   T F)  p\        VP                  R 4      ;'       g    ^ 4      x  K+  	  R# 5i)
shot_countN)intgetrW   s   & r#   rZ   r[   W   s)     C7aCl+00q117s   !33_total_shotszgroup not found: z0no mixed indoor/outdoor group in building_groupsc                    V ^,          ) # )   r,   )xs   &r#   <lambda>pick_group.<locals>.<lambda>b   s
    adUr%   )key)	rK   r`   
isinstanceboolanysumappend
SystemExitsort)	rM   groupscandsgrY   rR   has_inhas_outtotals	   &        r#   
pick_groupru   I   si   
 ,-.?@FE uuY/5525 <5 {!3T: 15 <aeeJ//5W55W55#:':###:'::C7CC}(G!GYGG6ggLLJQJ	7NEJERS  ,XJ788KLL	JJ?J#8A;%<s   'F.$F.c               $    V ^8  d   QhRRRRRR/# )r'   scene_indexr_   
shot_indexr*   rE   r,   )r-   s   "r#   r.   r.   f   s!     . .# .3 .3 .r%   c                    R V  RV 2# )S_Shotr,   )rw   rx   s   &&r#   shot_keyr|   f   s    {m5--r%   c                    V ^8  d   QhRRRR/# )r'   group_overriderN   r*   r)   r,   )r-   s   "r#   r.   r.   j   s     U U U Ur%   c                
  a#a$ \        V 4      pVR,           Uu/ uF  q"R,          VbK  	  upo$\        R4      pVR,          pVP                  4        UUu/ uF  w  rVVP                  R4      S$9   g   K  WVbK!  	  ppp/ pVP                  4        FH  w  rVVP                  R4      ;'       g    .  F$  p	VP	                  V	. 4      P                  V4       K&  	  KJ  	  \        R4      R,          p
/ pV
 F  pVP                  R4      ;'       g    .  F]  pR	VP                  R	4      R
VP                  R
4      RVP                  R4      ;'       g    . /V\        VR,          VR,          4      &   K_  	  K  	  \        R4      R,           Uu/ uF  p\        VR,          VR,          4      VbK   	  pp/ p\        R4      R,           F]  p\        VR,          VP                  R4      4      pRVP                  R4      RVP                  R4      RVP                  R4      /VV&   K_  	  / p\        VP                  4       4       F  w  p	p\        V Uu0 uF  pVV,          R,          kK  	  up4      p\        ;QJ d    V$3R lV 4       F  '       d   K   RM	  RM! V$3R lV 4       4      pV	P                  R4      w  ppR\        VR,          4      R\        V4      RVRVR\        V4      /VP                  V	4      ;'       g    / CRVP                  V	4      RVP                  V	4      /CVV	&   K  	  \        R4      R,          p/ pVP                  4        F  w  rVVP                  R 4      ;'       g    .  F  o#VP                  S#4      ;'       g    / pVP                  R!4      R"8X  g   K6  VP                  R#4      '       g   KO  VP	                  VR,          . 4      p\        ;QJ d    V#3R$ lV 4       F  '       g   K   RM	  RM! V#3R$ lV 4       4      '       d   K  VP                  R%S#R#VR#,          /4       K  	  K  	  \        VP                  4        UUu0 uF>  qP                  R4      ;'       g    .  F  p\        V\        4      '       g   K  VkK  	  K@  	  upp4      p\        4       p\!        4       p \"        R&,          R'VR(,           R)2,          p!R(VR(,          R*VP                  R*4      RVR,          R+TRTR,TR-TR.TR/T R0V!P%                  4       '       d   \        V!4      /
p"V"# R1/
p"V"# u upi u uppi u upi u upi u uppi )2uX   그룹 → loc → bg → shot 조인 + fp/passport/베이스라인 자산 인벤토리.rR   loc_idbackground_master_planbackground_catalogapplies_to_shotsshot_extractscenesshotsscene_headingdescription
charactersrw   rx   shot_stagingscene_detail_shot_indext2i_variationsvisible_entitiesrender_prompt_cardc              3  D   <"   T F  pSV,          R ,          x  K  	  R# 5irV   r,   )rX   llocss   & r#   rZ   build_recon.<locals>.<genexpr>   s     >gQ,,gs    FTr{   :rc   NNloc_idsrS   bg_idsstagingdetailfloor_plan_renderfloor_plansdepends_on_fpstatusokpng_pathc              3  :   <"   T F  qR ,          S8H  x  K  	  R# 5i)fp_idNr,   )rX   rd   r   s   & r#   rZ   r      s     <1W:.s   r   background_chainaerial_rT   z.png
anchor_locbgs	fp_by_loccharacters_in_shots	passportscharacter_name_to_sidbaseline_aerialN)ru   rK   itemsr`   
setdefaultrl   r|   sortedallsplitr_   rj   valuesrh   rE   query_passportsquery_character_names	EP_IMAGESexists)%r~   grouprY   mpcatalogbide	group_bgsshot_to_bgssksedesc_by_keyscshsstaging_by_keydetail_by_keykr   bidsbr   rS   sishi	fp_renderr   rlstcchar_idsr   name_to_sidr   reconr   r   s%   &                                  @@r#   build_reconr   j   s   ~&E$))$45$4qhKN$45D	+	,B%&G&-mmoQoFCxD9PoIQ )+K//#%%*+11r1B""2r*11#6 2 $
 
>	"8	,B-/K&&/''R'B!8rvvm4bff\288bJKM!2B|4DEF (  >*733A 	=!1\?3Q63   02M'11R&}(=>bff%56'9 : "&&)=">
a 2 (*E;,,./D$?$Q)A,x00$?@C>g>CCC>g>>	((7#C3r"v;#c(wfTl	
 r"((b	
 ~))"-m''+	
b		 0  -.}=I13I//#UU?+11r1Ee$**AuuX$&155+<+<**1X;;s<<sss<<<<JJ
AjMJK 2 $ %,,. ..Q55;N;T;TRT;TQ$Q, q;Tq. . /H!I')K"44zAR@SSW7XXO 	E*%eii-5#yYxY?3I3I3K3K3/E L RVE Le 6 R$ @4.s/   T1T64T6$T<U
U
5U

U
c                   V ^8  d   QhRR/# r'   r*   zDict[str, str]r,   )r-   s   "r#   r.   r.      s       r%   c                    R\          R2p / \        P                  CRR/Cp\        P                  ! RRRRR	R
R	RV .	RRVRR7      P
                  p/ pVP                  4       P                  4        F  pRV9  d   K  VP                  R^4      w  rV\        V4      pVP                  4       '       g   \        V,          pVP                  4       '       g   Kf  \        V4      W5P                  4       &   K  	  V# )uI   캐릭터 short_id → primary reference PNG 절대경로 (DB read-only).zSELECT ec.short_id, ia.file_path FROM image_asset ia JOIN entity_canon ec ON ec.id::text=ia.entity_id::text WHERE ia.project_id='zl' AND ia.asset_type='reference' AND ec.entity_type='character' AND ia.is_primary=1 AND ia.status='generated'
PGPASSWORDtheroad_dev_2026psql-h	localhost-Utheroad-d-tAcTcapture_outputtextenvcheck|)
PROJECT_IDosenviron
subprocessrunstdoutstrip
splitlinesr   r   is_absoluteROOTr   rE   )sqlr   r   resultlinesidpathps           r#   r   r      s    	  *| ,W	W  ;RZZ
:'9
:C
..	{D)T9fcR$Ct f   F		&&(d?JJsA&	J}}tA88::"%a&F99; ) Mr%   c                   V ^8  d   QhRR/# r   r,   )r-   s   "r#   r.   r.      s      ~ r%   c                 j   R\          R2p / \        P                  CRR/Cp\        P                  ! RRRRR	R
R	RV .	RRVRR7      P
                  p/ pVP                  4       P                  4        F@  pRV9   g   K  VP                  R^4      w  rVVP                  4       W5P                  4       &   KB  	  V# )u-   캐릭터 이름 → short_id (DB read-only).z:SELECT name, short_id FROM entity_canon WHERE project_id='z' AND entity_type='character'r   r   r   r   r   r   r   r   r   Tr   r   )	r   r   r   r   r   r   r   r   rsplit)r   r   r   r   r   namer   s          r#   r   r      s    	'L(E	G  ;RZZ
:'9
:C
..	{D)T9fcR$Ct f   F		&&($;C+ID#&99;F::<  ) Mr%   c               $    V ^8  d   QhRRRRRR/# )r'   rw   r_   rx   r*   z	List[str]r,   )r-   s   "r#   r.   r.      s!       # ) r%   c                   R\          RV  RV R2p/ \        P                  CRR/Cp\        P                  ! RRR	R
RRRRV.	RRVRR7      P
                  p. pVP                  4       P                  4        F  p\        VP                  4       4      pVP                  4       '       g   \        VP                  4       ,          pVP                  4       '       g   Ke  VP                  \        V4      4       K  	  V# )u^   해당 (scene, shot)의 production 최종 스틸 경로들 (베이스라인 육안 비교용).zrSELECT ia.file_path FROM image_asset ia JOIN scene_still ss ON ss.id::text=ia.still_id::text WHERE ia.project_id='z/' AND ia.asset_type='scene' AND ss.scene_index=z AND ss.shot_index=z ORDER BY ia.created_at DESCr   r   r   r   r   r   r   r   r   Tr   )r   r   r   r   r   r   r   r   r   r   r   r   rl   rE   )rw   rx   r   r   r   pathsr   r   s   &&      r#   query_production_stillsr      s    	  *| ,)]*=j\ J&	&  ;RZZ
:'9
:C
..	{D)T9fcR$Ct f  E		&&(}}tzz|#A88::LLQ  ) Lr%   c               4    V ^8  d   QhRRRRRRRRRRR	R
RR/# )r'   step_tagrE   system_promptuser_promptr   response_schemar)   modeltemperaturer!   r*   r,   )r-   s   "r#   r.   r.   	  sA      # c  '03%3r%   c                   \         P                  ! 4       p\        WW#V RV//WR7      p\        V\        4      '       d   TM>\
        P                  ! V Uu. uF  qP                  R4      R8X  g   K  VNK  	  upRR7      p	\        RRR	V RVR
\        \         P                  ! 4       V,
          ^4      RVRV	/4       V# u upi )u`   call_structured 차용 — model 강제(project_config), 전체 원문 전달(자르기 금지).r   )project_configschema_namer   typer   Fr5   kindllmrD   dur_sr   r   )
r8   	monotonicr   rh   rE   r?   r@   r`   rB   round)
r   r   r   r   r   r   t0r   r   	user_texts
   &&&&&&    r#   r  r  	  s     
	B 7E"23F
  *+s;;;Kq55=F#:K;%BQI
FE68WeU4>>+b0!4]M9F G M	 	<s   B=1B=c                    V ^8  d   QhRRRR/# )r'   r   z
str | Pathr*   r)   r,   )r-   s   "r#   r.   r.     s     W Wz Wn Wr%   c                    \         P                  ! \        V 4      P                  4       4      P	                  R 4      pRRRRRV 2//# )asciir  	image_urlurlzdata:image/png;base64,)base64	b64encoder   
read_bytesdecode)r   b64s   & r#   png_data_urlr    sF    


4:002
3
:
:7
CCKu8Nse6T.UVVr%   c               8    V ^8  d   QhRRRRRRRRRRRR	R
RRR/# )r'   tagrE   promptrefszOptional[Sequence[str | Path]]sizequalityout_pathOptional[Path]max_attemptsr_   r*   r   r,   )r-   s   "r#   r.   r.      sJ     %< %< %<c %<)G %<%<-0%<$%<;>%<GK%<r%   c                   Vf   Q hVP                   P                  RRR7       VP                  4       '       d   \        RRRV RRR	\	        V4      /4       V# V'       d   R
MRpTp\
        P                  ! 4       p	Rp
\        ^V^,           4       F  p \        \        4       YxV'       d   V Uu. uF  p\	        V4      NK  	  upMRRRRVRVR^/RR7      pVP                  V4       \        RRRRRTRT RT;'       g    .  Uu. uF  p\	        V4      NK  	  upRVRVR\        \
        P                  ! 4       V	,
          ^4      R\        V4      R	\	        V4      RV/4       Vu # 	  \        RV  RV
 24      hu upi u upi   \         d=   pTp
\        RRRT RTR\	        T4      R,          /4       \        T4      p Rp?EK5  Rp?ii ; i)u   gpt-image-2 generate/edit (production call_gpt_image_bytes 차용).

capture scope 미개방 상태로 호출 → DB 기록 없음. moderation 류 실패 시
sanitize_for_safety 로 1회 재시도(bounded).
NTr1   r  imgr  skipr   r   editgenerater   zgpt-image-2r  r  n)moder  	ref_pathscall_kwargsedit_image_as_listenginer%  r  attemptr  bytesr  	img_errorerrorNi  Nzimg_gpt failed for : )r;   r<   r   rB   rE   r8   r  ranger   r$   write_bytesr  len	Exceptionr   RuntimeError)r  r  r  r  r  r  r  r%  currentr	  lastr*  r   pngexcs   &&&&&&&        r#   img_gptr9     s    OO$6uc68UCMRS6zDG		B $DL1,-	3&d48404a3q640d$mVT&a9#'C   %FE8]FD%QTdjjbj:jSVj:FDwt~~7G"7La1PSXuc(mXwP Q O .( ,SED6:
;;! 1 ;  	3DFKYSXd^- .)'2G		3s=   F/E9;F?FE>AF9
FG
0GG
c               4    V ^8  d   QhRRRRRRRRRRR	R
RR/# )r'   r  rE   r  labeled_refsz Sequence[Tuple[str, str | Path]]aspect_ratior  r  r  r_   r*   r   r,   )r-   s   "r#   r.   r.   H  sB     < < <c <1Q <<2@<<&*<r%   c                   Vf   Q hVP                   P                  RRR7       VP                  4       '       d   \        RRRV RRR	\	        V4      /4       V# V UUu. uF   w  rgV\        V4      P                  4       3NK"  	  pppTp	\        P                  ! 4       p
Rp\        ^V^,           4       F  p \        \        4       \        P                  WVR
7      pVP                  V4       \        RRRRRRRT RV UUu. uF  w  r\	        V4      NK  	  uppRV UUu. uF  w  rVNK	  	  uppRVR\        \        P                  ! 4       V
,
          ^4      R\!        V4      R	\	        V4      RV	/4       Vu # 	  \'        RV  RV 24      hu uppi u uppi u uppi   \"         d=   pTp\        RRRT RTR\	        T4      R,          /4       \%        T	4      p	 Rp?EK-  Rp?ii ; i)uR   nb2(gemini image) i2i 차용 — 라벨드 ref 멀티 입력, 최종/임시샷용.NTr1   r  r   r  r!  r   r   )r<  r)  nb2r%  i2ir  
ref_labelsr*  r  r+  r  r,  r-  r.  zimg_nb2 failed for r/  )r;   r<   r   rB   rE   r   r  r8   r  r0  r   r   r   gemini_image_modelr1  r  r2  r3  r   r4  )r  r  r;  r<  r  r  labelr   inputsr5  r	  r6  r*  r7  _r   r8  s   &&&&&&           r#   img_nb2rE  H  s    OO$6uc68UCMRS<HILud1g((*+LFIG		B $DL1,-	3* ; ;W)C   %FE8UFE5#=SV= ">1">wt~~7G"7La1PSXuc(mXw	P Q
 O .$ ,SED6:
;;- J >">  	3DFKYSXd^- .)'2G		3s>   &F6A	F?F	FF"+A
FFG$(0GG$c               (    V ^8  d   QhRRRRRRRR/# )r'   r   rE   r   r)   include_stagingri   r*   r,   )r-   s   "r#   r.   r.   l  s(      3 > D C r%   c           
        RT  RVR,          '       d   RMR RRP                  VR,          4       R	VP                  R
4       R2	.pVP                  RVP                  R4       24       VP                  R4      '       d+   VP                  RRP                  VR,          4       24       VP                  R4      ;'       g    / pV'       d   V'       d   VP                  RVP                  R4       24       VP                  RVP                  R4       24       VP                  R4      ;'       g    . pV'       d/   VP                  R\        P                  ! VRR7      ,           4       VP                  R4      ;'       g    / pV'       d/   VP                  R\        P                  ! VRR7      ,           4       RP                  V4      # )uG   한 샷의 내용을 LLM 입력 텍스트 블록으로 (전체 원문).[z] (rS   indooroutdoorz; loc=,r   z; scene_heading=r   )zdescription: r   r   zcharacters: , r   zframing_scale: framing_scalezcamera_direction: camera_directionkey_bg_elementszkey_bg_elements: Fr5   frame_spatial_contractzframe_spatial_contract: r7   )joinr`   rl   r?   r@   )r   r   rG  linesstkbefscs   &&&    r#   
shot_blockrX  l  s|   CAkNN	B CHHQy\*++;AEE/<R;SSTV WE	LL=}!5 678uu\|DIIao$>#?@A	
y			RB2rvvo'>&?@A)"&&1C*D)EFGff&'--2LL,tzz#E/RRSff-.44"LL3::c>? @99Ur%   c                    V ^8  d   QhRRRR/# )r'   rR   zList[Dict[str, Any]]r*   rE   r,   )r-   s   "r#   r.   r.     s      / C r%   c                   . pV  Fh  pVP                  R 4      '       d   RMRpVP                  RV RVP                  R4       RVP                  R4       RVP                  R	4       24       Kj  	  R
P                  V4      # )rS   rJ  rK  z- (rN  r   z) rB  r/  summaryr7   )r`   rl   rS  )rR   rT  rY   r  s   &   r#   members_blockr\    sx    E%%,,h)s3%r!%%/!2"QUU7^4DBquuYGWFXYZ  99Ur%   c                   V ^8  d   QhRR/# )r'   r*   r)   r,   )r-   s   "r#   r.   r.     s     : :N :r%   c                 `    \         P                  ! \        R ,          P                  4       4      # )z
recon.jsonr?   rH   PLANSrJ   r,   r%   r#   
load_reconra    s    ::u|+66899r%   c                    V ^8  d   QhRRRR/# )r'   r   rE   r*   r)   r,   )r-   s   "r#   r.   r.     s     < <C <N <r%   c                f    \         P                  ! \        V  R 2,          P                  4       4      # ).jsonr_  )r   s   &r#   	load_planre    s%    ::u$u~-88:;;r%   c               $    V ^8  d   QhRRRRRR/# )r'   r   rE   rG   r)   r*   r   r,   )r-   s   "r#   r.   r.     s!      C ~ $ r%   c                    \         P                  R R R7       \         V  R2,          pVP                  \        P                  ! VR^R7      4       V# )Tr1   rd  F)r6   indent)r`  r<   
write_textr?   r@   )r   rG   r   s   && r#   	save_planrj    s@    	KKtK,4&ALLDuQ?@Hr%   )N)gptg?)N1536x864highNr'   )z16:9Nr'   )T)?__doc__
__future__r   r  r?   r   r   sysr8   pathlibr   typingr   r   r   r   r   r	   r   BACKENDEXPOUTr`  r:   r   
EPISODE_IDrI   r   chdirrE   r   insertapp.core.configr   app.modules.llm.llm_clientr   #app.modules.llm.gpt_image_primitiver   app.modules.llm.gemini_key_poolr   app.modules.llm.safetyr   app.modules.gemini_i2i_editorr   r   r$   rB   rK   ru   r|   r   r   r   r   r  r  r9  rE  rX  r\  ra  re  rj  r,   r%   r#   <module>r     s]   #   	  
   = =89


\L(	Ekg	~	 4
3
	J#m3j@:M:
*Z7*DxO	  w<sxxHHOOAs7|$ $ 6 D 8 6 B>I:.Up4&4$W
%<P<H*:<r%   