
    Oj                       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
 ddlmZmZmZmZmZ ddlmZ  ej&                  e      ZdZ G d	 d
e      Z ed       G d d             Z	 	 	 	 	 	 	 	 	 	 ddZe G d d             ZddZddZ	 	 	 	 	 	 ddZddZ	 d 	 	 	 	 	 	 	 d!dZ ddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d"dZ!	 	 	 	 	 	 	 	 d#dZ"	 	 	 	 d$	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d%dZ#y)&u(  T2I 프롬프트 최종 조립 서비스 — Phase 3b.1.

기존 `image_service._build_final_scene_prompt` 211줄을 4단계 함수로 분리.
단계:
1. `resolve_ref_roles` — labeled_refs → ref_roles + ref_instructions
2. `replace_entity_ids` — t2i_prompt 내 C##/C##O##/P##/L## ID를 참조 이미지 번호 또는 entity 텍스트로 치환
3. `translate_if_korean` — 한국어 잔재 있으면 LLM 번역
4. `build_scene_text` — 최종 조립
공개 진입점: `build_final_scene_prompt` (기존 함수 계약과 동일한 시그니처).
    )annotationsN)	dataclass)Path)AnyDictListOptionalTuple)settings)outfit_ref_explicitprevious_shot_same_frame_zoomedprevious_shot_same_roomprevious_shot_continuitybackground_chain_refcharacter_refcharacter_state_refoutfit_ref_inlineprop_refbackground_generalgrounding_part_detail_refgrounding_context_detail_reffallbackcomposition_guideimmobilized_pose_guideindoor_pose_guideoutdoor_canon_photo_refoutdoor_canon_map_refc                      e Zd ZdZy)RefRoleErroruK   LabeledRefPayload invariant 위반 — fail-fast (No Silent Fallback gate).N)__name__
__module____qualname____doc__     Q/Users/manta/Documents/Projects/TheRoad-I1/backend/app/services/prompt_service.pyr   r   H   s    Ur%   r   T)frozenc                  :    e Zd ZU dZded<   ded<   ded<   ded	<   y
)LabeledRefPayloadu  Area #11 v1 — producer-side structured payload for reference image dispatch.

    Invariants (fail-fast via make_labeled_ref_payload factory):
    - len(labeled_refs) == len(ref_roles) == len(ref_role_metadata) == len(attached_meta)
    - 각 ref_roles entry ∈ REF_ROLE_VALUES (else RefRoleError)
    - 각 ref_role_metadata entry 는 dict
    - 모든 4 collection 은 list (factory top-level isinstance gate)

    W1 = container + factory + REF_ROLE_VALUES + RefRoleError 신설.
    Production 진입은 W2 atomic switch.
    List[Tuple[str, Any]]labeled_refs	List[str]	ref_rolesList[Dict[str, Any]]ref_role_metadataList[Tuple[str, str]]attached_metaN)r    r!   r"   r#   __annotations__r$   r%   r&   r)   r)   L   s!    
 ('++((r%   r)   c           
        t        | t              s"t        dt        |       j                   d      t        |t              s"t        dt        |      j                   d      t        |t              s"t        dt        |      j                   d      t        |t              s"t        dt        |      j                   d      t        |       }t        |      |k7  rt        dt        |       d|       t        |      |k7  rt        dt        |       d|       t        |      |k7  rt        d	t        |       d|       t        |      D ]&  \  }}|t        vst        d
| d|dt         d       t        |      D ]:  \  }}t        |t              rt        d| dt        |      j                   d       t        t        |       t        |      t        |      t        |            S )u  Factory — fail-fast on invariant violation (No Silent Fallback gate).

    9 fail-fast paths:
    - 4 non-list isinstance: labeled_refs / ref_roles / ref_role_metadata / attached_meta
    - 3 length mismatch: ref_roles / ref_role_metadata / attached_meta vs labeled_refs
    - 1 invalid enum value: ref_roles entry not in REF_ROLE_VALUES
    - 1 non-dict metadata entry: ref_role_metadata entry not dict
    zlabeled_refs is z
, not listzref_roles is zref_role_metadata is zattached_meta is zref_roles length z != labeled_refs length zref_role_metadata length zattached_meta length z
ref_roles[z]=z" not in REF_ROLE_VALUES (allowed: )ref_role_metadata[z] is z
, not dict)r+   r-   r/   r1   )

