
    1j                    .   U d Z ddlmZ ddlZddlZddlmZ ddlmZ ddl	m
Z
mZmZmZmZ ddlmZ dZd	Zd
dddddddddddddZded<   dZ e e eej3                                ee      z              Z ed      d5d       Zd6dZd7dZd8dZd9d Zd:d!Z d;d"Z!	 	 	 	 	 	 	 	 	 	 d<d#Z"	 	 	 	 	 	 	 	 	 	 d=d$Z#d>d%Z$d?d&Z%d:d'Z&d@d(Z'	 	 	 	 	 	 	 	 	 	 dAd)Z(	 	 	 	 	 	 	 	 	 	 dAd*Z)dBd+Z*d,Z+dCd-Z,dd.	 	 	 	 	 	 	 dDd/Z-dEd0Z.d6d1Z/d2d3	 	 	 	 	 	 	 dFd4Z0y)Gui  W21B space_set_bg — frame/addon/life 3층 공간 세트 BG 파이프라인 (pure core).

~/tmp/geum_fp_frame_addon_exp.py 에서 14회 실험-육안-수정 반복으로 검증된 방식의
production 포팅 (2026-06-10 사용자 합의):

  · ANALYZE 3층: frame(경계·개구부·access path·construction_character)
                 / addon(씬 증거·기능 필수 핵심 가구 — FP 도면에 그려짐)
                 / life_baseline(인물 상황→환경 추론 고정물 — FP 미표기, BG 생성시만)
  · 실내: frame 2D(t2i) → addon i2i = ★최종 2D FP★ → VLM 좌표
          → 공간당 ★단일 번호 마킹★ 사본(PIL) → 그 사본을 base 로 BG i2i
          (★3D FP 단계 없음 — 실험 비교에서 손실 변환으로 판정, 제거 확정)
  · 옥외(open_air): FP 미경유 — 공간당 ★단 한 장★ T2I 기준 BG
                    (이후 모든 샷이 i2i 참조 변형하기 쉬운 일반 구도)
  · threshold = connector (plate 생략) / same_space = canonical 사진 참조
  · place_desc = whole/indoor/outdoor 분리 + 세계관 시각 정체성(문화권·시대 양식)
                 + visible_adjacent_enclosed_masses(반대 그룹 매스 외관) 반영
  · frame_check = VLM 진단(게이트 아님 — 사람 육안 반복이 완성 수단)

이 모듈은 pure core — 프롬프트 접근자 + 순수 함수만. 프롬프트 본문 15건은
2026-08-15 에 ``prompts/_base/space_set_bg/`` 팩으로 옮겼고(내용 무변경), 이 모듈은
그것을 지연 로드해 종전과 같은 상수 이름으로 내놓는다. LLM/VLM/이미지/DB I/O 는
provider(space_set_bg_provider) / step(space_set_bg_step) 몫.
★generic 절대 — 장소 종류·품목·시나리오 하드코딩 0 (SF 등 어떤 장소도 동일 작동).
★CLAUDE.md: LLM 입력(씬/시나리오 텍스트)은 절대 자르지 않는다.
    )annotationsN)OrderedDict)	lru_cache)AnyDictListOptionalTuple)capture_artifact   space_set_bganalyze_fa_sysplace_group_sysframe_fp_sys	addon_i2iframe_check_sysspace_pos_sysview_brief_sysmarked_fp_viewsamespace_viewindoor_bg_t2i_sysoutdoor_bg_t2i_sysshot_assign_sysderive_plate_view)ANALYZE_FA_SYSPLACE_GROUP_SYSFRAME_FP_SYS	ADDON_I2IFRAME_CHECK_SYSSPACE_POS_SYSVIEW_BRIEF_SYSMARKED_FP_VIEWSAMESPACE_VIEWINDOOR_BG_T2I_SYSOUTDOOR_BG_T2I_SYSSHOT_ASSIGN_SYSDERIVE_PLATE_VIEWDict[str, str]_PACK_STEMS)analyze_user_headview_briefs_user_tail)maxsizec                (    ddl m}  |t        |       S )uZ   팩에서 프롬프트 stem 을 읽고 프로세스 수명 동안 같은 객체로 캐시.r   )load_prompt)app.modules.prompt_loaderr.   PROMPT_PACK_MODULE)stemr.   s     W/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/space_set_bg.py_packr3   H   s     6)400    c                p    t         j                  |       }|t        dt        d|       t	        |      S )uP   상수처럼 보이는 프롬프트 이름을 팩에서 지연 로드 (PEP 562).zmodule z has no attribute )r)   getAttributeError__name__r3   )namer1   s     r2   __getattr__r:   O   s8    ??4 D|wxl2DTHMNN;r4   c                    ddl m} m} i }t        D ]q  } | t        |      j                  d      xs i j                  d      }|r|j                  d      st        t         d|       |d   }| |t        ||      d||<   s |S )u  소비하는 stem 각각의 (판 이름, 내용 해시) — 팩 신원.

    프롬프트가 코드에 있을 때는 코드 해시가 곧 프롬프트 신원이었다. 팩으로
    옮긴 뒤로는 ★파일이 실행 입력★ 이므로, 같은 코드로도 다른 프롬프트가 나갈
    수 있다 — 그래서 스텝의 config_hash·결과 기록이 이 신원을 접어야 한다.

    로더는 stem 마다 최신판을 ★따로★ 고른다(한 모듈 안에서 판이 섞일 수 있다).
    그래서 판 이름을 모듈 하나로 뭉뚱그리지 않고 stem 단위로 담는다. 내용 해시를
    같이 두는 이유는 판 이름이 그대로여도 파일이 바뀔 수 있기 때문이다.

    ★캐시하지 않는다★ — 캐시하면 파일이 바뀌어도 지문이 안 움직인다.
    stem 이 하나라도 없으면 예외(fail-closed): 없는 팩을 빈 신원으로 접으면
    "팩이 사라져도 지문이 그대로"인 구멍이 다시 생긴다.
    r   )get_effective_sourcepack_stem_content_hash
