
    XYjb              	         U d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	m
Z
mZmZmZmZ ddlmZmZ  ej$                  e      Z ej*                  d      ZdZdd	d
dddddZded<   ddlmZmZ d$dZd%dZ  e!h d      Z"d&dZ# G d de$      Z%	 	 d'	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d(dZ&dd	 	 	 	 	 	 	 d)dZ'	 	 	 	 	 	 d*dZ(d+dZ)dddd ejT                  ddd!d"	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d,d#Z+y)-u
  floor_plan_prompt — Phase 7 Step 3 / Phase 8 v2 / D6 v4 schema.

도면별 detailed t2i prompt 생성. plot-critical 시각 요소(커튼/시체 가림 등)를
LLM이 해당 도면 spec + scene segments에서 자연히 picking하도록.

Phase 8 v2: schema에 ``numbered_elements`` + ``camera_recommendations`` 추가.
- numbered_elements: 도면 안 가구/문/창/소품/영역 라벨에 부여한 번호 마커 매핑.
- camera_recommendations: 각 background bg_id에 대한 도면 좌표계 기준 카메라 추천.

D6 v4: BG_ID_RE (`^L\d{2,3}B\d{2,3}$`) 정합 + 3-layer 방어.
- L1 static schema: ``bg_id`` pattern → BG_ID_RE. 옛 lowercase 패턴 (`display`,
  `bg_a`, hash garbage) 구조적 reject.
- L2 runtime enum injection: ``run_floor_plan_prompt`` 가 ``expected_bg_ids`` 비
  어있지 않으면 schema deep-copy + ``bg_id.enum=sorted(expected_bg_ids)`` 주입.
  LLM 출력을 정확한 set 으로 강제.
- L3 validator exact-set: ``_validate_fp_prompt_extras`` 가 cam_bg ==
  expected_bg_ids (extras + missing + duplicate) 검사. silent fallback 회로 차단.

추가: ``build_fp_user_prompt`` 가 D6 raw intent 의 ``state_label_raw`` 필드
사용 (legacy ``state_label`` fallback 유지). ``Valid bg_ids:`` block 으로 LLM
에게 정확한 ID list 명시.
    )annotationsN)AnyCallableDictIterableListOptionalSet)load_promptload_schemau,   [ㄱ-ㆎ가-힣一-鿿㐀-䶿豈-﫿぀-ヿ]floor_plan_promptz5.202605201406z6.202605262300z7.202605291624z8.202606011200z9.202606011400z10.202607081930z11.202607170145)567891011zDict[str, str]PROMPT_VERSION_MAP)"SCALE_GUIDELINE_FP_PROMPT_VERSIONS V6_COMPATIBLE_FP_PROMPT_VERSIONSc                v    | xs g D cg c]%  }|j                  d      r|j                  dd      ' c}S c c}w )u   이 fp 를 참조하는 background 들의 surface_role 을 그대로 모아 반환.

    dedup 없이 bg 순서대로 (count 신호 보존). 빈 리스트면 분류 정보 없음 —
    prompt 가 보수적으로 처리한다. 코드 의미판단 0.
    surface_role )get)applied_backgroundsbs     \/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/floor_plan_prompt.pyderive_fp_space_rolesr   b   sH     &+++A55  	
nb!+  s   *6c                    | xs g D cg c]  }|s|	 }}|s	 yddl m} dj                  d t         ||      j	                               D              }d| dS c c}w )uY   user prompt 의 {space_type_block} 채움 (selector "8" 전용) — role 신호 relay만.zNo surface-role classification is available for this floor plan's backgrounds. Treat the space type as unknown: draw conservatively from the scope and scenes, and do NOT assume an enclosed multi-room dwelling.r   )Counter, c              3  0   K   | ]  \  }}| d |   yw)z xN ).0rolens      r   	<genexpr>z*_build_space_type_block.<locals>.<genexpr>y   s!     T5S'$$r!~5Ss   zUAn upstream model classified this floor plan's backgrounds with these surface roles: zs. Decide the diagram form from these roles together with the scope and scenes (see the Space-type adaptation rule).)collectionsr!   joinsorteditems)space_rolesrrolesr!   listeds        r   _build_space_type_blockr1   o   s{    $**1*1qQ*E1S	

 $YYTVGEN<P<P<R5STTF	 A	A 2s
   AA>   base_openingbase_structural_unitstate_overlay_plot_cuebase_persistent_fixturebase_persistent_furniturestate_overlay_transient_objectc           	     v    	 t         |    S # t        $ r$}t        d| dt        t                      |d}~ww xY w)u   selector ("5"|"6") → on-disk pack directory name.

    Raises ValueError on unknown selector so a typo cannot silently route to
    the latest pack (the entire point of the W19A versioned-load surface).
    z#unknown floor_plan_prompt selector z; allowed: N)r   KeyError
