
    ʶYj=                       d Z ddlm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mZ ddlmZmZ  ej"                  e      ZdZdd	iZd
Z ej.                  d      Z ed      D  cg c]  }  e ed      | z          c} ZddZddZ	 	 	 	 ddZ	 	 	 	 	 	 	 	 ddZ 	 	 	 	 	 	 	 	 ddZ!dddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ"dd	 	 	 	 	 	 	 	 	 	 	 	 	 d dZ#ddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d!dZ$yc c} w )"u  plate_select — 같은 location 복수 플레이트의 샷별 VLM 선택 판정.

E2E6 육안 피드백 ⑤ (2026-07-16): 한 location 에 서브공간별 플레이트가
여러 장일 때 상류 배정(shot_ids)이 샷의 실제 서브공간과 어긋나는 실측
(S18sh1 안방 침대 샷 ↔ 거실 플레이트). 후보가 2장 이상이면 VLM(gemini
-pro)이 샷 텍스트·장소 서술 근거로 서브공간을 선택한다 — 불명확하면
현행 배정 유지(계약 명시, 판정=전부 LLM/VLM·하드코딩 0).

bg group id 는 "L{loc}B{n}" 계약 형식 — location 묶음은 이 식별자
구조 파싱(의미 판단 아님)으로만 한다.
    )annotationsN)Path)AnyCallableDictListOptionalTuple)load_promptload_schemaplate_select1z1.202607161540   z^(L\d+)B\d+$   Ac           	     n    	 t         |    S # t        $ r  t        d| dt        t                d      w xY w)Nz-unknown plate_select prompt version selector z	 (known: ))PROMPT_VERSION_MAPKeyError
ValueErrorsorted)selectors    W/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/plate_select.pyresolve_prompt_versionr   )   sK    
!(++ 
;l)F+=$>#?qB
 	

s    )4c                p    t         j                  t        | xs d            }|r|j                  d      S dS )uB   bg group id → location 코드 (계약 형식 벗어나면 None). r   N)	_BG_ID_REmatchstrgroup)bg_idms     r   location_of_bg_idr#   3   s.    EKR()A1771:$$    c                V   i }t        | xs i       D ]  }| |   }t        |t              r|j                  d      dk7  r-t	        |      }|s;|j                  d      xs d}|rt        |      nd}||j                         sr|j                  |g       j                  ||f        |S )u   location 코드 → [(bg_id, plate_path)] (status ok + png 실재만).

    입력=background_render CP data.groups shape. 정렬=bg_id (결정론).
    statusokpng_pathr   N)	r   
isinstancedictgetr#   r   is_file
setdefaultappend)	bg_groupsoutr!   glocpngps          r   plate_candidates_by_locationr5   9   s     .0C	R(e!T"aeeHo&=&eeJ%2DI$ 9AIIKsB&&qz2 ) Jr$   c           
     
   ddl }ddlm} i }i }dD ]>  }t        |       |z  dz  dz  |z  |z  dz  }|j	                         s2	 |j                  |j                  d	            j                  d
i       }	|	j                  d      xs i j                         D ]  \  }}t        |t              r|j                  d      dk7  r+||vr|||<   |j                  d      xs g D ]j  }|j                  t        |            }|r7t!        |j#                  d             dt!        |j#                  d             n
t        |      }||vsf|||<   l  A ||fS # t        $ r"}
t        j                  d||
       Y d}
~
ld}
~
ww xY w)u   (bg_groups 병합, shot_key("si_shi")→bg_id) — resolve_shot_plate_map
    과 동일 소스·선순위(background_render 우선, chain fallback).r   N_SHOT_ID_REbackground_renderbackground_chain_rendercheckpointsepisodesmanifest.jsonutf-8encodingdatau"   plate_select: %s 로드 실패: %sgroupsr&   r'   shot_idsr   _   json%app.modules.pipeline.shot_conti_lightr8   r   existsloads	read_textr+   	Exceptionloggerwarningitemsr)   r*   r   r   intr    )projects_dir
project_id
episode_id_jsonr8   rC   assignstepcprB   excr!   r1   sidr"   keys                   r   load_bg_groups_and_assignmentr\   R   s   
 A(*FF@+m;jH "12 	 yy{	;;g.003FB 
 (+1r88:HE1a&!%%/T*AF" !uuuZ(.B.%%c#h/ aggaj/*!C
O+<=!$S f$"'F3K / ; A0 6>  	NN?sK	s   1E	F E==Fc           
        ddl }ddlm} i }dD ]!  }t        |       |z  dz  dz  |z  |z  dz  }|j	                         s2	 |j                  |j                  d	            j                  d
