
    sjIy                      d Z ddlmZ ddlZddlZddlZddlm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mZ ddlmZ ddlmZ dd	lmZmZmZ dd
lmZ ddlm Z  ddl!m"Z"m#Z#m$Z$m%Z%  ejL                  e'      Z(ddZ)dddd	 	 	 	 	 	 	 	 	 	 	 ddZ*	 	 	 	 	 	 	 	 ddZ+	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ,dZ-ddZ.d Z/ G d d      Z0y)ue  씬 이미지 reference/entity 해결 서비스 — W5 F22 Phase B.8.

SceneImageService에서 reference/visible-entity 매핑 로직을 이관. 공개 API는
facade가 유지하고 본 서비스는 구현을 담당. Codex B.6 조언(“reference resolution
성격은 validation과 분리”)을 반영한 별도 서비스.

공개 API (SceneImageService facade가 delegation):
- `get_visible_entities(visible_entities_json)` — JSON → EntityCanon lookup
- `get_reference_image_map(visible_entities)` — entity_id → file bytes
- `build_scene_ref_image_map(ref_image_map, entity_lookup)` — location 제외 + outlook + composite + state_variant
- `resolve_refs_for_prompt(t2i_prompt, ...)` — T2I ↔ ref 이미지 매칭 (short_id + 레거시 + state_variant). prop attach 는 Area D-min 이후 `required_refs` (Area B render_contracts SOT) 단일 결정자.

Module-level stateless helpers (facade static wrapper):
- `build_image_index(labeled_refs, entity_lookup)` — Image N 번호 부여 + sid→N 매핑
- `rewrite_t2i_with_image_refs(t2i_prompt, sid_to_img, sid_info)` — C##O##/P## → "from Image N" 치환
    )annotationsN)Path)AnyDictListLiteralOptionalSetTuple)and_not_or_)Session)AppError)LabeledRefPayloadRefRoleErrormake_labeled_ref_payload)validate_keep_elements)is_immobilized_state)CharacterOutlookEntityCanonEntityEpisodeLink
ImageAssetc                    t        | xs i j                  d      xs d      j                         }|r|S | xs i j                  d      xs g }t        |t              rdj                  d |D              }|r|S y)u  식별용 외형 descriptor (Inc2-a 메타-A): i2i 는 픽셀로 대상을 식별하므로
    라벨에는 "이 대상이 화면상 어떻게 생겼나"만 넣는다.

    SOT 우선순위: entity ``description`` → ``visual_traits`` join → "" (generic).
    ``t2i_prompt`` 는 reference 이미지를 *생성*할 때의 지시문(passport / plain white
    background / pose 등)이라 식별 라벨에 부적합 — 절대 쓰지 않는다. 고유명사(이름)도
    라벨에 넣지 않는다 (본문↔ref 연결은 rewrite_t2i_with_image_refs 의 "from Image N"
    번호 메커니즘이 담당).

    ★ production lookup shape (Codex MINOR, 2026-06-28): 주 소비처
    ``load_episode_entity_lookup`` 은 ``id/name/entity_type/short_id/description/
    t2i_prompt`` 6필드만 반환한다 — ``visual_traits`` / ``stable_traits`` 미포함.
    즉 production 에서 descriptor 는 사실상 ``description`` 기반이며, ``visual_traits``
    분기는 그 키를 주는 경로(테스트 등)를 위한 forward-compat 다. ``description`` 이
    비고 ``stable_traits`` 만 외형 데이터를 가진 경우(현 lookup 엔 미노출) descriptor
    가 generic("")으로 degrade 할 수 있으나, ``stable_traits`` JSON 파싱은 전샷 라벨
    (Inc2-a)에 영향을 주는 별도 변경이라 본 wave 범위 밖으로 둔다 (generic 라벨이
    실측 문제로 드러나면 그때 좁은 wave 로 stable_traits fallback 추가).description visual_traits, c              3     K   | ]7  }t        |      j                         st        |      j                          9 y wN)strstrip).0ts     Z/Users/manta/Documents/Projects/TheRoad-I1/backend/app/services/scene_reference_service.py	<genexpr>z'_identity_descriptor.<locals>.<genexpr>A   s'     H2aQ3q6<<>2s   ??)r!   getr"   
isinstancelistjoin)infodescvtjoineds       r%   _identity_descriptorr/   )   su    & 
.4"5;;=D
*"		/	*	0bB"dH2HHM    )	ref_rolesref_role_metadataattached_metac               B   ddl }|t        |      ng }|t        |      ng }|t        |      ng }| rt        |       }	|(t        |      |	k7  rt        dt        |       d|	       |(t        |      |	k7  rt        dt        |       d|	       |(t        |      |	k7  rt        dt        |       d|	       | s| i i |||fS i }
|j	                         D ]  }|j                  dd      }|s||
|<    g }i }t        | d	      D ]  \  }\  }}|d	z
  t        |      k  r||d	z
     nd}|d	z
  t        |      k  r||d	z
     ni }t        |xs i j                  d
      xs d      }|j                  d|      }|r|j                  d      nd}|dv rn|rlt        |
j                  |i             }|dk(  r-t        |xs i j                  d      xs d      }|rd| dnd}nd}|r| d| n|}|||<   ||k7  r|||<   n |dk(  r,|r*t        |
j                  |i             }|rd| nd}|||<   n|j                  d|      }|j                  d|      }|rj|j                  d	      }|j                  d|      j                  d      }t        |
j                  |i             }|rd| nd}|||<   d|v rJ||k7  rE|||<   n?|r;|j                  d	      }t        |
j                  |i             }|rd| nd}|||<   n|}|j                  ||f        |||
|||fS )u  labeled_refs에 Image N 번호를 부여하고 sid→Image N 매핑 반환.

    W5 F22 Phase B.8.4: static helper로 추출 (상태 없음, stateless transformation).

    D5 §4.3 (2026-05-09): attached_meta keyword-only param 도입. label 만 rewrite,
    meta 는 평행 통과 (P1 강화 — label 변형이 meta 에 영향 X). caller 가
    attached_meta=None 호출 시 빈 list 평행 반환 (backward 호환).

    Area #11 v1 W2 (2026-05-18+): ref_roles + ref_role_metadata parallel passthrough
    추가 (4-tuple → 6-tuple). length parity fail-fast (RefRoleError). spec §3.2.

    Returns: (indexed_labeled_refs, sid_to_img_map, sid_info,
              indexed_ref_roles, indexed_ref_role_metadata, indexed_attached_meta)
    r   Nz(build_image_index: attached_meta length z != labeled_refs length z$build_image_index: ref_roles length z,build_image_index: ref_role_metadata length short_idr      sidz[CP]\d{2,3})character_refoutfit_ref_inlinecharacter_state_refr:   statez/state-specific character appearance reference ()z-state-specific character appearance referencezcharacter appearance referencez: prop_refzobject appearance reference: zobject appearance referencez#character\s+(C\d{2,3}(?:O\d{2,3})?)zobject\s+(P\d{2,3})C\d{2,3}z character appearance reference: O)rer)   lenr   valuesr'   	enumerater!   matchgroupr/   searchappend)labeled_refsentity_lookupr1   r2   r3   _re	_attached_roles_metan	_sid_info_einfor7   indexed_refs
sid_to_imgilabel	img_bytes_role_md	_sid_full_bm	_base_sid_desc_state_head	new_label_char_match_prop_match	_char_sid_psids                                  r%   build_image_indexrc   G   s   ,  (5'@]#bI ) 5T)_2F'8'DD"#"E $Y1)<:3y>:JJbcdbef   S[A%56s6{mC[\][^_  (SZ1_>s5zlJbcdbef  RVUI==!#I&&(jjR(#IcN )
 L!#J!*<!;E9 "#QV!4q1u"!ec%j0eAElb.4"5	ii	2$'CIIaLR	QQV_(y")EFE--ciR__W5;<" K6(RST(W  9/45'E7+%I$%Jy!I%()
9%j Y(y")EFECH7w?NkI$%Jy! **%KUSK**%;UCK'--a0	IIk9=CCAF	,Y]]9b-IJJO>ugFUu	()
9%)#	Y(>,-Jy)#))!,,Y]]5"-EFGL;E7CRo	$%
5!!	Y	23c "<j YyHHr0   c                   ddl }|r| s| S | }t        |j                         d       D ]  }||   }|j                  d|      }|r9d| }d|vrd|j	                  |      z   d	z   }	nud|j	                  |      z   dz   }	n]|j                  d
      rK|j                  |i       }
