
    `j2                     
   d Z ddlZddlZddlmZmZ ddlmZmZm	Z	 ddl
mZ ddlmZ ddlmZmZ  ej"                  e      Z eej*                  j-                  dd	            Zd
ZdedefdZdededefdZ G d de      Zy)u   Shot 선택 단계 — 씬별 중요 샷 N개 선정.

2개 이상 shot이 있는 씬에서만 선정 (1개 shot은 자동 선택).
씬별 병렬 처리.

v4 (2026-04-19): 2중 캡 (절대 + 비율 50%) 적용, reason 필수.
    N)ThreadPoolExecutoras_completed)AnyDictList)
StepRunner)call_structuredload_promptload_schemaSHOT_SELECTION_MAX5   totalreturnc                      t        d| dz        S )u-   씬 총 샷 수의 50% 비율 캡 (최소 1).      )max)r   s    X/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/steps/shot_selection_step.py	_half_capr      s    q%1*    absolute_maxc           	      @    t        dt        |t        |                   S )u<   절대 상한과 50% 비율 상한 중 작은 값. 최소 1.r   )r   minr   )r   r   s     r   _effective_maxr      s    q#lIe$4566r   c                   t    e Zd ZdZdefdZdefdZddeeef   fdZ	e
deeeef      dedd	fd
       Zd Zy	)ShotSelectionStepu8   중요 샷 선택 — 씬별 2중 캡 (절대 + 비율).r   c           	      >   ddl }ddl}ddlm} ddlm}m} |j                         } |dd| j                         |dd| j                        fD ]3  }|j                  |j                  d	             |j                  d
       5 |j                  |j                   |dd| j                        d      j                  d	             |j                  d
       |j                   || j                        j                  d	             |j                         dd S )u   E2E13 Codex HIGH-3(재리뷰 HIGH-2): 실제 로드되는 effective
        prompt(system/user/selection_schema — DB active row 우선 포함)의
        내용 해시를 접는다 — 팩 내 내용 변경·DB winner 변경도 CP 무효화.r   N)compute_config_hashr
   shot_selectionsystemdbuserutf-8    selection_schemaT)	sort_keys   )hashlibjsonapp.core.step_runnerr    app.modules.prompt_loaderr   r   sha256r$   updateencodedumpsproject_config	hexdigest)selfr+   _jsonr    r   r   htexts           r   _config_hashzShotSelectionStep._config_hash$   s     	<FNN((tww?(&TWW=
D HHT[[)*HHW
 	
(*<I  
 &/	 	
	$T%8%89@@IJ{{}Sb!!r   step_idc                     dd l }ddlm} ddlm}  ||j
                        | j                  z  dz  dz  | j                  z  |z  dz  }|j                         r!|j                  |j                  d            S y )	Nr   )Pathsettingscheckpointsepisodeszmanifest.jsonr&   )encoding)r,   pathlibr<   app.core.configr>   projects_dir
project_id
episode_idexistsloads	read_text)r5   r:   r6   _Pathr>   cps         r   _load_prev_checkpointz'ShotSelectionStep._load_prev_checkpoint=   s    ), (''(oo -.0:;oo '(*9: 	
 99;;;r||W|=>>r   c                    | j                  d      }|r!|j                  di       j                  d      sddlm}  |ddd	      dd
lm}  ||| j                  d       |d   d   }t        dd| j                        }t        dd| j                        }t        dd| j                        }t        }	ddlm}
 |
j                  sg }|D ]f  }|j                  dg       }|D cg c]
  }|d   dd }}|j                  |d   |D cg c]  }|d   	 c}|t        |      t        |      d       h | j!                  ||
j"                         t%        d |D              }t%        d |D              }t&        j)                  d||       t        |      t        |      d||||	d| j+                         dS g }g }|D ]{  }|j                  dg       }t        |      dk  rH|r|d   d   ddgng }|j                  |d   |r	|d   d   gng |t        |      t        |      d       k|j                  |       } t&        j)                  dt        |      t        |      |	       g }|rt-        t.               5 }t1        |      D ci c]*  \  }}|j3                  | j4                  ||dz   ||||	      |, }}}t7        |      D ]&  }|j9                         }|s|j                  |       ( 	 d d d        ||z   }|j;                  d! "       ddlm} | j!                  ||j"                         t%        d# |D              }t%        d$ |D              }t&        j)                  d%||t        |             t        |      t        |      d||||	d| j+                         dS c c}w c c}w c c}}w # 1 sw Y   xY w)&Nshot_validatordatascenesr   )AppErrorzstep.no_shotsu   shot_extract 결과 없음i  )codemessagestatus_code)assert_no_failed_scenesr!   )consumer_stepr"   r#   r%   r(   r=   shots
shot_indexzSHOT_SELECTION_ENABLED=falserX   reasonscene_indexr[   selected_shot_indicesselected_shotstotal_shotsselected_countc              3   &   K   | ]	  }|d      ywr`   N .0rs     r   	<genexpr>z-ShotSelectionStep._execute.<locals>.<genexpr>m   s      Gh#3!4h   c              3   &   K   | ]	  }|d      ywr_   Nrc   rd   s     r   rg   z-ShotSelectionStep._execute.<locals>.<genexpr>n   s     A1a.rh   z-shot_selection DISABLED: %d/%d shots selected)rP   total_selectedr_   max_per_scene)completed_countapplicable_countfailed_countrO   config_hashr   u   씬 내 유일 샷zdshot_selection: %d scenes auto-selected (1-shot), %d scenes to evaluate (abs_max=%d, ratio_cap=50%%))max_workersc                     | d   S )Nr[   rc   )xs    r   <lambda>z,ShotSelectionStep._execute.<locals>.<lambda>   s	    q'7r   )keyc              3   &   K   | ]	  }|d      ywrb   rc   rd   s     r   rg   z-ShotSelectionStep._execute.<locals>.<genexpr>   s     F+QQ/0+rh   c              3   &   K   | ]	  }|d      ywrj   rc   rd   s     r   rg   z-ShotSelectionStep._execute.<locals>.<genexpr>   s     @Kq!M*Krh   z5shot_selection: %d/%d shots selected across %d scenes)rL   getapp.core.errorsrQ   "app.core.steps.shot_validator_steprU   r3   r   r$   r   r   rC   r>   shot_selection_enabledappendlen_apply_episode_max_shotsepisode_max_shotssumloggerinfor9   r   MAX_WORKERS	enumeratesubmit_select_for_scener   resultsort)r5   modeshot_cprQ   rU   rP   r"   user_templateschemamax_n	_settingsall_autosrW   sh	sel_shotsrk   r_   scenes_to_selectauto_selectedllm_selectedpoolidxfuturesfutr   all_resultsr>   s                               r   _executezShotSelectionStep._executeK   s    ,,-=>gkk&"599(C09UcfggN)<)<L\]*-xDGGD#$4fI-/AdggN" 	://Hgr* $# $&l#3?]^#   #$]#3IN-O2b.>-O&/#&u:&)%j!   ))(I4O4OP  Gh GGNAAAKKKGYde#&x=$'K !#+~(3eM#002  AEE'2&E5zQ  %*!H\$:FZ[\"$  $$#$]#3INeAh|.D-ETV&/#&u:&)%j&  !''* " 	+$4 5u	
 #< #,,<"=
 #>Q	 KK..37vu  #>   (0C ZZ\F$++F3 1 = $l278 	-%%k83M3MNF+FF@K@@K"K[1A	C  #;/ #F%"0*!&	  ,,.
 	