i       }|j                  d      xs i j                         D ]  \  }
}t        |t              s|j                  d      xs g D ]j  }|j                  t        |            }|r7t!        |j#                  d             dt!        |j#                  d             n
t        |      }||vsf|
||<   l  $ |S # t        $ r"}	t        j                  d||	       Y d}	~	Md}	~	ww xY w)u	  shot_key → bg_id 배정 기록 — **status 불문** (seed-bg B2 계약).

    load_bg_groups_and_assignment 는 status=ok 그룹만 통과시켜 producer
    실패(failed/rejected) 그룹의 배정 기록이 사라진다 — 그러면 '실패한
    생산'이 '구조적 무생산'으로 오인되어 seed fallback 이 fail-open 된다
    (Codex seed-bg 리뷰 BLOCKING-2). 이 로더는 생산 기록의 존재 자체를
    보존한다: 어떤 상태든 배정 기록이 있으면 seed 하강 금지 대상.
    r   Nr7   r9   r<   r=   r>   r?   r@   rB   u.   plate_select(any-status): %s 로드 실패: %srC   rD   r   rE   rF   rG   )rR   rS   rT   rU   r8   rV   rW   rX   rB   rY   r!   r1   rZ   r"   r[   s                  r   load_bg_assignment_any_statusr^   x   sh    AF@+m;jH "12 	 yy{	;;g.003FB  (+1r88:HE1a&uuZ(.B.%%c#h/ aggaj/*!C
O+<=!$S f$"'F3K / ; A. M  	NN@$M	s   1D66	E!?EE!)project_configprompt_versionopik_metadatac           	     
   ddl m} |D 	
ci c]  \  }	}
|	|

 }}	}
||vr|t        d      dd| difS t        |      dk  r
|||   ddifS t        |      t        t              kD  r'|||   dd	t        |       d
t        t               difS t        |      }t        t        d|      }t        t        dd|      }t        t        dt        |             }t        ||      D 	ci c]  \  \  }	}}|	| }}}	}dd|  d| dg}t        ||      D ]B  \  \  }	}
}|	|k(  rdnd}|j                  dd| | dd       |j                   ||
             D i |xs i t         dddii}	  |t         d||||t         d|      }|j!                  d      xs dj#                         }|j%                         D 	ci c]  \  }	}||	
 }}	}|j!                  |      }|j%                         D 	ci c]  \  }	}||	
 c}}	||t'        |j!                  d            |j!                  d      xs dd }|#t        j                  d!|       ||d"<   |||   |fS ||k7  r<|j!                  d      d#ur)t        j)                  d$||       ||d"<   ||d%<   |||   |fS ||d&<   |||   |fS c c}
}	w c c}}}	w # t        $ r4}t        j                  d|       |||   t        |      |dfcY d}~S d}~ww xY wc c}}	w c c}}	w )'u6  샷의 플레이트 VLM 선택 — (chosen_bg_id, path, record).

    후보 1장이거나 배정이 후보에 없으면 판정 없이 현행 유지(no-op,
    record.skipped 사유 기록). 판정 실패/계약 밖 choice = 현행 유지
    (fail-open — 배정 SOT 보존이 안전측, 기록 남김).
    r   )png_partr   skippedu   배정 u    후보 밖 — 판정 생략rF   u   후보 1장 — 판정 불요u   후보 u   장 > u    — 생략judge_system)versionjudge_schemaNtextz#SHOT TEXT (authoritative, Korean): z
LOCATION: )typerh   u    — CURRENTLY ASSIGNEDz
Candidate :_judgemodelz
gemini-pro)r_   schema_namera   u0   plate_select 판정 실패 — 배정 유지: %s)errorkeptchoice	confident	reason_ko)
candidatesassignedrp   rq   rr   u4   plate_select: 계약 밖 choice %r — 배정 유지ro   TuD   plate_select: low-confidence 전환 차단 %s→%s — 배정 유지blocked_low_confidencechosen)%app.modules.pipeline.multiroll_geminirc   r   len_LABELSr   r   _MODULEr   listzipr.   rM   rN   rO   r   r+   striprP   boolinfo)	shot_desc
place_textassigned_bg_idrs   call_structured_fnr_   r`   ra   rc   bidr4   by_idresolved	judge_sysrg   labels_plablabel_by_idpartsmarkpcresrY   rp   id_by_labelrv   records                               r   select_plate_for_shotr      s   " ?",-*QS!V*E-U"tBx 00MN*P P 	P
:u^4678 8 	8
:W%u^4Z 1G~[Q7S S 	S &n5HG^XFIw'/1L'+C
O,-F25j&2IJ2I	b3382IKJ1) =#&$ #E Z0a#.( *.0 	f
3%vQ.GHIXa[!	 1
 
Q^!r	Pwiv$6,8O	PB	7 iv	5,gYf+='
 ggh%2,,.F,7,=,=,?@,?S38,?K@__V$F0;0A0A0CD0CHCsCx0CD"#''+./WW[)/RF ~BF	L'vu^4f<< CGGK$8$DRF	
 (v+1'(u^4f<<F85=&((G ." K(  7I3Ou^4X~77 7 	77
 A Es5   J,)J20J9 K97K?9	K6)K1+K61K6)r`   c                    ddl }ddlm} |j                  | |||D 	cg c]  \  }}	|	 c}	}t	        |      |t
        ddd      }
 ||
