
    dj>4                        d 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
mZ ddlmZ ddlmZmZ  ej"                  e      ZdZddd	d
Z ej,                  d      Z ej,                  d      ZdZdedefdZ	 d/deeef   dee
e      dee   fdZde	eeef      de	eeef      defdZde	eeef      defdZde	eeef      defdZ dZ!dededefdZ"dee   defd Z#d!d!d"ddded#d$eeef   d%e	eeef      de	eeef      de	eeef      de	eeef      d&ed'ed(ed)eed*eeef   f      d+eeeef      d,eeeef      d-edeeef   fd.Z$y)0un  outdoor_place_spec — 야외 촬영 부지 마커 스펙 저작 (W22 직행 체인 ①).

building_group(실외 멤버 보유)당 1스펙을 LLM 이 evidence-bound 로 저작한다:

    {layout_narration_en, zone_labels_en[],
     items[{code, kind, name_en, placement_en, inferred,
            evidence{scene_index, quote_ko} | null}]}

- code: 대문자1+숫자1 (MARKER_CODE_RE), 그룹 내 비중복 — 결정론 검증.
- name_en: ID-free 서술명 — 하류(nb2 직행 프롬프트)에서 코드 대신 소비되므로
  이름 자체로 유일 구별 필요 (중복 검증).
- inferred=false 항목은 evidence(scene_index+quote_ko) 필수 — 발명 경계.

하류: outdoor_place_canon(항공 실사 마스터+탑다운 맵 생성),
outdoor_shot_grounding(샷별 존/앵커/카메라 접지).
설계: docs/w22-outdoor-canon-direct-compose-design-20260709/design.md
    N)AnyCallableDictListOptionalSequenceSetTuple)AppError)load_promptload_schemaoutdoor_place_specz1.202607100041z2.202607110020z3.202607251954)123z^[A-Z][0-9]$z\b[A-Z][0-9]\b   selectorreturnc           	      n    	 t         |    S # t        $ r  t        d| dt        t                d      w xY w)Nz3unknown outdoor_place_spec prompt version selector z	 (known: ))PROMPT_VERSION_MAPKeyError
ValueErrorsorted)r   s    ]/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/outdoor_place_spec.pyresolve_prompt_versionr   7   sN    
!(++ 
A( N012!5
 	

s    )4specallowed_scene_indicesc                 H	   g }| j                  d      }t        |t              r|j                         s|j	                  d       | j                  d      }t        |t
              r|rt        d |D              r|j	                  d       nDt        |D ch c]  }|j                          c}      t        |      k7  r|j	                  d       | j                  d      }t        |t
              r|s|j	                  d       |S |D ]M  }t        |t              s|j                  d	      d
k7  s)|j	                  d|j                  dd       d       O | j                  d      }t        |t
              r;|D ]6  }	t        |	t              s|	j                  d      s&|j	                  d       8 t               }
t               }t        |      D ]  \  }}t        |t              s|j	                  d| d       -|j                  d      }t        |t              rt        j                  |      s|j	                  d| d|d       n.||
v r|j	                  d| d|d       n|
j                  |       |j                  d      xs dj                         }|s|j	                  d| d       nJ|j                         |v r|j	                  d| d|d       n|j                  |j                                |r-t        j!                  |      r|j	                  d| d|d       |j                  d      xs dj                         }|s|j	                  d| d       n*t        j!                  |      r|j	                  d| d       |j                  d      }t        |t"              s|j	                  d| d       |d u s|j                  d!      }t        |t              rBt        |j                  d"      t$              r#|j                  d#      xs dj                         s|j	                  d| d$       {||j                  d"      |vs|j	                  d| d%|j                  d"       d&t'        |       d'        |S c c}w )(u  스펙 결정론 검증 — 위반 메시지 리스트 반환 (빈 리스트 = 통과).

    스키마(jsonschema)가 형식을 잡고, 여기서는 스키마로 표현하기 어려운
    계약을 검사한다: 코드/서술명 비중복, inferred=false ↔ evidence 필수,
    evidence.scene_index ∈ allowed_scene_indices(제공 시 — 프롬프트에 공급된
    씬 밖 인용 차단), 서술 필드 마커 코드 누출 금지(ID-free).
    layout_narration_enu%   layout_narration_en 이 비어 있음zone_labels_enc              3   d   K   | ](  }t        |t               xs |j                           * y wN)