[ .P^ =<s0   &N&
N+<N6/N0:#N6N60N66N?resultsep_maxNc           	      @   |dk  ryt        d | D              }||k  rydt        t        t        f   ddfd}| D ]<  }t	        dt        ||d   z  |z              }|d   |kD  s*|d	   d| |d	<    ||       > t        d
 | D              |kD  rJt	        d | D        d d      }|sn1|d	   j                           ||       t        d
 | D              |kD  rJt        d | D              }||kD  rt        j                  d||       yt        j                  d|||       y)uO   에피소드 총 shot 수를 ep_max 이하로 비례 삭감. 0이면 비활성.r   Nc              3   &   K   | ]	  }|d      ywrb   rc   rd   s     r   rg   z=ShotSelectionStep._apply_episode_max_shots.<locals>.<genexpr>   s     @11-.rh   rf   r   c                 b    | d   D cg c]  }|d   	 c}| d<   t        | d         | d<   y c c}w )Nr^   rX   r]   r`   )r}   )rf   r   s     r   _sync_from_shotszDShotSelectionStep._apply_episode_max_shots.<locals>._sync_from_shots   sD    CDEUCV)WCVa!L/CV)WA%&"%a(8&9":A *Xs   ,r   r`   r^   c              3   &   K   | ]	  }|d      ywrb   rc   rd   s     r   rg   z=ShotSelectionStep._apply_episode_max_shots.<locals>.<genexpr>   s     7w!!$%wrh   c              3   2   K   | ]  }|d    dkD  s|  yw)r`   r   Nrc   rd   s     r   rg   z=ShotSelectionStep._apply_episode_max_shots.<locals>.<genexpr>   s     ?Gqq)9':Q'>Gs   c                     | d   S )Nr`   rc   )rf   s    r   rt   z<ShotSelectionStep._apply_episode_max_shots.<locals>.<lambda>   s
    a 01r   )ru   defaultc              3   &   K   | ]	  }|d      ywrb   rc   rd   s     r   rg   z=ShotSelectionStep._apply_episode_max_shots.<locals>.<genexpr>   s     ?w!!,-wrh   u2   episode_max_shots=%d could not be met → final %du'   episode_max_shots=%d applied: %d → %d)