|
j                  dd      }|rd| d| nd| }d|j	                  |      z   dz   }	n|j                  |	||      } |S )un   T2I의 C##O## / P##을 'the [desc] from Image N'으로 치환.

    W5 F22 Phase B.8.4: stateless helper.
    r   Nc                    t        |        S r    )rA   )xs    r%   <lambda>z-rewrite_t2i_with_image_refs.<locals>.<lambda>   s
    Awr0   keyr>   zthe character from Image r?   z\bz(?:O\d{2,3})?\bPnamer   zthe z from Image zthe object from Image )r@   sortedkeysrD   escape
startswithr'   sub)
t2i_promptrR   sid_inforJ   	rewrittenr7   img_n_char_mreplacementpattern_info_names               r%   rewrite_t2i_with_image_refsrz      s    ZIjoo'->?3))K-5eW=K#~#**S/14FF#**S/1E9^^C LLb)EIIfb)E?DD|E7;LbchbiJjKcjjo-5GGGG[)<	! @$ r0   c                  |syddl m} t               }|j                  d      xs g D ]V  }|j                  d      dk7  r|j                  dd      }	|	s.t               }
|D ]R  }|j                  d	      |	k(  s|j                  d
      dk(  s-|j                  d      s?|
j	                  |d          T |j                         D ]R  }|j                  d	      |	k(  s|j                  d
      dk(  s-|j                  d      s?|
j	                  |d          T t        |
      dk(  r%|j	                  t        t        |
                   't        |
      dkD  s7t        j                  d|	t        |
             Y |syt        |       D ]i  \  }\  }}|t        |      k\  s|t        |      k\  r y||   dvr/|dk(  r|}n|dk(  r ||dd      }nI||v sNt        ||   t              sbd||   d<   k y)u  B-run S15 sh5/sh7 실측 fix (2026-06-12) — back_to_camera 인물 ref 계약.

    shot_staging 의 structured ``character_angles[].angle == "back_to_camera"``
    enum (글자 패턴 0) 인물이 character/outfit ref 로 부착돼 있으면 해당
    ``ref_role_metadata`` 에 ``view_angle_constraint`` 를 stamp 한다 (in-place).
    prompt_service 의 character_ref / outfit_ref_inline 분기가 이를 읽어
    "face must NOT be visible" 지시를 출력 — 정면 여권사진 ref +
    "match identity" 지시의 정면 bias 가 본문의 back-to-camera 작문을
    누르는 실측 결함의 대응.

    name→sid resolve 는 staged-character backstop 과 동일 규약: structured
    name exact equality 만, ambiguous → skip+log. staging 부재/해당 enum
    부재 still 은 no-op (기존 경로 byte-identical). resolve_refs_for_prompt
    에 넣지 않고 별도 helper 인 이유: staging 전달이 휴면 중인 W2 backstop
    전체를 활성화하지 않도록 좁게 분리 (Codex FEEDBACK3 합의).
    Nr   normalize_subject_idcharacter_anglesangleback_to_camera	characterr   rk   entity_typer5   r6   z?back_to_camera constraint skip: name %r ambiguous (%d entities))r8   r9   character_outlookF apply_back_to_camera_constraintswarn_on_outlookwhereview_angle_constraint)!app.core.subject_reference_policyr}   setr'   addrB   rA   nextiterloggerwarningrC   r(   dict)r3   r1   r2   stagingvisible_entitiesrI   r}   btc_sidscaca_namecandsver+   rS   kindr7   base_sids                    r%   r   r      s   2 FEHkk,-3366'?..&&b)U"Bv')bff].C{.Rz*		"Z.) # "((*D G+0G;0V,		$z*+ + u:?LLd5k*+Z!^NNQU% 4, #M2;D#I!s+<'="=Q<EE;H((+U8H
 xJ/@/CT$J<La !89 3r0   )z
[compositez[outfitz[state_variantz[outlook_idc                6    | xs dj                  t               S )u'  이 참조 자산이 **그 인물 자체**의 신원 그림인가 (파생본이 아닌가).

    ★파생본도 `asset_type='reference'` 에 `is_primary=1` 이다 (합성은 합성끼리
     대표를 다툰다 — `auto_set_primary`). 그래서 **기본 신원**을 고르는 조회가
     이걸 안 거르면, 옷 합성이 여러 벌 있는 인물의 「기본」이 그중 한 벌로
     뽑힌다.

    실측(컨트리로드 2판 2026-09-20): 찰리는 대표 참조가 4개(기본 1 + 합성 3)라
    `order_by(created_at.desc()).first()` 가 **코트 합성본**을 기본으로 집었다.
    그 결과 의상 배정이 `O00`(옷 없음)인 씬에서 찰리가 코트를 입고 나왔고,
    우비 씬에도 코트가 붙었다 — 사용자 육안 지적 여러 건의 공통 뿌리.
    r   )ro   DERIVED_REF_PREFIXES)prompt_useds    r%   is_base_identity_refr   ,  s      !r--.BCCCr0   c                     t        t        D  cg c]S  } t        t        j                  j                  d      t        t        j                  j                  | d                  U c}  S c c} w )u   위 판단의 SQL 판 — 기본 신원 참조만 남기는 조건.

    표식이 없으면(`NULL`·빈 문자열) 기본이다 — 옛 자산은 머리말이 없다.
    NT)
autoescape)r   r   r   r   r   is_r   ro   )pres    r%   base_identity_ref_filterr   ?  sp    
  ( (C 	J""&&t, ''22342HI		K
 (   s   AA*c                     e Zd ZdZddZddZ	 	 	 	 	 	 ddZdd	 	 	 	 	 	 	 ddZ	 dddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd	Z	 	 	 	 	 	 dd
Z		 dddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 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$dZdd	 	 	 	 	 	 	 d%dZy)&SceneReferenceServiceuV   씬 이미지 생성 시 reference/visible-entity 해결 서비스 (W5 F22 Phase B.8).c                     || _         || _        y r    )_db_project_id)selfdb
project_ids      r%   __init__zSceneReferenceService.__init__Q  s    %r0   c           
     v   |sg S 	 t        j                  |      }g }|D ]  }t        |t              r&|j                  d      xs |j                  dd      }nt        |t              r|}nM| j                  j                  t              j                  t        j                  |k(        j                         }|s|j                  |j                  |j                  |j                  |j                   xs d|j"                  xs d|j$                  xs dd        |S # t         j                  $ r g cY S w xY w)u   SceneStill.visible_entities_json을 파싱해서 EntityCanon 상세 dict 목록 반환.

        JSON 형식 허용:
        - 리스트 요소가 dict: `{"id": ...}` 또는 `{"entity_id": ...}`
        - 리스트 요소가 str: entity_id 자체
        id	entity_idr   z{})r   rk   r   r5   r   stable_traits)jsonloadsJSONDecodeErrorr(   r   r'   r!   r   queryr   filterr   firstrG   rk   r   r5   r   r   )r   visible_entities_jsonvisible_idsr   veidentitys          r%   get_visible_entitiesz*SceneReferenceService.get_visible_entitiesU  s    %I	**%:;K A!T"eeDk;QUU;%;As# {+#-. 
  '' ))"KK#)#5#5 & 52#)#5#5#;%+%9%9%AT)  .  7 ## 	I	s   D   D87D8c                4   |r|syt        | dd      }|	i }|| _        ||f}||v r||   S | j                  j                  t        j
                        j                  t        j                  | j                  k(  t        j                  |k(  t        j                  dk(  t        j                  |k(        j                  t        j                  j                         t        j                  j                               j!                         }|r|d   nd}|||<   |S )u  P0 (2026-07-01): chain_bg plate 의 실제 ImageAsset UUID 를 구조키로 resolve.

        ``bg_id`` (예: ``L04B06``) 는 ImageAsset UUID 가 아니라
        ``ImageAsset.variant_type`` 구조키다(Codex BLOCKING). scene 의 실제 입력
        lineage(input_image_ids)에는 반드시 실제 UUID 를 넣어야 generated_input
        엣지가 dangling 하지 않는다. asset_type=='chain_bg' AND variant_type==bg_id
        로 조회(is_primary 우선, 최신). 인스턴스 캐시로 샷 반복 조회 회피.
        N_chain_bg_uuid_cachechain_bgr   )getattrr   r   r   r   r   r   r   r   
