
    Oj]                    :   U d Z ddlmZ ddlmZmZmZ dZdZ e	d eD              Z
ded<   d	Zded
<   dZdZedfZded<   dZdZdZdZded<   dZded<   dZded<   dZdHdZdIdZdJdZdKdZeeiZded<   d e	d  d	D              d!Zd"ed#<   dLd$Zd%d&d'd(d)d*Zd+ed,<   eZded-<   d.d/d0d1d1d2Z d+ed3<    e	d4 eD              Z!ded5<   d6Z"d7Z#d8Z$e"e#e$fZ%e"e"e#e#e$d9Z&d+ed:<   dMd;Z'dNd<Z(dOd=Z)dOd>Z*dPd?Z+ e+        dPd@Z, e,        dMdAZ-dQdBZ.dOdCZ/dPdDZ0dRdEZ1dSdFZ2yG)Tu  엔티티 등록 계약 — **한 벌**. ★두 곳에 적으면 한쪽만 고쳐진다.

Codex (2026-08-31): 「`MIN_OCCURRENCES=2` 와 `entity_steps` 의 `max_scenes=2`
가 여전히 두 상수이고, 시험이 production 소스를 정규식으로 읽습니다. 검사를
시험으로 옮겼을 뿐 **SOT 가 하나가 된 것은 아닙니다.**」

그래서 값을 **여기 한 곳**에 두고, 저빈도 필터를 부르는 자리와 chunk merge 가
**같은 이름을 소비**한다. 시험이 소스를 파싱할 일도 없어진다.

★owner→접두도 마찬가지다. `A0` schema 가 갈래를 늘리면 접두가 없어 최종 ID
발급이 **터진다** — 그래서 parity 를 검사한다.
    )annotations)DictOptionalTuple   )	characterlocationlocation_partoutlookpropc              #  ,   K   | ]  }|d k7  s	|  yw)r   N .0os     d/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/grounding_entity_contract.py	<genexpr>r   $   s      1=)!Q)^A)s   
Tuple[str, ...]ENTITY_SYNC_OWNER_TYPES)r   r	   r   GENERIC_PROMOTION_OWNERSgrounding_chunk_adapterz3.202609012800z2.202609011200PRODUCER_CONTRACT_ACCEPTEDgrounding_producer_payloadgrounding_facet_bindinggrounding_host_context)coarse_type_labelvisual_briefsearch_terms_nativelanguage_lock_nativehard_to_generateviewers_would_noticeshot_binding_statusshot_appearance_idsPRODUCER_PAYLOAD_FIELDS)r   JUDGE_FIELDS)r   r   r   r   surface_formSEARCH_FIELDSzscene-c                    t          |  S )u    씬 번호 → 원문 구간 id.)SEGMENT_ID_PREFIX)scene_indexs    r   	scene_keyr+   w   s     ..    c                    t        | xs d      }|j                  t              sy|t        t              d }|j	                         rt        |      S dS )u   원문 구간 id → 씬 번호. ★꼴이 아니면 `None` — 짐작하지 않는다.

    ★이것은 **꼴 되읽기**이지 뜻 판단이 아니다. 만드는 쪽과 같은 규칙을
    쓴다(위 `scene_key`).
     N)str
startswithr)   lenisdigitint)
segment_idgottails      r   scene_index_of_keyr7   |   sM     jB
C>>+,s$%&'D3t90D0r,   c                (   g }dD ]  }| xs i j                  |      xs i }|j                  d      xs dD ]N  }|xs i j                  d      xs i j                  d      }t        |      }|9||vs>|j                  |       P  t        |      S )u  이 줄이 **원문 어느 씬**에 나오나. ★producer 가 적은 좌표만 본다.

    ★★★이름·부분문자열로 짐작하지 않는다 (사용자 절대 규칙). producer 가
    `occurrences[].source_span.segment_id` 로 **구조화해 적어 둔 것**만 읽는다.
    좌표가 없으면 **빈 목록**이다 — 부모가 보인다고 딸려 들어가지 않는다.

    ★두 자리에 적힌다: 걸러진 엔티티는 `grounding_provenance`, 장부 줄은
    `source_evidence`. 둘 다 본다.
    )grounding_provenancesource_evidenceoccurrencesr   source_spanr4   )getr7   appendsorted)entityoutholderblockr   segidxs          r   scene_indices_ofrF      s     C=2""6*0b))M*0b0AG==/52::<HC$S)C3c>

