
    YojL                       d Z ddlmZ ddlZddlmZ ddlmZmZm	Z	m
Z
 ddlmZmZ ddlmZ ddlmZ  ej$                  e      Zd	Zd
ZdZdZdZdZddddddddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZy)u  background_render — Phase 7 Step 6.

각 master plan background를 gpt-image-2로 PNG 생성. ref 우선순위는
``fp_path`` (1순위) → ``prior_bg_paths`` 의 concat. 두 개 이상이면
multi-image edit, 한 개면 single edit, 0개면 generate (text-only).

PromptSanitizer로 moderation 차단 시 retry. 실패 시 status='failed'
+ ``final_block_reason`` 채워서 dict 반환 (예외 미전파).
    )annotationsN)Path)AnyDictListOptional)ImageCallBudgetExceededreserve_current_call)call_gpt_image_bytes)PromptSanitizeru   BACKGROUND-ONLY architectural still — empty space, NO people, NO faces, NO body posture, NO action, NO weapons, NO blood. Photoreal scene without any human figures.

u  

The LAST attached image is a TOP-DOWN INTERIOR FLOOR PLAN of the building at this location — a STRUCTURAL REFERENCE ONLY, never content to draw. It defines the building's real scale: its footprint, room count, and the placement of its doors and windows. Render the scene so the building's exterior size, number of storeys, and the scale and placement of its doors and windows stay consistent with that plan. Do NOT draw the floor plan itself, its lines, text or numbers anywhere in the image.u  

One attached image is a PHOTOREAL RENDER of this SAME building/site seen from a different viewpoint — treat it as the VISUAL IDENTITY reference. Keep this render consistent with it: the same wall materials and colors, the same door and window styles, the same permanently installed fixtures and objects, the same weathering and age. Do NOT copy its composition, framing or camera angle — frame this scene per the prompt; only the building's physical identity carries over.u  

One attached image is a PHOTOREAL RENDER of this SAME place — the same physical location, possibly seen from a different angle or at a different moment. Treat it as the PLACE IDENTITY reference. Keep this render consistent with it: the same spatial layout and architecture, the same surface materials and colors, and the same permanently installed fixtures and furniture. Lettering visible in the reference is reproduced EXACTLY — same characters, same mark, same position; never invent different wording from render to render. Camera angle, framing, lighting, time of day and weather follow THIS prompt — do NOT copy the reference's composition or mood; only the place's physical identity carries over.a  

This interior belongs to the same single real building/place as this location group's other spaces. Keep its construction era, age and state of repair, material quality and window/door frame styles consistent with ONE ordinary real building of the kind this prompt describes, and anything visible through its windows, doorways or passages must plausibly belong to that same building and its immediate surroundings.u9  

One attached image is a PHOTOREAL RENDER of the SAME building/site this scene belongs to — it may show the outside while this scene is inside, or another part of the same place. Treat it as the BUILDING IDENTITY reference. Keep this render consistent with it: the same era, age and state of repair, the same palette and material quality, the same window and door frame styles, and anything visible through openings (windows, doorways, passages) must belong to that same building and its surroundings. Lettering visible in the reference is reproduced EXACTLY — same characters, same mark, same position; never invent different wording from render to render. Lighting and time of day follow THIS prompt. Do NOT copy the reference's composition, framing or camera angle; only the building's physical identity carries over. 1536x864high   F)bg_id	sanitizersizequalitymax_attemptsbuilding_fp_pathbuilding_anchor_pathsame_place_anchor_pathaerial_ref_pathcapture_input_image_idsplate_multiroll_ctx_unmanned_retryc                	   	
+,- g }d}#j                         r|j                         d}!j                         r|j                         D ]'  }||j                         s|j                  |       ) d}#j                         r|j                         d}d}#j                         r|j                         d}d}#j                         r|j                         d}|sd}n1t        |      dk(  rj                         rd}ndt        |       }dd	g |d||||d
	}d	dlm} |,t        t        |dd            }|xr t        t        |dd            +|r)d	dlm}m	} |dz    |       z   }r|dz    |       z   }d|d<   d7+,
 	fd}|}
t               t        t        |dd            rmd	dlm}  xs i }! | | ||||!j                  d      xs d|!j                  d      |!j                  d      t        |!j                  d            
            S t        d
