
    aCjL                       d Z ddlmZ ddlZddlmZmZmZmZ ddl	m
Z
mZ  ej                  e      ZdZddd	d
ZdZdZdZdZ ed      ZdZ ed      Z ed      ZdZef	 	 	 d%dZef	 	 	 d%dZdZd&dZd'dZd(dZ dZ!dZ"dZ#dZ$efd)dZ%ef	 	 	 d)dZ&ef	 	 	 d*d Z'dded!	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d+d"Z(	 	 	 	 	 	 	 	 	 	 d,d#Z)	 	 	 	 	 	 	 	 	 	 	 	 d-d$Z*y).u  signage_author — 장면이 부르는 실물 표기의 원어 문안 저작 (2026-08-14 #119②).

카나리아 S3 실측: 표기 정책 v20 은 "장면이 부르는 원어 표기 허용 + 발명
금지"를 실었는데 팻말이 **뭐라고 쓰여 있는지**는 아무도 공급하지 않아
모델이 규정 준수로 백지 팻말을 그렸다. 허용은 값이 아니다 — 값(문안)을
저작해 공급해야 그릴 재료가 생긴다.

계약 요점:
- 판별+저작 = 1콜(gemini-flash). "대부분의 샷은 빈 목록"이 팩 본문에
  명문 — 당연한 것은 저작하지 않는다.
- 문안은 무조건 원어(세계관 사실이 주는 장소·시대의 언어) — 팩이 소유.
  코드에는 표기 대상 어휘가 없다(통칭 하드코딩 금지).
- 산출은 호출측이 records 사이드카에 캐시·영속한다(지문 = 샷 텍스트+
  장소+세계관+정책+팩) — 기록 없는 유료 저작은 없다.
- 플래그 `signage_author_enabled` 기본 OFF = byte-identical.
    )annotationsN)AnyDictListOptional)load_promptpack_dir_content_hashsignage_authorz1.202608141700z2.202608251730z3.202608270320)123r   signage_author_v3_flash   )
scene_textworld_factsinferred)r   r   )scene_text_quotedscene_text_impliedr   r   )r   r   )r   zgemini-flashc                    | dk7  S )u   이 팩이 출처 칸을 내는가 — schema 와 거르기가 같이 갈린다.

    v1 에는 출처 칸이 아예 없다. 거기서 거르려 들면 전량이 사라진다
    (없는 칸은 언제나 「근거 없음」이므로).
    r    selectors    Y/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/signage_author.pypack_has_source_contractr   X   s     s?    c                
    | dvS )uX  이 팩이 **인용 칸**을 내는가 (v3~).

    v2 까지는 출처를 열거값으로만 받는다 — 자가신고다. v3 는 그 신고를
    뒷받침하는 원문 조각을 함께 받고, 코드가 **주어진 글에 실제로 있는지**
    본다. 옛 팩에서 이 검사를 걸면 인용 칸이 없어 전량이 사라진다.
    )r   r   r   r   s    r   pack_has_quote_contractr   b   s     :%%r   u   "'“”‘’「」『』c                p    dj                  d | D              }dj                  |j                               S )uU   앞뒤 공백·연속 공백·인용부호만 고른다 — 그 이상은 안 한다. c              3  T   K   | ]   }|t         vr|j                         rd n| " yw) N)_QUOTE_MARKSisspace).0chs     r   	<genexpr>z-_normalize_for_quote_match.<locals>.<genexpr>y   s,      2B"L0 zz|#s   &(r!   )joinsplit)textstrippeds     r   _normalize_for_quote_matchr+   w   s5    ww 22 2H 88HNN$%%r   c               H    t        | xs d      }|sy|t        |xs d      v S )u   인용이 주어진 글에 실제로 있는가.

    빈 인용은 근거가 아니다 — `inferred` 가 아닌 항목이 빈 인용을 내면
    그것은 신고만 하고 근거를 못 댄 것이다.
    r   Fr+   )quotehaystackneedles      r   quote_is_groundedr1      s-     (4F/B???r   c               H    t        | xs d      }|sy|t        |xs d      v S )uB  저작 문안이 **인용 안에** 있는가 — 읽을 글자의 마지막 관문.

    ★인용이 입력에 있다는 것만으로는 모자란다. "notice" 를 인용하고
     "안내" 를 저작하면 인용은 맞지만 **글자는 발명**이다 — v2 실측이
     확정한 실패이고, 이 검사 없이 낸 v3 첫 판이 그것을 그대로 재현했다
     (2026-08-27 Codex 판정).

    옮기거나 풀어 쓴 것은 여기서 걸린다. 그런 항목은 「읽을 것이 있다」는
    신호(`scene_text_implied`)이지 읽을 글자가 아니다.
    r   Fr-   )text_nativer.   r0   s      r   text_is_inside_quoter4      s.     ((9r:F/<<<r   not_grounded_sourceempty_quoteungrounded_quotetext_not_in_quotec           	     n    	 t         |    S # t        $ r  t        d| dt        t                d      w xY w)Nz%unknown signage_author pack selector z	 (known: ))PACK_VERSION_MAPKeyError
ValueErrorsortedr   s    r   resolve_signage_packr?      sN    4)) 43H< @./034 	44s    )4c                4    t        t        t        |             S )N)r	   _MODULEr?   r   s    r   signage_pack_content_hashrB      s     *>x*HIIr   c           	        t        |       rUddddt        t        |       rt        nt              ddddd}g d}t        |       r,ddi|d<   |j                  d       ndddddddddd	}g d	}d
dddd
||dddidgddS )ud  팩이 schema 를 정한다 — 옛 팩은 옛 계약 그대로 돈다.

    v2 는 `source` 를 필수로 받고 면(`surface_native`)은 **안 받는다**.
    면을 코드가 정하려니 통칭이 필요해졌고, 샷마다 면이 갈려 같은 장소가
    다른 곳처럼 보였다(완주 판: 같은 글자가 유리문과 벽면에 따로).
    string   )type	minLength)rF   enum   )r3   source	reason_korF   source_quote)surface_nativer3   rK   objectinscriptionsarray   F)rF   
