
    ,j8                        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  ej                  e
      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	 	 	 	 	 	 	 	 	 	 	 	 ddZy)u  G4.6 Wave A1 RC-C — entity reference 보호 룰.

low_freq_skip 의 deterministic protection cascade (모두 generic, 시나리오
의존 0건):
- variant pole / variant_self (RO-1) — character-character + identity/transformation
  관계의 second-participant 만 (possession prop / location-location 은 제외)
- is_base_for_variant — RelationFact 기반 base 측 보호
- pipeline manifest cascade — scene_director / shot_validator / shot_director
  + scene_detail rescue (force/retry 시 보강) — production data 만 읽음
- count > 1 — frequency 자체

이전 iter 의 keyword-based protection 은 제거됨 — 보호 판정은 graph (RelationFact)
+ pipeline manifest signal 만으로 derive.
    )annotationsN)Path)Anyc                   | sg S t        | t              r| D cg c]  }t        |       c}S t        | t              r	 t        j                  |       }t        |t              r|D cg c]  }t        |       c}S t        |t              r'|j                         D cg c]  }t        |       c}S t        |      gS g S c c}w # t        j
                  t        f$ r | gcY S w xY wc c}w c c}w )uS   `EntityCanon.stable_traits` 는 JSON 문자열 (또는 이미 list). 양쪽 지원.)	
isinstanceliststrjsonloadsJSONDecodeError	TypeErrordictvalues)rawtparsedvs       P/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/entity_protection.py_parse_traitsr      s    	#t #$1A$$#s	ZZ_F fd#$*+FqCFF++fd#$*MMO4OqCFO44F}I % $$i0 	5L	 ,4s"   CC (C)C.C&%C&c                D   ddl m} t        |j                        | z  dz  dz  |z  |z  dz  }|j	                         sy	 t        j                  |j                  d            S # t
        j                  t        f$ r!}t        j                  d	||       Y d}~yd}~ww xY w)
u   checkpoint manifest loader. 부재 시 None (caller 가 union 시 무시).

    PRO-5: settings.projects_dir 절대경로 사용 (process cwd 비의존).
    r   settingscheckpointsepisodesmanifest.jsonNutf-8encodinguM   entity_protection._load_cp(%s): manifest 파싱 실패 — %s — None 반환)app.core.configr   r   projects_direxistsr
   r   	read_textr   OSErrorloggerwarning)
project_id
episode_idstep_idr   cp_pathexcs         r   _load_cpr+   ,   s    
 ) 	X""#
	$	%'1	24>	?AH	I
	 
 >>zz'++W+=>>  '* [S	
 s    $A% %B>BBc                B    | rd| v r| j                  d      d   S | xs dS )uH   C##O## composite → C## base. C## / L## / P## / O## bare 는 그대로.Or    )split)short_ids    r   _short_id_baser1   D   s(    %-#/8>>#q!WTVW    c                   t               }t        | |d      }|r`|j                  di       j                  dg       xs g D ]7  }|j                  dg       xs g D ]  }|j                  t	        |              9 t        | |d      }|r{|j                  di       j                  dg       xs g D ]R  }|j                  dg       xs g D ]7  }|j                  dg       xs g D ]  }|j                  t	        |              9 T t        | |d      }|r{|j                  di       j                  dg       xs g D ]R  }|j                  dg       xs g D ]7  }|j                  d	g       xs g D ]  }|j                  t	        |              9 T t        | |d
      }	|	r|	j                  di       j                  dg       xs g D ]  }|j                  dg       xs g D ]  }|j                  t	        |              |j                  d      xs i }
