
    -ǚj)                   6   d Z ddlmZ ddlZddlZddlZddlmZmZ ddl	m	Z	m
Z
 ddlmZ ddlmZmZ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 ddlmZ ddlmZ ddl m!Z! ddl"m#Z# ddl$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l0m1Z1 ddl2m3Z3m4Z4 ddl5m6Z6m7Z7m8Z8m9Z9m:Z: ddl;m<Z< ddl=m>Z>m?Z?m@Z@mAZA ddlBmCZC ddlDmEZEmFZF ddlGmHZH ddlImJZJ ddlKmLZL ddlMmNZN ddlMmOZP dd lMmQZR d!gZS ej                  eU      ZVd'd"ZWd'd#ZXd$ZY	 	 	 	 	 	 d(d%ZZ G d& d!      Z[y))u	  씬 이미지 생성 서비스 — ImageService에서 분리된 scene 전용.

Phase 3b.3 (2026-04-18): ImageService의 씬 이미지 관련 12개 메서드를 이관.
Phase 3b.5b (2026-04-18): ImageService wrapper 전부 제거. 호출자
(api/v1/images.py, core/steps/image_steps.py)는 본 서비스를 직접 인스턴스화.

W5 F22 Phase A (2026-04-22): **Facade freeze**.
이 모듈의 공개 API 9개는 본 파일의 facade 책임이다. 파라미터 이름/순서는
`tests/services/test_scene_image_service.py`의 9개 signature 테스트로 고정.
Phase B에서 내부 구현을 5개 모듈로 분리해도 아래 9개 메서드의 외부 계약은
바뀌지 않는다. (호출자: api/v1/images.py + core/steps/image_steps.py)

## 공개 API (freeze — 변경 시 signature 테스트 실패)

| 메서드 | 역할 | 주요 호출자 |
|--------|------|-------------|
| `get_image(image_id)` | ImageAsset 조회 | api/v1/images.py |
| `generate_images(episode_id, ip, mode)` | 전체 씬 이미지 생성 (background) | api/v1/images.py, image_steps.py |
| `generate_variations_only(episode_id)` | variation만 추가 생성 | api/v1/images.py |
| `generate_single_scene_image(still_id, custom_prompt, ip)` | 단일 still 이미지 재생성 | api/v1/images.py |
| `generate_scene_with_variations(still_id, ip)` | 단일 still + variation 전체 | api/v1/images.py |
| `recommend_variations(still_id, ip)` | LVM 기반 variation 추천 | api/v1/images.py |
| `regenerate_variation(image_id, angle_json, color_prompt, ip)` | **현재는 fal.ai angle 편집만 구현**; `color_prompt`는 reserved — images.py color route는 angle_json 없으면 400 반환. Phase B에서 color 구현 예정. | api/v1/images.py |
| `select_variant(still_id, variant, ip)` | variation 대표 이미지 선택 | api/v1/images.py |
| `select_original(still_id, image_id, ip)` | 원본 이미지 복원 선택 | api/v1/images.py |

## Phase B 분해 대상 (내부 모듈, 공개 API 유지)

1. `scene_variation_service` — variation 생성/선택 로직
2. `scene_validation_service` — T2I 검증 + retry 전략
3. `scene_persistence_service` — DB UPSERT + still 동기화
4. `scene_provenance_service` — PNG metadata + 이력 추적
5. `scene_image_service` — facade (얇은 래퍼, ≤300 LOC 목표)
    )annotationsN)ThreadPoolExecutoras_completed)datetimetimezone)Path)AnyDictListOptional)Session)settings)to_relative_image_path)AppErrorStaleUpstreamError)bind_current_budget)RefContractError)t)ActivityLogger)Episode
ImageAsset
SceneStill)ModerationError)	key_countget_next_key)ProgressTracker)ProvenanceRecorder)build_scene_dependency_graphtopological_sort_scenes)build_lineage_fieldsfill_missing_t2i_promptsget_latest_world_guideload_project_llm_configpopulate_t2i_prompts)SceneReferenceService)load_background_chain_bg_mapload_outdoor_direct_contextload_shot_dependency_mapload_shot_staging_map)build_semantic_contract)SceneGenerationCoordinatorlookup_render_prompt_card)ScenePersistenceService)SceneProvenanceService)SceneValidationService)SceneVariationService)select_best_variation_idx)apply_validation_overrideSceneImageServicec                 d    t        j                  t        j                        j	                         S N)r   nowr   utc	isoformat     V/Users/manta/Documents/Projects/TheRoad-I1/backend/app/services/scene_image_service.py_nowr<   d   s    <<%//11r:   c                 <    t        t        j                               S r5   )struuiduuid4r9   r:   r;   _new_idrA   h   s    tzz|r:   i   c                
   | D cg c]%  }t        |t              r|j                  d      s|' }}|ry| D cg c]%  }t        |t              r|j                  d      r|' }}d| d}|s| dS g }|dd D ]F  }|j                  dd      }|j                  d      xs d	dd
 }	|j                  |	r| d|	 n|       H |dz   dj	                  |      z   }
t        |
      t        kD  r|
dt        dz
   dz   }
|
S c c}w c c}w )u  variation result list 의 typed failure 들을 cp.failed 메시지로 직렬화.

    - 성공 dict 가 1개라도 있으면 (mark_failed 안 함) None 반환.
    - 모두 typed failure → reason 들을 join 한 메시지 반환 (운영자 가시화).
    - 빈 list → "unknown" generic msg (이전 silent path 회피).
    - 길이 ``_FAILURE_MSG_MAX`` cap (cp.failed 손상 방지).
    _failure_reasonNz#all variations failed (scene_index=)u(   ; unknown — no failure reason captured   UNKNOWN_failure_detail    : z; z |    z...)
