
    ]_jw                    @   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mZmZmZmZmZ dd	lmZ  ej6                  e      Zd
Zed   ZddZ  e!h d      Z"	 	 	 	 	 	 ddZ#	 	 	 	 	 	 ddZ$	 	 	 	 ddZ%	 	 	 	 ddZ& G d de      Z'y)ua  BackgroundPromptStep — Phase 7 Step 5 / Phase 8 v2.

각 master plan의 backgrounds[]에 대해 LLM 1회씩 t2i prompt 생성.
depends_on_bg DAG로 level 병렬화. (이 step은 prompt만 — PNG는 T14 background_render).

Phase 8 v2: ``floor_plan_prompt`` 체크포인트(``data.floor_plans[fp_id]``)에서
``numbered_elements`` + ``camera_recommendations`` 를 로드해 bg_id 기준 flat
매핑을 만들고, ``build_bg_user_prompt`` 에 inject한다. 매핑 키는 master_plan
의 ``backgrounds[].depends_on_fp[0]`` 으로 fp_id 를 결정 (master_plan
invariant 4: 같은 sub_location 은 같은 fp).
    )annotationsN)ThreadPoolExecutoras_completed)Path)AnyDictListOptionalTuple)
StepRunner)PROMPT_VERSION_MAPBackgroundPromptErrorBackgroundPromptOverlayErrorbuild_bg_user_promptresolve_prompt_versionrun_background_prompt)"resolve_projection_plate_injection   6c                 8    ddl m}  t        | j                        S )u7  settings.background_prompt_version selector 를 on-disk pack name 으로 풀이.

    default selector "6" → ``PROMPT_VERSION_MAP["6"]`` 그대로 → 기존 v6 hash
    byte-identical. opt-in "7" → ``PROMPT_VERSION_MAP["7"]``. floor_plan_prompt
    의 ``_resolved_prompt_version()`` 패턴과 동일.
    r   settings)app.core.configr   r   background_prompt_versionr   s    [/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/steps/background_prompt_step.py_resolved_prompt_versionr   1   s     )!("D"DEE    >   789101112c                     | t         v r|sy| dfS )u  per-bg effective prompt version 결정 (overlay-missing fallback).

    v7+ pack 은 floor_plan_overlay_payload 입력을 전제로 한다. floor plan 이
    없는 bg(외부/exterior 등)는 overlay entry 가 없어 v7+ 가 fail-closed 된다.
    이때 fail-open 이 아니라 **non-overlay v6 pack 으로 explicit degrade** 하고
    진단 사유를 남긴다 (build_bg_user_prompt 와 run_background_prompt 양쪽에
    동일 effective version 을 넘겨 pack mismatch 0).

    - requested ∈ overlay-required + overlay 존재 → (requested, None) [기존 path 불변]
    - requested ∈ overlay-required + overlay 부재 → ("6", fallback_reason)
    - requested ∉ overlay-required (v6 등) → (requested, None) [byte-identical]
    )r   4overlay_payload_missing_for_requested_overlay_promptN)_OVERLAY_REQUIRED_VERSIONS)requested_versionoverlay_presents     r   _resolve_effective_bg_versionr)   C   s     66Jd""r   c                   | xs i j                  di       xs i j                  di       xs i }|xs i j                  di       xs i j                  di       xs i }i }|j                         D ]m  \  }}|xs i j                  d      xs i }|j                  dg       xs g D ]6  }|j                  d      xs g }	|j                  d      }
