
    Ojw                       d Z ddlmZ ddlZddlZddlmZmZmZm	Z	m
Z
 dZdZdZeefZdZ G d	 d
e      Z G d de      Zd%dZd%dZd&dZ	 	 d'dZ	 	 d'dZ	 	 	 	 d(dZdd	 	 	 	 	 	 	 d)dZd*dZ	 	 	 	 d+dZdd	 	 	 	 	 	 	 	 	 d,dZ G d de      Z	 	 d-dZ	 	 d.dZ d/dZ!	 	 d-d Z"dd!	 d0d"Z#	 d1	 	 	 d2d#Z$d3d$Z%y)4uL  사람이 **그 사진이 그 시대·그 지역 것인지** 정한 기록. ★DB 가 정본.

coarse 심판은 「무엇인가 · 보이는가」만 본다 — 시대·나라·정확성은 **안 묻는다**
(사용자 확정 2026-08-31). 그러니 `selected` 는 「그 종류의 사진을 골랐다」일
뿐이고, 그 사진이 맞는지는 **사람만** 정한다.

★★★계약 (Codex 2026-09-02) —

    · 정본은 **PostgreSQL 의 append-only 표**다. `artifact/` 의 HTML·JSON 은
      지워도 되는 **화면 산출**이지 결정이 아니다
    · 결정은 **입력 신원**에 결속된다 — 사진 SHA · acquisition identity ·
      요구 좌표(era/region). 하나라도 바뀌면 옛 결정은 **안 따라간다**
    · 정정은 `UPDATE` 가 아니라 `supersedes_id` 를 든 **새 행**
    · 서로 모순되는 미정리 최신 행이 둘이면 **미확인 + 감사 결함**으로
      fail-safe 한다 — HITL 때문에 파이프라인을 막지는 않는다
    · CP 는 **투영만** 한다. 사람이 CP 를 직접 고치는 길은 만들지 않는다
    )annotationsN)AnyDictListOptionalSequencez1.202609020900verifiedrejected
project_id
episode_idresearch_subject_idfinal_idpurposeacquisition_identityimage_sha256eraregion
source_urlquery_provenance_digestc                      e Zd ZdZy)ReviewInputBrokenuK   판정 입력이 모자라다. ★모르는 채로 결정을 안 받는다.N__name__
__module____qualname____doc__     d/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/grounding_fidelity_review.pyr   r   &   s    Ur   r   c                      e Zd ZdZy)ReviewConflictuQ   서로 모순되는 최신 결정이 둘이다. ★사람이 정리해야 한다.Nr   r   r   r    r"   r"   *   s    [r   r"   c           	         t         D ci c]!  }|t        | j                  |      xs d      # }}dD ]  }||   r	t        | d       t	        j
                  |ddd      S c c}w )u  판정 입력의 **정본 bytes**. ★hash 는 이 문자열에서만 나온다.

    ★없는 좌표는 **빈 값**이다 — 만들지 않는다. 그래서 「시대 선언이 없는
    현대 원고」와 「시대가 있는데 안 실은 판」이 **다른 신원**이 된다.

    Raises:
        ReviewInputBroken: 신원 칸이 비었다 — 무엇을 본 결정인지 모른다.
     )r   r   r   r   r   uL    가 비었다 — 무엇을 본 결정인지 모르는 채로 안 적는다FT,:)ensure_ascii	sort_keys
separators)INPUT_FIELDSstrgetr   jsondumps)payloadkgotmusts       r    canonical_inputr4   .   s}     2>
>A1c'++a.&B''C
>94y#&deg g9
 ::c!+- - ?s   &A$c                ~    t        j                  t        |       j                  d            j	                         dd S )u[   그 입력의 신원. ★사진·구매판·좌표 중 하나만 바뀌어도 달라진다.utf-8N    )hashlibsha256r4   encode	hexdigest)r0   s    r    
