
    )ajn                       U d Z ddlmZ ddlmZmZ ddlmZmZm	Z	m
Z
mZmZmZmZ dZded<   dZded	<   d
Zded<   dZded<    eh d      Zded<   dZded<   dZded<   dZded<   dZded<   dddddd Zd!ed"<    G d# d$e      Ze G d% d&             Zd6d'Zd7d(Z	 	 	 	 	 	 d8d)Z 	 	 	 	 	 	 d9d*Z!	 	 	 	 	 	 d:d+Z"	 	 	 	 	 	 	 	 d;d,Z#	 	 	 	 d<d-Z$d.Z%ded/<   	 d=	 	 	 	 	 d>d1Z&	 	 	 	 d?d2Z'd3d0d4	 	 	 	 	 	 	 	 	 	 	 	 	 d@d5Z(y0)Au  W20C: shot-aware background render adapter.

Materialization layer between the W20B ``shot_aware_bg_render_plan``
LLM-emitted graph and the W19B-3 ``background_render`` image call site.

Pure deterministic helpers. **No openai / litellm / sqlalchemy /
ImageAsset / DB imports.** The adapter:

  1. Gates the LLM plan via :func:`is_plan_production_clear` —
     a plan must be ``shot_aware_bg_render_plan_status == "ok"`` AND
     ``production_clear is True`` before any image work is allowed.
  2. Orders the graph nodes by ``node_index`` ascending.
  3. Materializes each node's reference / camera decision into the
     concrete shape ``render_one_background`` consumes:
     ``reference_paths`` (FP for anchor / catalog png paths otherwise)
     and a ``reference_guidance_prefix`` prepended to the LLM-output
     ``t2i_prompt`` before the image call.

Hard invariants (fail-closed, no fallback):
  - ``mode`` ∈ ``ALLOWED_MODES`` (exact set membership).
  - ``len(selected_refs) <= MAX_REFS_PER_BG`` (=2) for all modes.
  - ``fp_seeded_anchor`` → empty ``selected_refs`` AND a non-empty
    ``base_fp_png_str`` (FP is the only reference).
  - Every non-anchor mode → at least one ``selected_refs`` entry, FP
    NOT in ``reference_paths``.
  - ``two_refs_distinct_spaces`` → exactly two ``selected_refs``.
  - Every ``ref_bg_id`` referenced by a non-anchor node MUST already
    exist in the catalog passed by the caller. The adapter never
    re-orders / re-scores / re-classifies. No top-K. No semantic
    parsing.

The adapter does NOT mutate the catalog: the caller (the
``background_render`` step) is responsible for appending a successful
render's catalog entry before materializing the next node.

This module deliberately duplicates the small style / mode-guidance
text registry (rather than importing the W19B-3 planner) so it stays
import-clean and the two reference-graph code paths
(``w18j_overlap`` vs ``shot_aware_plan``) remain decoupled.

