
    \j$                       U 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
mZmZ ddlmZ  ej                  e      ZdZded<   d	Zded
<   dZded<   dZdZdZdZdZdZdZdZdZdZdZ dZ! e"ddh      Z#ee$e$f   Z%dd	 	 	 	 	 	 	 d=dZ&ddd	 	 	 	 	 	 	 	 	 	 	 d>dZ'dd 	 	 	 	 	 d?d!Z(ddd"	 	 	 	 	 	 	 	 	 	 	 	 	 d@d#Z)	 	 	 	 	 	 dAd$Z*ddd%	 	 	 	 	 	 	 	 	 dBd&Z+	 	 	 	 dCd'Z,d(Z-ded)<   e-d*	 	 	 	 	 	 	 	 	 dDd+Z.dEd,Z/dFd-Z0	 	 	 	 	 	 	 	 dGd.Z1dd/	 	 	 	 	 	 	 dHd0Z2	 	 	 	 	 	 dId1Z3d2d3	 	 	 	 	 	 	 dJd4Z4 ejj                  d5      Z6dKd6Z7dLd7Z8 ejj                  d8      Z9dKd9Z:	 	 	 	 	 	 dMd:Z;ddd;	 	 	 	 	 	 	 	 	 	 	 dNd<Z<y)OuN  W21B-W7 (2026-06-12) — visual_continuity_anchor deterministic core.

B-run 피드백 버킷 C(zoom pose 모순)/D(printed prop identity·scale) 의 production
1차. 이 모듈은 순수 deterministic 영역만 담는다:

  - seed 탐지 (LLM 0):
      C(zoom_continuity) = refined ``ref_usage == "zoom_in_detail"`` dep pair (같은 scene 의
          selected shot pair). exact same-moment(``exact_background``)는
          production 소비 없이 diagnostics 후보로만 기록 (Codex 판정 ①).
      D = prop short_id 가 2개 이상의 selected shots 의 visible_entity_ids /
          required_refs(kind=prop) 에 등장. framing 은 hard gate 가 아니라
          priority score (Codex 판정 ④) — 코드는 structured enum / ID 조인만
          수행하고 단어 regex / substring 의미 판별은 절대 하지 않는다.
  - cap 선별: per-episode LLM 콜 cap. 초과 seed 는 skipped(reason="cap") 로
    진단 기록 — silent drop 금지 (bg_space_partition cost-cap 선례).
  - manifest 조립 + 조인 무결성 검증 (의미 게이트 없음 — Codex 가이드 5).

LLM anchor 추출(스파이크 1/2 포팅)은 ``visual_continuity_anchor_provider`` 가
담당한다. anchor 품질은 deterministic 테스트 비대상 — canary + 육안.

설계: docs/w21b-w7-visual-continuity-anchor-production-brief-20260612/.
    )annotationsN)AnyCallableDictListOptionalSetTuple)is_immobilized_state   intSCHEMA_VERSION   DEFAULT_GROUP_CAP   DEFAULT_IMMOBILIZED_CAPzoom_continuityprinted_propimmobilized_subjectzoom_in_detailexact_backgroundsource_widezoomclose_insertenvironmentsame_axis_punch_indifferent_camera_same_moment	uncertaincloseinsert)source_still_existsc          
        g }g }g }t               }| xs g D ]  }|j                  d      }|j                  d      }	t        |t              rt        |	t              sG|j                  d      xs g D ]q  }
|
j                  d      }|
j                  d      }|
j                  d      }t        |t              rt        |t              sXd| d|	 d| d| }|t        k(  r||k(  r|j                  ||	g||gd       |t        k7  r|	|j                  |t                     vr|j                  |d	d
       ||k7  r|j                  |dd
       ||	f|v r|j                  |dd
       ||j                  |t                     v }|s|t         |||            }|s|j                  |dd
       G|j                  ||	f       |j                  ||	g||gd       t  |||fS )u  zoom_in_detail dep pair 에서 C seed 를 deterministic 하게 뽑는다.

    Args:
        dependencies: shot_dependency(_t2i) cp 의 ``data.dependencies`` —
            [{scene_index, shot_index, location_refs:[{scene_index, shot_index,
            ref_usage, ...}], ...}]. ref_usage 는 현재 refined cp
            (shot_dependency_t2i) 에만 실재한다 — caller 가 source 선택을 책임.
        selected_map: {scene_index: {selected shot_index}}.
        source_still_exists: (si, shi) → bool. source 가 selected 가 아니어도
            기존 still 이 실존하면 seed 성립 (Codex 판정 ① 추가 조건). None 이면
            selected 만 인정.

    Returns:
        (seeds, skipped, exact_candidates) —
        seeds: [{"zoom": [si, shi], "source": [si, shi]}]
        skipped: [{"seed": str, "reason": str}]
        exact_candidates: production 소비 없는 same-scene exact_background pair
            진단 후보 (``candidate_same_moment_exact_background``).
    scene_index