input_hashr<   A   s8    >> ''022;)+crC Cr   c           
        g }| xs dD ]  }|xs i j                  d      xs i }|j                  t        |xs i j                  d      xs d      t        |j                  d      xs d      t        |j                  d      xs d      d        t        j                  t        j                  |dd	
      j                  d            j                         dd S )u  사람이 본 **질의 출처 표시**의 지문. ★화면이 보여 준 것과 결속한다.

    우리가 준 질의와 provider 가 더한 질의를 **갈라 센 수**를 접는다 —
    화면이 다른 것을 보여 주게 바뀌면 옛 결정이 안 따라간다.
    r   query_provenanceround_nor   providedexpanded)roundr@   rA   Tr%   r)   r*   r6   N   )	r-   appendintr8   r9   r.   r/   r:   r;   )roundsrowsrdprovs       r    provenance_digestrK   G   s     Dllb~~017Rc28.."<"AB!$TXXj%9%>Q!?!$TXXj%9%>Q!?A 	B 
 >>

44(	**0&/;;D9;sL Lr   c                  | xs dD cg c].  }t        |j                  d      xs d      t        |      k(  r|0 }}|sddddS |D ch c].  }|j                  d      st        |j                  d            0 }}|D cg c]!  }t        |j                  d            |vs |# }}|sddd	dS |D ch c]   }t        |j                  d
      xs d      " }}t        |      dkD  r-t        |      dkD  rdddt        |       dt        |       ddS t        |d       d   }t        |j                  d
      xs d      }|t        vr
d|d|ddS ||ddS c c}w c c}w c c}w c c}w )u  이 입력의 **유효 결정 하나**. 없거나 어긋나면 미확인이다.

    ★`supersedes_id` 를 **실제로 해석한다** — 대신된 행은 죽는다.
    ★살아 있는 최신 행이 **둘 이상 서로 다르면** 미확인 + 감사 결함이다.
      「둘 중 아무거나」로 고르면 사람이 안 정한 것을 정한 것으로 만든다.

    Returns:
        `{"verdict", "row", "fault"}` — `verdict` 는 `verified`/`rejected`/
        `None`(미확인). `fault` 는 왜 못 정했는지.
    r   review_input_hashr$   Nu%   이 입력에 대한 결정이 없다)verdictrowfaultsupersedes_ididu9   살아 있는 결정이 없다 — 전부 대신되었다rN      u%   서로 모순되는 최신 결정이 u   개다 (u#   ) — 사람이 정리해야 한다c                >    t        | j                  d      xs d      S )Nreviewed_atr$   r,   r-   )rs    r    <lambda>z#effective_verdict.<locals>.<lambda>s   s    S})=)C%Dr   keyu   모르는 판정 u    — 미확인으로 접는다)r,   r-   lensortedKNOWN_VERDICTS)	rH   	want_hashrW   minedeadlivekindsr2   kinds	            r    effective_verdictre   X   s    

 H
!155,-34IF 
D H@B 	B15PA9OCo&'DP<t!s155;/t;AtD<TV 	V267$QSy!'R($E7
4y1}UaA#d) M$Um_,OQS 	S D
Eb
ICswwy!'R(D>!,TH4RSU 	UC"55)H
 Q< 8s#   3E 
E%!E%!E*%E*7%E/c                  ddl m} t        | |      }|d   }|t        k(  r|j                  n |t
        k(  r|j                  n|j                  }||j                  t        t        |      d}|d   lt        |d   j                  d      xs d      |d	<   t        |d   j                  d
      xs d      |d
<   t        |d   j                  d      xs d      |d<   |d   r|d   |d<   |S )uP   DB 결정 → CP 에 적을 **고증 축**. ★투영이지 판단이 아니다.r   reference_acquisitionr_   rN   )statecontractreview_contractrM   rO   rR   r$   	review_idrU   reviewer_actorrP   )app.modules.pipelinerh   re   VERDICT_VERIFIEDFIDELITY_VERIFIEDVERDICT_REJECTEDFIDELITY_REJECTEDFIDELITY_UNVERIFIEDFIDELITY_CONTRACT_VERSIONREVIEW_CONTRACT_VERSIONr,   r-   )rH   r_   rar2   rd   rj   outs          r    fidelity_statery   {   s     A
DI
6Cy>D%)-=%=R!!*.2B*B"&&(( 
 002 ^	C 5zs5z~~d39r:K U!>!D"EM #CJNN3C$D$J K