isinstancestrstrip).0zs     r   	<genexpr>z&validate_place_spec.<locals>.<genexpr>V   s+     F1:a%%6QWWY6s   .0u5   zone_labels_en 이 비어 있거나 빈 라벨 포함u    zone_labels_en 에 중복 라벨itemsu   items 가 비어 있음temporal_scopepersistent_sitezitems[code?uM   ].temporal_scope 가 'persistent_site' 아님 — 시간 불변 계약 위반excluded_transient_elementsu8   excluded_transient_elements 항목에 code 부여 금지u   ] 이 객체가 아님z].code u#    형식 위반 (대문자1+숫자1)u    중복name_en u   ].name_en 비어 있음z
].name_en u3    중복 — 서술명만으로 유일 구별 필요u<    에 마커 코드 포함 — ID-free 서술명이어야 함placement_enu   ].placement_en 비어 있음un   ].placement_en 에 마커 코드 포함 — 다른 항목은 코드가 아니라 서술명으로 지칭할 것inferredu   ].inferred 가 bool 아님Fevidencescene_indexquote_kou>   ]: inferred=false 인데 evidence(scene_index+quote_ko) 없음z].evidence.scene_index=u    는 제공된 씬(u5   ) 밖 — 제공된 씬 원문에서만 인용할 것)getr$   r%   r&   appendlistanylendictset	enumerateMARKER_CODE_REmatchaddlowerMARKER_CODE_LEAK_REsearchboolintr   )r   r   
violations	narrationzonesr(   r*   itexcludedex
seen_codes
seen_namesir-   name	placementr3   evs                     r   validate_place_specrS   A   sV    J./Ii%Y__->ABHH%&Eud#FFFQR	'Aaggi'	(CJ	6<=HHWEeT"%34 b$BFF+;$<@Q$Q,- .K L  xx56H(D!B"d#v!!N  eJeJ5!2"d#qc)?@Avvf~$$N,@,@,Fqc8[\]Zqc@ANN4 y!'R..0qc)@ABZZ\Z':dX-`a NN4::<('..t4:dX .1 1
 VVN+1r88:	qc)EFG ''	2 U U
 66*%(D)qc)CDE
#Br4(!"&&"7=z*0b779!!QC  4 4
 &1FF=)1FF!!QC6rvvm7L6M N%%+,A%B$C DBBm "x q (s   Rmembersentity_locationsc                    |xs g D ci c]  }|j                  d      | }}g }| xs g D ]  }|j                  dd      }|j                  |      xs i }|j                  d      xs |j                  dd      }|j                  dd      }	dj                  |j                  d      xs g       }
d	| d
| d|	 }|
r	|d|
 dz  }|j                  |        dj                  |      xs dS c c}w )uB   실외 멤버 loc 상세 블록 — entity_merge locations 조인.short_idloc_idr1   labelrP   descriptionz, visual_traitsz- [z] : u    (시각 특징: r   
u   (장소 상세 없음))r7   joinr8   )rT   rU   l	loc_by_idlinesmlidlocrY   desctraitslines               r   build_locations_blockrh      s
   
 0@/E2/EF/E!z"A%/EIFE]]eeHb!mmC &Bg5#''&""5ww}b)377?39r:SEE7"TF+'xq11DT  99U777 Gs   C/scene_textsc                 z    g }| xs g D ]  \  }}|j                  d| d|         dj                  |      xs dS )ud   씬 원문 블록 — 원문 전문 그대로, 절대 자르지 않는다 (CLAUDE.md 절대 규칙).u   ### 씬 r]   z

