
    h;jR\                     (   d 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
 ddlmZ ddlmZmZ ddlmZ ddlmZ dd	lmZ dd
lmZmZ  ej2                  e      Zdeeef   deeeef      dedeeeeef   f   fdZdededeeef   dedeeef   deeeef      dede
eeeeef   f   ef   fdZ dZ!dededee
eef   ef   dee
eef   ef   def
dZ"deeef   dee
eef   ef   fdZ#deeeef      dee
eef   ef   ddfd Z$ G d! d"e      Z%y)#u  Shot 연관 분석 (LLM) — 같은 장소의 앞쪽 샷 중 배경 참조를 고른다.

**shot description + typed 프레이밍**(`shot_staging` 의
`framing_scale`·`camera_direction`)을 LLM 에 전달해 연관성을 판단한다.
이미지 생성 시 참조 이미지 선택에 쓰이고, shot_dependency(1차) 결과를
덮어쓴다.

★2026-08-27 (v1 중복 저작 정리): 종전에는 프레이밍 재료를
 `scene_detail.t2i_variations[0].t2i_prompt`(호출당 832~1,160자 산문)로
 받았다. 같은 정보가 `shot_staging` 에 typed 로 있고 이 스텝은 그 CP 를
 이미 읽고 있었다(LLM **뒤** close 정책에만 썼다). 이제 그것을 LLM
 입력으로 쓴다 — `depends_on` 에 `shot_staging` 을 명시했다.
    N)Path)AnyDictListOptionalTuple)AppError)FRAMING_CLOSEget_framing_scale_or_raise)validate_keep_elements_entry)
StepRunner)call_structured)load_promptload_schemaresultshotsloc_idreturnc          
         i }| j                  dg       D ]<  }|j                  d      |j                  d      f|j                  dg       }g }t        |dd       D ]  \  }}| dd    d	d    d
| d}	d|vrt        d|	 d|d      |d   D ]  }
t        |
|	d        |j                  d      |j                  d      ft	        fdt        |      D        d      }t	        fdt        |      D        d      }d|cxk  r|k  rn n|j                  |       t        j                  dd   d   d   d           d|i|<   ? |S )ub  LLM result 의 dependencies → llm_results dict 변환 + post-validation.

    각 location_refs[].keep_elements 의 entry 를 validate_keep_elements_entry
    로 검증. forward reference 검증도 본 helper 안에서 수행.

    Returns: {(si, shi): {"location_refs": [validated_ref, ...]}}.

    Raises: AppError (keep_elements shape 위반).
    dependenciesscene_index
shot_indexlocation_refsN   z Sr   _Shotz	 loc_ref[]keep_elementsz4step.shot_dependency_t2i.keep_elements_shape_invaliduR   : 'keep_elements' key missing in location_ref — schema v7 required field. value=  codemessagestatus_code)label_source
error_codec              3   T   K   | ]  \  }}|d    d   k(  r|d   d   k(  r| ! ywr   r   r   r   N ).0isref_keys      ]/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/steps/shot_dependency_t2i_step.py	<genexpr>z&_process_llm_result.<locals>.<genexpr>H   s?      V/tq!m$
2q'RS*7T /   %(c              3   T   K   | ]  \  }}|d    d   k(  r|d   d   k(  r| ! ywr&   r'   )r(   r)   r*   keys      r,   r-   z&_process_llm_result.<locals>.<genexpr>M   s>      N/tq!m$A.1\?c!f3L /r.   uD   shot_dependency_t2i: forward ref S%d_Shot%d → S%d_Shot%d, skipping)get	enumerater	   r   nextappendloggerwarning)r   r   r   llm_resultsdeploc_refsvalidated_refsref_idx_in_listrefr#   entryref_idxcur_idxr1   r+   s                @@r,   _process_llm_resultrA      s    02Kzz."-ww}%sww|'<=77?B/$-hrl$; OS$XRAxuSVHIoEVVWXL c)O'. )LLO7T !$  _-,U . ww}-sww|/DEGVy/ VG
 Ny/ NG
 G%g%%%c*ZFCFGAJ