3	 1 > #;r,   c                    ddl }i }t        D ]8  }| xs i j                  |      }|
|dk(  s|g k(  r%|j                  |      ||<   : |S )u   reduced 행 → producer 판정 **한 덩어리**. ★중앙 helper 하나뿐이다.

    ★없는 칸은 **안 만든다.** 지어낸 값은 판정이 아니다.
    r   Nr.   )copyr$   r=   deepcopy)row_copyr5   kvs        r   producer_payloadrN      sU    
 C$YBOOA9R17"A	 %
 Jr,   zDict[str, Tuple[str, ...]]KNOWN_PRODUCERS))r
   r	   c              #  $   K   | ]  }||f 
 y wNr   r   s     r   r   r      s      ;91A9s   )part_ofvisual_variantz&Dict[str, Tuple[Tuple[str, str], ...]]RELATION_OWNER_PAIRSc           
         t         j                  t        | xs d      d      }t        t	        |D ch c]  }|D ]  }|  c}}            S c c}}w )uV   그 relation 이 **닿는** 갈래들. ★목록을 손으로 다시 적지 않는다.r.   r   )rT   r=   r/   tupler?   )relation_typepairspairr   s       r   relation_sync_owner_typesrZ      sH     $$S)<"%=rBEu;utddu;<==;s   ACLPLPO)r   r	   r   r
   r   zDict[str, str]OWNER_PREFIXREFERENCE_SUPPORTED_OWNERSr   character_outlookr   
background)r   r   r   r	   r
   REFERENCE_KIND_BY_OWNERc              #  P   K   | ]  }t         j                  |      d k(  r|   yw)rc   N)rd   r=   r   s     r   r   r      s+      07)!""1%5 )s   $&BACKGROUND_LANE_OWNERSpolicybackground_sidecarcharacter_outlook_attachment)r   r   r	   r
   r   REFERENCE_ENFORCEMENT_BY_OWNERc                F    t         j                  t        | xs d            S )uL   이 갈래의 참조를 **누가 집행하나**. 모르는 갈래면 `None`.r.   )rj   r=   r/   