isinstancedictgetappendjoinlen_FAILURE_MSG_MAX)var_results_listscene_indexr	successesfailuresheadpartsfreasondetailmsgs              r;   _summarize_failed_variationsr^   x   sB    $#aaquu->'? 	
#   ##aa155):#; 	
#   1Q?D?@@Ebq\()4%%)*0b$37fxr&*&A  +

5)
)C
3x""($q()E1J+s
   *C;*D c                  6   e Zd ZdZ	 	 	 	 	 	 	 	 ddZddZddZddZdddZddZ	ddddd		 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd
Z
	 	 d	 	 	 	 	 	 	 ddZ	 d	 	 	 	 	 ddZ	 d	 	 	 	 	 ddZ	 	 	 d	 	 	 	 	 	 	 	 	 ddZ	 d	 	 	 	 	 	 	 ddZ	 d	 	 	 	 	 	 	 d dZy)!r3   u  씬 이미지 생성 전담 서비스 (Phase 3b.3 · W5 F22 facade).

    본 클래스의 9개 public 메서드는 W5 F22 Phase A에서 facade로 freeze됐다.
    Phase B에서 내부 구현을 5개 모듈로 분리할 때도 이 9개의 외부 계약
    (파라미터 이름/순서/키워드 호출 가능성)은 변경하지 않는다.

    공개 API 및 Phase B 분해 계획은 모듈 docstring 참조.
    c                   || _         t        |      | _        || _        || _        t        |||      | _        t        ||      | _        t        ||      | _
        t        ||      | _        t        ||      | _        t        |||| j                  | j                  | j                  | j                  | j                  | j                  	      | _        y )N)	db
project_idactor_idactivity_loggerpersistence_svcreference_svcvalidation_svcvariation_svcprovenance_svc)_dbr   _logger_project_id	_actor_idr0   _variation_svcr/   _validation_svcr%   _reference_svcr-   _persistence_svcr.   _provenance_svcr+   _coord)selfra   rb   rc   s       r;   __init__zSceneImageService.__init__   s     %b)%! 4B
HM5b*E3B
C !8J G  6b*E0! LL 11--//--//

r:   c                
   | j                   j                  t              j                  t        j                  |k(  t        j
                  | j                  k(        j                         }|st        dt        d      d      |S )Nzepisode.not_found  codemessagestatus_code)
rj   queryr   filteridrb   rl   firstr   r   )rt   
episode_ideps      r;   _get_episodezSceneImageService._get_episode   sl    HHNN7#VGJJ*,g.@.@DDTDT.TUUW 	
 (-. 
 	r:   c                N    t        t        j                        | j                  z  S r5   )r   r   projects_dirrl   )rt   s    r;   _get_project_dirz"SceneImageService._get_project_dir   s    H))*T-=-===r:   c                
   | j                   j                  t              j                  t        j                  |k(  t        j
                  | j                  k(        j                         }|st        dt        d      d      |S )u>   image_id로 ImageAsset ORM 조회 (없으면 image.not_found).zimage.not_foundrw   rx   )
rj   r|   r   r}   r~   rb   rl   r   r   r   )rt   image_idimgs      r;   	get_imagezSceneImageService.get_image   ss    hhnnZ(//MMX%!!T%5%55
 %' 	 &+, 
 
r:   Nc                "   |}~ |dk7  rt        d|d       j                        }|j                  st        dt	        d      d      t
        j                  s$t               dk(  rt        dt	        d      d       j                  j                         dd	l
m}  | j                   j                         |j                  xs d
} j                         }|dz  z  }|dz  t!         j                   j                        }	ddlm}
 |dz  dz  z  } |