G %<N ,^<CW .X     systemuser_promptschemaproject_configopik_metadatac                 >   	 t        d| ||||      }t        |||      }t        j                  d|t	        |             |dfS # t
        $ r  t        $ rE}	t        j                  d||	       |D 
ci c]  }
|
d   |
d   fd	g i nc c}
w }}
|d
fcY d}	~	S d}	~	ww xY w)u  단일 location 의 LLM call + post-validation + provider failure fallback.

    Returns: (llm_results, failed_count_increment).

    Behavior:
    - call_structured 성공 시 _process_llm_result 로 dict 반환.
    - shape AppError 는 silent absorb 차단 — caller 까지 re-raise.
    - provider/transport/JSON parse failure (non-AppError Exception) 만
      catch + warning + fallback empty refs + failed_count++.
    shot_dependency_t2i)stepsystem_promptrD   response_schemarF   rG   )r   r   u-   shot_dependency_t2i: %s (%d shots) — LLM OKr   u<   shot_dependency_t2i: %s LLM failed: %s — fallback to emptyr   r   r   r   N)r   rA   r6   infolenr	   	Exceptionr7   )rC   rD   rE   rF   rG   r   r   r   r8   er*   emptys               r,   _handle_llm_call_for_locrR   \   s    ( & #")'
 *&fMCVSQVZXA~  	 UW]_`aUZ[UZPQ!M"AlO46KKUZ[[axs*   AA BB2BBBB)zoom_in_detailexact_backgroundatmosphere_referencesishiframing_scale_map
camera_mapc                     |j                  | |fd      }|j                  | |fd      }|st        dd|  d| d      |st        dd|  d| d      | d| S )	u  LLM 입력에 실을 프레이밍 한 줄 — **없으면 호출 전에 선다.**

    ★이 판은 t2i 산문을 빼고 프레이밍을 핵심 대체 재료로 삼는다. 그것이
     비어 있는데 그대로 유료 호출을 하면 1순위 기준(카메라 이동/확대
     연속)을 **재료 없이 판단**하게 된다 — 조용히 나빠지고 돈은 나간다.
     종전 enum 검증(`_build_framing_map`)은 **호출 뒤** close 정책에만
     있었다 (2026-08-27 Codex BLOCK-4).
     z+step.contract_violation.shot_dependency_t2iSr   uk   : framing_scale 부재 — shot_staging 가 이 샷의 프레이밍을 안 준다(유료 호출 전 중단))r    r!   u[   : camera_direction 이 비었다 — 프레이밍 재료 없이 참조를 고를 수 없다z / )r2   r	   )rV   rW   rX   rY   scalecams         r,   _framing_line_or_raiser_      s     !!2s)R0E
.."cB
'C>E# '[ \
 	

 >E# 'N O
 	

 WCurB   
staging_cpc                     i }| j                  di       j                  dg       D ]A  }|j                  d      }|j                  d      }||*t        |d| d|       |||f<   C |S )u   shot_staging checkpoint → {(scene_index, shot_index): framing_scale}.

    framing_scale 는 shot_staging enum SOT — get_framing_scale_or_raise 로
    누락/invalid 를 fail-fast (No Silent Fallback).
    datar   r   r   z(shot_dependency_t2i._build_framing_map Sr   )where)r2   r   )r`   framing_mapshotrV   rW   s        r,   _build_framing_maprf      s     /1Kvr*..w;XXm$hh|$:!;B2$eC5Q"