r   r   strr   r   roundpopr   warningr   )r   r   total_beforer   rf   quotamax_rfinal_totals           r   r~   z*ShotSelectionStep._apply_episode_max_shots   sE    Q;@@@6!	;S#X 	;4 	;
 A5!,<*=!=!LMNE!"U*&'(8&9&5&A"# #	  7w77&@?G?14E "#'')U# 7w77&@ ?w??NNDfk KKA6<Yder   c           
         |d   }|j                  dg       }t        |      }	t        |	      }
t        |	|      }dj	                  d |D              }|j                  ||j                  dd      ||	|
|      }	 t        d|||| j                  d	| | j                         
      }|j                  d      }|s'|j                  d      xs g }|D cg c]  }|dd	 }}|D ch c]  }|d   	 }}t               }g }|D ]h  }t        |t              s|j                  d      }||v s*||vs/|j                  d      xs d}|j                  ||d       |j                  |       j |d | }|s"t        j                  d|       |d   d   ddg}t        j!                  d|t        |      |	|       ||D cg c]  }|d   	 c}||	t        |      dS c c}w c c}w c c}w # t"        $ rm}t        j                  d|||       |d | }|D cg c]  }|d   d| d nc c}w }}||D cg c]  }|d   	 nc c}w c}||	t        |      dcY d }~S d }~ww xY w)Nr[   rW   
c              3   ^   K   | ]%  }d |d    d|j                  dd       d|d     ' yw)zShot rX   z (beat:based_on_beatr   z): descriptionN)rx   )re   r   s     r   rg   z6ShotSelectionStep._select_for_scene.<locals>.<genexpr>   sC       
 B|$%WRVVOQ-G,HB}L]K^_s   +-scene_heading )r[   r   r   r_   half_capshots_blockr!   shot_selection_)stepsystem_promptuser_promptresponse_schemar3   schema_nameopik_metadatar^   r]   rY   rX   rZ   u=   shot_selection scene %d: LLM empty → fallback to first shotr   u   fallback: LLM 결과 없음z4shot_selection scene %d: %d/%d selected (eff_max=%d)r\   u;   shot_selection scene %d FAILED: %s → fallback to first %dz
fallback: )rx   r}   r   r   joinformatr	   r3   build_opik_metadataset
isinstancedictr|   addr   r   r   	Exception)r5   
scene_datacall_idxr"   r   r   r   	scene_idxrW   r   r   effective_maxr   r   r   rawlegacyir   valid_indicesseendedupeditemr   rZ   selectedr   excfirstfallback_sels                                 r   r   z#ShotSelectionStep._select_for_scene   s   }-	w+E
U#&ul;ii  
 
 

 $**!$.."=# + 
E	$%$' &#22-hZ8"668F **-.C$;<B@FG1a26G8=>"R-M>5D,.G!$-hh|,-'CtO!XXh/52FNN##HIHHSM  ~.HS
 #((<"8; 
 KKF3x=%
  )CK)L8a!L/8)L"*$"%h- ? H>> *M
  	NNM3 .=)E A  !Jse:LML 
  )CH)I5a!L/5)I".$"%e* 	sn   :AG( GG( %G19G( +G( 0BG( <G#G( G( (	I1!IH%$I1H>=III)resume)__name__
__module____qualname____doc__r   r9   rL   r   r   r   staticmethodr   intr~   r   rc   r   r   r   r   !   sw    B"c "2S o
c3h o
b "f$tCH~*> "f "fPT "f "fHZr   r   )r   loggingosconcurrent.futuresr   r   typingr   r   r   r-   r   app.modules.llm.llm_clientr	   r.   r   r   	getLoggerr   r   r   environrx   r   r   r   r   r   rc   r   r   <module>r      s     	 ? " " + 6 >			8	$(<cBC S S 
7# 7S 7S 7
Z
 Zr   