
    Bfj ^                       d Z ddlmZ ddlZddlZddlmZmZmZm	Z	m
Z
 dZdZdZdZd	d
dddZdhZd%dZdd	 	 	 	 	 d&dZd'dZeZ	 	 	 	 	 	 	 	 d(dZddd	 	 	 	 	 	 	 	 	 	 	 d)dZ	 d*	 	 	 	 	 	 	 d+dZ	 	 	 	 	 	 	 	 d,dZ	 	 	 	 	 	 d-dZ	 	 	 	 d.dZd/dZd0dZd1dZd1dZd2dZd3d Z d!dddd"d#	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d4d$Z!y)5uP  야외 3레인 게이트 — place_segment 분할+샷 바인딩+레인 판정 (설계 v2 Stage A).

LLM 은 evidence-bound 구조 JSON 만 저작한다. 코드는 스키마 잠금·완전성
검증·보수 라우팅(저신뢰/혼합→structure_plate)만 담당 — substring/개수
휴리스틱으로 의미를 판단하지 않는다.
    )annotationsN)AnyDictListOptionalSequence)
map_markerstructure_platenone)movement	structure)highmediumlowoutdoor_lane_planz1.202607142200z2.202607190157z3.202607211500z4.202607251321)1234r   c                @    | t         vrt        d|        t         |    S )Nu.   outdoor_lane_plan 프롬프트 버전 없음: )PROMPT_VERSION_MAP
ValueErrorversions    \/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/outdoor_lane_plan.pyresolve_prompt_versionr   )   s)    ((I'STTg&&    Finclude_sitec          	     l   dddidddddd	gd
d}ddddddddt        t              id|dddt        t              idg dd
d}dddiddiddddt        t              idt        t              iddd|dg dd
d}d|ddd|ddd}ddg}|rdddiddddddgd
d|d<   dg|z   }d||d
dS )u   segments+shot_bindings 스키마 — lane/mode/confidence enum 잠금.

    include_site (팩 v4+): 장소 단위 복잡 구조물 선판정(site) 필드를
    required 로 추가. False=기존 팩 byte-identical.
    objecttypeintegerstring   )r"   	minLength)scene_indexquote_kor'   r(   F)r"   
propertiesrequiredadditionalPropertiesenumarray)r"   itemsminItems)
segment_idlabel_endominant_modeevidence
confidence)r'   
shot_indexr0   laner4   rationale_kor3   )segmentsshot_bindingsr8   r9   boolean   )has_complex_structurestructure_evidence_enr<   r=   site)list_MODES_CONFLANES)specr   r3   segmentbindingpropsr*   s          r   build_lane_schemarG   /   sW    "I.!):
 #J/ %H #+!<!):$d6l3!(8K!4;/
/ %G "I.!9-#+!<T%[)!4;/%-A> 
? %G  %wAF")G&')E
 O,H *0))<$1*6
 12IJ$)	
f 8h& %	 r   c                L    t        j                  dd|       j                         S )ua  연속 whitespace(개행 포함)→단일 공백. 원문 줄바꿈을 LLM 이 공백으로
    인용하는 정상 케이스(5회차 실측 2건) 허용 — 의미 판단 없음.

    2026-07-26: outdoor_marker_map 의 camera_facing evidence provenance 도
    같은 정규화를 쓴다 — 사설 이름 교차 import 를 피해 공개로 승격.
    z\s+ )resubstrip)texts    r   ws_normalizerN   x   s      66&#t$**,,r   c                   t        |t              r#|j                  d      xs dj                         s|  dgS |j                  d      }||vr|  d| dt	        |       dgS t        |d         }|t        ||         vr|  d| d	|gS g S )
u  evidence 무결성 — 공급 씬 범위 + literal 인용 실재 (Codex BLOCKING).

    substring 으로 의미를 판정하는 것이 아니라, LLM 이 제시한 literal
    인용이 실제 원문에 존재하는지(증거 진위)만 확인하는 결정론 게이트.
    r(    u    evidence(씬 인용) 누락r'   z evidence.scene_index=u    는 제공된 씬(u5   ) 밖 — 제공된 씬 원문에서만 인용할 것u    evidence 인용이 씬 u6    원문에 없음 — 원문 그대로 인용할 것: )
isinstancedictgetrL   sorted_ws_normalize)labelevscene_textssiquotes        r   _check_evidencer[      s     b$z(:(@b'G'G'I'5677		B	g+B4 0{#$$Y[
 	
 "Z.)EM+b/22g-bT 2#Y(
 	
 Ir   forbid_structure_maprequire_sitec          	        g }|r| j                  d      }t        |t              s|j                  d       nmt        |j                  d      t              s|j                  d       t        |j                  d      xs d      j                         s|j                  d       t               }i }| j                  d      xs g D ]  }	|	j                  d	      }
