
    Oj                       U d Z ddlmZ ddlZddlZddl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ded<   d	Zded
<   dZded<   dZded<   dZded<   dZdddiddddZdddddiddddg dddedd edd!igd"d#d$g d$d%d&d'ddddidd(ddd!gd)ddddd*ieedd!igd+d#d,g d,d%d&d'd-g d-d%d&d'dddd*ieed.g d.d%d&d'd/g d/d%d&Zd0ed1<   dd2dddd*iddiddddiddid3d4d5gd%d&d6d dd!gd7dd8g d8d%d&d'id2gd%d&Zd0ed9<   d:Zd;Z	 d}	 	 	 	 	 	 	 d~d<Z	 	 	 	 	 	 dd=Zd>Zd?Z d@e z   dAz   Z!ddB	 	 	 	 	 	 	 	 	 ddCZ"dDdE	 	 	 	 	 	 	 	 	 ddFZ#ddGddddHddg dIdddJdddKdddLdddMdddNddg dOddPg dPd%d&d'idGgd%d&Z$d0edQ<   dRZ%dSZ&ddB	 	 	 	 	 	 	 	 	 ddTZ'ddUZ(ddVZ)dWddX	 	 	 	 	 	 	 	 	 	 	 	 	 ddYZ*dWddX	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZZ+d[ddd\	 	 	 	 	 	 	 	 	 	 	 	 	 dd]Z,d[dd^	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd_Z-d`Z.ddaZ/dbZ0dcddddeZ1dDdddf	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddgZ2dddhidg dOdddiddiddjg djd%d&Z3d0edk<   dlZ4	 	 	 	 	 	 	 	 ddmZ5ddB	 	 	 	 	 	 	 	 	 	 	 ddnZ6dddhidg doddg dOdddddiddiddpddqg dqd%d&d'ddg drdd'dg dsddddidtd ddidug dud%d&Z7d0edv<   dwZ8ddxZ9ddB	 	 	 	 	 	 	 ddyZ:	 	 dddz	 	 	 	 	 	 	 	 	 	 	 dd{Z;	 	 d	 	 	 	 	 	 	 	 	 dd|Z<y)u  W21B-W8 (2026-06-12; 2026-06-13 구도 가이드 재배선) — outdoor_site_layout
LLM/이미지 IO boundary.

site-layout spike 의 production 포팅 — 단 spike 의 layout schema(특정 지형
enum)는 scenario-specific 이라 폐기하고 generic landmarks/figures/cameras
schema 로 대체한다.

  - ``emit_site_layout`` — LLM1: 같은 outdoor location 의 멤버 샷들 + 씬 원문
    전체에서 sparse top-down 좌표(0–100) emit. 좌표 검증은 plan 의
    shape/범위 validator (의미 게이트 없음).
  - ``rewrite_position_phrases`` — LLM2: deterministic 공간 요약을 근거로
    위치/깊이/상대크기/카메라거리 구절만 최소 보정 (Codex ⓑ 계약 — entity ID
    불변, edited_spans + no_edit_reason 출력, 카메라워크 언어 금지).
  - ``generate_composition_brief`` — 구도 가이드 ①: scene action + SCENE
    GEOGRAPHY → 요소포함 구도 브리프 텍스트 (gemini text).
  - ``generate_composition_sketch`` — 구도 가이드 ②: 브리프 → 마네킹+Loomis
    스케치 PNG (gpt-image-2 T2I). 최종 i2i still(③)은 이 모듈 밖(coordinator)
    에서 production 이미지 모델(nb2)로 — 변경하지 않는다.

규칙 (CLAUDE.md / 절대 규칙):
  - 입력 씬 텍스트는 절대 자르지 않는다 — 원문 전체 전달.
  - 프롬프트에 작품 고유명사/특정 시나리오 토큰 0 (요소는 데이터/LLM 으로만).
  - 출력 품질은 deterministic 테스트 비대상 — canary + 육안 gate.

LLM1/LLM2 모델 라우팅은 ``call_structured(step="outdoor_site_layout")`` —
manifest default_model(gpt=gpt-5.5) + project_config override 를 따른다. 구도
브리프/스케치 모델은 step 이 config(outdoor_composition_brief_model,
outdoor_composition_guide_model)에서 주입한다.
    )annotationsN)AnyDictListOptionalTuple)capture_artifactoutdoor_site_layout_v1strPROVIDER_VERSIONz1.202606170100PROMPT_VERSIONz#shared_model_aerial_v4.202607031200SHARED_MODEL_GUIDE_VERSIONi>  intLAYOUT_MAX_TOKENSip  REWRITE_MAX_TOKENSoutdoor_site_layoutarraytypenumber   z[x, y] normalized 0-100)r   itemsminItemsmaxItemsdescriptionobjectstringzWshort English label taken from the scene text (e.g. the road, the shoreline, a shelter))r   r   )pointlinearea)r   enumz01 point for kind=point, 2+ for line, 3+ for area)r   r   r   nullu  if this structure has ONE open/front side whose orientation the scene text states or its function makes unambiguous (an open-fronted structure faces what it serves; a doorway opens onto its approach): a point that open/front side faces toward. null when uncertain — never guess.)anyOfr   )idlabelkindpointsfaces_towardF)r   