dz         D ]  }"|"|d<   	 	dd}#t        |      dk\  r.t        d       t!         d|||#d d|t        |      d!"      }$nWt        |      dk(  r%t        d#       t!         d|||#d d#|dd!"      }$n$t        d$       t!         d%|d|#d d$|d	d!"      }$|$st#        d&      j%                  |$       d'|d(<   t'              |d)<    ||      c S  |S # t(        $ r  t*        $ r4}%t'        |%      j-                         -t/        -fd*d+D              }&|&r|"
k  r	 t1        |"d,      }'j3                  |d-g |'.      }(|(j                  d/d      })|)j5                         j7                  d0      s	t8        |)z   })|)}|d1   j                  |(j                  d2             Y d}%~%# t*        $ r}*d3|* dd4 |d5<   |cY d}*~*cY d}%~%c S d}*~*ww xY wt:        j=                  d6|"
t'        |%      dd4        |"
k\  rt'        |%      dd4 |d5<   |cY d}%~%c S Y d}%~%#d}%~%ww xY w)8u  단일 배경 PNG 생성.

    - ``aerial_ref_path`` (W-L: 야외 location 의 항공뷰 배치도 — top-down
      site plan, 위상 SOT)가 존재하면 ref **최우선(첫 번째)** 으로 추가 —
      야외 bg 는 호출자가 fp_path 를 None 으로 교체해 분열된 개별 평면도의
      위상 지배를 제거한다.
      프롬프트의 위상 계약(AERIAL_SITE_GUIDANCE)도 호출자가 덧붙인다.
      None(default) = byte-identical.
    - ``fp_path`` 가 존재하면 ref 1순위로 사용.
    - ``prior_bg_paths`` 의 존재하는 path들을 ref 2순위로 concat.
    - ``same_place_anchor_path`` (W-K: 같은 location 의 anchor 렌더, 장소
      정체성 참조)가 존재하면 prior 다음·building anchor **앞**에 추가. 중복
      경로 dedup 은 호출자 책임(계약) — 프롬프트의 장소 정체성 지시도
      호출자가 덧붙인다. None(default) = byte-identical.
    - ``building_anchor_path`` (W-I: 같은 building 그룹의 anchor 렌더, 시각
      정체성 참조)가 존재하면 그 다음·building fp **앞**에 추가. 중복 경로
      dedup 은 호출자 책임(계약) — 프롬프트의 정체성 지시도 호출자가 덧붙인다.
      None(default) = byte-identical.
    - ``building_fp_path`` (W-G: same-building indoor floor plan, 구조 참조)가
      존재하면 ref **마지막**에 추가 — 기존 ref 우선순위 불변, 프롬프트의
      정합 지시는 호출자가 덧붙인다. None(default) = byte-identical.
    - 합산 ref ≥2 → ``client.images.edit(image=[...])`` (multi-image edit).
    - 합산 ref == 1 → ``client.images.edit(image=f)`` (single edit).
    - 합산 ref == 0 → ``client.images.generate`` (text-only).

    ``capture_input_image_ids`` 는 capture(generation_context scope 열림 시)의
    intermediate ImageAsset lineage 로 threading 만 한다(None=기존과 동일).

    moderation 류 차단이 감지되면 PromptSanitizer로 retry. attempt가 한도에
    도달하면 status='failed' + ``final_block_reason`` 으로 종료.

    Returns dict: ``{status, attempts, strategies, ref_used,
    final_block_reason, building_fp_attached, building_anchor_attached,
    same_place_anchor_attached, aerial_ref_attached, png_path?}``.
    FNT	text_only   fp_onlyrefs_failedr   )	statusattempts
strategiesref_usedfinal_block_reasonbuilding_fp_attachedbuilding_anchor_attachedsame_place_anchor_attachedaerial_ref_attached)settingsbackground_no_people_enabled!background_no_people_gate_enabled)build_no_people_clausebuild_retry_clausez

no_people_clause_attachedc                    rr| S ddl m}  || xs i j                  d      	
fd      S )Nr   )run_unmanned_gateproject_configc                     t        di d	dd d
ddddd	d
dddddddddS )Nopenai_clientimage_modelpromptout_pathfp_pathprior_bg_pathsr   r   r   r   r   r   r   r   r   r   r   r   T )render_one_background)_orig_promptr   r   r   r   r   r:   r7   r   r6   r9   r   r;   r   r   r   r   s   \/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/background_render.py<lambda>z6render_one_background.<locals>._gate.<locals>.<lambda>   s    2 +' $ "	
    .  $    * "2 &: (> !0  )@!" %8#$ !%%    )infor9   r4   retry_fn)(app.modules.pipeline.background_unmannedr3   get)resultr3   _gate_onr>   r   r   r   r   r   r   r:   r7   r   r6   r9   r   r;   r   r   r   r   s     r?   _gatez$render_one_background.<locals>._gate   sL     ?M	
 !/52::;KL  	
 	
