
    Kj{_                       U d Z ddlmZ ddlmZ ddl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ZdZdZdZdZded<   dZd	ed<   dZdZdZdZd9dZd:dZ	 	 	 	 	 	 	 	 	 	 	 	 d;dZeed	 	 	 	 	 	 	 	 	 d<dZdZdZ d Z!d!Z"d"Z#d#d$d% e$e      d&d'd(id'd%id)d'd%id*d+g d+d,Z%ded-<   d=d.Z&	 	 	 	 	 	 	 	 d>d/Z'd?d0Z(d1ed2	 	 	 	 	 	 	 	 	 d@d3Z)efdAd4Z*dBd5Z+ e,        e,       eed6	 	 	 	 	 	 	 	 	 	 	 	 	 dCd7Z-y8)Du	  W21B-w5 — BG space partition (pure module).

Turns pass-2 edge judgements (the LLM/VLM space-adjudication of bg-pair
relationships) into a deterministic ``space_partition_plan``: which bgs share a
plate (same physical space), which anchor each plate, the render action per bg,
and the bounded reference tree. This REPLACES the dwelling-level
``same_physical_space_view`` reference SOURCE that incorrectly chained
cross-zone plates (an enclosed zone inheriting an adjacent open zone's plate
within a multi-zone dwelling).

Design (Codex + Claude consensus, 2026-05-31):

* The deterministic floor / edge judge already classified each pair. This module
  is PURE clustering + assignment — NO LLM/VLM/image/IO, NO text parsing.
* Clustering is **anchor-centered constrained**, NOT connected-components
  (a plain transitive closure would re-merge cross-zone spaces via bridges):
    - a node joins a group only if it has a DIRECT strong edge to the anchor AND
      has no ``different_space`` edge with ANY current member,
    - so an A~B / B~C strong pair with A~C ``different_space`` never collapses
      into one group, and an establishing/wide hub never bridges two clusters.
* Edge eligibility (Codex strong-parent gate v0, conservative — the failure mode
  we fix is false reuse): ``same_space`` AND ``confidence >= 0.75`` AND
  ``strong_parent_allowed`` true. ``different_space`` is a hard negative;
  ``adjacent_related`` / ``style_only`` / ``uncertain`` never form a group and
  never become a strong spatial parent (they may surface as weak cross-group
  context only).
* Anchor selection prefers an existing production anchor, then highest strong
  degree, then confidence-sum, then a NON-hub over a hub, then stable bg_id.
* Reference tree: within a group, non-anchor members reuse/derive from the
  anchor (``ref_tree_parents`` capped at ``max_refs``). Across groups there is
  NO spatial parent (the substrate keeps FP-first; cross-group context, if any,
  is weak/style/text only — never a parent PNG across a cross-zone edge).

NOTE (Codex caveat): the edge judgements consumed here are the SOT for the
*partition*, but if the FP / projection-card substrate is later simplified the
judgements must be regenerated before a production run — this module's contract
is stable across that, only its inputs change.
    )annotations)combinations)AnyDict	FrozenSetIterableListOptionalTupleg      ?floatSTRONG_CONF_THRESHOLD   intMAX_REFSstrong	differentweakrender_new_platereuse_existing_plateg?UBIQUITY_FRACTION   HUB_NODE_UNIT_COUNTdifferent_space
same_space	uncertainhub_uncertainc                    t               }t        | t        t        t         t        f      s|S | D ]5  }t        |t
              st        |t              r%|j                  |       7 |S )z@Filter an iterable down to genuine ints (drop bools / non-ints).)set
isinstancelisttuple	frozensetr   booladd)valuesoutns      ]/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/bg_space_partition.py_int_setr)   G   sP    uCftUC;<
ajD&9GGAJ  J    c                   i }t        | t              s|S | D ]o  }t        |t              s|j                  d      dk7  r)|j                  d      }t        |t              sKt        |t
              r\|j                  d      ||<   q |S )u+  Map ``base_structural_unit`` marker number -> its label.

    ``base_layer_decision`` is a closed enum produced upstream; we read it and
    the integer ``number`` only. No label/text parsing (absolute rule) — the
    label is carried through as opaque passthrough evidence, never matched on.
    base_layer_decisionbase_structural_unitnumberlabel)r   r    dictgetr   r#   )base_marker_inventoryr&   mnums       r(   _structural_unit_numbersr5   R   s~     C+T2