shot_indexlocation_refs	ref_usageSshz<-S)r   sourcezoom_not_selectedseedreasoncross_scene_zoomduplicate_zoom_membersource_still_unavailable)	setget
isinstancer   REF_USAGE_EXACT_BGappendREF_USAGE_ZOOMbooladd)dependenciesselected_mapr!   seedsskippedexact_candidates	seen_zoomdepzoom_sizoom_shirefusagesrc_sisrc_shilabel	source_oks                   h/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/visual_continuity_anchor_plan.pydetect_c_seedsrI   H   s   2 #%E$&G-/!eI!r!''-(77<('3'z(C/H77?+1r1CGGK(EWW]+Fggl+Gfc**Wc2Jy8*Cxr'CE**v/@ ''%x0VW<MN &|//??9LMN 9KLM"i/9PQR<#3#3FCE#BBI!4!@ !4VW!EF	9STUMM7H-.LL7H"5&'ARSTE 2 "R '+++    )framing_by_shotrequired_prop_refs_by_shotc          
        xs i xs i i }|j                         D ]  \  }}t        |      D ]  }||f}	t               }
| j                  |	g       xs g D ]  |v s|
j	                          j                  |	t                     D ]  |v s|
j	                          |
D ]#  |j                  g       j                  |	       %   g }t        |      D ]  t        |         }t        |      dk  r t        fd|D              }t        fd|D              }|j                  |D 	cg c]  }	t        |	       c}	|dz  |dz  z   t        |      z   d        |j                  d        |S c c}	w )	u,  prop 반복 등장 기반 D seed + priority score (Codex 판정 ④).

    base = prop short_id 가 2개 이상의 selected shots 에서 visible_entity_ids
    또는 required_refs(kind=prop) 로 등장. score 는 ID/enum 조인만:
      - required_refs(kind=prop) 등장 샷 수 × 10  (가장 강한 신호)
      - framing_scale ∈ {close, insert} 샷 수 × 3 (structured enum)
      - 멤버 샷 수 × 1

    required_prop_refs_by_shot 은 scene_detail cp soft 역참조 — fresh run 에는
    None (신호 부재일 뿐 seed 차단 아님).
       c              3  \   K   | ]#  }j                  |t                     v s d  % ywr   N)r2   r1   ).0keyrL   sids     rH   	<genexpr>z!detect_d_seeds.<locals>.<genexpr>   s,      
##)C)G)GSU)S"SAs   !,,c              3  R   K   | ]  }j                  |      t        v sd    ywrP   r2   _CLOSE_FRAMING_SCALES)rQ   rR   rK   s     rH   rT   z!detect_d_seeds.<locals>.<genexpr>   s(      
#/"5"5c":>S"SAs   ''
      )prop_short_idmember_shotsscorec                    | d    | d   fS )Nr\   rZ    ss    rH   <lambda>z detect_d_seeds.<locals>.<lambda>   s    qzk1_+=>rJ   rR   )itemssortedr1   r2   r8   
