
    -ǚjP                    ,   d Z ddlmZ ddlZddlZddlmZm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  ej*                  e      Zd	Zd
ZddZe G d d             Ze G d d             Z	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 ddZy)uS  Asset readiness preflight — Group 1 #2 (visual_pipeline_contracts_plan.md).

scene_image_pipeline 진입 전 expected vs registered vs disk_found 3-way 검증.
fail-fast 정책 — 참조 이미지가 없는데 silent text-only 로 fallback 하지 않는다.

핵심 원칙 (memory: feedback_no_silent_fallback):
- 참조 없으면 만들지 않는다.
- continuity 모르면 조용히 비우지 않는다.
- 배경 소유 객체를 scene prompt가 다시 그리지 않는다.

본 모듈은 첫 원칙을 강제한다. ENV `ALLOW_TEXT_ONLY_WITHOUT_REFS=true` 또는
``settings.allow_text_only_without_refs=True`` 시에만 missing 을 무시 (디버깅용
opt-in). 기본값은 block.

본 모듈은 시나리오 의존성 0 — entity short_id / asset_type 같은 generic 표기만
검사한다. 어떤 작품 고유명사도 hardcode 안 한다.
    )annotationsN)	dataclassfieldPath)AnyDictListOptionalTuple)Session)is_immobilized_state)1trueyeson)0falsenooff c                    t         j                  j                  d      } | R| j                         j	                         }|t
        v ry|t        v ryt        j                  d| t
        t        z          	 ddl	m
} t        |dd      }|xs dj                         j	                         }|t
        v ryy# t        $ r Y yw xY w)	u  디버깅 opt-in flag — ENV 우선, settings fallback, default False (block).

    ENV ``ALLOW_TEXT_ONLY_WITHOUT_REFS`` 인식 못 하는 값 (typo) 은 silent True 가
    아니라 settings default 로 fallback (fail-fast 원칙 — typo 가 safety 우회 X).

    Codex review IMPORTANT (Group 1 #2): settings field 가 ``bool`` 타입이면
    Pydantic 이 import 단계에서 ENV ``garbage`` 를 ValidationError 로 raise 해
    앱 부팅이 실패. settings field 를 ``str`` 로 두고 본 helper 가 직접 parse.
    ALLOW_TEXT_ONLY_WITHOUT_REFSTFua   ALLOW_TEXT_ONLY_WITHOUT_REFS=%r unrecognized — falling back to settings default. Use one of %s.r   settingsallow_text_only_without_refsr   )osenvirongetstriplower_TRUTHY_VALUES_FALSY_VALUESloggerwarningapp.core.configr   getattr	Exception)rawvr   raw_settings       N/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/asset_readiness.py_is_text_only_allowedr-   %   s     **..7
8C
IIK/-/	

	,h(FKB%%'--/ s   5=B4 4	C ?C c                  R    e Zd ZU dZded<   ded<   ded<   ded<   ded<   dZded	<   y
)AssetMissEntryuM  단일 expected asset 의 missing 사유.

    상태 분류:
      - "no_db_row": ImageAsset row 자체가 없음 (preceding step 누락 or skip)
      - "no_file_path": ImageAsset row 있으나 file_path 비어 있음 (저장 실패)
      - "disk_missing": file_path 있으나 실제 disk 에 파일 없음 (이동/삭제됨)
    str	entity_idshort_idname
asset_typereasonr   	file_pathN)__name__
__module____qualname____doc____annotations__r6        r,   r/   r/   G   s,     NM
IOKIsr=   r/   c                      e Zd ZU dZded<   ded<    ee      Zded<    ee      Zded<    ee      Z	ded	<    ee      Z
d
ed<   edd       Zedd       ZddZdddZy)AssetReadinessCardu  scene_image_pipeline preflight 결과.

    expected/registered/disk_found 3-way:
      - expected: 본 episode 가 필요로 하는 모든 ref asset (entity scope 기반)
      - registered: 그 중 ImageAsset row 가 존재하는 것
      - disk_found: 그 중 file_path 가 실제 disk 에 존재하는 것

    invariant: ``len(disk_found_ids) <= len(registered_ids) <= len(expected_ids)``.
    missing 은 위 invariant 의 빈자리를 reason 별로 분류.
    r0   
project_id
episode_id)default_factoryz	List[str]expected_idsregistered_idsdisk_found_idszList[AssetMissEntry]missingc                J    | j                    xr t        | j                        S )u    missing 0 이고 expected ≥ 1.)rF   boolrC   selfs    r,   is_readyzAssetReadinessCard.is_readyk   s!     <<;D):):$;;r=   c                ,    t        | j                        S )N)lenrF   rI   s    r,   missing_countz AssetReadinessCard.missing_countp   s    4<<  r=   c           
         d| j                   dd  dt        | j                         dt        | j                         dt        | j                         d| j
                   