rA   plate_multiroll_enabled)render_plate_multiroll
project_idr   
episode_idr4   force)
r8   	ref_pathsr9   r   rB   r   rK   rL   r4   rM   r$   )modelr   r   n   zbackground_render.edit_multi)sourceeditbackground_render)budget_sourcer   r&   	ref_count)moder8   rN   call_kwargscapture_rolecapture_metadatar   zbackground_render.edit_singlezbackground_render.generategeneratezempty b64 responseokr#   png_pathc              3  &   K   | ]  }|v  
 y w)Nr<   ).0kmsgs     r?   	<genexpr>z(render_one_background.<locals>.<genexpr>s  s!       	A S	s   )
moderationsafetycontent_policy
prohibitedpolicyblockedviolates	violation   SAFETY)original_promptblock_reasonblock_categoriesattemptsanitized_promptzBACKGROUND-ONLYr%   strategyzsanitize_failed:    r'   z&background_render %s attempt %d/%d: %s)rF   Dict[str, Any]returnrt   )existsappendlenapp.core.configr,   boolgetattrrD   r/   r0   r   $app.modules.pipeline.plate_multirollrJ   rE   ranger
   r   RuntimeErrorwrite_bytesstrr	   	Exceptionloweranyminsanitizelstrip
startswith_BACKGROUND_ONLY_REINFORCEMENTloggererror).r6   r7   r8   r9   r:   r;   r   r   r   r   r   r   r   r   r   r   r   r   rN   r+   pr*   r)   r(   r&   rB   	_settings_no_people_onr/   r0   rH   current_promptrJ   _ctxrp   rX   pngexcis_moderation	s_attemptsr	sanitizedserG   r>   ra   s.   `` ```````````````                         @@@r?   r=   r=   r   sC   p I"'='='?)"w~~/!=QXXZQ  "').D.K.K.M/0%)"$',@,G,G,I-.#'  #(8(?(?(A)*#	Y1	!49I3y>*+ " 4$<&@2
D  6L	95ACM H	>F"HH	

 &#9#;;f_'9';;F,0()"
 "
 "
H N#%	
 GI8%@A	
 #(b+xx-3xx-88$45txx()
  	 L1,-"Zo	 %"	K 9~"$,JK*!)' +!4)G!&$,%(^	& -D Y1$$,KL*!)' +!4)H!&$,%&	& -D  %,HI*!#)" +!4)E!&$,%&	& -D  "#788  %!DN"8}D;I .d K[ ' 	 *	c(.."C  	  M <!7  #GQI"++(6%-)+ )	 , B !#'92 >I$++-889JK$BY$N	%.N&--bffZ.@A   3DRD1I$31OD-.KK  LL8C# ,&-0Xds^)* 'Q*	sP   (CM  R4RB P	P5P0#P5$R(R0P55?R4RR)&r6   r   r7   r   r8   r   r9   r   r:   Optional[Path]r;   z
List[Path]r   r   r   zOptional[PromptSanitizer]r   r   r   r   r   intr   r   r   r   r   r   r   r   r   zOptional[List[str]]r   zOptional[Dict[str, Any]]r   rz   ru   rt   )__doc__
__future__r   loggingpathlibr   typingr   r   r   r   app.core.image_call_budgetr	   r
   #app.modules.llm.gpt_image_primitiver   app.modules.prompt_sanitizerr   	getLogger__name__r   r   BUILDING_FP_PLATE_GUIDANCEBUILDING_ANCHOR_PLATE_GUIDANCESAME_PLACE_PLATE_GUIDANCE$INTERIOR_SAME_BUILDING_TEXT_GUIDANCE*BUILDING_INTERIOR_EXTERIOR_ANCHOR_GUIDANCEr=   r<   rA   r?   <module>r      su   #   , , E 8			8	$5 @ B 
& ( %"A +2 +/'++/-1&*3748!'jj j 	j
 j j j j )j j j j %j )j +j  $!j" 1#j$ 2%j& 'j( )jrA   