
    Oj#                        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 G d de      Z	 d	 	 	 	 	 	 	 	 	 	 	 ddZy)u  BackgroundMasterPlanStep — Phase 7 Step 2.

각 chain_bg group에 대해 LLM 1회 호출 → master plan 산출. 그룹간 ThreadPool 병렬.
prev_shot_ref 그룹은 skip.

흐름:
  1. background_classify + scene_save + shot_validator + shot_selection +
     visual_world_rules 체크포인트 로드
  2. classify의 chain_bg group만 추려 그룹별 LLM 호출 (ThreadPool 병렬)
  3. prev_shot_ref 그룹은 plan=null로 기록 (호출 X)

체크포인트 data:
  data.plans = {group_id: {status, plan | error}}
    )annotationsN)ThreadPoolExecutoras_completed)Path)AnyDictListOptionalTuple)
StepRunner)run_background_master_plan   z6.202605290248c                    | xs g D cg c]*  }t        |t              s|j                  d      dk(  s)|, c}S c c}w )u   `background_classify` 의 building_groups → **chain_bg 그룹**(이 스텝의 구매 단위). ★술어는 여기 한 곳 —
    스텝과 canary 의 사전 문(`assert_chain_group_cap_covers`)이 같은 함수를 부른다.kindchain_bg)