"!T"55&'+AAeeHoc3
3(=uuW~CH # Jr*   c                R    |rt         S | st        S |r|dk\  r|dk\  rt        S t        S )a  Deterministic borderline classification for one candidate edge.

    Conservative v0 (the failure mode being fixed is *false* cross-zone reuse):
    deterministic only decides the hard negative, the hub isolation, and an
    OBVIOUS positive. Everything else defers to the pass-2 edge judge.

      * hub view                                       -> hub_uncertain
      * no shared structural unit                      -> different_space (negative)
      * distinctive non-empty AND jaccard==1 AND
        overlap_min==1                                 -> same_space (obvious)
      * else (shares only common/hub zone, partial
        distinctive, or small-dwelling degeneracy)     -> uncertain
          ?)CAND_HUB_UNCERTAINCAND_DIFFERENT	CAND_SAMECAND_UNCERTAINshareddistinctivejaccardoverlap_minhubs        r(   _classify_candidate_edgerB   g   s2      !!w#~+*<r*   )ubiquity_fractionhub_node_unit_countDict[str, Any]c                   t        |       }t        |      }t        |t              r|ni }g }i }|j	                         D ]F  \  }	}
t        |	t
              rt        |
t              s't        |
j                  d            |z  ||	<   H t        |      xs d}|D ci c]%  t        fd|j                         D              ' }}|j	                         D ch c]  \  }}||z  |k\  s| }}}|j	                         D ch c]  \  }}t        |      |k\  s| }}}g }t        t        |      d      D ]  \  }}||   ||   }}|r|s|j                  |s|n| d| d| d       3||z  }||z  }|rt        |      t        |      z  nd}t        |      t        t        |      t        |            z  }||z
  }||v xs ||v }t        |||||	      }|t         k(  r|j                  |||t        |      t        |      t#        |d
      t#        |d
      |d        |j	                         D ci c]  \  }}|t        |       c}}t        |      t        |      t        |      ||dS c c}w c c}}w c c}}w c c}}w )u  Generate bounded pairwise candidate edges for one floor plan.

    PURE deterministic floor — NO LLM/VLM/image/IO, NO text/label parsing. Both
    inputs come from the ``base_location_dossier`` checkpoint (upstream of the
    plan), so this can run in a step placed *before* shot_aware_bg_render_plan:

      * ``base_marker_inventory`` — the dossier's flat marker list; we read only
        ``base_layer_decision == "base_structural_unit"`` integer numbers.
      * ``per_bg_render_facts_by_bg_id`` — per-bg exact-ID facts; we read only
        ``target_unit_marker_numbers`` (the camera-target floor) per bg.

    A bg's *signature* is its target-unit floor intersected with the dwelling's
    structural units. Pairwise edges whose deterministic state is a hard
    negative (no shared structural unit) are dropped; every other pair with any
    shared structural signal becomes a candidate the pass-2 judge must rule on
    (over-generation is fine — the judge is the SOT; under-generation is not).

    Returns ``{signatures, structural_units, ubiquitous_units, hub_bg_ids,
    candidate_edges, diagnostics}``. Each candidate edge carries the
    deterministic ``state`` plus the shared/distinctive marker sets and the
    jaccard / overlap_min metrics for the judge's prompt context.
    target_unit_marker_numbers   c              3  ,   K   | ]  }|v sd   yw)rH   N ).0sus     r(   	<genexpr>z(build_candidate_edges.<locals>.<genexpr>   s     A#6a!q&#6s   	r   u)    has empty structural signature — edge ~z  not deterministically generated        r<      )bg_abg_bstateshared_unitsdistinctive_unitsr?   r@   rA   )
signaturesstructural_unitsubiquitous_units
hub_bg_idscandidate_edgesdiagnostics)r5   r   r   r0   itemsstrr)   r1   lensumr%   r   sortedappendminrB   r9   round)r2   per_bg_render_facts_by_bg_idrC   rD   unitsunit_setfactsr\   rW   bg_idfnbrM   	unit_freqc
ubiquitousbgrL   rZ   r[   abABr=   unionr?   r@   r>   rA   rT   s               `                  r(   build_candidate_edgesru      s   : %%:;E5zH,67SUY,Z(`bEK "$JKKMq%%Z4-@$QUU+G%HIHT
5 "
 
Z	ABKST8aCA:#4#4#6AAA8IT ) 1Q 11QV?P5P! 1JQ"((**ur1c!f8K.K*   -/OVJ/31!}jm1 11% &s!A3>@ QA.3#f+E
*&kCAA$77z):o0j(##
 N" &v%+K%8 !,$[!4		
5 4P 3=2B2B2DE2DQr6!9}2DE"8,":.Z(*" ] UQZ Fs$   *I8I=-I=JJ<J	)r   adjacent_related
style_onlyr   r   judgedskippedinvalida!  You are a spatial reasoning judge for film background plates. Two background plates (bg_a, bg_b) from the same dwelling are given, each with a projection card describing what is VISIBLE in that shot (visible_items). Decide their spatial relationship from the visible evidence ONLY.

States:
- same_space: the two plates show the SAME physical room/zone (possibly a different angle). Requires shared DISTINCTIVE features, not just a common corridor or shared open zone.
- adjacent_related: different rooms that are spatially connected (one visible through a doorway from the other).
- style_only: only generic style/material similarity, no spatial continuity.
- different_space: clearly different rooms with no spatial continuity.
- uncertain: insufficient visible evidence.

Rules:
- Ground every judgement in visible_items evidence; cite it.
- Sharing only a common/circulation zone (corridor, shared living-kitchen) is NOT enough for same_space.
- A wide/establishing 'hub' view that sees many zones must NOT auto-merge with a specific narrower zone.
- Label text is NOT evidence; only what is visibly shown.
- Be conservative: when in doubt use uncertain, never same_space.
- shared_distinctive_features must list ONLY concrete distinctive items visible in BOTH plates (empty if none).
Output strict JSON only.objectFstring)typeenumr}   r.   array)r}   r]   )
edge_state
confidenceevidenceshared_distinctive_features)r}   additionalProperties
propertiesrequiredEDGE_JUDGE_SCHEMAc                    t        | t              sy| j                  d      dk7  ry| j                  d      du s| j                  d      du ry| j                  d      }t        |t              xr t	        |      dkD  S )	u   R3 precondition: a card may seed a judge call only if it is a real,
    passed projection card with visible items — never a synthetic placeholder,
    a non-pass card, or a card lacking visible evidence.F