episode_id
asset_typevariant_typeorder_by
is_primaryr,   
created_atr   )r   r   bg_id_cache_keyrow_uuids          r%   resolve_chain_bg_asset_idz/SceneReferenceService.resolve_chain_bg_asset_id}  s     5t<>F(.D%E"6>$<HHNN:==)V%%)9)99%%3%%3''50	 Xj++002J4I4I4N4N4PQUW 	 A4tr0   N)out_asset_id_mapc                 # ddl }i }|<|D cg c],  }|j                  |i       xs i j                  d      dk7  r|. }}|r| j                  j                  t        j
                  t        j                        j                  t        j                  | j                  k(  t        j
                  j                  |      t        j                  dk(  t        j                  dk(  t                     j                  t        j                  j!                               j#                         }|D ]  \  }	}
|j%                  |	|
        i }|j'                         D ]A  \  }}|j                  |i       }|j                  d      dk(  r-|||<   |5||v s:||   ||<   C 	 |j'                         D cg c]  \  }}|j                  d      dk(  s| }}}|r| j                  j                  t              j                  t        j                  | j                  k(  t        j
                  j                  |      t        j                  dk(  t        j                  dk(        j#                         }|D ]p  }t)        |j*                        }|j-                         s)|j
                  |vs8|j/                         ||j
                  <   |X|j                  ||j
                  <   r | j                  j                  t              j                  t        j                  | j                  k(  t        j                  dk(  t        j0                  j3                  d            j#                         }i #|j'                         D cg c]  \  }}|j                  d      d	k(  r| }}}|r| j                  j                  t              j                  t        j                  | j                  k(  t        j
                  j                  |      t        j                  dk(  t        j                  dk(  t                     j#                         D ](  }#j%                  |j
                  |j                         * i }|D ]  }|j5                  d
|j0                  xs d      }|s&d|j7                  d       d|j7                  d       }|j7                  d      xs d}|j%                  |g       j9                  |||j7                  d      f        |j'                         D ]p  \  }}||v r#fd}t;        ||      D ]Q  \  }}}t)        |j*                        }|j-                         s-|j/                         ||<   ||j                  ||<    p r | j                  j                  t              j                  t        j                  | j                  k(  t        j                  dk(  t        j0                  j3                  d            j#                         }|D ]  } |j5                  d| j0                  xs d      }|s&d|j7                  d       d|j7                  d       }!t)        | j*                        }|j-                         sr|!|vsw|j/                         ||!<   || j                  ||!<    t<        j?                  d       |S c c}w c c}}w c c}}w # t@        $ r!}"t<        jC                  d|"       Y d}"~"|S d}"~"ww xY w)u  ref_image_map에서 location 제외 + outlook 단독 + composite + state_variant refs 포함.

        W5 F22 Phase B.8.2 이관. generate_images의 씬 이미지 생성 경로에서 사용.

        Keys in returned map:
        - entity_id (character/prop): ref_image_map 그대로 (location 제외)
        - outlook_id: 아웃룩 ref (단독)
        - `composite:{char_id}:{outlook_id}`: 캐릭터 + 아웃룩 합성
        - `state_variant:{char_id}:{state}`: 죽은/다친 인물 state variant

        P0 (2026-07-01): ``out_asset_id_map`` 이 주어지면 **동일 key** → 실제
        ImageAsset UUID 를 병렬로 채운다(실제 첨부 lineage SOT). base entity 는
        primary reference asset 을 자체 조회(load_entity_reference_images 와 동일
        criteria), outlook/composite/state 는 아래 loop 의 asset row 에서 id 회수.
        None 이면 기존 경로 byte-identical (out-param, return 불변).
        r   Nr   location	referencer6   outlookz%composite:%r   z5composite:([a-f0-9-]+):([a-f0-9-]+)(?::([a-f0-9-]+))?r   
composite::      c                    | \  }}}j                  |      xs d}|r	|r||k(  rdn|sdnd}ddlm}  |t        |dd             }||r|j	                          fS d fS )Nr   r   r6   r   )	parse_isor   )r'   app.core.step_lockr   r   	timestamp)	r   r   base_idchar_idcurtierr   _ts	_cur_bases	           r%   _rankz>SceneReferenceService.build_scene_ref_image_map.<locals>._rank  sv    +.(B#--06BC ")SW^1!(a  =#GBd$CDC sCMMO"BCC"BCCr0   rh   z%state_variant:%z state_variant:([a-f0-9-]+):(\w+)state_variant:zGLoaded outfit + composite + state_variant refs into scene_ref_image_mapz6Failed to load outfit/composite/state_variant refs: %s)"r@   r'   r   r   r   r   r   r   r   r   in_r   r   r   r   r   r,   all
setdefaultitemsr   	file_pathexists
read_bytesr   likerF   rE   rG   rl   r   r+   	Exceptionr   )$r   ref_image_maprI   r   _re_comp_base_asset_idsr   
_base_eids
_base_rows_eid_aidscene_ref_image_maprU   entity_infor+   outlook_eidsoutlook_assetsoafpcomposite_assets
_char_eidsba_candsr   mcomp_keyr   rowsr   _b_cstate_v_assetssvasv_keyexcr   s$                                      @r%   build_scene_ref_image_mapz/SceneReferenceService.build_scene_ref_image_map  s'   . 	 +-',,!%%c2.4"99-HJV }   HHNN:#7#7GV"--1A1AA",,00<"--<"--202 Xj3388:;SU  #-JD$#..tT: #- 13+113NC'++C4K}-;'0$+0F(7(< % 4t	Z1>1D1D1Fo1FIC$((S`JaenJnC1FLoHHNN:.V"--1A1AA",,00>"--<"--2	 SU  )Bbll+Byy{r||;N'N<>MMO+BLL9+7=?UU,R\\: ) z*))T-=-==))[8**//?
   )+I/</B/B/D E/D)#t!XXm4C /DJ E88>>*5<<"--1A1AA",,00<"--<"--2 12 #%B ((ruu= ')F&OOLNN(b* '
|1QWWQZLA''!**!!(B/66GQWWQZ7PQ ' #),,.$22D" #)5"9JBBbll+Byy{8:+H5+79;,X6 #:+ #1> z*))T-=-==))[8**//0BC
   &OO$GI^\^_-aggaj\1771:,GFcmm,Byy{v5H'H68mmo+F3+77:vv,V4 & KKab #"i@ pFE\  	ZNNSUXYY""	Zsv   1[[  1[[C	[  [  +[  B7[   ["G[  (C[   A[  [  [  '&[  [   	\
)\\
)required_refsr   force_character_namesscene_ref_asset_id_mapc                   |st        g g g g       S dj                  d |D              }
|
st        g g g g       S | j                  |
||||||||		      S )u|  Multiple t2i_prompts 의 ID 매칭 union 으로 ref/meta build.

        2026-05-10 — 24 shot deterministic ref-contract fail fix (Fix A): 기존
        coordinator 가 first variation 의 t2i_prompt 만 보고 attached_meta build
        하던 결함 차단. target_variations[*] 모든 prompt 의 ID union 으로
        attach — 모든 variation 이 동일 ref set 공유 (Gemini image_edit 은 사용
        안 하는 ref 무시 OK).

        구현: prompts 를 ``\n`` join 후 기존 resolve_refs_for_prompt 위임.
        char/outlook short_id regex 매칭은 join 텍스트 전체에서 작동, _used_ref_ids
        set 이 dedup 보장. 빈 list / 모두 빈 string → 빈 payload 반환. invariant 1 보존.

        Area D-min (2026-05-14): prop attach 는 required_refs keyword arg 단일 SOT —
        join 텍스트의 P##/prop name 매칭은 더 이상 prop attach 신호가 아님.

        Area #11 v1 W2 (2026-05-18+): return shape 2-tuple → LabeledRefPayload.
        producer 가 ref_roles + ref_role_metadata 동반 emit. consumer (resolve_ref_roles)
        는 enum dispatch only (substring branch 0).
        rH   r1   r2   r3   
c              3  &   K   | ]	  }|s|  y wr     )r#   ps     r%   r&   zDSceneReferenceService.resolve_refs_for_prompt_set.<locals>.<genexpr>y  s     7kQ1ks   )	rq   r   r   rI   state_variant_sidsr  r   r  r  )r   r*   resolve_refs_for_prompt)r   t2i_promptsr   r   rI   r  r  r   r  r  r.   s              r%   resolve_refs_for_prompt_setz1SceneReferenceService.resolve_refs_for_prompt_setR  s    @ +"$ 	  7k77+"$ 	  ++- 3'1'"7#9 , 

 
	