ValueErrorr+   )selectorexcs     r   resolve_prompt_versionr=      sQ    !(++ 1( >1235
 	s    	838c                      e Zd ZdZy)FloorPlanPromptErroru   retry 한도까지 실패.N)__name__
__module____qualname____doc__r$       r   r?   r?      s    $rD   r?   r   c                   t        t        dt        |            }|D cg c]&  }|j                  d      s|j                  dd      ( }	}|	rdj	                  d |	D              nd}
g }|D ]  }|j                  dd      xs d}|j                  d	d      }|j                  d
      xs |j                  d      xs d}|j                  d      xs g }|rdj	                  |      nd}|j                  d| d| d| d|         dj	                  |      xs d}dj	                  d |D              xs d}g }|D ]  }|j                  d      }|j                  d      xs dj                         }|j                  d      xs dj                         }|j                  |rd| d| nd|        |j                  |       |j                  d        dj	                  |      j                         xs d}|t        |      nd}|j                  d|      j                  d| j                  dd            j                  d| j                  dd            j                  d| j                  dd            j                  d|
      j                  d |      j                  d!|      j                  d"|      j                  d#|xs d      S c c}w )$u  도면 user prompt — scene text 무절단 inject.

    .replace() 사용 (.format() brace 충돌 회피, T4/T7과 동일 패턴).

    D6 v4:
    - ``Valid bg_ids:`` block 에 정확한 L##B## list inject (LLM 이 schema
      pattern 으로 추측하지 않게 명시적으로 보여줌).
    - bg detail line 은 ``state_label_raw`` (D6 raw intent 필드) 우선, 없으면
      legacy ``state_label`` fallback.

    W19A: ``prompt_version`` selector ("5"|"6") 를 받아 versioned-load
    surface (prompt_loader) 로 pack 디렉토리를 pin. v5/v6 의 user_template
    이 placeholder 동일하므로 동작 차이는 없지만, latest-pack auto-resolve
    silent shift 를 방지하기 위해 항상 pin 한다.
    user_templateversionbg_idr   
c              3  &   K   | ]	  }d |   ywz- Nr$   )r%   rI   s     r   r(   z'build_fp_user_prompt.<locals>.<genexpr>   s     9L5Bug,L   z(none)z(unassigned)loc_idstate_label_rawstate_labelapplies_to_shotsr"   z	- bg_id: z
  loc_id: z

  state: z
  applies_to_shots: c              3  &   K   | ]	  }d |   ywrL   r$   )r%   ss     r   r(   z'build_fp_user_prompt.<locals>.<genexpr>   s     <mbHmrM   scene_indexheadingtextz
### Scene u    — z{space_type_block}z{fp_id}fp_idz{sub_location}sub_locationz{scope}scopez{valid_bg_ids_block}z{backgrounds_block}z{shots_block}z{scene_segments_block}z{visual_world_rules})
r   _MODULEr=   r   r*   appendstriprstripr1   replace)fp_specr   applied_shotsscene_segmentsvisual_world_rulesprompt_versionr-   templater   valid_bg_idsvalid_bg_ids_blockbg_linesrI   rN   stateshots	shots_strbg_blockshots_block	seg_linessegsirU   rV   	seg_blockspace_type_blocks                             r   build_fp_user_promptrr      s   0 *@*PH
 1DV0C1quuW~AEE'2&0CLV=I		9L99x  H gr"4nx$'(FAEE-,@FB()/R(-DIIe$8	w  !w ##,+/	
 ! yy".hH))<m<<HKIWW]#779%+224%2,,./6jE'+j<M	
 	  		)$++-9I
 1<0G,R 
 		%'7	8	GKK4	5	!7;;~r#B	C	GKK4	5	');	<	&	1	+	.	)9	5	');)Gx	HU Ws
   KK)rc   c                   | j                  d      xs g }|D cg c]  }d|v st        |d          }}t        |      t        t        |            k7  rt	        d|       t        d |D              rt	        d|       |t        v r~t        |      D ]p  \  }}t        |t              st	        d| d      |j                  d      }|t	        d| d
      |t        vsPt	        d| d|dt        t               d       |t        v rM| j                  d      }	t        |	t              rt        |	j                               dk  rt	        d|	dd      | j                  d      xs g }