Generic only: no scenario tokens, no work-specific nouns, no bg_id
keyed prose.
    )annotations)	dataclassfield)AnyDict	FrozenSetListMappingOptionalSequenceTuplefp_seeded_anchorstrANCHOR_MODEtwo_refs_distinct_spacesTWO_REFS_MODE   intMAX_REFS_PER_BGdwelling_zone_mapRENDER_ACTION_SOURCE_ZONE_MAP>   r   reference_derivedrelated_style_new_spacesame_physical_space_viewr   zFrozenSet[str]ALLOWED_MODES)visible_space_directivecamera_framing_directivesubject_position_directivestate_cue_directivenegative_continuity_directivezTuple[str, ...]RENDER_GUIDANCE_FIELDS)camera_unitcamera_celllook_at_unitlook_at_cell	lens_enumfov_deg_CAMERA_REQUIRED_FIELDS)framing_notes_CAMERA_OPTIONAL_FIELDSuK  STYLE CONTRACT — an ordinary, modest, real place, exactly as the supplied plan, reference images and prose establish it. Treat the result as an actual reference photograph of that real place: everyday materials, believable wear, plain practical furnishings appropriate to the place's established type and use, natural daylight or simple ceiling lighting. Strictly avoid luxury, showroom, hotel-suite, boutique-design, high-end-retail, or magazine-staging styling. No marble surfaces, no chandeliers, no designer furniture, no polished decor displays, no curated styling props, no glossy magazine finish. When the supplied inputs establish a currently occupied dwelling, its rooms carry the ordinary, unstaged traces of the residents' daily life, accumulated naturally on surfaces and in corners — never a bare, just-moved-in or showroom-empty room — and which particular belongings show this is the image's own choice. Whenever the supplied inputs establish a different place type, occupancy state, age or condition, that established fact wins over every default in this entire style contract._STYLE_CONTRACT_PREAMBLEu  Use the supplied base floor plan as the only visual reference. Render a fresh photoreal interior under the modest style contract. The base plan's numbered markers, labels, arrows and diagram graphics are planning aids ONLY — the rendered photograph must contain NONE of them: no numerals, no letters, no circles or arrows, no diagram lines of any kind anywhere in the image. Treat the markers purely as invisible spatial guidance.a  Inherit the visible identity of this dwelling from the supplied prior background reference images. Match floor, wall, window, lighting, and fixed-furniture palette so the space reads as the same home; do not redraw any transient cue already present in the references.u  The supplied reference image shows the SAME physical room as this background, from a different camera angle or framing. Inherit the exact architecture, floor, walls, windows, fixed furniture and lighting of that room — this is literally the same space, only the viewpoint changes.zThe supplied reference image shows a related part of the same dwelling, not the current room. Inherit the overall material and lighting identity only; the new space is its own architectural room and must read as the same home.a  The two supplied references depict different visible spaces within the same dwelling that are co-visible from the planned camera. Inherit the dwelling's shared material and lighting identity from both, but keep each room's architecture distinct as the references show.)r   r   r   r   r   Dict[str, str]_MODE_GUIDANCEc                      e Zd ZdZy)ShotAwareRenderAdapterErrorz6Fail-closed signal from the shot-aware render adapter.N)__name__
__module____qualname____doc__     g/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/shot_aware_bg_render_adapter.pyr/   r/      s    @r5   r/   c                      e Zd ZU dZded<   ded<   ded<   ded<   ded	<   ded
<   ded<   ded<   ded<   ded<   ded<   ded<   ded<    ee      Zded<   ddZy)MaterializedShotAwareDecisionu	  Concrete render-ready record for one plan node.

    Persisted on ``data.groups[bg_id]`` by the step layer as audit /
    downstream-readable fields (``reference_decision``,
    ``camera_decision``, ``reference_guidance_prefix``,
    ``render_guidance``, …).
    r   bg_idfp_idr   
node_indexmodeboolis_dwelling_identity_anchor	rationalez	List[str]reference_pathssource_bg_idsfp_includedDict[str, Any]reference_decisioncamera_decisionr,   render_guidancereference_guidance_prefix)default_factorydiagnosticsc                   | j                   | j                  | j                  | j                  | j                  | j
                  t        | j                        t        | j                        | j                  t        | j                        t        | j                        t        | j                        | j                  t        | j                        dS )Nr9   r:   r;   r<   r>   r?   r@   rA   rB   rD   rE   rF   rG   rI   )r9   r:   r;   r<   r>   r?   listr@   rA   rB   dictrD   rE   rF   rG   rI   )selfs    r6   to_dictz%MaterializedShotAwareDecision.to_dict   s    ZZZZ//II+/+K+K#D$8$89!$"4"45++"&t'>'>"?#D$8$89#D$8$89)-)G)G 0 01
 	