r0   c                   ddl m}  |d      )ua  Area #11 v1 W1 placeholder helper — W2 implementation pending.

        Codex iter 2 Important 1 fix — silent fallback stub 폐기. Production
        unused (W2 atomic switch 까지). W1 호출 시 RefRoleError raise
        (No Silent Fallback gate 일관). W2 atomic 에서 producer append site 와
        동시 implementation rewrite.
        r   )r   z}_collect_ref_roles_for_prompt_set not production-ready (W1 placeholder; W2 implementation required). No Silent Fallback gate.)app.services.prompt_servicer   )r   rH   r3   r   s       r%   !_collect_ref_roles_for_prompt_setz7SceneReferenceService._collect_ref_roles_for_prompt_set  s     	=U
 	
r0   c          
       	78 ddl }
g }g }g }g }t               }dE	fd}D ci c]7  }|j                  d      s|j                  dd      |j                  dd      9 c}88j                  j	                         D ci c]*  \  }}|j                  d      s|j                  dd      |, c}}       8fd}|xs i }|
j                  d	|      D ]  }|j                  d      }|j                  d
      }|j                  d      }||v r||   }|d   |vr|d   |v r|j                  d| d|d    d||d      f       |j                  d| d|d    f       |j                  d       |j                   |||d   d|d   d             |j                  |d          8j                  |      }|r|j                  |       8j                  |      }|r|j                  |       |dk(  r8j                  |      }|rr||v rn||vrj|j                  d| d||   f       |j                  d|f       |j                  d       |j                   |d|i|d             |j                  |        ||j                  d
      |j                  d            \  }}|s|rd| d| nd}|r||v r||vr|j                  d| d||   f       |j                  d|f       |j                  d       |j                   ||dd |d!             |j                  |       |j                  |       |r|j                  |       ~||v rn||vrj|j                  d| d||   f       |j                  d|f       |j                  d       |j                   |d|i|d             |j                  |       |s|j                  |        |
j                  d"|      D ]e  }|j                  d
      |j                  d      }} |||      \  }}|s5j                  |      xs i } | j                  dd      }!|!s't        j                  d#|rt        |      dd$ nd%       |d&k(  ro||v rj||vrf|j                  d'||   f       |j                  d|!f       |j                  d       |j                   |d|!i|d             |j                  |       |rd| d| nd}|r||v r||vr|j                  d| d||   f       |r%j                  |      xs i j                  dd      nd}"|"r|j                  d|! |" f       n|j                  d|!f       |j                  d       |j                   ||!dd |d!             |j                  |       |j                  |       |r|j                  |       ||v rj||vrf|j                  d'||   f       |j                  d|!f       |j                  d       |j                   |d|!i|d             |j                  |       |sU|j                  |       h g }#|rV|D ]Q  }$t        |$t              s|$j                  d(      dk(  s)|$j                  d      xs d}%|%sA|#j                  |%       S |#D ]T  }&8j                  |&      }'|'s|'|v r|j                  |&      }(|(r|(j                  d      r|(d   |vr|(d   |v r|j                  d|& d|(d    d||(d      f       |j                  d|& d|(d    f       |j                  d       |j                   ||&|(d   d|(d   d             |j                  |(d          |j                  |'       |'|vr|j                  d|& d||'   f       |j                  d|&f       |j                  d       |j                   |d|&i|'d             |j                  |'       W |rdd)lm}) D ch c]&  }|j                  d      s|j                  dd      ( }*}i }+|j                  d*      xs g D ]H  },t        |,t              s|,j                  d+      s& |)|,d+   d,d-.      }-|,j                  d/d      |+|-<   J |j                  d0      xs g D ]!  }.|.j                  dd      }/|/si }0D ]V  }|j                  d1      |/k(  s|j                  d2      dk(  s-|j                  d      s?|j                  dd      |0|d   <   X j	                         D ]Q  \  }}|j                  d1      |/k(  s|j                  d2      dk(  s0|0j!                  ||j                  dd             S t#        |0      d
k7  r0t#        |0      d
kD  r t        j                  d3|/t#        |0             t%        t'        |0j	                                     \  }1}2|2sA|/|xs
 t               v }3|3s|+j                  |2d      d4k(  rj|1|v rp|1|vrt        j                  d5|2       |j                  d|2 d||1   f       |j                  d|2f       |j                  d       |j                   ||2d6|3rd7n|1|*vrd8nd9d:d;|3rd<d6ini |1d             |j                  |1       t        j                  d=|2|/       $ g }4|rV|D ]Q  }$t        |$t              s|$j                  d(      d>k(  s)|$j                  d      xs d}5|5sA|4j                  |5       S |4D ]  7t%        7fd?D        d      }6|6s|6|v r |6|vr%|j                  d@7 dA||6   f       |j                  d>7f       |j                  dB       |j                   |d7i|6dC             |j                  |6        t)        ||||D      S c c}w c c}}w c c}w )Fu  T2I 프롬프트에서 참조 이미지 매칭 — short_id(C01O02) + 레거시([[name]+[outlook]]) + state_variant.

        prop attach 는 Area D-min 이후 `required_refs` (Area B render_contracts
        producer SOT) 단일 결정자. 옛 prop text matching 분기 (P## word-boundary,
        prop name word-boundary/CJK substring) 는 폐기.

        W5 F22 Phase B.8.3 이관. state_variant_sids: 죽은/다친 인물 short_id →
        {key, state} — state variant ref 제공.

        D5 §4.2 + §4.3 (2026-05-09): return 을 (labeled_refs, attached_meta) 2-tuple.
        attached_meta = list[(kind, id)] — append 시점의 source-of-truth (regex 매칭
        결과 / entity_lookup short_id) 만 사용 (P1: 라벨 추론 X). composite 부재
        fallback 은 ("character", char_sid) 별 kind 로 기록 — character_outlook
        위조 금지 (P2). invariant 1: len(labeled_refs) == len(attached_meta).

        Area D-min (2026-05-14): prop attach 는 required_refs keyword arg 단일 SOT.
        r   Nri   c                F    j                  |      }|r|| d<   || d<   | S )Nasset_idpipeline_roler'   )metari   roler   r  s       r%   _stampz=SceneReferenceService.resolve_refs_for_prompt.<locals>._stamp  s5    %1-11#6'+D$(,_%Kr0   r5   r   r   c                     j                         }j                        }|st         fdD        d      }|st        fdD        d      }|s#t        fdj                         D        d      }||fS )uI   char/outlook 참조에서 UUID 찾기 (short_id 우선, 이름 fallback).c              3     K   | ]>  }|j                  d       k(  r(|j                  d      dk(  r|j                  dd       @ yw)rk   r   r   r   r   Nr  )r#   r   char_refs     r%   r&   z`SceneReferenceService.resolve_refs_for_prompt.<locals>._find_char_outlook_ids.<locals>.<genexpr>  sF      ]0@"v(2rvvm7LP[7[ VVD"%0@   AANc              3     K   | ]>  }|j                  d       k(  r(|j                  d      dk(  r|j                  dd       @ yw)rk   r   r   r   r   Nr  )r#   r   outlook_refs     r%   r&   z`SceneReferenceService.resolve_refs_for_prompt.<locals>._find_char_outlook_ids.<locals>.<genexpr>  sF      ^0@"v+5"&&:OS\:\ VVD"%0@r   c              3  x   K   | ]1  \  }}|j                  d       k(  r|j                  d      dk(  r| 3 yw)rk   r   r   Nr  )r#   r   r+   r"  s      r%   r&   z`SceneReferenceService.resolve_refs_for_prompt.<locals>._find_char_outlook_ids.<locals>.<genexpr>  s@      b*?YS$(K7DHH]<SW`<` *?s   7:)r'   r   r   )r  r"  r   
outlook_id_sid_to_uuidrI   r   s   ``  r%   _find_char_outlook_idszMSceneReferenceService.resolve_refs_for_prompt.<locals>._find_char_outlook_ids  s    "&&x0G%))+6J]0@ ]
 !^0@ ^

 !b-*=*=*? b

 J&&r0   z(C\d{2,3})(O\d{2,3})r6   r   z
character u    — r;   z state referencecharacter_stater   r:   )r7   r;   character_state_variantO00z	 identityr   r8   r7   reference_facer   z
 in outfitr   r9   	composite)r7   outfit_kindreference_compositez\[\[([^\]]+)\]\+\[([^\]]+)\]\]uW   legacy ref skip: char_id=%s entity_lookup short_id 부재 — P1 SOT 위반 회피 (D5)   z<empty>u	   미지정character identityr   r|   subject_reference_policy
