
    Oj-                        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e
eefZdZdZd	Zd
d	 	 	 	 	 	 	 ddZddZddZddZdZdZeefZ	 	 	 	 ddZy)u<  `location_part` 가 **어디에 달려 있나** — 세 상태 계약. ★유료 0.

## 왜 있나 (Codex DESIGN BLOCK 4, 2026-08-31)

부분만 홀로 찾으면 **어느 장소의 무엇인지**를 못 준다. 실측(1960년대 원고):
「이발소 회전 간판」은 `part_of` 부모가 **아예 없어서** 홀로 대상이 됐다 —
「이발소」가 행에 없기 때문이다.

★그렇다고 **부모 엔티티를 지어내면 안 된다.** 원문에 독립된 부모가 없는데
엔티티를 만들면 없는 신원을 만들고 같은 장소가 구간마다 중복 생긴다.
**엔티티 관계**와 **검색 맥락**은 다른 것이다 — 그 둘을 갈라야 사용자 요구
(「1960년대 이발소 맥락 + 회전 간판 상세, 참조 여럿, 완전 자동」)를 지킨다.

## 세 상태

    bound_parent            원문에 부모가 **독립 대상**으로 있다
                            → `parent_local_id` **필수** · rows 에 **존재** ·
                              owner **= location** · `part_of` **정확히 1개**
    explicit_context_only   장소 맥락은 있는데 **부모 행이 없다**
                            → `parent_local_id` **금지** ·
                              `search_subject`·`evidence` **필수**
    unresolved              맥락도 못 정했다
                            → 의미 칸을 **몰래 쓰지 않는다**

★`grounding_shot_catalog` 의 세 상태 계약과 **같은 모양**이다 — 어기면
「그럴듯한 기본값」으로 접지 않고 **`unresolved` + 사유**로 내린다.

## 갈리면 고르지 않는다

같은 LP 가 여러 구간에서 **서로 다른** `host_context` 를 내면 한쪽을 고르지
않는다 — `conflict` 사유와 함께 **`unresolved`** 로 남긴다. 그래야 나중에
사람이 무엇이 갈렸는지 본다.
    )annotations)AnyDictListOptionalSequenceTuplebound_parentexplicit_context_only
unresolvedlocation_partlocation)parent_local_idsearch_subjectT)evidence_okc          	     j   t        | xs i       }t        |j                  d      xs d      j                         }|t        vrt
        i d|dt         fS t        |j                  d      xs d      j                         }t        |j                  d      xs d      j                         }t        |j                  d      xs d      }	|t
        k(  r[t        D 
cg c]1  }
t        |j                  |
      xs d      j                         s0|
3 }}
|rt
        i d	t
         d
| dfS |i dfS |t        k(  rO|rt
        i d	t         d|dfS |st
        i d	t         dfS |	st
        i d	t         dfS |s	t
        i dfS |||	ddfS |st
        i d	t         dfS |j                  |      }|t
        i d	t         d|dfS t        |j                  d      xs d      }|t        k7  rt
        i d|d|dt         dfS |D cg c]+  }t        |j                  d            t        |      k(  s*|- }}t        |      dk7  rt
        i dt        |       dt         dfS t        |d   j                  d             |k7  r"t
        i d!|d   j                  d       d"|d#fS |d|idfS c c}