u   (씬 없음))r8   r^   )ri   partssitexts       r   build_scenes_blockrn      sJ    E%2%Dxt2dV,- &;;u//    shotsc                    g }| xs g D ]n  }|j                  d      }|j                  d      }|j                  dd      }|j                  dd      }d| d| d| }|r|d	| z  }|j                  |       p d
j                  |      xs dS )uP   선택 샷 스테이징 요지 블록 — 화면 중요 요소 단서 참고용.r5   
shot_indexrZ   r1   camera_directionz- S_Shotr\   z / camera: r]   u   (샷 연출 데이터 없음))r7   r8   r^   )rp   ra   shrl   shire   camrg   s           r   build_shots_blockrx      s    EkrkVVM"ff\"vvmR(ff',RDcU"TF+k#''DT  99U>>>ro   group_labellocations_blockworld_rules_blockscenes_blockshots_blocktemplateblocksc                 p    | }t         D ]*  }|j                  d|z   dz   |j                  |d            }, |S )uQ   user_template 변수 치환 — .format 대신 replace (본문 중괄호 안전).{}r1   )_TEMPLATE_KEYSreplacer7   )r   r   outkeys       r   build_user_promptr      s9    
Ckk#)c/6::c2+>? Jro   rG   c                 \    g d}|j                  d | D               dj                  |      S )N)r1   r1   uU   [재시도 — 직전 응답이 아래 계약을 위반했습니다. 전부 고쳐서u*    전체 스펙을 다시 출력하세요:]c              3   &   K   | ]	  }d |   yw)z  - N )r'   vs     r   r)   z%_format_retry_hint.<locals>.<genexpr>   s     0Z4sZs   r]   )extendr^   )rG   ra   s     r   _format_retry_hintr      s,    E 
LL0Z0099Uro   r1   r   )
rules_textcreator_corrections_blockprompt_versioncall_structured_fnproject_configopik_metadatamax_attemptsgroupoutdoor_membersr   r   r   r   .r   r   r   c                    |ddl m} |}t        |      }t        t        d|      }||xs dz  }t        t        d|      }t        t        d|      }| j                  d	d      }|}| j                  d
d      }|r	|d| dz  }t        ||t        ||      |xs dt        |      t        |            }|xs g D ch c]  \  }}|	 }}}d}|}g }||k  ru|dz  } |d||||	d|
      }t        |xs i |      }|s||dS |}t        j                  d||t        |      dj                  |dd              |t!        |      z   }||k  rut#        dd|d| ddj                  |dd       z   d      c c}}w )u   그룹 1개의 마커 스펙 저작 — 검증 위반 시 위반 목록을 힌트로 재시도.

    반환 {"spec": <검증 통과 스펙>, "attempts": n}.
    max_attempts 소진 시 AppError(step.contract_violation.outdoor_place_spec).
    Nr   )call_structuredsystem)versionr1   user_templateschemagroup_id
anchor_locz
 (anchor: r   u   (세계 물리 규칙 없음)ry      r   )r   schema_namer   )r   attemptsz8outdoor_place_spec group=%s attempt=%d violations=%d: %sz;    z*step.contract_violation.outdoor_place_speczoutdoor_place_spec group=u     스펙 계약 위반 (attempts=z):    i  )r-   messagestatus_code)app.modules.llm.llm_clientr   r   r   _MODULEr   r7   r   rh   rn   rx   rS   loggerwarningr;   r^   r   r   )r   r   rU   ri   rp   r   r   r   r   r   r   r   r   resolvedr   r   r   gidrz   anchoruserrl   _r   r   current_userlast_violationsresultrG   s                                r   run_outdoor_place_specr      s   ( !>,%n5H(H=F
'-2-F7OXFH(H=F
))J
#CKYY|R(FF81---o?OP$G(G'4%e,D .9->B->?->EBR->?HL!#O
\
!A# ),'
 )27LM
"99$F3z?DIIj!n,E	
 0<<' \
!* 9'w /%c+-1YYr7J-KL  5 @s    E9r#   )%__doc__loggingretypingr   r   r   r   r   r   r	   r
   app.core.errorsr   app.modules.prompt_loaderr   r   	getLogger__name__r   r   r   compiler?   rC   MAX_ATTEMPTSr%   r   rF   rS   rh   rn   rx   r   r   r   r   r   ro   r   <module>r      s  $  	 L L L $ >			8	$
 
 
 
   O, !bjj!23 
S 
S 
 15p
sCx.p#CH-p 
#Ypf8d38n%8tCH~.8 	8(0HU38_$= 0# 0?Xd38n5 ?# ? s s 49  $ %'BF/3.2$QS>Q d38n-Q tCH~.	Q
 %S/*Q DcN#Q Q  #Q Q !#tCH~*=!>?Q T#s(^,Q DcN+Q Q 
#s(^Qro   