
    Oj!#                        U d Z ddlmZ ddlmZmZmZmZmZ ddl	m
Z
mZ dZdZdZdZe
e   D  ci c]  \  } }| |
 c}} Zd	ed
<    G d de      Z	 	 ddZddZddZ	 	 	 	 ddZyc c}} w )u3  구조 관계를 `entity_relation` CP 모양으로 **투영**한다. ★inert. 유료 0.

## 무엇인가

`location_part` → `location` 의 `part_of` 를 **관계 표**에 넣는다.
★새 표를 안 만든다 — `RelationFact`/`RelationParticipant` 가 이미
`relation_family`·`relation_type`·참가자 `role`/`order` 를 갖는다.

## 지켜야 할 것 (Codex 2026-08-31)

    ★**한 CP = 한 producer.** `entity_relation` CP 는 활성
     `EntityRelationStep`(13.6)이 만든다. adapter 가 그 CP 를 **직접 쓰면**
     `visual_variant` 와 `part_of` 가 실행·재개 차례에 따라 **서로 덮는다**.
     → `v2_chunk` 에서는 그 스텝이 **결정적 no-call projection** 으로 이것을
       불러 CP **한 벌**을 만든다.

    ★**타입별로 가른다.** 한 타입의 sync 가 다른 타입을 **stale 로 지우면
     안 된다**. 실측: 기존 조회는 이미 `WHERE relation_type='visual_variant'`
     로 잠겨 있다 — 새 `part_of` 갈래도 **제 타입만** 봐야 한다.

    ★`outlook → character` 는 **여기 안 온다.** `CharacterOutlook` 이 SOT 이고
     orchestrator 가 RelationSync(2) → OutlookSync(4) 라 관계를 쓸 때 outlook
     canon 이 **아직 없을 수 있다**.

## 아직 안 쓰인다

`app/` 어디서도 부르지 않는다.
    )annotations)AnyDictListSequenceTupleRELATION_OWNER_PAIRSowner_of_final_idpart_ofz2.202609020300partwholezDict[str, str]ALLOWED_PART_OFc                      e Zd ZdZy)RelationProjectionErroruC   투영이 계약을 어겼다. ★조용히 넘어가지 않는다.N)__name__
__module____qualname____doc__     h/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/grounding_relation_projection.pyr   r   8   s    Mr   r   c                <   ddl m}m} t         |j                  t
        d            }g g }}| D ]  }t        |xs i j	                  d      xs d      }t        |xs i j	                  d      xs d      }|r|st        d|       ||       ||      f}	|	d   |	d   t        d	|d
|	d    d|d
|	d    d	      ||k(  rt        d|      |	|v r|j                  ||d       |j                  ||t        |	      dt        |       d        ||dS )u  `part_of` 짝들 → **DB 가 받는 것**과 **안 받는 것**을 갈라 낸다.

    ★★★producer 의 `part_of` 는 **한 벌인데 소비자가 둘**이다 (실측
    2026-09-02 유료 재개):

        `grounding_facet_binding`  아웃룩·장소부분을 주인에 붙이는 **재료**.
                                   `O01 → C01` 같은 짝이 **거기 쓰인다**
        이 투영                    DB `relation_fact` — 계약이 받는 짝은
                                   `RELATION_OWNER_PAIRS["part_of"]` 뿐

    그래서 producer 가 `O01 → C01` 을 내는 것은 **옳고**, 좁은 쪽은 이 투영이다.
    앞 판은 그것을 받고 `assert_pair` 로 **주행을 세웠다** — 실제 유료 주행이
    `entity_relation` 에서 죽었다.

    ★**조용히 버리지 않는다** — 안 받는 짝도 까닭과 함께 돌려준다.

    Returns:
        `{"for_db": [...], "not_for_db": [{"part","whole","owners","why"}]}`
    r   r	   r   r    r      짝이 비었다:       정본 ID 가 아니다: part=(u   ) · whole=)u   자기 자신을 잇는다: r   r   uF   이 갈래 짝은 DB `part_of` 계약이 안 받는다 — 받는 것 )r   r   ownerswhy)for_db
not_for_db).app.modules.pipeline.grounding_entity_contractr
   r   setgetRELATION_PART_OFstrr   appendlistsorted)
linksr
   r   okr#   otherxr   r   pairs
             r   select_db_part_ofr2   <   s]   *1 