|d      } j                  j'                        } j                  j)                        \  }t+               }|dk(  r j                  j-                  |      }ddlm}m}  |t5        t
        dd            }d}|r	 ||      }t7         j                   j                        }t9              t9        |      z
  }|t9        t+        |      |z
        }|dz   }t;         j                  d j                        }|j=                  dd|        j                  j?                  |||||      |D ci c]  }|d   |
 c} j@                  jC                         \  }}t
        jD                  }|j=                  dd|        jF                  jI                  |      }|s-t        dt	        d      dk7  rt	        d      d      dd       jJ                  jM                         tO         j                  |||       tQ               i  jF                  jS                  |      tU        t
        jV                   j                        t5        t
        dd      dk(  rddl,m-}  |dJi d  j                  d! j                  d"d#d$|d%d&|d' jF                  d(d)d*d+|d, j                  d-|d.|	d/d0|d1|xs d}|j]                          |"t^        ja                  d2|t9                     yt^        ja                  d3|d4jc                  te        tf        |                   yti        t
        jV                   j                        tk        t
        jV                   j                        tm        t
        jV                   j                        dd5l7m8} dd6l9m:}   | j                        }!|!r8 | |!jw                  d7      xs i |!jw                  d8      xs
 t+                     ni }" j                  jy                  ||"xs d9      }#|#d:   }$|#d;   |#d<   }%|#d=   i t{              }&dd>l>m?}'  |' j                        dd?l7m@}( dd@l9mA})  |( j                        }* |)jw                  dA      xs i |*jw                  dB      xs i       }+|+ri dA|+ir |r	 ddClBmC}, D -ci c]%  }-|-jw                  dD      |-jw                  dE      f|-' }.}-jw                  dF      xs i D /ci c]'  }/|/|.jw                  |/      xs i jw                  d      ) }0}/ |, j                   j                  |0      }1|1r|1D /ch c]&  }/|.jw                  |/      xs i jw                  d      ( }2}/|2j                  d       |2r=|D 3ch c]	  }3|3|2vs|3 }}3t^        ja                  dGt9        |2      t        |1             rt              D 5-ci c](  \  }5}-|-jw                  dD      |-jw                  dE      f|5* }6}5}-jw                  dF      xs i j                         D ]  \  }7}8|6jw                  |7      }9|6jw                  t        |8jw                  dI      xs dJ            }:|9G|:J|9|:k7  sP|&|9   j                  |:       t^        ja                  dK|9|7|:|8jw                  dI              ddLl7mL};  |; j                        }<|<rt              D 5-ci c](  \  }5}-|-jw                  dD      |-jw                  dE      f|5* }=}5}-|<j                         D ]  \  }>}?|?jw                  dM      dNk7  r|?jw                  dO      }@t        |@t        t        f      rt9        @      dPk(  sQ|=jw                  |>      }A|=jw                  @d   |@d   f      }B|A~BABk7  s|&A   j                  B       t^        ja                  dQ|A|>|B@d   |@d   f        t        t5        t
        dRdS            r ddTlPmQ}C ddUlRmS}D  |C j                        }E |D|Exs i jw                  dV      xs i       }F|Frt              D 5-ci c](  \  }5}-|-jw                  dD      |-jw                  dE      f|5* }G}5}-Fj                         D ]o  \  }H}IGjw                  |H      }J|Gjw                  t        |IdO               }K|J7K:JKk7  s@|&J   j                  K       t^        ja                  dW|JH|KIdO          q d}L|ddXlmT}MmU}NmV}OmW}P  |N||&      \  }Q}R |O |Pt
        jV                   j                         |M|||RdYZ             t+        |Q      }Lt9        |L|z
        }|dz   }t^        ja                  d[t        |      t9        |      t9        |R      t9        |L      |       t        t9              |&      }Sdd\lYmZ}T  |T j                   j                  |L]	      t^        ja                  d^t9              t9        |S      |       d}U j@                  j                         } jF                  j                  |_      |xs i j                         D ]6  }V|Vxs i jw                  d`      }W|Ws jF                  j                  W       8 ddal_m`}X ddblamb}Ymc}Z  |Z|X j                        ~t        S      D ]  \  }[}\|\D ]cg c]  }]|]   jw                  d      |vs] }\}]L%\D ]cg c]  }]|]   jw                  d      Lv r] }\}]\sW|j=                  dcU dd| |Udz   |       d}|}~ fde}^t        |t9        \            }_g }`t        |_f      5 }at        ^      }b\D ]ci c]  }]aj                  b|]      |] }c}]t        |c      D ].  }dc|d   }]	 dj                         }e`j                  |e       Udz  }U0 	 ddd       t        `dk l      D ]~  }e|e\  }]}g}h}i|]   }jt        |g|]      }k|k@jjw                  dd      }f|j                  |fk       t^        j                  dm]|fddn |k       ^gD lcg c]%  }lt        |lt              rljw                  do      sl' }g}lhD cg c]  }|d   	 }m}t         j                   j                  dp|mdqr      }n j                  j                  g|n      \  }o}p j                  j                  |gj]|$        j@                  j                  |g|o|j|]s      }qt        |g|jjw                  dtd            \  }r}s|sr5t^        ja                  du]rdz   t9        g      qjjw                  d             t        grt9        o      q      \  }r}t|trt\  }u}vt^        ja                  dv]|u|v        j@                  j                  rogj]||%|$w
      }w|j=                  dcU dd| dx|jjw                  dtd       |Udz   |       |wj                         swj                         }xiD ]	  }yxjf|y<    jjw                  dD      }zirM|"sQt        zt              sc|"jw                  z      }{|{sxxjf{<     |j]                           j                  j                   j                  dydz j                  t9        |      t9              d{||       yc c}w c c}-w c c}/w c c}/w c c}3w # t        $ r!}4t^        j                  dH|4       Y d}4~4	Md}4~4ww xY wc c}-}5w c c}-}5w c c}-}5w c c}]w c c}]w c c}]w # t        $ r  t        $ r}4 Y|4      r,t^        j                  dg]t5        |4dhtg        |4                    t^        j                  di]|4       |]t9              k  r]   jw                  dd      nd}f|j                  |ftg        |4      ddj        Udz  }UY d}4~4;d}4~4ww xY w# 1 sw Y   xY wc c}lw c c}w )~u  Full image generation pipeline for an episode.

        mode: "resume" — 이미 씬 이미지 있는 still_id 스킵 (기본).
        과거 'full' 모드 (전체 삭제 후 재생성) 는 feedback_never_delete_images
        규칙 위반으로 제거됨. 강제 재생성은 SceneImagePipelineStep mode='force'
        를 사용 (UPDATE is_primary=0 + checkpoint 재시작 — DB row/PNG 보존).
        resumez(SceneImageService.generate_images: mode=z unsupported. Only 'resume' is allowed (feedback_never_delete_images). Use SceneImagePipelineStep mode='force' for non-destructive regeneration.zanalysis.no_text  rx   r   image.gemini_key_missingassert_episode_asset_readinesskoimagesscene)ImageCheckpointManagercheckpoints)parse_target_scenesrequested_still_idsscene_image_target_scenesrH   N   image_generationu   세계관 가이드 생성 중)r   episodeentitiesstillsmode
provenancelanguager~   u"   기존 참조 이미지 로드 중zimage.no_reference_imagesuW   참조 이미지가 없습니다. 엔티티 참조 이미지를 먼저 생성하세요.)out_asset_id_mapstill_recipe_modeoffv1)run_still_recipe_generationra   rb   r   r   
stills_ormentity_lookupref_image_maprf   scene_ref_image_mapscene_ref_asset_id_mapstaging_mapscene_cpre   progressproject_config	scene_diralready_done_stillstarget_scenesu5   still_recipe v1: %d/%d 샷 생성 완료 (episode=%s)uL   still_recipe v1(표적): %d 샷 생성 완료 (episode=%s, target_scenes=%s),)load_outdoor_prev_frame_context)primary_location_uuid_by_sceneprimary_location_by_sceneoutdoor_loc_sids)fallback_location_uuid_by_scenescene_paths_by_indexscene_paths_by_index_by_idscene_results_by_indexlocation_scene_historyload_zoom_continuity_context load_outdoor_site_layout_contextmerge_prompt_overridessource_overridesprompt_overrides)stale_zoom_targetsrT   
shot_indexzoom_targetsuP   zoom_continuity: bbox 모델이 바뀐 대상 %d개를 done 에서 뺀다 — %suE   zoom_continuity: 낡은 대상 판별 실패 — 그대로 간다: %rsourcer9   uW   zoom_continuity(batch): dep edge 추가 — still[%d](zoom %s) → still[%d](source %s))load_composition_guide_contextr   continuity_anchoranchor_source   u`   composition_continuity(batch): dep edge 추가 — still[%d](anchor %s) → still[%d](source %s)$immobilized_prev_frame_chain_enabledF)'load_immobilized_subject_anchor_context)!build_immobilized_prev_frame_planmembers_by_groupud   immobilized_prev_frame(batch): dep edge 추가 — still[%d](target %s) → still[%d](env anchor %s))build_scope_auditclosure_over_index_graphsave_scope_auditscope_audit_pathbatch)r   requested_idsdependency_added_ids	path_kindu[   scene_image 표적 scope(batch): scenes=%s requested=%d dep_added=%d effective=%d to_gen=%d)load_indoor_shared_pose_guides)	rb   r   r   r   background_chain_bg_mapzoom_ctxra   already_done_still_idsexecution_allowlist_still_idsz=Scene generation: %d scenes in %d batches (max_concurrent=%d))r   bg_id)SessionLocal)is_abortCancellationTokenu   씬 이미지 /c                    j                  d|         j                  j                  | 
	      S )Nu   씬 이미지 si=)where)sir   r   r   "scene_primary_asset_id_by_still_idr   r   r   r   r   dep_detail_mapgemini_client	sanitizer	validatorr   cached_style_contextcached_entity_text_mapworld_guider   r   indoor_pose_ctxoutdoor_direct_ctx)raise_if_cancelledrs   _generate_scene_in_loop)r   _cached_entity_text_map_cached_style_context_cancel_token_indoor_pose_ctx	_zoom_ctxr   r   r   r   r   r   r   r   r   r   r   r   r   rt   r   r   r   r   s    r;   _generate_one_scenez>SceneImageService.generate_images.<locals>._generate_one_scene  s|     009J2$7O0P{{::!"//I7Y+A +(;+A,C#1"/''')>+B +)&$4'9- ;  r:   )max_workersu)   씬 배치 중단 (scene_index=%d) — %srz   z1Scene generation failed: scene_index=%d, error=%si  c                    | d   S )Nr   r9   )rU   s    r;   <lambda>z3SceneImageService.generate_images.<locals>.<lambda>T  s    adr:   )keyzScene %d still %s: %s   rC   scene_image_generator	cinematic)ref_entity_idsprompt_type)rS   saved_asset_ids
