
    Ej.                    x    d Z ddlmZ ddlZddlmZmZmZmZm	Z	 ddl
mZ dddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d	dZy)
u  G3.2 scene_detail owned-objects judge wrapper.

post-parse 단계가 호출하는 1-call (no retry — round 4 Q1=B / round 5 M1) judge.

Routing:
    `_PIPELINE_STEP_EXTENSIONS["scene_detail_owned_judge"]` 에 등록된 model
    (gpt-mini) 로 `call_structured_fn(step="scene_detail_owned_judge", ...)`
    호출. `default_model` 키는 `call_structured()` 인자가 아니다 (round 3 #1).

Contract:
    - owned 가 빈 list → LLM 호출 자체 skip → `[]` 반환 (불필요한 비용 절감).
    - owned 가 1+ entries → judge 1회 호출 → `violations` list 반환.
    - violations 발견 시 retry 하지 않음 — caller 가 `contract_violation`
      status 로 marking 한다.
    - C2 v1 (W3): judge v4 prompt-pack 은 redraw verb whitelist 대신 producer
      가 declare 한 `owned_object_usage` echo 를 t2i_prompt 와 cross-check 한다.
      `run_owned_judge` 가 해당 echo 를 judge prompt 로 전달한다.
    - LLM 응답에 ``violations`` 키가 없거나 list 가 아니면 schema contract 위반
      → AppError(step.contract_violation) raise (Wave 6 BLOCKING fix —
      silent ``[]`` fallback 금지 / feedback_no_silent_fallback.md 준수).

이 모듈은 helper (`_owned_helpers.py`) 와 분리 — helper 는 hash/sentinel 등
순수 함수만 담고, judge 는 LLM 호출 의존성 (prompt_loader / call_structured)
을 가진다. 분리 이유: helper test 가 LLM 호출 mock 없이 가능해야 함.
    )annotationsN)AnyCallableDictListOptional)AppError )project_configopik_metadatashot_intentc           	     `   |sg S ddl m}m}	  |dd      }
 |	dd      } |dd      }dj                  d |D              }t	        j
                  |d	d
d      }|j                  d| xs d      j                  d|      j                  d|      j                  d|xs d      j                  d|xs d      } |d|
|||d|      }t        |t              rd|vrt        dd|      |d   }t        |t              s&t        ddt        |      j                   d|      t        |      S )u  post-parse 가 호출하는 judge wrapper.

    `call_structured_fn(step="scene_detail_owned_judge", ...)` 만 호출 — model
    라우팅은 `_PIPELINE_STEP_EXTENSIONS` 가 처리. `default_model` 은
    `call_structured()` 인자가 아님 (round 3 #1 / round 4 MINOR 1).

    owned 가 빈 list 면 LLM 호출 skip → 빈 violations.

    1-call only (round 4 Q1=B / round 5 M1): violations 가 비어있지 않아도 retry
    하지 않는다. caller 가 sentinel 안에 violations 보존 + status =
    contract_violation 로 marking.

    Args:
        t2i_prompt: scene_detail 이 작성한 영어 또는 source-language T2I 프롬프트.
        owned: English canonical common nouns (round 4 Q2=B / round 5 BLOCKING 1
            — `_owned_helpers.normalize_owned_list` 가 ASCII 만 통과시켜
            영어 canonical 강제).
        owned_object_usage: C2 v1 (W3) — producer (scene_detail v30) 가 본
            variation 에 declare 한 per-token echo. 각 entry =
            `{owned_token, usage_kind, source_phrase}`. judge v4 prompt 가
            t2i_prompt 와 cross-check 하도록 그대로 전달한다 (coverage validation
            은 caller 의 `build_owned_sentinel` 책임 — 여기서 중복 검증하지 않음).
        camera_direction: shot 의 자연어 카메라 정보.
        shot_intent: judge v5 (2026-07-02) — shot 의 representative moment
            텍스트. narrow exception (1) allowed_visual_state_change 의
            유일한 증거 소스 (shot-intent 가 owned 객체의 시각 상태/내용
            변형을 명시 요구하는 경우만 위반 아님). 빈 문자열이면 v5 prompt
            가 exception (1) 을 적용할 근거가 없음 = 기존과 동일 판정.
        call_structured_fn: `app.modules.llm.llm_client.call_structured` (또는
            test 에서 주입한 fake).
        project_config: optional — 프로젝트별 model override.
        opik_metadata: optional — Opik trace metadata.

    Returns:
        violations list. 각 항목:
            - `owned_object` (str): 위반 owned name.
            - `violating_phrase` (str): 위반 구절 발췌.
            - `reason` (str): 위반 사유 1 문장.
        violations 없거나 owned 가 빈 list → 빈 list.
    r   )load_promptload_schemascene_detail_owned_judgesystemschemauser_template
c              3  &   K   | ]	  }d |   yw)z- N ).0os     Q/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/steps/_owned_judge.py	<genexpr>z"run_owned_judge.<locals>.<genexpr>_   s     4ebHes   F   T)ensure_asciiindent	sort_keysz{t2i_prompt}r
   z{owned_list_block}z{owned_object_usage}z{camera_direction}z{shot_intent})stepsystem_promptuser_promptresponse_schemar   schema_namer   
violationszstep.contract_violationz>scene_detail_owned_judge response missing 'violations' field: )codemessagezAscene_detail_owned_judge response 'violations' must be list (got z): )app.modules.prompt_loaderr   r   joinjsondumpsreplace
isinstancedictr	   listtype__name__)
t2i_promptownedowned_object_usagecamera_directioncall_structured_fnr   r   r   r   r   r   r   templateowned_blockowned_usage_blockr"   resultr%   s                     r   run_owned_judger;   "   sj   f 	 C3X>F3X>F5GH))4e44K 

qD 		!1r	2	%{	3	'):	;	%'7'=2	> 
+"3	4   '%.#F fd#|6'A*P*
 	
 %Jj$'*Z(112#j^E
 	
 
    )r2   strr3   z	List[str]r4   List[Dict[str, str]]r5   r=   r6   zCallable[..., Dict[str, Any]]r   Optional[Dict[str, Any]]r   r?   r   r=   returnr>   )__doc__
__future__r   r*   typingr   r   r   r   r   app.core.errorsr	   r;   r   r<   r   <module>rE      s   2 #  6 6 $ 04.2ll l -	l
 l 6l -l ,l l lr<   