card_statepass	syntheticTis_syntheticvisible_itemsr   )r   r0   r1   r    r_   )cardviss     r(   card_is_judgeabler   $  sq     dD!xx'xx$(@D(H
((?
#Cc4 1SX\1r*   c           	     .   ddl }| j                  d      | j                  d      | j                  d      | j                  d      | j                  d      |j                  d      |j                  d      d	}t        |j                  |d
      t        dS )u4  Assemble the strict prompt bundle (system/user/schema) for one edge.

    Carries the deterministic candidate ``state`` as a non-binding hint and the
    two cards' ``visible_items`` as the only evidence. No text/label matching
    here — the visible_items pass through verbatim for the model to read.
    r   NrR   rS   rT   rU   rV   r   )rR   rS   deterministic_hintrU   rV   card_a_visible_itemscard_b_visible_itemsF)ensure_ascii)systemuserschema)jsonr1   EDGE_JUDGE_SYSTEMdumpsr   )edgecard_acard_b_jsonr   s        r(   build_edge_judge_promptr   2  s        "hhw/0!XX&9: &

? ; &

? ;D $Du5# r*   c               ~    | j                  d      | j                  d      | j                  d      t        |dddg dd
S )	u"  A non-result for an edge that could not be judged (e.g. a partial card).

    Codex lock: a missing card must NOT fail the whole step — it yields a
    ``skipped`` judgement that can never become a same-space strong parent, so
    the rest of the dwelling's partition still proceeds.
    rR   rS   rT   r   rP    F)
rR   rS   deterministic_statejudge_statusskip_reasonr   r   r   r   strong_parent_allowed)r1   JUDGE_STATUS_SKIPPED)r   reasons     r(   skipped_edge_judgementr   P  sG       #xx0,!')!& r*   T)both_cards_passstrong_conf_thresholdc           
        | j                  d      | j                  d      | j                  d      d}t        |t              s|j                  t        dddg ddd	
       |S |j                  d      }|t
        vrd}|j                  d      }t        |t        t        f      r0t        |t              s t        dt        dt        |                  }nd}|j                  d      xs g D cg c]$  }t        |t              r|j                         r|& }	}|j                  d      }