t        |
      D ]%  \  }}|j                  d      rt	        d| d       |
D cg c]  }|d   	 }}t        |D ch c]  }|j                  |      dkD  s| c}      }|rt	        d|       t        |      }t        |      }||z
  }|rt	        dt        |             ||z
  }|rt	        dt        |             |t        v r|D ch c]  }d|v st        |d          }}|D ch c]W  }t        |t              rEd|v rAt        |j                  d      t              r"|d   j!                  d      rt        |d         Y }}t               }t        |
      D ]1  \  }}t        |t              st	        d| d      dD ]  }|j                  |      }t        |t"              st	        d| d| d      t        |      D ]=  \  }}t        |t$              st        |t              r't	        d| d| d| d|d	        t        |d          }t        |d!         }||z
  }|rt	        d| d"t        |             ||z
  }|rt	        d| d#t        |             ||z  } | rt	        d| d$t        |              |||z  z  }4 ||z
  }!|!rt	        d%t        |!             y	y	c c}w c c}w c c}w c c}w c c}w )&u  v2/v4 신규 필드 검증 — D6 exact-set 계약.

    - ``numbered_elements[].number`` 가 unique integer ≥1 인지 확인.
    - ``camera_recommendations[].bg_id`` 가 ``expected_bg_ids`` 와 **정확히 일치**
      (extras + missing + duplicate 모두 fail-fast).

    D6 v4 변경 (이전 subset → exact-set):
        v3 까지는 ``cam_bg - expected_bg_ids`` (extras) 만 검사 → LLM 이
        cam=[] 또는 partial set 반환해도 통과 (silent fallback). v4 는 system
        prompt rule 10 의 "for every bg_id ... emit one entry" 를 코드로 강제.
        empty cam_bg + non-empty expected → missing fail-fast.

    schema 기반 type/required validation은 ``call_structured`` 가 처리하므로
    여기서는 cross-field invariant(번호 중복, bg_id exact set, duplicate)만 본다.

    W19A version-aware (selector "5"|"6"):
        ``prompt_version="5"`` (default) — v5 동작 verbatim 유지. 추가
        검증 없음.
        ``prompt_version="6"`` — 모든 ``numbered_elements`` entry 가
        ``base_layer_decision`` (W19 brief §2.2 의 6-value enum 중 하나) 을
        carry 해야 한다. exact-set membership (regex / substring 없음).
        on-disk v6 schema.json 도 동일 enum 을 LLM 호출 단계에서 강제하지만,
        본 validator 는 mocked / strict-off provider 안전망으로 다시 점검.

    W19A2 (selector "6") — per-bg marker applicability:
        v6 의 모든 ``camera_recommendations`` entry 는
        ``use_numbered_elements`` / ``ignore_numbered_elements`` (integer 배열)
        을 carry 해야 한다.
        - 두 배열 모두 list 이고 각 원소는 ``numbered_elements[].number`` 의
          set 안에 있어야 한다 (exact integer join — natural language /
          substring 추론 금지).
        - ``use_numbered_elements ∩ ignore_numbered_elements`` 는 공집합.
        - 모든 ``state_overlay_*`` numbered_element 는 적어도 하나의
          camera 의 ``use_numbered_elements`` 에 등장해야 한다. 어떤 bg
          에서도 ``use`` 에 등장하지 않으면 fail (overlay leak 후보).
        - v5 path 는 이 검증을 적용하지 않음 (use/ignore 필드 자체가 v5
          schema 에 없음 — verbatim 보존).
    numbered_elementsnumberz$duplicate numbered_elements.number: c              3  &   K   | ]	  }|d k    yw)   Nr$   )r%   r'   s     r   r(   z-_validate_fp_prompt_extras.<locals>.<genexpr>'  s     