propertiesrequiredadditionalProperties)r   r   z3the figure's name exactly as written in the stagingzlthe figure's entity ID token (like C01) if one appears in the provided staging, else null. Never invent one.integerzgif the staging says this figure is moving at this moment: a point they move toward. null if stationary.)
shot_indexposmoving_toward)	figure_idr$   entity_token	positions)r,   r-   look_at)	landmarksfigurescamerasDict[str, Any]SITE_LAYOUT_SCHEMArevised_prompts)originalrevisedr9   r:   z\each placement phrase you changed: the original span and what it became. Empty if unchanged.zhif you changed nothing, why the original already matches the spatial summary. null when edits were made.)variation_indexrevised_promptedited_spansno_edit_reasonREWRITE_RESULT_SCHEMAu  You are a film location planner. Several shots take place in the SAME outdoor location. From the scene text(s) (source of truth) and each shot's staging, emit a SPARSE top-down site layout as JSON with normalized coordinates 0-100 (pick any consistent orientation and keep it for everything).
Rules:
- landmarks: ONLY the large spatial anchors that the scene text itself establishes (paths, water, structures, terrain edges...) — no props, no decoration, no invention.
- faces_toward: for a structure whose open or front side is stated in the text or unambiguous from its function (an open-fronted structure faces what it serves; a doorway opens onto its approach path), give a point that side faces toward. When the orientation is not clearly established, use null — NEVER guess.
- figures: every person the staging places in these shots, one entry per person, with a position per shot. If the staging says a figure is moving at that moment, give a moving_toward point consistent with the text; otherwise null.
- cameras: one entry per shot, position + look_at consistent with that shot's staging (camera_direction / frame_spatial_contract). The camera MUST reproduce the staged screen zones: a figure whose staging puts it on the left of the frame (screen_zone *_left) must fall on the LEFT of the frame as seen from your camera position and look_at, and likewise for the right.
- The same physical place must keep the same coordinates across shots — figures and cameras move, the place does not.
- A figure who is DEPARTING (their moving_toward leads away from the other figures) must already be plotted FARTHER from each camera than the stationary figures are — the stills must read them as receding into the scene, never as a large foreground subject, even if the staging text frames the camera near them.
- Make distances meaningful: a figure described as far away must be plotted far from the camera; a figure right next to something must be plotted next to it.u  You revise T2I still-image prompts for ONE shot so that figure placement matches a SPATIAL SUMMARY computed from the production's site-layout coordinates. The summary is the source of truth for figure depth, relative size and camera distance.
Rewrite ONLY the placement phrases of figures the summary marks as FARTHER than the nearest figure or as moving. Rewrite those minimally so each such figure reads as ALREADY at its summary position: already partway along the path the geography states, deeper in the frame, visibly smaller than the nearer figure, with the movement direction written out in the geography's terms (which frame side their path leads to, what it follows, what it moves away from, and what it does NOT head into). You may also insert ONE short scene-geography sentence taken from the summary (how the main path runs across the frame and where the key landmarks sit), placed before the figures are described. Everything else — the nearest figure's existing phrases, camera position and angle, framing, lens feel, lighting, mood, environment description, character appearance, gaze and actions — stays AS-IS.
Hard rules:
- The figure the summary marks as NEAREST keeps its size, depth, prominence, gaze and action wording exactly as the original — do not shrink it, push it deeper, or add foreground/size wording to it (the ONLY change ever allowed on the nearest figure is the left/right lateral correction described below, which never alters its size or depth). Never describe any figure as small/tiny/deep unless the summary marks that figure as farther or moving, and never add wording like 'in the foreground', 'nearest to the camera' or 'larger' to any figure — relative size is expressed only by making the farther figure smaller and deeper.
- Express distance as a position already reached, never as motion relative to the camera: do not write 'toward the camera' or 'away from the camera'. Keep the movement's destination/path exactly as the original prompt states it (the same road, doorway, etc.).
- This is a single STILL frame: never use camera-movement language (tracking, dolly, panning, zooming, following...).
- LATERAL placement: each figure must occupy the frame side (left / center / right) that the summary gives for THAT figure. If the original wording places a figure on a different side — including an orientation or facing phrase (which way the figure is turned, e.g. a three-quarter angle) that could be misread as a screen position — correct that figure's stated screen side to match the summary, while preserving its facing/orientation, gaze and every other aspect of its wording. An orientation/angle phrase describes how a figure is turned, NOT which side of the frame it stands on; the frame side comes only from the summary. This left/right correction also applies to the nearest figure (it changes only the side of the frame, never its size, depth or prominence).
- Do NOT add new characters, events or locations. Do NOT add any person who is not already present in the original prompt — not by entity ID, not by name, not by plain-text description.
- Do NOT introduce or remove entity ID tokens (like C01, P02, L03): the exact set of ID tokens in each revised prompt must equal the original's.
- If an original prompt already matches the summary, return it unchanged with a no_edit_reason.
- Revise EVERY prompt given, keeping its variation_index, and list each changed span in edited_spans.c           	        g }| D ]  \  }}|j                  d| d| d        |D ]  }|j                  d|j                  d       d|j                  d              |j                  d      r |j                  d	t        |d         z          |j                  d
      ,|j                  dt        j                  |d
   d      z          |j                  d        |r|j                  d|z          dj                  |      S )u  scene_texts: [(scene_index, 원문 전체)] — 자르지 않는다.
    member_shots: [{scene_index, shot_index, description, staging}].
    constraint_feedback: retry 시 이전 시도의 staged-side 충돌 목록 (deterministic
    검증 결과 — caller 가 조립).zSCENE z TEXT (full):