isinstancelistr   typer    len	enumerateREF_ROLE_VALUESdictr)   )r+   r-   r/   r1   nirolems           r&   make_labeled_ref_payloadrA   `   s
    lD)-d<.@.I.I-J*UVVi&]4	?+C+C*DJOPP'.248I3J3S3S2TT^_``mT*.tM/B/K/K.LJWXXLA
9~.s9~.>>VWXVYZ[[
"',=(>'??WXYWZ[
 	
 =Q23}3E2FF^_`^abccY'4&QCr$ *,-Q0  ( +,1!T"!3A3eDG<L<L;MZXYY - ,'y/01=)	 r%   c                  J    e Zd ZU dZded<   ded<   edd       Zedd       Zy)	RefResolutionu(   `resolve_ref_roles` 반환 컨테이너.r,   r-   ref_instructionsc                T    | j                   rdj                  | j                         S dS )N
zNo reference images.)r-   joinselfs    r&   
roles_textzRefResolution.roles_text   s!    ,0NNtyy(V@VVr%   c                8    dj                  | j                        S )NrF   )rG   rD   rH   s    r&   instructions_textzRefResolution.instructions_text   s    yy..//r%   N)returnstr)r    r!   r"   r#   r2   propertyrJ   rL   r$   r%   r&   rC   rC      s9    2W W 0 0r%   rC   c                    d|  d|  dS )uU  B-run S15 실측 fix (2026-06-12) — back_to_camera staged 인물의 ref 지시.

    producer 는 scene_reference_service.apply_back_to_camera_constraints
    (staging.character_angles structured enum 조인) — 정면 여권사진 ref +
    "match identity" 지시가 본문의 back-to-camera 작문을 누르는 정면 bias 대응.
    - image ue   : this character faces AWAY from the camera in this shot — the face must NOT be visible; use image u   only for hair silhouette, body build, and clothing/outfit continuity; show the back of the head and body, do not turn the character toward the camera; in a single still image motion direction reads through depth — if the prompt describes this character as moving away from the camera or from another figure, they must be visibly receding: placed deeper in the scene, and smaller in the frame than the stationary figure they are moving away from when both are visible — never large in the immediate foregroundr$   )r>   s    r&   _back_to_camera_instructionrR      s$     1# 334# 6#	$r%   c                8    d|  d}d|  dd|  dd|  dg}||fS )	u  W21B-W8 재배선 v2 — 같은 장소·순간 인접 샷의 '완성 프레임' 을 연속성 anchor
    로 사용(마네킹 스케치 대신). 장소·조명·인물 외형 + broad staging/figure
    placement 의 연속성은 이 프레임에서, 이 샷의 카메라/프레이밍/POV/crop/액션은
    본문 SOT (충돌 시 본문 우선 — 예: 같은 그룹 샷이 '엿보는 POV' 등 고유 프레이밍을
    가질 때 그 프레이밍 보존). 인접 샷끼리 '같은 연속된 순간' 으로 읽히게 한다.Reference image ue   : SAME-PLACE COMPLETED FRAME from an adjacent shot at the same moment — visual continuity referencez0- keep this shot visually continuous with image z: the same location, environment, architecture, materials, lighting, weather and time of day, and the same people's appearance, build and clothingz- match image u  's broad staging — where each figure stands across the space and their relative scale — so the adjacent shots read as one continuous moment; this is the one exception to the 'do not copy compositions' rule below, limited to continuity of place and figure placementu   - follow the prompt text for THIS shot's specific camera position, framing, point of view, crop and action — where the prompt text and image z5 differ on framing or viewpoint, the prompt text winsr$   )r>   	role_textinstructionss      r&   _continuity_anchor_refrW      si     1# = 	> 
 ;1# >D 	E   	