r5   N)returnrC   )	r0   r1   r2   r3   __annotations__r   rL   rI   rO   r4   r5   r6   r8   r8      sf     JJO
I!%%N&&####"""48K8
r5   r8   c                t    t        | t              sy| j                  d      dk7  ry| j                  d      du S )u  Gate — caller MUST check this before reading the plan.

    Both conditions are required, both with **exact identity** checks
    (no truthy-coercion — fail-closed contract):
      - ``shot_aware_bg_render_plan_status == "ok"`` — the plan's LLM
        output passed every validator inside W20B.
      - ``production_clear is True`` — readback was not synthetic and
        validators all passed. A string ``"true"``, integer ``1``, or
        any non-empty list / dict does NOT pass the gate; the upstream
        producer (``shot_aware_bg_render_plan.build_render_plan_for_fp``)
        only ever writes a Python boolean here, so any non-bool value
        is a contract violation we surface as ``False``.

    Anything else (non-dict, missing keys, wrong types) → False.
    F shot_aware_bg_render_plan_statusokproduction_clearT)
isinstancerM   get)plans    r6   is_plan_production_clearrY      s;      dD!xx23t;88&'4//r5   c                x   t        | t              sg S | j                  d      xs i }|j                  d      xs g }g }t        |      D ]5  \  }}t        |t              st	        d| d      |j                  |       7 |j                  d        t        d |D              r|j                  d        |S )	u  Return the plan's graph nodes sorted by ``node_index`` ascending.

    W21B Phase 2 exception: in a dwelling_zone_map plan (a node carries
    ``render_action_source == dwelling_zone_map``) the fresh anchors are ordered
    BEFORE their reuse aliases (stable secondary sort — node_index still breaks
    ties) so the consumer renders a zone's plate before any alias reuses it. The
    legacy / partition path keeps pure ``node_index`` order.

    Plans / graphs without nodes → empty list. Non-dict entries inside
    ``graph.nodes`` are a hard error (the W20B validator should have
    already caught this; the adapter mirrors that contract).
    graphnodeszplan.graph.nodes[] is not a dictc                8    t        | j                  dd            S )Nr;   r   )r   rW   ns    r6   <lambda>zordered_nodes.<locals>.<lambda>  s    3quu\156r5   )keyc              3  L   K   | ]  }|j                  d       t        k(    yw)render_action_sourceN)rW   r   ).0r`   s     r6   	<genexpr>z ordered_nodes.<locals>.<genexpr>%  s(      A 	
$%)FFs   "$c                ,    | j                  d      rdS dS )Nneeds_new_plater      )rW   r_   s    r6   ra   zordered_nodes.<locals>.<lambda>)  s    AEE*;$<qC!Cr5   )rV   rM   rW   	enumerater/   appendsortany)rX   r[   r\   outidxr`   s         r6   ordered_nodesrp     s     dD!	HHW#EIIg$"E "CE"Q!T"-#C58  	

1 # HH6H7    	CDJr5   c                h   |j                  d      xs g }t        |t              st        d| d      g }t	        |      D ]m  \  }}t        |t
              st        d| d| d      |j                  d      }t        |t              r|st        d| d| d      |j                  |       o |S )Nselected_refsbg_id=z/ reference_decision.selected_refs is not a listz selected_refs[r]   	ref_bg_idz#].ref_bg_id missing or not a string)rW   rV   rL   r/   rj   rM   r   rk   )r9   ref_decisionrawrn   irrids          r6   _coerce_selected_refsrz   -  s     

?
+
1rCc4 )UILM
 	
 !#C#1!T"-	?C  eeK #s#3-	 4    	

1  Jr5   c           
        |j                  d      }t        |t              st        d| d      t	        t        |j                               t        t              z
        }|r!t        d| d| dt        t                     i }t        D ]J  }|j                  |      }t        |t              r|j                         st        d| d| d      |||<   L |S )uV  Pass-through copy of the LLM-emitted ``render_guidance`` block.

    Every field listed in :data:`RENDER_GUIDANCE_FIELDS` must be present
    and a non-empty string. No semantic parsing — the adapter only
    enforces shape so a malformed plan never reaches the renderer.
    Extra keys are rejected (the prompt-pack schema is closed).
    rF   rs   z render_guidance is not a dictz" render_guidance has unknown keys z