candidatesfileversionu     팩 stem 을 찾지 못했다: )r@   	sha256_16)r/   r<   r=   PACK_STEM_NAMESr0   r6   FileNotFoundError)r<   r=   identityr1   candr@   s         r2   pack_identityrF   W   s    
 +-H$%7>\")&(##f+ 	488I.#%&&FtfMO Oy//"GT3
   Or4   c                h    t        d      dz   | z   dz   dj                  d |D              z   dz   |z   S )uP   analyze user 프롬프트 — ★씬/전문 입력 절대 무삭제 (CLAUDE.md).r*   
u   

[원본 시나리오 전문 — 표제(슬러그라인) 아래 '/' 하위구분 = 별개 공간 강력 증거. 위 샷이 다루는 장면(scene z, c              3  2   K   | ]  }t        |        y w)N)str).0ns     r2   	<genexpr>z%build_analyze_user.<locals>.<genexpr>   s     /JqCFJs   u   )만 분석하고 다른 장소의 장면은 무시하라. ★예외: 이 장소로 들어오는 access path 의 증거만은 전문 어디서든 사용하라(그 경유 장소들을 spaces 로 추가하진 말 것).★]
)r3   join)shot_blocks_textfulltext
scene_numss      r2   build_analyze_userrR      sY     	!"T),<< 	`	`
))/J/
/	03n	n qy	yr4   c                R   t               }| D ]  }|j                  d      }||vrg |j                  d      g d||<   |j                  d      }|r!|||   d   vr||   d   j                  |       |j                  d      sv||   d   j                  |d           g }|j                         D ]d  \  }}dj	                  |d         }d	j	                  d
 |d   D              }|j                  d| d| d|j                  d      xs d d|        f dj	                  |      S )uj   scene 별로 묶은 LLM 입력 텍스트 (heading + 요약 + 그 scene 의 모든 shot 묘사, 무삭제).scenesummary)headingsrU   shotsheadingrV   	shot_descrW   z / rH   c              3  &   K   | ]	  }d |   yw)z  - N )rK   xs     r2   rM   zshot_blocks.<locals>.<genexpr>   s     9jD*js   z[scene ]    