H	JL l""r%   c                h    d|  d}|r
d|  d|  d}nd|  d|  d|  d}d	|  d
|d|  dd|  g}||fS )u?  A5 (2026-07-02) — immobilized 그룹 env 멤버의 '완성 프레임' 연속성 ref.

    부동(dead/unconscious/severely_injured) 인물은 샷 사이에 움직일 수 없으므로
    이 프레임이 그 인물의 포즈/접촉점/지지면 + 방 상태·소품의 연속성 SOT.
    단 등록 pose 가이드가 함께 붙으면 자세/접촉/지지면의 1차 SOT 는 가이드
    (P8 I-1 역할 분리) — 이 프레임은 same-configuration 확인 + 환경/소품 연속성.
    프레이밍/카메라/crop 은 본문 프롬프트 SOT. 시나리오 토큰 0.
    rT   ug   : COMPLETED EARLIER FRAME from this same scene — state continuity reference for an immobilized figureze- any figure in an immobilized state (dead, unconscious, severely injured) has NOT moved since image z. Take its body pose, contact points and the surface it rests on from the attached pose guide (the source of truth for this figure's pose and contact); image z< shows the same configuration for environment and continuityzY: reproduce its exact body pose, contact points and the surface supporting it from image u]    — if the prompt text implies a different body position for this immobilized figure, image  winsz!- keep the room state from image z^: the same architecture, furniture, materials, lighting, and the same props in the same placesup   - follow the prompt text for THIS shot's camera position, framing, point of view and crop — do NOT copy image z's composition4- do NOT copy any standing/moving people from image r$   )r>   pose_guide_presentrU   
_pose_linerV   s        r&   _immobilized_prev_frame_refr]      s     1# ? 	@  223 5OOPc RJK 	223 5MMNC P))*5	2 	 ,A3 /E 	F889s.	J
>qcBL l""r%   c           
     &   g }g }d| j                   v }t        d | j                  D              }t        | j                  d      D ]  \  }\  }}| j                   |dz
     }| j                  |dz
     }	|dk(  r|	j                  d      }
t        |
t              r.t        |
      dk7  s t        |
d   xs d      j                         st        d|dz
   d	|