setdefaultr5   lensumlistsort)
ve_by_shotr:   prop_short_idsrK   rL   r[   siselectedshirR   presentr;   shotsrequired_countclose_countrS   s      ``          @rH   detect_d_seedsrs      s   & &+O!;!Ar-/L$**,H(#Cs)C #G!~~c2.4"4.(KK$ 5 255c35A.(KK$ B ''R077<  $ - #%El#|C()u:> 

 
  

 
 	 278%3T#Y%8#b(;?:SZG
 	 $  
JJ>J?L 9s   <F	character)entity_typec               B  
 i dfd
d
fd}| xs g D ]  }t        |t              st        |j                  d      xs d      |k7  r6t        |j                  d      xs d      j	                         }t        |j                  d      xs d      j	                         }|r|r||k(  r |||        i }D ]1  }|j                   
|      t                     j                  |       3 i }|j                         D ]  }	|	D ]  }t        |	      ||<     |S )	u  entity_relation 체크포인트 relations → short_id identity-family 맵 (union-find).

    변형 EntityCanon(예: 같은 인물의 환영/시신 외형)은 base 와 동일 인물이라는
    데이터 계약(base_short_id ↔ variant_short_id)을 short-id 공간의 등가집합으로
    노출한다. VE/staging 이 서로 다른 variant 표기를 쓸 때 exact-ID 게이트가
    같은 인물을 놓치는 것을 막는 SOT (2026-07-02, S12 시신 seed 전멸 수정).

    - ``entity_type`` 정확 일치(enum 비교) 항목만 결합 — prop/location 미적용.
    - 반환: 모든 family 멤버 sid → 그 family 의 전체 sid set(자기 포함).
      관계가 없거나 입력이 비면 빈 dict (게이트는 기존 exact 동작으로 환원).
    c                J    |    | k7  r|       | <   |    } |    | k7  r| S Nr^   )xparents    rH   _findz+build_identity_family_by_sid.<locals>._find   s=    Qi1nvay)F1Iq	A Qi1n rJ   c                    j                  | |        j                  ||        |        |      }}||k7  r||<   y y rx   )re   )abrarbr{   rz   s       rH   _unionz,build_identity_family_by_sid.<locals>._union   sI    !Q!Qq58B8F2J rJ   ru    base_short_idvariant_short_id)ry   strreturnr   )r}   r   r~   r   r   None)	r3   dictr   r2   stripre   r1   r8   values)	relationsru   r   rbasevariantrootsrS   outmembersr{   rz   s             @@rH   build_identity_family_by_sidr      s      F _"_!T"quu]#)r*k9155)/R0668aee./526<<>7dgotW  "$EsSU+//4 !C<<>C7|CH  " JrJ   )rK   identity_family_by_sidc               
   xs i i }g }|j                         D ]  \  }t        |      D ]  }	|	f}
| j                  |
      }|st        |j                  |
g       xs g       }|j                  d      xs g D ]V  }|j                  d      }t	        |xs d      s%|j                  dd      xs d}|j                  |      xs
 t               }t        |      dk7  r&|j                  d d|	 d|xs d	 |rd
ndd       t        t        |            }||vr|xs i j                  |      xs
 t               }t        ||z        }|s1d d|	 d| dd}|rt        |      |d<   |j                  |       |j                  d d|	 d| d|d   t        |      d       |j                  |fg       j                  |	|d       Y   g }t        |j                               D ]  \  \  }}i }|D ]  }|j                  |d   |d           t        |      dk  r9t        |j                               }t        |      dkD  r%|j                  d d| dt        |      d       t        |      }t        fd|D              }|j                  |t        t        |            |D 	cg c]  }	|	g c}	|dz  t        |      z   d        |j                  d        ||fS c c}	w )u  immobilized 인물의 씬 내 다중 샷 연속성 seed (Codex 합의 P8 v1 gate).

    gate (전부 구조 신호 — 글자/시나리오 판정 0):
      - shot_staging ``character_angles[*].subject_state`` 가 IMMOBILIZED_STATES
        (``is_immobilized_state``) 인 인물.
      - 그 인물 이름(``character``) → short_id 를 ``name_to_sids`` exact 조인.
        모호(2+)/미상(0) 이름은 seed 제외 + diagnostic (substring 매칭 금지 —
        entity catalog display-name exact equality 만, 기존 state/ref 흐름과 동일).
      - 그 short_id 가 그 샷의 ``visible_entity_ids`` 에 실재해야 멤버 (VE 누락
        샷 억지 포함 금지 — producer 오류 은폐 방지, Codex 세부의견).
        ★identity-variant aware (2026-07-02): subject sid 가 VE 에 없어도
        ``identity_family_by_sid``(entity_relation 데이터 계약, character 한정)의
        같은 family 멤버가 VE 에 실재하면 같은 인물로 보고 멤버 인정 — 매칭
        내역은 skipped 채널에 ``subject_matched_via_identity_variant`` 진단으로
        기록(subject/ve_member/family 감사 가능).
      - 같은 ``scene_index`` 의 같은 short_id 가 ≥2 selected shots 에 등장 → seed.
        shot_index 인접 조건 없음 (insert/cutaway 건너뛰는 연속성, Codex Q2).
      - 한 그룹의 멤버 subject_state 가 섞이면(예: dead + unconscious)
        ``mixed_immobilized_states`` diagnostic + v1 skip (Codex 보수안).

    score (cap 선별용, deterministic): close/insert framing 가중 + 멤버수.

    Returns: (seeds, skipped). seed 항목:
        {character_short_id, scene_index, subject_state,
         member_shots: [[si, shi], ...], score}
    character_anglessubject_stater   rt   r   r'   r(   :z<empty>ambiguous_subject_nameunknown_subject_namer+   subject_not_in_visible_entitiesidentity_family$subject_matched_via_identity_variantr   )r,   r-   	ve_memberr   )r$   r   r$   rN   mixed_immobilized_states)r,   r-   statesc              3  T   K   | ]  }j                  |f      t        v rd  ! ywrP   rV   )rQ   rn   rK   rl   s     rH   rT   z3detect_immobilized_subject_seeds.<locals>.<genexpr>j  s0      