|
s,|	s/|	d   ||
<   8 o i }i }|j                         D ]p  \  }}|j                  |      xs i }t        |j                  d	      xs g       ||<   |j                  d
      xs g D ]  }|j                  d      |k(  s|||<    p r ||fS )u  ``bg_id`` → (numbered_elements list, camera_recommendation entry) flat 매핑.

    - ``numbered_elements`` 는 fp 단위로 ``fp_prompt_cp.data.floor_plans[fp_id]``
      에 저장되어 있고, 각 bg 는 ``depends_on_fp[0]`` 으로 fp_id 를 가리킨다
      (master_plan invariant 4: 같은 sub_location 은 같은 fp 공유).
    - ``camera_recommendation`` 은 fp 안 list 에서 ``bg_id`` 로 일치하는 단일
      entry 를 lookup. 없으면 entry 자체를 dict 에 넣지 않는다 (caller 가
      ``lookup.get(bg_id)`` 로 None 처리).
    - cp 가 None / data 누락 / fp 미존재 등 어떤 결손에도 안전하게 빈 매핑
      반환 (KeyError 발생 X).
    datafloor_plansplansplanbackgroundsdepends_on_fpbg_idr   numbered_elementscamera_recommendations)getitemslist)fp_prompt_cpmaster_plan_cp
fp_promptsr-   bg_to_fp_gidgpr.   bgdepbidnumbered_lookupcamera_lookupr1   fp_idfpcams                    r   _build_fp_lookup_for_bgrE   W   s   " 
"	!	!&"	-	388KQr  
B	#	#FB	/	52::7BGM2 
  "HKKMbb~~f%+((="-33B&&)/RC&&/Cs #A	 4 " 8:O/1M (u^^E"(b!%bff-@&A&GR!H66239r9Cwww5('*e$ : ) M))r   c                   i }| xs i j                  di       xs i j                  di       xs i }t        |t              s|S |j                         D ]  }t        |t              s|j                  d      xs i j                  d      xs g }t        |t              sM|D ]B  }t        |t              s|j                  d      }t        |t
              s6|s9||vs>|||<   D  |S )u  ``{bg_id: plan_node}`` from the shot_aware_bg_render_plan checkpoint (C3).

    Each fp's ``data.per_fp[fp_id].graph.nodes`` carries one node per bg with
    the C3 projection-card pointer fields. bg_id is unique across the dwelling
    graph; a duplicate (defensive) keeps the first seen. Any structural gap
    (cp absent / shape broken) yields an empty mapping → projection path inert.
    r+   per_fpgraphnodesr1   )r4   
isinstancedictvaluesr6   str)plan_cplookuprG   r.   rI   noder1   s          r   _build_plan_node_lookuprQ      s     )+F}"!!&"-3882FL"Ffd#$%((7#)r..w7>B%&DdD)HHW%E%%%E4G $u    Mr   c                V   i }| xs i j                  di       xs i j                  di       xs i }t        |t              s|S |j                         D ]S  }t        |t              s|j                  d      }|j                  d      }|r|s;||t	        |      t	        |      f<   U |S )u  ``{(bg_id, shot_id): card_entry}`` from the shot_projection_card cp (C2).

    Re-keys ``data.cards`` (keyed ``{bg_id}::{shot_id}``) into the
    ``(bg_id, shot_id)`` tuple the guard re-joins on. Entries without a
    bg_id/shot_id are skipped. The card envelope (``card`` /
    ``bg_plate_visible_description``) and ``card_state`` are passed through
    unchanged — the guard, not this builder, decides injectability.
    r+   cardsr1   shot_id)r4   rJ   rK   rL   rM   )card_cprO   rS   entryr1   rT   s         r   _build_projection_card_lookuprW      s     57Fm  ,277DJEeT"%&		'"))I&G-2E