propertiesrequiredadditionalProperties)rF   maxItemsitems)r   listr   SOURCE_VALUES_V3SOURCE_VALUESappend)r   
item_propsrS   s      r   build_author_schemar\      s      )$,1='$;H$E "2$ % #+;&

 :"8, +1();J~&OON+ (0a@$,1="*;


 BQ$", (,1	

 $$ % r   )project_configopik_metadatapack_selectorc           
        ddl m} t        t        dt	        |            j                         }dd|j                         z   dz   |xs dj                         z   d	z   |xs dj                         z   d
g}	i |xs i | dt        ii}
 || ||	t        |      |
| |      }|j                  d      xs g D cg c]  }t        |t              r| }}t        |||||      \  }}}|rDt        j                  d| t        |      t        |      dj                  d |D              dd        |r t        j                  d| t        |             |dt          ||dS c c}w )u  샷 하나의 표기 필요 판별 + 원어 문안 저작 — 1콜, 빈 목록이 기본.

    반환 `{"inscriptions": […], "cues": […], "dropped": […]}`.

    - `inscriptions` — **읽을 글자**. 인용 안에 그 글자가 있는 것만.
    - `cues` — 「읽을 것이 있다」는 신호. 물체는 있으나 문안은 아무도
      안 줬다. 읽을 글자로 안 나간다.
    - `dropped` — 버린 것과 그 이유.

    ★버린 것을 **값으로** 돌려준다(2026-08-27). 종전에는 리스트만 돌려주고
     버린 것은 로그로 흘러가, 나중에 「몇 건이 왜 버려졌나」를 셀 수 없었다.
     호출부가 records 에 함께 적는다.
    r   )call_structured
author_sys)versionr)   zSHOT (authoritative):
z

LOCATION:
r   u;   

WORLD FACTS (place, era and language — authoritative):
)rF   r)   model)r]   schema_namer^   rO   )	shot_text
place_textworld_facts_blockr_   uF   signage_author[%s]: 근거 없는 저작 %d/%d 건을 버린다 — %sz, c              3  >   K   | ]  }|d    dd  d|d    d  yw)r3   N   (whyr:   r   )r$   ds     r   r&   z&author_inscriptions.<locals>.<genexpr>  s6      (&! =)#2./q5
!<&s   Ni,  ut   signage_author[%s]: 읽을 것이 있다는 신호 %d건 — 문안은 안 나간다(물체는 두되 안 읽히게))rO   cuesdropped)app.modules.llm.llm_clientra   r   rA   r?   stripAUTHOR_MODELr\   get
isinstancedict_filter_groundedloggerinfolenr'   MAX_INSCRIPTIONS)step_tagrf   rg   rh   r]   r^   r_   ra   rb   partspcdataxrV   keptrn   ro   s                    r   author_inscriptionsr      s   . ;$]355:UW  *Y__->>$%(2(8b'?'?'AB&& *;)@b(G(G(IJ E 
G^!r	FHw.E	FB*e%8%Gx#D
 .17R7 %711d# 7E %*z+=JD$ Tc'lCJII (&( ((,.	/
 <=Es4y	R !!2"23W. .%s   8Ec               .    | dk(  r|xs dS | dv r|xs dS y)u  그 출처가 **가리키는 글**만 돌려준다.

    ★셋을 뭉쳐 찾으면 신고한 출처와 다른 데서 인용해도 통과한다 —
     `world_facts` 라 해 놓고 샷 텍스트를 인용하는 식이다. 그러면 출처
     칸이 다시 자가신고로 돌아간다(2026-08-27).

    ★**LOCATION 은 맥락이지 허용 출처가 아니다** (감사 보고서 v3 계약).
     장소 글은 「어떤 종류의 곳인가」를 말한다 — 거기서 인용해 저작하면
     「이런 곳이면 이런 글자가 있겠지」를 정당화하게 되고, 그것이 v2 가
     막으려던 바로 그 추론이다. 처음 이 함수를 쓸 때 팩 문안의 "the shot
     **or location** text" 를 따라 장소를 넣었는데, 그 문안 자체가 v2 에서
     물려받은 것이라 계약과 어긋났다(2026-08-27 Codex BLOCK). 팩과 코드를
     함께 고쳤다.
    r   r   )r   r   r   r   )rJ   rf   rg   rh   s       r   haystack_for_sourcer     s-    "  &B&JJBr   c          
     N   t        |      st        |       g g fS t        |      }|rt        nt        }g }g }g }	d	d