%!%%&6;	<BEFAG==(.B/QW"MM'*0b15),>qe*DEE!$'):5)AB
 7?d1go)0$q' C	47)1./ / 5=),H*QRR2:MM4%89LL$$(J#44:2J<#AC D' . E22r   c           
     `   i }| D ]k  }t        |xs i j                  d      xs d      }t        |xs i j                  d      xs d      }|r|st        d|      t        ||       d|||f<   m t	        |      D cg c]   \  }}t
        |t        dd|t        ddgd	" c}}S c c}}w )
u|  `{part, whole}` 정본 ID 짝들 → `entity_relation` CP 행들.

    ★**결정적**이다 — 같은 입력이면 같은 차례·같은 내용.

    Args:
        links: `{"part": "LP01", "whole": "L03"}` 꼴. ★**정본 ID** 다.

    Returns:
        CP 행 목록. 각 행은 `relation_type` 을 **명시**한다 —
        그래야 sync 가 타입별로 가른다.
    r   r   r   r   Nr   )short_idroleorder   )relation_typeparticipants)r)   r'   r   assert_pairr,   r(   	ROLE_PART
ROLE_WHOLE)r-   seenr0   r   r   pws          r   project_part_ofr@   p   s     )+DAG==(.B/QW"MM'*0b15),>qe*DEED% "dE]  ,	  $!Q *&'QG&'aHJ 	     s   %B*c                ,   t        |       t        |      }}||t        d| d| d|d| d	      t        j                  |      }|t        d| dt	        t                     ||k7  rt        d| d| d	| d
      | |k(  rt        d|        y)u  이 짝이 **갈래 계약 안**인가. ★아니면 선다 — 조용히 넘어가지 않는다.

    ★★★`desired_keys` 가 이걸 안 부르던 것이 결함이었다 (Codex BLOCK
    2026-09-01). `project_part_of` 만 검사하고 sync 는 참가자 역할·존재만 봐서,
    망가진 CP 의 `C01(part) → P01(whole)` 도 양쪽 canon 만 있으면 **DB 에
    `part_of` 로 저장**됐다.
    Nr   r   z) whole=r   `u)   ` 는 `part_of` 로 잇지 않는다 — u   ` 의 부모는 `u   ` 여야 하는데 `u   ` 다u   제 자신에 붙었다: )r   r   r   r'   r,   )r   r   powowants        r   r:   r:      s     t$&7&>B	zRZ%,THAbT%!B4qQS 	Sr"D|%t<VO=T<UVX 	X	Tz%t$TF*>rd%HJ 	Ju}%(A$&HII r   c          
     *   g }| D ]  }t        |xs i j                  d      xs d      |k7  r)|j                  d      xs dD ci c]5  }t        |j                  d            t        |j                  d            7 }}|t        k(  rc|j                  t              |j                  t              }}|r|st        dt         d|       t        ||       |j                  ||f       t        d	|       t        t        |            S c c}w )
u  CP 행들에서 **그 타입만** 골라 짝을 낸다.

    ★★한 타입의 delta 가 다른 타입을 **stale 로 지우면 안 된다** (Codex).
    부르는 쪽이 타입을 **명시**하게 해서 실수로 넓히지 못하게 한다.

    ★★★그리고 **갈래 짝을 여기서도 검사한다** — DB sync 가 실제로 부르는
    자리가 여기다. 투영에서만 검사하면 CP 가 어디서 왔든 믿는 것이 된다.
    r8   r   r9   r   r5   r4   rB   u!   ` 에 참가자가 모자란다: u   이 투영이 모르는 타입 )
r)   r'   r(   r;   r<   r   r:   r*   r,   r&   )rowsr8   outrr>   by_roler?   s          r   desired_keysrK      s    "$CR}}_-34EUU>28b8;8q quuV}%s155+<'==8 	 ;,,;;y)7;;z+BqAA-())J7)TV V1JJ1v)1-1BCE E  #c(;s   :DN)r-   Sequence[Dict[str, Any]]returnzDict[str, Any])r-   rL   rM   zList[Dict[str, Any]])r   r)   r   r)   rM   None)rG   rL   r8   r)   rM   zList[Tuple[str, str]])r   
__future__r   typingr   r   r   r   r   r%   r
   r   r(   PROJECTION_CONTRACT_VERSIONr;   r<   r   __annotations__RuntimeErrorr   r2   r@   r:   rK   r    s   00r   <module>rT      s   8 # 3 3-   / 	
 $88H#I#K#IKD%D%K#I#K KNl N13)13h 6J.#&+@]#Ks   A(