|
j                  d      xs i }|j                  dg       xs g D ]A  }t        |t              s|j                  d      }|s(|j                  t	        |             C  |S )um  pipeline manifest cascade — short_id 집합 union.

    sources (모두 manifest-driven, union):
    - scene_director.data.scenes[].present_entity_ids
    - shot_validator.data.scenes[].shots[].character_ids (RO-1)
    - shot_director.data.scenes[].shots[].visible_entity_ids (production schema)
    - scene_detail rescue — visible_entities + render_prompt_card.asset_requirements.required_refs[].id
      (force/retry 시 보강)

    DB EntityEpisodeLink.t2i_appearance_count 기반 source 는 의도적으로 제외
    (Codex iter2 BLOCKING — count >= 1 보호는 low_freq_skip 정책 자체를 무력화).
    scene_directordatascenespresent_entity_idsshot_validatorshotscharacter_idsshot_directorvisible_entity_idsscene_detailvisible_entitiesrender_prompt_cardasset_requirementsrequired_refsid)setr+   getaddr1   r   r   )r&   r'   	short_idsdirector_cpscsidsv_cpshsdir_cpsd_cprpc	asset_reqrefrids                 r   _collect_required_entity_idsrR   I   su    %I:z3CDK//&"-11(B?E2EBvv2B7=2=nS12 > F Z-=>E))FB'++Hb9?R?BffWb)/R/66/26<"<CMM."56 = 0 @
 z:?G++fb)--h;ArABffWb)/R/66"6;ArACMM."56 B 0 B
 Z^<E))FB'++Hb9?R?Bvv0"5;;nS12 <&&-.4"C 45;I }}_b9?R?!#t,ggdmMM."56 @ @ r2   c                   t               }t        | |d      }|r|j                  di       j                  dg       xs g D ]  }|j                  d      xs i }|j                  d      xs i }|j                  dg       xs g D ]A  }t        |t              s|j                  d      }|s(|j                  t        |             C  |S )ug  Phase 1 — reference 생성 보호의 단일 SOT.

    `scene_detail.render_prompt_card.asset_requirements.required_refs[].id` 만
    수집한다 (`_collect_required_entity_ids` 의 4-source broad union 과 달리
    scene_director.present / shot_validator.character_ids / shot_director.
    visible_entity_ids / scene_detail.visible_entities 전부 **제외**).

    설계 근거: docs/reference-necessity/index.html §6.1 — broad union 은
    catalog/audit 용도로만 남기고, reference generation 보호는 실제 attach
    계약(required_refs)에 가장 가까운 단일 source 로 좁힌다.
    r=   r5   r6   r?   r@   rA   rB   )rC   r+   rD   r   r   rE   r1   )	r&   r'   rF   rM   rK   rN   rO   rP   rQ   s	            r   _collect_reference_required_idsrT   }   s     %IZ^<E))FB'++Hb9?R?B&&-.4"C 45;I }}_b9?R?!#t,ggdmMM."56 @ @ r2   c                J   ddl m} ddlm} t	        ||      } || ||d      }|r% t               j                  |j                          n	t               }t        | ||t        |            }||z  |z  }	|	si S i }