|D cg c]	  \  }}||f c}}dd	      S c c}	}w c c}}w )
u  권위 판정의 결정 입력 지문 — 후보 플레이트 **bytes** 포함.

    플레이트 재생성·후보 구성·배정·샷 텍스트·팩·모델이 바뀌면 재판정,
    일치하면 판정 0콜 재사용 (Codex R1: 선택 기록 재사용 계약).
    r   N)compute_input_fingerprint)r   r   rt   candidate_idspackjudge_modelauthority_versionTF)	sort_keysensure_asciir   )promptlabeled_refs
roll_countcritique_enabled)rH   %app.modules.pipeline.multiroll_selectr   dumpsr   PLATE_AUTHORITY_VERSION)r   r   r   rs   r   r`   rU   r   r   rE   payloadr4   s               r   plate_authority_fingerprintr      s      kk"$&0:;
fc1c
;*>:&!8	
 U  G %-78Z63sAhZ8	  < 9s   A(A.F)r_   r`   ra   forcec                   ddl m} i }t        | |      D ]  } ||      \  }}| d| }|j                  |      (|j                  |      xs d}|s@t	        |      }|j                  |xs dg       }t        |      dk  rp||D ch c]  \  }}|	 c}}vrt        j                  d||       t        |j                  |      xs d|j                  |d      ||||
      }|j                  |      xs i }|s|j                  d	      |k(  rr|j                  d
      sn`t        |d
         j                         rD|j                  d|      |j                  d|      |d
   |j                  d      xs i |dd||<   pt        |j                  |      xs d|j                  |d      ||||	|
|      \  }}}t        |      rt        |      j                         s"t        j                  d|t        |             ||t        |      ||d}|||<   dD ci c]  }|||   
 c}||<    |S c c}}w c c}w )u  콘티 생성 **전** 플레이트 권위 판정 (R1 — 선행 고정).

    tags = 콘티 대상 샷(선택·person-visible·no-prev, 맵 플레이트 샷 제외).
    같은 location 후보 2장 이상 + 배정 존재 샷만 판정. 반환
    {tag: {assigned, chosen, plate_path, record, fingerprint}} — caller 가
    plate_map override 및 CP/사이드카 영속. sidecar 는 in-place 갱신
    (지문 일치=판정 0콜 재사용, force=재판정).

    판정된 샷만 entry 를 갖는다 — 후보 1장/배정 없음/플레이트 없음 샷은
    entry 없음(배정 플레이트가 콘티·스틸 공통 소스라 권위 충돌 없음).
    r   )	parse_tag)r[   rE   r   rF   uI   plate_authority %s: 배정 %s 후보 밖 — 판정 생략(배정 유지))r   r   r   rs   r   r`   fingerprint
plate_pathrt   rv   r   T)rt   rv   r   r   r   reused)r   r   r   rs   r   r_   r`   ra   uA   plate_authority %s: 선택 결과 비파일(%r) — 배정 유지)rt   rv   r   r   r   )&app.modules.pipeline.shot_ref_classifyr   r   r+   r#   rx   rN   rO   r   r   r,   r   r   )tags	plate_mapassign_by_keycands_by_locshot_desc_by_tagplace_text_by_scenesidecarr   r   r_   r`   ra   r   r   	authoritytagsishir[   rt   r2   candsr   rE   r   prev	chosen_idchosen_pathr   entryks                                  r   run_plate_authorityr   #  s   6 A+-Id	**C.CAcUm==% $$S)/R)  B/u:> e4eFCCe44NN 1&**3/52*..r26##)
 {{3%2';6,'2T,'(002 !HHZ:((8X6"<0((8,2*IcN )>&**3/52*..r26#1))'	*
&	; ;tK'8'@'@'B NNSS%  k*&
 	#%
% uQxK%
Q +Z } 5r
s   H8
 H>)r   r   returnr   )r!   r   r   zOptional[str])r/   Dict[str, Dict[str, Any]]r   !Dict[str, List[Tuple[str, Path]]])rR   r   rS   r   rT   r   r   z0Tuple[Dict[str, Dict[str, Any]], Dict[str, str]])rR   r   rS   r   rT   r   r   Dict[str, str])r   r   r   r   r   r   rs   List[Tuple[str, Path]]r   Callable[..., Dict[str, Any]]r_   Optional[Dict[str, Any]]r`   r   ra   r   r   z Tuple[str, Path, Dict[str, Any]])r   r   r   r   r   r   rs   r   r   r   r`   r   r   r   )r   z	List[str]r   zDict[str, Path]r   r   r   r   r   r   r   zDict[int, str]r   zDict[str, Any]r   r   r   r   r_   r   r`   r   ra   r   r   r~   r   r   )%__doc__
__future__r   loggingrepathlibr   typingr   r   r   r   r	   r
   app.modules.prompt_loaderr   r   	getLogger__name__rN   rz   r   r   compiler   rangechrordry   r   r#   r5   r\   r^   r   r   r   )is   0r   <module>r      sf  
 #  	  = = >			8	$
 	    BJJ'	&+Ah
/h3s3x!|h
/
%(&2###&#47#5#L''#&'47''b 04.2V)V) V) 	V)
 'V) 6V) -V) V) ,V) &V)@ %% % 	%
 '% % % 	%f 04.2k
k k "	k
 4k %k (k k 6k k -k k ,k k k{ 0s   %C<