7|7|GJr   c           
         t        |xs i       }dD ]X  }t        | j                  |      xs d      }t        |j                  |      xs d      }||k7  sEt        d| d|d|d       y)u=  사람이 **실제로 본 것**이 이 입력과 같나. ★다르면 안 받는다.

    화면이 옛 사진을 캐시해 보여 주고 서버가 새 사진에 결정을 붙이면,
    사람이 안 본 것을 승인한 것이 된다.

    Raises:
        ReviewInputBroken: 본 것과 붙일 것이 다르다.
    )r   r   r   r$   u   사람이 본 u    가 u    인데 붙일 것은 u,    다 — 안 본 것을 승인할 수 없다N)dictr,   r-   r   )r0   observedseenaxiswantr2   s         r    assert_observed_matchesr      s~     BD17;;t$*+$((4.&B'$;# eC72H Q6 78 8	 2r   T)strictc               t   g }| xs i j                  d      xs i j                  d      xs dD ]  }|j                  d      xs i }t        |      }	|	s'|j                  d      xs i }
t        |j                  d      xs d      }|xs i j                         D ci c]#  \  }}|t	        |xs d      j                         % }}}	 t        ||       |	D ]b  }i |di |||j                  d
      di}t	        |      t	        |      t	        |j                  d      xs d      t	        |
j                  d      xs d      t	        |
j                  d      xs d      t	        |j                  d      xs d      t	         ||      xs d      |j                  dd      |j                  dd      t	        |j                  d      xs d      t        |      d}	 t        |      }|j                  ||||t	        |j                  d
      xs d      t        |j                  d            |d       e  |S c c}}w # t        $ r+}|r |j                  |t	        |      d       Y d	}~Kd	}~ww xY w# t        $ r,}|r |j                  |t	        |      |d       Y d	}~d	}~ww xY w)ur  이 CP 에서 **사람이 볼 것들**과 그 입력 신원. ★한 곳이 만든다.

    화면·서버 끝점·CP 투영이 **같은 함수**를 쓴다. 세 곳이 각각 조립하면
    지문이 갈리고, 그러면 사람이 본 것과 붙는 것이 달라진다.

    Args:
        coordinates: **런타임 구조화 칸**(`visual_world_rules.era`/`.region`)
            에서 온 선언 좌표. ★여기서 짐작하지 않는다 — 저작기가 쓴 것과
            **같은 SOT** 여야 사람이 본 것과 붙는 것이 같아진다.
            없으면 빈 값이고 **만들지 않는다**.
        sha_of: `(row) -> str` — 그 줄의 **사진 내용 지문**. 파일을 여는 일은
            부르는 쪽이 한다(이 모듈은 디스크를 안 본다).

    Raises:
        ReviewInputBroken: 기록에 적힌 요구 축과 준 좌표가 어긋난다.

    Args (이어서):
        strict: 참이면 신원을 못 만드는 줄에서 **선다**. 거짓이면 그 줄을
            `fault` 와 함께 돌려준다.
            ★★★왜 가르나 (2026-09-02): 사진 파일 하나가 없다고 **파이프라인
            전체**를 죽이면, 멀쩡한 다른 컷까지 못 그린다. 그 줄은 어차피
            **안 붙는다**(미확인) — 위험한 것은 「확인된 것이 조용히 안
            붙는 것」과 「안 본 것이 붙는 것」이고 둘 다 안 일어난다.
            반대로 **DB·CP 를 통째로 못 읽는 것**은 확인된 것이 전부 사라지는
            일이라 거기서는 선다.

    Returns:
        `[{row, candidate, payload, hash, chosen_path, model_pick, rounds}]` — **행 × 후보**.
        받아 둔 사진이 있는 줄은 모델이 골랐든 아니든 후보마다 한 줄씩이다(Codex BLOCK 3).
        `strict=False` 에서 신원을 못 만든 줄은 `{row, fault}` 만 갖는다.
    datarH   r   acquisition