d
| d      t        |
d         j                         }|j                  d| d|        |j                  d| d| d       |dk(  r^|j                  d| d|        |j                  d| d       |	j                  d      dk(  s-|j                  t        |             I|dk(  r(|j                  d| d       t        |	j                  d            }|r
d| d| d}n	d| d| d}|j                  d| d|dd | g       |r|r|j                  d!| d"       n|r|j                  d#| d$| d%| d&       |	j                  d'g       xs g D ]I  }t        |t               r|j                  d(d      n
t        |      }|s3|j                  d#| d)|        K |	j                  d*d      }|s^|j                  d#| d+|        w|d,k(  r|	j                  d-      r2t#        ||      \  }}|j                  |       |j                  |       n|	j                  d.      r1t%        |      \  }}|j                  |       |j                  |       nt|	j                  d/      r4|j                  d| d0       |j                  d1| d2| d3d4| g       n/|j                  d| d5       |j                  d6| d7d4| g       |	j                  d'g       xs g D ]I  }t        |t               r|j                  d(d      n
t        |      }|s3|j                  d#| d)|        K |	j                  d*d      }|s|j                  d#| d+|        |d8k(  r|	j                  d-      r3t#        ||      \  }}|j                  |       |j                  |       Q|	j                  d.      r2t%        |      \  }}|j                  |       |j                  |       |	j                  d9      r7|j                  d| d:       |j                  d;| d<d=d>| d?d@| g       |j                  d| dA       |j                  dB| dC| dD| g       |dEk(  r<|j                  d| dF       |j                  d| dGdH| dIdJ| dK| dLdMg       Q|dNk(  r^|j                  d| d|        |j                  d| dO       |	j                  d      dk(  s|j                  t        |             |dPk(  r\|	j                  dQd      }|j                  d| d|        |r|j                  d| dR| dS       |j                  d| dT       |dUk(  r-|j                  d| d|        |j                  dV|        G|dWk(  r+|j                  d| dX       |j                  dY|        w|dZk(  r,|j                  d| d[       |j                  d\| d]       |d^k(  r,|j                  d| d_       |j                  d`| da       |dbk(  r@|j                  d| dc       |rdd| dendd| df}|j                  |dg| dhdJ| g       |dik(  r2|j                  d| dj       |j                  d| dkdl| dmg       U|dnk(  r<|j                  d| do       |j                  dp| dqdr| dsdd| dtdu| dvg       |dk(  rt        |	j                  dw      xs d      }t        |	j                  dx      xs d      j                         j'                         }|dyk(  rdznd}|j                  d| d{       |j                  dd| d|| d&d}| d~d|rd| dndz   dz   dd| dg       E|dk(  r3|j                  d| d       |j                  dd| dddd| dg       }|j                  d| d|        |j                  d| d|         |j                  g d       t)        ||      S )u  Area #11 v1 W2 — `payload.ref_roles` enum dispatch only.

    `_classify_label` substring branch 폐기 (production dispatch path). consumer
    는 producer 가 emit 한 11-enum sidecar 만 read (No Silent Fallback gate).

    spec §3.3 / plan Task 2.5.
    r   c              3  b   K   | ]'  }t        |t              xr |j                  d        ) yw)outdoor_direct_place_continuityN)r6   r<   get).0mds     r&   	<genexpr>z$resolve_ref_roles.<locals>.<genexpr>!  s1      $+B 	2tJ(I!JJ+s   -/   r   subject_final_idsr    r5   zM] of outfit_ref_explicit must carry exactly one subject_final_ids entry (got u/   ) — the prompt cannot say whose outfit image z isrT   z*: standalone outfit/costume reference for z- dress z in the outfit shown in image uO   ; this image is the outfit only — do not take face, body, or identity from itr   z: z- use image uh    as character appearance reference — match the person's identity and outfit where visible in the sceneview_angle_constraintback_to_camerar   uF   : PREVIOUS SHOT (SAME MOMENT, zoomed-in reframing) — continuity baseimmobilized_subjectsz@- keep the environment, lighting, and spatial layout from image zw; render the poses of any standing or moving subject as described in the prompt text (for a true zoom they match image r4   zh; render the subjects and their poses as described in the prompt text (for a true zoom they match image ul    as the continuity base for a zoomed-in reframing — same moment, same environment, the camera moved closerz- do NOT duplicate body partszx- render the focused region (hand, wrist, face area, object, etc.) enlarged, consistent with the frame context of image a1  - the immobilized figure (dead, unconscious, severely injured) has NOT moved between these two shots. Take its body pose, the visible limb (hand, wrist, arm), the surface it rests on, and its hand-object contact from the attached pose guide (the source of truth for this figure's pose and contact); image u    shows the same configuration for environment and continuity. The crop and framing may change (this is a closer insert), but reproduce that one pose and contact exactly — do not let the prompt wording move the immobilized figurez- from image z: any figure in an immobilized state (dead, unconscious, severely injured) has NOT moved between these two shots. Lock to image u  : its body and the visible limb (hand, wrist, arm), their exact position and orientation, the surface the figure/limb rests on, and how the hand contacts any held object — reproduce them precisely. The crop and framing may change (this is a closer insert), but if the prompt text implies a different body position, resting/support surface, or hand-object contact for this immobilized figure, image rY   keep_elementslabelz: keep ignorez	: ignore r   immobilized_prev_frame_anchorcomposition_continuity_anchorcomposition_relaxeduK   : BACKGROUND from a previous shot (SAME PLACE) — place identity referencezd- keep this location's materials, architecture, furniture identity, lighting and weather from image z:- do NOT copy the camera position or composition of image uJ    — the composition sketch and the prompt text define this shot's framingrZ   u;   : BACKGROUND from a previous shot (SAME ROOM) — use as-iszG- use the background, furniture layout, walls, and lighting from image z as-isr   r`   u[   : PREVIOUS STILL at the same property — continuity source for stable visible details onlyz!- where this shot overlaps image z~'s view, keep the same stable visible details (openings, fixed fittings, lettering, local object placement) consistent with itzs- the attached LOCATION PHOTOGRAPH/SITE PLAN stay the primary source for the property's overall identity and layoutz1- do NOT copy lighting or time of day from image u3    — the shot text is the sole time/lighting sourcezC- do NOT copy characters, people, poses, or composition from image zB: BACKGROUND/ENVIRONMENT from a previous shot at the same locationzH- use ONLY the lighting, color palette, and environment mood from image zB- do NOT copy characters, people, or their appearances from image z<- do NOT copy the composition or character poses from image r   uU   : pre-rendered BACKGROUND environment reference — layout/material/lighting identityzX for this location's wall/floor/ceiling materials, furniture identity, and lighting moodu   - follow the camera position, framing, and action requested in the prompt text — do NOT copy the camera position or composition of image z) unless the prompt explicitly asks for itz$- do NOT copy any people from image zG- architectural openings (doors, windows, thresholds) must match image u    — same position, type, and material; do NOT invent additional doors or windows unless the prompt text explicitly asks for themu3  - if the prompt text places the camera inside or at one of these openings, camera-side walls and door/window frames may appear only as local foreground edges — do NOT show the same opening again in the scene, and do NOT show the exterior facade of the structure that contains the camera out in front of itr   u]    as character appearance reference — match the person's identity where visible in the scener   statez as state-variant reference (uA   ) — match the character's body language for this specific stateu[    as state-variant reference — match the character's body language for this specific stater   z$- include the object shown in image r   z#: background/environment reference.zB- use the lighting, architecture, and environment mood from image r   u?   : FIXED PART OF THE PLACE — how this component itself is madez)- reproduce the component shown in image uu    — its shape, proportions, materials and markings; take its form only, not the photograph's framing or surroundingsr   uK   : THE PLACE AND ITS FIXED PART — the setting, and the component within itz9- take the place's architecture and materials from image uc   , and reproduce the component within it — form only, not the photograph's framing or surroundingsr   uH   : LOCATION PHOTOGRAPH — the real filming property this shot happens atrQ   z is the PRIMARY source of how this place looks (building, materials, aging, colours, surroundings); the previous-still reference refines stable visible details in overlapping areasz^ is the SOLE source of how this place looks: building, materials, aging, colours, surroundingsz- do NOT copy image ud   's camera angle, composition, time of day or lighting — the prompt text defines this shot's momentr   u?   : SITE-PLAN DRAWING of the same property — layout source onlyzS ONLY to understand where things are on the property (positions, adjacency, routes)z- NEVER draw image zB itself or its colours, circles, markers or labels into the outputr   u   : POSE + COMPOSITION STORYBOARD SKETCH of THIS exact shot — figures drawn as featureless artist's mannequins (framing + pose guide only)z*- match this frame's composition to image u   : the same figure screen positions, relative sizes and depth, and the same receding path / vanishing perspective — this sketch is the one exception to the 'do not copy compositions' rule belowz- each human figure in image u*   is a featureless posed mannequin with a constructed head: reproduce its body pose, stance and head-facing direction exactly, then render it as a real clothed person — identity, face, hair, clothing and skin come from the prompt text and the character reference image(s), never from the mannequinu   is only a rough construction sketch: do NOT draw any mannequin, joints, spheres or construction lines in the final image, and do NOT copy its flat tones, line style or any environment surface detail — environment identity comes from the other reference images and the prompt textzu- the final image must be a full-colour PHOTOREALISTIC render under the scene's real lighting: never reproduce image z's monochrome line-art look, pencil/marker strokes, sketch shading, unfinished background or paper texture in ANY part of the framevisible_focussubject_statedeadz (now lifeless)u   : POSE / SUPPORT GUIDE for the immobilized figure — a line-art mannequin registered over a faint room (pose / contact / support-surface reference only, NOT identity, appearance or environment)u    fixes the immobilized figure's exact body posture, limb positions, the support surface it rests on, and how its hand contacts any held or adjacent object: reproduce that one pose and contact exactly so the body stays supported on that surface and never floats. This guide is the source of truth for this figure's pose, resting/support surface and hand-object contact — it is the one exception to the 'do not copy poses' rule below, and where the prompt wording implies a different body position or contact for this immobilized figure, image z=- render this figure as ONE real, photorealistic human personu   with real skin, hair, fabric clothing and a natural human face and body: its identity, face, hair, clothing and state come from the prompt text and the character reference image(s), and its environment from the background reference — NEVER from this guide. It must NOT look like a wooden mannequin, an articulated dummy, a doll, a statue, a figurine or a 3D render — no wooden or plastic surface, no visible ball joints, no smooth featureless or missing facez- this shot's camera, framing and crop come from the prompt text: if it is a tight insert, show only the visible portion relevant to this shotz (z/ and do NOT force the whole body into the framez is only a rough pose sketch: do NOT draw any mannequin, joints, spheres or construction lines in the final image, and do NOT copy any environment, surface texture or background from itr   u   : POSE + PLACEMENT GUIDE for this shot — line-art mannequins registered over a faint underlay of this room (pose/placement reference only, NOT identity, appearance, style or environment)u    fixes each figure's screen position, depth, body posture and facing direction: reproduce that placement and pose for the matching figures — it is an exception to the 'do not copy poses' rule belowz- render those figures as real clothed people: identity, face, hair and clothing come from the prompt text and the character reference image(s), never from the mannequinsu   is only a rough registration sketch: do NOT draw any mannequin, joints or construction lines in the final image, and never reproduce its monochrome line-art look, pencil strokes, faint washed-out underlay or paper texture in ANY part of the frame — the final image is a full-colour PHOTOREALISTIC render under the scene's real lighting, and its environment comes from the background reference and the prompt text, not from this guidez- reference image )z9- do not copy poses or compositions from reference imageszL- do not alter character identities where their face is visible in the scenez- each character's clothing/outfit must match that character's appearance reference image EXACTLY; if any clothing wording in the text conflicts with the reference image, follow the reference image and ignore that wordingu{   - only render what the scene description asks for — if only a hand or wrist is described, do NOT add the character's face)r-   rD   )r-   anyr/   r:   r+   ra   r6   r7   r9   rN   stripr   appendrR   boolextendr<   r]   rW   lowerrC   )payloadref_roles_textrD   r[   direct_place_prev_presentr>   rl   _r?   metadata	_subjects_sid_has_immobilizedr\   elem_label_ignore_rt_ins_state_canon_authority_vf_lifes                          r&   resolve_ref_rolesr     s    !#N"$ 2W5F5FF
 !$ $++$ ! #7#7#7;:E1  Q',,QU3(( !%89Ii.#i.A2Ey|1r288:"(Q 0F m $Cs$% %
 y|$**,D!!"1# &f ##4& >qc B  ((!!$4QCr%"AB##qc "T U ||348HH ''(CA(FG66 !!"1# &1 2  $HLL1G$HIC  Cq" C  LLM3aQ 
 ##qc "I J/HHIsL% "  $6 '''
 ()c *)	* " ''#A3 '667S 91 235	: !_b9?R?2<T42H'2.cRVi$++mA3gfX,NO @ ll8R0G ''-s)G9(MN..||;<
 8;MN	T%%c* ''-=> 315	T%%c* ''-34 %%&qc *@ A !''AABEPc *+ K1#N)  %%&qc)de !'']^_]``fgJ1#N)  !_b9?R?2<T42H'2.cRVi$++mA3gfX,NO @ ll8R0G ''-s)G9(MN//||;<7;MN	T%%c* ''-=>215	T%%c* ''-?@ %%&qc *H I !''7s ;A BLGs KA BYZ[Y\]	) 	 %%&qc)kl !''^_`^abXYZX[\RSTRUV) 
 ++ !!"1# &4 5 ##qc "8 9DF 7qc:  !)% 6 _$!!$4QCr%"AB##qc "I J ||348HH ''(CA(FG**\\'2.F!!$4QCr%"AB ''"1#%B6( KR S
 !''"1# &R S Z!!$4QCr%"AB##&J1#$NO))!!$4QC7Z"[\##&hijhk$lm00!!"1# &+ , ##;A3 ?? @
 33!!"1# &7 8 ##K# ?@
 .. !!"1# &0 1 -	 1# $ %
 1# D E  ## &qc *Q R6qc:	%  ,,!!"1# &) * ##qc ": ;%aS )4 5%  (( !!"1# &9 :
 ##<QC @I J 0s 3+ , 1# A BJJK M /% : -- hll?39r:C
 o6<"=CCEKKMF)/6)9%rE!!"1# &- . ##1# 2 34E; P'   #&RuA;2/ D	E
 1# R S7% > (( !!"1# &( ) ##1# 1 2J 1# 3 4% $ !!$4QCr%"AB##&82eW$EFy <|    >DTUUr%   c                   |xs i }dfd}i }t        j                  d|       D ]Y  }|j                  d      }||vs ||      }|r	d| ||<   ,||v r	||   ||<   9|j                  d      d   }	|	|v sR||	   ||<   [ t        j                  d|       D ]8  }|j                  d      }||vs ||      }|r	d| ||<   ,||v s1||   ||<   : t        j                  d|       D ]8  }|j                  d      }||vs ||      }|r	d| ||<   ,||v s1||   ||<   : t        j                  d	|       D ]%  }|j                  d
      }||vs||v s||   ||<   ' | }
t        j                  dd|
      }