still_datar   r   r   r   
beat_titleuD   Scene %d: final best selection — image %d/%d (fal=%s) for still %szLScene %d: overriding selection with higher-scored alternative (score %s->%s))
best_idxr   rS   r   r   r   r   r   r   r   rJ   zepisode.generate_imagesr   )reference_loadedscene_count)rc   actionresource_typeresource_idrb   r\   
ip_address)r   intreturntuple)~
ValueErrorr   fulltextr   r   r   gemini_api_keygemini_key_countrq   validate_episode_readyapp.core.asset_readinessr   rj   rl   r   r   r#   app.modules.image_checkpointr   load_episode_entity_dictsload_episode_still_dictssetscan_completed_scene_stills&app.modules.pipeline.scene_image_scoper   r   getattrr   rQ   r   updateresolve_world_guiders   _init_scene_gen_clientsmax_concurrent_image_genrp   load_entity_reference_imagesro   create_validatorr$   r!   build_scene_ref_image_mapr)   r   !app.services.still_recipe_servicer   completeloggerinforP   mapr>   r(   r&   r'   'app.core.steps.outdoor_site_layout_stepr   -app.modules.pipeline.outdoor_site_layout_planr   rN   build_resume_stater   *app.core.steps.zoom_continuity_anchor_stepr   r   r   +app.services.zoom_continuity_render_servicer   discardsorted	Exceptionwarning	enumerateitemsr  addr   rL   listbool,app.core.steps.visual_continuity_anchor_stepr   2app.modules.pipeline.visual_continuity_anchor_planr   r   r   r   r   r   /app.core.steps.indoor_shared_pose_guide_contextr   _get_style_contextbuild_entity_text_mapvaluesresolve_chain_bg_asset_idapp.core.databaser   app.core.run_controlr   r   minr   r   submitr   resultrO   r   errormark_failedr^   rM   r    save_scene_variationsrr    compute_and_embed_scene_metadata_run_fal_angle_pipeline_select_best_variation_idx_apply_validation_override_finalize_and_track_primaryexists
read_bytesr	  rk   logrm   )rt   r   ipr   r   r   r   project_dir
images_dir_plcr   cp_dirr   r   r   r   r   r   r   target_requestedr   stills_to_gentotal_itemsr   eopenai_clienttrackermax_concurrentr   r   	generatedr   r   _opf_ctx_main_opf_uuid_by_scene_main_rsr   r   
scene_depsr   r   r   	_site_ctx_merged_overridesr   s__idxk_sid_by_key_stale_dropiexci__zc_key_to_idx_zkey_zt_zi_src_ir   _cg_ctx_cg_key_to_idx_ckey_centry_src_ti_sir   r   _ipf_ctx	_ipf_plan_ipf_key_to_idx_tkey_tentry_ti2_si2target_effectiver   r   r   r   _eff_ids
_dep_addedscene_batchesr   scenes_generated	_bg_entry_warm_bg_id_SessionLocalr   r   	batch_idxscene_batchr   r   workersbatch_resultsexecutor_submit_generatefuturesfuturer>  still_id_failedrS   visible_entitiescurrent_location_idsr   _all_failed_msgrU   scene_ref_idsscene_lineager   saved_pathsfal_generatedr  _best_was_selected	_override
_old_score
_new_scoreprimary_pathscene_bytesloc_id_opf_si_opf_ur   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   s   ``                                                                                                                          @@@@@@@@@@@@@@@@@@@@@r;   generate_imagesz!SceneImageService.generate_images   s    8:4( C\ \ 
 ##J/',-  &&+;+=+B/45  	44Z@ 	L&txx1A1A:N##+t++- 8+j8
(	 'txx1A1AB 	H},x7*D)&': ((BB:N!22KKJW
 $'58"&"7"7"S"SH#	
 ,H92>@0426=I ($2B2BC
Fc*=&>>'  $4 58K KLM#a'"488Z9KTM]M]^ 	8![I++??!! @ 
 .66X4!X6 <@;;;^;^;`8}i!::<aM ++HHR 0:;<W:X\w:w56  ~W  ((99;	 	TXXvz8]S 	!(
 24"11KK=;Q L 

 ,H,A,A4CSCSU_` 80%8D@ 4 88++ & 	
 & , , #11 %8 (> ( " !% 5 5 "  $  $!" %8#* ,3t+I. 'Ks6{J  (zHHSm45	 1(2G2GIYIY[ef">!!4#3#3Z#
 9!!4#3#3Z
	
	
 88H8H*U
 	 #A9:@b01:SU#
 !#	 	 
 ##66',C,Kt 7 
 144J0K69:V6W"<?@X<Y367O3P >@* 2&-H
	
 11A1A:N		
	
 5T5E5EzR	2MM,-3MM,-3
 L9L&8:KLI ,
 #)*"(B .|0DErI"(  * (mmN;ArAA )r..t44A   ,HHd..=?EG?E!!*//5v  GMM$'':/N':!aunA': , /N89<UVF^U  (//FB &|(<=rA/    )}}^<BIIK
s$((/'++E#''(2C2Ir,JK?v'9cVmsO''/KK/03UFCGGHDU L 	
 11A1A:N (//FB &|(<=rA/   #*--/w;;v&*=={{?3"4$7CIN$((/$(($q'47);<?s3#:sO'',KK?UC$q'47); #2* "H%PQ ?  *.H9R$$%78>B@I #,F"3#"3B VVM*BFF<,@A2E"3   # '0oo&7NE7*..u5D*..uW_5M/NOD'D,<"4(,,T2G %w/G '8 +/'  $<(*$6 Hj ))4+;+;ZI!"/"2)3%		  #8}   03F FGM'!+KKK6]#S)9%:J%5!6	 0FZH	
 :''Jv#=T488#6 +;	
 	KK]+^	

  !% > > @ #'"5"5"K"K #L #- 27R??AI$?//8K##==j+V B 	DD)4++Z
 '0&>"I{(3g"vbz~~d7KSf7f2Kg  +!,!,2bz~~d+/??    OO !1 2!M?C 1$k# # # #L .#k*:;G)+M#8H $77J#K  *) OO$4b92=)   +73F B!.!'%,,V4(A-( 4 9` !NCCOUL$&68L#BZ
 #??OQS"T".&0nnT2&>O((/JLL/OBQ/   0$/!!!T*1559J3K / ! $
 3C C2BQ42B C 4TXXt?O?O+#0 +! 04/D/D/Z/Z$m0,
 $$EE$j&"(*D !% C C%5$3)!') !D ! 0J$jnn\2&F0,, &KK^HqL#.>*?%z~~d'; 'A$hO0Dm'#) -6*J
KKfJ
  ${{FF%$3%5)%+A)=/I7Y  G   $%5$6abXdfhIiHjk$q(+  &&("."9"9";K"6:Ez9R.v6 #7
 )nn];G05L *7C 8!8!<!<W!E!>I:=V26:e DY '?@ 	^^,#"''$'$6"6{  	 	
K 7d*G/N
  [ 2J#z hv .  $ . $C="NN K "GCCH$E "OQSUX GI3v;FV&*..r*B\^ ,,_c#htnM(A-(+./ 98D$
 !Ds  :A@7
AA *A@< AA ,AA&AA ++AAAA /	AA9AA=+AA 8-AA=-AB*-AB	0ABABABAE'ABAE&AB=AE8*AE(AE#@<AA A	AA:AAA5A5AA:BAEBAE	B0BAE		EAEE	AE	EAEEAE	c                8    | j                   j                  |      S )u   기존 원본 이미지 기반 A/B 변형 추천 + 생성 (별도 실행).

        W5 F22 Phase B.4: 구현이 scene_variation_service.SceneVariationService로 이관됨.
        )rn   generate_variations_only)rt   r   s     r;   r  z*SceneImageService.generate_variations_only  s    
 "";;JGGr:   )rT   r   semantic_constraintsattached_payloadc                  ddl m} ddlm} 	  ||||||r|ndd| j                  |d||	|
d|      }t               dd|||d   ||j                  dt        j                        dddt        j                  |j                  di       d      d| ||      ni dt               iS # t
        $ r }t        dd	|j                   d
      d}~ww xY w)u  Generate scene image via pipeline + build scene_result dict.

        W5 F22 Phase B.21.4 (2026-04-22): generate_single_scene_image의 두 경로
        (custom_prompt / _build_final_scene_prompt)가 공유하던 try/except +
        scene_result dict 블록을 통합. ModerationError는 AppError로 re-raise.

        prompt_used만 path별로 다름 (custom_prompt 원문 vs _full_prompt 빌드 결과).

        Phase 4 iter 7 follow-up I1: scene_index / shot_index 인자 추가 — targeted
        mode 의 _decide_scene_lvm 가 trace_meta.scene_index/shot_index 로 매칭
        키 생성. 이 인자가 없으면 single-scene/UI 재생성 경로가 항상
        targeted_no_meta 로 silent skip (Codex+Claude review I1 합의).
        r   )generate_and_validate_scene)_attached_lineage_fieldsNsingle_scene_image_gen)rb   r   operation_typestill_idrT   r   )r   
t2i_promptr  
output_dirreference_imagesprevious_scene_bytes
trace_metar  zimage.generation_blockedzImage generation blocked: r   rx   r   	file_pathgeneration_modelrV  
validationFensure_ascii)r~   
asset_type	entity_idr  r   r  prompt_usedr  widthheightstatusreview_notes
created_at))app.modules.pipeline.scene_image_pipeliner  )app.services.scene_generation_coordinatorr  rl   r   r   block_reasonrA   rN   r   gemini_image_modeljsondumpsr<   )rt   r   r  r  r  r  r  r   r  rT   r   r  r  r  _single_attached_lineage_fieldspipe_resultrd  s                    r;   '_single_scene_generate_and_build_resultz9SceneImageService._single_scene_generate_and_build_result  s   : 	Z	
	5+%%%5E!14%) #'"2"2",&> (#.", &:#K4 )! $$[1& +0BHD_D_ `T! JJ{|R'HW\]
 EUD`./?@fh
 $&
 	
  	/4S5E5E4FG 	s   %B( (	C1CCc                z   | j                   j                  |      }|j                  }| j                         }|dz  |z  dz  }ddlm}  || j                  | j                  |       |j                  |j                  |j                  |j                  |j                  |j                  xs d|j                  xs d|xs |j                  xs d|j                   xs d|j"                  xs d|j$                  xs dd}	t'        | j                  | j                  |      }
| j(                  j+                  |j$                        }| j(                  j-                  |      }| j                  j/                  t0              j3                  t0        j                  |k(        j5                         }|r|j6                  nd	}| j8                  j;                         \  }}}}t=        t>        j@                  | j                  |      }|j                  '|j                  |j                   d|j                   nd
}|r|r|jC                  |      nd
}	 tE        | j                  ||j                  |j                        }|||	d<   tM        |||      }|jN                  }|rtQ        t>        j@                  | j                  |      }|r|jC                  |      nd
}| j(                  jS                  |||      }| jU                  |||	jC                  dd      ||||||j                  |j                  |      }naddl+m,}  || j                  |      }|j                  |j                  f} ddl-m.}! ddl/m0}"  |!| j                  |      }# |"|jC                  d      xs i |#jC                  d      xs i       }$d
}%|$jC                  |       }&|&rd|&d   v r|&d   d   }%tH        jc                  d|j                  |j                  |&jC                  d      xs i jC                  d      xs |&jC                  d      |&jC                  d      |&jC                  d      xs i jC                  di       jC                  d             |jC                  d       xs i jC                  |       }'d
}|'rPdd!l2m3}( dd"l4m5})m6}*  |(| j                  ||||j                  |j                  #      \  }+},	  |*| j                  | j                  ||j                  |j                  |'||+|,d$|d%|&      \  }-}.}/|to                d'z  }0|0jp                  js                  d(d()       |0ju                  |-       to               dd
||tw        ty        |0            |.t>        jz                  d
d
d*t}        j~                  d+|/id,-      t               d.}tH        jc                  d/|j                  |j                  |'jC                  d             || j8                  j                  ||||1      S | j8                  j                  |||	|||%2      \  }2}}3}4| jU                  ||2|	jC                  dd      |||||2|j                  |j                  ||43      }|	jC                  d4      r|	d4   |d5<   | j8                  j                  ||||1      S # tF        $ r:}tH        jK                  d||j                  |j                  |       d
}Y d
}~{d
}~ww xY w# |)$ r7}1tH        jK                  d0|j                  |j                  |1       Y d
}1~1=d
}1~1ww xY w)6z*Generate a single scene image for a still.r   r   r   r   rH   z{}z[])r~   still_indexrT   r   dependent_scene_idscreenplay_scene_headingr  still_frame_promptcamera_jsonlighting_jsonvisible_entities_jsonr   N_)rb   r   rT   r   zQPatch B-min: render_prompt_card lookup failed for still %s (scene=%s shot=%s): %srender_prompt_card)shot_stagingr  r  )chain_bg_entryr  )r   r  r  r  r  r  r   r  rT   r   r  r   r   r   r   r   reviseduS   scene prompt override 적용: S%ssh%s (prompt_source=%s, group=%s, revised_hash=%s)prompt_source_by_indexprompt_sourcegroup_idr   0revised_prompt_hashr   )build_zoom_fill_prop_refs)ZoomContinuityRenderErrorgenerate_continuity_crop_png)rT   r   r  )r  r  )ra   rb   r   rT   r   zoom_targetr   	prop_refsprop_refs_have_anchor_contractr  r  z.pngTparentsexist_okrV  zoom_continuityFr  )r~   r  r  r  r   r  r  r  r  r  r  r  r  u8   zoom_continuity: S%ssh%s crop/i2i-fill 성공 (group=%s)uA   zoom_continuity: S%ssh%s crop 경로 fallback → 기존 T2I (%s))scene_resultr  r  rJ  )stillr   r   r  r   var_t2i_override)r   r  r  r  r  r  r   r  rT   r   r  r  _prev_frame_chain_diagprev_frame_chain)Crq   fetch_still_for_generationr   r   r  r   rj   rl   r~   r  rT   r   r  r  r  r  r  r  r  r"   rp   get_visible_entities%get_ref_image_map_excluding_locationsr|   r   r}   r   r   rs   r  r)   r   r   rN   r,   r   r"  r-  r*   sanitizer_constraintsr&   build_custom_labeled_refsr  r(  r   r%  r   r&  r   r#  r3  r  r)  r  r  rA   parentmkdirwrite_bytesr   r>   r  r  r  r<   _finalize_single_scene#_build_single_scene_prompt_and_refs)5rt   r  custom_promptrJ  r  r   rK  r   r   r   r   r  r   r   r   r   rS  r   rT  _stg_map_stg_key_stg_rpc_rpc_exc_semantic_contract_semantic_constraints_bg_mapr~  labeled_refsr  r   r   _zc_keyr   r   r[  r\  _var_t2i_override_zc_override
_zc_targetr  r  r  
_prop_refs_prop_anchor_applied_png_fill_prompt_zc_diag_out_zc_exc_full_prompt_attached_meta_attached_payloads5                                                        r;   generate_single_scene_imagez-SceneImageService.generate_single_scene_image,  sY    %%@@J%%
++-(*Z7'A	
 	L&txx1A1A:N (( ,, ,,**"'":":(-(F(F(L"**0b"/"Q53K3K"Qr ,,4"008D%*%@%@%HD

 -TXXt7G7GT  ..CCED_D_` ++QQRbcXX^^G$++GJJ*,DEKKM"$2;;$ <@;;;^;^;`8}i ))>)>@P@PR\]   ,1A1A1M   !5#3#3"45 	
 *2hx||H%D	,++%!-- ++	D" /3J+,4#-

 !3 H H 3%%t'7'7G 2:H-tI..HH -	 I L
  GG+(%>>,;$!-!%)!-- ++%: H L" 5T5E5EzRI((%*:*:;G 99I9I:VI 6017R017R! !%,009L\)%< <$0$;A$>!D%%u'7'7!%%&>?E2JJ1M 9#''8 $$Z0!%%l39r>>sBGKKLab $--7=2BB7KJL 4M$$j$m % 1 1$//	40
0/3O88#'#3#3#-$)$5$5#(#3#3$.&3",7K /G(0$ "*%40D,( %')D'99DKK%%dT%B$$T*%i&-%)$,&0%;CI%F'3,4,G,G!%"-(,

.9)O&*f$L KKR))5+;+;Z^^J=W '{{99!-%5%	 :   MQKKL{L{%%!1+!2 M| MIL,8I  GG+'%>>,;$!-!%(!-- ++%:!2 H L  ~~673=>V3W/0 {{11%-	 2 
 	
U   	NN)%++U-=-=x
 D	r 1 NN[))5+;+;W s1   -Z8 &D[> 8	[;/[66[;>\:,\55\:c                <    | j                   j                  ||      S )u   LLM에게 A/B 변형 추천 받기.

        W5 F22 Phase B.3: 구현이 scene_variation_service.SceneVariationService로 이관됨.
        rJ  )rn   recommend_variations)rt   r  rJ  s      r;   r  z&SceneImageService.recommend_variationsI  s      ""77R7HHr:   c           
     t   t         j                  s$t               dk(  rt        dt	        d      d      | j
                  j                  t              j                  t        j                  |k(  t        j                  | j                  k(        j                         }|st        dt	        d      d      |j                  }ddlm}  || j
                  | j                  |       | j!                         }|dz  |z  d	z  }|j#                  d
d
       | j%                  ||      }|d   }	| j&                  j)                  |	       t+        |d         }
|
j-                         s|dd|j.                  xs ddS |
j1                         }ddlm}  |t7               t         j8                        }t;        | j
                  | j                  d      }| j<                  j?                  d||||	|||      }| j<                  j?                  d||||	|||      }| j<                  jA                  ||||	|||      S )u  원본 + A변형 + B변형 생성.

        1. Generate original (existing scene image generation)
        2. If variation_a has angle/color -> i2i from original
        3. If variation_b has angle/color -> i2i from original
        4. Set recommended as PDF default
        r   r   r   rx   zstill.not_foundrw   r   r   r   Tr  r  r~   r  Noriginal)r  	variant_a	variant_brecommended)GeminiI2IEditor)api_keymodelgemini_i2i_editora)slotr  