S )u*   logger / error message 용 한 줄 요약.zAssetReadiness episode=N   z: expected=z registered=z disk=z	 missing=)rA   rM   rC   rD   rE   rN   rI   s    r,   summaryzAssetReadinessCard.summaryt   st     &doobq&9%: ;D--./ 0d1123 4++,- .))*	,	
r=   c                    | j                   sy| j                   d| }| j                  t        |      z
  }dj                  d |D              }||dkD  rd| dz   S dz   S )	u(   missing 들의 reason-별 sample 요약.z(no missing)Nz, c              3     K   | ]>  }|j                   xs |j                  d d  d|j                   d|j                   d @ y w)NrP   (/))r2   r1   r4   r5   ).0ms     r,   	<genexpr>z5AssetReadinessCard.missing_summary.<locals>.<genexpr>   sG      
 zz,Q[[!_-Qq||nAahhZqIs   AAr   z (+z more)r   )rF   rN   rM   join)rJ   	max_itemsheadmoreitemss        r,   missing_summaryz"AssetReadinessCard.missing_summary~   so    ||!||JY'!!CI-		 

 
 dQh#dV6*??B??r=   NreturnrH   )ra   int)ra   r0   )   )r[   rb   ra   r0   )r7   r8   r9   r:   r;   r   listrC   rD   rE   rF   propertyrK   rN   rQ   r_   r<   r=   r,   r?   r?   X   s    	 OO#D9L)9 %d ;NI; %d ;NI;$)$$?G!?< < ! !

@r=   r?   c                   ddl m} ddlm} ddlm} ddlm}m}m	}m
}	 t        ||      }
ddlm}  |||      }ddlm} | j!                  |      j#                  ||j$                  |j&                  k(        j)                  |j*                  |k(  |j,                  |k(  |j.                  |k7  |j0                  j3                  t5         |                         j7                         }|D cg c]G  }|j&                  |vr7|j&                  |j8                  xs d	|j:                  xs d	|j0                  d
I }}|D ]  }|d   }|
j<                  j?                  d|        | j!                  |	      j)                  |	j*                  |k(  |	j@                  |k(  |	jB                  dk(  |	jD                  dk(        jG                  |	jH                  jK                               jM                         }|s0|
jN                  j?                  tQ        ||d   |d   dd             |
jR                  j?                  d|        |jT                  s1|
jN                  j?                  tQ        ||d   |d   dd             @ ||jT                        }|r|jW                         s<|
jN                  j?                  tQ        ||d   |d   dd|jT                               |
jX                  j?                  d|         |D ch c]  }|d   dk(  s|d    }}| j!                  |      j)                  |j*                  |k(  |j8                  dk(  |j0                  dk(        j7                         D ch c]  }|j&                   }}|rddlm-}  || ||      D cg c]  }|j\                  |v r| }}| j!                  |	      j)                  |	j*                  |k(  |	jB                  dk(  |	j^                  ja                  d            j7                         }ddl1}i }|D ]J  }|je                  d|j^                  xs d	      }|s&|||jg                  d      |jg                  d      f<   L |D ci c]  }|d   |
 }}|D ]  } | j\                  | jh                  }"}!|"|v s|!|v s|"|v r)d|! d |" }#|
j<                  j?                  |#       |jk                  |!|"f      }$|$sj|
jN                  j?                  tQ        |!|jk                  |!i       jk                  dd	      |jk                  |!i       jk                  dd	      d!d             |
jR                  j?                  |#       |$jT                  sk|
jN                  j?                  tQ        |!|jk                  |!i       jk                  dd	      |jk                  |!i       jk                  dd	      d!d             ] ||$jT                        }|r|jW                         sv|
jN                  j?                  tQ        |!|jk                  |!i       jk                  dd	      |jk                  |!i       jk                  dd	      d!d|$jT                               |
jX                  j?                  |#        tm        |||      }%|%r| j!                  |	      j)                  |	j*                  |k(  |	jB                  d"k(        j7                         }&|&sU| j!                  |	      j)                  |	j*                  |k(  |	j^                  ja                  d#            j7                         }&i }'|&D ]J  }(je                  d$|(j^                  xs d	      }|s&|(|'|jg                  d      |jg                  d      f<   L |D ci c]  }|d   |
 }}|%D ]  \  }!})to        |)      sd%|! d |) }#|