|
|v r|j                  d
|
 d       |j                  |
       |	j                  d      ||
<   |	j                  d      xs g }|s|j                  d|
 d       |D ]"  }|j                  t        d|
 d||             $  |D 	ch c]  }	|	d   |	d   f }}	t               }| j                  d      xs g D ]  }|j                  d      |j                  d      f}||v r|j                  d| d       |j                  |       |j                  d	      |vr'|j                  d| d|j                  d	       d       |rL|j                  d      dk(  r8|j                  |j                  d	            dk(  r|j                  d| d       |j                  t        d| |j                  d      |              ||z
  }|r|j                  dt        |              ||z
  }|r|j                  dt        |              |S c c}	w )u  결정론 완전성·증거 무결성 검증 — 위반 리스트 반환 (fail-closed 재시도용).

    forbid_structure_map (팩 v3, E2E10 fix①): LLM 자신이 structure 로
    선언한 세그먼트에 lane=map_marker 바인딩 금지 — 맵은 개활지 전용.
    세그먼트 mode enum 대조만 하는 결정론 가드(의미 판단 없음).
    default False = 기존 팩(v1/v2) 동작 불변.

    require_site (팩 v4, 2026-07-25 지적②): 장소 단위 선판정 필드의
    **실재**만 검증한다 — 판정 내용에 따른 lane 강등은 재시도가 아니라
    apply_site_complexity_gate 의 결정론 몫(게이트 입력이 없으면 게이트
    자체가 무력화되므로 여기서 fail-closed).
    r>   uG   site(장소 단위 복잡 구조물 선판정) 결손 — 팩 v4 필수r<   u-   site.has_complex_structure 가 boolean 아님r=   rP   u(   site.structure_evidence_en 비어 있음r8   r0   zsegment_id 'u   ' 중복r2   r3   z	segment 'u   ' evidence(씬 인용) 누락'r'   r5   r9   u   샷 u    바인딩 중복u    가 미정의 segment 'u   ' 참조r6   r	   r   ur   : structure 세그먼트에 lane=map_marker 금지 — 맵은 개활지 전용, structure_plate 또는 none 으로u   바인딩 누락 샷: u   대상 밖 샷 바인딩: )rS   rQ   rR   appendboolstrrL   setaddextendr[   rT   )resultgroup_shotsrX   r]   r^   
violationsr>   seg_idsseg_mode_by_idssidevsrW   expectedseenbkeymissingextras                      r   validate_lane_planru      s   ( Jzz&!$%Y[ dhh'>?F!!CEtxx 78>B?EEG!!"LM5G%'NZZ
#)r)eeL!'>SE:;CeeO4seeJ%2	#.KLMB)C5 2BDF  * >II[=!1\?3[HIDZZ(.B.uu]#QUU<%89$;SE):;<55g-se3AEE,4G3HQS !f-""155#67;Fse N N 	d3%L!%%
*;[I	K# /& oG26'?2CDE8OE6ve}oFG7 Js   1K6c                    |C| j                  d      xs g D cg c]%  }|j                  d      |j                  d      d' }}t        | ||      }|j                  t        |              |S c c}w )u  persisted plan 소비 직전 재검증 (Stage D — Codex 배선 조건).

    저장된 plan 을 신뢰하지 않는다: 증거 인용 실재·씬 범위·segment 참조·
    중복을 전부 재검사. group_shots 미제공 시 plan 자신의 바인딩으로
    커버리지 집합을 구성(커버리지 외 무결성 검사는 전부 동일하게 수행) —
    실제 그룹 샷 목록을 가진 소비자는 명시 전달로 커버리지까지 잠근다.
    r9   r'   r5   )r'   r5   )rS   ru   rf   site_gate_violations)planrX   rh   rq   ri   s        r   revalidate_persisted_planry      s      XXo.4"4
 5 EE-055.04 	 

 $D+{CJ *401
s   *A/c                   ddl m}m}m | xs i j	                  di       j	                  dg       xs g  ||       ||      i |xs i j	                  di       j	                  dg       xs g D ]=  }|j	                  d      }|j	                  dd      xs d}|-|s0|t        |      <   ? i |xs i j	                  di       j	                  dg       xs g D ]  }|j	                  d      }	|	|j	                  dg       xs g D ]f  }
|
j	                  d	      }|i }|
j	                  d
      r|