i2i_editororiginal_bytesoriginal_idr   r   lineageb)r  r  original_resultr  variant_a_resultvariant_b_resultrJ  )!r   r  r  r   r   rj   r|   r   r}   r~   rb   rl   r   r   r  r   r   r  r  rq   mark_asset_as_originalr   rG  recommended_variantrH  app.modules.gemini_i2i_editorr  r   r  r    rs   _maybe_generate_variation_slot_finalize_scene_with_variations)rt   r  rJ  r  r   r   rK  r   r  r  original_pathr  r  r  r  r  r  s                    r;   generate_scene_with_variationsz0SceneImageService.generate_scene_with_variationsU  sB    &&+;+=+B/45  HHNN:&VJMMX-z/D/DHXHX/XYUW 	
 &+,  %%

 	L&txx1A1A:N++-(*Z7'A	t4 ::8:K%d+ 	44[A _[9:##%+!!$88FJ	  '113A$ N--


 'txx1A1ACVW  ;;EEEj){J F 

  ;;EEEj){J F 
 {{::+#-- ; 
 	
r:   c                @    | j                   j                  ||||      S )u  원본 기반 fal.ai i2i로 변형 재생성.

        W5 F22 Phase B.2: 구현이 scene_variation_service.SceneVariationService로 이관됨.
        color_prompt는 reserved (Phase A 기준 angle-only). 자세한 계약은 모듈 docstring 참조.
        )
angle_jsoncolor_promptrJ  )rn   regenerate_variation)rt   r   r"  r#  rJ  s        r;   r$  z&SceneImageService.regenerate_variation  s.     ""77!%	 8 
 	