RI < rB   r   rd   c                    | D ]  }|j                  d      }|j                  d      }|j                  ||f      }|t        dd| d| dd	      |t        k7  rY|j                  d
g       }|sn|d   }|j                  d      }|dk(  rd|d<   |dk(  rg |d
<   |dk(  rt        dd| d| d|dt         d	d	       y)u  close framing × ref_usage cross-step 정합 — deterministic post-process.

    FINDING 9 W3 sub-cause A. shot_dependency_t2i 의 LLM 은 shot_staging.
    framing_scale 을 모른 채 ref_usage 를 고른다. consumer close×ref_usage
    matrix invariant 상 close shot 의 location_ref 는 `zoom_in_detail` 만
    허용되므로, shot_staging.framing_scale SOT 기준으로 각 dependency 의
    location_ref 를 deterministic 하게 정합한다 (in-place mutate).

    close + location_ref present:
      - exact_background     → ref_usage='zoom_in_detail' (같은 방 배경 연속)
      - atmosphere_reference → location_ref drop (다른 방 — zoom_in_detail 의미모순)
      - zoom_in_detail       → keep
      - 그 외(빈값/enum 밖)  → AppError fail-fast (silent normalize 금지)
    close + location_refs=[] / non-close → 변경 없음.

    Raises:
      AppError: dependency shot 의 framing_scale 누락, 또는 close shot 의
        location_ref ref_usage 가 invalid.
    r   r   Nz.step.shot_dependency_t2i.framing_scale_missingr\   r   uY   : framing_scale 부재 — shot_staging checkpoint 에 해당 shot 없음 (FINDING 9 W3).r   r   r   r   	ref_usagerT   rS   rU   z0step.shot_dependency_t2i.close_ref_usage_invalidu+   : close framing location_ref 의 ref_usage=u    invalid — expected one of u)    (FINDING 9 W3, silent normalize 금지).)r2   r	   r
   _VALID_REF_USAGE)	r   rd   r9   rV   rW   framingr:   r=   rh   s	            r,   %_apply_close_framing_ref_usage_policyrk      s   . WW]#ggl#//2s),?Et5 &H I    m#77?B/qkGGK(	**/C00#%C **Gt5 &!!*-J'((QS   7 rB   c                   @    e Zd ZdZdefdZddeeef   fdZdefdZ	y)	ShotDependencyT2iStepuM   T2I 기반 shot 연관 재계산 — LLM이 스토리 연관성으로 판단.step_idc                     ddl m} t        |j                        | j                  z  dz  dz  | j
                  z  |z  dz  }|j                         r%t        j                  |j                  d            S y )Nr   settingscheckpointsepisodeszmanifest.jsonutf-8)encoding)
app.core.configrq   r   projects_dir
project_id
episode_idexistsjsonloads	read_text)selfrn   rq   cps       r,   _load_prev_checkpointz+ShotDependencyT2iStep._load_prev_checkpoint   su    ,&&'$//9()+/??;'( 	
 99;::bllGl<==rB   r   c                    | j                  d      }|r!|j                  di       j                  d      st        ddd      dd	lm}  ||| j
                  d
       | j                  d      }i }|rO|j                  di       j                  d      r.|d   d   D ]#  }t        |j                  dg             ||d   <   % | j                  d      }i }i }	|rH|j                  di       j                  d      r'|d   d   D ]  }