d
   |d
<   |
j	                  d      r|
d   |d<   |sN|t        |	      t        |      f<   h  dfd}|S )u%  Stage A 와 동일한 '현재 선택 그룹 샷' 재구성 클로저 (Stage D 공용).

    persisted lane plan 소비자(seed/conti/canon lane filter)가 plan 자기
    바인딩이 아니라 **현재 authoritative 선택 샷 집합**으로 커버리지를
    재검증하기 위한 것 (Codex Stage D BLOCKING-2). 반환 클로저는
    spec 그룹 entry({spec, outdoor_loc_ids, scene_indices})를 받아
    Stage A 저작 입력과 동일한 샷 dict 리스트를 돌려준다
    (staging 샷 + validator description/characters 병합).
    r   )build_selected_keysbuild_shot_loc_mapfilter_group_shotsdatashotsscenesr'   primary_locationrP   r5   description
charactersc                    | xs i j                  d      xs g t        | xs i j                  d      xs g             }|D cg c]2  }i |j                  t        |d         t        |d         fi       4 c}S c c}w )Nscene_indicesoutdoor_loc_ids)r   loc_idsscene_primaryshot_loc_by_keyselected_keysr'   r5   )rS   rd   int)	
spec_entryr   shr}   r   r   shot_desc_by_keyr   staging_shotss	      r   group_shots_forz7build_group_shot_reconstructor.<locals>.group_shots_for6  s    "%+00AGR)r../@AGRH'+'
 
 Fr F%))R&'R-=)>?E F
 	
 
s   7B)r   Dict[str, Any]returnzList[Dict[str, Any]])*app.modules.pipeline.outdoor_direct_commonr{   r|   r}   rS   r   )
staging_cpvalidator_cpselection_cpdirector_cpr{   r|   scrY   primaryv_sir   v_shirt   r   r}   r   r   r   r   r   s                 @@@@@@r   build_group_shot_reconstructorr     s      
	rvr*..w;Ar  (5M(6O$&M b%%fb155hCIrIVVM"&&+R06B>g%,M#b'"	 J
 57!r&&vr266xDJJvvm$<&&"%++BFF<(E}$&Evvm$')-'8m$vvl#&(&6l#<A #d)SZ!89 ,	 K 
 
 r   c                    g }| xs i j                  di       xs i }t        |xs i       D ]  }||   }t        |t              s ||      }|s$|j                  d      s4|j	                  d| d|j                  d      dt        |       d       i|j                  |      }t        |t              r|j                  d      nd	}	|	d
k7  s|dnd|	 }
|j	                  d| d|
 dt        |       d        |S )u  그룹 단위 parity 검증 (Codex 재리뷰 BLOCKING-1).

    현재 spec 그룹 중 authoritative 재구성 샷이 1개 이상인 **모든** 그룹은
    ①spec 실재 + ②lane plan entry status=ok 여야 한다 — 그룹 누락/failed
    를 통과시키면 해당 샷이 일반 콘티/canon 경로로 조용히 하강한다(핵심
    fail-closed 계약 위반). ①은 재재리뷰 지적: spec 그룹 LLM 실패
    ({error,...})가 lane plan 의 'spec missing' skip 으로 세탁되어 lane
    3분류를 우회하던 상류 fail-open — 실패 entry 의 보존 구조키
    (outdoor_loc_ids/scene_indices)로 현재 샷을 재구성해 검사한다.
    현재 선택 샷 0인 그룹만 entry 부재/skip 허용.
    반환 = 위반 리스트 (빈 리스트 = parity 성립).
    groupsrC   u   그룹 'u)   ' outdoor_place_spec 실패/결측(error=erroru   ) — 선택 샷 u   개가 lane 분류 밖statusNoku   누락zstatus=z' lane plan u    — 선택 샷 u   개가 lane 미배정)rS   rT   rQ   rR   ra   len)	lane_dataspec_groupsreconstructri   lane_groupsgidentryr   
lane_entryr   states              r   validate_group_parityr   H  s,   $ J?''"5;Kk'R(C %&E"yy 3%  ))G,//@u:,46
  __S)

D1 ..*7; 	T> * 2H'&8JE3%|E72Bu:,35' ). r   c                T   i }t        | xs i j                  di       xs i       D ]y  }| d   |   }t        |t              r|j                  d      dk7  r1|j                  d      xs i }|j                  d      xs g D ci c]$  }t        |t              r|j                  d      |& }}|j                  d      xs g D ]  }t        |t              s|j                  d      |j                  d	      }	}||	;|j                  |j                  d            xs i }
|j                  d
      |j                  d      ||
