
    OjV~                        U d Z ddlmZ ddlZddlmZmZ ddlmZm	Z	m
Z
mZ ddlmZ ddlmZ  ej                   e      ZdZd	Zd
ZddddZded<   dZddZdZdZdZ G d dee      Zy)u~  C(c) producer — 구간당 **단일 판독** + compact evidence merge. ★아직 안 켠다.

## 무엇을 대신하나

지금은 원문 전문이 **네 번**(`grounding_a0` + `entity_extract_*` 셋), 샷 전부가
**세 번**(`entity_all_*`) 나간다. 이 스텝은 그것을 **구간 수 + 1** 로 줄인다 —
구간마다 한 번 읽고, 마지막에 한 번 합친다.

## ★아직 아무 데도 안 붙어 있다

`step_manifest` 에도 `STEP_CLASSES` 에도 없다. `uses_chunk_producer(mode)` 가
True 일 때만 도는데, 그 모드(`v2_chunk`)를 **아무도 안 쓴다.** 사람이 v3 검토
화면에서 샷 결속 의미·두 축·엔티티 품질을 보고 GO 를 준 뒤에 켠다.

## 이 파일이 **안 하는** 것

- 승인 없이 provider 를 부르지 않는다 — `uses_chunk_producer` 가 False 면
  **한 푼도 안 쓰고** 돌아간다
- 구간 나누기·payload 조립·행 해석·합치기·adapter 를 **다시 적지 않는다**.
  전부 이미 있는 함수를 부른다. 여기서 다시 적으면 도구가 재는 것과 주행이
  굽는 것이 갈린다
- 샷 `description` 을 **자르지 않는다**
    )annotationsN)ThreadPoolExecutoras_completed)AnyDictListOptional)
StepRunner)_EntityStepMixin
   g      @g     r@Fg?)enable_fallbacknum_retriestemperatureDict[str, Any]REQUEST_CONTRACT   c                    | j                  d      rdnd}t        | j                  d      xs d      }t        d|      |z  d|z   z  S )u@  한 논리 호출이 여는 **물리 최대**. ★계약이 바뀌면 같이 바뀐다.

    ★글 호출 경로(`_completion`)에는 이미지 쪽 `reserve_current_call` 같은
    **세는 자리가 없다.** 그래서 물리 수를 「막을」 수는 없고, 대신
    **구조적으로 상한을 정한다** — 재시도와 fallback 을 끄면 남는 것은 키 슬롯
    loop 뿐이고 그것은 `slot_count()` 로 위가 정해진다.

    ★이것은 **상한이지 실측이 아니다.** 실제로 몇 번 나갔는지는 Opik+provider
    로그로 본다.
    r         r   r   )getintmax)request_contractslotstiersretriess       Y/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/steps/grounding_chunk_step.pyphysical_per_logicalr   J   sN     "%%&78AaE"&&}5:;Gq%=5 AK00    grounding_chunk_readzop:grounding-chunkcc_call_identityc                      e Zd ZdZddZdddZd Zd ZddZddZ	ddZ
dd	Zdd
ZddZddZddZd	 ddZdd	 	 	 ddZdd dZed        ZddZy)!GroundingChunkStepuU  구간당 단일 판독 producer. ★`v2_chunk` 모드에서만 산다.

    ★★★`_EntityStepMixin` 을 **왜 쓰나** — `_load_prev_checkpoint` 때문이다.
    이 스텝은 `scene_save`·`shot_validator`·`visual_world_rules` 체크포인트를
    읽는데, 그 함수는 `StepRunner` 에 없고 스텝마다 각자 갖고 있다. 이웃인
    `GroundingScreenStep` 도 같은 이유로 이 mixin 을 쓴다.

    ★실측 (2026-09-02 유료 canary ①): 이것이 없어서 첫 실주행이
    `AttributeError: 'GroundingChunkStep' object has no attribute
    '_load_prev_checkpoint'` 로 죽었다. **시험은 CP 를 손으로 넣어 줘서**
    이 자리를 한 번도 안 지났다 — producer 가 실제로 돈 적이 없었다.
    ★이 클래스가 제 `_config_hash` 를 갖고 있으므로 mixin 것에 안 가린다.
    c                   ddl }ddl}ddlm} ddlm} ddlm} ddlm} ddlm	}  || j                        i | j                         d |d	|j                        i| j                         |j                  |j                  |j                   |j"                  d