subject_idFz'resolve_refs_for_prompt.staged_backstopr   policyr~   rk   r   uS   staged-character backstop skip: name %r ambiguous (%d entities) — diagnostic onlygeneric_descriptor_allowedzQstaged-character backstop skip: %s staged but no ref bytes in scene_ref_image_mapT$staged_character_in_continuity_group.staged_character_missing_from_visible_entities!staged_character_ref_not_attachedstaging_character_angles)r7   "staged_character_backstop_attachedreasonpolicy_source'outdoor_continuity_forced_character_refu   staged-character backstop attach: %s (%s) — staged in character_angles but no ref attached by token/required_refs (feedback6-B)propc              3     K   | ]>  }|j                  d       k(  r(|j                  d      dk(  r|j                  dd       @ yw)r5   r   r<  r   r   Nr  )r#   r   	_prop_sids     r%   r&   z@SceneReferenceService.resolve_refs_for_prompt.<locals>.<genexpr>-  sF      6,<bFF:&)3VVM*f4 b!,<r   zobject z (required)r=   reference_propr	  )r  Dict[str, Any]ri   r!   r  r!   returnr@  )r@   r   r'   updater   finditerrE   rG   r   r   r   r!   r(   r   r   r}   r   rA   r   r   r   )9r   rq   r   r   rI   r  r  r   r  r  rJ   rH   r3   r1   r2   _used_ref_idsr  r   r   r+   r&  _sv_sidsrD   r7   char_sidoutlook_sidsvr   r$  composite_key	char_nameoutlook_name
_char_info_legacy_char_sid_legacy_outlook_sid_required_char_sids_r_cidra   
_char_uuid_svr}   _ve_ids_policy_by_sid_prZ   r   r   r   _bs_char_id_bs_char_sid_forced_required_prop_sids_pid_target_eidr>  r%  s9     ` `    `                                             @@r%   r  z-SceneReferenceService.resolve_refs_for_prompt  sd   < 	02/1  "	24 U	 L\rK[R_a_e_efp_qz2.tR0@@K[rML_L_LazLaysDeiememnxeyTXXj"5s:Laz{	'0 &+ \\"9:FE++a.C{{1~H++a.K 8#h'e9M1biCV6V ''$SEr'{m;KL+BuI6) 
 "((*;z2g;-=X)YZ$$%:;%,,V (2g;?5	#<.> ? "%%bi0*..x8G%))'2!-!1!1+!>J!%))*5 e#&**84w*=='Q^B^ '':hZy)IK^_fKg(hi!((+x)@A$$_5%,,V)74D.F G!%%g."8QUV"XGZCMj	:,?SWM2E!E-_lJl##z#j%ACVWdCe$fg$$&93%?@  !45!((<!#8*: ; !!-0!!'*!%%j1--'2N##z(9%EGZ[bGc$de $$k8%<=  1!((H%w0@*B C!!'*!!*-S GX \\"CZPE&+kk!nekk!n|I"8L"QGZ
 '**739rJ)~~j"=#0(/CL!$Y
 {*11g]6R '')=?RSZ?[(\]!((+7G)HI$$_5%,,V 017<L.N O!%%g. DNj	:,?SWM2E!E-_lJl##z)J%GI\]jIk$lmcm}'8'8'D'J&O&OPZ\^&_su#&!((*=BRASTgSh?i)jk "((+7G)HI  !45!((,[I!#8*: ; !!-0!!'*!%%j1 --'2N##%9;Nw;W$XY$$k3C%DE  1!((,-w8H*J K!!'*!!*-C QR *,#b$'BFF6Nk,I66$<-2D+2248	 $
 -I%)))4J]* ,,y)CE
-7E
&99## 5W>NO'E
3%  $$&7I;aG~9V%WX  !67!((%G=J 9*; < !!#e*-!!*-!44YKy1#J/! 
   +y!9:_-$$V	"J0@&B Cj)] -v N2BS2BBbffTlrvvdB'2BGS-/Nkk"<=CCb$'BFF<,@ 4<(%G!I 13x0DN9- D kk"45;;&&b1)+*Bvvf~0RVVM5Jk5Y^`^d^dei^j+-66*b+Ar$x( + "/!4!4!6ICxx'72txx7NR]7]))#txx
B/GH "7 v;!#6{Q@#S[
 ,0flln1E,F)\# "&;&DsuE*..|R@78-/&99NN7$
 ## i8'4%  $$k<%@A  1!((':> # ? 'g5 N@%?1 " B4H')1  0*2 3 !!+.$ !'	A <` *,#b$'BFF6Nf,D66$<-2D+2248	 $
 -I6,< 6 	K m+"55)K0#K0!    &)!45Z($$V	"K1A&C Dk*- -2 (%/'	
 	
a szl Ts"   o"%o"o'
o'
o-4o-c                  i }|D ci c]  }|d   |
 }}|D ]?  }|j                  dd      }|s|j                  d      xs |j                  dd      ||<   A |st        ddd	      	 d
dlm}  || j                  | j
                  |      }|D ]  }	|j                  |	j                        }
