
    Ojf$                    >   d Z ddlmZ ddl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  ej"                  e      ZdZd	Zd
ZdZd
ZdZdZ	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZddZddd	 	 	 	 	 	 	 	 	 ddZddeedddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZy)u  location_floor_plan pipeline 모듈 — 도면 prompt 생성 + PNG 호출.

Phase 3 (v3 plan, 2026-04-29). spike test 6 패턴 production화.
Phase 5 T4 (2026-04-29). prompt v2 multi-turn context + ref_paths multi-image edit.

순수 함수:
  - build_user_prompt: location 메타 + scenes + shots → user_prompt
  - validate_prompt_length: 500 ≤ len ≤ 6000

LLM 호출:
  - generate_floor_plan_prompt: gpt-5.5 → 영문 도면 prompt
  - generate_floor_plan_image: gpt-image-2 → PNG bytes (text-only or multi-image edit)
    )annotationsN)Path)AnyDictListOptional)ImageCallBudgetExceededreserve_current_call)call_gpt_image_bytesi  ip        	1024x1024highc                   g }|D ]^  }	|j                  d|	d    d|	j                  dd              |j                  |	j                  dd             |j                  d       ` dj                  |      j                         }
i }|D ]&  }|j	                  |d   g       j                  |       ( g }t        |      D ]\  }|j                  d| d       ||   D ],  }|j                  d	|d
    d|j                  dd              . |j                  d       ^ dj                  |      j                         }| j                  |||
||xs dj                         |xs dj                         |xs dj                               S )ut  user_template에 placeholder 치환.

    v2 prompt는 multi-turn 컨텍스트용 placeholder 2개 추가:
      - previous_floor_plans_block: 이전 도면 요약 (없으면 "")
      - building_group_context: 같은 building_group 도면 ref 참조 안내 (없으면 "")
    v1 template에는 이 placeholder가 없으므로 .format()이 무시 — backward-compat.
    z
### Scene scene_indexu    — heading text
z selected shots:z- Shot 
shot_indexz: description)location_short_idlocation_labelscenes_textselected_shots_textvisual_world_rulesprevious_floor_plans_blockbuilding_group_context)appendgetjoinrstrip
setdefaultsortedformatstrip)templater   r   scenesselected_shotsr   r   r   scenes_linesscr   by_sceneshshots_linessir   s                   ^/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/location_floor_plan.pybuild_user_promptr1   &   s   " !LjM):(;5	SUAV@WXYBFF62./B  ))L)002K02HB}-r299"= KXZt+;<=2,BL)9(:"RVVMSU=V<WXY 2	 
 ))K0779??+%/.4";;=$>$D"#K#K#M 6 <"CCE       c                    t        |       }|t        k  rt        d| dt               |t        kD  rt        d| dt               y)u5   gpt-image prompt 길이 검증. 500 ≤ len ≤ 6000.zfloor plan prompt too short: z < zfloor plan prompt too long: z > N)lenPROMPT_LEN_MIN
ValueErrorPROMPT_LEN_MAX)r   ns     r0   validate_prompt_lengthr9   T   sT    D	A>83~>NOPP>7s#n=MNOO r2   )opik_metadatacall_text_fnc           	        |ddl m} |}d}t        t        dz         D ]6  }	  |d| |||xs i       }|xs dj	                         }t        |       |c S  t        d	t         d
|       # t        $ r\}	|	}|t        k  rFt        |dz   z  }
t        j                  d|dz   t        |
|	       t        j                  |
       Y d}	~	Y d}	~	 ud}	~	ww xY w)u   gpt-5.5에 도면 prompt 자동 작성 요청. retry + length 검증.

    call_text_fn: 테스트용 주입. None이면 app.modules.llm.llm_client.call_text 사용.
    Nr   )	call_text   location_floor_plan)stepsystem_promptuser_promptproject_configr:   r   z6generate_floor_plan_prompt retry %d/%d (sleep %ds): %sz(generate_floor_plan_prompt failed after 
 retries: )app.modules.llm.llm_clientr=   rangeLLM_RETRY_MAXr&   r9   	ExceptionLLM_RETRY_BACKOFF_STEPloggerwarningtimesleepRuntimeError)rA   rB   rC   r:   r;   r=   last_excattemptr   excdelays              r0   generate_floor_plan_promptrS   ]   s     8 $(H*+	*+'-+1rD JB%%'D"4(K ,0 
2=/H:V   
	H&.'A+>LaKs 

5!
	s   1A--	C6ACCzgpt-image-2.5-sunburstfloor_plan_image)	ref_pathsmodelsizequalitycapture_rolecapture_input_image_idscapture_extra_metadatac        	        b   g }	|xs g D ]D  }
|
	 |
j                         r.|
j                         j                  dk\  r|	j                  |
       F d}|||dd}dfd}t        t        dz         D ]  }	 |	s't        d       t        |d	| d||| |dd
            }n0t        d       t        |d| |	|||d |dt        |	            	      }|rt        |      dk  rt        dt        |       d      |c S  t        dt         d|       # t        $ r"}t
        j                  d|
|       Y d}~-d}~ww xY w# t        $ r  t        $ r]}|}|t        k  rGt        |dz   z  }t
        j                  d|dz   t        ||       t!        j"                  |       Y d}~1Y d}~ d}~ww xY w)u  gpt-image-2 호출 → PNG bytes 반환. retry 3회.

    ref_paths가 None/빈 리스트/모두 invalid면 text-only(images.generate). 1+ valid이면
    multi-image edit(images.edit). 유효성 기준: 파일 존재 + size >= 1024 bytes.

    Phase 5 T4: building_group multi-turn ref 도입. backward-compat — ref_paths 미지정 시
    Phase 3 동작과 동일.

    ``capture_role`` — 이 함수는 floor_plan / outdoor aerial·blocking·sketch /
    registered pose guide 등 여러 용도의 수렴점이므로, capture pipeline_role 은 호출자가
    결정한다(Phase C scope 배선). default-off 라 scope 미개방 시 미사용.
    Ni   z6generate_floor_plan_image: ref_path stat failed %r: %sr>   )rV   rW   rX   r8   c                :    t        xs i       }| |d<   ||d<   |S )Nbudget_source	ref_count)dict)sourcer_   metar[   s      r0   _capture_metaz0generate_floor_plan_image.<locals>._capture_meta   s.    
  $$:$@bA &_%[r2   zlocation_floor_plan.generate)ra   generater   )modepromptrU   call_kwargsrY   rZ   capture_metadatazlocation_floor_plan.editeditT)re   rf   rU   rg   rY   rZ   edit_image_as_listrh   z!empty or too-small PNG returned: z bytesz5generate_floor_plan_image retry %d/%d (sleep %ds): %sz'generate_floor_plan_image failed after rD   )ra   strr_   intreturnzDict[str, Any])existsstatst_sizer   OSErrorrJ   rK   rF   IMAGE_RETRY_MAXr
   r   r4   rN   r	   rH   rI   rL   rM   )rf   openai_clientrU   rV   rW   rX   rY   rZ   r[   
valid_refsprQ   rO   rg   rc   rP   	png_bytesrR   s           `         r0   generate_floor_plan_imagerw      s   0  Jo2o9	]xxzaffh..$6!!!$  %)H "4G!LK 1,-,	$,JK0!#!" +!-,C%23QST%U		 %,FG0!!( +!-,C'+%22C
O&E	 I 5"7I7GvN  A .^ 
1/1B*XJW E  	]NNSUVX[\\	]h ' 	 
	H(.'A+>KaK% 

5!
	s1   >D6BD?	D<D77D<?F.AF))F.)r   r   )r'   rk   r   rk   r   rk   r(   List[Dict[str, Any]]r)   rx   r   rk   r   rk   r   rk   rm   rk   )r   rk   rm   None)
rA   rk   rB   rk   rC   r   r:   Optional[Dict[str, Any]]rm   rk   )rf   rk   rs   r   rU   zOptional[List[Path]]rV   rk   rW   rk   rX   rk   rY   zOptional[str]rZ   zOptional[List[str]]r[   rz   rm   bytes)__doc__
__future__r   loggingrL   pathlibr   typingr   r   r   r   app.core.image_call_budgetr	   r
   #app.modules.llm.gpt_image_primitiver   	getLogger__name__rJ   r5   r7   rG   rI   rr   
IMAGE_SIZEIMAGE_QUALITYr1   r9   rS   rw    r2   r0   <module>r      s   #    , , E			8	$ 
 ')"$+++ + !	+
 )+ + !$+  + 	+\P /3++ + 	+
 ,+ 	+d '+) "4377;cc c $	c
 c c c  c 1c 5c cr2   