t        |j                         d       D ]  \  }}|
j                  ||      }
 t        j                  dd|
      }
|
S )u  t2i_prompt 내 short_id(C##/C##O##/P##/L##)를 참조 이미지 번호 또는 엔티티 텍스트로 치환.

    - labeled_refs에 해당 ID가 있으면 "the {type} shown in image N"
    - 없으면 entity_text_map 경유 한글/영어 설명으로 치환
    - 대괄호 `[L##: desc]`은 치환 전 내용만 남겨 global replace 간섭 방지

    표현 주의 (Area #5 v1, 2026-05-18): numbered image wording
    (e.g. "the character shown in image N") is preserved for reference mapping.
    phantom validation is handled by structured `reference_phrase_kinds` sidecar
    (scene_detail v26 producer + ref_contract_validator step 6), not prompt-prose
    regex. legacy "from Reference image N" 표현은 의미 동등 표현 "shown in
    image N" 으로 치환.
    c                D    t        d      D ]  \  }\  }}| |v s|c S  y )Nre   )r:   )sididxrl   r~   r+   s       r&   _get_ref_numz(replace_entity_ids.<locals>._get_ref_num
  s-    (q9OC%e|
  : r%   zC\d{2,3}O\d{2,3}r   zthe character shown in image Oz(?<![CO\d])C\d{2,3}(?!O\d)z(?<![A-Z])P\d{2,3}zthe object shown in image z#(?<!\[)(L\d{2,3})(?!\d)(?![^\[]*\])re   z\[L\d{2,3}:\s*([^\]]+)\]z\1c                     t        | d          S )Nr   )r9   )xs    r&   <lambda>z$replace_entity_ids.<locals>.<lambda>@  s    S1YJr%   )keyz,?\s*in O\d{2,3}\brg   )r   rN   rM   zOptional[int])refinditergroupsplitsubsorteditemsreplace)
t2i_promptr+   entity_text_map_etmr   	id_to_refmatchr   ref_nchar_sidcleanedreplacements    `          r&   replace_entity_idsr     s   $  bD !#I 0*=kk!ni %E#@!H	# $;%)#YIcN"yy~a0H4')-h	# >  :JGkk!ni %E#@!H	#!%c	# H 2J?kk!ni %E#=eW!E	#!%c	# @ CZPkk!niC4K!#YIcN Q Gff0%AG #9??#4:NO[//#{3 P ff*B8GNr%   )scene_indexproject_configtracerlabeled_refs_countc                  t        j                  d|       s| S ddlm} ddlm}	 	  |	dd||||       }
d
}	 t        j                         } |dd|
|d      }t        t        j                         |z
  dz        }t        |t              rT|j!                         rD|j!                         } d}|r0|j#                  dt$        j&                  |
d	d dz   |d| d|i|       |s0t        j                  d|       rt        j                  d|| d	d        | S # t        t        t        f$ r"}t        j                  d|       | cY d	}~S d	}~ww xY w# t(        $ r }t        j+                  d|       Y d	}~d	}~ww xY w)u  한국어가 남아있으면 LLM으로 영어 번역. 실패 시 원본 유지.

    프롬프트 템플릿은 `prompts/_base/scene_image/{version}/translate_prompt.md`에서 읽음.
    번역 후에도 한국어 잔재가 있으면 error 로그 (안전 필터 위험).
    u	   [가-힣]r   )	call_text)load_promptscene_imagetranslate_prompt)r|   rD   style_contextr   uP   translate_prompt unavailable for scene_image module — skipping translation: %sNFprompt_translationu5  T2I 프롬프트 번역 전문가. 한국어를 영어로 변환하세요. 'the character/object shown in image N' 표현은 정확히 보존하세요. 원문에 없던 reference attribution 표현을 새로 추가하지 마세요 (producer 가 declare 한 reference_phrase_kinds sidecar 와 mismatch 차단).g?)stepsystem_promptuser_promptr   temperaturei  Ti  z...)translation_prompt	ref_countr   )stagemodel
input_dataoutput_datar   
elapsed_mszPrompt translation failed: %szqPrompt translation failed and Korean text remains in prompt (scene=%s). Gemini may trigger safety filter. Raw: %si,  )r   searchapp.modules.llm.llm_clientr   app.modules.prompt_loaderr   FileNotFoundErrorRuntimeErrorKeyErrorloggererror_timetimeintr6   rN   rv   logr   gemini_flash_model	Exceptionwarning)r   r|   ref_instructions_textr   r   r   r   r   r   r   r   exc
translated_t0result_elapseds                   r&   translate_if_koreanr   M  s     99\7+45()2'
" J$=jjl%a
 +)
 

s*d23fc"v||~llnGJ

. #55.@#.F.N%7  !(+[9'  & "))L':DDSM		
 No |X6 '(+	
 Z  =6<<=s5   D B(E E	'E>E	E		E5E00E5c                V    d|  d|j                  dd      j                          d| dS )u  최종 조립: ref_roles + 씬 본문 + 지시 + CRITICAL 주의.

    단일 프레임 계약 (2026-07-02): 하나의 t2i = 한 순간·한 프레임 (CLAUDE.md
    단일 스틸컷 원칙)의 렌더측 강제 — 매체 형식 계약이라 시나리오 무관.
    z!Photorealistic cinematic still.

z

zPhotorealistic cinematic still.rg   z

Generate one image:
u`  
- CRITICAL: Only render what the scene description explicitly describes. If a character's face is visible in the scene, match it to the reference. If only a body part is shown, do NOT add the face.
- CRITICAL: Output exactly ONE single continuous photograph of ONE moment — never a panel grid, collage, contact sheet, split-screen, storyboard sheet, or multiple sub-frames inside one image.
- CRITICAL: Every person must be physically grounded and weight-bearing — in real contact with a supporting surface, with body weight visibly carried by that contact. For a figure caught mid-action (falling, stumbling, collapsing, jumping off something), render a physically plausible instant: joints bent, weight clearly transferring, at least one believable point of contact or support — never a rigid body hovering, tilted or suspended in mid-air without support.)r   rv   r|   r   r   s      r&   build_scene_textr     sE     	.
$??<bAGGI
J K  56 7#	#r%   c                B   t        |      }t        | |j                  |      }t        ||j                  |j
                  ||||t        |j                              }t        |j                  ||j
                        }	|r|j                  || |j                  |	       |	S )u  T2I 프롬프트를 최종 영어 씬 프롬프트로 변환.

    Area #11 v1 W2 (2026-05-18+): signature `labeled_refs: list` →
    `payload: LabeledRefPayload`. producer-side structured sidecar 받아
    consumer enum dispatch only (substring branch 0). spec §3.3.

    4단계 파이프라인: ref 역할 (enum dispatch) → ID 치환 → 한글 번역 → 최종 조립.
    )r|   r   r   r   r   r   r   r   )	r   r   r+   r   rJ   rL   r9   r   log_ref_matching)