CL)*   Mr   c                  .    e Zd ZddZddZddZd	d
dZy)BackgroundPromptStepc                
   dd l }dd l}ddlm} |j                  t
        t               d}t        |dd      rddlm	} ||d<   |j                  |j                  |d	      j                  d
            j                         d d S )Nr   r   )background_modeschema_versionprompt_versionbackground_scale_anchor_enabledF)HUMAN_SCALE_RULE_PACKbackground_scale_anchor_packT)	sort_keysutf-8   )hashlibjsonr   r   r[   SCHEMA_VERSIONr   getattr&app.modules.pipeline.background_promptr_   sha256dumpsencode	hexdigest)selfrd   _jsonr   payloadr_   s         r   _config_hashz!BackgroundPromptStep._config_hash   s    ,  (77,
 78
 8>F 7LG23~~KK4K077@

)+cr 	r   c                L   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 # t        $ r!}t        j                  d||       Y d }~y d }~ww xY w)	Nr   r   checkpointsepisodeszmanifest.jsonrb   )encodingz&background_prompt: %s parse failed: %s)r   r   r   projects_dir
project_id
episode_idexistsre   loads	read_text	Exceptionloggerwarning)rm   step_idr   cpexcs        r   _load_prev_checkpointz*BackgroundPromptStep._load_prev_checkpoint   s    , &&'oo  oo	
   	 99;zz",,,"@AA
 	  <gs  	s   $A9 9	B#BB#c                8    dddt         | j                         i dS )Nr   applicable_countcompleted_countfailed_countr\   config_hashr+   )rf   rp   )rm   s    r   _not_applicablez$BackgroundPromptStep._not_applicable   s'     ! ,,,.
 	
r   c                   6789:;<=>?@ABCD ddl mC Cj                  dvr j                         S Cj                  6t        6        j                  d      } j                  d      } j                  d      }d }6dv r j                  d      }i @i 96d	k(  r8 j                  d
      } j                  d      }t        |      @t        |      9 j                  d      } j                  d      }	 j                  d      }
 j                  d      } j                  d      }|xs i j                  di       xs i j                  di       xs i }|xs i j                  di       xs i j                  di       xs i j                         D ci c],  \  }}|j                  d      dk(  r||j                  dd      . c}}:ddlm} ddlm7 ddlm} g }i }g }|j                         D ]  \  }}|j                  d      dk7  r|j                  d      xs i }|j                  d      xs g D ]_  }|j                  d      }|s|j#                  |||d       |j                  d      xs g }d |r|d   ndi||<   |j#                  |       a  |sa|xs i j                  di       xs i }d!d!dt$         j'                         i |j                  d"d      xs d|j                  d#d      xs dd$d%S t)        |      } ||||d &      }|xs i j                  di       xs i j                  d'g       xs g }i ;|j                         D ]  \  }}|j                  d      dk7  r|j                  d      xs i }|j                  d      xs g D ch c]  }|j                  d(       }}||D cg c]	  }|r|dd) c}d*}  || ||	|
      \  }!}"|!;|<    dd+lm}#m}$  |#||,      B |$|      xs dDt1        ||-      \  =8i ?6dv r.|xs i j                  di       xs i j                  d.i       xs i ?i Ad}%dd/lm}&  |&d0d12      }' j7                         >|D (ci c]  }(|(d   |(
 c}(<d:6789:;<=>?@AB CDfd3})|D ]  }*|*st9        d!t;        |'t=        |*                  }+t?        |+4      5 },|*D ci c]  }|,jA                  |)|      | }-}tC        |-      D ]:  }.|-|.   }	 |.jE                         \  }/}0|0A|<   |0j                  d      dk7  s6|%d!z  }%< 	 d d d         |D ci c]  }|Av s|A|    }2}|xs i j                  di       xs i }3t=        |2      }4tO        d9 |2jQ                         D              }5t9        d!|4      |5|%t$         j'                         |2|3j                  d"d      xs d|3j                  d#d      xs dd$d%S c c}}w c c}w c c}w c c}(w c c}w # tF        $ r3}1tH        jK                  d5||1       d6tM        |1      d d7 d8}0Y d }1~1+d }1~1ww xY w# 1 sw Y   xY wc c}w );Nr   r   >   onfloor_plan_anchoredbackground_master_planfloor_plan_renderfloor_plan_prompt>   r   r   r    r!   r"   r#   floor_plan_overlay_payloadr#   shot_aware_bg_render_planshot_projection_card