#""B9-1FF s   %(rY   )character_short_idr#   r   r[   r\   c                     | d    | d   | d   fS )Nr\   r#   r   r^   r_   s    rH   ra   z2detect_immobilized_subject_seeds.<locals>.<lambda>u  s    qzk1]+;Q?S=TUrJ   rb   )rc   rd   r2   r1   r   rf   r5   nextiterre   r   rg   ri   )staging_by_shotrj   r:   name_to_sidsrK   r   groupedr<   rm   rn   rR   stagingvecastatenamesidsrS   famfam_hitentryr;   r   by_shimr   rp   rr   rl   s       `                       @rH    detect_immobilized_subject_seedsr     s?   F &+O;=G$&G$**,H(#Cs)C%))#.GZ^^C,23Bkk"45;;/+EKR8vvk2.4"#''-6t9>NN"#B4r#a0A	/B C?C#;(>$ 
 4:&b=17R<<SAJSUC$S2X.G"&'t2cU!C5$9&G1 7=c{E"34u-  NN"#B4r#au 5"H%,QZ+1#;	$  ""B9b188#&?AE < $ -X #%E$W]]_5	S7!#Aaoq/AB v;?V]]_%v;?NNB4q4 . 
 v 

 
 	"%!$v,/278%3b#Y%8 1_s5z1
 	) 66 
JJUJV'>	 9s   L c                    t        |d      }t        t        |       |      }| d| }| |d D cg c]#  }d|d    d|d    d|j                  d      d	% }}||fS c c}w )
u   immobilized seed 를 자체 cap 으로 선별 (printed_prop cap 과 독립 — Codex ⑧).

    초과분은 silent drop 금지 — skipped(reason="immobilized_cap") + score 기록.r   Nzimmobilized:Sr#   r   r   immobilized_capr\   r,   r-   r\   )maxminrf   r2   )r;   captakerm   r`   r<   s         rH   !select_immobilized_seeds_with_capr   y  s     c1+Cs5z3DUd|H tu A !=!1 2!A6J4K3LM$quuW~	?  
 Ws   (A)rK   related_zoom_group_idsc               <   |xs i }| d   }| d   }g }i }d | d   D        D ]P  \  }}	|j                  ||	f      t        v rt        nt        }
|j	                  ||	|
d       ddi|t        ||	      <   R d | d   D        D 	ch c]  \  }}	|		 }}}	i }|j                  d	      xs g D ]w  }t        |t              s|j                  d
      }t        |j                  d      xs d      j                         }t        |t              sb||v sg|sj||t        |      <   y d| d|j                          dt        ||j                  d      xs g || d   t        |j                  d      xs d      j                         |dt        |xs g       |dS c c}	}w )u  P8 seed + LLM 추출 → immobilized_subject group.

    member role 은 framing_scale structured enum 으로만 분류 (close/insert →
    close_insert, 그 외 → environment) — 의미 추론 없음. subject_anchor 는 그룹
    전체 공유 SOT (locked_elements = evidence-backed 공유 시각 사실) + per-shot
    visible focus (insert 샷에 wide body 강요 방지). zoom 그룹은 SOT 가 아니라
    참고 링크(related_zoom_group_ids) 로만 남긴다 (Codex Q1).r   r#   c              3  2   K   | ]  }t        |        y wrx   tuplerQ   r   s     rH   rT   z2build_immobilized_subject_group.<locals>.<genexpr>  s     =(<1eAh(<   r[   r#   r$   roleenforce_state_continuityTc              3  2   K   | ]  }t        |        y wrx   r   r   s     rH   rT   z2build_immobilized_subject_group.<locals>.<genexpr>  s     'O:NQa:Nr   per_shot_visible_focusr$   visible_focusr   vca-s-z-immobilizedlocked_elementsr   shared_state_contract)r   r   r   r   )group_idanchor_typer   r   subject_anchorr   per_shot_consumption_hints)r2   rW   ROLE_CLOSE_INSERTROLE_ENVIRONMENTr5   shot_hint_keyr3   r   r   r   r   lowerANCHOR_TYPE_IMMOBILIZEDrh   )r,   
