
    TjT                    8   U d Z ddlmZ ddlmZmZmZmZmZm	Z	 dZ
dZdZdZe
eeefZded	<   d
dddZded<   dZded<    G d de      Zeeef   Zeeef   Zeeef   Z	 	 	 	 	 	 ddZed	 	 	 	 	 ddZdedd	 	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 d dZ	 	 	 	 	 	 d!dZy)"u	  W20A2: FP geometry readback bridge — pure deterministic builder.

Consumes a ``base_location_dossier`` (W20A) and emits:
  - ``readback``        — a ``GeometryReadback`` shape with marker cells.
                          status ∈ {ok, synthetic_fixture, failed}.
                          **W20A2 produces synthetic_fixture ONLY**; the
                          ``ok`` status path is reserved for a future
                          wave that wires a real VLM provider (see the
                          provider-slot docstring below).
  - ``geometry``        — geometry candidates derived from the readback:
                          per-marker cells, per-unit camera/look-at
                          candidate cells, direction-vector records,
                          view-cone records keyed on a generic lens
                          enum, visible-unit / visible-opening
                          superset maps, and wall/door invalidation
                          diagnostics.

W20 boundaries enforced in this module:
  - exact-ID only — every join uses marker number / fp_id exact equality.
  - no semantic reference policy, no camera pick. Code emits **candidate**
    sets and diagnostics; the consumer (W20B) makes the LLM-owned
    decisions.
  - no scenario-specific static literals (the only string keys here are
    generic enum / shape-kind strings).
  - no LLM / image / VLM / DB / ImageAsset imports.
  - the synthetic fixture path is intentionally a placeholder. It exists
    so downstream code (W20B planner contract, HTML overlay emitter,
    tests) can be exercised before the real VLM is wired. The geometry
    decisions made off synthetic cells must not be promoted to a
    production smoke without first being recomputed against a real
    VLM readback.

Real VLM provider slot
======================

A ``call_vlm_grid_readback`` callable can be passed into
``compute_readback(...)`` to swap the synthetic_fixture path for a real
provider. The callable signature is::

    Callable[[BaseLocationDossier, fp_image_path], GeometryReadback]

The W20A2 wave **does not** wire any real provider. ``compute_readback``
with ``vlm_provider=None`` (default) always returns the
synthetic_fixture readback. A future wave behind explicit approval will
wire a litellm vision call to populate the ``ok`` path; see
``backend/scripts/experiment_floor_plan_vlm_readback_slice.py`` (W17C)
for the historical reference pattern.
    )annotations)AnyCallableDictListOptionalTuplebase_structural_unitbase_openingbase_persistent_fixturebase_persistent_furniturezTuple[str, ...]
BASE_KINDSZ   2      )widenormal	telephotozDict[str, int]VIEW_CONE_LENS_ENUM_MAP)
   r   Tuple[int, int]DEFAULT_GRID_SIZEc                      e Zd ZdZy)GeometryReadbackErrorz7Fail-closed signal for the readback / geometry builder.N)__name__
__module____qualname____doc__     g/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/floor_plan_geometry_readback.pyr   r   N   s    Ar    r   c                h   |\  }}g }t         D ci c]  }|g  }}| D ],  }|j                  d      }||v s||   j                  |       . g }	t               }
d}t	        d |j                         D              }||z  }||kD  rt        d| d| d| d| d	      t         D ]t  }t        ||   d	 
      D ]^  }	 ||z  }||z  }|dz  }||k\  rt        d      ||f|