zSHOT scene scene_indexz / shot_index r,   r   zdescription: stagingz	staging: F)ensure_ascii u   PREVIOUS ATTEMPT VIOLATED STAGED SCREEN ZONES — fix the camera positions/orientations (or figure positions, staying consistent with the scene text) so each staged side is reproduced:
)appendgetr   jsondumpsjoin)scene_textsmember_shotsconstraint_feedbackpartssitextshots          g/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/outdoor_site_layout_provider.pybuild_site_layout_user_promptrS   )  s    EDvbTb9:  $((=12.,AW@XY	
 88M"LL3tM/B+CCD88I*LLtzz$y/PU'VVWR  ?ATU	

 99U    c                    dg}| D ]  \  }}|j                  d| d|         |j                  d|z          dj                  |      S )NzORIGINAL T2I PROMPTS:z[variation_index z]
rA   )rF   rJ   )
variationsspatial_summaryrN   idxprompts        rR   build_rewrite_user_promptrZ   G  sT     %%E!V(S9: "	LL'(99UrT   u  You are a storyboard supervisor composing ONE film frame. From the scene action and the spatial geography below, write a SHORT shot-composition brief for a rough storyboard sketch. Cover TWO things:
(1) PEOPLE — for each person: screen position (left/center/right), depth (foreground / midground / background), relative size, and how their head and body are oriented relative to the camera. DERIVE each orientation only from the geometry of whom they look at or move toward and where that target sits (a person attending to a target deeper in the frame turns away from the lens; a person attending to something toward the camera faces the lens).
(2) ENVIRONMENT — list the salient built/natural FEATURES that are visible in this shot (the kind named in the geography), each with its screen placement and depth, so the sketch can actually include them (do not omit a structure the camera would see).
Refer to people only by role (e.g. the one staying, the one leaving). No colors, no mood, no proper names — layout, elements and facing only. At most 5 sentences.

SCENE ACTION:
{body}

SPATIAL GEOGRAPHY (source of truth for positions, depth, movement and which features exist):
{geo}u  Draw every PERSON as a posed artist's wooden MANNEQUIN — a smooth featureless articulated mannequin (clear ball joints at shoulders, elbows, hips and knees; no clothing; no face) so the body POSE, stance and limb positions are completely unambiguous. Construct each mannequin's head with the LOOMIS METHOD — a sphere with the side plane sliced flat, a vertical centerline and a horizontal brow line wrapping it — so the head's facing direction is explicit. Keep the surrounding ENVIRONMENT as clean thin line-art (not mannequin).u!  A rough black-and-white STORYBOARD SKETCH of a single cinematic film frame (eye-level). Composition and ELEMENTS only — include EVERY feature and person described below at its stated screen position, depth and relative size; do not omit any structure. No color, no lettering, no labels. z\
The ground/path recedes into the distance with clear perspective.

FRAME TO SKETCH:
{brief})log_contextc                   ddl m}  ||      }|r |j                  di | |j                  t        j                  | |      d      }t        |t              r|S t        |      S )uZ   ① scene action + SCENE GEOGRAPHY → 요소포함 구도 브리프 텍스트 (LLM 1콜).r   GeminiTextClientmodelbodygeo皙?)temperature )"app.modules.llm.gemini_text_clientr^   set_contextsendCOMPOSITION_BRIEF_INSTRUCTIONformat
isinstancer   scene_actionrW   r`   r[   r^   clientouts          rR   generate_composition_briefrq     sk     DE*F)[)
++%,,,O,T  C S#&34CH4rT   	1536x1024)sizec               N    ddl m}  |t        j                  |       |||      S )uM   ② 구도 브리프 → 마네킹+Loomis 스케치 PNG (gpt-image T2I 1콜).r   generate_floor_plan_image)brief)rY   openai_clientr`   rs   )(app.modules.pipeline.location_floor_planrv   COMPOSITION_SKETCH_PROMPTrk   )rw   rx   r`   rs   rv   s        rR   generate_composition_sketchr{     s/     S$(//e/<#	 rT   r4   zjgeneric role label for this person in the shot, e.g. 'the seated one', 'the one leaving'. NO proper names.)standingsitting	crouchingkneelinglyingleaning	squattingwalkingrunningbendingunknownzwhat the arms/hands/body are doing (reaching, holding, pushing, pointing, covering the face, arms crossed, bracing, hands at sides). Generic action words only, NO specific object names. 'none' if nothing stated.zowhich way the head and torso face relative to the camera, derived from whom/what they attend to or move toward.zYwhat the body rests on or touches, as a GENERIC class: ground, seat, wall, railing, none.zgeneric role of what they interact with: 'another figure', 'a nearby object', 'a surface', 'a held object', or 'none'. NO specific prop or person names.zBshort verbatim words from the scene action that justify this pose.)lowmediumhigh)slotbody_posturelimb_actionhead_body_orientationcontact_or_supportinteraction_target_roleevidence_quote
confidencePOSE_BRIEF_SCHEMAu  You extract ONLY the BODY POSE and ACTION of each person in ONE film frame, so an artist can draw them as posed mannequins. Work solely from the scene action and the spatial geography given. For EACH person actually present in this shot, report their body_posture (standing / sitting / crouching / kneeling / lying / leaning / squatting / walking / running / bending — pick what the action implies; use 'unknown' ONLY if the text gives no posture cue at all), limb_action (what the arms, hands and body are doing), head_body_orientation (which way the head and torso face relative to the camera, derived from whom or what they attend to or move toward — a person attending to a target deeper in the frame turns away from the lens; toward the camera faces the lens), contact_or_support (what the body rests on or touches, as a GENERIC class) and interaction_target_role (the GENERIC role of what they interact with).
Refer to people ONLY by a generic role slot. Extract POSE and ACTION only — NEVER output clothing, colours, facial features, mood or emotion words, proper names, specific object or place names, camera or lens talk, or environment description. Put the short verbatim words that justify each pose in evidence_quote and set confidence honestly. If the scene action does not describe a person's posture, set body_posture='unknown' and confidence='low'. Report only people actually in THIS shot; if none, return an empty figures list.u   SCENE ACTION (verbatim — do not skip anyone or anything):
{body}

SPATIAL GEOGRAPHY (who is where / who moves toward what — use ONLY to derive each figure's orientation):
{geo}c                   ddl m}  ||      }|r |j                  d	i | |j                  t        j                  | |      t        dt        d      }t        |t              r|S dg iS )
u/  P1(2026-07-01) — scene action(무절단) + geography → 인물별 자세/동작/배향 structured
    brief (gemini text, evidence-backed). shared-model v2 Stage C 가 포즈를 드롭해 generic
    서있는 마네킹으로 최종을 오염하던 회귀 복구: camera_brief(좌표/프레이밍)와 병행해 pose
    SOT 를 운반한다. legacy generate_composition_brief(orientation/framing only, OFF 경로)와
    는 별개·미변경. 반환 = POSE_BRIEF_SCHEMA dict({figures:[...]}); 렌더/fail-closed 판정은
    step(render_pose_brief_text).r   r]   r_   ra   figure_pose_briefrd   response_schemaschema_namesystem_instructionre   r4   rf   )
rg   r^   rh   ri   POSE_BRIEF_INSTRUCTIONrk   r   POSE_BRIEF_SYSTEMrl   dictrm   s          rR   generate_pose_briefr     st     DE*F)[)
++%%<_%M)',  C S$'3<i_<rT   c                t    dd l }|j                  | xs dj                  d            j                         d d S )Nr   rE   zutf-8   )hashlibsha256encode	hexdigest)rP   r   s     rR   _sha16r   F  s3    >>4:2--g67AACCRHHrT   c                V    dd l }|j                  | xs d      j                         d d S )Nr   rT   r   )r   r   r   )datar   s     rR   
_png_sha16r   K  s(    >>$+#&0023B77rT   floor_plan_image)capture_rolecapture_extra_metadatac               *    ddl m}  || |||||      S )ux   ref 없는 T2I (Stage A base). capture_role/extra_metadata 는 Phase C scope 배선용
    (default 면 byte-identical).r   ru   )rY   rx   r`   rs   r   r   )ry   rv   )rY   rx   r`   rs   r   r   rv   s          rR   
_t2i_imager   P  s&     S$]%d!:PR RrT   c          	        ddl }ddlm} ddlm}	 |j                  dd      }
	 |
j                  |       |
j                          |
j                           |	| | ||
j                        g||||      	  ||
j                        j                          S # t        $ r Y S w xY w# 	  ||
j                        j                          w # t        $ r Y w w xY wxY w)	u_   ref 1장 I2I edit (Stage B/C) — ref_png 를 임시파일로 쓰고 generate_floor_plan_image.r   N)Pathru   z.pngF)suffixdelete)rY   rx   	ref_pathsr`   rs   r   r   )tempfilepathlibr   ry   rv   NamedTemporaryFilewriteflushclosenameunlinkOSError)rY   ref_pngrx   r`   rs   r   r   r   r   rv   tmps              rR   _edit_image_with_refr   ^  s     R

%
%VE
%
BC		'				(CHH~&e$%>TV
	N!!# 			N!!# 		sB   AB& 6 B	B#"B#&C( C	C		CCCC	1024x1024)rs   r   building_fp_pngc          	         ddl m}m}  ||       }i |xs i ddd}	|r||z   }t        |||||d|	      }
nt	        ||||d|	      }
|t        |      t        |
      t        t        |      d}|
|fS )	u  Stage A — location 공통 빈 항공뷰 base PNG (T2I, 환경=원형 숫자, 엔티티/카메라
    제외). step 이 group 단위로 1회 생성/캐싱해 모든 샷에 공유한다(set 일관성).
    실패는 raise (호출자가 no-guide + diagnostic).

    Phase C: ``capture_extra_metadata``(group_id/location_id)가 주어지면 producer_stage/
    parent_stage 를 덧붙여 capture(scope 미배선이면 no-op, default None=byte-identical).

    W-G: ``building_fp_png``(같은 building 그룹 indoor floor plan PNG)가 주어지면
    T2I→I2I(fp ref) 승격 + 정합 지시(BUILDING_FP_AERIAL_GUIDANCE) — 건물 footprint/
    개구부가 fp 와 정합된 base 를 만들고 blocking/sketch 체인이 그대로 상속한다.
    None(default) = 기존 T2I 경로 byte-identical.r   )BUILDING_FP_AERIAL_GUIDANCEbuild_aerial_base_promptaerial_baseN)producer_stageparent_stageoutdoor_aerial_baserx   r`   rs   r   r   )base_promptbase_prompt_hashbase_png_hashbase_prompt_versionbuilding_fp_used)	-app.modules.pipeline.outdoor_site_layout_planr   r   r   r   r   r   r   bool)layoutrx   r`   rs   r   r   r   r   rY   cap_metapngmetas               rR   generate_location_aerial_baser   y  s     
 &f-FG)/R G"/GH55"O=$9#+-
 -u4.xQ "6N#C9 1D 9rT   )rs   r   c          	        ddl m} t        fd|j                  d      xs g D        d      }|t	        d        |||      }	i |xs i ddd	}
t        |	| |||d
|
      }||	t        |	      t        |      dfS )u*  Stage B — 공통 base 위 I2I 블로킹: 이 샷의 엔티티=원형 글자 + 카메라 1개 주입,
    base set 보존. 실패는 raise. 조건부 호출(단순 구도는 step 이 생략하고 base→C 직행).

    Phase C: parent_stage=aerial_base (base 위 I2I). default None=byte-identical.r   )build_shot_blocking_promptc              3  L   K   | ]  }|j                  d       k(  s|  ywr,   NrG   .0cr,   s     rR   	<genexpr>z'inject_shot_blocking.<locals>.<genexpr>  $     U/q1553F*3T/   $$r5   Nz/inject_shot_blocking: no camera for shot_index=shot_blockingr   r   r   r,   outdoor_shot_blockingr   )blocking_promptblocking_prompt_hashblocking_png_hash)r   r   nextrG   RuntimeErrorr   r   r   )base_pngr   r,   rx   r`   rs   r   r   camerarY   r   r   s     `         rR   inject_shot_blockingr     s     YUFJJy)/R/UF ~=j\JL 	L'
CF*)/R *"1=(*H U,XOC ! &v'_  rT   u  The attached image is a flat TOP-DOWN SET MAP (a bird's-eye plan) of one single outdoor location, drawn only as plain coloured shapes, a small green camera wedge and red figure dots seen from straight above. It has NO words on it. Use it ONLY as a spatial reference for WHERE the structures, ground areas and figures are and how they are laid out relative to the camera. Do NOT keep the top-down look and do NOT copy the flat map shapes.

Produce ONE rough black-and-white STORYBOARD control sketch: the EYE-LEVEL PERSPECTIVE camera view from the marked camera, drawn as clean thin pencil/ink line-art outlines (an animation layout sheet — NOT a photograph, NOT 3D, no shading, no textures, white paper). Draw any people as featureless wooden artist mannequins (ball joints, no face, no hair, no clothing) that only mark position, depth and rough scale.

This sketch fixes LAYOUT and GEOMETRY ONLY: copy the spatial layout, the depth ordering, the figures' placement and the rough outer ENVELOPE/footprint of the major structures and ground areas. Do NOT decide or draw any material, surface texture, roof or structural style, ornament or architectural design — leave every structure as a plain blank outline. ABSOLUTELY NO text, letters, numbers, labels, arrows or signatures anywhere in the image.c                (    t         dz   | xs dz   dz   S )u   LEGACY (v2 미사용, 삭제 금지) — PIL clean birdseye edit 용 카메라뷰 프롬프트.
    v2 는 plan.build_blocking_sketch_prompt (라벨 마커 블로킹 ref) 를 쓴다.u   

Camera-view brief (computed from the map coordinates — use it to place the structures and figures by depth and screen position):

rE   u   

Reminder: plain blank structure outlines only — no material, style or design; figures are featureless mannequin markers; no text or labels anywhere in the image.)SHARED_MODEL_CAMVIEW_SYSTEM)camera_briefs    rR   build_shared_model_guide_promptr     s3     	$ 'D 	D 2	"^	^rT   )n   r   r      )scalec          	       &' ddl m} ddlm}m} | j                  d      xs ddg}t        |d         t        |d         c&}d't        |&z
  z  'd	z  z         }|j                  d
||fd      }|j                  |d      }	d/&'fd}
| j                  d      xs g D ]  }|j                  d      xs g D cg c]
  } |
|       }}t        |j                  d      xs t              }|j                  d      }|dk(  r#t        |      d	k\  r|	j                  ||d       |dk(  r?t        |      dk\  r1|	j                  |t        |j                  d      xs d      |       |s|d   \  }}|	j                  |dz
  |dz
  |dz   |dz   g|d	        t        | j                  d      xs d      }| j                  d      xs g D ]0  } |
|d         \  }} |
|d          \  }}t!        j"                  ||z
  ||z
        }d!z  }t!        j$                  d      }||t!        j&                  ||z
        z  z   ||t!        j(                  ||z
        z  z   f}||t!        j&                  ||z         z  z   ||t!        j(                  ||z         z  z   f}|	j                  ||f||g|d   |d   |d	   d"f|       |	j                  ||f||fg|d	       |	j                  |d#z
  |d#z
  |d#z   |d#z   g|$       3 t        | j                  d%      xs d&      }| j                  d'      xs g D ]l  } |
|d         \  }} |	j                  |d#z
  | d#z
  |d#z   | d#z   g|$       |j                  d(      }!|!sH |
|!      \  }"}#|	j                  || f|"|#fg|d	       n  |       }$|j+                  |$d)*       |$j-                         }%t/        |%d+d,d-i.       |%S c c}w )0u  LEGACY (v2 미사용, 삭제 금지) — birdseye spec → top-down set-map PNG (PIL, 결정론).
    literal 좌표 렌더라 좌표 품질을 그대로 노출(셸터가 도로 위)해 v2 에서 T2I 라벨
    마커 항공뷰(generate_location_aerial_base)로 교체됨. 회귀/다른 소비 참조용 보존.

    모델 입력 전용 — **글자/라벨/숫자 0** (색 도형 + 카메라 wedge + figure dot 만).
    spec 이 색/도형/좌표만 담으므로 이 렌더는 텍스트를 그리지 않는다(by construction).r   )BytesIO)Image	ImageDrawcoord_rangeg        g      Y@      r   RGB)   r      RGBAc                `    t        | d         z
  z  z   t        | d         z
  z  z   fS )Nr   r   )float)plopadr   s    rR   _xyz'_render_clean_birdseye_png.<locals>._xy  s<    uQqT{R'500#qtr9IU8R2RSSrT   r3   r&   edgeshaper      )fillwidthpolygon   r  )   r  r  Z   )r  outline   )r
  r  camera_color)(   x   r  r5   r-   r2      7   r   )r  figure_color)r  <   r  r4   r.   PNG)rk   outdoor_birdseye_setmaplegacyT)rolepipeline_metadata)r   r   returnzTuple[float, float])ior   PILr   r   rG   r   r   newDrawtuple_BIRDSEYE_EDGE_FALLBACKlenr   r  ellipsemathatan2radianscossinsavegetvaluer	   )(specr   r   r   r   rnghisideimdrr   lmr   ptsr  r  xy	cam_colorcampxpylxlyanglengthhalfab	fig_colorfgfxfymtmxmybufr   r   r   s(    `                                    @@rR   _render_clean_birdseye_pngrE    s    $
((=
!
1c5\C3q6]E#a&MFB
CR5 37*+D	54,	8B	F	#BT hh{#)r)!vvh/52565!s1v56RVVF^>'>?wF?s3x1}GGCd!G,iCHMJJsrvvf~'L9L!M#  %q6DAqJJAq1ua!eQU3TJK * dhh~.?-@Ixx	"(b(SZBS^$Bjjb"r'*e||B&488C$J///ftxxd
?S6S1ST&488C$J///ftxxd
?S6S1ST


RHa#"1y|Yq\2FPY 	 	[
"bB8$9A>


BFBFBFBF3)
D ) dhh~.?-@Ihhy!'R'RYB


BFBFBFBF3)
DVVO$WFBGGb"XBx(yGB ( )CGGCG
,,.C +$?O JW 7s   =O#)rs   r   
pose_briefc          	     ~   ddl m}	m}
 t        fd| j	                  d      xs g D        d      }|t        d        |
||       }|st        d       |st        d      |t        |      t        |xr t        |      j                               |rt        |      ndt        d	}|r,t        || |||
      \  }}|}d|d<   |d   |d<   |d   |d<   n|}d|d<   d|d<   d|d<    |	||      }i |xs i d|rdndd}t        |||||d|      }||d<   t        |      |d<   t        |      |d<   ||fS )uW  Stage C — group 공통 base(Stage A 결과, step 이 캐싱해 전달) 에서 이 anchor 샷의
    카메라뷰 구도 가이드 PNG + meta 를 파생한다 (v2 라벨 마커 항공뷰 파이프라인).

    use_blocking=True  → Stage B(inject_shot_blocking)로 base 위에 엔티티 글자 + 카메라를
                         주입한 블로킹을 ref 로 C (다수 figure/깊이 분리 등 복잡 구도).
    use_blocking=False → base 를 직접 ref 로 C (단순 구도 — figure/카메라뷰는 좌표 산술
                         brief 가 운반; 검증상 brief-only 도 단순 구도엔 충분).
    어느 경로든 최종 스케치는 eye-level 라인아트 + 좌표 산술 brief, 디자인 발명 0,
    diagram 마커(숫자/글자/카메라 아이콘) 누출 금지(프롬프트 가드 + canary 평가항목).
    실패는 raise — 호출자(step)가 no-guide + diagnostic (old sketch fallback 금지).

    반환: (png, {camera_brief, camera_brief_hash, blocking_stage, blocking_prompt_hash,
    blocking_png_hash, sketch_prompt_hash, camera_view_sketch_hash, helper_version}).r   )build_blocking_sketch_promptcompute_camera_briefc              3  L   K   | ]  }|j                  d       k(  s|  ywr   r   r   s     rR   r   z5generate_shared_model_camera_guide.<locals>.<genexpr>O  r   r   r5   Nz4shared_model_camera_guide: no camera for shot_index=z@shared_model_camera_guide: camera coords missing for shot_index=u?   shared_model_camera_guide: base_png missing (Stage A 미제공))r   camera_brief_hashpose_brief_presentpose_brief_hashhelper_version)rx   r`   r   blockingblocking_stager   r   skipped_simplecamera_sketchr   r   r   outdoor_camera_sketchr   sketch_promptsketch_prompt_hashcamera_view_sketch_hash)r   rH  rI  r   rG   r   r   r   r   stripr   r   r   r   )r   r,   r   use_blockingrx   r`   rs   r   rF  rH  rI  r   rw   r   blocking_pngblk_metar   rT  sketch_cap_metar   s    `                  rR   "generate_shared_model_camera_guider\  0  s   2
 UFJJy)/R/UF ~B:,OQ 	Q 0E$'( 	( MO 	O #E] #:#I#j/2G2G2IJ1;6*-4D !5fjU#9";h !+'/0F'G#$$,-@$A !!1'+#$$( !0