ledger_rowrG   r$   )rO   rP   Npath)chosenchosen_pathr   r   r   identityr   r   urlr   )rO   rP   	candidate
model_pick)rO   r   r0   hashr   r   rG   )r-   candidates_oflistitemsr,   strip_assert_axes_agreer   rE   rK   r<   bool)cpr   r   coordinatessha_ofr   rx   rW   acqcandsledrG   r1   vcoordsexccandviewr0   r2   s                       r    review_inputs_forr      s   D !#CxRnnV$*//7=2=eeM"(ba eeL!'Rcggh'-2.*0b779;9da Sb\''))9 	 ;	vv. DKa K )J3 )J$8<8H)J KD "*o!*o'*1551F+G+M2'N
 3 9r:swwy17R8(+AEE*,=,C(D #F4L$6B 7zz%, **Xr2!$((5/"7R8+<V+DG ) JJqt #C8H8NB4O&*488L+A&B"(* +3 ! >Z JM; ! 	JJq3s845		2 % 

1s3xdKL	s6    (II%J	I? I::I?	J7!J22J7c                @   | j                  d      xs i }|j                  d      xs i }t        |j                  d      xs |j                  d      xs d      }g }t               }|j                  d      xs dD ]  }|j                  d      xs dD ch c]'  }t        |      j                         st	        |      ) }}|j                  d	      xs dD ]  }	t        |	xs i j                  d      xs d      }
|
r|
|v r,|j                  |
       |	xs i j                  d
      }|j                  i |	|
|j                  d      |
|k(  t        |t              rt	        |      |v ndd         |r||vr|j                  i ||dddd       |S c c}w )u  한 줄에서 **사람이 볼 사진 전부** — 받아 둔 후보(라운드 순 · 경로로 중복 제거) 에
    모델이 고른 것을 더한다. ★★Codex BLOCK 3 (2026-09-02 밤): 앞 판은 모델(VLM)이 고른
    한 장만 사람 앞에 갔다 — `unsure` 하나면 후보 전부가 사람 눈에 안 닿았다(O01 넉 장이
    그렇게 사라졌다). 모델 선택은 **표시**(`model_pick` · `model_eligible`)일 뿐 문이 아니다.
    r   r   r   r   r$   rG   r   eligibledownloaded_candidatesindexr?   N)r   r?   r   model_eligibleT)r-   r,   setisdigitrF   addrE   
isinstance)rO   r   r   r   rx   r}   rI   xr   cr   idxs               r    r   r      s    ''-
 
&BCWWX$"Ffjj(HCGGM,BHbIK "CDwwx &B&%'VVJ%7%=2%=S%=q#a&..BRCF%=S&&017R7AR}}V,23D44<HHTN7--(CJJ d! dTrvvj7I&*k&9DNsTWDX3s8x+?^bd e 8 ' {$.

 @f @kt"&$@ 	AJ Ts   F6Fc           	        t        t        | xs d            D ]  }|xs i j                  d      xs i }|j                  d      }t        |t        t        f      sD|D ch c]  }t        |       }}|xs i j                         D ch c]%  \  }}t        |xs d      j                         s$|' }	}}||	k7  r$t        dt        |       dt        |	       d       y yc c}w c c}}w )	uw  그때 **요구됐던 축**과 지금 준 좌표가 같나. ★다르면 안 적는다.

    나간 기록은 어느 축이 의무였는지를 안다(`coordinates_carried.declared`).
    지금 세계 규칙이 바뀌었다면 그때 본 것과 다른 것을 승인하게 된다.
    ★옛 CP 는 그 칸이 없다 — 그때는 검사할 것이 없으니 지나간다.
    r   coordinates_carrieddeclaredr$   u   그때 요구된 축은 u    인데 지금 준 좌표는 u=    다 — 그때 본 것과 다른 것을 승인할 수 없다N)