| j                  |      j                  |j                  |k(  |j                  j                  |	            j                         }|D ]N  }|j                  xs d}||v rd|
|j                  <   '||v rd|
|j                  <   ;||v s@d	|
|j                  <   P |
S )
uz  참조 생성 보호 대상 — canon id → 보호 사유.

    세 출처다.

    - ``scene_detail.required_refs`` (`_collect_reference_required_ids`) —
      종전의 단일 SOT.
    - **선택된 샷의 VE 에 있는 캐릭터** (2026-09-19 사용자 지시). 샷 이미지
      (still_recipe)는 VE 를 보고 캐릭터 참조를 붙인다 — 그것이 실제 첨부
      기준이다. 둘을 따로 보면 「샷에는 나오는데 참조는 안 만든」 캐릭터가
      생긴다(컨트리로드 2판 6명·6샷 — 여섯 다 `t2i_count=0 · required=False`
      로 건너뛰었다. 찰리 홀로그램은 scene_detail 이 visible 에는 넣고
      required_refs 에는 안 넣었다).
    - **선택된 샷의 촬영 계획이 화면 안에 세운 캐릭터** (`_staged_in_frame_sids`
      — 스틸 조립이 VE 밖이어도 참조를 붙이는 인물). 이 출처는 배경 전용 샷을
      안 가르므로 붙지 않을 참조를 만들 수 있다 — **샷 그림은 안 망가지지만
      참조 생성 요금은 든다**(해가 없다 ≠ 무료).

    ★캐릭터만 넓힌다 — 보호를 좁힌 설계 취지(쓸데없는 참조 생성을 막는다)는
     그대로이고, 선택 샷 VE 는 그 샷이 **실제로 붙이는** 것이라 넓게 잡는
     출처가 아니다.
    ★short_id 는 프로젝트마다 따로다 — project 로 거른다.
    r   EntityCanon)load_character_sids_by_tagT)selected_onlyr.   zscene_detail.required_refszselected shot VEzselected shot staging)app.models.projectrW   &app.modules.pipeline.shot_ref_classifyrX   rT   rC   unionr   _staged_in_frame_sidsqueryfilterr&   r0   in_allrB   )dbr&   r'   rW   rX   sd_sidsby_tagve_sidsstaged_sidswantoutrowserI   s                 r   $collect_reference_required_canon_idsrk      s"   2 / .j*EG'
J
$8F9?V]]_5SUG'J
(+F5KW{*D	C
	""j0  $$T*

 
 	 jjB'>4CIG^*CIK/CI  Jr2   c                   ddl m} ddlm} ddlm} ddlm}m} t        |j                        |z  dz  dz  |z  dz  d	z  }	|	j                         s
t               S 	 t        j                  |	j                  d
            j!                  d      xs i j!                  d      xs g }
ddlm} t         || ||            }| j-                  |      j/                  |j0                  |k(  |j2                  dk(        j5                         D cg c]  }|j6                  |v r| }} |d |D              }|D ci c]&  }|j8                  s|j6                  |j8                  ( }}t               }|
D ]  }t;        |t<              s|j!                  d      |j!                  d      }}|| |t?        |      t?        |            |vrX ||j!                  d      xs g ||j!                  d            D ]$  }||v s|jA                  tC        ||                &  |S # t"        $ r*}t$        j'                  d|       t               cY d}~S d}~ww xY wc c}w c c}w )ue  선택 샷의 촬영 계획(shot_staging)이 **화면 안에 세운** 등록 인물 short_id
    (2026-09-19, Codex 지적 — 참조를 붙이는 명단과 만드는 명단이 같아야 한다).

    스틸 조립은 이 인물에게 참조를 붙인다(`still_recipe_service._staged_in_frame`).
    실측: 셰퍼드·재판관이 촬영 계획엔 있고 VE 엔 없어 **참조가 아예 없었다** —
    붙일 것이 없으면 이름만 실린다.

    ★이름 조인은 스틸 조립과 **같은 함수**다(`staged_in_frame_character_ids` —
     POV 제외 · 동명 제외). 배경 전용 여부는 가르지 않는다 — 넓게 잡아도 샷
     그림은 안 망가지지만(그 샷에 안 붙을 뿐) **참조 생성 요금은 든다**.
     실측(컨트리로드 2판)에서 이 출처로 새로 생기는 참조는 2장(셰퍼드·재판관).
    r   r   rV   )tag_of)staged_in_frame_character_idsunique_character_ids_by_namer   r   shot_stagingr   r   r   r5   r9   u5   entity_protection: shot_staging 을 못 읽었다: %sN)active_episode_canon_ids	characterc              3  T   K   | ]   }|j                   |j                  xs d f " yw)r.   N)rB   name).0rj   s     r   	<genexpr>z(_staged_in_frame_sids.<locals>.<genexpr>  s%      .-&+qvv|es   &(scene_index