j<                  j?                  |#       |'jk                  |!|)f      }$|$sm|
jN                  j?                  tQ        |!|jk                  |!i       jk                  dd	      |jk                  |!i       jk                  dd	      d&|) d             |
jR                  j?                  |#       |$jT                  sn|
jN                  j?                  tQ        |!|jk                  |!i       jk                  dd	      |jk                  |!i       jk                  dd	      d&|) d             M ||$jT                        }|r|jW                         sy|
jN                  j?                  tQ        |!|jk                  |!i       jk                  dd	      |jk                  |!i       jk                  dd	      d&|) d|$jT                               |
jX                  j?                  |#        |
S c c}w c c}w c c}w c c}w c c}w c c}w )'u  scene_image_pipeline 의 expected ref asset 들이 모두 준비됐는지 검사.

    Expected scope:
      - 본 episode 의 모든 entity (location/outlook 제외, low_freq skip 제외) 의
        primary reference asset.
      - 본 episode 의 모든 (character, outlook) composite asset (O00 + cid/oid skip 제외).
      - 본 episode 의 character_state_variant ref (shot_staging cp 의 dead/injured/
        unconscious 인물 — Codex review BLOCKING 보강).

    각 expected 에 대해:
      - DB 에 ImageAsset row 가 있는가? (asset_type="reference", is_primary=1)
      - file_path 가 비어 있지 않은가?
      - 실제 disk 에 파일이 존재하는가?

    어느 단계가 빠지면 ``AssetMissEntry`` 로 missing 에 add. silent skip 0.
    r   r   )resolve_image_path)load_low_freq_skip_ids)CharacterOutlookEntityCanonEntityEpisodeLink
ImageAsset)r@   rA   )canonical_ref_owner_types)PRESENCE_SHELVEDr   )idr2   r3   typero   z
reference:	reference   r2   r3   	no_db_row)r1   r2   r3   r4   r5   no_file_pathdisk_missing)r1   r2   r3   r4   r5   r6   rp   	characterO00outlook)episode_outlook_rowsz%composite:%Nz#composite:([a-f0-9-]+):([a-f0-9-]+)   z
composite::	compositecharacter_state_variantz%state_variant:%z state_variant:([a-f0-9-]+):(\w+)zstate_variant:zstate_variant/)8pathlibr   app.core.file_pathsrg   app.core.low_freq_skiprh   app.models.projectri   rj   rk   rl   r?   .app.modules.pipeline.grounding_entity_contractrm   app.core.entity_identityrn   queryrZ   canon_idro   filterr@   rA   presence_statusentity_typein_rd   allr2   r3   rC   appendr1   r4   
is_primaryorder_by
created_atdescfirstrF   r/   rD   r6   existsrE   ry   character_idprompt_usedlikeresearchgroup
outlook_idr    _collect_expected_state_variantsr   )*dbr@   rA   _Pathrg   rh   ri   rj   rk   rl   cardrm   skipped_idsrn   entity_rowseexpected_entitiesenteidprimarypathchar_idso00_idsry   c
all_comboscomposite_assets_reexisting_pairscarX   	ent_by_idcombocidoidkeyassetexpected_state_pairs	sv_assets
sv_by_pairsa
state_types*                                             r,   compute_episode_asset_readinessr      sQ	   * &6=  
KD )Z@K
 : 		!2!;!;{~~!M	N	((J6((J6--1AA##''-F-H(IJ	

 
  A44{" tt!1r166<RQRQ^Q^_   !$i  :cU!34HHZ V%%3$$+%%4%%*	 Xj++0023UW 	 LLJc&k&{!  ""Zu#56  LLJc&k&~!  !'"3"34LLJc&k&~!++! 
 ""Zu#56G !L &7U%6c#f+:TD	%6HUhh{+22""j0  E)##y0
 #%	     	B ,B
JG
G!~~) G 	 
 HHZ V%%3%%4&&++N;
 SU 	 	<>"B

A2>>CWUWXA;=
AGGAJ78 # 0AA/@SY^/@	AE))5+;+;C g~!3sk7Ise1SE*C$$S)"&&Sz2E##N!IMM#r,B,F,FzSU,V"sB/33FB?*;% 
 &&s+??##N!IMM#r,B,F,FzSU,V"sB/33FB?*>% 
 %eoo6DT[[]##N!IMM#r,B,F,FzSU,V"sB/33FB?*>#oo	%  &&s+C  R <J 1  HHZ V%%3%%)BB SU 	 $))Z7**//0BC   9;
B

>@TRTUA79
AGGAJ
34 
 0AA/@SY^/@	A3OC (
3"3%q5C$$S)NNC#45E##N!IMM#r,B,F,FzSU,V"sB/33FB?!/
|<[% 
 &&s+??##N!IMM#r,B,F,FzSU,V"sB/33FB?!/
|<^% 
 %eoo6DT[[]##N!IMM#r,B,F,FzSU,V"sB/33FB?!/
|<^#oo	%  &&s+C  4F KyZ V
. BN Bs+   +Af5 f:f:0f?g/g	gc                   ddl }ddlm} ddlm} ddlm}m}  ||j                        | z  dz  dz  |z  dz  d	z  }|j                         sg S 	 |j                  |j                  d
            }	t        |	t               sg S |D cg c]  }|j#                  d      dk(  s| }} ||d d       }|	j#                  d|	      }t        |t               sg S |j#                  dg       }t        |t$              sg S t'               }|D ]  }t        |t               s|j#                  dg       xs g D ]Y  }t        |t               s|d   }t)        |      s%|j#                  dd      }|r	 |||      nd}|sG|j+                  ||f       [  t-        |      S # t        $ r"}