$Q1q5$rM   z&numbered_elements.number must be >=1: znumbered_elements[z] not a dict under v6base_layer_decisionNz&] missing base_layer_decision under v6z] invalid base_layer_decision z under v6 (allowed: )scale_guideline   zLscale_guideline missing or too short under v10 (need string >=30 chars, got z.80camera_recommendationsrI   zcamera_recommendations[z] missing bg_idrw   z,camera_recommendations has duplicate bg_id: z2camera_recommendations references unknown bg_ids: z0camera_recommendations missing required bg_ids: state_overlay_)use_numbered_elementsignore_numbered_elementsz].z missing or not a list under v6[z"] is not an integer under v6 (got r~   r   z=].use_numbered_elements references unknown numbers under v6: z@].ignore_numbered_elements references unknown numbers under v6: uI   ] use_numbered_elements ∩ ignore_numbered_elements non-empty under v6: zstate_overlay numbered_elements not covered by any camera_recommendations.use_numbered_elements under v6 (overlay leak candidate): )r   intlensetr?   any_V6_COMPATIBLE_SELECTORS	enumerate
isinstancedict_V6_BASE_LAYER_DECISION_ENUMr+   _SCALE_GUIDELINE_SELECTORSstrr\   count
startswithlistbool)"	fp_resultexpected_bg_idsrc   nums_rawenumsidxentrydecision	guidelinecams_rawccam_bg_listbgdupscam_bgexpectedextramissingvalid_numbersstate_overlay_numberscovered_state_overlaycam_idxcamfieldarritem_idxitemuse_set
ignore_setunknown_useunknown_ignoreoverlapmissing_state_overlays"                                     r   _validate_fp_prompt_extrasr      sh   X }}017RH&.@h(a-C(hD@
4yCD	N""24&9
 	
 
$
"4TF;
 	
 11#H-JCeT**(-BC  yy!67H*(-ST  ;;*(-Kl"6:;<A?  .  33 MM"34	)S)S1B-Cb-H&009C/@C  }}56<"H H%QuuW~&)#o>  &
 (00x!1W:xK0I"{/@/@/Dq/H2IJD":4&A
 	
 F?#HXE"@P
 	
 G">vg>OP
 	
 11&.#
&.(a-C(h 	 #
 '/+
&.!T"A155!67='(334DE	 (h 	 +
 +.%%h/LGSc4(*-gY6KL  Oggen!#t,.1'"UG D. /  '0nNHd!$-Zc5J25gYbq
 S??ChaI  '5 O #567GS!;<=J!M1K*-gY 7<<B;<O;PR  (-7N*-gY 7<<B><R;SU  
*G*-gY 7Dg') 
 "W/D%DD!Q 0R !68M M &--34I-J,KM  !m 2} AV 1I$#
+
s/   	P>P>+QQQ	Q Q6AQc                   t        j                  |       }|j                  di       j                  di       j                  di       j                  di       }d|v r%d|d   j                  dd      t        |      d|d<   |S )	u  schema deep-copy + ``camera_recommendations[].bg_id.enum`` 주입.

    D6 v4 L2 방어: LLM 출력을 정확한 set 으로 강제 (schema-level constraint).
    base_schema 는 매 호출마다 ``load_schema`` 가 새로 반환하지만, LRU 캐시 등
    공유 가능성 + 같은 process 안 반복 호출 방어를 위해 deep-copy.

    Returns: 새 schema dict (mutation-safe).
    
propertiesr|   r,   rI   stringpatternz^L\d{2,3}B\d{2,3}$)typer   enum)copydeepcopyr   r   )base_schemabg_idsschema	cam_propss       r   _inject_bg_id_enumr     s     ]];'F

<$	%r	*	Wb		\2		  )  )--i9NOL
	'
 MrD   c                0   | j                  d      |k7  r t        d| j                  d      d|      | j                  d      xs d}t        j                  |      rt        d| d      t	        |      dk  rt        d| d	t	        |       d
      y)z3 invariants:
    1. fp_id matches expected
    2. t2i_prompt no non-ASCII (Korean/Hanja/kana)
    3. t2i_prompt >= 30 chars
    rW   zfp_id mismatch: got z, expected 