; allowed=z render_guidance.z must be non-empty string)rW   rV   rM   r/   sortedsetkeysr!   rL   r   strip)r9   noderv   
extra_keysrn   fnamevalues          r6   _coerce_render_guidancer   E  s     (($
%Cc4 )UI;<
 	
 CHHJ#.D*EEFJ)UI?l*T*@%A$BD
 	
 C'%%U[[]-	!25' :# $  E
 ( Jr5   c                ~   t         D cg c]	  }||vs| }}|rt        d| d|       dg}t         D ]  }|j                  d| d||           t        D ]L  }|j	                  |      }t        |t              s%|j                         s6|j                  d| d|       N dj                  |      S c c}w )a  Deterministic camera-decision block for the guidance prefix.

    The exact LLM-validated camera_decision fields are echoed verbatim,
    one ``key: value`` line per required field, ``framing_notes`` only
    when present and non-empty. No re-formatting / re-interpretation.
    rs   z  camera_decision missing fields z-CAMERA DECISION (exact, from validated plan):  - : 
)	r(   r/   rk   r*   rW   rV   r   r   join)r9   rE   fmissinglinesr   r   s          r6   _format_camera_sectionr   f  s     +*aa.F*   )UI=gYG
 	
 ==E(tE7"_U%;$>?@ )(##E*eS!ekkmLL4wb	23 ) 99Us
   	B:B:c           	         d| g}|r|j                  d       |rPt        |      D ]A  \  }}|j                  d      }|j                  d      }|j                  d| d|d|       C n|s|j                  d       d	j                  |      S )
a  Deterministic reference-plan summary for the guidance prefix.

    Echoes the chosen mode + the LLM-emitted ``ref_bg_id`` /
    ``physical_space_id`` exact strings. ``fp_seeded_anchor`` is
    surfaced explicitly when the base FP is the only reference.
    zREFERENCE PLAN: mode=z'  - reference: base floor plan (anchor)rt   physical_space_idz  - selected_refs[z]: ref_bg_id=z physical_space_id=z  - selected_refs: (none)r   )rk   rj   rW   r   )r<   rr   rB   r   rw   rx   rt   psis           r6   _format_reference_sectionr     s     %TH-.E>?m,DAqk*I%%+,CLL$QC}YM B%%(G- - LL4599Ur5   c                t    dg}t         D ]  }|j                  d| d| |            dj                  |      S )z8Deterministic render-guidance block (exact LLM strings).z&RENDER GUIDANCE (from validated plan):r   r   r   )r!   rk   r   )rF   r   r   s      r6   _format_render_guidance_sectionr     sD     66E'tE7"_U%;$<=> (99Ur5   u_  VIEW AUTHORITY: the CAMERA DECISION and RENDER GUIDANCE above are the sole authority for camera position, camera height and view direction. If any prose below implies a different vantage point or an opposite view direction, keep this committed view and restage that prose's content inside it — never blend the two views into one impossible geometry._VIEW_AUTHORITY_CLAUSENc                Z   ddd}|fd|D        D ch c]  }|| }}g }| D ]{  }t        |t              st        |j                  d      xs d      j	                  d      sB| |j                  d            }|||vrb|j                  t        |             } |S c c}w )	u  bg별 STRUCTURE FACTS 선별 (Codex 리뷰 BLOCKING-1).

    floor_plan_prompt 계약: numbered_elements 는 base_*(영구 구조)와
    state_overlay_*(일시 상태 — 도면에도 그리지 않는 항목)를
    base_layer_decision 으로 분리하고, bg별 use/ignore_numbered_elements
    exact integer join 을 하류 계약으로 정의한다. STRUCTURE FACTS 는
    **base_* ∩ 해당 bg 의 use 목록** 만 — transient 는 기존 overlay
    소비부(floor_plan_overlay_payload/background_prompt) 관할 유지.

    use_numbered_elements=None(해당 bg 의 camera_recommendation 부재) =
    base_* 전체(구조 식별 사실은 bg 무관 유효 — join 불가 시 보수 포함).

    exact join = **정수 항등**(Codex 재리뷰 NARROW-2): malformed LLM 값
    (bool/str/fractional float)은 양쪽 모두 default-deny — int() 절삭으로
    1.9↔1.1 이 매칭되던 오염 경로 차단.
    c                H    t        | t              st        | t              sy | S N)rV   r=   r   )vs    r6   