owner_types    r   enforcement_gate_ofrn     s    )--c*2B.CDDr,   c                n      t         vrt        d dt                t         fdt        D              S )uV   그 집행자가 맡은 갈래들. ★부르는 쪽이 목록을 다시 적지 않게.u   모르는 집행자 u    — c              3  R   K   | ]  }t         j                  |      k(  r|   y wrQ   )rj   r=   )r   r   gates     r   r   z%owners_enforced_by.<locals>.<genexpr>   s-      C6q266q9TA 6s   $')ENFORCEMENT_GATES
ValueErrorrV   MATERIALIZABLE_OWNER_TYPES)rq   s   `r   owners_enforced_byru     sE    $$/xu=N<OPQQ C6 C C Cr,   c                      t        t              S )u3  AI canonical ref(참조 이미지)를 **만들고·요구하고·싣는** 갈래 — 정책 집행 갈래 그대로.

    ★한 이름을 네 소비자가 같이 쓴다: readiness(`asset_readiness`) · 게이트
    (`pipeline_gate.episode_reference_entities`) · 생산자(`reference_phase1_service`
    와 orchestrator 의 저빈도 스킵) · 최종 ref map(`scene_reference_service`).
    ★★실측 (Codex BLOCK 2026-09-02): 계약은 location_part → background_sidecar
    하나인데 세 소비자는 `entity_type not in ("location", "outlook")` 로 적어
    LP 가 base ref 대상에 들었다 — 사람이 확인한 실사 sidecar 와 모델이 지어낸
    LP canonical ref 가 같은 그림에 실리고, LP 5장(정상)/25장(hard)을 헛산다.
    집행자는 한 갈래에 **하나**다.
    )ru   ENFORCE_BY_POLICYr   r,   r   canonical_ref_owner_typesrx   $  s     /00r,   c                      t        t              S )uR   배경 sidecar 가 집행하는 갈래 — canonical ref map 에서 **빠진다**.)ru   ENFORCE_BY_BACKGROUND_SIDECARr   r,   r   sidecar_owner_typesr{   3  s    ;<<r,   c                 *   t         D  cg c]  } t        j                  |       t        vr| ! }} |rt	        d| d      t        t        t                    }t        t              }||k7  r#t	        dt        |       dt        |             yc c} w )u  ★다섯 갈래가 **각각 집행자 하나**를 갖나. 늘리면 여기서 선다.

    ★★배경 자리로 가는 갈래는 반드시 `background_sidecar` 다 —
    `grounding_reference_obligations.plan` 이 그 갈래에만 `purpose` 를 주고
    묶음이 그것으로 거른다. 선언이 그 사실과 어긋나면 한쪽이 거짓말이다.
    ★★그 반대도 본다 — 배경 자리가 아닌 갈래를 `background_sidecar` 로
    선언하면 묶음은 그 줄을 `purpose` 가 없어 **조용히 건너뛴다**.
       갈래 u=    에 집행자가 없다 — 늘렸으면 여기도 적어라u0   배경 자리 선언이 어긋난다 — 선언 u*    · 의무가 `purpose` 를 주는 갈래 N)
rt   rj   r=   rr   rs   setru   rz   rf   r?   )r   missingr5   wants       r    assert_enforcement_covers_ownersr   8  s     5 )4Q044Q7'( 4G ) gY[\^ 	^
 !>?
@C%&D
d{>vc{m L55;D\NDE 	E )s   $Bc                     t         D  cg c]  } | t        vr|  }} |rt        d| d      t        D  cg c]  } | t        vr|  }} |rt        d| d      yc c} w c c} w )u  ★투영이 **다섯 갈래를 다 덮나.** 갈래가 늘면 여기서 선다.

    ★★그리고 **선언과 수용을 가른다** — 투영에 있다고 참조를 만드는 것이
    아니다. 실제로 여는 것은 `REFERENCE_SUPPORTED_OWNERS` 이고, 그것을
    넓히는 것이 곧 cutover 다. 미리 넓히면 새 producer 없이 옛 유료 경로만
    켜진다(2026-08-31 에 실제로 그랬다).
    r}   uF    가 참조 kind 투영에 없다 — 늘렸으면 여기도 적어라u   연 갈래 u    가 투영 밖이다N)rt   rd   rs   ra   )r   r   outsides      r   assert_projection_covers_ownersr   R  s     5 44Q22 4G 4gYdeg 	g4 44Q22 4G 4;wi/DEFF 4
4s
   AAc                F    t         j                  t        | xs d            S )u   이 갈래가 **어느 참조 자리**로 가나. 모르는 갈래면 `None`.

    ★부르는 쪽이 `"background"` 같은 값을 손으로 적지 않게 한다.
    r.   )rd   r=   r/   rl   s    r   reference_kind_of_ownerr   h  s    
 #&&s:+;'<==r,   c                0    t        |       }|t        v r|S dS )u  이 신원으로 **참조를 만들 수 있나**. 못 만들면 `None`.

    Returns:
        만들 수 있으면 그 갈래(`"character"`/`"prop"`), 아니면 `None`.
        ★신원이 아닌 글자(`Pfoo`)도 `None` 이다 — 접두만 보면 지나간다.

    ★열려 있는 갈래(`REFERENCE_SUPPORTED_OWNERS`)만 답한다 — 투영에 있다는
    것과 지금 만들 수 있다는 것은 다르다.
    N)owner_of_final_idra   )short_idr5   s     r   reference_owner_ofr   p  s!     H
%C333==r,   c                    ddl m}  | j                         d   | j                     d   }t	        |t
              rddl}|j                  |      }t        |j                  d|      j                  di       j                  di       j                  d	i       j                  di       j                  d
i       j                  d      xs d      }|st        d      |S )u   다섯 갈래 — ★`grounding_a0` schema **에서** 온다.

    여기 손으로 다시 적으면 A0 가 갈래를 늘려도 이쪽은 모른다.
    r   )grounding_a0stemscontentNschema
properties
candidatesitemsrm   enumr   u^   A0 schema 에서 owner enum 을 못 읽었다 — 여기 손으로 적으면 두 벌이 된다)app.modules.pipeliner   	load_packSCHEMA_STEM
isinstancer/   jsonloadsrV   r=   RuntimeError)a0schr   r5   s       r   ownersr   ~  s    
 8
,,.
!"..
1)
<C#sjjo
3'++L"=#lB'GR(8#lB'L"(=cc&kQNPRC  J K 	KJr,   c            	         t        t                     t        t              }} | |k7  r)t        dt	        | |z
         dt	        || z
               y)u   ★A0 갈래와 접두표가 **같은 집합**인가.

    안 맞으면 `assert_rows` 는 통과시키는데 최종 ID 발급이 **터진다** —
    받는 문과 내보내는 문이 갈린 것이다 (Codex).
    u2   A0 갈래와 접두표가 다르다 — A0 에만 u    · 접두에만 N)r~   r   r`   AssertionErrorr?   )abs     r   assert_prefix_parityr     sX     vx=#l+qAAv@A P"1q5M?,- 	- r,   c                p    t        |       }|y|t        t        |       t        t        |         d       fS )u#  `final_id` → `(owner, 번호)`. ★모르면 `None` — **짐작하지 않는다**.

    ★★★왜 이 함수가 필요한가 (2026-08-31 실측).

    `EntitySyncService` 가 번호를 이렇게 셌다 —

        prefix = short_id[0]                 # `LP01` → **"L"**
        int(short_id[1:])                    # int("P01") → ValueError
        except (ValueError, IndexError): 「형식이 이상하다」로 **건너뛴다**

    즉 `LP01` 이 **죽지 않고 조용히 빠진다.** 그러면 `L` counter 가 실제보다
    작아져 **이미 있는 `L##` 를 다시 발급**할 수 있다 — 오류도 안 난다.

    ★`owner_of_final_id` 와 **같은 규칙**(가장 긴 접두 + 뒤가 전부 숫자)을
     쓴다. 파서를 두 벌로 만들면 한쪽만 고쳐진다.
    N)r   r3   r/   r1   r`   )final_idowners     r   split_final_idr     s<    " h'E}#c(mCU(;$<$=>???r,   c                   t        | xs d      }|syt        t        j                         d       D ]M  \  }}|j	                  |      s|t        |      d }|s)|j                         s:|j                         sK|c S  y)u  신원 문자열이 **어느 갈래의 것**인가. ★못 읽으면 `None`.

    ★★두 가지를 **같이** 봐야 한다 (Codex 2026-08-31).

    ①`startswith` 만으로는 안 된다 — 접두표가 **prefix-free 가 아니다**.

        location       "L"
        location_part  "LP"      ← `LP01` 이 `"L"` 로도 시작한다

    그래서 `location` 행에 `LP01` 이 와도 통과했다.

    ②접두를 떼고 남은 것이 **숫자여야** 한다. `Pfoo` 도 `"P"` 로 시작하므로
    접두만 보면 `prop` 으로 통과한다 — 신원이 아닌 것이 신원 자리에 앉는다.

    가장 긴 접두부터 보고, 남은 것이 **한 자 이상 전부 숫자**인 첫 갈래를
    돌려준다. 접두가 겹칠 때 긴 쪽이 숫자로 안 끝나면 짧은 쪽도 본다.

    ★**이 함수가 한 곳이다.** 부르는 쪽이 각자 접두를 비교하면 접두표가 바뀔
    때 한쪽만 고쳐지고, adapter 가 제 문법을 하나 더 만들면 두 벌이 된다.
    r.   Nc                     t        | d          S )N   )r1   )kvs    r   <lambda>z#owner_of_final_id.<locals>.<lambda>  s    SAZKr,   )key)r/   r?   r`   r   r0   r1   isasciir2   )r   fidr   prer6   s        r   r   r     sv    * hn"