scene_saveshot_validatorshot_selectionvisual_world_rulesworld_guider+   r-   r,   statusokpng_path )_select_scenes_for_group)call_structured)compute_dag_levelsr.   r/   r1   )r1   specgroup_iddepends_on_bg	parent_id   bg_catalog_hashshot_binding_hash)r/   consumed_bg_catalog_hashconsumed_shot_binding_hashr   )parent_fieldsegmentsloc_id)r   label)r   members)build_visual_context_blockextract_source_language)world_guide_cpvisual_world_rules_cp)r7   r8   overlays)resolve_workers      )defaultcapc                   |    }|d   }|j                  d      xs dgd   }j                  |d      }g }|j                  d      xs g D ]A  }j                  |      xs i }|j                  d      dk(  s-|j                  d| d	       C }d }	d }
d
k(  r[j                  |       }t        |t              rt	        |      }
nddd}
|
j                  d      r|
j                  d      }	d
}nd}|}t        |j                  |       d u      \  }}|rt        j                  d| ||       	 t        |||j                  |d   g       j                  |       j                  |       !||dv rj                  |       nd |	      }	 t        || |j                  d      xs g j                  |t        t         dd                  }d|d   |j                  dd      |j                  dg       |d    ||d   |||d!
}|
ct        |
j                  d            |
j                  d"d      |
j                  d#d      |
j                  d$d      |
j                  d%d      d&|d'<   | |fS # t        $ r9}t        j                  d| |       | dt        |      d d ||d   dfcY d }~S d }~ww xY w# t         $ r9}t        j                  d(| |       | dt        |      d d ||d   dfcY d }~S d }~ww xY w))Nr   r0   r   r   r   r   r   z	<bg ref: >r#   )	plan_nodecard_lookupFprojection_plan_node_missing)injectfallback_reasonr   plate_proser!   uh   background_prompt: bg %s requested v%s but has no overlay payload (no floor plan) — degrade to v6 (%s)r   >   r   r   r    r!   r"   r#   )bg_specfloor_plan_pathprior_bg_pathsscene_segmentsr   r2   camera_recommendationsource_languager]   overlay_payloadprojection_plate_prosez,background_prompt: bg %s overlay invalid: %sfailed   )r   errorr   r   applies_to_shotsr^   )user_promptexpected_bg_idr   call_structured_fnproject_configopik_metadatar]   scale_anchor
t2i_prompt	ref_guideshot_guidesobjects_owned_by_background)
r   r   r   r   r   r   r   requested_prompt_versioneffective_prompt_versionoverlay_fallback_reasonr   source_bg_idanchor_shot_idcard_id)r   r   r   r   r   projection_cardz#background_prompt: bg %s failed: %s)r4   appendrJ   rK   r   r)   r|   infor   r   r   rM   r   r   boolrg   r   )"r?   jobr   fp_firstfp_pathr   r>   prior_entryr   r   projection_decisionrP   r   r   upr   outrV   bg_prompt_selectorr   rA   r   fp_pathsgroup_scene_cache
jobs_by_idr@   opikoverlay_lookupplan_node_lookupresults
rules_textrm   r   r   s"                     r   _processz/BackgroundPromptStep._execute.<locals>._process  s   S/Cv;D19bT1=Hll8R0G )+Nxx06B6%kk#.4"??8,4"))IcU!*<= 7 (:$48"<@!T)'++C0dD)*L"&K+'
 #(+I+' '**84-@-D-D]-S*/3, 04, (@$-,n.@.@.ET.Q >$&=
 'E13J
%) $+#1#4#8#8Z"#M'1 '6&9&9#&>*7*;*;C*@$3 $< 4<= '**3/ " ,B/J=+ "#&%)XX.@%A%GR'6#'#6#6"& $< "&*K %'"$ #"%l"3!$b!9#&77="#=
 477T3U  #J 1I0H/F!)$ '2
 #'':'>'>x'H"I+>+B+B-r, )<(?(?PR(S*=*A*A,b+ $7#:#:9b#I