t2i_promptr   floor_plan_prompt z t2i contains non-ASCII textr{   z t2i too short (ry   N)r   r:   _NON_ASCII_TEXT_REsearchr   )outputexpected_fp_idt2is      r   validate_fp_prompt_outputr     s     zz'n,"6::g#6"9^DVW
 	
 **\
"
(bC  %  00LM
 	
 3x"}  00@S
!L
 	
 rD      g       @r   )project_configopik_metadatamax_retriesbackoff_base_secsleep_fnr   rc   creator_corrections_blockc           
     8   t        |	      }t        t        d|      }||
xs dz  }t        t        d|      }d}|t	        |      n	t	               }|rt        |t        |            }nt        j                  |      }t        |      D ]1  }	  |d|| ||d|      }t        ||       |t        |||	       |c S  t        d| d| d|       # t        $ r>}|}t        j                  d	||d
z   ||       |d
z   |k  r |||d
z   z         Y d}~d}~ww xY w)u  LLM 호출 후 schema-validated dict 반환.

    Phase 8 v2: ``expected_bg_ids`` 가 주어지면 ``camera_recommendations[].bg_id``
    가 그 집합의 부분집합인지 추가 검증한다(``_validate_fp_prompt_extras``).
    None이면 cross-field 검증만 skip하고 결과는 dict 그대로 반환.

    D6 v4: ``expected_bg_ids`` 비어있지 않으면 schema deep-copy 후 ``bg_id.enum
    = sorted(expected_bg_ids)`` 주입 (L2 방어). validator 는 exact-set 검사
    (L3 방어). schema 는 매 호출 deep-copy 되므로 호출 간 mutation 누수 없음.

    W19A: ``prompt_version`` ("5" default | "6" opt-in) 가 prompt_loader 의
    versioned-load surface 로 plumbing 된다. v5 selector 는 5.202605201406
    pack 을 정확히 pin 하고 validator 도 v5 동작 verbatim 유지. v6 selector
    는 6.202605262300 pack 을 pin 하고 validator 가 모든
    ``numbered_elements`` entry 의 ``base_layer_decision`` enum membership
    까지 추가 검증한다.
    systemrG   r   r   Nr   )stepsystem_promptuser_promptresponse_schemar   schema_namer   )r   r   rc   z-floor_plan_prompt %s attempt %d/%d failed: %srw   r   z exhausted z
 retries: )r=   r   rZ   r   r   r   r+   r   r   ranger   r   	Exceptionloggerwarningr?   )r   r   call_structured_fnr   r   r   r   r   r   rc   r   resolvedr   r   last_errbg_setr   attemptresultr<   s                       r   run_floor_plan_promptr     sP   > &n5H(H=F '-2-FgxBK$(H/>/Js?+PSPUF
 #K@{+%	;'($' &-/+F &fn=**$$*#1
 M% &6 
^,K}JxjY   	;HNN?![# {[()Wq[9:	;s   ,C	D4DD)r   List[Dict[str, Any]]return	List[str])r-   Optional[List[str]]r   r   )r;   r   r   r   )r   N)r_   Dict[str, Any]r   r   r`   r   ra   r   rb   r   rc   r   r-   r   r   r   )r   r   r   zSet[str]rc   r   r   None)r   r   r   r   r   r   )r   r   r   r   r   r   )r   r   r   r   r   zCallable[..., Dict[str, Any]]r   Optional[Dict[str, Any]]r   r   r   r   r   floatr   zCallable[[float], None]r   zOptional[Iterable[str]]rc   r   r   r   r   r   ),rC   
__future__r   r   loggingretimetypingr   r   r   r   r   r	   r
   app.modules.prompt_loaderr   r   	getLoggerr@   r   compiler   rZ   r   __annotations__app.core.fp_prompt_compatr   r   r   r   r   r1   	frozensetr   r=   r   r?   rr   r   r   r   sleepr   r$   rD   r   <module>r      s  , #   	  E E E >			8	$RZZ 
  
	
 
 
 
  G$& N $V
(  ) *   %9 % '+RR-R R )	R
 R R %R 	Rr 	ff f 	f
 
fR)28
4 04.2!(,

/3%'MM M 6	M
 -M ,M M M &M -M M  #M MrD   