| j                  j                  t              j                  t        j                  |	j                  k(        j                         }|
sx|s{|
j                  d      s|j                  s|
d    |j                   }|
j                  d      xs |
j                  dd      }|j                  xs |j                  }| d| ||<    	 |S c c}w # t         $ r6}t"        j%                  d|j&                  j(                  |       Y d}~|S d}~ww xY w)u>  Build short_id → text description map (+ C##O## composite keys).

        W5 F22 Phase B.16 (2026-04-22): generate_images 배치 준비 단계의
        entity text map 빌드 블록을 이관. short_id가 지정된 각 entity를
        'description' 맵에 등록하고, CharacterOutlook 링크별로 C##O##
        composite 키로 '<char desc>, wearing <outfit desc>' 문자열을 추가.

        CharacterOutlook 쿼리 실패 시 warning log만 찍고 기본 short_id
        map만 반환한다 (non-fatal). fallback 순서: description → name.

        ★계약 (2026-07-02): entity `t2i_prompt` 는 reference 이미지 생성 전용
        프롬프트(스타일 래퍼: "Photorealistic product photo, isolated object,
        plain neutral background" 등 포함)라 in-scene 치환 텍스트로 절대 사용
        금지 — 씬 프롬프트 문장 중간에 스플라이스되면 스타일 지시 충돌로
        부양 오브젝트/패널 콜라주 등 렌더 오염을 일으킨다.

        Returns: {short_id: description, 'C##O##': composite, ...}
        r   r5   r   r   rk   z scene_reference.episode_requiredus   build_entity_text_map 은 화를 받아야 한다 — 안 주면 C##O## 합성 키가 조용히 비거나 틀린다i  )codemessagestatus_coder   )episode_outlook_rowsz
, wearing z'Failed to build C##O## text map: %s: %sN)r'   r   app.core.entity_identityra  r   r   character_idr   r   r   r   r$  r   r5   r   rk   r   r   r   	__class____name__)r   entitiesr   text_mapeentity_by_idr7   ra  char_outlookscochar
outfit_entck	char_descoutfit_descr  s                   r%   build_entity_text_mapz+SceneReferenceService.build_entity_text_mapK  s   ( $&,45Hq$
H5A%%
B'C !m 4 Ifb8I  7X	! !	8E0$**J8M##''8!XX^^K8??NNbmm3%'  J488J+?JDWDW ,-j.A.A-BCB $ 7 O488FB;OI","8"8"KJOOK&/[
;-#HHRL $" W 6H  	8 NND==1138 8 	8s7   F+BF 	F F F +AF 	G+GGc           	     t   i }|D ]  }| j                   j                  t              j                  t        j                  | j
                  k(  t        j                  |d   k(  t        j                  dk(  t        j                  dk(  t                     j                  t        j                  j                               j                         }|st        |j                        }|j!                         s|j#                         ||d   <   	 t$        j'                  dt)        |      t)        |             |S )u  generate_images 초기 단계에서 전체 entity 목록의 primary reference 로드.

        W5 F22 Phase B.12: scene_image_service.generate_images의 참조 이미지 로드
        루프를 이관. get_reference_image_map과 달리 fallback 없음 — primary만 포함.

        Returns: {entity_id: image_bytes} (파일이 없는 primary는 skip).

        P0 (2026-07-01): base entity 의 실제 첨부 asset UUID 는 build_scene_ref_image_map
        (out_asset_id_map) 이 동일 criteria(is_primary==1, created_at desc)로 self-query 한다
        — 여기서 별도 out-param 을 노출하지 않는다(dead code 회피, Codex minor).
        r   r   r6   z3Loaded %d existing reference images for %d entities)r   r   r   r   r   r   r   r   r   r   r   r   r,   r   r   r   r   r   r   r+   rA   )r   rf  r   r   primary_assetr   s         r%   load_entity_reference_imagesz2SceneReferenceService.load_entity_reference_images  s     +-Fz*))T-=-==((F4L8))[8))Q. -. *//4467  -11299;24--/M&,/% & 	AH	
 r0   c           	        i }|D ]  }|d   }| j                   j                  t              j                  t        j                  | j
                  k(  t        j                  |k(  t        j                  dk(  t        j                  dk(  t                     j                         }|s| j                   j                  t              j                  t        j                  | j
                  k(  t        j                  |k(  t        j                  dk(        j                  t        j                  j                               j                         }|sKt        |j                        }|j!                         sr|j#                         ||<    |S )u   visible_entities의 각 entity_id에 대해 primary(없으면 latest) reference 이미지 bytes.

        Returns: {entity_id: image_bytes}
        r   r   r6   )r   r   r   r   r   r   r   r   r   r   r   r   r   r,   r   r   r   r   )r   r   r   entity_datar   ref_imgr   s          r%   get_reference_image_mapz-SceneReferenceService.get_reference_image_map  s7   
 +-+Kd#C z*))T-=-==((C/))[8))Q.,.   HHNN:.V"--1A1AA",,3"--<
 Xj3388:;UW  '++,99;)+M#&9 ,< r0   c                    ddl m} t         |             | j                  |      }|j	                         D ci c]  \  }t        fd|D              s| c}}S c c}}w )u  visible_entities의 ref image map에서 location 엔티티 제외.

        W5 F22 Phase B.23.3 (2026-04-23): generate_single_scene_image의
        all_ref → location 필터링 블록(~10 LOC)을 이관.

        처리:
          1. `get_reference_image_map(visible_entities)`로 전체 map 조회
          2. 각 entry에서 entity_type=='location'인 eid 제외
        location 판정은 visible_entities 리스트에서 id 매칭으로만 수행 (literal 보존).
        r   )sidecar_owner_typesc              3  X   K   | ]!  }|d    k(  xr |j                  d      v  # yw)r   r   Nr  )r#   rh  r   excludeds     r%   r&   zNSceneReferenceService.get_ref_image_map_excluding_locations.<locals>.<genexpr>  s7      )A $3C155#78#CC)s   '*).app.modules.pipeline.grounding_entity_contractrz  r   rx  r   any)r   r   rz  all_ref_image_mapr   rU   r|  s       ` @r%   %get_ref_image_map_excluding_locationsz;SceneReferenceService.get_ref_image_map_excluding_locations  sv    " 	W*,- 889IJ #4"9"9";
";Y )  N";
 	
 
s   "A$c                   ddl m}  || j                  | j                  |      }|rY| j                  j	                  t
              j                  t
        j                  j                  |            j                         ng }|D ci c]^  }|j                  |j                  |j                  |j                  |j                  xs d|j                  xs d|j                  xs dd` c}S c c}w )u  Load full episode entity lookup (canon_id → 6-field dict) for T2I matching.

        W5 F22 Phase B.21.2 (2026-04-22): scene_image_service.generate_single_scene_image의
        entity_lookup 구성 블록을 이관. EntityEpisodeLink로 에피소드 전체 엔티티 id를
        조회하고, EntityCanon에서 메타데이터를 로드해 6 필드 dict로 매핑한다.

        generate_single_scene_image은 T2I 프롬프트에서 non-visible 캐릭터 short_id
        매칭을 위해 visible뿐 아니라 에피소드 전체 엔티티가 필요하다. 필드는
        id / name / entity_type / short_id / description / t2i_prompt (stable_traits 제외).

        에피소드에 연결된 entity가 없으면 빈 dict 반환.
        r   )active_episode_canon_idsr   )r   rk   r   r5   r   rq   )rb  r  r   r   r   r   r   r   r   r   rk   r   r5   r   rq   )r   r   r  ep_entity_idsep_entitiesees         r%   load_episode_entity_lookupz0SceneReferenceService.load_episode_entity_lookup  s     	F0HHd&&
4  HHNN;'..{~~/A/A-/PQUUW"$ 	 "

 " EEee!~~KK-2!~~3 mm1r  "

 
	
 

s   A#C,c               B   |sy|dvr6ddl m}  |d|j                  d       d|j                  d       d	|d
      |dk(  r8s6ddl m}  |d|j                  d       d|j                  d       dd
      t               }|D ](  }|j                  dd      }|s|j	                  |       * d}d}d}|dk(  rt        fd|D        d      }n|dk(  r|D ]  }||v s||   \  }}|} n |dk(  r8|s6ddl m}  |d|j                  d       d|j                  d       d|d
      g }t               }|r	 t        j                  |j                  dd            }|D ]`  }d}t        |t              r&|j                  d      xs |j                  dd      }nt        |t              r|}|sP|j	                  |       b ||z
  }|D ]f  }|j                  |      }|s|j                  d      dv s+|j                  d|      }|j                  dd      }|r||v rV|j                  |       h |dk(  rr|s|D ]  }t        |t              r%|j                  d      xs& |j                  dd      nt        |t              r|nd}|sO|j                  |      xs i }|j                  d      dk(  sy|j                  dd      } | s| } n |j                  dd       d|j                  dd       }!|	j                  |!i       }"|"j                  dd      }#|"j                  dd      }$|#dk(  r>|dk7  r9ddl m}  |d |d!|j                  d       d|j                  d       dd
	      dd"lm}%m}& ddl m}  |&|