llm_resultrK   r   rS   rl   r   hintsmsimshir   _member_shisfocus_by_shotr   f_shif_texts                    rH   build_immobilized_subject_groupr     s    &+O
#
$C	m	B$&G')E=^(<=	T ""C;/3HH ! 	
 	s$MN+Et*LmC&' > (P$~:N'OP'OGAt4'OKP$&M 89?R?%&		,'UYY/526<<>eS!e{&:v(.M#e*% @ B4q\:.%>>*;<B"%!/2%(67=2&??Duw&3
 #''='C"D&+  Qs   Fc           
     4   i }t        | j                               D ]  }| j                  |      xs g }g }t               }|D ]  }|j                  d      |j                  d      }}t	        |t
              rt	        |t
              sF||f}	|	|v rO|j                  |	       |j                  |	t        |j                  d      xs d      f        |j                  d        d}
|D ])  \  }	}|
|	|
k7  r|	|vr|
|t        d||	<   |t        k(  s(|	}
+  |S )	u  A5 (2026-07-02) — immobilized 그룹 prev 완성프레임 chaining 계획 (pure).

    star-to-environment (Codex 합의): 각 멤버 샷을 (scene_index, shot_index) 순으로
    정렬해, 자신보다 **앞선 가장 가까운 role=environment 멤버**를 anchor_source 로
    매핑한다. immobilized state 는 정의상 불변이라 환경(wide) 프레임이 room state·
    지지면·소품 연속성의 가장 안정적인 SOT — close/insert 프레임은 앵커로 쓰지
    않는다(직전-멤버 체인은 insert 가 끼면 정보가 줄고 오류 전파가 큼).

      - 선행 env 멤버가 없는 멤버(그룹 첫 env 이전 샷 포함)는 엔트리 없음 → 소비자
        no-op. env 멤버 자기 자신(anchor==target)은 제외.
      - 한 샷이 여러 그룹 멤버면 group_id 정렬 순 첫 매핑 유지 (결정론).
      - role 은 manifest 의 structured enum 만 조인 — 의미 추론 0.
    r#   r$   r   r   c                    | d   S )Nr   r^   )es    rH   ra   z3build_immobilized_prev_frame_plan.<locals>.<lambda>  s    1Q4rJ   rb   N)anchor_sourcer   anchor_role)rd   keysr2   r1   r3   r   r8   r5   r   ri   r   )members_by_groupplangidr   orderedseenr   r   r   rR   last_envr   s               rH   !build_immobilized_prev_frame_planr     s     +-D&++-."&&s+1r-/ UAm,aeeL.ACsC(Zc-B;Cd{HHSMNNCQUU6]%8b!9:;  	(&* IC#xCtO%- ##3S	
 '' !! /2 KrJ   rN   DEFAULT_D_RESERVED_SLOTS)