|
j                  dd      }|||
d   <    | j                  d      }i }|r|j                  di       j                  dg       D ],  }|j                  dd      }|s|j                  d|      |	|<   . dD ]S  }|j                  di       j                  |g       D ],  }|j                  dd      }|s|j                  d|      ||<   . U i }|rF|j                  di       j                  d      r%|d   d   D ]  }
|
j                  dg       ||
d   <    ddlm} t        |dd      dk(  }| j                  d      }t        |xs i       }i }|xs i j                  di       j                  dg       D ][  }|j                  d      |j                  d      }}|(|+t        |j                  d      xs d      j                         |||f<   ] i }|s| j                  d       }|r|j                  d|      j                  d|j                  dg             D ]]  }|j                  d      |j                  d!      }}||*|j                  d"g       D ]  }|j                  d#      s|d#   |||f<    ] _ i } |d   d   D ]E  }!|!d   }"|!j                  dg       D ])  }#|#j                  d$d      | |"|#j                  dd      f<   + G g }$i }%|d   d   D ]  }!|!d   }"|j                  |"      }&|!j                  dg       D ]  }#|#j                  dd      }'|&|'|&v s|j                  |"d      }|"|'|| j                  |"|'fd      |rt        |"|'||      nd|j                  |"|'fd      d%}(|$j                  |(       |s|%j                  |g       j                  |(         |$s	dddd&g id'S t!        d
d(      })t#        d
d)      }*i }+d},|%j%                         D ]-  \  }-}.t'        |.      d*k  r|.D ]  }d+g i|+|d   |d   f<    ,|	j                  |-|-      }/g }0|.D ]  }|d   }"|j                  |"g       }1|1D cg c]  }|s|j                  ||       }2}|2rd,d-j)                  |2       nd}3|0j                  d.|"d/d0|d    d1|d$    d2|rd3|d4    nd5|d6    z   |3z           d7|/ d8|- d9d:j)                  |0      z   }4t+        |)|4|*| j
                  | j-                         |.|-;      \  }5}6|+j/                  |5       |,|6z  },0 g }7|$D ]L  }|d   |d   f}8|+j                  |8d+g i      }9|7j                  |d   |d   |9j                  d+g       g d<       N | j                  d      }:|:st        d=d>d      t1        |7t        |:             t3        d? |7D              };t4        j7                  d@|;t'        |$             t'        |7      t'        |$      |,| j9                         d&|7idAS c c}w )BNshot_validatorrb   sceneszstep.no_shotsu   shot_extract 결과 없음r   r   r   )assert_no_failed_scenesrI   )consumer_stepshot_selectionselected_shot_indicesr   scene_directorprimary_locationr[   entity_merge	locationsshort_idname)
charactersr   propspresent_entity_idsrp   still_recipe_modeoffv1shot_stagingr   r   camera_directionscene_detail_shot_indext2i_variations
t2i_promptdescription)r   r   locationr   rj   t2ir   )completed_countapplicable_countfailed_countrb   rC   rE      r   z
  Entities: z, z[S02dr   z]
  Description: 
z  Framing: rj   z  T2I: r   u   장소: z (u   )

아래는 이 장소에서 촬영되는 샷들입니다 (시간순).
각 샷에 대해 배경 참조로 가장 적합한 앞쪽 샷을 선택하세요.