d}|j%                  |j'                  |dd      j)                  d            j+                         dd S )uf  ★팩 **바이트**와 계약을 지문에 접는다.

        안 접으면 팩·후처리 계약·adapter 를 고쳐도 resume 이 **옛 체크포인트를
        그대로 건너뛴다** — 고친 것이 아무 데도 안 닿는다. 버전 문자열만
        접으면 같은 버전 디렉토리의 **내용**을 고쳤을 때 못 잡는다.
        r   N)compute_config_hash)_pack_fingerprintgrounding_chunkgrounding_chunk_adapter)grounding_chunk_plan
pack_bytesr(   )contractadapter
chunk_planbundle_target)baseacquisition	admission
processingTF)	sort_keysensure_asciizutf-8   )hashlibjsonapp.core.step_runnerr%   app.core.steps.grounding_stepsr&   app.modules.pipeliner(   r*   r+   project_config_acquisitionCHUNK_PACK_VERSION
_admissionPROCESSING_CONTRACT_VERSIONADAPTER_CONTRACT_VERSIONCHUNK_PLAN_CONTRACT_VERSIONBUNDLE_TARGETsha256dumpsencode	hexdigest)	selfr8   _jsonr%   r&   gcadcppayloads	            r   _config_hashzGroundingChunkStep._config_hashn   s     	<D>FC ((;(;<##%/0A020E0EG *::66 <<!#!1!1	
& ~~KK4%*  ,,2F7O==FY["N 	Nr   c                   ddl m}m}  || j                        } ||      s!t        j                  d|       ddd|ddddS | j                         \  }}}}| j                  |      }	ddlm	}
 | j                  |      } |
| j                         t              5  | j                  |||||	      cd d d        S # 1 sw Y   y xY w)	Nr   )resolve_grounding_modeuses_chunk_produceru4   grounding_chunk: mode=%s — 아무것도 안 한다T)modeskippedprovider_calls)completed_countapplicable_countfailed_countdata)research_run_scope)capdeadline_seconds)app.core.grounding_moderQ   rR   r=   loggerinfo_plan_journalapp.core.research_call_budgetrZ   _assert_physical_bound_transmission_capCHUNK_DEADLINE_SECONDS_run)rI   rS   rQ   rR   g_modeplansegscatsworldjrrZ   bounds               r   _executezGroundingChunkStep._execute   s    	B ((;(;<"6* KKNPVW'(a$%%+/023 3
 #'**,dD%]]4  	E ++D1  D$:$:$<1GI99VT4r:I I Is   B<<Cc                    |j                          | j                  ||||      \  }}}| j                  |||      }	| j                  |||||	|      S N)assert_no_uncertain_read_chunks_merge_wrap)
rI   rg   rh   ri   rj   rl   rowsquarantined_reduceds
             r   rf   zGroundingChunkStep._run   sV    
 #00tT2Fk1++dD"-zz&$k7BGGr   c                   ddl m}m} ddlm} ddlm} ddlm}m}  |        | j                  d      xs i }|j                  d      xs i j                  d      xs g }|sdd	lm}	  |	d
dd      | j                  d      xs i }
|
j                  d      xs i j                  d      xs g }| j                  d      xs i } ||      sdd	lm}	  |	ddd       ||      }|D ci c]*  }d|j                  d       |j                  d      xs d, }} ||D cg c]?  }t        |j                  d      xs d      t        |j                  d      xs d      dA c}      }g }i }t        |      D ]\  \  }}|D cg c]  }d| 	 }}|j!                  ||      }||d| <   |j#                  d| ||j%                  ||||      d       ^ ||||fS c c}w c c}w c c}w )uR   구간·원문·샷 catalog·세계 규칙. ★전부 **이미 있는 함수**로.r   )build_grounding_world_factsgrounding_world_facts_readyr'   )grounding_shot_catalog)assert_bundle_target_agreesbundle_scenes
scene_saverY   segmentsAppErrorzgrounding_chunk.no_scenesu   scene_save 산출이 없다i  codemessagestatus_codeshot_validatorscenesvisual_world_ruleszgrounding_chunk.no_world_factsuf   세계 규칙이 아직 없다 — 빈 시대로 물으면 모델이 상상 묘사만 보고 답한다zscene-scene_indextext )idxlengthc)shot_catalog)chunk_idsegment_idsrN   )app.core.world_contextrz   r{   r<   r(   r|   )app.modules.pipeline.grounding_chunk_planr}   r~   _load_prev_checkpointr   app.core.errorsr   r   len	enumeratebuild_catalogappendbuild_chunk_payload)rI   rz   r{   rK   scr}   r~   scene_cpr   r   shot_cpr   rules_cprk   sri   bundlesrh   rj   nbiidscats                           r   r`   zGroundingChunkStep._plan   s>   	I>E	8 	$%--l;ArLL(.B33J?E20 ;#@cS S ,,-=>D"++f%+00:@b--.BCIr*8405=	! !
 ,H5 "#!A }-./!%%-2E2E! 	 #;CE;Ca }-23AEE&M/R02;CEF &(!g&DAq)*+AVA3<C+""63/CD1QCMKKsGC11#tU?B 2 D 	 ' T4&&##E ,s   +/G!"AG&G+c                    ddl m}m}  || j                  |z  | j	                         t        |      d      }|j                         rt        j                  d       |S )uE  구간 장부. ★계약이 바뀌었으면 **이어 쓰지 않고** 새로 시작한다.

        신원(`acquisition_identity`)이 팩·모델·요청 계약을 접으므로 사실은
        저절로 안 맞지만, 계약 자체를 장부에 적어 두면 **왜 다시 사는지**가
        기록에 남는다.
        r   )ChunkJournalJOURNAL_NAME)r2   chunks)r-   ut   grounding_chunk: 획득 계약이 바뀌었다 — 신원이 다른 것만 다시 산다(장부는 그대로 둔다))	,app.modules.pipeline.grounding_chunk_journalr   r   _cp_dirr>   r   contract_driftedr^   r_   )rI   rh   r   r   rl   s        r   ra   zGroundingChunkStep._journal   s^    	( $,,5373D3D3F.1$i$9:   KK K L	r   c                    ddl m} ddlm} | j                   || j                  | j
                        |j                  t        t              dS )u  **provider 응답의 뜻을 지배하는 것만**. ★신원은 이것만 접는다.

        ★★세 가지를 갈라야 한다 (Codex 2026-08-31) —

            provider 획득   payload · 모델 · **실제 전송 인자**
                            → 바뀌면 **다른 답**이다. 다시 산다
            admission 정책  마감 · 슬롯 · 예산
                            → 「언제·몇 번 보낼까」지 **답의 뜻이 아니다**.
                              여기 넣으면 정책만 바꿔도 raw 를 재구매한다
            해석            후처리 · adapter · 구간 나누기
                            → 무료 재해석

        ★`max_tokens` 는 **실제 전송 인자**라 여기 든다 — 앞 판엔 빠져 있었다.
        r   )_resolve_modelr'   )aliasphysicalpackrequest)	app.modules.llm.llm_clientr   r<   r(   step_idr=   r?   dictr   )rI   r   rK   s      r   r>   zGroundingChunkStep._acquisition  sC     	>> \\&t||T5H5HI)),-	
 	
r   c                |    ddl m} |j                         }t        |t	        t
        |      | j                         dS )u  **언제·몇 번 보낼까**. ★신원에 안 접는다 — 답의 뜻이 아니다.

        지문에는 **따로** 접는다. 정책이 바뀌면 「같은 조건에서 돌았다」가
        거짓이 되지만, 이미 산 raw 는 **여전히 그 답**이다.
        r   )openai_keys)r\   r   r   transmission_cap)app.corer   
slot_countre   r   r   rd   )rI   r   r   s      r   r@   zGroundingChunkStep._admission  s@     	)&&( 6$89I9>%@ $ 6 6 8
 	
r   c                Z    t        | j                  j                  d      xs t              S )uR   물리 전송 상한. ★`grounding_research` 와 **같은 설정 키**를 본다.research_transmission_cap)r   r=   r   DEFAULT_TRANSMISSION_CAP)rI   s    r   rd   z$GroundingChunkStep._transmission_cap/  s+    4&&**+FG /.0 	0r   c                f    ddl m} | j                         } |||d   |d   d|d   i|d         S )u   호출 **신원** — 팩·모델·**실제 요청 계약**까지 접는다.

        ★production 함수 한 곳(`acquisition_identity`)이 만든다 — 감사 도구와
        같은 것이라 장부·trace 대조가 성립한다.
        r   )acquisition_identityr   r   r   r   )model_aliasmodel_physicalr   )$app.modules.pipeline.grounding_chunkr   r>   )rI   rN   r   acqs       r   	_identityzGroundingChunkStep._identity4  sI     	N!#Wc*o$c&kDS^DF 	Fr   c                    t        |      dz   S )uN   논리 상한 — 구간 수 + merge 1. ★계획에서 **한 번만** 센다.r   )r   )rI   rh   s     r   _capzGroundingChunkStep._capA  s    4y1}r   c                    | j                         }|d   }| j                  |      t        |d         z  }||kD  r0ddlm}  |dd| d| d| j                  |       d	|d    d
	d      |S )u  물리 **상한**이 승인 범위 안인가. ★넘으면 provider 앞에서 선다.

        ★막는 것이 아니라 **정하는 것**이다 — 글 호출 경로에는 세는 자리가
        없어서 물리 수를 그 자리에서 거절할 수 없다. 대신 재시도·fallback 을
        끄고 슬롯 수로 위를 정한 뒤, 그 수가 **손으로 적은 승인 수**를 넘으면
        아무것도 안 사고 선다.
        r   r   r   r   z'grounding_chunk.physical_bound_exceededu   물리 상한이 u     인데 승인 전송 상한은 u    이다 (논리 u    × 슬롯·재시도 ug   ) — 사람이 `research_transmission_cap` 을 다시 정해야 한다. 아무것도 안 사고 선다i  r   )r@   r   r   r   r   )rI   rh   admr[   rm   r   s         r   rc   z)GroundingChunkStep._assert_physical_boundE  s     oo$%		$#c*@&A"BB3;0>,UG3STWSX Y$$(IIdO#44J 678 9::
  ! ! r   Nc                   ddl m} ddlm} ddlm} g }g }	g }
i }t        t        t        t        |                  5 } || j                        }|D ci c])  }|j                  |||| j                  |            |d   + }}t        |      D ]  }||   }	 |j                         ||<    	 d	d	d	       |D ]j  }|j)                  |d         }||j+                  |j)                  d
      xs g |d   |d   ||j)                  |d               }||d
   z  }|	|d   z  }	l |
r@ddlm}  |ddt        |
       dt        |       d|
D cg c]  \  }}|	 c}} dd      ||	|
fS c c}w # t        $ rT} ||      rt         j#                  d|        t         j%                  d||       |
j'                  ||f       Y d	}~:d	}~ww xY w# 1 sw Y   %xY wc c}}w )uU   구간마다 **한 번** 읽는다 — 병렬. ★행 해석은 production 함수가.r   r'   )bind_current_research_budget)is_abort)max_workersr   u   grounding_chunk 중단 — %su   grounding_chunk %s 실패: %sNru   r   )r   r   r   r   rv   r   zgrounding_chunk.incomplete_readu   구간 /u    을 못 읽었다 (u   ) — 등록 횟수는 구간을 걸쳐 세므로 남은 것만으로 내면 **수가 틀린다**. 재개해서 빠진 구간만 다시 읽어야 한다i  r   )r<   r(   rb   r   app.core.run_controlr   r   minMAX_WORKERSr   _onesubmitr   r   result	Exceptionr^   warningerrorr   r   resolve_rowsr   r   )rI   rh   ri   rj   rl   rK   r   r   ru   rv   failedgotexsendr   futsfcidexcrespoutr   rw   s                          r   rr   zGroundingChunkStep._read_chunks^  s   >N1%',. C03D	-; <?A 0		:D+/1+/a IIdAr499T?;jM"+/  1!$'1g. xxzCH (<0 A771Z=)D|//$((6"2"8b+,Z=./.>/3xx*/F " HC CKD3}--K  06"3v;-qT <,23FDAqaF34 5QQ    [&((Y1 ! 
.  }'FLLL!@#sKMM3*--
.< <X 4sN   G.E-GE2,GG-G2	G;A	G
G
GGGc           
     |    ddl m} |d   }| j                  ||j                  ||d|j	                  d             S )Nr   r'   rN   zchunk:r   slot)r<   r(   _callCHUNK_SCHEMA_NAMEr   )rI   chunkrl   r[   rK   ps         r   r   zGroundingChunkStep._one  sI    >)zz!R112s!'		*(='>?  A 	Ar   r   c          	         	
 ddl m ddlm ddlm ddlm} ddlm		  j                        
	
 fd}| |       S  ||
||| fd	      S )
u  산다 — **장부·예산·정지를 거쳐서**. ★한 곳에서만 보낸다.

        ## ★신원은 `opik_metadata` 로는 **안 간다**

        litellm 이 `metadata["opik"]` 에서 읽는 키는 **넷뿐**이다
        (`project_name`·`current_span_data`·`tags`·`thread_id`,
        `llm_client._build_opik_metadata` 주석). 다른 키는 조용히 버려지고,
        태그로 실어도 **축 whitelist**(`is_axis_tag`)가 거른다.

        ★이 결함을 앞서 한 번 겪고 기억에 적었는데 **다시 냈다**
        (Codex 2026-08-31). 신원은 **부모 trace 의 metadata** 로 간다 —
        감사 도구(`cc_runner.live_send`)가 쓰는 바로 그 자리다.
        r   )research_calls_armed)call_structured)
open_trace)buy_or_reuse)call_with_deadlinec                     t         t        gt        dj                  dij                   dj
                   di      5          5    j                  d   d   d   t        j                  dt        git        d	   t        d
   t        d         cd d d        cd d d        S # 1 sw Y   nxY w	 d d d        y # 1 sw Y   y xY w)Nstepschema:identity)nametagsmetadata	thread_id
input_datasystempartsr   r   r   r   )r\   r=   schema_nameopik_metadatar   r   r   )	CHUNK_TRACE_NAME	CHUNK_TAGID_META_KEYr   
project_id
episode_idCHUNK_CALL_DEADLINE_SECONDSr=   r   )r   r   identr   rN   r   r   rI   s   r   _sendz'GroundingChunkStep._call.<locals>._send  s     !1&15&,dll&.&= *.(94??:K&L(2E':< *+-'gh&79I))D'+':':$/'-	{&;$4]$C(89J(K$4]$C
E ,+< < ,++< < <s%   C	AB3 	C	3B<	8C		Cc                 0    j                  d  d      S )Nzgrounding_chunk Twherestrict)checkpoint_gate)r   rI   s   r   <lambda>z*GroundingChunkStep._call.<locals>.<lambda>  s"    t33(6t  4  Er   )r[   r   r   
stop_check)rb   r   r   r   app.modules.llm.opik_tracer   r   r   !app.modules.pipeline.llm_deadliner   r   )rI   rN   r   rl   r[   r   r   r  r   r   r  r   r   s   ```     @@@@@r   r   zGroundingChunkStep._call  s]     	G>9MHw'	E 	E< :7N3UEF 	Fr   c           	     f   ddl m} ddl m} |s|j                  g g |      S | j	                  dd       |j                  |      }| j                  ||j                  ||xs# |r|j                  j                  dd      d	z   ndd
      }|j                  ||j                  d      xs g |      S )Nr   r'   )grounding_chunk_merge)r   zgrounding_chunk mergeTr  r   r   merger   	decisions)