CM106B 1)8:FM%/1O wm5t,_VC *D!'!6D	&0oD	"#9rT   booleanzBa short verbatim quote from the shot text that decides the verdict)is_open_exterior_departurer   	reasoningr   COMPOSITION_GUIDE_JUDGE_SCHEMAu  You gate an optional departing-figure composition guide for a single film shot. The guide should be drawn ONLY for an OPEN-EXTERIOR shot whose frame has real depth receding away from the camera and where a figure moves off into that unobstructed distance. It must NOT be drawn when the shot is framed or bounded by a structural opening or surface (seen through a window or glass pane, square to a doorway or threshold), set inside or looking into an enclosed interior, or staged as a confined close-quarters confrontation where the figures are clustered at one depth. Decide ONLY from this shot's own camera description, action and geography below — do not assume anything not stated. Set is_open_exterior_departure true only when the text clearly shows the open-exterior receding case, and quote the deciding words in evidence_quote. When the framing is unclear or the text is ambiguous, answer false with low confidence. Judge by these generic spatial classes only — never by what the place or the objects are called.c                    g }|r|j                  dt        |      z          | r|j                  dt        |       z          |r|j                  t        |             dj                  |      S )NzCAMERA DESCRIPTION (staging):
zSHOT ACTION:
z

)rF   r   rJ   )rn   camera_directionrW   rN   s       rR   $build_composition_guide_judge_promptrc    sa    
 E6=M9NNO%L(99:S)*;;urT   c                   ddl m}  ||      }|r |j                  di | |j                  t	        | ||      t
        dt        d      }t        |t              r|S i S )uQ   샷레벨 적용성 판정 (gemini text, structured). 반환 = JUDGE_SCHEMA dict.r   r]   r_   composition_guide_applicability皙?r   rf   )	rg   r^   rh   ri   rc  r`  COMPOSITION_GUIDE_JUDGE_SYSTEMrl   r   )rn   rb  rW   r`   r[   r^   ro   rp   s           rR   "judge_composition_guide_applicablerh    sm     DE*F)[)
++,*O	=659  C S$'3/R/rT   )cross_shot_continuitysingle_shot_complexitybothno_guidezOverbatim words from that shot's signals/staging that support the route decision)shot_keysource_fieldquote)multi_angle_same_setshared_structure_continuityfigure_placement_continuitydepth_layeringocclusion_or_framing_riskcamera_or_framing_riskinsufficient_evidence)groupsinglenoneu   shot_key list whose staging/directives frame the subject through a reflective or transparent surface (reflection, mirror, through-glass) — those shots must not receive a mannequin sketch; [] when none)needs_shared_model_guidedecision_typer   evidencereasonsguide_scopeoptical_risk_shot_keys
risk_notesSHARED_MODEL_GUIDE_JUDGE_SCHEMAu  You ROUTE an optional shared-model composition guide for a GROUP of film shots the production has placed in the SAME outdoor location. The guide is one shared top-down set map turned into per-shot eye-level layout sketches; its ONLY purpose is to keep the SAME set (structures, ground, water, paths) and the figures' placement CONSISTENT across the group's shots. You decide ROUTING ONLY — whether such a guide is warranted and at what scope. You do NOT decide any material, surface, roof, architectural style, ornament or visual design, and you do NOT describe how anything should look.
Set needs_shared_model_guide=true with decision_type=cross_shot_continuity when the group shows the same location across two or more shots — especially from different camera angles or positions — so an unguided render would risk drawing a different version of the same structures or moving the figures between shots. Set needs_shared_model_guide=true with decision_type=single_shot_complexity when a SINGLE shot's composition is complex enough on its own that an unguided render would likely get the framing or figure placement wrong — for example several figures to arrange, figures spread across distinct depth layers, or a figure moving through the space — so a one-shot layout sketch would meaningfully help. Even with a SINGLE figure on a SINGLE depth plane, the camera or framing directive ITSELF can justify a single_shot_complexity guide when that directive creates spatial ambiguity or a high placement risk — for instance an extreme viewpoint (steep overhead or very low angle), a strong wall/floor/ceiling relationship the body must sit correctly within, foreshortening, or occlusion — because an unguided render often mis-places the body or collapses the intended depth. When you route on that basis, cite the directive in evidence (source_field=camera_direction or the relevant raw director field) and include camera_or_framing_risk in reasons. Use both when both hold, and no_guide when the layout signals are weak and a single render would be fine unaided. Set guide_scope=group for a cross-shot guide, single for a single-shot guide, none otherwise.
A shot with NO figures can still warrant a single_shot_complexity guide when its camera or framing directive alone creates spatial ambiguity — an approach path, threshold or structure whose position and scale must read correctly (tight architectural framing, an entrance seen head-on, an extreme viewpoint); route on the directive evidence exactly as above.
Separately, ALWAYS fill optical_risk_shot_keys: list the shot_key of every shot whose staging or directives present the subject through a REFLECTIVE or TRANSPARENT surface — a reflection in glass, a mirror image, a subject visible only as a reflection, or similar through-surface optics. A mannequin layout sketch routinely confuses the subject's true position with its reflected or transmitted image, so those shots are excluded from sketches even when the group is otherwise admitted; cite the deciding words in evidence. Use an empty array when none apply.
Judge ONLY from the structured group signals and each shot's staging/geography below; never decide by what the place or objects are CALLED — use the generic spatial classes in reasons only. Put the deciding words in evidence with their shot_key and source_field. If evidence is empty or the case is unclear, answer needs_shared_model_guide=false with confidence=low.c                2    t        j                  | dd      S )u   group route judge 입력 — 구조화 group meta + per-shot signals/staging/geography
    (시나리오 명사는 데이터 label 로만 흘러들 뿐, 템플릿은 generic).Fr   )rD   indent)rH   rI   )group_payloads    rR   %build_shared_model_guide_judge_promptr  L  s     ::m%BBrT   c                   ddl m}  ||      }|r |j                  di | |j                  t	        |       t
        dt        d      }t        |t              r|S i S )u   group-level shared-model 가이드 route 판정 (gemini text, structured). route only —
    반환 = SHARED_MODEL_GUIDE_JUDGE_SCHEMA dict (가드/attach 판정은 step).r   r]   r_   shared_model_guide_routerf  r   rf   )	rg   r^   rh   ri   r  r  SHARED_MODEL_GUIDE_JUDGE_SYSTEMrl   r   )r  r`   r[   r^   ro   rp   s         rR   judge_shared_model_guider  R  se     DE*F)[)
++-m<7.:  C S$'3/R/rT   )rM   c          
     d    ddl m}  |t        t        t	        | ||      t
        |d|t              S )Nr   call_structuredsite_layoutstepsystem_promptuser_promptr   project_configr   opik_metadata
max_tokens)app.modules.llm.llm_clientr  _STEPSITE_LAYOUT_SYSTEMrS   r7   r   )rK   rL   r  r  rM   r  s         rR   emit_site_layoutr  l  s9     ;(1':<*%!#$
 
rT   c           
     n   ddl m}  |t        t        t	        | |      t
        |d|t              }i }|j                  dg       D ]m  }t        |t              st        |j                  d      t              s4|d   |j                  d      xs g |j                  d      d	|t        |d
         <   o |S )zB{variation_index: {revised_prompt, edited_spans, no_edit_reason}}.r   r  revised_position_promptsr  r8   r<   r=   r>   )r<   r=   r>   r;   )r  r  r  REWRITE_POSITION_SYSTEMrZ   r?   r   rG   rl   r   r   r   )rV   rW   r  r  r  resultrp   rs           rR   rewrite_position_phrasesr    s     ;--j/J-%.#%	F &(CZZ)2.a:aee4D.Es#K"#$4"5 !n 5 ;"#%%(8"9.CA'()* / JrT   )N)rK   List[Tuple[int, str]]rL   List[Dict[str, Any]]rM   Optional[str]r  r   )rV   r  rW   r   r  r   )
rn   r   rW   r   r`   r   r[   Optional[Dict[str, Any]]r  r   )
rw   r   rx   r   r`   r   rs   r   r  bytes)
rn   r   rW   r   r`   r   r[   r  r  r6   )rP   r   r  r   )r   r  r  r   )rY   r   rx   r   r`   r   rs   r   r   r   r   r  r  r  )rY   r   r   r  rx   r   r`   r   rs   r   r   r   r   r  r  r  )r   r6   rx   r   r`   r   rs   r   r   r  r   zOptional[bytes]r  Tuple[bytes, Dict[str, Any]])r   r  r   r6   r,   r   rx   r   r`   r   rs   r   r   r  r  r  )r   r   r  r   )r(  r6   r   r   r  r  )r   r6   r,   r   r   r  rX  r   rx   r   r`   r   rs   r   r   r  rF  r  r  r  )rn   r  rb  r  rW   r   r  r   )rn   r  rb  r  rW   r   r`   r   r[   r  r  r6   )r  r6   r  r   )r  r6   r`   r   r[   r  r  r6   )NN)rK   r  rL   r  r  Optional[Dict]r  r  rM   r  r  r6   )
rV   r  rW   r   r  r  r  r  r  zDict[int, Dict[str, Any]])=__doc__
__future__r   rH   loggingr!  typingr   r   r   r   r   app.services.image_capture.sinkr	   	getLogger__name__loggerr   __annotations__r   r   r   r   r  _POINT_SCHEMAr7   r?   r  r  rS   rZ   rj   _SKETCH_TECHrz   rq   r{   r   r   r   r   r   r   r   r   r   r   r   r   r  rE  r\  r`  rg  rc  rh  r  r  r  r  r  r  rf   rT   rR   <module>r     s  : #    3 3 <			8	$0 # 0& & #H C G  3  C 
 h,   !8, ((X
 &.7PQ '!.'Y #0&&1A!B(N%. N(-5
@  "((!3 ('\
 "*6 2(g% !($,/5y.A'4.;ff=M-N4Q2"	+ )O49""< P(-C"%
N  #)9"5(,
 =(-	
MSh 4!oX& N Xv  (.	':'-x&8 '$,-3X,>,2H+=+ *4Y(?49"(V% "*6 2(a'#. ).9 
"F ##!M') ~ 'XT P0 v *.&& ' 		<% 	. .: > AMMP    -1555 	5
 *5 	52   	
  6   !)(0 !)!% !)(9$ !)(6. !)(S+ !)(00 !)(<'
 ,4=V"W]/`
 ).o8;
=| !CB% > BJ3 .$  -1=== 	=
 *= =^I
8 +7;RR#&R/2R:=RR 5R 	R  +7;36?BJM 5 	8 LW7;'+)).1):=)EH)4) %) "	)\ 1<7;+9<"*- 5 "	D2 ,
 *  FG AX 7; $QQQ 	Q
 Q Q Q Q 5Q Q "Qx '-y&9'1JKh')
		 "2  &9 $#  		* -100#0 0
 0 *0 0F %+Y$7
  (1JK !' 2%+X$6 ((I B(-
$  
 !)2MNh'T	#
 x(_0b "o83  8v-:  dC -1	0!0 0 *	0
 0: &*$(	 *.&& # "	 ' 4 &*$(	% # "	
 rT   