d#|j                  d       d|j                  d       $      }'|'|%k(  r|!|	vry|'|%k(  r/t#        |	j                  |!      xs i j                  d%d&            sy|'|%k(  r5|#dk7  r0 |d'|#d!|j                  d       d|j                  d       d(      g }(|#dk(  r||D ]g  }|j                  dd      }|j                  |      xs i }|j                  d      d)k(  s?||vsD|(j                  |j                  d      xs |       i |(r:t$        j'                  d*|j                  dd      |j                  dd      |(       d+}#g })|#dk(  r||D ]g  }|j                  dd      }|j                  |      xs i }|j                  d      d,k(  s?||vsD|)j                  |j                  d      xs |       i |)r8t$        j'                  d-|j                  dd      |j                  dd      |)       |#dk(  rd.}*|$r|*d/|$ z  }*|"j                  d0g       }+|+rQt)        |+d1|j                  d       d|j                  d       d2d34       |*d5d6j+                  d7 |+D               z  }*|r|*d8d6j+                  |       z  }*n|#d+k(  rd9}*n|#d:k(  rd;}*|$r|*d/|$ z  }*|"j                  d0g       }+|+rQt)        |+d1|j                  d       d|j                  d       d<d34       |*d5d6j+                  d= |+D               z  }*|rg|*d8d6j+                  |       z  }*nOd>},|
r+|
j                  d?g       D ]  }-|-d@   }.t-        |.      sd&}, n |,rdA}*ndB}*|r|*d8d6j+                  |       z  }*|xs |xs d}/|#dk(  rdC}0n|#d:k(  rdD}0n
|#d+k(  rdE}0ndE}0|"j                  d0g       |$xs d|rt/        |      ng |#dF}1|(r|(|1dG<   |#dk(  rW|
rUg }2|
j                  d?g       D ]7  }-|-d@   }3t-        |3      s|2j                  |-j                  d)d      |3dH       9 |2r|2|1dI<   |)r|)|1dJ<   t$        j1                  dK|j                  dd      |j                  dd      |r|ndL|/r|/ndM|0       |*||/|0|1fS # t        j                  t        f$ r g }Y w xY w)Nu  Build (label, bytes, loc_id, ref_role, ref_role_metadata) for prev-shot, or None.

        Area #11 v1 W2 (2026-05-18+): return 3-tuple → 5-tuple. ref_role 은 기존
        structured `ref_usage` (`zoom_in_detail` / `atmosphere_reference` / `exact_background` /
        else) → enum 1:1 매핑 (spec §3.2). ref_role_metadata 안 pre-parsed
        keep_elements / ignore / remove_hints — consumer (resolve_ref_roles) 가
        prose split 안 함 (substring branch 0).

        W5 F22 Phase B.22.2 (2026-04-22): scene_image_service._generate_one_scene의
        prev-shot background ref 구성 블록(~90 LOC)을 이관. 처리:
          1. best_prev_bytes 없으면 None 반환 (Layer 1 early return)
          2. _cur_vis_ids: 현재 샷 visible entity UUID 집합
          3. _prev_still_data 결정: **bytes_source_kind 기반 metadata selection
             branching (no cross-source fallback)** —
              - "dep_scene" → dep_scene_id matching still 만 lookup
              - "location_history" → current_location_ids × location_scene_history
                매칭 만 lookup
              - mismatch / no match → Layer 2.5 / 2.6 fail-fast (RefContractError)
          4. _remove_hints: prev에 있던 character/prop 중 현재에서 빠진 것 (state_variant 제외)
          5. ref_usage 분기 (zoom_in_detail / atmosphere_reference / exact_background / fallback)
          6. fallback은 staging에 죽은 캐릭터 있는지로 라벨 분기

        D5 §4.2.2 (2026-05-09): return 을 (label, bytes, loc_id) 3-tuple 로 확장.
        2026-05-15 zoom_in_detail source provenance hardening (spec
        docs/superpowers/specs/2026-05-15-zoom-in-detail-source-provenance-design.md §4.5):
        loc_id 결정도 bytes_source_kind 기반 (no cross-source fallback):
          - bytes_source_kind == "dep_scene" → loc_id_from_dep (dep_scene 의
            prev_still 안 entity_type='location' 첫 항의 short_id)
          - bytes_source_kind == "location_history" → loc_id_from_history
            (current_location_ids x location_scene_history 매칭 key)
        결정 실패 시 빈 문자열 "". P2 strict 는 validator (T3) 에서 enforce —
        empty loc_id 가 어떤 required bg_id 도 만족 X.
        N>   	dep_scenelocation_historyr   )RefContractErrorzGprev_ref_source_missing: bytes present but bytes_source_kind invalid (Sscene_index_Shot
shot_indexz, got=r<   r  zBprev_ref_source_missing: dep_scene source requires dep_scene_id (Sz, dep_scene_id=r   r   c              3  L   K   | ]  }|j                  d       k(  s|  yw)r   Nr  )r#   sdep_scene_ids     r%   r&   zGSceneReferenceService.build_prev_shot_background_ref.<locals>.<genexpr>s  s!     BFqaeeDk\&AFs   $$r  zqprev_ref_source_missing: location_history source requires current_location_ids match in location_scene_history (Sz, current_location_ids=r   z[]r   r   )r   r<  rk   r5   r   _	ref_usageignore_elementszoom_in_detailzfzoom_in_detail_source_violation: ref_usage='zoom_in_detail' requires bytes from dep_scene, got source=z (S)FRAMING_CLOSEget_framing_scale_or_raisez scene_reference_service.matrix S)r   t2i_annotatedTzUclose_ref_usage_violation: close framing requires ref_usage='zoom_in_detail' but got z(). Allowed: close + zoom_in_detail only.r   u~   Scene %d Shot %d: zoom_in_detail downgraded to continuity — subjects not in dep frame: %s (W3 exact-reuse 금지 diagnostic)atmosphere_referencer<  u   Scene %d Shot %d: zoom_in_detail props not in dep frame: %s (feedback6-C diagnostic — 줌아웃/줌인 작문 모순 의심, upstream VE/scene_detail 점검 대상)us  previous shot at same location (SAME MOMENT, zoomed-in reframing) — continuity reference. Keep the environment, lighting, and spatial layout consistent with this frame; the camera moved closer to a detail region of the same moment. Render the subjects and their poses as described in the prompt text (for a true zoom they match this frame). Do NOT duplicate body parts. keep_elementsSz zoom_in_detailz/step.scene_reference.keep_elements_kind_invalid)label_sourceerror_code_entryz Keep: r   c              3  &   K   | ]	  }|d      ywrT   Nr  r#   rh  s     r%   r&   zGSceneReferenceService.build_prev_shot_background_ref.<locals>.<genexpr>5       ,FAQwZ   z Also ignore: u   previous shot at same location (DIFFERENT ROOM/ANGLE) — use ONLY as style, lighting, and atmosphere reference. Do NOT copy the exact background layout, furniture positions, or wall details from this image.exact_backgrounduI   previous shot at same location (SAME ROOM) — use this background as-is.z exact_backgroundc              3  &   K   | ]	  }|d      ywr  r  r  s     r%   r&   zGSceneReferenceService.build_prev_shot_background_ref.<locals>.<genexpr>H  r  r  Fr~   subject_stateu  previous shot at same location — use ONLY the background/environment from this image. Ignore all standing/moving people and human silhouettes. Keep registered immobilized-state figures exactly as they are. Keep architecture, furniture, lighting, and environment.u   previous shot at same location — use ONLY the background/environment from this image. Ignore all people, figures, and human silhouettes. Keep only architecture, furniture, lighting, and environment.previous_shot_same_frame_zoomedprevious_shot_same_roomprevious_shot_continuity)r  ignoreremove_hintsr  zoom_downgraded_new_subjects)r   r;   immobilized_subjectszoom_props_missing_in_depzVScene %d Shot %d: prev-shot background ref injected (remove: %s, loc_id: %s, role: %s)nonez	<unknown>)app.core.ref_contract_validatorr  r'   r   r   r   r   r   r   	TypeErrorr(   r   r!   rG   app.core.framing_scaler  r  boolr   r   r   r*   r   r)   r+   )4r   best_prev_bytesbytes_source_kind
still_datar   current_location_idsr  stillslocation_scene_historydep_detail_mapr   r  rI   r  cur_vis_idsr   ve_idloc_id_from_deploc_id_from_historyprev_still_data_hist_loc_idr  r  prev_vis_idsprev_vis_rawpvpv_idgone_idsgidentent_nameent_sidloc_siddep_keydep_infor  r  r  r  _framing_scaler  r  rT   keephas_immobilized_charsr   r;   loc_id_resolvedref_roler2   _immobilized_subjects	_ca_states4         `                                             r%   build_prev_shot_background_refz4SceneReferenceService.build_prev_shot_background_ref  s	   b 
 $EEH"^^M2359U8V W(+1.  +LH"^^M2359U8V W  ,/q2  e"BFF4$E& #  "#% +"BFBDO "44 4#99)?)M&A*6'	 !5  22;NH"^^M2359U8V W((<'?qB  #%E"#zz/*=*=>UW[*\] #b$'FF4LCBFF;,CEC(E $$U+ # $k1H#'',377=15JJ"wwvs3H!ggj"5G7.@#@  ''1   !K/L&B &b$/ t?{B(?<Fr3<ObUW  ! '++E28bCww}-;"%''*b"9".5O! '  ^^M156a
|UV8W7XY!%%gr2LLb1	/4
 ((->+-MH"44E3H I^^M2359U8V W  ,/q2 	
 	E3NN=12%
|8T7UW
 ]*wn/L
 m+n009?RDD_VZ[\]*y<L/L"66?] C^^M23 4!~~l34 5'(  35$((_-H&tR(#''.4"77=)[8U,=V077
8K8TuU	 '
 ,WNN=!4NN<30 3	 02!((_-H&tR(#''.4"77=)V3\8Q-44SWWZ5H5QER	 '
 )5 NN=!4NN<3- (( JE1VH%<<4D '#$Z^^M%B$C5%/^^L%A$B/"S%V	 7499,F,F#F"GHH>$))L*A)BCC00 fE,,_E1VH%<<4D '#$Z^^M%B$C5%/^^L%A$BBS"U%V	 7499,F,F#F"GHH>$))L*A)BCC %*!!++&8"=B/E+E204-	 >
 % c c>$))L*A)BCC
 *F-@FB ((8H,,0H001H1H%\\/2>l2>D.B"	-
 (, <= ((W:<!kk"4b9/	'	2)00%'VVK%<!*2  : %<Q!"89$) 9: 	dNN=!,jnn\1.M(Lf.OK	
 CTUUM (()4 "!"s   =%_? ?``c                   t        | di       }||v r||   S i }	 ddlm} ddlm} ddlm}  ||j                  | j                  |d      }|j                         rSt        j                  |j                  d            j                  d	      xs i j                  d
      xs g } ||      }|||<   || _        |S # t        $ r!}	t        j!                  d||	       Y d}	~	3d}	~	ww xY w)uk  entity_relation 체크포인트 → character identity-variant family 맵 (인스턴스 캐시).

        detect_state_variant_sids 의 identity-variant aware 매칭 입력 (2026-07-02,
        S12 시신: staging=base 표기 vs VE=variant 표기 어긋남 수정). cp 부재/파손 시
        빈 dict — 소비자는 기존 exact 매칭으로 환원.
        _identity_family_cacher   )settings)build_identity_family_by_sid)_ep_checkpoint_pathentity_relationzutf-8)encodingdata	relationsu.   identity family 로드 실패 (episode=%s): %sN)r   app.core.configr  2app.modules.pipeline.visual_continuity_anchor_planr  %app.services.scene_checkpoint_loadersr  projects_dirr   r   r   r   	read_textr'   r   r   r   r  )
r   r   cachefamr  r  r  r  r  r  s
             r%   load_identity_family_by_sidz1SceneReferenceService.load_identity_family_by_sid  s     18*B10$$#%	^0 R#%%t'7'7EVXAxxz"jjg)FGKKFSYWY!c+.524 29=  j&+#
	  	^NNKZY\]]	^s   BB< <	C&C!!C&c           
        i }|s|S |xs i }|D ci c]6  }|j                  d      r#|j                  dd      |j                  dd      8 }	}|	j                  |j                         D 
ci c])  \  }
}|j                  d      r|j                  dd      |
+ c}}
       i }|j                         D ]V  \  }}|j                  d      |j                  d      }}|s+|s.|j                  |t	                     j                  |       X |j                  dg       D ]C  }|d   }t        |      s|j                  dd      }d}|D ]/  }|j                  d      |k(  s|j                  d      s*|d   } n |s|r|j                  |      xs
 t	               }t        |      dk(  rV|j                  t        t        |                  xs
 t	               }|D ]"  }|j                  d      s|d   |v s|d   } n |s|gt        |j                  |      xs
 t	               |hz
        z   D ].  }|	j                  |d      }d	| d
| }|s ||v s%||d||<    C F |S c c}w c c}}
w )uH  Detect state_variant short_ids from staging subject_state.

        W5 F22 Phase B.22.1 (2026-04-22): scene_image_service._generate_one_scene의
        state_variant 감지 블록(~26 LOC)을 이관. staging.character_angles를 훑어
        subject_state 가 immobilized (unconscious/dead/severely_injured) 인 캐릭터를
        찾고, scene_ref_image_map에 해당하는 state_variant ref가 있으면 short_id를
        key로 {"key": "state_variant:UUID:STATE", "state": STATE} 매핑을 반환.

        Area #2 W5 (2026-05-17): legacy gaze field literal 판정 폐기 —
        ``is_immobilized_state(ca["subject_state"])`` 단일 SOT helper 사용 (Gate 1).

        staging이 None이면 빈 dict 반환.

        skip 로직 없음 — C##이 t2i에 있으면 무조건 ref 제공 (판단은 scene_detail 책임).
        r5   r   r   rk   r~   r  r   r6   r   r   )ri   r;   )r'   rB  r   r   r   r   r   rA   r   r   rl   )r   r   rI   r   r   identity_family_by_sidr  fam_mapr   sid_to_uuidr   r+   name_to_sidsr   nmr  r   r;   r   rF  r   r  cand_sid	cand_uuidr  s                            r%   detect_state_variant_sidsz/SceneReferenceService.detect_state_variant_sids  s   . 9;%%(.B '
&"&&*< FF:r"BFF4$44& 	 
 	*002
2	Tdhhz6J HHZ$c)2
 	 -/'--/JD$HHV$dhhz&:Ba''CE266q9 0
 ++0"5B'E'.ff["-GH&66&>W,
1C!*~H '  %((1:SUu:?!++d4;&78ACEC.66*-"Z.C2G')*~H! /  &J[[*3cezA*C C'OOHb9	))AeW=+>!>;AE3R&x0C1 6> "!a

s   ;I(,.I-
)chain_bg_entryc                  g }	 |D ci c]  }|d   |
 }}|D ]C  }|d   }|j                  dd      }|dk7  s ||v s%|dk(  rdnd}	|j                  |	||   f       E |r;|j                  d      r*|j                  d	|j                  d
      xs d|d   f       |S c c}w # t        $ r!}
t        j                  d|
       Y d}
~
|S d}
~
ww xY w)u  Build labeled reference list for custom-prompt scene generation.

        W5 F22 Phase B.21.3 (2026-04-22): scene_image_service.generate_single_scene_image의
        custom_prompt 경로 labeled_refs 구성 블록을 이관. 각 visible entity에 대해:
          - location은 제외 (custom_prompt 경로는 캐릭터/소품 참조만 사용)
          - ref_image_map에 bytes가 있는 항목만 수집
          - 라벨: character → "character identity", 그 외 → "object appearance"

        Phase 3 정련 (2026-06-11 S10 sh5 v2 실측): chain_bg_entry(해당 shot 의
        bg map entry — space plate/chain bg)가 주어지고 image_bytes 가 있으면
        ★첫 ref 로 부착★ — custom_prompt 라도 환경 정체성 ref 가 없으면 모델이
        벽 재질·구조를 텍스트만으로 발명한다(자동 경로 5a 와 정합).
        no-plate sentinel(image_bytes 없음)은 부착하지 않는다.

        예외는 suppress하고 warning log + 빈 리스트 반환 (non-fatal — ref 없이 진행).
        r   r   r   r   r   r/  zobject appearanceimage_bytesr   rT   zbackground environment refu4   labeled_refs 구성 실패: %s — ref 없이 진행N)r'   rG   insertr   r   r   )r   r   r   r  rH   rh  rI   r   etyperT   r  s              r%   build_custom_labeled_refsz/SceneReferenceService.build_custom_labeled_refs  s   . 13	X1AB1AAQtWaZ1AMB%gmR0J&3-+?49[4H0NaE ''c0B(CD & ."4"4]"C##&**73S7S&}5 # C  	XNNQSVWW	Xs2   B# B#B# B# AB# B# #	C,CC)r   
OrmSessionr   r!   rA  None)r   Optional[str]rA  List[Dict[str, Any]])r   r  r   r  rA  r  )r   Dict[str, bytes]rI   Dict[str, Dict]r   Optional[Dict[str, str]]rA  r  r    )r  	List[str]r   r)   r   r  rI   r  r  Optional[Dict[str, Dict]]r  Optional[List[Dict[str, Any]]]r   Optional[Dict[str, Any]]r  Optional[Set[str]]r  r  rA  r   )rH   zList[Tuple[str, Any]]r3   List[Tuple[str, str]]rA  z&Tuple[List[str], List[Dict[str, Any]]])rq   r!   r   r)   r   r  rI   r  r  r  r  r  r   r  r  r  r  r  rA  r   )rf  r  r   r!   rA  zDict[str, str])rf  r  rA  r  )r   r  rA  r  )r   r!   rA  Dict[str, Dict[str, Any]])r  zOptional[bytes]r  z0Literal['dep_scene', 'location_history', 'none']r  r@  r   r  r  r  r  r  r  r  r  r@  r  r@  r   r  r  r@  rI   r@  rA  z5Optional[Tuple[str, bytes, str, str, Dict[str, Any]]])r   r!   rA  zDict[str, Set[str]])r   r  rI   r  r   r  r   r  r  zOptional[Dict[str, Set[str]]]rA  zDict[str, Dict[str, str]])r   r  r   r  r  r  rA  zList[Tuple[str, bytes]])re  
__module____qualname____doc__r   r   r   r  r  r  r  rq  rt  rx  r  r  r  r  r  r  r  r0   r%   r   r   N  s   `&& P!'!0=!	!P 6:p#'p# 'p#
 3p# 
