
    OjGk                       U d Z ddlmZ ddlmZmZmZmZmZm	Z	  e
h d      Zded<    e
ddh      Zded	<   eez  Zded
<   dZded<   ddddZded<   dZded<    G d de      Zd*dZ	 	 	 	 d+dZ	 	 	 	 d,dZ	 	 	 	 d-dZ	 	 	 	 	 	 d.dZd/dZ	 	 	 	 d0dZ	 	 	 	 d1d Z	 	 	 	 	 	 	 	 d2d!Z	 	 	 	 d3d"Z	 	 	 	 	 	 d4d#Z	 	 	 	 	 	 	 	 d5d$Z	 	 	 	 d6d%Z 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d7d&Z!d'ed(	 	 	 	 	 	 	 	 	 	 	 d8d)Z"y')9u
  W20A: base location dossier — pure deterministic builder.

Consumes existing W19A v6 / W19B-1 outputs and produces a per-fp_id dossier
that downstream W20B (shot-aware LLM planner) consumes.

LLM / image / VLM / DB / ImageAsset write 0. **The VLM 10x10 readback gate
is represented in the artifact but never invoked in W20A — only the
synthetic_placeholder path is emitted.** Real VLM wiring is deferred to a
future wave behind explicit approval.

Dossier shape (per fp_id):

    {
      "fp_id": str,
      "fp_image_path": Optional[str],            # from floor_plan_render cp
      "grid_size": [10, 10],

      "dwelling_identity": {
          "structure": { unit_markers, opening_markers,
                         fixed_fixture_markers, anchor_furniture_markers,
                         marker_count_per_layer },
          "materials": { "diagnostics": [...] },             # LLM-emit slot
          "fixed_elements_inventory": [...],                 # base_persistent_fixture
          "standard_of_living_band": "modest_residential",
          "lighting_identity": { "diagnostics": [...] },     # LLM-emit slot
      },

      "fp_geometry_candidates": {
          "grid_size": [10, 10],
          "camera_cell_candidates_per_unit": {},      # populated by VLM
          "look_at_cell_candidates_per_unit": {},     # populated by VLM
          "view_cone_lens_enum_map": {wide:90, normal:50, telephoto:25},
          "visible_units_candidates": {},             # populated by VLM
          "visible_openings_candidates": {},          # populated by VLM
          "wall_door_invalidation_diagnostics": [],
          "diagnostics": [...],
      },

      "fp_geometry_vlm_readback": {
          "status": "synthetic_placeholder",
          "grid_size": [10, 10],
          "observed_markers": None,
          "missing_markers": None,
          "extra_markers": None,
          "confidence": None,
          "gate_decision": "synthetic_pass",
          "diagnostics": [...],
      },

      "base_marker_inventory": [...],            # base_*
      "overlay_marker_inventory": [...],         # state_overlay_* per (bg_id, number)

      "per_bg_render_facts_by_bg_id": {          # W20A revision (Codex)
          # exact-ID per-bg facts W20B planner consumes without re-reading
          # raw W19 / master_plan checkpoints. integer / string equality only.
          "<bg_id>": {
              "bg_id", "fp_id",
              "target_unit_marker_numbers",
              "dominant_target_unit_marker_number",
              "use_numbered_elements", "ignore_numbered_elements",
              "base_marker_numbers_to_reference",
              "transient_marker_numbers_to_describe",
              "ignored_state_overlay_marker_numbers",
              "clean_background_expected",
              "applies_to_shots", "depends_on_bg",
              "diagnostics",
          },
      },

      "anchor_selection_metadata": {
          "candidate_bg_ids": [...],
          "selection_diagnostics": [...],
          "selected_anchor_bg_id": None,         # W20B owns selection
      },

      "diagnostics": [...],
    }