t        |
t              r|
nd}
|dk(  xr" ||k\  xr t        |	      dkD  xr t        |      }|j                  t        |||
|	|d       |S c c}w )u_  Validate one raw judge response into a canonical edge judgement.

    Enforces the closed state vocabulary and a numeric confidence in [0,1].
    Computes the R2 strong-parent gate WITHOUT any semantic reading of the free
    text: ``strong_parent_allowed`` is true iff ``edge_state == same_space AND
    confidence >= threshold AND shared_distinctive_features is non-empty AND
    both_cards_pass`` (Codex lock — the gate is a validator computation, never
    trusted from provider output). The free-text ``evidence`` and the feature
    strings are carried through as opaque passthrough audit only.
    rR   rS   rT   )rR   rS   r   r   rP   r   Fznon-dict judge output)r   r   r   r   r   r   validation_okvalidation_errorr   r   r7   r   r   r   r   T)r   r   r   r   r   r   r   )r1   r   r0   updateJUDGE_STATUS_INVALIDEDGE_JUDGE_STATESr   r   r#   maxrc   r^   stripr_   JUDGE_STATUS_JUDGED)r   rawr   r   baserT   conf_rawconfrj   featsr   r   s               r(   validate_edge_judge_outputr   e  s   $   #xx0D
 c4 -"(*"'4 	 		
 GGL!E%%ww|$H(S%L)*Xt2L3Cx12GG9:@b@@aa!'') 	
@ 
  wwz"H%h4x"H 	 	"))	"JN	" !	  	KK($)3   K-s   .)Fc                    | j                  d      }|dk(  rt        S | j                  d      }|dk(  rDt        |t        t        f      r.t        |t
              s||k\  r| j                  d      du rt        S t        S )u$  Map one edge judgement to ``strong`` / ``different`` / ``weak``.

    Strong requires ALL of: ``same_space`` AND confidence >= threshold AND
    ``strong_parent_allowed`` (the bool the judge layer already computed — we do
    NOT re-parse ``shared_distinctive_features`` content here).
    r   r   r   r   r   T)r1   EDGE_DIFFERENTr   r   r   r#   EDGE_STRONG	EDGE_WEAK)jr   rT   r   s       r(   classify_edger     sv     EE,E!!55Dtc5\*4&))EE)*d2r*   c                    | |k  r| |fS || fS NrJ   )rp   rq   s     r(   	_pair_keyr     s    !VAq6'!Q'r*   )rZ   existing_anchor_bg_idsr   max_refsc                  #$%&'() t        | D ch c]  }t        |       c}      }t        |      }g }	i }