장면요약:  u   
shot 묘사들:


)r   r6   appenditemsrN   )	rW   bysschblocksdheadsliness	            r2   shot_blocksrk      s-   -8]BUU7^R<"$y1ABOBrFEE)"R&,,rF:%%a(55rF7O""1[>2  FA

1Z=)		9aj99t2eW,<QUU9=M=SQS<TTghmgnop  ;;vr4   c                   | j                  d      xs g }|D cg c]  }|j                  d      r| }}g }g }| j                  d      xs g }|D cg c]1  }t        |j                  dd            j                         dk(  s0|3 }}t        |      t        |      kD  r)|j	                  dt        |       dt        |              |D ci c]>  }|j                  d	      t        |j                  d
      xs d      j                         @ }	}|D ]  }|j                  d      }
|
s|
|	v st        |j                  d
      xs d      j                         }|	|
   }|dk(  |dk(  k7  sY|j	                  d|j                  d	       d| d|
 d| d	        |D ]  }t        |j                  d
      xs d      j                         dk(  r3|j                  d      xs i }|j                  d      xs g }|j                  d      xs g }|s'|s%|j	                  d|j                  d	       d       |r|j	                  d|j                  d	       d        | j                  d      xs g D ]L  }|j                  d      du s|j	                  d|j                  d       d|j                  d       d       N | j                  d       xs g D ]8  }|j	                  d!|j                  d"       d|j                  d#       d       : ||t        |      t        |      d$| d%<   | S c c}w c c}w c c}w )&u{   deterministic — frame/addon 계약 + access path 검사 (의미추론 X, 카운트·존재만). 게이트 아님(진단).spacessame_space_asstructure_kindskindr_   separate_spaceu(   STRUCTURE SHRUNK: separate_space 증거 u    > 독립 spaces r9   	enclosureenclosedopen_airzCROSS-ENCLOSURE same_space: 'z'(u   ) ↔ ')	thresholdframeaccess_pathsopeningsu   NO ACCESS: 공간 'uA   ' 에 access_path/opening 둘 다 없음 (물리적으로 막힘)u6   access_path 명시 없음(opening 으로만 추정): ''access_validation
has_accessFz$access_validation has_access=false: spacez (noteuncertain_structure_itemszuncertain: itemreason)failswarningsindependent_spacesseparate_space_evidence_validation)r6   rJ   lowerlenra   )analysisroomsrindepr   warnsskrd   sepenc_byrefe1e2rw   apsopsavus                     r2   validate_far      s2   LL"(bE<1QUU?%;QE<EE	'	(	.BB
ObCfb 1288:>NN1bC
O
3x#e*?CzIZ[^_d[eZfghTYZTYqaeeFmS{!3!AzBHHJJTYFZeeO$3&=QUU;'5:6<<>BBj bJ&67<QUU6]O2bTQXY\X]]_`b_ccdef  quu[!'R(..0K?g$"ii'-2ii
#)r3LL.quuV}o=~  ALLQRSRWRWX^R_Q``abc  ll./52566,5(LL?w?PPRSUSYSYZ`SaRbbcde 6 \\56<"<{155=/AEE(O3DAFG = E!%jSXH] OE = P [s   L7L71L<L<AMc                    g }g }| j                  d      xs g D ]F  }t        |j                  d      xs d      j                         }|dk(  r|n|j                  |       H t	               }|r||d<   |r||d<   |S )u   spaces 를 enclosure 로 그룹핑. indoor=enclosed+threshold(connector 라 실내 FP 에 붙임), outdoor=open_air.
    ★generic: enclosure 미지정이면 enclosed 취급. indoor 가 항상 먼저(있으면).rm   rr   rs   rt   indooroutdoor)r6   rJ   r   ra   r   )r   r   r   rd   encgroupss         r2   enclosure_groupsr      s     $&F$&G\\(#)r)!%%$2
399;:%699!< * 8C}F!x#yMr4   c                   |D cg c]x  }|j                  d      |j                  d      |j                  d      |j                  d      xs g D cg c]%  }|j                  d      s|j                  d      ' c}dz }}}|D ch c]  }|j                  d       }}| j                  d      xs g D cg c]w  }|j                  d      |vrbt        |j                  d      xs d      j                         d	v r4|j                  d      r#|j                  d      |j                  d      d
y }}|rdt        j                  |dd      z   nd}	dt        j                  |d      z   d| dz   t        j                  |dd      z   dz   t        j                  | j                  d      xs g d      z   |	z   d| dz   S c c}w c c}}w c c}w c c}w )Nr9   rr   construction_characteraddonimplementation)r9   rr   constructionr   rm   r_   rs   rv   )r9   r   u   