W20 spec boundaries enforced:
  - exact-ID only (marker numbers / bg_id / fp_id exact equality).
  - no substring / regex / lexical inference over labels or position hints.
  - static contract carries no scenario-specific tokens — runtime label
    passthrough (label / position_hint) is data, not contract.
  - no BG-id keyed prose dict.
  - reference / camera selection NOT performed here (W20B).
  - anchor selection NOT performed here — only the candidate surface.
    )annotations)AnyDict	FrozenSetListOptionalTuple>   base_openingbase_structural_unitbase_persistent_fixturebase_persistent_furniturezFrozenSet[str]BASE_LAYER_DECISIONSstate_overlay_plot_cuestate_overlay_transient_objectSTATE_OVERLAY_DECISIONSALL_LAYER_DECISIONS)
   r   Tuple[int, int]DEFAULT_GRID_SIZEZ   2      )widenormal	telephotozDict[str, int]VIEW_CONE_LENS_ENUM_MAPmodest_residentialstrSTANDARD_OF_LIVING_BAND_DEFAULTc                      e Zd ZdZy)BaseLocationDossierErrorz+Fail-closed signal for the dossier builder.N)__name__
__module____qualname____doc__     `/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/base_location_dossier.pyr!   r!      s    5r'   r!   c                    | xs i j                  d      xs i }|j                         D ci c],  \  }}t        |t              r|j                  d      dk(  r||. c}}S c c}}w )Nfloor_plansstatusokgetitems
isinstancedict)fp_prompt_datafpsfidentrys       r(   _index_floor_plansr6      sk    R
$
$]
3
9rC ))+%JCeT"uyy':d'B 	U
%  s   1A"c                    i }| xs i j                  d      xs i }|j                         D ]*  \  }}t        |t              s|j                  d      ||<   , |S )u=  Best-effort fp_id → png_path resolution.

    The floor_plan_render cp shape carries
    ``data.floor_plans[fp_id].png_path``. Dossier accepts a None when the
    path is missing — the gate registers a diagnostic but does not raise,
    because a dossier may legitimately be built before fp render completes.
    r*   png_pathr-   )fp_render_dataoutr3   r4   r5   s        r(   _index_floor_plan_renderr;      s]     %'CR
$
$]
3
9rCiik
U%&99Z(C " Jr'   c                   i }| xs i j                  d      xs i }|j                         D ]  \  }}t        |t              r|j                  d      dk7  r+|j                  d      xs i }|j                  d      xs g D ]`  }t        |t              r|j                  d      s%|j                  d      xs g }|s=|j	                  |d   g       j                  |       b  |S )	Nplansr+   r,   planbackgroundsbg_iddepends_on_fpr   )r.   r/   r0   r1   
setdefaultappend)master_plan_datagroupedr=   _gidr5   r>   bgdependss           r(   _collect_bg_specs_by_fprI      s     02G#((17RE{{}e%&%))H*=*Eyy &B((=)/R/Bb$'rvvgff_-3Gwqz2.55b9 0	 % Nr'   c                    | xs i j                  d      xs i }i }|j                         D ]M  \  }}t        |t              s|j                  d      }t        |t              s9||j                  |i       |<   O |S )Noverlaysfp_id)r.   r/   r0   r1   r   rB   )overlay_payload_datarK   rE   r@   payloadrL   s         r(   _collect_overlays_by_fprO      s~     %*//
;ArH46G"..*w'4(G$%%/65"%e, + Nr'   c                    |j                         D ]g  \  }}|j                  d      }t        |t              st	        d| d| d|d      |t
        vsEt	        d| d| d|dt        t
                      y)	a  Re-assert the W19B-1 partition enum on the dossier consumer side.

    Mirrors floor_plan_overlay_payload's exact-ID partition check. We re-do
    this here rather than trust the overlay payload alone, because the
    dossier consumes floor_plan_prompt v6 directly for its own marker
    inventory and a downstream change in the overlay step should not
    silently relax the dossier's contract.
    base_layer_decisionfp_id=z	 marker #z0 base_layer_decision missing or non-string (got )z base_layer_decision=z not in allowed enum N)r/   r.   r0   r   r!   r   sorted)rL   markersnumnedecisions        r(   _validate_base_partitionrY      s     ==?R66/0(C(*	3% 0&&.\4  ..*	3%/D,3-./1  #r'   c                    | |j                  dd      |j                  dd      |j                  dd      |j                  dd      dS )Nlabel categoryposition_hintrQ   )numberr[   r]   r^   rQ   )r.   )rV   rW   s     r(   _marker_inventory_entryr`      sH    $FF:r*4!vv&;R@ r'   c           
        g }g }g }g }| j                         D ]r  \  }}|j                  d      }|dk(  r|j                  |       .|dk(  r|j                  |       E|dk(  r|j                  |       \|dk(  sb|j                  |       t |j                          |j                          |j                          |j                          ||||t	        |      t	        |      t	        |      t	        |      ddS )NrQ   r   r
   r   r   )r   r
   r   r   )unit_markersopening_markersfixed_fixture_markersanchor_furniture_markersmarker_count_per_layer)r/   r.   rC   sortlen)rU   unitsopeningsfixtures	furniturerV   rW   ds           r(   _structure_blockrn      s     EHHI==?RFF()&&LL. OOC ++OOC --S! # 
JJLMMOMMONN#!)$-$'JM'*8}),Y	#
 r'   c           
         g }t        |       D ]b  }| |   }|j                  d      dk7  r|j                  ||j                  dd      |j                  dd      |j                  dd      d       d |S )u%  Generic shape-kind inventory tied to base_persistent_fixture markers.

    Runtime labels (LLM-authored upstream) pass through verbatim — they may
    carry scenario-specific names because that content is data, not
    contract. W20 spec §4.1 / F-11 cover this static-vs-runtime split.
    rQ   r   r[   r\   r^   r]   )r_   r[   r^   r]   )rT   r.   rC   )rU   r:   rV   rW   s       r(   _fixed_elements_inventoryrp   	  s{     !#CgS\66'(,EE

,!#!<FF:r2		
	  Jr'   c           	         g }|dk(  r|j                  d       |d   s|j                  d       t        |       i i t        t              i i g |dS )u  Return the geometry-candidates shape with VLM-dependent slots
    initialized to empty containers.

    W20A intentionally does **not** populate camera_cell_candidates etc.
    from numbered_elements alone — position_hint is a label string and the
    user's no-literal-substring rule forbids parsing meaning out of it. The
    populated values arrive once the VLM 10x10 readback gate is wired up
    in a follow-up wave. For now the shape is declared so downstream W20B
    can rely on the keys being present.
    synthetic_placeholderzVLM 10x10 readback is synthetic_placeholder; camera / look-at / visible-units / visible-openings cell enumerations are left empty pending a real VLM wiring wave.rb   u`   no base_structural_unit markers — geometry candidates cannot be enumerated against zero units.)	grid_sizecamera_cell_candidates_per_unit look_at_cell_candidates_per_unitview_cone_lens_enum_mapvisible_units_candidatesvisible_openings_candidates"wall_door_invalidation_diagnosticsdiagnostics)rC   listr1   r   )rs   	structure
vlm_statusrz   s       r(   _fp_geometry_candidates_shaper~   "  sp       K,,4	

 ^$0	

 )_+-,.#'(?#@$&').0"	 	r'   c           	     .    dt        |       dddddddgdS )u  Synthetic-placeholder readback. W20A never invokes a real VLM.

    The gate decision is ``synthetic_pass`` — not ``ok`` — so downstream
    consumers can distinguish "VLM readback passed" from "no VLM readback
    was attempted". Wiring a real VLM provider would replace this function
    in a follow-up wave behind explicit approval; the gate decision then
    becomes ``ok`` / ``failed`` based on the VLM's observation set.
    rr   Nsynthetic_passz4VLM readback not invoked (W20A synthetic-only path).zwA real VLM provider must be wired and approved separately before this slot can transition out of synthetic_placeholder.)r+   rs   observed_markersmissing_markersextra_markers
confidencegate_decisionrz   )r{   rs   s    r(   #_vlm_readback_synthetic_placeholderr   J  s5     *)_ )BL
 r'   c               
   g }| j                  |      xs g D ]V  }t        |t              s|j                  d      }t        |t              st        |t              sF|j                  |       X t        t        |            S )a  Pull exact integer marker numbers from an overlay payload list field.

    Skips non-dict entries and any number that isn't already a strict
    int (no string-int coercion; the upstream W19B-1 validator already
    enforced integer-only on its way in).
    r_   )r.   r0   r1   boolintrC   rT   set)overlayfieldr:   r5   rV   s        r(   _collect_overlay_marker_numbersr   f  sq     CU#)r)%&ii!c4 
3(<

3 * #c(r'   c                2   i }t        t        |      t        |      z        }|D ]U  }|j                  |      }|j                  |      }g }||j                  d       ||j                  d       |?|j                  d      xs g D 	cg c]$  }	t	        |	t
              rt	        |	t              s|	& }
}	|j                  d      }t	        |t
              rt	        |t              s|}nd}|j                  d      xs g D 	cg c]$  }	t	        |	t
              rt	        |	t              s|	& }}	|j                  d      xs g D 	cg c]$  }	t	        |	t
              rt	        |	t              s|	& }}	t        |d	      }t        |d
	      }t        |d	      }t        |j                  dd            }ng }
d}g }g }g }g }g }d}|g|j                  d      xs g D cg c]  }t	        |t              s| }}|j                  d      xs g D cg c]  }t	        |t              s| }}ng }g }|| t        t        |
            |t        t        |            t        t        |            |||||||d||<   X |S c c}	w c c}	w c c}	w c c}w c c}w )u!  Per-bg exact-ID facts the W20B shot-aware planner consumes.

    Joins the W19B-1 overlay payload (use/ignore arrays, base / transient
    marker partitions, target unit numbers, clean expectation) with the
    W19 background_master_plan bg spec (applies_to_shots, depends_on_bg)
    on ``bg_id`` exact-string equality. **No semantic inference** —
    integer / string equality only.

    Every bg_id seen on either side is surfaced once with a
    ``diagnostics`` list naming the missing side (if any). When overlay
    is missing the overlay-derived fields default to empty lists / None
    / False; when master_plan spec is missing the spec-derived fields
    default to empty lists. The W20B consumer can read the diagnostics
    to decide whether the bg has enough fact coverage to act on.
    Nz7missing floor_plan_overlay_payload entry for this bg_idz5missing background_master_plan bg spec for this bg_idtarget_unit_marker_numbers"dominant_target_unit_marker_numberuse_numbered_elementsignore_numbered_elementsbase_markers_to_reference)r   transient_markers_to_describeignored_state_overlay_markersclean_background_expectedFapplies_to_shotsdepends_on_bg)r@   rL   r   r   r   r    base_marker_numbers_to_reference$transient_marker_numbers_to_describe$ignored_state_overlay_marker_numbersr   r   r   rz   )	rT   r   r.   rC   r0   r   r   r   r   )rL   overlays_by_bgbg_specs_by_bg_idr:   
all_bg_idsr@   r   specrz   ntarget_unitsdominant_rawdominantuse_listignore_listbase_marker_numstransient_numsignored_overlay_numscleansr   r   s                         r(   _per_bg_render_factsr   z  s    * &(CN+c2C.DDEJ $$U+ $$U+!#?I <G  "++&BCIrIIAa%jD.A I  
 #;;'KLL<-"<6*6 "++&=>D"DDAa%jD.A D   "++&@AGRGGAa%jD.A G  
  ?:  =>N $C>$  %@%HIELHHK!N#% E HH%78>B> >a:aQTCU>    !HH_5;;;a
1c@R;    "M *0\1B*C2:%+CM%:(.s;/?(@0@4B4H). 0*&
E
I f JK
4 s*   )J )J)J
J4JJ'Jc                p   t        d | j                         D              }dg}|r||ddS | s|j                  d       g |ddS ddt        fd| j	                         D              t        fd| j                         D              }|j                  d	t        |       d
 d       ||dddS )u  Surface anchor candidates only — never pick.

    Per W20 spec §4.4 the anchor BG selection is LLM-owned. W20A surfaces
    the candidate set (every overlay whose ``clean_background_expected``
    is True) and leaves selection to W20B.

    W20F5 fallback (Codex 2026-05-28 narrow wave): if no overlay has
    ``clean_background_expected=True`` but ≥1 overlay exists for the fp,
    fall back to the BGs with the fewest transient markers (ramped
    candidate set). The fallback is explicitly tagged in diagnostics so
    the LLM and downstream auditors know the candidate set was widened.
    Empty overlays_by_bg still produces empty candidates (fail-closed at
    the consumer).
    c              3  \   K   | ]$  \  }}t        |j                  d d            r| & yw)r   FN)r   r.   ).0r@   rN   s      r(   	<genexpr>z,_anchor_candidate_surface.<locals>.<genexpr>  s0      4NE77?@ 	4s   *,z=anchor selection deferred to W20B (LLM-owned per W19J pivot).N)candidate_bg_idsselection_diagnosticsselected_anchor_bg_iduZ   no overlays exist for this fp — anchor candidate set is empty (fail-closed at consumer).c                    | j                  d      xs | j                  d      xs g }t        |t        t        f      rt	        |      S dS )Nr   r   r   )r.   r0   r{   tuplerh   )rN   r/   s     r(   _transient_countz3_anchor_candidate_surface.<locals>._transient_count  sN    ;< 2A
  	 (e}=s5zD1Dr'   c              3  .   K   | ]  } |        y wNr&   )r   pr   s     r(   r   z,_anchor_candidate_surface.<locals>.<genexpr>  s      %<%<s   c              3  >   K   | ]  \  }} |      k(  r|  y wr   r&   )r   r@   rN   r   min_transients      r(   r   z,_anchor_candidate_surface.<locals>.<genexpr>  s*      4NE7G$5 	4s   uf   no clean_background_expected=True overlay for this fp — W20F5 fallback widened candidate set to the z* BG(s) with the fewest transient markers (zF); LLM may still pick any of these but must declare the chosen anchor.T)r   r   r   ramped_fallback)rN   Dict[str, Any]returnr   )rT   r/   rC   minvaluesrh   )r   clean_candidatesrz   rampedr   r   s       @@r(   _anchor_candidate_surfacer     s   "  ,224  	HK  0%0%)
 	
 /	

 !#%0%)
 	
E  %3%:%:%< M  ,224 F
 	114V >))6 8@	@ #!,!%	 r'   c                   |j                  d      xs g }i }|D ]+  }t        |t              rd|vr	 t        |d         }	|||	<   - t        | |       t        |      }
|
d   st        d| d      t        |      D 	cg c]+  }	||	   j                  d      t        v rt        |	||	         - }}	g }t        |      D ]  }||   }|j                  d      xs g D ]d  }t        |t              s	 t        |j                  d            }	|j                  ||	|j                  dd	      |j                  dd	      d
       f  g }|s|j                  d       ||j                  d       |s|j                  d       t        |      }t        ||
|d         }t        |      }|D ci c]  }|j                  d      s|d   | }}t!        | ||      }| |t#        |      |
ddgit%        |      t&        ddgid|||||||dS # t        t
        f$ r Y w xY wc c}	w # t        t
        f$ r Y \w xY wc c}w )Nnumbered_elementsr_   rb   rR   z\ has zero base_structural_unit markers; a dossier cannot be built without at least one unit.rQ   r   r[   r\   )r@   r_   r[   rQ   zino backgrounds reference this fp in background_master_plan; downstream BG consumers will produce nothing.ztfloor_plan_render did not yet emit a png_path for this fp; downstream consumers must wait for the render checkpoint.z{no floor_plan_overlay_payload entries reference this fp; the overlay step may be not_applicable upstream (v5 default path).r   r+   )rs   r|   r}   r@   )rL   r   r   rz   zQmaterials palette is an LLM-emit slot (W20B); W20A leaves this empty by contract.zQlighting identity is an LLM-emit slot (W20B); W20A leaves this empty by contract.)r|   	materialsfixed_elements_inventorystandard_of_living_bandlighting_identity)rL   fp_image_pathrs   dwelling_identityfp_geometry_candidatesfp_geometry_vlm_readbackbase_marker_inventoryoverlay_marker_inventoryper_bg_render_facts_by_bg_idanchor_selection_metadatarz   )r.   r0   r1   r   	TypeError
ValueErrorrY   rn   r!   rT   r   r`   rC   r   r~   r   r   r{   rp   r   )rL   fp_entryfp_png_pathr   bg_specsrs   raw_markersrU   rW   rV   r|   base_inventoryoverlay_inventoryr@   rN   r5   rz   vlm_readbackgeometryanchorr   r   per_bg_factss                          r(   _build_one_dossierr   .  s    ,,239rK)+G"d#xr'9	bl#C   UG, )I^$&UI A B
 	
 '?,"C3<126JJ 	 WS\2"  , /1' '[[!@AGRGEeT*%))H-. $$"!"YYw3+0995JB+O	 H ($  K<	
 H	
 M	

 7KL,)H
 '~6F(04(0DHHW4EWt  4 (%+L $)_":  )B'(J'F: "
" #+$0!/$5(4%+"7 O :& 		, z* F4s5   H 0H!1H&,H<H<HH&H98H9N)r9   rs   c                   t        |       }|st        d      t        |      }t        |      }t	        |xs i       }t        |t              rt        |      dk7  rt        d|      t        d |D              rt        d|      i }	|j                         D ]H  \  }
}t        |
||j                  |
      |j                  |