p#p 9=9
 9=,048;?9
9
 9
 .	9

 '9
 69
 69
 *9
  29
 !99
 
9
v
 ,
 -	

 
0
2 9=i
 9=,048;?i
i
 i
 .	i

 'i
 6i
 6i
 *i
  2i
 !9i
 
i
V@*-@2@@D$L$L
 4
	
< 
D{V ){V L	{V
 #{V /{V ({V ${V %{V !/{V '{V *{V +{V &{V 
?{VzH AEL".L" 1L" .	L"
 *L" !>L" 
#L"f 48*.* (*
 1* 
!*r0   r   )r+   r@  rA  r!   )rH   r)   rI   r  r1   zOptional[List[str]]r2   r  r3   zOptional[List[Tuple[str, str]]]rA  tuple)rq   r!   rR   zDict[str, int]rr   r  rA  r!   )r3   r   r1   r  r2   r  r   r  r   r  rI   r  rA  r  )r   r  rA  r  )1r  
__future__r   r   loggingr@   pathlibr   typingr   r   r   r   r	   r
   r   
sqlalchemyr   r   r   sqlalchemy.ormr   r  app.core.errorsr   r  r   r   r   app.core.keep_elementsr   app.core.subject_stater   app.models.projectr   r   r   r   	getLoggerre  r   r/   rc   rz   r   r   r   r   r   r  r0   r%   <module>r     sY    #   	  A A A & & 0 $ 
 : 7 [ [			8	$D &*8<59oIoI"oI #	oI
 6oI 3oI oId!!! ! 		!HEM(EMEM ,EM
 &EM +EM #EM 
EMX R D&^ ^r0   