r:   c                >    | j                   j                  |||      S )u   PDF 대표 이미지 선택 (original/A/B).

        W5 F22 Phase B.1: 구현이 scene_variation_service.SceneVariationService로 이관됨.
        facade 계약(still_id/variant/ip signature) 그대로 유지.
        r  )rn   select_variant)rt   r  variantrJ  s       r;   r&  z SceneImageService.select_variant  s"     ""11(G1KKr:   c                >    | j                   j                  |||      S )u   원본 이미지 선택 (여러 장 중).

        W5 F22 Phase B.1: 구현이 scene_variation_service.SceneVariationService로 이관됨.
        facade 계약(still_id/image_id/ip signature) 그대로 유지.
        r  )rn   select_original)rt   r  r   rJ  s       r;   r)  z!SceneImageService.select_original  s"     ""228X"2MMr:   )ra   
OrmSessionrb   r>   rc   r>   r
  None)r   r>   r
  r   )r
  r   )r   r>   r
  r   )Nr   )r   r>   rJ  Optional[str]r   r>   r
  r+  )r   r>   r
  r+  )r   r	   r  r>   r  r>   r  r   r  zOptional[List]r  r>   r   r>   r  r>   rT   Optional[int]r   r-  r  zOptional[Dict[str, Any]]r  r	   r
  Dict[str, Any])NN)r  r>   r  r,  rJ  r,  r
  r.  r5   )r  r>   rJ  r,  r
  r.  )NNN)