_exact_intz*select_structure_facts.<locals>._exact_int  s    ajC&8r5   Nc              3  .   K   | ]  } |        y wr   r4   )re   r   r   s     r6   rf   z)select_structure_facts.<locals>.<genexpr>  s      '<!
1'<s   base_layer_decision base_number)r   r   rP   zOptional[int])rV   r
   r   rW   
startswithrk   rM   )numbered_elementsuse_numbered_elementsuse_setr`   rn   elnumr   s          @r6   select_structure_factsr     s    (
 "G('<
!   	 

 !#C"g&266/06B7BB7KRVVH-.C{c0

48   J!
s   B(c                   dg}| D ]  }t        |t              st        |j                  d      xs d      j	                         }|sC|j                  d      }t        |j                  d      xs d      j	                         }t        |j                  d      xs d      j	                         }|d| dnd}|rd	| d
| n|}|j                  ||z   |rd| ndz           t        |      dk(  rydj                  |      S )uH  STRUCTURE FACTS 블록 (E2E11 ② — L04B01/02 계단 누락 실측).

    floor_plan_prompt ``numbered_elements``(dossier 파생 SOT)의 category/
    label/position_hint 를 **원문 그대로** 나열 — 의미 파싱·substring 판단
    없음(글자 판단 금지 계약). 계약문: 뷰 안에 위치가 들어오는 요소는
    반드시 존재, 연결·상하(승강) 방향·상대 배치 반전/미러 금지, RENDER
    GUIDANCE 가 뷰에서 배제한 요소는 생략 가능. 생성·판정·critique 가
    prompt 를 공유하므로 검출 축을 겸한다.
    uQ  STRUCTURE FACTS (floor-plan truth for this location): every element listed below whose stated position falls inside the committed view MUST appear, with its position, connections and vertical (up/down) direction exactly as stated — never mirror, reverse or omit it. Elements the RENDER GUIDANCE excludes from this view may be left out.labelr   r   categoryposition_hintr   z. [z] u    — ri   r   )rV   r
   r   rW   r   rk   lenr   )	structure_factsr   r   r   r   r   hintheadbodys	            r6   _format_structure_facts_sectionr     s   	%E "g&BFF7O)r*002ffXrvvj)/R0668266/*0b1779!$cU"~f*28*Bug&TD[deD6NCD  5zQ99Ur5   T)validate_reference_catalogr   c                   t        | t              st        d      | j                  d      }t        |t              r|st        d      | j                  d      }|t
        vrt        d|d|      | j                  d      xs i }t        |t              st        d|d      t        ||	      }	t        |	      t        kD  r!t        d|d
t        |	       dt               g }
g }d}|t        k(  r4|	rt        d|dt        |	       d      |st        d|d      d}|g}
n|	st        d|d|d      |t        k(  r)t        |	      dk7  rt        d|dt        |	       d      |	D ]k  }|d   }|j                  |      }|s/|r,t        d|d|dt        |j                                d      J|
j                  |       |j                  |       m | j                  d      xs i }t        |t              st        d|d      t        ||       }t        ||	|      }t!        ||      }t#        |      }t$        dz   t&        |   z   dz   |z   dz   |z   dz   |z   dz   }|&|rt)        |       nd!}|r||dz   z  }|t*        dz   z  }| j                  d"d#      }	 t-        |      }t3        ||||t5        | j                  d%d            t	        | j                  d&      xs d!      |
||t        |      t        |      ||g '      S # t.        t0        f$ r}t        d|d$|      |d}~ww xY w)(u  Concrete render inputs for a single plan node.

    ``catalog`` is a ``bg_id -> absolute_png_path`` mapping built by
    the caller from previously rendered nodes in this fp's queue.
    For ``fp_seeded_anchor`` the catalog is ignored (the FP is the
    only reference). For every other mode, every ``ref_bg_id`` that
    the LLM selected MUST already be present in the catalog —
    otherwise this raises and the caller fails the bg.

    ``validate_reference_catalog`` (default ``True`` — legacy/byte-identical):
    when ``False`` the selected_refs shape is still validated (count / mode
    rules / metadata), but a ``ref_bg_id`` missing from the ``catalog`` is
    SKIPPED rather than raising. This is for the W21B-w4 #4(C) substrate
    consumer, which owns reference resolution (FP + ref_tree_parents with a
    missing-parent graceful drop) and only needs the prompt prefix / node-shape
    gate from this helper — never its ``reference_paths``.
    znode is not a dictr9   znode.bg_id missingr<   rs   z unknown mode=rD   z! reference_decision is not a dict)r9   ru   z selected_refs count=z exceeds max=Fz5 fp_seeded_anchor must have empty selected_refs (got )z6 fp_seeded_anchor requires a resolved base FP png pathTz mode=z# requires at least one selected_refr   z@ two_refs_distinct_spaces requires exactly 2 selected_refs (got rt   z ref_bg_id=z not in catalog (catalog keys=rE   z camera_decision is not a dict)r9   r   )r<   rr   rB   )r9   rE   )rF   z

N)r   r   r;   r   z" node_index not coercible to int: r>   r?   rK   )rV   rM   r/   rW   r   r   rz   r   r   r   r   r|   r~   rk   r   r   r   r   r+   r-   r   r   r   	TypeError
ValueErrorr8   r=   )r   r:   base_fp_png_strcatalogr   r   r9   r<   ref_decision_rawrr   r@   rA   rB   rx   ry   png_path
camera_rawrF   reference_sectioncamera_sectionrender_guidance_sectionguidancefacts_sectionnode_index_rawr;   excs                             r6   materialize_decisionr     sU   4 dD!)*>??HHWEeS!)*>??88FD= )UI^D84
 	
 xx 45;&-)UI>?
 	
 *"2M =O+)UI23}3E2F G*+-
 	

 "$O!MK{-	 "&&)-&8%9<  -	 "# $  *+-	x 0    = S%71%<-	 "003M0B/C1F  AK.C{{3'H-5 	SG <))/)?(@C  ""8,  %  +,2Jj$')UI;<
 	
 .EEO1#
 ,ZN >'
 	!
	

	 	 		
 	 	 	 "	" 		  "  ,OL$& 	 ..H*V33XXlA.N(
 )$(HH2E:%
 dhh{+1r2'# 01Z('"*!  z" )UI? "
 	s   ,L L=&L88L=)rX   r   rP   r=   )rX   r   rP   List[Dict[str, Any]])r9   r   ru   rC   rP   r   )r9   r   r   rC   rP   r,   )r9   r   rE   rC   rP   r   )r<   r   rr   r   rB   r=   rP   r   )rF   r,   rP   r   r   )r   Sequence[Mapping[str, Any]]r   zOptional[Sequence[Any]]rP   r   )r   r   rP   r   )r   rC   r:   r   r   zOptional[str]r   zMapping[str, str]r   r=   r   z%Optional[Sequence[Mapping[str, Any]]]rP   r8   ))r3   
__future__r   dataclassesr   r   typingr   r   r   r	   r
   r   r   r   r   rQ   r   r   r   	frozensetr   r!   r(   r*   r+   r-   	Exceptionr/   r8   rY   rp   rz   r   r   r   r   r   r   r   r   r4   r5   r6   <module>r      s=  *V # (	 	 	 &S %/s /  &9 s 8 !*!~ +  ,   ,>  =$3 # H			!	=	"G)" )XA) A (
 (
 (
V0.'T!/0'B$24
 ( 	
 	:&   6:+2+2+ +\"3""V (,=Ag
g g #	g
 g !%g ;g #gr5   