C\//1!79
s~~c"3s89~ DLLNt||~L9 r,   N)r*   Anyreturnr/   )r4   r   r   zOptional[int])r@   Dict[str, Any]r   z	List[int])rJ   r   r   r   )rW   r/   r   r   )rm   r/   r   Optional[str])rq   r/   r   r   )r   r   )r   None)r   r/   r   r   )r   r/   r   zOptional[Tuple[str, int]])r   r/   r   r   )3__doc__
__future__r   typingr   r   r   ENTITY_MIN_OCCURRENCESrt   rV   r   __annotations__r   PRODUCER_ISSUERPRODUCER_CONTRACT_VERSIONr   PRODUCER_PAYLOADFACET_BINDINGHOST_CONTEXTr$   r%   r'   r)   r+   r7   rF   rN   rO   rT   rZ   r`   ra   rd   rf   rw   rz   ENFORCE_BY_OUTLOOK_ATTACHMENTrr   rj   rn   ru   rx   r{   r   r   r   r   r   r   r   r   r   r,   r   <module>r      s"   # ( (  1  ,1 1=)1= ,=  = -N / M , - /H/?/A O A 0  *
 (,   !7o 6"G G  /

1* //+  0 ;9; ;@ < >  n & /I O H* "!+   +0 07)07 +7  7*   4  > &(E24  #-2,2  E
C1=
E. ! "G&   !>>*
-@.!r,   