shot_indexcharacter_anglespov_character)"r   r   rZ   rW   r[   rm   $app.modules.pipeline.shot_visibilityrn   ro   r   r    r!   rC   r
   r   r"   rD   	Exceptionr$   r%   app.core.entity_identityrq   r^   r_   r&   entity_typera   rB   r0   r   r   intrE   r	   )rb   r&   r'   selected_tagsr   rW   rm   rn   ro   pathr9   r*   rq   activerj   chars
id_by_name	sid_by_idrh   ssishieids                          r   r]   r]      sQ    ).=
 &&'*4}D%&(679HID;;=uDNNGN<=AA&I G+(* 	 B)"j*EFF HH[!VK**j8++{:<SU  446> 	
 
  . .-&+.- -J+0?5aAJJqzz!5I?EC!T"%%&l(;C:s2wC(A(V0()/Ro&(C iIcN+,	(  JE  NPSTu @s1   AH  I8I
I 	I)IIIc                   | D ci c]  }|d   |j                  d       }}i }|D ]&  }|j                  |d   g       j                  |       ( t               }|D ]  }|j                  d      dvr|j                  |d   g       }	t	        |	      dk  r;t        |	      D ]V  \  }
}|	|
dz   d D ]F  }|j                  |d	         d
k7  r|j                  |d	         d
k7  r3|j                  |d	          H X  |S c c}w )u  RO-1 narrowed — character-character + relation_family in
    {identity, transformation} 의 second participant 집합 (variant 측 canon_id).

    `build_visual_dependency_graph` 의 generic deps 는 possession (character→prop)
    + location-location 도 dependency 로 만들어 variant_self 가 너무 넓어짐
    (Codex iter2 IMPORTANT). 이 helper 는 RO-1 의 의도인 'variant pole 자체
    보호' 만 정확히 반영.

    relations 는 [{id, relation_family}], participants 는 [{relation_id, canon_id}].
    rB   r~   relation_idrelation_family)identitytransformation      Ncanon_idrr   )rD   
setdefaultappendrC   len	enumeraterE   )entities	relationsparticipantsrj   entity_types	rel_partspvariant_polesrelpartsip1p2s                r   compute_variant_pole_idsr     s"    <DD8aAdGQUU=118LD!#IQ}-r299!<  "eM77$%-KKc$i,u:>u%EArAEFm  ##BzN3{B##BzN3{B!!"Z.1 $ &  + Es   C?c                P    | j                  dd      }|dv ry|dkD  ry|ry|ry|ryy)u  deterministic protection cascade — skip 조건 모두 만족 시 True.

    보호 발동 (return False) 우선순위:
    - location/outlook entity_type — helper 진입 시 False (caller 가 이미 skip)
    - count > 1 — frequency 자체로 충분
    - is_base_for_variant — variant pole 의 base 측
    - is_variant_self — character-character identity/transformation variant 자체 (RO-1)
    - required_by_pipeline — manifest 4-source cascade union 안

    위 조건 모두 안 걸리면 skip (return True).
    r~   r.   )locationoutlookFr   T)rD   )rj   countis_base_for_variantis_variant_selfrequired_by_pipelineetypes         r   should_skip_low_freqr   ;  s?    $ EE-$E''qyr2   )r   r   returnz	list[str])r&   r	   r'   r	   r(   r	   r   zdict | None)r0   r	   r   r	   )r&   r	   r'   r	   r   set[str])rb   r   r&   r	   r'   r	   r   zdict[str, str])
rb   r   r&   r	   r'   r	   r   rC   r   r   )r   
list[dict]r   r   r   r   r   r   )rj   r   r   r   r   boolr   r   r   r   r   r   )__doc__
__future__r   r
   loggingpathlibr   typingr   	getLogger__name__r$   r   r+   r1   rR   rT   rk   r]   r   r    r2   r   <module>r      s   #    			8	$&0X
1h6888*-88v@@@*-@>A@@F""%/"?I""J  	
  
r2   