reversedr   r-   r   tupler,   r   r   r   r]   )
rG   r   rI   r2   declr   r   r1   r   nows
             r    r   r     s     tFLb)*xRnn239rwwz"$u. $%1A%$l113L3TQs17|7I7I7Kq3L3;#*6$<.8U#;-\^_ _ 	 +
 &Ls   !C&%C+2C+r   )
selectionsc          
     R	   ddl }ddlm} |j                  | xs i       }	i }
t	        |	||||d      D ]/  }|
j                  t        |d         g       j                  |       1 |	j                  d      xs i j                  d      xs d	D ]  }|
j                  t        |            }|s!|D cg c]  }|j                  d
      s| }}|r@t        |      t        |      k(  r)|j                  |j                  t        |d   d
   d|d<   |D cg c]  }|j                  d
      r| }}|D ci c]  }|d   t        ||d          }}|D cg c]  }||d      d   t        k(  s| }}t        d |D        d      }t!        |j                  d      xs d      }t#        ||      d}d}t        t%        ||            dkD  rd}9t        fd|D        d      }|d}nN||vrJ||d      d   }|t&        k(  rdnd}d}n/t        |      dk(  r|d   }nt        |      dkD  r|||v r|n|d   }||}t)        |j                  d      xs i       }t)        |d         }|j                  d      }|j                  d       }|j+                         D ci c]  \  }}|dvr|| c}}|d<   |j                  d      |d <   |j,                  |d!<   |j,                  |d"<   |rd#nd$|d%<   |r||d<   ||d<   |j,                  |d"<   t/        ||d         |d<   $t!        j                  d&      xs d      |d   d'<   |r||d   d