r   r{   r   r   r   r   r   ref_resolutionr   finals
             r&   build_final_scene_promptr     s    " 'w/N W-A-A?SG!%00,>>#%w334	G %00,>>E
 Z9M9MuULr%   )
r+   r*   r-   r,   r/   r.   r1   r0   rM   r)   )r>   r   rM   rN   )r>   r   rM   Tuple[str, List[str]])r>   r   r[   rx   rM   r   )r{   r)   rM   rC   )N)r   rN   r+   r*   r   Optional[Dict[str, str]]rM   rN   )r   rN   r|   rN   r   rN   r   rN   r   r   r   Optional[Dict]r   r   r   r   rM   rN   )r|   rN   r   rN   r   rN   rM   rN   )Nr   NN)r   rN   r{   r)   r   rN   r   r   r   r   r   r   r   r   rM   rN   )$r#   
__future__r   loggingr   r   r   dataclassesr   pathlibr   typingr   r   r   r	   r
   app.core.configr   	getLoggerr    r   r;   r   r   r)   rA   rC   rR   rW   r]   r   r   r   r   r   r$   r%   r&   <module>r      s  	 #  	  !  3 3 $			8	$&RV9 V $) ) )&1'11 ,1 )	1
 1p 0 0 022#4'#
'# $'#'#T`VT 15PP'P .P 		Pz %)aaa a 	a a #a a a 	aP  		T %)04$$$ $ 	$
 $ #$ .$ 	$r%   