r<   r(   r  reduce_episoder
  build_merge_payloadr   MERGE_SCHEMA_NAMEr-   r   )	rI   ru   ri   rl   r[   rK   cmr   r   s	            r   rs   zGroundingChunkStep._merge  s    >D$$Rd$;; 	#:4H""4(zz!R112 *b"++//(A">"B'(  9   txx'<'B*. ! 0 	0r   c                6   | j                  d      xs i fd}g }| j                  d      xs dD ]Q  } ||xs i j                  d             ||xs i j                  d            }}|s<|s?|j                  ||f       S t        t        |            S )uT   `part_of` 를 **정본 ID 짝**으로. ★등록된 것만 — 없으면 안 낸다.
registeredc                x    t        j                  t        |             xs i j                  d      xs d      S )Nfinal_idr   )strr   )lidregs    r   _fidz3GroundingChunkStep._part_of_final_ids.<locals>._fid  s0    S*0b55jAHbIIr   part_of partwhole)r   r   sortedset)rx   r  r   xr   wr  s         @r   _part_of_final_idsz%GroundingChunkStep._part_of_final_ids  s     kk,'-2	J ++i(.B.Abf-.ag2]]75K0LqAQ

Aq6" / c#hr   c                b   ddl m} ddl m} ddl m}	 |j	                  || j
                  | j                        }