<   |rE|j                  |j                  t        t!        xs i j                  d&      xs d      |d(|d<   |t/        ||d         |d<   |r||d   d
<   |j                  |d   i       j                  d      t&        k(  rot)        |j                  d      xs i       }|j1                  |j2                  |j4                  d)d*       ||d<   |j2                  |d!<   |j4                  |d"<   d+|d,<   |j                  |j                  t        d-t        |       d.d|d<    |	S c c}w c c}w c c}w c c}w c c}}w )/ux  DB 의 유효 결정을 CP 에 **투영한다**. ★판단이 아니라 옮기기다.

    결정 없음·불일치·깨짐은 전부 `unverified` 다. `rejected` 도 안 붙는다.

    ★★후보별 판정 + 대상당 선택 하나 (Codex BLOCK 3 · 재리뷰 2026-09-02 밤):
      - 판정은 후보 한 장마다 「그 시대·그 곳 것인가」다 — **여러 장이 verified 여도 옳다**.
      - 어느 장을 쓸지는 `selections`(대상당 살아 있는 선택 하나)가 정한다. 선택된 후보가
        verified 면 그것이 `acquisition.chosen`(`chosen_by: human` · 모델 선택은 `model_pick`
        보존) · `outcome/status = selected` — 모델이 `no_match` 라 했어도 참조가 된다.
      - 선택이 없고 verified 가 **딱 하나**면 그것을 쓴다(고를 것이 하나뿐이다).
      - 선택이 없고 verified 가 여럿이면 미확인 + 「한 장 고르세요」— 기록은 다 유효하다.
      - 선택된 후보가 verified 가 아니면(거절·미판정) 미확인 + 사유. 확인이 없고 모델
        선택이 `rejected` 면 `rejected`. 나머지는 미확인.
    r   Nrg   F)r   r   r   r   r   rO   r   rH   r   rP   )rj   rk   rl   rP   grounding_fidelityr   ri   rN   c              3  D   K   | ]  }|j                  d       s|  yw)r   N)r-   ).0r   s     r    	<genexpr>z apply_reviews.<locals>.<genexpr>Y  s     <1l(;Qs     r   r$   
subject_idrS   u\   이 대상에 살아 있는 선택이 둘 이상이다 — 다시 고르면 하나가 된다c              3  l   K   | ]+  }|d    t        j                  d      xs d      k(  s(| - yw)r   rM   r$   NrV   )r   r   sels     r    r   z apply_reviews.<locals>.<genexpr>a  s2      iDqAfISWWM`EaEgegAh4hDs   )44uP   고른 사진이 지금 후보에 없다 — 사진이나 좌표가 달라졌다uW   고른 사진이 rejected 다 — 다른 장을 고르거나 판정을 바꿔야 한다u.   고른 사진이 아직 verified 가 아니다r   r   r   r   )r   r   r?   r   r   statusoutcomehumanzmodel+human	chosen_byrR   selection_id)rj   rk   rl   r   rP   T)r   r   rejected_by_humanu[   사람이 모델 선택을 거절했고 다른 확인된 후보가 없다 (도구 override)whyu   받아 둔 후보 u%   장을 사람이 아직 안 골랐다)copyro   rh   deepcopyr   
setdefaultrR   rE   r-   r\   rt   ru   rv   re   rp   nextr,   effective_selectionlive_selectionsrr   r{   r   STATUS_SELECTEDry   updateSTATUS_NO_MATCHSTATUS_UNAVAILABLE)r   reviewsr   r   r   r   r   r   rw   r2   by_rowr   rW   inputsfaultsrb   verdictsr	   picksidchosen_input	sel_faultrd   oner   r   washuman_changedr1   r   r   s                                 @r    apply_reviewsr   -  s   $ @
--b
!C /1Fszj+6veU"QuX,+2215U wwv$"))&17R7BqE"#6VquuW~!V6c&kS[0//88#:7+	'-A"#
 !86aw68X\]X\STAfI0AfINNX\]#Zt!x&	':9'EIY'YAtZ<<dC!%%-.4"5!*=	;3zcJKaOvI? iD ikopL#n	X-V 45i@ $(8 8 w>n #]a#A;L]Q %)$49I4PXYZP[L#Cquu]+1r2CK()D''(#C $ 66M.2jjl Wlda !)U U Tl WCM!%&!1C..CM//C	N*7w]C$'L!"Am--AiL&4WF&TA"#:=cggdm>Qr:R&'73<&'0//88#: #SYBOOD$9$?R @9	'VA"#
 &4WV&UA"#3<&'0||DL"-11)<@PP 155/526

b&8&8REZEZ157 8#&-  00(!44)x%++446)#d)4YZ	#\
i 8r Jk 7 9]Z<Ws0   3R
RR-R7RR0R&R#c                      e Zd ZdZy)ReviewsUnreadableuY   판정 기록을 못 읽었다. ★「없다」가 **아니다** — 모르는 것이다.Nr   r   r   r    r   r     s    cr   r   c          
        ddl m} 	 | j                  |      j                  t	        |      t	        |            j                         }|D cg c]G  }|j                  |j                  |j                  |j                  |j                  |j                  dI c}S # t        $ r}t        d| d      |d}~ww xY wc c}w )ur   이 에피소드의 **쓸 사진 선택** 전부. ★못 읽으면 선다 — 「선택이 없다」가 아니다.r   )GroundingReferenceSelectionr   r   u'   사진 선택 기록을 못 읽었다 (un   ) — 「선택이 없다」가 아니라 **모른다**는 뜻이다. `alembic upgrade head` 를 먼저 보라N)rR   r   rM   selected_atselected_byrQ   )app.models.projectr   query	filter_byr,   all	Exceptionr   rR   r   rM   r   r   rQ   )dbr   r   SrH   r   rW   s          r    read_selectionsr     s     DMc*o&)*o  77:su 	 W[\VZQR 440E0E"#"5"5ammMMAOOMVZ\ \	  M5cU ;B CDIL	MM\   AB AB>	B;&B66B;c               D    t        | |      }t        |      dk7  ry|d   S )u   그 대상의 **살아 있는 선택 하나**. `supersedes_id`(`;` 로 여럿)를 해석한 뒤 살아 있는 것이
    **정확히 하나**일 때만 낸다 — 둘 이상이면 `None`(고르지 않는다 · 다음 선택이 둘 다 대신한다).r   rS   Nr   )r   r\   )r   r   rb   s      r    r   r     s)     :*=D
4yA~ 7Nr   c          
        | xs dD cg c]/  }t        |j                  d      xs d      t        |      k(  s.|1 }}t               }|D ]d  }t        |j                  d      xs d      j                  d      D ]2  }|j	                         s|j                  |j	                                4 f t        |D cg c]!  }t        |j                  d            |vs |# c}d       S c c}w c c}w )	uo   그 대상의 살아 있는 선택들 — `supersedes_id`(여러 개면 `;` 로 이어진다)를 해석한 뒤.r   r   r$   rQ   ;rR   c                >    t        | j                  d      xs d      S )Nr   r$   rV   )ss    r    rX   z!live_selections.<locals>.<lambda>  s    AEE-$8$>B ?r   rY   )r,   r-   r   splitr   r   r]   )r   r   r   r`   ra   r   s         r    r   r     s    "(b(h(!S7L1M1SQS-TX[\fXg-gA(DhDQUU?+1r288=Awwy# >  dCdc!%%+&6d&B1dC?A A i Ds   /C+C+;!C0C0c          
        ddl m} 	 | j                  |      j                  t	        |      t	        |            j                         }|D cg c]G  }|j                  |j                  |j                  |j                  |j                  |j                  dI c}S # t        $ r}t        d| d      |d}~ww xY wc c}w )u  이 에피소드의 판정 전부. ★못 읽으면 **선다**.

    ★★「못 읽었다」를 「판정이 없다」로 접으면, 사람이 맞다고 한 사진이
    조용히 안 붙는다 — 그건 기록 손실이지 판정 결과가 아니다.
    r   ) GroundingReferenceFidelityReviewr   u'   고증 판정 기록을 못 읽었다 (un   ) — 「판정이 없다」가 아니라 **모른다**는 뜻이다. `alembic upgrade head` 를 먼저 보라N)rR   rM   rN   rU   rn   rQ   )r   r   r   r   r,   r   r   r   rR   rM   rN   rU   rn   rQ   )r   r   r   MrH   r   rW   s          r    read_reviewsr     s     I^c*o&)*o  77:su 	 9=> 9=1 44a.A.A		!-- //oo/ 9=> >	  ^5cU ;S TUZ]	^^>r   )requiredc          	        ddl m} ddlm} | j	                  d      }|y| j	                  d      xs i }|j                  d      xs i }t        |j                  d      xs d	      j                         t        |j                  d
      xs d	      j                         d} ||j                        j                  i fd}t        | j                  | j                  | j                        }	t        | j                  | j                  | j                        }