r   r>   r"  r,  r#  r,  rJ  r,  r
  r.  )r  r>   r'  r>   rJ  r,  r
  r.  )r  r>   r   r>   rJ  r,  r
  r.  )__name__
__module____qualname____doc__ru   r   r   r   r  r  r  r  r  r   r$  r&  r)  r9   r:   r;   r3   r3      s+   "
"
 "
 	"

 
"
P>,Z
zH$ &*$(9= $K
 K
 	K

 K
 K
 )K
 K
 K
 K
 #K
 "K
 7K
 K
 
K
` (, 	V
V
 %V
 	V

 
V
@	 !	I	I 	I 
		I ![
[
 [
 
	[
B %)&* 

 "
 $	

 
 

0 !	LL L 	L
 
L" !	NN N 	N
 
Nr:   )r
  r>   )rS   zList[Dict[str, Any]]rT   r	  r
  r,  )\r2  
__future__r   r  loggingr?   concurrent.futuresr   r   r   r   pathlibr   typingr	   r
   r   r   sqlalchemy.ormr   r*  app.core.configr   app.core.file_pathsr   app.core.errorsr   r   app.core.image_call_budgetr   app.core.ref_contract_validatorr   app.i18n.loaderr   app.logging.activity_loggerr   app.models.projectr   r   r   #app.modules.llm.gemini_image_clientr   app.modules.llm.gemini_key_poolr   r  r   app.modules.progress_trackerr   app.modules.provenancer   app.modules.entity_dependencyr   r   "app.services.image_service_helpersr    r!   r"   r#   r$   $app.services.scene_reference_servicer%   %app.services.scene_checkpoint_loadersr&   r'   r(   r)   $app.modules.semantic_contract_routerr*   r  r+   r,   &app.services.scene_persistence_servicer-   %app.services.scene_provenance_servicer.   %app.services.scene_validation_servicer/   $app.services.scene_variation_servicer0   r1   rD  r2   rE  __all__	getLoggerr/  r"  r<   rA   rR   r^   r3   r9   r:   r;   <module>rP     s   !D #    ? '  , , 0 $ 6 8 : <  6 
 @ W 8 5  G  I K H H F h h
			8	$2   *    FDN DNr:   