w c c}w )$u  한 행의 `host_context` 를 계약에 맞춰 내린다.

    Args:
        hc: 모델이 낸 것. 없으면 `unresolved`.
        row_local_id: 이 행의 ID.
        rows_by_id: 같은 판독의 모든 행 (부모 존재·갈래 확인용).
        part_of: `{"part", "whole"}` 관계 목록.
        evidence_ok: 근거 인용이 **원문 그 자리에 있었나** — 부르는 쪽이
            기존 span 대조(`grounding_chunk._find_span`)를 태워 넘긴다.
            ★여기서 원문을 다시 읽지 않는다(두 벌이 된다).

    Returns:
        `(state, kept, why)`. 어기면 `state=unresolved` 이고 `why` 에 사유.
        ★「그럴듯한 기본값」으로 접지 않는다.
    state u   모르는 맥락 상태     — r   r   evidence `u   ` 인데 의미 칸 u6    에 값이 있다 — 「모른다」와 어긋난다u   ` 인데 `parent_local_id` 가 u=    다 — 부모 행이 있으면 `bound_parent` 여야 한다uS   ` 인데 `search_subject` 가 없다 — 무엇을 맥락으로 찾을지가 없다ua   ` 인데 근거가 없다 — 원문 어디서 왔는지 없으면 지어낸 것과 못 가른다uT   근거 인용이 원문 그 자리에 없다 — 지어냈거나 다듬어 적었다)r   r   u%   ` 인데 `parent_local_id` 가 없다u   ` 의 부모 u    가 판독 행에 **없다**
owner_typeu   부모 u    의 갈래가 u
    다 — `u   ` 여야 한다part   u   `part_of` 가 u   개다 — `u   ` 는 정확히 1개r   wholeu   `part_of` 부모 u    와 `parent_local_id` u    가 다르다)dictstrgetstrip	HC_STATESHC_UNRESOLVEDlist_SEMANTIC_FIELDSCONTEXT_ONLYBOUND_PARENTPARENT_OWNERlen)hcrow_local_id
rows_by_idpart_ofr   dstpidsubjevksmuggledparentpownerxlinkss                   a/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/grounding_host_context.py	normalizer9   5   sF   & 	RX2A	QUU7^!r	"	(	(	*B	b$<RF%	{"SSS
aee%&,"
-
3
3
5Cquu%&,"-335D	aeeJ%2	&B	]  0O/!3quuQx~23F3L3L3NA/O "M?"6xj A2 23 3 2rz	\ "L>!@ HG GH H  "L> "= => >  "L> "; ;< <  "fh hd;R?? b~BCE 	E^^C F~b~]3'1NOQ 	QL)/R0FbcWOF:Z~_]_ 	_K13quuV}#5\9J#JQEK
5zQbSZL\NBVWY 	Y
58<< !S(baW 58 9!!$~78 	8 !3'++[ PJ Ls   1J+J++J01J0c                   g }| D ]  }t        |      }t        |      dk(  r2|j                  t        |d         t	        |d   xs i       ddf       Ot        |      dk(  rB|j                  t        |d         t	        |d   xs i       t        |d   xs d      df       t        |      dk(  rR|j                  t        |d         t	        |d   xs i       t        |d   xs d      t        |d   xs d      f       t        d|d       |D cg c]  \  }}}}|s||f }}}}}|s	t        i d	fS |D 	
ch c]	  \  }	}}
}|	 }}
}}	}t        |      dkD  rt        t        |      d
t        |       dfS |d   d   }	|	t        k(  r|	t        |      dfS |	t        k(  rdnd}|D 
ch c]%  \  }}}
}t        |j                  |      xs d      ' }}
}}}t        |      dkD  r"t        t        |      d| dt        |       dfS |t        |      d   i}|D 
ci c]-  \  }}}
}|j                  d      xs dD ]  }t        |      | / }}}
}}}|rt        |      D cg c]  }||   	 c}|d<   |j                  t        |             |	|dfS c c}}}}w c c}}
}}	w c c}}
}}w c c}}}
}}w c c}w )uh  구간마다 낸 것을 합친다. ★**한쪽을 고르지 않고, 버리지도 않는다**.

    ★★★앞 판은 뜻이 같으면 `got[0]` 을 그대로 돌려줬다 — 그러면 **뒤 구간의
    근거가 통째로 사라진다**(Codex 2026-08-31). 그리고 `normalize` 가 낸
    실패 사유(`why`)가 서명에 아예 없어서 **왜 떨어졌는지도 잃었다**.
    다구간에서는 이것이 **실제 기록 손실**이다.

    이제 —

        뜻이 같으면      근거를 **결정적으로 합친다**(정렬·중복 제거).
                        입력 차례가 바뀌어도 **같은 결과**여야 한다
        뜻이 다르면      `conflict` → `unresolved` (앞과 같다)
        실패한 구간      사유를 **구간별로 보존**한다

    Args:
        seen: `(state, kept)` 또는 `(state, kept, why)`. ★뒤엣것을 권한다 —
            `why` 를 안 주면 실패 사유가 어디에도 안 남는다.

    Returns:
        `(state, kept, why)`. `kept["evidence"]` 는 **합쳐진** 근거이고,
        `kept["per_chunk"]` 에 구간별 사유가 남는다.
       r   r   r         u   모르는 모양 u%    — (state, kept[, why[, chunk_id]])u   맥락을 낸 구간이 없다u(   구간마다 맥락 상태가 다르다 u    — conflictr   r   u   구간마다 u    가 다르다 r   r   )tupler(   appendr   r   
ValueErrorr"   _auditsortedr&   r   _ev_keyupdate)seenrowsitemt_s_kwcauditr.   _w_cstateskeyr2   valsoutr6   r1   s                      r8   	reconcilerT      s   . 8:D$Kq6Q;KKQqTD1$4b"=>Vq[KKQqTD1$4c!A$*"orJKVq[KKQqTD1$4c!A$*"oQqTZR* + #A5(MNP P  )-2B1aVE2b"BBB'+,t^RRbtF,
6{QfUm6vf~6FmTV 	V	aB	]6%="$$!\1
7GC7;<tmb!RCc
 b!tD<
4y1}fUmC5t~]KM 	M tQ0C .2 
.TMB2rEE*%++a !*a-+ TB 
.	*0*5*Q2a5*5JJJve}sB;5 3 - =
. 6s$   J'
(J'
J/
8*J7
/2J?6Kc                    t        | t              rYt        | j                  d      xs d      t        | j                  d      xs d      t        | j                  d      xs d      fS ddt        |       fS )u!  근거 하나의 **정본 열쇠**. ★같은 자리를 가리키면 같은 것으로 본다.

    구조화된 span 이면 `(segment_id, start, end)`, 글자만 오면 그 글자.
    ★글자 비교로 **뜻**을 판단하는 것이 아니다 — **같은 자리인가**만 본다.
    
segment_idr   startend)
isinstancer   r   r   )r6   s    r8   rC   rC      sh     !TAEE,'-2.AEE'N4Hb0IAEE%L&B') 	)CF    c           
         t        | D ch c]"  \  }}|s	t        |xs d      t        |      f$ c}}      }|rd|D cg c]
  \  }}||d c}}iS i S c c}}w c c}}w )u  구간별 실패 사유. ★**결정적**이어야 한다 — 차례에 안 흔들린다.

    ★★★앞 판은 받은 차례 그대로 담아서 `['A','B']` 와 `['B','A']` 가 **다른
    결과**였다(Codex 재현 2026-08-31). 그리고 **어느 구간 사유인지**도 없었다
    — 「per_chunk」라고 이름만 붙였지 구간을 안 적었다.

    이제 `(chunk_id, 사유)` 로 **정렬·중복 제거**한다.
    ★비면 칸을 안 만든다 — 빈 칸이 늘면 신원이 흔들린다.
    r   	per_chunk)chunk_idwhy)rB   r   )reasonsrL   rgots       r8   rA   rA      so     WBWTQ3qwB<Q(WB
CC D1q3DE  CDs   
A
A
Acontextdetailc                   ddl }|t        vrt        d|dt               t        | xs d      j	                         st        d      t        |xs d      j	                         st        d      |j                         }t        |       t        |      t        |      fD ]3  }|j                  |j                  d             |j                  d	       5 |j                         dd
 S )uC  묶음 멤버 하나의 신원. ★**결정적**이어야 재사용과 재구매가 안 갈린다.

    접는 것 — LP 정본 ID · 쓰임 · **실제 나가는 획득 신원**.
    ★쓰임을 빼면 맥락과 상세가 같은 것이 되어, 부모를 재사용했더니 상세까지
    안 사는 일이 난다.
    r   Nu   모르는 쓰임 r   r   u    `subject_final_id` 가 비었다u?   획득 신원이 비었다 — 무엇으로 사는지가 없다zutf-8       )	hashlibPURPOSESr@   r   r    sha256rD   encode	hexdigest)subject_final_idpurposeacquisition_identityrg   hr   s         r8   member_identityrp      s     h,WKuXJGHH%2&,,.;<<#)r*002Z[[A%&Gc:N6OP	W%&	 Q ;;="rZ   N)r)   zOptional[Dict[str, Any]]r*   r   r+   zDict[str, Dict[str, Any]]r,   zSequence[Dict[str, Any]]r   boolreturnTuple[str, Dict[str, Any], str])rE   zSequence[Any]rr   rs   )r6   r   rr   zTuple[str, str, str])r_   zSequence[Tuple[str, str]]rr   zDict[str, Any])rl   r   rm   r   rn   r   rr   r   )__doc__
__future__r   typingr   r   r   r   r   r	   r&   r%   r"   r!   HOST_CONTEXT_OWNERr'   r$   r9   rT   rC   rA   PURPOSE_CONTEXTPURPOSE_DETAILrh   rp   r   rZ   r8   <module>rz      s    B # = = &<7	 %  9  #'L,3L,/L,  L, ,KL,^@F	  ^,*-25rZ   