t               &g }g }|D ]  }|j                  d      |j                  d      }}||vs	||vs||k(  r3t	        ||      }|j                  i |d|i       t        ||      }|t        k(  r"t        |j                  d      xs d      |
|<   |t        k(  r&j                  |       |j                  |        d$&fd'|D ci c]  }|i  c})|
j                         D ]  \  \  }}}|)|   |<   |)|   |<    d%)fd%d&)fd$d'$%fd	}i }g }t        ||
      D ]  ##|v rg }t        )#   #)fd
      D ]W  ((|v r '(#      st        '(fd|D              r! '(#      r|	j                  ( d# d       G|j                  (       Y d# }||#<   |D ]  (||(<   	 |j                  #||d        |D ci c]  }|d   |d    }}|D ]Y  }|||      })|   D cg c]  }|j                  |      ||   k7  r| }}|s7|	j                  | dt        |       d| d       [ |D ]o  }|d   h|d   }|
j                         D cg c]!  \  }}|d   |v r|d   |v rt        |      |d# }}}||d<   t        t        d |D        d      d      |d<   q i } i }!|D ]7  }|d   #t         dd | #<   g |!#<   |d   D ]  (t"        #d | (<   #gd!| |!(<    9 t        |D ch c]5  \  }}|j                  |      |j                  |      k7  s*t        ||      7 c}}      }"|"r|	j                  t%        |"       d"       ||t'        |      | |!|"D cg c]  }t        |       c}|	d#S c c}w c c}w c c}w c c}w c c}}w c c}}w c c}w )(a  Build the deterministic ``space_partition_plan`` from edge judgements.

    Returns a dict with ``edge_judgements`` (echoed, classified),
    ``plate_groups`` ``[{plate_group_id, anchor_bg_id, member_bg_ids,
    share_score, evidence_edges}]``, ``node_assignments`` ``{bg: group_id}``,
    ``render_actions`` ``{bg: {render_action, reuse_target_bg_id}}``,
    ``ref_tree_parents`` ``{bg: [parent_bg_ids]}``, and ``diagnostics``.
    rR   rS   	edge_kindr   rP   c                     t        | |      v S r   )r   )xydifferent_pairss     r(   is_differentz0build_space_partition_plan.<locals>.is_different  s    A/11r*   c                     t        |          S r   )r_   ro   strong_nbrss    r(   degreez*build_space_partition_plan.<locals>.degree  s    ;r?##r*   c                <    t        |    j                               S r   )r`   r%   r   s    r(   conf_sumz,build_space_partition_plan.<locals>.conf_sum  s    ;r?))+,,r*   c                J    | v rdnd |         |        | v rd| fS d| fS )Nr   rH   rJ   )ro   r   r   r   rZ   s    r(   anchor_rankz/build_space_partition_plan.<locals>.anchor_rank  sK    --A1BZKb\Mz!A
 	
 ()
 	
r*   )keyc                       |     | fS r   rJ   )r   anchorr   s    r(   <lambda>z,build_space_partition_plan.<locals>.<lambda>  s    [=PQR=S<SUV;Wr*   c              3  0   K   | ]  } |        y wr   rJ   )rK   mmr   r3   s     r(   rN   z-build_space_partition_plan.<locals>.<genexpr>  s     -TGbl1b.AGs   z% both strong and different vs anchor u    — skippedzgrp::)anchor_bg_idmember_bg_idsplate_group_idr   r   z had strong edge(s) to z outside its group (anchor u3   ) — resolved by anchor order; review if ambiguousr   r   rH   )pairr   evidence_edgesc              3  &   K   | ]	  }|d      yw)r   NrJ   )rK   es     r(   rN   z-build_space_partition_plan.<locals>.<genexpr>.  s     %Br!aors   )defaultrQ   share_scorer   )render_actionreuse_target_bg_idNz` weak (adjacent/style/uncertain) cross-group edges available as context only, not strong parents)edge_judgementsplate_groupsnode_assignmentsrender_actionsref_tree_parentscross_group_context_edgesr\   )r   r^   r   r^   returnr#   )ro   r^   r   r   )ro   r^   r   r   )ro   r^   r   r   )ra   r^   r   r1   r   rb   r   r   r   r   r$   r]   anyr    rd   rc   
RENDER_NEWRENDER_REUSEr_   r0   )*bg_idsr   rZ   r   r   r   rq   bgsbg_setr\   strong_conf
weak_pairs
classifiedr   rp   kindr   ro   rm   r   assignedgroupsmembersgroup_idg	anchor_of	my_anchorr'   crossgsetkevr   r   	cross_ctxr   r   r   r   r   r3   r   s*     ``                               @@@@@@@r(   build_space_partition_planr    s   " V4VSVV45CXFK 13K5O(*J')JuuV}aeeFm1F?avoaQ 562Q2T231o;$QUU<%8%?C@K^#$c" 2 BE/E2B/EK &&(	AAqAq )$-

 
  "H#%F+.XF+1WXAH}Av&#-TG-T*T6*&&!,QRXQYYe'fgNN1 Y 6(##A"HQK v[cde% /, BHHA#$a&77IHhrl+	'O 5OqLLOx|3 O 5$-fUm_ =$+%XZ[  .!7Ao$67%++-P-$!Q1!A$$, Awa0- 	 P 
 %Br%BC!PRST-  13N-/>"3=UW!Xv#% ?#A2>V\ ]N1#)(9H"5Q $	  '1XzVaX\\!_UV5W1azXZI9~ < =	>
 & N(,7@%Ay!d1gy%A" _ 56 0F^ I5P$ 	Y &Bs/   O
OO= O*&O$+O*
1O*
8O0N)r%   r   r   r   )r2   r   r   zDict[int, Any])r=   r   r>   r   r?   r   r@   r   rA   r#   r   r^   )
r2   r   re   r   rC   r   rD   r   r   rE   )r   r   r   r#   )r   rE   r   rE   r   rE   r   rE   )r   rE   r   r^   r   rE   )
r   rE   r   r   r   r#   r   r   r   rE   )r   rE   r   r   r   r^   )rp   r^   rq   r^   r   zTuple[str, str])r   zIterable[str]r   zList[Dict[str, Any]]rZ   FrozenSet[str]r   r  r   r   r   r   r   rE   ).__doc__
__future__r   	itertoolsr   typingr   r   r   r   r	   r
   r   r   __annotations__r   r   r   r   r   r   r   r   r9   r:   r;   r8   r)   r5   rB   ru   r   r   r   r   r   r    r   r   r   r   r   r   r   r"   r  rJ   r*   r(   <module>r
     s`  %L # " H H H# u ## 	
%  5   S  #	$ *!$/4CHOS:  12__ #&_ 	_
 _ _H        : !'6G1HIx(X&h'(
	% > *2
  	
 <2 !#8A
A 
A 	A
 !A AH EZ ,( "+-6[#8HH *H 	H
 +H !H H Hr*   