z

)rC   rD   rE   rF   rG   r   r   )r   r   r   character_refsz(step.shot_dependency_t2i.no_shot_staginguM   shot_staging checkpoint 없음 — framing_scale 정합 불가 (FINDING 9 W3)c              3   ,   K   | ]  }|d    s	d  yw)r   r   Nr'   )r(   ds     r,   r-   z1ShotDependencyT2iStep._execute.<locals>.<genexpr>  s     C1/0BQs   
z-shot_dependency_t2i: %d/%d linked (LLM-based))r   r   r   config_hashrb   )r   r2   r	   "app.core.steps.shot_validator_stepr   rF   setrv   rq   getattrrf   strstripr_   r5   
setdefaultr   r   itemsrN   joinrR   build_opik_metadataupdaterk   sumr6   rM   _config_hash)<r~   modeshot_cpr   sel_cpselected_mapr*   director_cpscene_locationlocation_namesdslocmerge_cpentity_namessidetentscene_entities_st_leanstaging_cp_inrX   rY   _sh_si_shit2i_map	detail_cps_si_	shot_idx_varshot_desc_mapscrV   shall_selectedlocation_groupssel_indicesshot_idxrM   rC   rE   r8   failedr   r   loc_name
shot_linesent_sidsent_names_listent_linerD   results_for_loc
failed_incr   r1   r9   r`   linkeds<                                                               r,   _executezShotDependencyT2iStep._execute  s
   ,,-=>gkk&"599(C9UcfggN)<)<Lab++,<=')fjj,00:F^H-14QUU;RTV5W1XQ}-. . 001AB)+)+;??626::8D!&)(3ff/447r-01 4
 --n=')||FB/33KDggj"-*-''&#*>N3' E ;#<<377B?C''*b1C,/GGFC,@S) @ ; 02;??626::8D!&)(346FF;OQS4Tr-01 4" 	40%8D@22>B
 /}/BC')
!'R,,VR8<<WbIC.0EC4#3*-GG./52+77<uw C;' J %'22>BI#--	:>> )--""=?B%'VVM%:BFF=<QC{i&7 !vv&6;77<08;L8IGS)$45!  <? +-&/(+BM"BffWb)?AvvmUW?Xr266,#:;< * , $&13&/(+BM"B&**2.KffWb)66,2&(k*A(,,R4C')&.$''4'8'8"h'L # $:*;Z$(*&{{B>2>	D !''-'223;BB4H! * ,* '(aQR+R02 2 2H=2H= *,,224MFE5zA~AHWY[G\K=!11\? CD %))&&9HJ}%)--b"5HP!XTW,"2"23"<!XKY^DIIn,E+FG_a!!Cao%6 7&&'&6%7r;7<Qy\N3%ahZ02 	  8*Bvh /i j ++j)*  +C'#22"668+'OZ /j FO 5T A]#Q|_5C//#'<=C /o!$"!="$	!   //?
?%    	.,Z8	
 CCCCVSQ]M^_  #<0 #L 1",,.#\2
 	
 "Ys   \\c                 J   ddl }ddl}ddlm}  |ddd      \  }}} |ddd	      \  }}}|xs d
|xs d
| j                  d}ddlm} t        |dd      dk(  rd|d<   |j                  |j                  |ddd       j                  d            j                         dd S )ue  실제로 로드된 프롬프트 팩 판을 담은 config_hash.

        ★이 step 은 판 인자 없이 **최신** 팩을 로드하는데(`_execute` 의
        `load_prompt`/`load_schema`) 반환에 `config_hash` 가 없어
        `StepRunner` 가 `compute_config_hash(project_config)` 로 떨어졌다.
        그래서 팩을 새 판으로 올려도 **옛 판 완료 CP 가 유효한 CP 로 SKIP**
        된다 — v8 완료 CP 가 v9 배포 뒤에도 그대로 재사용됐고, CP 만 보고는
        어느 판으로 만든 산출인지 가릴 수도 없었다. `step_runner` 주석이
        말하는 *"PROMPT_VERSION 등 step-local 시그널"* 이 이 자리다.

        `version_registry` 의 v9 표기는 provenance 기록용이라 이 판정에
        참여하지 않는다. 그래서 로더가 고른 판을 여기서 직접 읽는다.
        r   N)_resolve_stem_in_packrI   rC   z.md)extrE   z.jsonr[   )prompt_pack_systemprompt_pack_schemarF   rp   r   r   r   v1_typed_framingllm_input_contractTFc                 ,    t        |       j                  S )N)type__name__)os    r,   <lambda>z4ShotDependencyT2iStep._config_hash.<locals>.<lambda>  s    $q'*:*:rB   )	sort_keysensure_asciidefaultrt      )hashlibr{   app.modules.prompt_loaderr   rF   rv   rq   r   sha256dumpsencode	hexdigest)	r~   r   _jsonr   _sys_versch_verpayloadr   s	            r,   r   z"ShotDependencyT2iStep._config_hash  s     	C-!887A-!8:7A #*-R #*-R"11
 	43+U3t;,>G()~~KK4e :  <<BF7O
 )+cr 	rB   N)resume)
r   
__module____qualname____doc__r   r   r   r   r   r   r'   rB   r,   rm   rm      s4    W	S 	T
c3h T
l,c ,rB   rm   )&r   r{   loggingpathlibr   typingr   r   r   r   r   app.core.errorsr	   app.core.framing_scaler
   r   app.core.keep_elementsr   app.core.step_runnerr   app.modules.llm.llm_clientr   r   r   r   	getLoggerr   r6   r   tuplerA   intrR   ri   r_   rf   rk   rm   r'   rB   r,   <module>r	     s      3 3 $ L ? + 6 >			8	$<cN< S#X< 	<
 
%c3h
 <~'' ' cN	'
 ' S>' S#X' ' 4tCH~%&+,'T R E#s(OS01 U38_c)* 		<S#X	%S/3
(:tCH~&:eCHos*+: 
:zPJ PrB   