d}
| D ]  }t        |j                  d      xs d      }|r|t        v rt        |j                  d      xs d      }|j                         s|	j                   |
|t                     wt        |t        ||||            s|	j                   |
|t                      |
|      }d|d<   |j                  |       ||vr|	j                   |
|t                     |s|j                  |       t        |j                  d      xs d      }|j                         s|	j                   |
|t                     Ot        |t        ||||            s|	j                   |
|t                     t        t        |j                  d      xs d      |      s|	j                   |
|t                      |j                  |        |||	fS )u  세 갈래로 가른다 — **읽을 글자 / 신호 / 버림**.

    ★거르기가 **상한보다 먼저**다. 먼저 자르면 추론 항목이 앞자리를
     차지했을 때 근거 있는 항목이 밀려 사라진다 — 걸러야 할 것만 남기고
     살려야 할 것을 버리는 정확히 반대 결과가 난다.

    ★버린 것도 **값으로** 돌려준다. 종전에는 로그로만 흘러가 나중에
     「몇 건이 왜 버려졌나」를 셀 수 없었다.

    ★`scene_text_implied` 는 **버리지 않고 신호로 남긴다**(2026-08-27
     Codex 판정). 샷이 「읽을 것이 있다」고만 했으면 물체는 있는 것이고,
     뭐라고 쓰였는지만 아무도 모른다 — 읽을 글자로 내보내면 발명이고,
     통째로 버리면 백지 물체가 된다. 둘 다 피하려면 **물체는 두되 안
     읽히게** 찍어야 하고, 그 판단의 재료로 이 목록이 남는다.
    r   c                    t        | j                  d      xs d      t        | j                  d      xs d      t        | j                  d      xs d      d}|r||d<   |S )Nr3   r   rJ   rL   )r3   rJ   rL   rl   )strrs   )itemrl   rows      r   _rowz_filter_grounded.<locals>._rowR  s^    txx6<"=$((8,23 8 >B?

 CJ
r   rJ   rL   )rf   rg   rh   )r/   r3   )r.   )r   )r   Dict[str, Any]rl   r   returnr   )r   rW   r   READABLE_SOURCES_V3GROUNDED_SOURCESr   rs   CUE_SOURCES_V3rq   rZ   DROP_EMPTY_QUOTEr1   r   DROP_UNGROUNDED_QUOTEDROP_UNGROUNDED_SOURCEr4   DROP_TEXT_NOT_IN_QUOTE)rV   rf   rg   rh   r_   check_quotereadabler   rn   ro   r   itsrcr.   cues                  r   rv   rv   4  s   ( $M2E{B"")-8K&1"7GH!#D!#D$&G "&&"(b)3.0 ~.4"5E;;=tB(89:$U5H9&769 : tB(=>? r(C!#CKKhNN4$:;<KKOBFF>*0b1{{}NN4$456 1DyZ"325 6 NN4$9:; $BFF=)/R0?NN4$:;<Bk l wr   )r   r   r   bool)r)   r   r   r   )r.   r   r/   r   r   r   )r3   r   r.   r   r   r   )r   r   r   r   )r   r   r   r   )r{   r   rf   r   rg   r   rh   r   r]   Optional[Dict[str, Any]]r^   r   r_   r   r   r   )
rJ   r   rf   r   rg   r   rh   r   r   r   )rV   zList[Dict[str, Any]]rf   r   rg   r   rh   r   r_   r   r   zI'tuple[List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]]]')+__doc__
__future__r   loggingtypingr   r   r   r   app.modules.prompt_loaderr   r	   	getLogger__name__rw   rA   r;   SIGNAGE_PACK_VERSIONSIGNAGE_POLICY_VERSIONrz   rY   	frozensetr   rX   r   r   rr   r   r   r"   r+   r1   r4   r   r   r   r   r?   rB   r\   r   r   rv   r   r   r   <module>r      s    #  , , H			8	$
 
 
 
 .   3  
 ::;     DE 23 -15 -&&15&$ -&	@=$ /   * ,  *> 4 -JJ14J -,,1?,j 04.2-9.9. 9. 	9.
 9. -9. ,9. 9. 9.x"03HK0^^^ #^8;^ ^ O	^r   