isinstancedictget)groupsgs     `/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/steps/background_master_plan_step.pychain_groups_ofr   /   s;     "]!At)<vR\A\A]]]s   ;;;c                  l    e Zd Zd
dZddZdddZddZ	 	 	 	 	 	 ddZ	 	 	 	 ddZddZ	e
dd       Zy	)BackgroundMasterPlanStepc                    dd l }dd l}ddlm} |j                  |j
                  t        t        d}|j                  |j                  |d      j                  d            j                         d d S )Nr   settings)background_modemodelschema_versionprompt_versionT)	sort_keysutf-8   )hashlibjsonapp.core.configr   r   openai_modelSCHEMA_VERSIONPROMPT_VERSIONsha256dumpsencode	hexdigest)selfr%   _jsonr   payloads        r   _config_hashz%BackgroundMasterPlanStep._config_hash5   se    %,'77**,,	
 ~~KK4K077@

)+cr 	    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episodesmanifest.jsonr#   encodingz+background_master_plan: %s parse failed: %s)r'   r   r   projects_dir
project_id
episode_idexistsr&   loads	read_text	Exceptionloggerwarning)r/   step_idr   cpexcs        r   _load_prev_checkpointz.BackgroundMasterPlanStep._load_prev_checkpointB   s    ,&&'$//9()+/??;'( 	
 99;\zz",,,"@AA   \LgWZ[[\s   $A9 9	B#BB#c                	  &'()*+,-./0 ddl m} |j                  dvr;t        j	                  d|j                         dddt
        | j                         i dS | j                  d      }| j                  d      }| j                  d      0| j                  d	      /| j                  d
      }| j                  d      }0rddlm	}  |0| j                  d       i -|rj|j                  di       xs i j                  dg       xs g D ]=  }|j                  d      }	|j                  dd      xs d}
|	-|
s0|
-t        |	      <   ? |xs i j                  di       xs i j                  dg       xs g }|s2t        j	                  d       dddt
        | j                         di idS |xs i j                  di       xs i j                  dd      xs. |xs i j                  di       xs i j                  dd      xs d,|xs i j                  di       xs i j                  dg       xs g .ddlm'm& ddlm( t%        |      }i }d}| j'                         *| j                  +|r| j)                         ni )d2&'()*+,-./0fd}d}i }t+        dt-        dt/        |                  }|rt1        |      5 }|D ci c]  }|j3                  ||      |d     }}t5        |      D ]q  }||   }	 |j7                         \  }}|j?                  d%d       }|r|j                  d&      r|||<   |||<   |j                  d'      }|d"k(  r|dz  }g|d(k(  sm|dz  }s 	 d d d        |D ]"  }|j                  d)      d*k(  sd+d d,||d    <   $ |D ci c]  }|d    |v r|d    ||d        }}| jA                  ||      }| jC                  |      \  }} }!}"|jE                         D ]C  \  }}#|#j                  d'      d-k7  r|#j                  d.      xs i }$| jG                  |$      |$d/<   E t-        dt/        |      |z
        }%|%t-        d|%|z
        |t
        | j                         ||| |!|"||d0d1dS c c}w # t8        $ r4}t        j;                  d!||       d"t=        |      d d# d d$}Y d }~d }~ww xY w# 1 sw Y   ^xY wc c}w )3Nr   r   >   onfloor_plan_anchoredu6   background_master_plan: skipped — background_mode=%s)applicable_countcompleted_countfailed_countr    config_hashdatabackground_classify
scene_saveshot_validatorshot_selectionvisual_world_rulesscene_director)assert_no_failed_scenesbackground_master_plan)consumer_steprN   scenesscene_indexprimary_location building_groupsu8   background_master_plan: no groups from classify — skipplans
rules_texttextsegments)build_master_plan_user_promptMasterPlanError)call_structuredc                r   | d   }| j                  dg       xs g }g }g }|D ]o  }|j                  dd      }|r|v r|j                  |       -|j                  ||j                  dd      t        |j                  dd      xs d      dd	       q t        |      t        |      |d
}|s0t        j                  d||D cg c]  }|d   	 c}       |dd |dfS |r2t        j                  d|t        |      |D cg c]  }|d   	 c}       |ri | d|in| }	|D cg c]  }|d   	 }
}t        |	      \  }} |	|      }	 t        |||
|      }|d||dfS c c}w c c}w c c}w # $ r6}t        j                  d||       |dt        |      d d d |dfcY d }~S d }~ww xY w)Ngroup_idmembersloc_idr[   label
shot_countr   no_location_profile)rg   rh   ri   reason)raw_member_countkept_member_countfiltered_membersuH   master_plan: group %s skipped — all members lack location_profile (%s)all_filtered)statusplan
pre_filterzHmaster_plan: group %s pre-filtered %d member(s) w/o location_profile: %s)scene_primary)grouprX   rS   location_profiles)user_promptexpected_group_idgroup_loc_idsgroup_shot_idsru   call_structured_fnproject_configopik_metadataokz master_plan: group %s failed: %sfailed   )rp   errorrq   rr   )r   appendintlenrA   rB   info_select_scenes_for_groupr   r   str)r   gidraw_memberskept_membersrn   mlocrr   fmg_effrx   relevant_scenesry   rv   rq   rE   rb   ra   rc   ru   	opik_metar{   r^   rs   scene_segmentsshot_selection_cpshot_validator_cps                   r   _processz3BackgroundMasterPlanStep._execute.<locals>._process   s9   J-C %%	2.4"K13L57 eeHb)3"33 ''*$++"%!"w!3&)!%%a*@*EA&F"7	-  ! %($4%(%6$4J
  ^1AB1A2"X,1AB ,d*    ^-.,<=,<bR\,<=
 7G2q2)\2AE2>?,QQx[,M?.F~'8:K+/+O^ 8&#-"3	K1 +&)"/#1&7'6#1"+	 tTTTTE C > @. # ?cJ&S$3",  s0   E, E1#E6E; ;F6 +F1+F61F6      )max_workersre   z'master_plan: group %s thread raised: %sr~   r   )rp   r   rq   rr   rn   rp   ro   r   prev_shot_refprev_shot_only)rp   rq   r}   rq   	gen_order)w21b_surface_role_scope_filterlocation_profile_pre_filter)r]   background_catalogshot_background_mapbg_catalog_hashshot_binding_hashdiagnostics)r   Dict[str, Any]returnzTuple[str, Dict[str, Any]])$r'   r   r   rA   r   r)   r2   rF   "app.core.steps.shot_validator_steprU   r{   r   r   +app.modules.pipeline.background_master_planra   rb   app.modules.llm.llm_clientrc   r   build_opik_metadata_load_location_profilesminmaxr   r   submitr   resultr@   r   r   pop%_apply_w21b_surface_role_scope_filter_d6_post_processitems_build_gen_order)1r/   moder   classify_cpscene_save_cprules_cpscene_director_cprU   scsiprimaryr   chain_groupsr]   r~   r   ro   pre_filter_by_groupr   poolr   futuresfutr   _gidresrE   pfrp   ordered_plans
scope_diagr   r   r   r   gentryrq   	attemptedrb   ra   rc   ru   r   r{   r^   rs   r   r   r   s1                                         @@@@@@@@@@@r   _executez!BackgroundMasterPlanStep._executeP   sS   ,##+HHKKH((
 %&#$a"0#002  001FG22<@ 667GH 667GH--.BC 667GHR#$5t7J7JZrs )+(,,VR8>BCCHbQWUWWVVM*&&!3R8>B>g-4M#b'*	 X $"))&"5;@@ARTVW][]KKRS$%#$a"0#002 "   ~2**626<"AA,PRS NR$$VR06B;;FBGNKM 	(.B33FB?E2JJ:WYZ`^`	
 	?&v. ",,.	,, /;D((* 	J	 J	X .0!SC$567#<LXYLq4;;x3Qz]BLY'0C!#,CZ$'JJL	c t4Bbff%7835+C0!$E#J WWX.F)!>1$)# 1 =, AuuV}/2BD'Qa
m$  #)D"(QAjMU,B :a
m(<<"( 	 D ??6

 !!-0 	T/BS )..0KCzz(#t+::f%+D $ 5 5d ;D	 1 3|,|;<	 )"1i&&89",,,.& '9':#2%66@ 4G	 
 	
A Z
 % Z%NPSUXY)1CHTcNTXYZ =<6DsU   S	Q?&S<RAS SS?S	S)R<6S<SSSc                   ddl m} ddlm} i } |       }	 |j	                  |      j                  |j                  | j                  k(  |j                  dk(  |j                  j                  d            j                         }|D ]}  }	 t        j                  |j                  xs d      }|xs i j%                  d      }t'        |t(              sM|j%                  d      }	t'        |	t(              so|	||j                  <    	 |j+                          |S # t        t        f$ r# t         j#                  d|j                         Y w xY w# |j+                          w xY w)	u
  entity_canon.metadata_json.location.space_profile 을 loc_id → profile 로 build.

        D6 SOT: location.space_profile 은 entity_extractor (T2) 가 생성.
        master_plan 후처리의 normalize_space_key 가 본 dict 를 단일 source 로 사용.
        r   )SessionLocal)EntityCanonlocationNz{}uH   background_master_plan: EntityCanon %s metadata_json parse fail — skipspace_profile)app.core.databaser   app.models.projectr   queryfilterr;   entity_typeshort_idisnotallr&   r>   metadata_json	TypeError
ValueErrorrA   rB   r   r   r   close)
r/   r   r   profilesdbrowsrowmd	loc_blockprofiles
             r   r   z0BackgroundMasterPlanStep._load_location_profiles=  s2    	32.0^	88K(//&&$//9'':5$$**40 ce	 
 C$5$5$=>B  X2NN:6	!)T2#--8gt,-4HS\\*   HHJ ":. NNb  HHJs7   A2E 
#D-AE 5E /EE 
EE E c                  - ddl m} i }|D ]  }|j                  dd      }|j                  dg       xs g }|D ci c]o  }t        |t              r]|j                  d      rL|j                  dd      t        |j                  dd            t        |j                  d	d      xs d      d
q c}||<    d/d-d0-fd}	d1d}
g }g }g }|j                         D ]  \  }}t        |t              s|j                  d      dk7  r-|j                  d      xs i }t        |t              sSt        |j                  d      xs g       }t        |j                  d      xs g       }|j                  |i       }|D ch c]F  }t        |j                  dd      t              r$|j                  dd      r|j                  dd      H }}g }t               }t               }|D ]&  }|j                  dd      }t        |j                  d      xs g       }t        |j                  d      xs g       }g }||vr|j                  d       |s|j                  d       |dk(  r|s|j                  d       |D cg c]  }t        |t              r|r||vr| }}|r|j                  d       |rH|j                   |
||             |j                   -|             |j                   |	|             |j                  |       ) t        |      }d} | rd} g }!|D ]  }t        |j                  d      xs g       }"d}#|"D ]  }$t        |$t              r|$s|$|v sd}# n |#rJ|j                   |
|dg             |j                   -|             |j                   |	|             d} |!j                  |        |!}| r|D %ch c]0  }|j                  d      xs g D ]  }%t        |%t              r|%r|% 2 }&}}%g }'|D ]  }|j                  dd      }(|j                  dd      })|j                  |)      xs i }*|(|&v r|'j                  |       R|j                  ||(|)t        |*j                  dd            t        |*j                  d	d      xs d      ddgd         |'|d<   ||d<   |r|j                  |d!d"       d#|d<   d$|d<    |j                         D ]  \  }}|j                  d      dk7  r|j                  d      xs i }|j                  d      xs g D ch c]  }|j                  dd       }+}|j                  d      xs g D ]j  }|j                  d      xs g D ]P  }%t        |%t              s|%s|%|+vst        d%| d&|j                  d      d'|j                  d(      d)|%d*	       l  d+t        d, |j                         D              t        d- |j                         D              t!        |      t!        |      t!        |      |||d.	},|,S c c}w c c}w c c}w c c}%}w c c}w )2u  W21B-wave-2 — surface-role scope filter + transitive BG cascade.

        This replaces the W20F6 hard filter
        ``indoor AND shot_count>=2 AND dep_BG_count>=1``. The old policy made
        exterior/transition/site surfaces disappear before rendering. The new
        policy is background-first:

        - ``interior_room`` backgrounds remain floor-plan-bound.
        - ``exterior_plate`` / ``transition_zone`` / ``site_surface`` backgrounds
          may be fp-less and are not dropped merely because the location is
          outdoor.
        - floor_plans are retained only when a kept background still references
          them.
        - dropped backgrounds cascade through exact raw ``depends_on_bg`` keys.

        bg_id 부재 (_d6_post_process 호출 전 trim) — diagnostic 은 raw identity
        (loc_id, space_key_hint, time_phase, state_class, applies_to_shots,
        reasons) carry.

        plan.floor_plans / plan.backgrounds 둘 다 in-place trim. group 의
        backgrounds[] 가 0 으로 비게 되면 ``dropped_groups[]`` 에 reason
        ``all_filtered_after_trim`` carry (gen_order 단에서 빈 plan 은 자연
        skip; defensive).

        Returns: diagnostics dict (manifest.data.diagnostics
        .w21b_surface_role_scope_filter 안으로 carry).
        r   )SURFACE_ROLE_ENUMre   r[   rf   rg   	is_indoorFri   )r   ri   c           	         t        | j                  dd            t        | j                  dd            t        | j                  dd            t        | j                  dd            fS )Nrg   r[   space_key_hint
time_phasestate_class)r   r   )bgs    r   _raw_bg_identityzXBackgroundMasterPlanStep._apply_w21b_surface_role_scope_filter.<locals>._raw_bg_identity  sY    BFF8R()BFF+R01BFF<,-BFF="-.	 r3   c                2    dj                   |             S )N|)join)r   r   s    r   _raw_bg_key_stringzZBackgroundMasterPlanStep._apply_w21b_surface_role_scope_filter.<locals>._raw_bg_key_string  s    88,R011r3   c           
         | j                  dd      | j                  dd      | j                  dd      | j                  dd      | j                  dd      t        | j                  d      xs g       t        |      dS )	Nrg   r[   r   r   r   surface_roleapplies_to_shots)rg   r   r   r   r   r   reasons)r   list)r   r   s     r   _bg_identity_dictzYBackgroundMasterPlanStep._apply_w21b_surface_role_scope_filter.<locals>._bg_identity_dict  sq    &&2."$&&)92"> ff\26!vvmR8 "~r :$(0B)C)Ir$J= r3   rp   r}   rq   floor_plansbackgroundsfp_idr   r   depends_on_fpinvalid_surface_roleno_consuming_shotinterior_roommissing_floor_plan_dependencydepends_on_missing_fpTdepends_on_bgdepends_on_dropped_bgno_kept_background_ref)re   r   rg   r   ri   referenced_by_kept_backgroundr   all_filtered_after_trim)re   rk   ro   Nz*W21B surface-role filter assertion: group=z background loc_id=z space=r   z  still references dropped fp_id=z after trim.zsurface_role-aware background-first filter; interior_room remains floor-plan-bound; exterior/transition/site may be fp-less; transitive cascade depends_on_bg exact raw key.c              3     K   | ]Y  }t        |t              rG|j                  d       dk(  r3t        |j                  d      xs i j                  d      xs g        [ yw)rp   r}   rq   r   Nr   r   r   r   .0r   s     r   	<genexpr>zQBackgroundMasterPlanStep._apply_w21b_surface_role_scope_filter.<locals>.<genexpr>C  S      */Aa&155?d+B QUU6](b--m<BC/   AA!c              3     K   | ]Y  }t        |t              rG|j                  d       dk(  r3t        |j                  d      xs i j                  d      xs g        [ yw)rp   r}   rq   r   Nr   r  s     r   r  zQBackgroundMasterPlanStep._apply_w21b_surface_role_scope_filter.<locals>.<genexpr>H  r  r  )	policykept_floor_plans_countkept_backgrounds_countdropped_floor_plans_countdropped_backgrounds_countdropped_groups_countdropped_floor_plansdropped_backgroundsdropped_groups)r   r   r   zTuple[str, str, str, str])r   r   r   r   )r   r   r   	List[str]r   r   )r   r   r   r   r   boolr   r   r   r   setr   addRuntimeErrorsumvaluesr   ).r/   r   r   r   member_by_gid_locr   r   rf   r   r   r   r  r  r  r   rq   r   r   loc_mapfpfp_idskept_backgrounds_pass1dropped_raw_keys_in_group dropped_raw_key_strings_in_groupr   roler   dep_fpsr   fmissing_fp_refskept_backgroundschanged
still_keptdep_bgsmatchedreffp_refreferenced_fp_idskept_floor_plansr   rg   memberkept_fp_idsr   r   s.                                                @r   r   z>BackgroundMasterPlanStep._apply_w21b_surface_role_scope_filterb  s   @ 	R
 CEA%%
B'CeeIr*0bG !&
 !Aa&155? h#!%aeeK&?!@"%aeeL!&<&A"B&  !&c" $		2		 5746/1
 )..0KCfd+zz(#t+::f%+DdD)txx6<"=Ktxx6<"=K'++C4G &%BbffWb137BFF7B<O w#%   <>"-0U%47E,!vvnb1#'/A(B(Hb#I rvvo6<"=%'00NN#9:'NN#67?*7NN#BC&#&!!!S)aAVO w   # #NN#:;'../@G0  .112B22FG4889KB9OP*11"51 "<  $$:;G35
*B"266/#:#@bAG#G&)#s33$"BB&*G!  ' +223D!8 94  2556Fr6JK8<<=OPR=ST"&"))"-# +$ $. + 8 +!*B!vvo6<"<Ffc*v < *  ! 68!w+"- V,2--$++B/#** #"$!%fjje&D!E"%fjjq&A&FQ"G5: 89,  "$ #3D"2D#%% #7'  $2x !%vg 1p )..0KCzz(#t+::f%+D:>((=:Q:WUW:WY:W2266'2.:WKYxx.4"4!vvo6<"<F!&#.6fK>W*H N&&(ffX&6%9HXAY@\ ]>>DZ|U   = 5 1 B '* *&--/* '
 '* *&--/* '
 *--@)A),-@)A$'$7#6#6,+
. S&p*#^!X Zs   A4X< AY=Y5Y8Yc           
     X   ddl m}m}m}m} ddlm} | j                         }g }i }	|j                         D ]v  \  }
}|j                  d      dk7  r|j                  d      xs i }t        |j                  d      xs g       D ]&  \  }}t        |      |	|
|f<   |j                  |       ( x | j                         } ||||      } ||       ddl m}m}m} |j                         D ci c]  \  }}|d	   | }}}|	j                         D ]K  \  \  }
}}||
   d   d   |   }	 ||d
      } ||d
   |d   |      } ||d
   ||d   |d         }||   |d<   M  ||      } ||      } ||      }||||fS c c}}w # t"        |f$ r}t%        d|
 d| d|       |d}~ww xY w)u  LLM raw intent → deterministic bg_id catalog + sibling fields.

        Returns: (background_catalog, shot_background_map, bg_catalog_hash, shot_binding_hash).
        실패 시 raise — downstream (background_render) 가 빈 dict 로 silent 진행 안 함.

        I8 intent sort + B7 monotonic next_b 는 `assign_bg_ids` 가 처리.
        B3 handshake: 각 plan.backgrounds[].bg_id slot 에 새 L##B## 갱신.
        r   )assign_bg_idsbuild_shot_background_mapcompute_bg_catalog_hashcompute_shot_binding_hash)validate_master_plan_assignedrp   r}   rq   r   )compute_semantic_keynormalize_space_keySemanticKeyErrorsemantic_keyrg   r   r   r   zD6 handshake: group=z idx=u    sem_key 재계산 실패 — Nbg_id)app.core.bg_catalogr.  r/  r0  r1  r   r2  r   r   r   	enumerater   r   _load_prev_background_catalogr3  r4  r5  KeyErrorr  )r/   r   r.  r/  r0  r1  r2  ru   flat_intentsbackref_to_flatr   r   rq   idxr   prev_catalogcatalogr3  r4  r5  r7  entrysem_to_bgid	_flat_idxr   	space_keysem_keyrE   shot_bg_mapcatalog_hashbinding_hashs                                  r   r   z)BackgroundMasterPlanStep._d6_post_processV  s   	
 	
	

 !88: .068(..0KCzz(#t+::f%+D$TXXm%<%BCR /2,.?c
+##B' D	 1 99;l<MN 	&g.	
 	
 IPXuu^,e3X%4%:%:%<!JS#	s#F+M:3?B
+BxL9/8bAQ>RT[\	.xL)R-=r-?P &g.BwK &=  08.w70=\<??+ Y ./ "*3%uSE9WX[W\]s   /E>*,FF)F$$F)c                   	 | j                   dz  }|j                         rv|j                  d      j                         }|rTt	        j
                  |      }|xs i j                  di       xs i }|j                  d      }t        |t              r|r|S 	 | j                         }|t	        j
                  |j                  d            }|xs i j                  di       xs i }|j                  d      }t        |t              r"t        j                  d|j                         |S i S # t        $ r }t        j                  d|       Y d}~d}~ww xY w# t        $ r!}t        j                  d	|       Y d}~i S d}~ww xY w)
u  T4-fix B2 + T4-fix2 B1: 자기 cp 또는 archive 에서 이전 background_catalog 로드.

        force re-run / 운영 중 master_plan 재실행 시 prev_catalog 가 비어있으면
        next_b counter reset 가능 → ID 변경 → 모든 downstream chain_bg 재렌더 발생.
        이전 catalog 를 prev 로 넘기면 assign_bg_ids 가 same-semantic_key 는 ID
        재사용 + 새 sem_key 만 monotonic next_b 부여.

        T4-fix2 B1 (review iter6): explicit force (`.force_cleared` marker) 환경에서도
        prev catalog 보존 의무. `load_checkpoint()` 는 marker 있으면 archive 복원
        차단 → prev={} → ID reset. 본 helper 는 archive 를 직접 read (manifest restore
        안 함, force 의도 보존).

        조회 우선순위:
          1. manifest.json (정상 resume — load_checkpoint 와 동일 결과).
          2. .force_cleared marker 있어도 latest archive 직접 read (`_find_latest_archive`).
          3. 둘 다 없으면 빈 dict (fresh — 첫 실행).
        r7   r#   r8   rN   r   zCbackground_master_plan: manifest.json read failed (try archive): %sNzdbackground_master_plan: prev catalog read from archive %s (D6 ID preservation under explicit force).z<background_master_plan: archive prev catalog read failed: %s)_cp_dirr=   r?   stripr&   r>   r   r   r   r@   rA   rB   _find_latest_archiver   name)	r/   manifest_path	text_dataown_cprN   r@  rE   archive_patharchive_datas	            r   r:  z6BackgroundMasterPlanStep._load_prev_background_catalog  st   &	 LL?:M##%)33W3EKKM	!ZZ	2F"Lb--fb9?RD"hh';<G!'40W&	446L'#zz,*@*@'*@*RS$*//;Ar((#78gt,KKE$))
 #N 	1  	NNU 	&  	NNNPS  		s1   BD. BE .	E7EE	F#E??Fc           
       	
 | j                  d      xs g D ci c]$  }|d   t        |j                  d      xs g       & }}| j                  d      xs g D ci c]K  }|j                  d      r8|d   t        |j                  d      xs g |j                  d      xs g z         M }}i ||g 
t               t               	dd		
fd|D ]
  } |        |D ]
  } |        
S c c}w c c}w )
u  floor_plans + backgrounds 에서 topological gen_order build.

        floor_plans 가 먼저 (DAG levels), 그 후 backgrounds. depends_on_fp / depends_on_bg
        모두 앞에 등장하도록 정렬. fp_id 와 bg_id 같이 unique key 로.

        T4-fix I3: missing dependency / cycle 검출 시 raise.
        - missing: depends_on_bg/depends_on_fp 가 plan 안 fp/bg 가 아님 → ValueError.
        - cycle: DFS 진행 중 같은 노드 재진입 → ValueError.
        r   r   r   r   r7  r   c                   | v ry | vr2t        d| d|r|d   nddt        j                                      | v r#dj                  || fz         }t        d|       j	                  |        |    D ]  } ||| fz           j                  |        j	                  |        j                  |        y )Nz%_build_gen_order: missing dependency z (referenced from rootz). available: u    → u%   _build_gen_order: cycle detected — )r   sortedkeysr   r  discardr   )	nodeparent_chain
cycle_pathdep_visit	all_nodesin_progressorderseens	       r   r^  z9BackgroundMasterPlanStep._build_gen_order.<locals>._visit  s    t|9$ ;D8 D(<HR(8f'W X""()9":!;= 
 {"$\\,$*@A
 ;J<H  OOD! sLD723 '%HHTNLLr3   ) )rZ  r   r[  zTuple[str, ...]r   None)r   r   r  )rq   r  fp_nodesr   bg_nodesr   r7  r^  r_  r`  ra  rb  s          @@@@@r   r   z)BackgroundMasterPlanStep._build_gen_order  s"    hh}-33