j                  d      xs d|j                  d      t	        |j                  d            d|dt        |       dt        |	       <    | |S c c}w )u  CP data.groups → {"S{si}sh{shi}": binding+group/segment 컨텍스트}.

    status=ok 그룹만. 반환 항목 = {lane, segment_id, group_id,
    segment_label_en, confidence, routed_conservatively?} (Stage D 소비용
    결정론 조인 — LLM 판정 재해석 없음).
    r   r   r   rx   r8   r0   r9   r'   r5   r6   r1   rP   r4   routed_conservatively)r6   r0   group_idsegment_label_enr4   r   Sr   )rT   rS   rQ   rR   rb   r   )lane_cp_dataoutr   r   rx   rl   	seg_by_idrq   rY   shisegs              r   lane_bindings_by_tagr   v  s    &(C|)r..x<BCX&s+%&%))H*=*Eyy &B XXj)/R/
/!T" EE,"/ 	 

 /*0b0Aa&eeM*AEE,,?BzS[--l 34:CfeeL1$'GGJ$7$=2eeL1)-EE12*4.C!CG9Bs3xj)* 1 D4 J+
s   )F%c                n    | j                  d      }t        |t              xr |j                  d      du S )uC   장소 단위 선판정 소비 — 필드 부재(구 팩)는 False.r>   r<   T)rS   rQ   rR   )rx   r>   s     r   site_has_complex_structurer     s6    88FD4 	6HH,-5r   c                |    t        |       sg S t        d | j                  d      xs g D              }|sg S d| dgS )uT   장소 게이트 잔존 위반 — 복잡 구조물 장소에 map_marker 가 남음.c              3     K   | ]:  }|j                  d       dk(  r$|j                  d      |j                  d      f < yw)r6   r	   r'   r5   N)rS   ).0rq   s     r   	<genexpr>z'site_gate_violations.<locals>.<genexpr>  s?      0A55=L( 
}	quu\230s   A Ar9   u6   복잡 구조물 장소인데 lane=map_marker 잔존: uO    — 맵은 구조물 없는 개활 장소 전용 (structure_plate/none 으로))r   rT   rS   )rx   leakeds     r   rw   rw     s_    %d+	 /*0b0 F
 	
@ IN 	N r   c                    t        |       s| S t        j                  |       }|j                  d      xs g D ]&  }|j                  d      dk(  sd|d<   d|d<   d|d<   ( |S )	u  장소(그룹) 단위 복잡 구조물 게이트 — 2026-07-25 사용자 확정.

    site.has_complex_structure 가 참인 장소에서는 **샷이 아무리 개활해
    보여도** map_marker 를 쓰지 않는다(옥탑 마당 실측: LLM 이 "계단
    아래의 열린 지상 공간"을 근거로 conf=high 맵 배정). 샷별 판단을
    믿지 않고 장소 단위로 결정론 강등한다 — 재시도 비용 0, 차단 확실.
    r9   r6   r	   r
   Tr   site_complex_structurerouting_reason)r   copydeepcopyrS   )rg   r   rq   s      r   apply_site_complexity_gater     sp     &f-
--
CWW_%++55=L()AfI)-A%&":A	 ,
 Jr   c                   t        j                  |       }|j                  d      xs g D ci c]  }|d   |
 }}|j                  d      xs g D ]  }|j                  d      dk(  r|j                  |j                  d            xs i }|j                  d      dk(  xs |j                  d      dk(  }|j                  d      d	k(  xr |j                  d      d
k(  }|s|sd|d<   d|d<   |rdnd|d<    |S c c}w )u  저신뢰/혼합 판정 보수 라우팅 — structure_plate 로 강등+감사 마킹.

    강등 조건(둘 중 하나):
    - binding.confidence == "low" 또는 소속 segment.confidence == "low"
    - binding.lane 과 소속 segment.dominant_mode 가 상충
      (movement↔structure_plate 는 보수 방향이라 허용,
       structure↔map_marker 만 혼합으로 본다)
    r8   r0   r9   r6   r   r4   r   r2   r   r	   r
   Tr   low_confidence
mixed_moder   )r   r   rS   )rg   r   rl   r   rq   r   r   mixeds           r   apply_conservative_routingr     s    --
C-0WWZ-@-FB-FG-F<!#-FIGWW_%++55=F" mmAEE,/06BeeL!U*Lcggl.Cu.L)[8 3UU6]l2 	%)AfI)-A%&69"2|A , J Hs   C=c                D    ddl m dj                  fd| D              S )Nr   )build_shot_block

c              3  .   K   | ]  } |        y wN )r   rl   r   s     r   r   z_shots_block.<locals>.<genexpr>  s     @Kq'*Ks   )+app.modules.pipeline.outdoor_shot_groundingr   join)rh   r   s    @r   _shots_blockr     s    L;;@K@@@r   c                |    g }t        |       D ]  }|j                  d| d| |            dj                  |      S )Nz[Scene z]
r   )rT   ra   r   )rX   partsrY   s      r   _scene_texts_blockr     sE    E[!wrd#k"o%678 " ;;ur   r   r;   )prompt_versioncall_structured_fnproject_configopik_metadatamax_attemptsc           	     8   ddl m} |ddlm}	 |	}ddlm}
 ddlm} t        |      } |t        d|      } |t        d	|      }|t        v }t        | |
      }|} |
|       dj                  d | j                  dg       xs g D              t        |      t        |      dj!                         D ]  \  }}|j#                  d|z   dz   |      } d|dg}d}|}g }||k  r|dz  } |t        ||||t        |      }t%        |xs i |||dk(  xs ||      }|st'        |      }|rt)        |      }||dS |}dj                  g d|D cg c]  }d| 	 c}z         }|d|dgz   }||k  r |dd| ddj                  |dd       z   d       c c}w )!u   장소 그룹 1개 lane plan — 완전성 위반 시 위반 힌트 재시도.

    반환 {"plan": <보수 라우팅 적용본>, "attempts": n}.
    소진 시 AppError(step.contract_violation.outdoor_lane_plan).
    r   )AppErrorN)call_structured)build_legend_block)load_promptsystemr   user_templater   
c              3  &   K   | ]	  }d |   yw)z- Nr   )r   zs     r   r   z.run_outdoor_lane_plan_group.<locals>.<genexpr>  s      !
BbHBs   zone_labels_en)legend_blockzones_blockshots_blockscene_texts_block{}rM   )r"   rM   r%   )r   schema_namer   r   r\   )rx   attempts)rP   rP   uK   [재시도 — 직전 응답이 아래 계약을 위반했습니다. 전부u4    고쳐서 전체 결과를 다시 출력하세요:]z  - z)step.contract_violation.outdoor_lane_planu"   lane plan 계약 위반 (attempts=z): z;    i  )codemessagestatus_code)app.core.errorsr   app.modules.llm.llm_clientr   r   r   app.modules.prompt_loaderr   r   _MODULE_SITE_GATE_PACKSrG   r   rS   r   r   r.   replaceru   r   r   )rC   rh   rX   r   r   r   r   r   r   r   r   r   resolvedr   template
_site_gateschemafilledrr   val
base_partsr   r   lastrg   ri   rx   vhints                                r   run_outdoor_lane_plan_groupr    s     )!>, 6%n5H(H=F7OXFH#33Jt*=FF*40yy !
"hh'7<BB!
 
 $K0/< egS c	C5 "623JHED
\
!A#VUF)'	
 (Lb+{ #%3#

  .f5D1$7 h77yyE#-.:aaSz:./

 vt<==9 \
!< 84\N#F))D!H%&	 	 /s   F
)r   rc   r   rc   )rC   r   r   rb   r   r   )rM   rc   r   rc   )rV   rc   rW   r   rX   Dict[int, str]r   	List[str])rg   r   rh   Sequence[Dict[str, Any]]rX   r  r]   rb   r^   rb   r   r  r   )rx   r   rX   r  rh   z"Optional[Sequence[Dict[str, Any]]]r   r  )r   Optional[Dict[str, Any]]r   r  r   r  r   r  )r   r   r   r   r   r  )r   r   r   zDict[str, Dict[str, Any]])rx   r   r   rb   )rx   r   r   r  )rg   r   r   r   )rh   r  r   rc   )rX   r  r   rc   )rC   r   rh   r  rX   r  r   rc   r   Dict[str, Any] | Noner   r  r   r   r   r   )"__doc__
__future__r   r   rJ   typingr   r   r   r   r   rB   r@   rA   r   r   r   r   rG   rN   rU   r[   ru   ry   r   r   r   r   rw   r   r   r   r   r  r   r   r   <module>r
     s"   #  	 6 61	"!
 
 

 
 
 & 5 ' 38F
F+/FFR- &4< "'II)I  I
 I I I^ 7;
 4 	2A(A +A +	A
 *AH++  +
 +\$ $$N"&6A ,0+/T
T *T  	T
 T *T )T T Tr   