t        j                  d|
       g cY d}
~
S d}
~
ww xY wc c}w )u  shot_staging cp 에서 dead/injured/unconscious 로 매핑된 (cid, state_type) 추출.

    CharacterStateVariantStep 와 동일 source + 동일 name normalization (Codex review
    Critical: producer 가 ``build_name_index/lookup_name`` 으로 normalized name 매칭.
    preflight 가 exact match 하면 LLM 의 name 변형 (괄호/공백/캐릭터 변형 표기) 이
    drift 되어 false-positive block).

    shot_staging cp 부재 시 빈 리스트 (해당 step 미실행 — silent skip 아닌 expected 0
    으로 처리).

    cp shape 두 가지 모두 지원: ``{"data": {"shots": [...]}}`` (pipeline-v4 manifest)
    + ``{"shots": [...]}`` (legacy / direct).
    r   Nr   r   )build_name_indexlookup_namecheckpointsepisodesshot_stagingzmanifest.jsonzutf-8)encodingu;   shot_staging cp parse failed (%s) — state_variant scope=0rp   rv   c                &    | j                  dd      S )Nr3   r   r   r   s    r,   <lambda>z2_collect_expected_state_variants.<locals>.<lambda>  s    vr*r=   c                &    | j                  dd      S )Nro   r   r   r   s    r,   r   z2_collect_expected_state_variants.<locals>.<lambda>  s    155r?r=   )key_fnvalue_fndatashotscharacter_anglessubject_stater   )jsonr~   r   r&   r   app.core.name_matcherr   r   projects_dirr   loads	read_textr(   r$   r%   
isinstancedictr   rd   setr   addsorted)r@   rA   r   _jsonr   	_settingsr   r   cp_pathcpexcr   char_entities
name_to_idinnerr   pairsshotr   statecnamer   s                         r,   r   r     s   $ %5C 	i$$%
2
	$	%'1	2
	*	+ 
 >>	[[**G*<=
 b$	 %6X$5SK9WS$5MX!**J FF62EeT"	IIgr"EeT"	E$%((-r28b8B b$''E#E*{B/8=k*e44IIsEl+ 9  %=O  TVYZ	 Ys*   !F G	!G		G$G;GGc                   ddl m} t        | ||      }|j                  r&t        j                  d|j                                |S t               r5t        j                  d|j                         |j                                |S t        j                  d|j                         |j                                 |dd|j                   d|j                          d	d
      )u  compute_episode_asset_readiness + fail-fast block 합쳐서 호출.

    missing 0 이면 card 반환. missing > 0 이면:
      - settings.allow_text_only_without_refs=True (or ENV) 시: logger.warning 후 card 반환 (옛 silent path 유사하지만 명시 opt-in 만)
      - 기본 (block): logger.error + AppError("image.assets_not_ready") raise.

    호출자: scene_image_service.generate_images / generate_single_scene_image.
    r   )AppErrorzAssetReadiness OK: %suT   AssetReadiness opt-in bypass (ALLOW_TEXT_ONLY_WITHOUT_REFS=true): %s — missing: %su(   AssetReadiness BLOCK: %s — missing: %szimage.assets_not_readyu/   참조 이미지 자산이 누락됐습니다 (u   건). 누락: u   . ref_image_gen / composite_image_gen 단계를 먼저 실행하세요. (디버깅용 우회: ALLOW_TEXT_ONLY_WITHOUT_REFS=true)i  )codemessagestatus_code)app.core.errorsr   r   rK   r$   inforQ   r-   r%   r_   errorrN   )r   r@   rA   r   r   s        r,   assert_episode_asset_readinessr     s     )*2z:FD}}+T\\^<bLLND002	
  LL2,,. %=d>P>P=Q R++-. /_`  r=   r`   )r   
OrmSessionr@   r0   rA   r0   ra   r?   )r@   r0   rA   r0   r   zList[Dict[str, Any]]ra   zList[Tuple[str, str]]) r:   
__future__r   loggingr   dataclassesr   r   r~   r   typingr   r	   r
   r   r   sqlalchemy.ormr   r   app.core.subject_stater   	getLoggerr7   r$   r"   r#   r-   r/   r?   r   r   r   r<   r=   r,   <module>r      s  " #  	 (  3 3 0 7			8	$ ,/D     /@ /@ /@dvvv v 	vrHHH ,H 	HV((( ( 	(r=   