3 wKbff_5;<<3 	 
 hh}-33
3vvg wKrvvo6<"AXA^\^_``3 	 

 -x,8,	E5	 	, E5M E5M S

s   )C8AC=N)r   r   )rC   r   r   Optional[Dict[str, Any]])resume)r   r   r   r   )r   Dict[str, Dict[str, Any]])r   ri  r   List[Dict[str, Any]]r   r   )r   ri  r   z:Tuple[Dict[str, Dict[str, Any]], Dict[str, str], str, str])rq   r   r   r  )__name__
__module____qualname__r2   rF   r   r   r   r   r:  staticmethodr   rc  r3   r   r   r   4   su    k
Z#Jr0r %r 
	rhL@6L@	CL@\5n 4 4r3   r   c           
     *   | j                  dg       xs g D ch c]  }|d   	 }}|xs i }i }|xs i j                  di       xs i j                  dg       xs g D ]A  }	|	j                  d      }
|
t        |	j                  dg       xs g       |t        |
      <   C i }|xs i j                  di       xs i j                  dg       xs g D ]  }	|	j                  d      }
|
t        |
      }|j                  |t                     }|	j                  dg       xs g D ]  }|j                  d      }|||vr|j                  d	d
      xs |j                  |d
      xs d
}||vrJ|j                  |g       j	                  ||j                  dd
      xs d