0E+, Ez! 0  BC & Xds^  #J	  @ ) 	93 & Xds^  #J	  		s>   5AI5 C"J: 5	J7>.J2,J72J7:	K<.K71K<7K<)max_workersz*background_prompt: bg %s thread raised: %sr   r   )r   r   c              3  h   K   | ]*  }t        |t              r|j                  d       dk(  rd , yw)r   r   r   N)rJ   rK   r4   ).0es     r   	<genexpr>z0BackgroundPromptStep._execute.<locals>.<genexpr>k  s1      
'!!T"quuX$'> 's   02)r?   rM   returnzTuple[str, Dict[str, Any]]))r   r   r[   r   r   r   r   rQ   rW   r4   r5   *app.core.steps.background_master_plan_stepr   app.modules.llm.llm_clientr    app.modules.pipeline._dag_levelsr   r   rf   rp   set"app.services.visual_context_helperr   r   rE   app.modules.pipeline._workersr   build_opik_metadatamaxminlenr   submitr   resultr{   r|   r   rM   sumrL   )Erm   modeplans_cpfp_render_cpr7   
overlay_cprN   rU   scene_save_cpshot_validator_cpshot_selection_cprules_cpr   	plans_mapfidrV   r   r   bg_jobsr5   ordergidr.   r=   r?   depsplans_data_empty
renderablelevelsr   group_member_locslocsynthetic_groupscenes_shotsr   r   r   r   r   jr   levellevel_workerspoolfuturesfut_bidresr   ordered
plans_data	total_bgscompleted_bgsr   r   rA   r   r   r   r   r@   r   r   r   r   r   r   r   sE   `                                                     @@@@@@@@@@@@@@@r   _executezBackgroundPromptStep._execute   s!   ,##+HH''))
 &??12--.FG112EF 112EF
 04
!BB334PQJ 79=?%001LMG001GHG6w?7@K22<@ 667GH 667GH--.BC33MBn"))&"5;@@"MSQS	 $"))&"5;@@PRS eg

U yy"d* :r**
	
 	?G )++-#//+JCyy"d*99V$*Dhh}-33ffWo!$ "$' vvo.4")d47Cc
S! 4	 ,&  (B33FB?E2$%#$ !"0#002#%0@0D0DEVXZ0[0a_a2B2F2FGZ\^2_2ece  Z
#5*;
 !r&&vr28b==j"MSQS 	 >@#//+JCyy"d*99V$*D,0HH],C,Ir,I!,IRx ,I  !    10  #R00O 6!!	NFF &,c"+ ,0	
 0)"*

 2(;Ar
 *A%#*
& 57!BB"''39r>>z2N  
 #%A%aQ7'')-45Waj!mW5
a	 a	 a	F E3{CJ#?@M#>$FKLes4;;x5s:eL'0C!#,CL$'JJL	c $'GCLwwx(D0! 1 ?>	 , 16H3$H
 n"))&"5;
 L	 
~~'
 
 !$Ay 1,",,,.&,6NN;Lb,Q,WUW.8nn=PRT.U.[Y[
 	
]

L!
h 6T M
 % LH
 *2CHTcNKL ?>$ Isr   1W+ W1?W61W;	YX (Y>XY+Y	YY Y	Y	(X<	6Y<Y	YY	Nr   rM   )r~   rM   r   Optional[Dict[str, Any]])r   Dict[str, Any])resume)r  rM   r   r'  )__name__
__module____qualname__rp   r   r   r$   r   r   rY   rY      s    6*
F
r   rY   r%  )r'   rM   r(   r   r   zTuple[str, Optional[str]])r7   r&  r8   r&  r   zATuple[Dict[str, List[Dict[str, Any]]], Dict[str, Dict[str, Any]]])rN   r&  r   zDict[str, Dict[str, Any]])rU   r&  r   z%Dict[Tuple[str, str], Dict[str, Any]])(__doc__
__future__r   re   loggingconcurrent.futuresr   r   pathlibr   typingr   r   r	   r
   r   app.core.step_runnerr   rh   r   r   r   r   r   r   1app.modules.pipeline.background_prompt_projectionr   	getLoggerr)  r|   rf   PROMPT_VERSIONr   	frozensetr&   r)   rE   rQ   rW   rY   r,  r   r   <module>r8     s   
 #   ?  3 3 +  
		8	$ $C(F ''HI ##-1##()**)* -)* G	)*X%:%*4A
: A
r   