i       |j                  |
g       |      |	|
<   J |	S )u  Build one dossier per fp_id present in the floor_plan_prompt cp.

    The iteration order matches the floor_plan_prompt cp insertion order.
    Each fp's dossier is independent — a per-fp failure raises
    ``BaseLocationDossierError`` and aborts the whole call; the step
    wrapper turns that into a ``failed_count=1`` checkpoint.

    Parameters
    ----------
    fp_prompt_data:
        ``floor_plan_prompt`` checkpoint ``data`` block.
    master_plan_data:
        ``background_master_plan`` checkpoint ``data`` block.
    overlay_payload_data:
        ``floor_plan_overlay_payload`` checkpoint ``data`` block. Empty
        dict is acceptable (the per-fp dossier records a diagnostic).
    fp_render_data:
        Optional ``floor_plan_render`` checkpoint ``data`` block; used
        only to surface ``fp_image_path``. Missing render is non-fatal.
    grid_size:
        VLM readback grid size. Defaults to (10, 10) per W20 spec §2.4.
    z_floor_plan_prompt cp carries no floor_plans entries with status='ok'; cannot build any dossier.   z!grid_size must be a 2-tuple; got c              3  L   K   | ]  }t        |t               xs |d k    yw)r   N)r0   r   )r   vs     r(   r   z!build_dossiers.<locals>.<genexpr>  s'     
?Yz!S!!+Q!V+Ys   "$z0grid_size dimensions must be positive ints; got )rL   r   r   r   r   rs   )r6   r!   rI   rO   r;   r0   r   rh   anyr/   r   r.   )r2   rD   rM   r9   rs   fp_indexbg_specs_by_fpoverlays_by_fpfp_png_indexr:   rL   r   s               r(   build_dossiersr     s   < ".1H&5
 	
 --=>N,-ABN+N,@bALi'3y>Q+>&/	}=
 	
 
?Y
??&>ymL
 	
 &(C#>>+x'$((/)--eR8#''r2
E
 , Jr'   )r2   r   r   Dict[str, Dict[str, Any]])r9   r   r   zDict[str, Optional[str]])rD   r   r   zDict[str, List[Dict[str, Any]]])rM   r   r   z$Dict[str, Dict[str, Dict[str, Any]]])rL   r   rU   Dict[int, Dict[str, Any]]r   None)rV   r   rW   r   r   r   )rU   r   r   r   )rU   r   r   List[Dict[str, Any]])rs   r   r|   r   r}   r   r   r   )rs   r   r   r   )r   r   r   r   r   z	List[int])rL   r   r   r   r   r   r   r   )r   r   r   r   )rL   r   r   r   r   zOptional[str]r   r   r   r   rs   r   r   r   )r2   r   rD   r   rM   r   r9   zOptional[Dict[str, Any]]rs   r   r   r   )#r%   
__future__r   typingr   r   r   r   r   r	   	frozensetr   __annotations__r   r   r   r   r   	Exceptionr!   r6   r;   rI   rO   rY   r`   rn   rp   r~   r   r   r   r   r   r   r&   r'   r(   <module>r      st  Vn # > > (1( n  +4 (+   ';=T&T ^ T%- ? -
 +   (<  ;6y 6"&$$&()2	4 &  F&2%% % 	%
 %P 8'*(jj .j 1	j
 jZD-DDNss s 	s
 .s #s s sv 04!2;"; %; )	;
 -; ; ;r'   