v r'|
j                  ||f       |	j                  t        |d         |||d       ` v |j                  d       |	|fS c c}w )uS  Deterministically spread base markers across the grid.

    Marker order is ``BASE_KINDS`` outer × ``number ascending`` inner.
    Each marker takes the next unused cell in column-major order. This
    is **not** a realistic floor-plan layout — it exists so geometry
    computation has something to operate on before a real VLM readback
    is wired up.

    Raises ``GeometryReadbackError`` when the grid cannot fit every
    base marker (``rows * cols < |markers|``). This is intentional: a
    silent overwrite would let synthetic geometry collide cells and
    mask bugs downstream.
    base_layer_decisionr   c              3  2   K   | ]  }t        |        y wN)len.0vs     r!   	<genexpr>z'_spread_marker_cells.<locals>.<genexpr>{   s     :)9AQ)9   zsynthetic_fixture cannot fit z base markers in a xz grid (capacity )c                    t        | d         S )Nnumber)int)es    r!   <lambda>z&_spread_marker_cells.<locals>.<lambda>   s    Qx[9Ir    )key   uF   synthetic_fixture column overflow — guard should have prevented thisr/   )r/   rowcolkindzsynthetic_fixture: marker cells are deterministic placeholders, not real VLM observations. Geometry derived from this readback must not be promoted to a production smoke.)
r   getappendsetsumvaluesr   sortedaddr0   )base_inventory	grid_sizerowscolsdiagnosticskby_kindentrydecisionobservedusedcell_idxtotal_to_placegrid_capacityr7   rcs                    r!   _spread_marker_cellsrO   ]   s   $ JD$K?I/Jz!2zG/J9923wH$$U+  
 %'HDH:)9::N4KM%#+N+; <64& 0qB
 	

 GDM/IJE tO$A9 05  q6T>!Q "%eHo"6   $	 3 K 8 	6
 [  e 0Ks   
D/)r@   c           	        | j                  d      }t        |t              r|st        d|d      | j                  d      xs g }|st        d|d      t	        ||      \  }}|D ch c]  }t        |d          }}|D ch c]  }|d   	 }}t        ||z
        }	t        ||z
        }
d	|t        |      ||	|
d
|dS c c}w c c}w )zReturn a synthetic_fixture-status readback for the given dossier.

    The readback shape mirrors what a real VLM provider would return,
    so the geometry / HTML layer below treats both code paths
    uniformly.
    fp_idz)dossier.fp_id missing or non-string (got r-   base_marker_inventoryzdossier fp_id=z  has empty base_marker_inventory)r?   r@   r/   synthetic_fixtureN)statusrQ   r@   observed_markersmissing_markersextra_markers
confidencerC   )r8   
isinstancestrr   rO   r0   r=   list)dossierr@   rQ   base_invrH   rC   r1   inv_numbersseen_numbersmissingextras              r!   "compute_synthetic_readback_fixturerb      s    KK EeS!#7yB
 	
 {{239rH#UI%EF
 	
 1Hk
 .66X3q{#XK6)12AAhKL2[</0G<+-.E &)_$""	 	 72s   -CCN)fp_image_pathr@   vlm_providerc                   |t        | |      S  || ||      }t        |t              s"t        dt	        |      j
                   d      |j                  d      dk7  rt        d|j                  d      d      |j                  d	      | j                  d	      k7  r/t        d
|j                  d	      d| j                  d	            |S )a  Return a readback for the dossier's fp_id.

    When ``vlm_provider`` is ``None`` (W20A2 default), the
    synthetic_fixture path is used and ``fp_image_path`` is ignored.

    When a provider callable is passed, it is invoked as
    ``vlm_provider(dossier=..., fp_image_path=..., grid_size=...)`` and
    its return value must be a readback dict with ``status='ok'``,
    matching the GeometryReadback shape. Any non-conforming return
    raises ``GeometryReadbackError``.
    )r\   r@   )r\   rc   r@   z vlm_provider returned non-dict (r-   rT   okzvlm_provider returned status=z1; only 'ok' is accepted on the real-provider pathrQ   zvlm_provider fp_id mismatch: z != dossier )rb   rY   dictr   typer   r8   )r\   rc   r@   rd   outs        r!   compute_readbackrj      s    ( 1y
 	
 #C
 c4 #.tCy/A/A.B!D
 	
 wwxD #+CGGH,=+@ A9 :
 	
 www7;;w//#+CGGG,<+? @{{7+.0
 	
 Jr    c                0   |\  }}t               }i }g }| j                  d      xs g D ]i  }|j                  d      }|j                  d      }	|j                  d      }
|j                  d      }t        |t              rt        |t              rt        d|d      ||v rt        d| d	      |j                  |       t        |	t              rt        |
t              st        d
| d|	d|
d      d|	cxk  r|k  rn nd|
cxk  r|k  sn t        d
| d|	 d|
 d| d| d      |j                  |	|
f      }||j                  d
| d|	 d|
 d| d	       n|||	|
f<   |t        vsKt        d
| d|dt        t                      |S )a  Validate readback cell shape and return cell-collision diagnostics.

    W20E6-A: distinct marker numbers may share a single 10x10 coarse
    cell. The hard fail on duplicate ``(row, col)`` has been lowered to
    a returned diagnostic so the geometry layer can soft-degrade
    instead of dropping the whole readback. All other invariants stay
    fail-closed:

      - duplicate marker NUMBER       -> raise
      - non-int row/col               -> raise
      - out-of-grid cell              -> raise
      - unknown kind                  -> raise

    Returns a list of per-pair collision messages (empty when no
    collisions). The caller folds these into the geometry diagnostics.
    rU   r/   r5   r6   r7   z(observed_marker.number must be int (got r-   zobserved_marker.number=z duplicated in readbackzobserved_marker #z row/col must be int (got row=z, col=r   z cell (,z) out of grid (r,   z shares 10x10 cell (z) with marker #z6 (W20E6-A: surfaced as diagnostic, not a hard failure)z kind=z not in )
r:   r8   rY   r0   boolr   r>   r9   r   r[   )readbackr@   rA   rB   r_   
cell_ownercell_collision_diagnosticsrF   numrM   rN   r7   priors                r!   _validate_readback_cellsrs     s   & JD$L-/J,.017R7ii!IIeIIeyy #s#z#t'<':3'C  ,')#.EF  	!S!As);'#C5 )5qe1.  Q!q-4-'#C5s!A3 764&#  1v&&--#C5(<QCq D  %w '&' "%J1vz!'#C5thh
#$& E 8L &%r    c                  '( |j                  d      dvrt        d|j                  d      d      |j                  d      | j                  d      k7  r/t        d|j                  d      d| j                  d            t        |j                  d      xs t              }t	        |      d	k7  st        d
 |D              st        d|j                  d            t        |d         t        |d         f}t        ||      }i 'i (|d   D ]  }|d   |d   g'|d   <   |d   (|d   <    d3'(fd} |t              } |t              } |t              }	 |t              }
t        |      }t        |      }|D ci c]  }|t        ||         g }}t               }|j                         |	j                         |
j                         fD ]#  }|D ]  }|j!                  t        |              % t        d |D              }|D ci c]  }||D cg c]  }t        |       c} }}}g }|D ]o  }||   }|D ]c  }||k(  r	||   }|d   |d   z
  }|d   |d   z
  }|j#                  |t        |      |t        |      ||t%        |      t%        |      z   d       e q g }|D ]\  }t&        j)                         D ]C  \  }}|j#                  |d   t        |d         |d   t        |d         |d   |d   ||d       E ^ |D ci c]  }||D cg c]
  }||k7  s	| c} } }}|D ci c]  }|t        |       }!}ddg}"g }#|#j+                  |       |d   d k(  r|#j#                  d!       |s|#j#                  d"       |s|#j#                  d#       i d| d   dt        |      d$|d   d%'j)                         D $ci c]  \  }$}t-        |$      t        |       c}}$d&|j)                         D $ci c]  \  }$}t-        |$      t        |       c}}$d'|j)                         D $ci c]  \  }$}t-        |$      t        |       c}}$d(|	j)                         D $ci c]  \  }$}t-        |$      t        |       c}}$d)|
j)                         D $ci c]  \  }$}t-        |$      t        |       c}}$d*|j)                         D %ci c](  \  }}%t-        |      |%D cg c]  }t        |       c}* c}}%}d+|j)                         D %ci c](  \  }}%t-        |      |%D cg c]  }t        |       c}* c}}%}d,|d-|d.| j)                         D &ci c]  \  }}&t-        |      t        |&       c}&}d/|!j)                         D &ci c]  \  }}&t-        |      t        |&       c}&}d0|"d1t        |      d2|#S c c}w c c}w c c}}w c c}w c c}}w c c}w c c}}$w c c}}$w c c}}$w c c}}$w c c}}$w c c}w c c}}%}w c c}w c c}}%}w c c}&}w c c}&}w )4u  Compute geometry candidates from the dossier + readback.

    The output carries only **candidate** sets and diagnostics — no
    LLM-owned decisions. W20 boundaries: code never picks a camera,
    never picks a reference image, and never asserts that two
    candidates depict the same physical space.

    Raises ``GeometryReadbackError`` on:
      - readback.status not in {ok, synthetic_fixture}
      - readback cell out of grid, duplicate cell, unknown kind
      - readback fp_id mismatch with dossier
    rT   >   rf   rS   zreadback.status=z not in {ok, synthetic_fixture}rQ   zreadback.fp_id=z != dossier fp_id=r@      c              3  J   K   | ]  }t        |t              xr |d kD    yw)r   N)rY   r0   r'   s     r!   r*   z.compute_geometry_candidates.<locals>.<genexpr>c  s'      !.2
1c$q1u$ds   !#zreadback.grid_size invalid: r   r4   )rn   r@   rU   r5   r6   r/   r7   c                z    j                         D ci c]  \  }}|   | k(  r|t        |       c}}S c c}}w r%   )itemsr[   )r7   ncellmarker_cellsmarker_kindss      r!   _by_kindz-compute_geometry_candidates.<locals>._by_kindu  sK     (--/
/4A$& tDzM/
 	
 
s   7c              3  2   K   | ]  }t        |        y wr%   )r[   )r(   rN   s     r!   r*   z.compute_geometry_candidates.<locals>.<genexpr>  s      3*AQ*r+   )camera_unitcamera_celllook_at_unitlook_at_celldxdy	length_l1r   r   r   r   r   r   )r   r   r   r   r   r   	lens_enumfov_degzxreadback does not encode wall segment polylines; wall/door line-of-sight invalidation cannot be performed at this layer.zopening-to-unit adjacency cannot be derived from cell positions alone; consumer must treat visible_units_candidates and visible_openings_candidates as supersets, not committed sets.rS   zkgeometry candidates computed from a synthetic_fixture readback; placeholder cells, not production geometry.zKzero base_structural_unit cells in readback; geometry candidates are empty.zfewer than two base_structural_unit cells in readback; direction-vector candidates are empty and view-cone records were skipped (a cone needs a direction).readback_statusr{   unit_marker_cellsopening_marker_cellsfixture_marker_cellsfurniture_marker_cellscamera_cell_candidates_per_unit look_at_cell_candidates_per_unitdirection_vector_recordsview_cone_recordsvisible_units_candidatesvisible_openings_candidates"wall_door_invalidation_diagnosticsrp   rC   )r7   rZ   returnzDict[int, List[int]])r8   r   tupler   r&   allr0   rs   BASE_STRUCTURAL_UNITBASE_OPENINGBASE_PERSISTENT_FIXTUREBASE_PERSISTENT_FURNITUREr=   r[   r:   r<   r>   r9   absr   rx   extendrZ   ))r\   rn   gridr@   rp   rF   r}   
unit_cellsopening_cellsfixture_cellsfurniture_cellsunits_sortedopenings_sortedur   _look_at_cell_setsourcerz   shared_look_at_cellsrN   r   r   cam_ucam_celltgt_utgt_cellr   r   r   dvlensfovr   r   wall_door_diagnosticsrC   ry   cellsr)   r{   r|   s)                                          @@r!   compute_geometry_candidatesr   F  s   " ||H%@@#x||H58 9( )
 	
 ||GG 44#hll736 7[[),.
 	

 k*?.?@D
4yA~S !.2!  $*8<<+D*GH
 	
 #&d1g,DG!=I,DY- *,L#%L,-).uuU|(DU8_%(-fU8_% .
 ./J\*M45M89O*%L]+O +7C*6QDA  , $ C !U 
 D!!%+.  -3 3*3 - =ID<Hq121DG122L % D
 68e$!E~!%(H!x{*B!x{*B$++#(#'>$)$(N!$R3r7!2
 " 8 /1&0668ID#$$#%m#4#'=(9#:$&~$6$(N);$<T(T(!%"	 9 '* "6!E 	<6<a1:<66!  6
 3?92>tO$$,   9
	H	L(  K 1200D	
 $	
 $?	
"!"T)_" 	8H-" 	\5G5G5IJ5ITQQa5IJ	"
 	*:J:J:LM:L$!Qc!fd1go:LM" 	(5(;(;(=!
(=1CFDGO(=!
" 	(5(;(;(=!
(=1CFDGO(=!
" 	!(7(=(=(?#
(?1CFDGO(?#
" 	*;AAC,
C5 Fe,eT!We,,C,
"& 	+<BBD-
D5 Fe,eT!We,,D-
'". 	#$</"0 	.1"2 	#(@(F(F(H%
(H1CFDGO(H%
3"8 	&(C(I(I(K(
(K1CFDGO(K(
9"> 	-.C?"@ 	%d+E&FA"B 	{C" "eC. 3Dn 769L KM!
!
#
 -,

 --
%
(
s   W*
W4!W/3W4
W?#
W:.W:2W?>XX

X
X
;X
2X"
*X- X(X-1X9X4X9=Y 
4Y
/W4:W?(X-4X9)r?   zList[Dict[str, Any]]r@   r   r   z%Tuple[List[ReadbackEntry], List[str]])r\   Dict[str, Any]r@   r   r   GeometryReadback)
r\   r   rc   zOptional[str]r@   r   rd   z)Optional[Callable[..., GeometryReadback]]r   r   )rn   r   r@   r   r   z	List[str])r\   r   rn   r   r   GeometryCandidates)r   
__future__r   typingr   r   r   r   r   r	   r   r   r   r   r   __annotations__r   r   	Exceptionr   rZ   ReadbackEntryr   r   rO   rb   rj   rs   r   r   r    r!   <module>r      s|  /` # = =
 . 3 7 	
O  +   &. ? -BI B
 S#XS> #s(^ G!(G! G! +	G!Z "3)) ) 	)^ $(!2 	++ !+ 	+
+ +f=&!=&.==&=&@UU U 	Ur    