|	j                  |d   |j                  d      xs g |j                  d      xs i       }|	j                  |d	          | j                         t        |      t        |      d|t        |      |r|j                         nt        |      |rd
ndz   | j                  |      | j                         d   | j                  |      z  | j                         d|j                   |j"                  |j$                  dd|
d   g |
d   |
j                  d      xs g |
d   ||D cg c]V  }|j                  d      rC|j                  d      |j                  d      |j                  d      |j                  d      dX c}| j'                  |      D cg c]
  \  }}||d c}}|d	   |d   |j                  d      xs i |j                  d      xs i ddS c c}w c c}}w )uO   ★`entity_merge` 모양과 **근거**를 함께 낸다. adapter 가 짓는다.r   r'   r)   )grounding_facet_binding)r  r  ru   r  r  bindingsr   r   T)r   r4   r.   )rS   r   rU   logical_capphysical_upper_boundr   complete	contracts
candidates
facet_debtrT   salvage_problemslocal_id
owner_typesurface_form)r2  r3  r4  problems)r!  r"  debtcountsrefused)removedgrounding_candidatesgrounding_facet_debt_rowsgrounding_skippedgrounding_quarantinedgrounding_salvagedgrounding_part_ofgrounding_facet_bindingsgrounding_facet_debtgrounding_countsgrounding_refused)config_hashrV   rW   rX   rY   )r<   r(   r*   r)  project_rows_and_candidatesr  r  bindr   assert_owner_pairsrO   r   boughtr   r@   rd   r?   rA   rB   r'  )rI   rg   rh   ru   rv   rx   rl   rK   rL   fb	projectedfacetr%  r  _wids                  r   rt   zGroundingChunkStep._wrap  s=   >FF 22DOO 3 M	Y)?)E2L17R9
eJ/0
  ,,."4y #D	d) 35299;(+D	$QA(F#yy )-(9*),.2iio)>$($:$:$< 11"$"@"@!::,( F#),* (1,(?-6]]<-H-NB%.y%9)4 "'@ "QUU+=%> "#z!2!%%BU%&UU>%:N`Hac!'@ '+&=&=g&F&H&F
d "D1&F&H -2*,=(-f$+KK$9$?R%,[[%;%ArW,6
 6	
N'@&Hs   -AH&H+)returnr  )resume)rS   r  rM  r   )rM  r   )rM  r   )rM  r   )rN   r   rM  r  rp   )Nr   )r   r   r[   r   rM  r   )
rN   r   r   r  r[   r   r   zOptional[str]rM  r   )r[   r   )__name__
__module____qualname____doc__rO   rn   rf   r`   ra   r>   r@   rd   r   r   rc   rr   r   r   rs   staticmethodr'  rt   r   r   r   r#   r#   _   s    %NN;:H0'd2
2
"0
F2<)|A A (,:F$:F0>:Fz0$    F
r   r#   )r   r   r   r   rM  r   )rR  
__future__r   loggingconcurrent.futuresr   r   typingr   r   r   r	   r:   r
   app.core.steps.entity_stepsr   	getLoggerrO  r^   r   re   r  r   __annotations__r   r   r   r   r   r#   r   r   r   <module>r[     s   . #  ? , , + 8			8	$    $  $ .   1 *  	 i
): i
r   