t        ||	|
t        | j                        t        | j                        ||      S )u  **소비자가 읽는** 중앙 체크포인트. ★DB 의 사람 판정을 얹어서 준다.

    ★★★왜 여기 (Codex 재리뷰 2026-09-02): `apply_reviews` 를 만들어 놓고
    **production 호출자가 0곳**이었다. 그러면 사람이 「맞다」로 정해도
    scene_detail·sidecar·정책은 여전히 `unverified` 를 보고 아무것도 안
    붙인다 — 「판정 후 3장 부착」은 내 도구 안에서만 참이었다.

    소비자가 **하나같이 이 함수로** CP 를 읽는다. 두 곳이 각각 읽으면
    한쪽만 판정을 반영한다.

    Raises:
        ReviewsUnreadable · ReviewInputBroken: 기록을 모르는 채로 안 붙인다.
    r   )Path)settingsrh   Nvisual_world_rulesr   r   r$   r   )r   r   c                P    ddl m}m}  ||       }|vr ||       |<   |   S )Nr   )_chosen_rel_pathrow_content_sha256)root)-app.modules.pipeline.grounding_sidecar_writerr   r   )rO   r   r   relr   r}   s       r    _shaz%central_cp_with_reviews.<locals>._sha  s3    	2s#d?*3T:DICyr   r   )r   r   r   r   r   )pathlibr   app.core.configr   _load_prev_checkpointr-   r,   r   projects_dirparentr   r   r   r   r   r   )runnerr   r   r   r   worldr   r   r   r   r   r   r}   s              @@r    central_cp_with_reviewsr    s*    (
 
	%	%&=	>B	z(()=>D"EIIf#D%.B/557DHHX.4"5;;=?F%%&--DD 6991B1B&,&7&79G v7H7H,2,=,=?JWFL]L]H^$'(9(9$:%+D: :r   c                   | xs dD ch c]-  }|j                  d      rt        |j                  d            / c}t        fd| xs dD              }xs dD ch c]   }t        |j                  d      xs d      " }}t        fd|D              }t        j                  t        j                  ||gdd	      j                  d
            j                         dd S c c}w c c}w )u  **유효 결정 묶음**의 지문. ★사람이 마음을 바꾸면 이 값이 달라진다.

    ★★★왜 (Codex 조건 4 · 2026-09-02): 이미 끝난 정책·`scene_detail` 이
    **옛 결과를 되쓴다**. 사람이 「맞다」를 「아니다」로 바꿨는데 체크포인트가
    그대로면, 안 붙어야 할 사진이 계속 붙는다.

    ★**구매 신원에는 안 넣는다** — 판정이 바뀌었다고 사진을 다시 사지
    않는다. 소비자의 **처리 지문**에만 접는다.

    ★대신된 줄(`supersedes_id`)은 빼고, 남은 것을 `(입력신원, 판정)` 으로
    정렬해 접는다 — 적힌 순서·시각이 달라도 같은 결론이면 같은 지문이다.
    r   rQ   c              3     K   | ]\  }t        |j                  d             vr>t        |j                  d      xs d      t        |j                  d      xs d      f ^ yw)rR   rM   r$   rN   NrV   )r   rW   ra   s     r    r   z!reviews_digest.<locals>.<genexpr>/  sZ      BACd$4D$@ 