|d         t        |j                               }g }|D ]n  }|j                  d      }
t        |
t              r|
|vr)|j	                  |
|j                  dd
      |j                  dd
      |j                  |
g       d       p |j                  d        t        |j                               D 
cg c]  }
||
   D ]  }d|
 d|d      }}
}||fS c c}w c c}}
w )u,  그룹 멤버 loc_id가 등장하는 씬 + 그 씬의 shots만 추출.

    shot에 location_id가 없으면 scene_primary[scene_index]로 fallback (Phase 5 패턴).

    Returns:
        (relevant_scenes [{scene_index, heading, text, shots[]}],
         group_shot_ids ["S{si}_Shot{shi}", ...])
    rf   rg   rN   rX   rY   selected_shot_indicesshots
shot_indexlocation_idr[   description)rr  rt  rs  headingr_   )rY   ru  r_   rq  c                    | d   S )NrY   rc  )xs    r   <lambda>z*_select_scenes_for_group.<locals>.<lambda>L  s	    q'7r3   )keyS_Shot)	r   r  r   
setdefaultr   rX  r   sortrW  )rt   r   r   r   rs   r   member_locsprimary_mapsel_mapsr   shots_by_scenesi_intselshshir   relevant_scene_indicesr   segry   s                        r   r   r     s    ).		)R(@(FB(FG(F11X;(FKG%2K G &B++FB7=2BB8RPVTVVUU=!:quu%<bAGRHB	 W 79N &B++FB7=2BB8RPVTVVUU=!:Rkk&#%(%%$**B&&&C{cn&&+P{vr/JPbC+%%%fb188!!vvmR8>B":  + W& !!4!4!67,.OWW]#"c"b0F&Fwwy"-GGFB'#''B/	 
 		  78 ,,.//B $B B4uR%&'$ 	(/  
 N**c HXs   J
'J)r   rj  )N)rt   r   r   rj  r   rg  r   rg  rs   zOptional[Dict[int, str]]r   z&Tuple[List[Dict[str, Any]], List[str]])__doc__
__future__r   r&   loggingconcurrent.futuresr   r   pathlibr   typingr   r   r	   r
   r   app.core.step_runnerr   r   r   	getLoggerrk  rA   r)   r*   r   r   r   rc  r3   r   <module>r     s    #   ?  3 3 + 
		8	$ !^
\z \H /3@+@+(@+ 0@+ 0	@+
 ,@+ ,@+r3   