d_reservedc          	        t        |d      }t        t        |d      t        |      |      }t        t        |       ||z
        }t        t        |      ||z
        }| d| D cg c]
  }t        |f c}|d| D cg c]
  }t        |f c}z   }| |d D cg c]  }t        t        |      dd c}||d D cg c]%  }t        t        |      d|j                  d      d' c}z   }||fS c c}w c c}w c c}w c c}w )u,  cap 안에서 (anchor_type, seed) 선별 — type 별 공정성 장치 포함.

    Codex W_A_STAGE_REVIEW (2026-06-12): C 가 많은 에피소드에서 1차 production
    핵심인 D 가 cap 에 밀리지 않도록 D(printed_prop) 에 최소 예약 슬롯을 둔다.
      1. D 가 먼저 min(d_reserved, len(d_seeds), cap) 슬롯 확보 (score 순)
      2. 남은 슬롯을 C 가 채움 (dep 순)
      3. 그래도 남으면 D 추가분 (score 순)
    초과분은 skipped(reason="cap_after_priority_score") + score 기록 —
    silent drop 금지.r   Ncap_after_priority_scorer+   r\   r   )r   r   rf   ANCHOR_TYPE_ZOOMANCHOR_TYPE_PROP_seed_labelr2   )	c_seedsd_seedsr   r   d_takec_taker`   rm   r<   s	            rH   select_seeds_with_capr     s7     c1+CZ#S\37FWsV|,FWsV|,F )0(89(81
A	(89*1'6*:
;*:Qa *:
;	<  !!A -q1=WX! !	 "A -q1-g	H!	G W 	:
;	s   $C-<C2C7:*C<c                    | t         k(  r"|j                  dddg      }d|d    d|d    S d|j                  dd       S )	Nr   ?zzoom:Sr   r(   r   zprop:rZ   )r   r2   )r   r,   zs      rH   r   r     sS    &&HHVc3Z(!vR!v&&488OS1233rJ   c                    |  d| S )Nr   r^   )r#   r$   s     rH   r   r   &  s    ]!J<((rJ   c                V   | d   }| d   }|j                  d      xs t        }d|d    d| t        |d   |d   t        d|d   |d   t        dg|j                  d	      xs g |j                  d
      xs g ||j                  dd      t        | ddit        | d|t        k(  iidS )u  C seed + LLM 추출 결과 → zoom_continuity group. hint 는 deterministic:
    source_wide → include_in_wide / zoom → crop_from_source (camera_relation 이
    punch-in 일 때만 — Cinematography 축. 부재(legacy/stub)는 punch-in 취급 =
    기존 동작 보존).r)   r   camera_relationr   r   z-zoom-r   r   r   wide_shot_contractcamera_relation_evidencer   include_in_wideTcrop_from_source)r   r   r   r   r   r   r   r   )r2   CAMERA_RELATION_PUNCH_INr   ROLE_SOURCE_WIDE	ROLE_ZOOMr   )r,   r   ordinalsrcr   relations         rH   build_zoom_groupr	  *  s     x.C<D~~/0L4LHDG9F7)4'F#a&BRS G47IN
 &>>*;<B(nn-ABHb#$.NN3Mr$R3"3T!:4 "H0H$H#'
 rJ   )rK   c                  |xs i }| d   }g }i }d | d   D        D ]P  \  }}|j                  ||f      t        v rt        nt        }|j	                  |||d       ddi|t        ||      <   R d|j                          dt        ||j                  d	      xs g ||j                  d
d      |j                  dd      |j                  dd      |j                  dd      |j                  dd      d|dS )u   D seed + LLM 추출 결과 → printed_prop group. member role 은
    framing_scale structured enum 으로만 분류 (close/insert → close_insert,
    그 외 → environment) — 의미 추론 없음.rZ   c              3  2   K   | ]  }t        |        y wrx   r   r   s     rH   rT   z#build_prop_group.<locals>.<genexpr>W  s     ;&:E!H&:r   r[   r   keep_real_scaleTzvca-z-propr   carries_printed_contentapplicability_evidencer   printed_contentphysical_formscale_contract)rZ   r  r  r  r  r  )r   r   r   r   prop_anchorr   )r2   rW   r   r   r5   r   r   r   )	r,   r   rK   rS   r   r   rl   rn   r   s	            rH   build_prop_groupr  J  s    &+O

C$&G')E;d>&:;C ""B9-1FF ! 	
 	rdKL):D(AmB$% < 399;-u-'%>>*;<B  (2~~6OQU'V&0nn5Mr&R)~~.?D'^^OR@(nn-=rB

 ',! rJ   c                    t         | |dS )N)schema_versiongroupsdiagnostics)r   )r  r  s     rH   build_manifestr  s  s    
 )" rJ   T)include_framingc               L   | j                  dd      du r|xs dj                         S g }|r|j                  d       | j                  d      r+|j                  t        | d         j                                | j                  d      r.|j                  dt        | d         j                         z          | j                  d	      r+|j                  t        | d	         j                                d
j	                  |      }|xs dj                         }|r|d|z   z  }|S )u0  printed_prop anchor → reference image generation prompt overlay (deterministic).

    Codex 판정 (W_A_STAGE_REVIEW / 설계 D-①): anchor 의 printed_content /
    physical_form / scale_contract 가 **우선**하되 기존 canon t2i 를 버리지
    않고 스타일/맥락 source 로 뒤에 보존한다. canon 자체(EntityCanon.t2i_prompt
    / entity_t2i cp)는 절대 변경하지 않는다 — 그것이 provenance.

    include_framing=False 는 description 슬롯용 — ref_image_pipeline 의 prop
    템플릿이 product-photo framing 을 이미 제공하므로 (entity_description 으로
    조립, t2i_prompt 는 템플릿 부재시 fallback — 2026-06-12 실측) framing
    상수를 중복하지 않는다.

    LLM 재호출 0 — 스파이크2 ref_prompt 패턴의 문자열 조립만.
    r  TFr   z_Photorealistic product photo of a single physical prop, isolated on a plain neutral background.r  r  z%The content printed/displayed on it: r   uy   
Original canon description (style and context reference — where it conflicts with the anchor above, the anchor wins): )r2   r   r5   r   join)r  	canon_t2ir  partsoverlays        rH   build_prop_ref_prompt_overlayr     s   2 0$75@R&&((E(	
 'S_56<<>?()3+/01779:	
 '(S%567==?@hhuoGb'')IBDMN	
 NrJ   z[CLP]\d{2,3}(?:O\d{2,3})?c                F    t        t        j                  | xs d            S )Nr   )r1   _ENTITY_TOKEN_REfindall)texts    rH   extract_entity_tokensr%    s    ''
344rJ   c                0    t        |      t        |       z
  S )u   revised 가 original 에 없던 entity ID 토큰을 도입했으면 그 집합 반환.

    비어있지 않으면 audit 위반 — caller 는 revised 를 폐기(substitution 미적용)
    하고 diagnostic 으로 기록한다 (비차단).)r%  )originalreviseds     rH   revised_prompt_new_tokensr)    s    
 !),A(,KKKrJ   z	^C\d{2,3}c                    t               }t        |       D ]:  }t        j                  |      }|s|j	                  |j                  d             < |S )uP   인물(C) entity ID 토큰의 base 집합 — C08O06 → C08. L/P 토큰 제외.r   )r1   r%  _CHAR_BASE_REmatchr8   group)r$  basestokr   s       rH   char_token_basesr0    sF    eE$T*$IIaggaj! + LrJ   c                l    t               }| xs g D ]  }|t        |      z  } |t        |xs d      z
  S )uW  zoom 멤버 프롬프트들의 인물 토큰 base 중 source 프롬프트에 없는 것.

    비어있지 않으면 그 source variation 은 zoom 의 피사체를 담을 수 없다 —
    caller 는 해당 variation 재작문을 skip 하고, 전 variation 이 그러면 seed
    자체를 skip(reason="zoom_subject_absent_in_source") 한다.r   )r1   r0  )zoom_promptssource_prompt
zoom_charsps       rH   zoom_subject_missing_charsr6    sC     5JR&q))
  ()<"===rJ   )extra_known_shotsknown_char_short_idsc          
     f   g }|xs
 t               }| j                  d      }t        |t              sdgS t               }|D ]  }|j                  d      xs d}	|	|v r|j	                  |	 d       |j                  |	       |j                  d      }
|
t        t        t        fvr|j	                  |	 d|
       }|j                  d      xs g }t        |      d	k  r|j	                  |	 d
       g }|D ]  }|j                  d      |j                  d      }}|j	                  |j                  dd             ||f}t        |t              r2t        |t              r"||j                  |t                     vs||vs|j	                  |	 d| d| d        |j                  d      xs g D ]@  }t        |j                  dd            j                         r-|j	                  |	 d       B |j                  d      xs i }|
t        k(  rpt        |vst        |vr|j	                  |	 d       |j                         D ]7  \  }}|j                  d      st        |vs!|j	                  |	 d| d       9 F|
t        k(  rB|j                  d      xs i j                  d      }||vsy|j	                  |	 d|       |j                  d      xs i j                  d      }|s|j	                  |	 d       |||vs|j	                  |	 d |        |S )!u   schema parse + 조인 무결성만 — 의미 게이트 없음 (Codex 가이드 5).

    Returns: violation 문자열 목록 (빈 목록 = 통과).
    r  zgroups must be a listr   z	<missing>z: duplicate group_idr   z: invalid anchor_type r   rN   z: needs >=2 membersr#   r$   r   r   z
: member Sr(   z not a known selected shotr   evidence_quotez': locked_element without evidence_quoter   z+: zoom group needs source_wide + zoom rolesr  z(: crop_from_source without source_wide ()r  rZ   z: unknown prop_short_id r   r   z.: immobilized group missing character_short_idz: unknown character_short_id )r1   r2   r3   rh   r5   r8   r   r   r   rf   r   r   r   r  r  rc   )manifestr:   known_prop_short_idsr7  r8  
violationsr  seen_group_idsgr   atyper   rolesr   rl   rn   rR   ler   key_shintrS   csids                          rH   validate_anchor_manifestrG    s,    J)2SU\\(#Ffd#'(("uNeeJ.;. %9:;3m$)+;=TUU%;E9EF%%	"(bw<!%89:AeeM*AEE,,?BLLvr*+s)Cr3'!#s+|//CE::sJ[?[!!SEB4r#>X"YZ  %%)*0b0Brvv.34::<!!SE)P"QR 1 239r$$u,	0F!!SE)T"UV${{}t88./4DE4Q%%-UV[U\\]&^_  - &&55'-222?CC..!!SE)A#"IJEE*+1r667KLD!!SE)W"XY%1dBV6V!!SE)Fth"OP] ` rJ   )r9   List[Dict[str, Any]]r:   Dict[int, Set[int]]r!   z$Optional[Callable[[int, int], bool]]r   zGTuple[List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]]])rj   Dict[ShotKey, List[str]]r:   rI  rk   Set[str]rK   Optional[Dict[ShotKey, str]]rL   z!Optional[Dict[ShotKey, Set[str]]]r   rH  )r   rH  ru   r   r   Dict[str, Set[str]])r   Dict[ShotKey, Dict[str, Any]]rj   rJ  r:   rI  r   rM  rK   rL  r   zOptional[Dict[str, Set[str]]]r   1Tuple[List[Dict[str, Any]], List[Dict[str, Any]]])r;   rH  r   r   r   rO  )
r,   Dict[str, Any]r   rP  rK   rL  r   zOptional[List[str]]r   rP  )r   zDict[str, List[Dict[str, Any]]]r   rN  )
r   rH  r   rH  r   r   r   r   r   z=Tuple[List[Tuple[str, Dict[str, Any]]], List[Dict[str, Any]]])r   r   r,   rP  r   r   )r#   r   r$   r   r   r   )r,   rP  r   rP  r  r   r   rP  )r,   rP  r   rP  rK   rL  r   rP  )r  rH  r  rP  r   rP  )r  rP  r  r   r  r7   r   r   )r$  r   r   rK  )r'  r   r(  r   r   rK  )r2  	List[str]r3  r   r   rK  )r<  rP  r:   rI  r=  rK  r7  zOptional[Set[ShotKey]]r8  zOptional[Set[str]]r   rQ  )=__doc__
__future__r   loggingretypingr   r   r   r   r   r	   r
   app.core.subject_stater   	getLogger__name__loggerr   __annotations__r   r   r   r   r   r6   r4   r  r  r   r   r  CAMERA_RELATION_DIFFERENTCAMERA_RELATION_UNCERTAIN	frozensetrW   r   ShotKeyrI   rs   r   r   r   r   r   r   r   r   r   r	  r  r  r   compiler"  r%  r)  r+  r0  r6  rG  r^   rJ   rH   <module>ra     s  , #  	 B B B 7			8	$  3   !   $ !  0 !'   	"    0 : '  "7H"56 
S/ AE	G,&G,%G, >	G,
 MG,d 59DH6(6%6 6
 26 !B6 6~ #1#1 1 	1t 59<@q2q(q &q &	q 2q :q 7qh	 7, 59267
77 2	7
 07 7t*5*"*` !" # ! /!!!!! 
!
 ! C!H4)
  	H 59	&
&& 2	&
 &R  $ !	111 	1
 	1t 2:::; 5L$ 

<(>>,/>>, 15/3CC%C #C
 .C -C CrJ   