visible_adjacent_enclosed_masses(이 그룹에서 외벽이 보이는 인접 구조물 — 외관만 반영, 내부 금지):
F   ensure_asciiindentu   [세계관]
r   u   

[대상 그룹 = u   ]
공간들:
z

physical_realism:
physical_realismu   

이 uX    그룹 공간만 일반 영어로 묘사하라(다른 환경 맥락 제외). 2~3문장.)r6   rJ   r   jsondumps)
r   guidegkeygspacesrd   apayloadgnamesmasses	mass_parts
             r2   build_place_group_userr      s      A	 EE&Mk0BEE":;89g8L"8Li8L!QRQVQVWgQhaee,-8Li	

    &--WaeeFmWF- ,,x(.B..A55=&k"(b)//15NNEE*+	 v6N0OP.   
 	M
**V%
:	; 
 	$**U??!$
7	8:>**W[`ij:k	l
#	$&*jj>P1Q1WUWfk&l	m 	 TFr
s		t# j	 .s+   AGG
)G
<G
G;A<G
Gc                   g }|D ]  }|j                  d      xs i }|j                  d      |j                  d      |j                  d      xs g |j                  d      xs g |j                  d      xs g d}|sG|j                  d      xs g D cg c]%  }|j                  d	      s|j                  d	      ' c}|d<   |j                  |        | j                  d
      | j                  d      | j                  d      xs g ||d}	|D ch c]  }|j                  d       }
}| j                  d      xs g D cg c]T  }|j                  d      |
vr?t        |j                  d      xs d      j                         dv r|j                  d      V }}|r||	d<   |rdnd}d|rdndz   dz   }|t	        j
                  |	dd      z   |z   S c c}w c c}w c c}w )Nrw   r9   rr   
boundariesry   rx   )r9   rr   r   ry   rx   r   r   
space_typegenre_or_settingr   )r   r   r   render_grouprm   rm   r_   r   +adjacent_enclosed_masses_outside_this_groupu5   [frame ONLY — 가구 절대 금지, 구조 셸만]
u-   [frame 우선 + addon 종속 — 한 번에]
u   

위로 ★흑백 2D 구조 평면도★ T2I 프롬프트(영어)를 작성하라. spaces 의 모든 공간·경계·개구부·★access_path(옥외/상부 진입경로 포함, 막히지 않게)★ 를 정확히 반영하라. u>   가구·설비·물체는 절대 그리지 마라(frame only).ud   frame 을 먼저 정확히 그리고 그 안에 각 공간 addon 만 심볼로(frame 변형 금지).uj    지정 그룹만(반대 그룹 매스는 빈 외벽 윤곽). 인물명·분위기·재질·치수 금지.Fr   r   )r6   ra   rJ   r   r   r   )r   rm   
group_kind
frame_only	fr_spacesrd   frr   r   r   r   oppheadtails                 r2   build_fp_userr     s    IUU7^!rEE&Mk0B&&.4"z*0bFF>28b	 
 ?@uuW~?SQS?Sp?SXYX]X]^nXoQUU#34?SpDM  ll<0hllSeFf$LL);<B"iG &,,VaeeFmVF, (X 6 <" < <!55=&k"(b)//15NN 	
f <  
 AD=>HRD  YID	n  L E	F w		w 	 $**W5CCdJJ1 q -s   G,GGAG"c                6    dt        j                  | d      z   S )Nun   이 평면도에서 다음 공간들 각각의 방 중심 정규화 좌표를 찾아 schema 로 출력하라: Fr   )r   r   )namess    r2   build_space_positions_userr   8  s;    {  C  I  I  JO  ^c  d  d  dr4   c           
     x   ddl m}m}m} |j	                  |       j                  d      }|j                  \  }	}
t        t        |	|
      dz        }t        ||	z        t        ||
z        }}|j                  |      }|j                  ||z
  ||z
  ||z   ||z   gdt        d|dz               t        d	t        |d
z              }	 |j                  d|      }	 |j                  ||ft!        |      d|d       |j#                  |       t	        |d      5 }t%        |j'                         ddd|i       ddd       y# t        $ r |j                         }Y w xY w# t        $ rB |j                  t        d||dz  z
        t        d||dz  z
        ft!        |      d       Y w xY w# 1 sw Y   yxY w)u   최종 2D FP 사본에 ★빨간 원 + 번호 1개만★ 마킹 (코드 이미지 처리, LLM 미사용).
    ★BG 한 장당 마킹 하나 — 모든 번호를 한 도면에 같이 찍지 않는다 (사용자 hard rule).r   )Image	ImageDraw	ImageFontRGBg?)   r   r   r      )outlinewidth   g?z#/System/Library/Fonts/Helvetica.ttcmm)fillfontanchorr   )r   rbspace_set_marked_fp
diagnosticnum)roledispositionpipeline_metadataN)PILr   r   r   openconvertsizeintminDrawellipsemaxtruetype	Exceptionload_defaulttextrJ   saver   read)fp_png_pathout_png_pathcxcyr   r   r   r   imwrf   r   r\   yrh   fsizer   _mfs                     r2   mark_fpr   <  s    0/	K	 	(	(	/B77DAqC1IArAv;BFqArAIIq1ua!eQUAE*Ks1aSTf~IVCCL!E(!!"GO]	1vs3xkTJ GGL 
lD	!SHHJ2$cl	
 
"	!  (%%'(  ]	Aq5A:~&Aq5A:~(>?SP[\]
 
"	!s1   	E !E"  F0EE"AF-,F-0F9c                   ddl }|j                  |       }g }|j                  dg       D ]  }|j                  d      xs g D cg c]%  }|j                  d      s|j                  d      ' c}|d<   |j                  d      r|j                  d      gng |d<   |j                  d	      xs i j                  d
      xs g D ]  }|j                  |         ||d
<   |S c c}w )u   frame/addon analysis → view brief 입력 형태(fixed_elements + defining_features + top-level openings)로 평면화.
    construction_character 는 defining_features 로(재질 정체성이 view 에 흘러가게).r   Nrm   r   r   fixed_elementsr   defining_featuresrw   ry   )copydeepcopyr6   ra   )r   r   r   flat_openingsrd   r\   os          r2   adapt_for_viewr   ]  s     hA*,MUU8R ABwAUSUAUrAU1Z[Z_Z_`pZqquu%56AUr
FGeeLdFe!%%(@"A!Bkm
55>'R,,Z8>B>A  # ? !
 "AjMH ss   C#C#c                h    dt        j                  t        |       dd      z   dz   t        d      z   S )Nu   공간 분석(JSON):
Fr   r   r`   r+   )r   r   r   r3   )r   s    r2   build_view_briefs_userr   l  s9     4::nX.FUZcd#ee
	01	2r4   c                   |j                  d      xs i }|j                  d      |j                  d      |j                  d      |j                  d      xs g |j                  d      xs g |j                  d      xs g |j                  d      xs g D cg c]%  }|j                  d	      s|j                  d	      ' c}|j                  d
      xs g D cg c]%  }|j                  d      s|j                  d      ' c}d| j                  d      xs g ||d}t        j                  |dd      dz   S c c}w c c}w )Nrw   r9   rr   r   r   ry   rx   r   r   life_baseliner   r9   rr   r   r   ry   rx   r   uk   life_baseline(이동하지 않는 배경 필수 물품 — 그 환경의 현실 수준으로 자연 배치)r   r}   r   placesuggested_viewFr   r   u  

위 실내 공간의 ★단 한 장 기준 배경 실사진★ T2I 프롬프트(영어)를 작성하라. 이후 샷들이 i2i 로 변형해 쓰는 유일한 원본이므로 입구를 등진 wide establishing 일반 구도로, 구조·고정물·구축 방식(재질 이질감)·life_baseline 을 정확히, place 의 시각 정체성을 충실히. 사건·표식·인물·글자 금지.r6   r   r   r   r}   r  	view_hintr   r   r\   r   s           r2   build_indoor_bg_userr    s@    
7		!rB IIf%EIIk4J&+ii0H&I&&.4""&&BTBZXZFF>28b8=		'8J8Pb8Pm8P!UVUZUZ[kUlaee,-8Pm(-		/(B(Hb(H|(H!QUUSY]f(H|	
 %LL);<B)G 	

7q9k	k n|   D>0D>E3Ec                   |j                  d      xs i }|j                  d      |j                  d      |j                  d      |j                  d      xs g |j                  d      xs g |j                  d      xs g |j                  d      xs g D cg c]%  }|j                  d	      s|j                  d	      ' c}|j                  d
      xs g D cg c]%  }|j                  d      s|j                  d      ' c}d| j                  d      xs g ||d}t        j                  |dd      dz   S c c}w c c}w )Nrw   r9   rr   r   r   ry   rx   r   r   r   r   r  r   r  Fr   r   uN  

위 옥외 공간의 ★단 한 장 기준 배경 실사진★ T2I 프롬프트(영어)를 작성하라. 이후 샷들이 i2i 로 변형해 쓰는 유일한 원본이므로 변형이 쉬운 일반적 wide establishing 구도로, 구조·고정물·구축 방식(재질 이질감)을 정확히. 사건·표식·인물·글자 금지.r  r  s           r2   build_outdoor_bg_userr    s@    
7		!rB IIf%EIIk4J&+ii0H&I&&.4""&&BTBZXZFF>28b8=		'8J8Pb8Pm8P!UVUZUZ[kUlaee,-8Pm(-		/(B(Hb(H|(H!QUUSY]f(H|	
 %LL);<B)G 	

7q9r	r n|r	  c                t    |rddj                  |      z   dz   nd}t        d      j                  | |||      S )ug   실내 마킹 FP → BG i2i 프롬프트. life_baseline(FP 미표기 고정물)은 여기서만 주입.u   ★Additionally, make the room realistically lived-in by including these non-movable items that belong in this place (NOT drawn in the plan): z; u    — true to the place's real condition and level, placed plausibly, WITHOUT altering any wall, door, window or the drawn furniture positions.★ r_   r   )r   r  viewextra)rN   r3   format)r   r  r  
life_itemsr  s        r2   build_marked_view_promptr    s\     
	$&*ii
&;	<?X	X 	 
 !"))u4u * 6 6r4   )
reuse_basederive_from_baseno_platec                   | j                  d      xs g D cg c]x  }|j                  d      |j                  d      |j                  d      |j                  d      xs g D cg c]%  }|j                  d      s|j                  d      ' c}dz }}}g }t               }|D ]  }|j                  d      }||vrQ|j                  |       |j                  d	| d
|j                  d      xs d d|j                  d      xs d        |j                  d| d|j                  d       d|j                  d      xs d         dt	        j
                  |dd      z   dz   dj                  |      z   dz   S c c}w c c}}w )u   shot→space 배정 user 프롬프트 — ★씬/샷 입력 절대 무삭제 (CLAUDE.md).
    shot_rows = step._scenes_to_shot_rows 출력 (scene/shot 인덱스 포함).rm   r9   rr   rn   r   r   )r9   rr   rn   r   rT   z
[scene r]   rX   r_   r^   rU   z  - (scene z, shot shotz) rY   uI   [이 장소의 공간 목록 — space 값은 이 name 들 중에서만]
Fr   r   uS   

[샷 목록 — 각 샷의 (scene, shot) 인덱스를 출력에 그대로 사용]rH   uq   

각 샷이 촬영되는 주 공간 하나를 증거로 배정해 schema 로 출력하라. 불확실하면 null.)r6   setaddra   r   r   rN   )	r   	shot_rowsrd   r   rm   rj   
seen_scener   re   s	            r2   build_shot_assign_userr    s    ,,x(.B. /A	 EE&Mk0BUU?389g8L"8Li8L!QRQVQVWgQhaee,-8Li	

 /   EeJUU7^ZNN2LL9RD155+;+Ar*BBRSTSXSXYbScSigiRjkl{2$gaeeFm_Bquu[?Q?WUW>XYZ  	U
**V%
:	;
a	b ))E
	 @		@ j	s   AF%F<FFF)connector_namesc          
     ,   i }g }|xs
 t               }| xs g D ]I  }|j                  d      |j                  d      }}|j                  d      }		 t        |      t        |      }}
|
 d| }|	s&|j                  d|||j                  d      d	       }|j                  d
      xs d}|t        vr|j                  d|||	|d       d}|dk(  r'|j                  d|||	|j                  d      d       d}|dk(  r=|j                  d      xs dj                         }|s|j                  d|||	d       d}|j                  |	      }| |	|v rdnd}|j                  ||||	d       V|j                  d      dk7  r|j                  d|||	d       ||v r|j                  d|||	d       |	|j                  d      |j                  d      |j                  d      d|
 d| |j                  d      xs d|d }|dk(  r-||d<   |j                  d      |d!<   |j                  d      |d"<   |j                  d#      r|j                  d#      |d#<   |||<   L ||fS # t        t        f$ r |j                  d|||	d       Y yw xY w)$u  배정 결과 × plate 산출의 ★순수 deterministic 조인★ (LLM/이미지 호출 없음).

    반환: (shot_plate_map, diagnostics)
      shot_plate_map["{scene}_{shot}"] = {space, plate_key, plate_png, plate_kind,
                                          shot_id, basis, plate_action[, secondary_spaces]
                                          [, derive_instruction, canonical_plate_key,
                                             canonical_plate_png]}
      diagnostics = 배정 불가 사유 기록 (게이트 아님 — 해당 shot 은 기존 경로 유지)
      connector_names = plate 를 의도적으로 생략한 threshold 공간 — 배정되면
      LLM 발명(unknown_space)과 구분해 connector_no_plate 로 기록 (운영 디버깅).

    Phase 3 plate_action (얇은 gate — 조인은 비치명 유지):
      · reuse_base(기본/미지정/enum 밖 값) = canonical plate 그대로.
      · derive_from_base = derive 메타만 기록하고 plate_png 는 ★canonical 유지★ —
        step 이 derive i2i 성공 시에만 derived png 로 교체 (실패 = canonical fallback).
      · no_plate = spm 제외 + 진단 (해당 shot 은 기존 경로 유지).
    rT   r  r}   invalid_index)r   rT   r  r}   _
unassignedbasis)r   rT   r  r!  plate_actionr  unknown_plate_action)r   rT   r  r}   r"  r  plate_action_no_plate)r   rT   r  r}   r!  r_   r  derive_instructionderive_missing_instructionconnector_no_plateunknown_spacestatusokplate_not_okduplicate_shotkeypngrp   S_Shot)r}   	plate_key	plate_png
plate_kindshot_idr!  r"  canonical_plate_keycanonical_plate_pngsecondary_spaces)r  r6   r   	TypeError
ValueErrorra   PLATE_ACTIONSstrip)assignmentsplatesr  spmdiags
connectorsr   re   shr}   scishir-  actionr%  plater   entrys                     r2   build_shot_plate_maprG    s   . &(C"$E )CEJBwvBg	2wBC QsenLLL2r#$55>3 4~&6,&LL$:RQS#(&B C!FZLL$;bRT#(155>C D''"#%%(<"="C!J!J!L%(Dr&(5: ;%

5!=-2j-@)oFLLFReTU99X$&LLNRV[\]#:LL$4r2X]^_5)5)))F+3%uSE*UU7^)r"!
 ''*<E&'+099U+;E'(+099U+;E'(55#$().@(AE$%Cq r :i :& 	LLOb"W\]^	s   I**%JJc                \    t        d      j                  | xs dj                               S )ua   derive_from_base i2i 프롬프트 — canonical plate 를 identity 참조로 카메라만 이동.r   r_   instruction)r3   r  r;  rI  s    r2   build_derive_plate_promptrK  J  s3     $%,, &B--/ - 1 1r4   c                r    t        j                  dd| xs dj                               j                  d      S )Nz
[^a-z0-9]+r  r_   )resubr   r;  )r9   s    r2   slugrO  R  s-    66-tzr&8&8&:;AA#FFr4   F)threshold_platec               v   | j                  dg       D ci c]'  }|j                  d      |j                  d      xs d) }}| j                  dg       D ci c]>  }|j                  d      t        |j                  d      xs d      j                         @ }}t        |       }t	        |      dkD  }i }|j                         D ]-  \  }	}
|rd|	 nd	}|
D ]  }|||j                  d      xs d	<    / |D cg c]+  }|s'|j                  |      d
k(  r|j                  |      s|- }}|D cg c]  }|j                  |      r||vs| }}|D cg c]  }|j                  |      s||vs| }}||z   }i }t        |      D ]N  \  }}t        |      xs d|dz    }||j                         v r| d|dz    }||j                         v r|||<   P g }t        |      D ]  \  }}|j                  |      }|j                  |      dk(  }|r||v rd}n|rd}nd}|j                  ||   |dz   ||j                  |d	      |||r|j                  |      nd|j                  |d	      |j                  |      d	        i }|D ])  }|d   dk(  s|j                  |d   d      dz   ||d   <   + |D ])  }|d   dk(  s|j                  |d   d      dk(  s%d|d<   + |D cg c]'  }||j                  |      d|j                  |      d) }}||fS c c}w c c}w c c}w c c}w c c}w c c}w )u  각 공간의 BG 생성 계획을 결정 (순수 로직 — 이미지/LLM 호출 없음).

    반환: (plans, connectors)
      plans[i] = {key, num, room, view, kind, ref_room, group_suffix}
        kind ∈ {"marked_indoor", "outdoor_t2i", "samespace_ref"}
        · canonical(독립) 공간 먼저, same_space 종속은 그 뒤 (참조 가능 순서)
      connectors = threshold(=connector) 공간 — plate 생략 (threshold_plate=False 시)
    rm   r9   rn   Nrr   rs      r  r_   rv   r}   rt   samespace_refoutdoor_t2imarked_indoor)	r-  r   roomr  rp   ref_roomref_keygroup_suffixrr   rp   rY  r   
indoor_t2izentry connector (no plate))rV  rr   r   r  )
r6   rJ   r   r   r   rb   	enumeraterO  valuesra   )r   briefsrP  r   samer   r   multispace_suffixgkgssfxrd   rnr@  r   depsorderedkeymapikplansr   is_outrp   marked_by_sfxp	conn_metas                               r2   plan_space_viewsro  V  s    FN\\RZ\^E_`E_AEE&MAEE/2:d:E_D`QYQ]Q]^fhjQk
lQkA155=#aeeK0>J?EEGGQkC
lh'FK!OE#%L,,.B"hRA03Lv,"-  !
 rSWWR[K%?QS 	V   !N&B:9MR&ENI2488B<Bj4HBDIdlGF7#2H'%Aw6==?"#Qq1ugA 6==?"r
	 $
 #%E7#2hhrl
*3&="D D"D":a!eRBPRAScfjjoRV(,,R43772;
 	 $" %'MV9'/</@/@>ARTU/VYZ/ZM!N+,  V9'M,=,=a>OQR,SWX,X$AfI 
 B #''"+7S]c]g]ghj]kl   )g a
l OIBs<   ,LAL";0L'1L,L,L,L1.L13L1+,L6)r1   rJ   returnrJ   )r9   rJ   rp  rJ   )rp  zDict[str, Dict[str, str]])rO   rJ   rP   rJ   rQ   z	List[int]rp  rJ   )rW   List[Dict[str, Any]]rp  rJ   )r   Dict[str, Any]rp  rr  )r   rr  rp  z('OrderedDict[str, List[Dict[str, Any]]]')
r   rr  r   rr  r   rJ   r   rq  rp  rJ   )
r   rr  rm   rq  r   rJ   r   boolrp  rJ   )r   	List[str]rp  rJ   )r   rJ   r   rJ   r   floatr   ru  r   r   rp  None)r   rr  rp  rJ   )
r   rr  r}   rr  r  rJ   r  rJ   rp  rJ   )
r   r   r  rJ   r  rJ   r  rt  rp  rJ   )r   rr  r  rq  rp  rJ   )r<  rq  r=  zDict[str, Dict[str, Any]]r  zOptional[set]rp  z6Tuple[Dict[str, Dict[str, Any]], List[Dict[str, Any]]])rJ  rJ   rp  rJ   )r   rr  r]  r(   rP  rs  rp  z1Tuple[List[Dict[str, Any]], List[Dict[str, Any]]])1__doc__
__future__r   r   rM  collectionsr   	functoolsr   typingr   r   r   r	   r
   app.services.image_capture.sinkr   SCHEMA_VERSIONr0   r)   __annotations___FRAGMENT_STEMStuplesortedr  r\  rB   r3   r:   rF   rR   rk   r   r   r   r   r   r   r   r   r  r  r  r:  r  rG  rK  rO  ro  r[   r4   r2   <module>r     sA  2 #  	 #  3 3 < $  '("($&&&,.(,^ " A s;#5#5#783;OOPQ 41 1!P(%P(!!! ! "	!
 	!V(K(K (K (K 	(K
 	(Kfd
B*  	
 	@  	
 	:
6" ?@ &*	S%S%S #	S
 <Sx1G "	AAA 	A
 7Ar4   