QUU&'-2	.AEE)4D4J0KLs   A"A%r   r$   c              3  |   K   | ]3  }|t        t        |       xs i j                  d      xs d      f 5 yw)r   rM   r$   N)r,   r   r-   )r   r   r   s     r    r   z!reviews_digest.<locals>.<genexpr>4  sC      (&s 1*MSQSXXYlmsqstu&s   9<Tr%   rC   r6   NrD   )	r-   r,   r]   r8   r9   r.   r/   r:   r;   )r   r   rW   rb   r   subjectspicksra   s    `     @r    reviews_digestr    s     3:-R- '-Auu_% o&'- 'D B-R-B BD CMBRPRBRTBRAAEE/06B7BRHT (&( (E>>

D%=D(	**0&/;;D9;sL L' Us   2C( %C-c                    t        t        | j                  | j                  | j                        t        | j                  | j                  | j                              S )uO   그 주행이 보는 판정 묶음의 지문. ★소비자 지문에 접는다.r   )r  r   r   r   r   r   )r  s    r    reviews_digest_forr  ;  sP    ,vyyV=N=N282C2CE)&))@Q@Q5;5F5FHI Ir   )r0   Dict[str, Any]returnr,   )rG   Sequence[Dict[str, Any]]r  r,   )rH   r  r_   r,   r  r  )r0   r  r|   Optional[Dict[str, Any]]r  None)r   r  r   r,   r   r,   r   Dict[str, str]r   r   r  List[Dict[str, Any]])rO   r  r  r  )rG   r  r   r  r  r  )r   r  r   r  r   r,   r   r,   r   r  r   r  r  r  )r   r   r   r,   r   r,   r  r  )r   r  r   r,   r  r  )r   r  r   r,   r  r  )r  r   r   r   r  r  )r   )r   r  r   r  r  r,   )r  r   r  r,   )&r   
__future__r   r8   r.   typingr   r   r   r   r   rv   rp   rr   r^   r+   
ValueErrorr   RuntimeErrorr"   r4   r<   rK   re   ry   r   r   r   r   r   r   r   r   r   r   r  r  r  r   r   r    <module>r     s  " #   6 6 +   "$45+V
 V\\ \-&CL" 6) 6F&28&>8CG8, .2P"%P4BP&*P6JPf8-262 :<v!v/2v-v 7v AOvrd d\-\$
5
	A>*>. >B 0:!90:h ;=L7LADL8Ir   