
    Oj֊                        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
 ddlmZ  ej                  e      ZdZdZ G d	 d
e      ZddZddiZdZd Zd ZddZddZddZddlmZ dZ G d dee
      Zy)uI  참고 사진 획득 — GROUNDING-V2 §2-4. `entity_filter`(13.7) 뒤 · 13.8.

★★**참조를 사는 경계는 하나다** — `grounding_central_acquisition.run` 과 이 모듈의 공장
(`make_search/make_download/make_judge/make_writer`). 이 스텝(19.3)이 중앙 의무를 사고,
야외 구조물(21.915)은 **같은 경계·같은 공장**으로 `structure_form` 보충 의무만 산다
(2026-09-03 · 설계 §8). 검색·다운로드·VLM 선택 코드를 다른 자리에 두지 않는다 —
앞뒤가 각각 제 손으로 사면 같은 것을 두 번 산다.

primitive 는 `search_grounded_ref` 에 이미 있다. **복사하지 않고 부른다.**

대상은 `generation_difficulty` 가 `hard`/`uncertain` 인 것뿐이다.
★`route` 로 고르면 안 된다 — 실측에서 아홉 축이 **전부** `research` 였다.
    )annotationsN)AnyDictListOptional)
StepRunner)_EntityStepMixinreference_acquisition   c                      e Zd ZdZy)OwnerCoverageRefuseduQ   이 판의 대상 갈래가 **기대와 다르다**. ★provider 0 으로 선다.N)__name__
__module____qualname____doc__     _/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/steps/reference_acquisition_step.pyr   r      s    [r   r   c                    ddl m} t        |j                  |             }|D ch c]  }t	        |       }}t        ||z
        }|rt        d| dt        |       d      t        |      t        |      dS c c}w )u&  장부가 **그 갈래들을 다 갖고 있나**. ★공개 끝점.

    ★★이름·부분문자열로 안 본다 — 장부가 적은 `owner_type` 만 본다
    (Codex 2026-09-02).

    Raises:
        OwnerCoverageRefused: 빠진 갈래가 있다. 조사를 **시작도 안 한다**.
    r   grounding_central_inputsu#   이 판의 조사 대상에 갈래 u    가 없다 — 있는 것 uZ   . 기대한 것과 다른 것을 재게 되므로 provider 를 안 부른다(inconclusive))requiredpresent)app.modules.pipeliner   setowners_reaching_the_doorstrsortedr   )obligationsr   cigotxwantmissings          r   assert_owner_coverager%   #   s     D
b))+6
7C$%HqCFHD%TCZ G"1':Uc{m *+, 	, t== &s   A;max_retriesc                 V    ddl m}  ddlm ddlm  | di t        fd}|S )Nr   openai_client)MAX_CANDIDATES)search_reference_imagesc                 :    | j                  d        fi | S )Nmax_results)
setdefault)kwr*   clientr+   s    r   _fnzmake_search.<locals>._fnJ   s!    
m^4&v444r   r   )app.core.openai_keysr)   !app.modules.pipeline.era_researchr*   (app.modules.pipeline.search_grounded_refr+   LANE_CLIENT_KWARGS)r)   r1   r*   r0   r+   s     @@@r   make_searchr6   C   s&    2@P0/0F5 Jr   c                     ddl m}  | S )Nr   download_candidate)r4   r9   r8   s    r   make_downloadr:   Q   s    Kr   c                8    ddl m} t         ||       xs d      S )ut   창작자 확정 세계 사실 블록 — `build_grounding_world_facts` (다섯 갈래 공용 · 실패 안 삼킴).r   )build_grounding_world_facts )app.core.world_contextr<   r   )worldr<   s     r   world_facts_block_ofr@   V   s    B*517R88r   c                    ddl m} ddl m} ddlm} |j                  |       }|j                  t        |       ||d   |d    |d	i t        |||d      S )
ut   검색 지시문 **조사 저작기** (사용자 5단계 ①·②·④) — `grounding_target_research.make_writer`.r   )grounding_search_brief)grounding_target_researchr(   eraregion
project_id
episode_idstep)world_factssource_textrD   rE   r0   opik_metadatar   )	r   rB   rC   r2   r)   coordinates_ofmake_writerr@   r5   )	r?   rK   rG   rH   step_idgsbgtrr)   cos	            r   rN   rN   \   sa    BE2			E	"B??(/[uIbl212%/zSZ[	  ] ]r   c                    	
 ddl m ddlm ddlm ddlm ddlm		 j                         d   j                     d   j                         
d
d	 
f
d	}|S )uo   거친 종류·가시성 + CRITERIA 일치·닮은 정도 **1심**. ★시대·국가·품질은 안 묻는다.r   Path)call_structured)coarse_type_pick)
PICK_MODEL)png_partstemscontentc                  
 ddt        | d   xs i j                  d      xs d      z   dg}|r|j                  dd|z   d       ddlm} | D ]n  }|j                  dd	|d
    dd        	|d         }|j                         s 	|j                        j                  |z  }|j                   |             p d} 
||d   j                     d   |dii|dt              }|iS )Ntextz,THE KIND OF THING the photograph must show:
r   	kind_namer=   )typer]   z,CRITERIA (what the photograph should show):
settingszPHOTOGRAPH index:pathgrounding_coarse_pickrZ   r[   modelrF   )project_configschema_namerL   num_retries)
r   getappendapp.core.configra   is_absoluteprojects_dirparentSCHEMA_STEMLANE_LLM_NUM_RETRIES)
candidatescriteriaparts	_settingsgpthtagrawrX   _PrV   ctprH   packrY   rG   rO   sys_texts           r   r1   zmake_judge.<locals>._fnu   s$    J *Q-"52!:!:;!G!M2NOQ R LL&"QT\"\^ _9ALL&K'
|12MNOQvY-C??$//077#=LL#'  &5$w-"@"K':!67S)3:W^_,	.
 C  r   )r=   )rs   r   )pathlibrU   app.modules.llm.llm_clientrV   r   rW   r3   rX   %app.modules.pipeline.multiroll_geminirY   	load_packSYSTEM_STEMstrip)rG   rH   rO   r1   rX   rz   rV   r{   r|   rY   r}   s   ``` @@@@@@@r   
make_judger   i   sO    ":<<>==?DG}S__-i8>>@H! !, Jr   )OUTBOUND_CONSUMER_STEPSzjournal_replay.jsonc                  $   e Zd ZdZddZedd       ZddZddddd	 	 	 	 	 d dZddd	 	 	 	 	 d!d	Z	e	 	 	 	 d"d
       Z
d Zd Zd Zed        Zd Zd ZddZddZd ZeZd#d$dZd%dZdZdZd%dZddZed&d       Zed'd       Zd(ddZy))ReferenceAcquisitionStepu+  ★어려운 대상마다 참고 사진 하나를 구해 둔다.

    한 대상의 흐름:

    ```
    검색 지시문 저작        ← 「무엇을 찾을지」 자료 모으기 (판정 아님)
        ↓
    이미지 검색 (라운드당 `era_research.MAX_CANDIDATES` 장 = 4)
        ↓
    안전 다운로드
        ↓
    VLM 거친 종류 선택      ← 「자동차인지 화폐인지」만
        ↓  없으면
    질의를 좁혀 **한 번 더** (총 2라운드)
        ↓  그래도 없으면
    no_match_after_retry — **terminal**. ★하류를 **막지 않는다**
    ```

    ★★종결 상태가 하류를 막나 안 막나는 **여기서 정하지 않는다** —
    `reference_acquisition.acquisition_outcome` / `downstream_blocked` 한 곳이
    정한다. 못 구하면 `reference_unavailable` 로 적고 참조 **없이** 내려간다
    (사용자 확정 2026-08-31: 「HITL 을 무조건 필요한 요소로 하면 안 된다」).
    이 줄은 HITL 을 없앨 때 **안 고쳐진 자리**였다 (Codex 2026-09-01).
    c           
        ddl m} | j                  d      xs i j                  d      xs i }|j                  d      xs g }t	         ||            }|sg S | j                  d      xs i j                  d      xs i }|j                  d      xs i }|D ci c]!  }t        |j                  d      xs d	      |# }}g }	t        |t              r|j                         ng D ]  \  }
}|xs g D ]w  }t        |j                  d      xs d	      }|s!| j                  |j                  d
      |      }|sG||v sL|	j                  i ||
||j                  |      xs i d       y  |	S c c}w )uu  이 주행이 참조를 살 대상. ★**최종 엔티티 집합** 위에서 고른다.

        ★두 자리를 **모두** 만족해야 한다:
        ① `grounding_plan` 이 `generation_difficulty` 로 연 것
        ② `entity_filter` 를 **살아남은** 행

        ①만 보면 필터가 지운 것까지 사고, ②만 보면 흔한 것까지 산다.
        r   )reference_acquisition_idsgrounding_plandatadecidedentity_filterfiltered_entitiesresearch_subject_idr=   name)
entity_keyr   r   )&app.modules.pipeline.grounding_plannerr   _load_prev_checkpointrj   r   r   
isinstancedictitems_sid_for_surfacerk   )selfr   planr   r#   filtrowsd
by_subjectoutkeyr   esids                 r   _targetsz!ReferenceAcquisitionStep._targets   s    	U**+;<BGGOUSU((9%+,W56I**?;ArFFvNTRTxx+,2 KRR'Qc!%% 56<"=q@'
R$&+5dD+A4::<rIJCkrk!%% 56<"= //fzJC3$;JJ  F!  F37:+5>>#+>+D" F G " J 
 Ss   &E0c                    ddl m}  |t        | xs d            }|sy|j                         D ]K  \  }} |t        |j	                  d      xs |j	                  d      xs d            }|s@||v s||v sI|c S  y)u  이름으로 subject 를 찾는다. ★**완전성 확인**이지 뜻 판단이 아니다.

        `grounding_overlay.missing_candidates` 와 **같은 규칙**을 쓴다 —
        「고무줄로 묶인 회수권 뭉치」가 「회수권 뭉치」로 줄어도 잡는다.
        r   )_normr=   _surface_formsurface_form)&app.modules.pipeline.grounding_overlayr   r   r   rj   )r   r   r   r#   r   r   forms          r   r   z)ReferenceAcquisitionStep._sid_for_surface   s|     	AS_% &&(FCQUU?3Rquu^7LRPRSTD
 ) r   c                    ddl m} |j                  | j                  d      | j                  d      | j                  d            S )uI   이 주행의 **의무 장부**. ★CP 셋을 읽어 합치기만 한다.r   r   grounding_screenr   outlook_phase3)r   r   build_obligationsr   )r   r    s     r   central_obligationsz,ReferenceAcquisitionStep.central_obligations   sE    G##&&'9:&&'78&&'78: 	:r   N)write_briefrel_root
stop_checkroundsc       
        F    ddl m} |j                  |||||	|||||
|      S )u   의무 장부 → **조사 결과**. ★조사기는 하나뿐이다.

        ★부르는 것을 **주입받는다** — 이 스텝이 provider 를 직접 안 연다.
        r   grounding_central_acquisition)
journalcapworkdirr   searchdownloadjudger   r   r   )r   r   run)r   r   r   r   r   r   r   r   r   r   r   r   cas                r   central_resultz'ReferenceAcquisitionStep.central_result   s7     	Mvvk7W'!{!+F  < 	<r   )rootr   c          	        ddl m} ddlm} ddlm}	 |	j                  | j                         |      }
|	j                  |j                  d      xs d| ||xs  ||j                        j                        | j                         ||      }i ||d   i |j                         D ci c]  \  }}|dk7  s|| c}}d|
id	}| j                  ||      S c c}}w )
uV  이미 **받아 둔** 후보만 다시 판정한다. ★검색 0 · 받기 0.

        ★★★왜 (실측 2026-09-02 유료 canary ①): 검색 10회·받기 38장을 다 사고
        12대상 **전부**가 `판정 실패: [Errno 2]` 로 떨어졌다. 산 것을 버리고
        다시 사지 않으려면 받아 둔 것으로 **판정만** 다시 하는 자리가 있어야
        한다.

        ★이 메서드는 **조립만** 한다 — `search`·`download` 를 안 만들고,
        중앙의 `rejudge_rows` 에게 넘기지도 않는다(그 함수는 받지도 않는다).
        r   r`   rT   r   r   r   )r   r   r   r   r   lanes)r   replay)rl   ra   r~   rU   r   r   reconcile_lanes_journalrejudge_rowsrj   rn   ro   _judger   central_wrap)r   r   resultr   r   r   r   ra   rz   r   r   r!   kvmergeds                  r   central_rejudgez(ReferenceAcquisitionStep.central_rejudge  s     	-&L
 ""4==?G<ooJJv$"gD<Bx445<<=++-SZ  A.F .CK-LAV1L -$e-.   f55  Ms   *C8Cc                   ddl m ddl m} ddl m} ddlm}m j                  | |      }|d   st        d|       j                  |      }|rt        dt        |       d	      |j                  d
      xs g }|D cg c]  }j                  |      | }	}t        j                  |      }
ddt        |
d         i dt        |
d         dt        |
d         dt        |
d         d|d|j                   d|j"                  dj$                  dt'        |j)                  |             d
|d|j                  d      xs i d|j                  d      xs i dt        |	      dt+        fd|	D              d|	D cg c]  }j                  |      k7  r|d    c}dt        |      t        |	      z
  dt        |      d S c c}w c c}w )!u   조사 결과 → **이 스텝의 CP 모양**. ★들어온 줄이 전부 남는다.

        ★못 구한 것도 `rows` 에 남는다 — 나중 UI 가 「무엇이 없었나」를 읽는다.
        r   r   r   )grounding_reference_obligations)ACQUISITION_CONTRACT_VERSIONSTATUS_SELECTEDoku(   의무가 산출에서 사라졌다 — u   사람을 기다리는 줄이 u&   개다 — 이 판의 정책은 HITL 0r      totalrejudge_pendingrejudgeresearch_retry_pendingresearch_retrypending_totalcontract_versionobligation_contractinputs_contractwiring_contractowners_present	purchasesdispositionstarget_countselected_countc              3  J   K   | ]  }j                  |      k(  rd   yw)r   N)acquisition_outcome_of).0rr   r   s     r   	<genexpr>z8ReferenceAcquisitionStep.central_wrap.<locals>.<genexpr>d  s,      &H%!003F vs    #
unresolvedr   not_applicable_countledger_rowscompleted_countapplicable_countfailed_countr   )r   r   r   r   *app.modules.pipeline.reference_acquisitionr   r   ledger_coverageRuntimeErrorunfinished_rowslenrj   r   r   pending_rowsintOBLIGATION_CONTRACT_VERSIONCENTRAL_INPUTS_CONTRACT_VERSIONCONTRACT_VERSIONlistr   sum)r   r   r    ror   covleftr   r   neededpendingr   r   s              @@r   r   z%ReferenceAcquisitionStep.central_wrap%  sm    	MGN	;   f54y!I#OPP!!&)0T;abd dzz&!'R "NTR%>%>q%A%M!TN +77?  ! ! 01 "3wy'9#: )#g6F.G*H	
  WW%5!6 #$@ &r'E'E "2#E#E "2#6#6 !$//<#>  VZZ4: 

> : @b  F!" !# &H%&H #H#*  3A"$";";A">"1#2  !!67 3+2 'D	CK(?34 s4y5!
 !	
 OR3s   GG Gc           
        ddl m} t        | dd      }t        |t              r%t        |j                  d      t              r|d   }n.| j                  t              }|xs i j                  d      xs i }| j                  |      }|d   s |dg di d	      S g }|d
   r|j                  d|d
    d       |d   r|j                  d|d    d        |d|d|d
   |d   |d   ddd	      S )u  ★★★**산 것을 못 본 판은 끝난 판이 아니다** (실측 2026-09-02).

        유료 판이 검색 10회·받기 38장을 다 사고 12대상 전부 판정에서 죽었는데
        `completed` 로 닫혔다. 그러면 재개가 **`SKIP`** 으로 지나가고 —
        받아 둔 사진을 다시 볼 길이 없다. 이 자리가 「아직 못 본 것이 있다」를
        말해야 재개가 `RERUN_SELF` 로 다시 돈다.

        ★`origin='artifact_missing'` 이라 **cleanup 이 안 일어난다** —
        `force` 로 가면 `cleanup_artifacts`+`clear_checkpoint` 가 받아 둔
        사진과 장부를 지운다. 산 것을 지우면 안 된다.
        r   )CompletionReport_last_execute_resultNr   r   Tclean)is_completer$   severitymetadataoriginr   u   판정 못 한 대상 uA   개 — 받아 둔 사진이 있는데 아무것도 못 골랐다r   u   다시 사야 할 대상 u6   개 — 후보 없이 끝났다(정지·검색 실패)Fpartialu   재개가 자동으로 처리한다 — 후보 있는 줄은 판정만, 후보 없는 줄은 그 대상만 다시 산다. 이미 고른 줄은 장부 되쓰기(provider 0))r   r   r   u   ★meansartifact_missing)
app.core.integrity_reportr   getattrr   r   rj   r   STEP_IDr   rk   )r   r   lastr   cpr   r$   s          r   verify_completionz*ReferenceAcquisitionStep.verify_completionu  s<    	? t3T:dD!j&1A4&H<D++G4BH">>&)/RD##D)w#b-4r+24 4 9NN3GI4F3G  HI  J  K#$NN6w?O7P6Q  RH  I  J)0);078H0I'.w'7$df
 &	' 		'r   c                    ddl m} ddlm}  ||j                        | j
                  z  dz  dz  | j                  z  }|j                  dd       |S )uS   받은 사진을 둘 자리. ★**서빙 가능한 곳**이어야 사람이 본다.r   rT   r`   
references	groundingTparentsexist_ok)r~   rU   rl   ra   rn   rG   rH   mkdir)r   rz   ra   r   s       r   _workdirz!ReferenceAcquisitionStep._workdir  sO    &,%%&8<G!__-	t,r   c                    t               S N)r6   r   s    r   _searchz ReferenceAcquisitionStep._search  s
    }r   c                     t               S r  )r:   r   r   r   	_downloadz"ReferenceAcquisitionStep._download  s
    r   c                ,    | j                  d      xs i S )Nvisual_world_rules)r   r  s    r   _worldzReferenceAcquisitionStep._world  s    ))*>?E2Er   c                    t        | j                         | j                         | j                  | j                  t
              S )uw   검색 지시문 저작기. ★**production 이 이것을 넘긴다** — 공장은 모듈 함수 `make_writer` 하나.)r?   rK   rG   rH   rO   )rN   r  _source_samplerG   rH   r
  r  s    r   _write_briefz%ReferenceAcquisitionStep._write_brief  s4    D<O<O<Q&*oo$//[bd 	dr   c                    t        |      S r  )r@   )r   r?   s     r   _world_facts_blockz+ReferenceAcquisitionStep._world_facts_block  s    #E**r   c                    | j                  d      xs i }|j                  d      xs i }t        |j                  d      xs |j                  d      xs d      S )u   언어 판정용 원문 표본. ★`search_grounded_ref` 와 같은 상수로 자른다.
        ★이 자리만 발췌한다 — 사용자 2026-08-03 조건부 예외(정보 하나를 얻는 용도). 나머지 어디서도 원문을 안 자른다.text_cleanupr   cleaned_textr]   r=   )r   rj   r   )r   r  r   s      r   r   z'ReferenceAcquisitionStep._source_sample  sR     ''7=2vvf~#488N+Etxx/?E2FFr   c                N    t        | j                  | j                  t              S )uR   거친 종류·가시성 **1심** — 공장은 모듈 함수 `make_judge` 하나.)rG   rH   rO   )r   rG   rH   r
  r  s    r   r   zReferenceAcquisitionStep._judge  s    T__Zabbr   c                "   ddl m} ddlm} ddlm} ddlm}  ||j                        | j                  z  dz  dz  | j                  z  t        z  t        |      z  }|j                  j                  dd	        ||d
|j                  i      S )u  이 스텝의 장부. ★구매와 재판정은 **다른 장부**다.

        ★★★왜 갈랐나 (실측 2026-09-02): 재판정을 구매 장부에 적었더니
        `reserve` 가 **이미 산 12건**을 제 상한에서 빼서, 12대상 중 **4개만**
        되보고 멈췄다. 상한은 「이 판이 몇 번 부르나」인데 앞 판의 구매가
        그것을 먹은 것이다. 문이 다르면 장부도 다르다.

        ★원 구매 줄은 **한 글자도 안 건드린다** (Codex 계약 2026-09-02).
        r   r`   r   )ChunkJournalrT   checkpointsepisodesTr  wiring)contract)rl   ra   r   r   ,app.modules.pipeline.grounding_chunk_journalr)  r~   rU   rn   rG   rH   r
  r   ro   r  r   )r   r   ra   r   r)  rz   rd   s          r   r   z!ReferenceAcquisitionStep._journal  s     	-LM&8(()DOO;mK#/189;>t9E$6DHb6I6I+JKKr   c           	     P   ddl m} ddl m} t        |j	                  |            }| j
                  xs i j                  d      }t        |t              r)t        |t              sd|cxk  r|k  rn nt        |      n|}|t        dt        |j                  d                  z  S )u  이 주행의 **논리 조사 상한** = 대상 수 × pass 수.

        ★★pass 마다 구매 신원이 다르다(1차 전원 → 2차 못 구한 것만). 앞 판은 상한이
        대상 수 N 이라 1차가 N 을 다 쓰고 **2차가 문 앞에서 전부 막혔다** — 시험은
        `buy_or_reuse` 를 대역으로 바꿔 못 봤다 (Codex BLOCK 1, 2026-09-02).
        `grounding_reference_cap` 은 **대상 수** 단위로 받고 여기서 같이 곱한다.r   )grounding_acquisition_ledgerr   grounding_reference_capr   N)r   r0  r   r   acquisition_targetsrg   rj   r   r   boolmaxresolve_rounds)r   r   glr   nry   
per_targets          r   _capzReferenceAcquisitionStep._cap  s     	LL&&{34""(b--.GH!+C!5j?=q= X./ 	C3r'8'8'>#?@@@r      <   c                    | j                   xs i j                  d      }|rt        |      S 	 t        |xs d      }|dkD  r|| j
                  z  S | j                  S # t        t        f$ r d}Y 3w xY w)Nreference_transmission_capr   )rg   rj   r   	TypeError
ValueErrorTRANSMISSIONS_PER_LOGICAL_SLOTDEFAULT_TRANSMISSION_CAP)r   logical_capry   r7  s       r   _transmission_capz*ReferenceAcquisitionStep._transmission_cap  s}    ""(b--.JKs8O	K$1%A ;<a%q4666bTEbEbb :& 	A	s   A A10A1c                   ddl m} ddlm}m} | j                         }| j                  xs i j                  d      }|rt        ||        || j                  | j                  |                  5   |       5  | j                  || j                         | j                  |      | j                         t        d      j                  |j                         j"                  | j%                         | j'                         | j)                         | j+                         t-        | dd      	
      }| j/                  |      r[| j1                  ||| j                  | j2                        | j                  |      t-        | dd      
      cddd       cddd       S 	 ddd       ddd       | j5                  |      S # 1 sw Y   #xY w# 1 sw Y   'xY w)u   중앙 갈래 — 의무를 세우고 **한 곳에서** 조사한다. ★물리 전송 문(`research_run_scope`) 안에서 판다 (PR #82 리뷰).r   r`   )research_calls_armedresearch_run_scoperequired_owner_types)r   r~   _stop_checkN)	r   r   r   r   r   r   r   r   r   )r   r   r   )rl   ra   app.core.research_call_budgetrE  rF  r   rg   rj   r%   rC  r9  r   r   r  
__import__rU   rn   ro   r  r  r   r!  r	  rows_to_rejudger   REPLAY_JOURNALr   )r   ra   rE  rF  obr#   ress          r   _centralz!ReferenceAcquisitionStep._central  s   ,Z%%'
 ##)r../EF!"d+D$:$:499R=$IJ%'))TYYr] MMO'	2778M8MNUU<<>DNN4D++-T5F5F5H&t]DA * C '',//Ct7J7J)K IIbM#*4#E 0 G (' KJ - ( K$   S))# (' KJs+   3G;DG 
	GG G		GGc                   ddl m} ddl m} g }g }| j                  d      xs dD ]  }|j                  d      xs i }|j                  d      |j                  k7  s|j                  d      rH|j                  t        |j                  d	      xs |j                  d	      xs d
            rt        |j                  d      xs d
      }t        d |j                  d      xs dD              r|j                  |       |j                  |        t        |      t        |      t        |      t        |      z   ||dS )u8  **자동 재시도 빚** — 아직 raw terminal 이 아닌 줄을 둘로 갈라 센다 (Codex BLOCK 2026-09-03 05:30):
          · `rejudge`: 받아 둔 후보가 있는데 아직 못 본 줄 → 재개가 **사지 않고 판정만** 다시 한다
          · `research_retry`: 후보가 하나도 없는 retryable(정지·검색 실패) → 재개가 **그 대상만** 다시 산다
        둘 다 `total` 에 들어가 completed 봉인을 막는다. ★사람 대기가 아니다 — 재개가 자동으로 처리한다.
        ★실측 4398a55dc0bb: 정지 요청에 맞은 9줄이 후보 0 이라 `rows_to_rejudge` 에 안 세어져 completed 로 봉인됐고
        plain resume 이 SKIP 으로 지나갈 자리였다. terminal(`selected` · `no_match_after_retry`)·이미 고른 줄·안 산 줄은 안 센다.r   r   r
   r   r   acquisitiondispositionchosenstatusr=   r   c              3  F   K   | ]  }|j                  d       xs d  yw)downloaded_candidatesr   N)rj   )r   rds     r   r   z8ReferenceAcquisitionStep.pending_rows.<locals>.<genexpr><  s*      85r FF239r95s   !r   )r   r   r   rejudge_subjectsresearch_retry_subjects)
r   r   r
   rj   DISP_ACQUIREDis_terminalr   anyrk   r   )r   r   rar   r   r   acqr   s           r   r   z%ReferenceAcquisitionStep.pending_rows(  s"    	MD$&**V$**A%%&,"Cuu]#r'7'773778;L~~c!%%/"LSWWX5F"L"MNaee128b9C 8!ggh/5258 8s#%%c* + w<3~;NWN(;;$+X 	Xr   c                \    ddl m} ddl m} t        t        j                  |       d         S )u{  되볼 줄이 **몇 개**인가. ★고르는 규칙을 한 곳에만 적는다.

        받아 둔 후보가 있는데 **아직 못 본** 줄이다. 이미 고른 줄·애초에 안
        산 줄·**다 보고 없었던 줄**은 세지 않는다 — 그래야 「되볼 것이 없으면
        VLM 0」이 성립한다.

        ★★★`no_match_after_retry` 를 빼는 것이 중요하다 (실측 2026-09-02):
        「고른 것이 없다」로만 세면 **다 보고 없었던 줄**까지 매 재개마다 다시
        판정한다 — 같은 것을 계속 사는 것이고, 스텝이 영원히 `partial` 이다.
        r   r   rQ  r   )r   r   r
   r   r   r   )r   r   r^  s      r   rK  z(ReferenceAcquisitionStep.rows_to_rejudgeE  s)     	MD +88@KLLr   c                    ddl m}m} ddlm}m}  | || j                              r| j                         S | j                         } |t              }|sddd||g g ddg ddS t        dt        |       d	      )
Nr   )resolve_grounding_modeuses_chunk_producer)r   acquisition_contract_sha)r   r   )r   acquisition_contracttargetsrecordsr   r   r   r   uC   참조 획득 orchestration 은 아직 안 배선했다 — 대상 u.   개. 유료 승인과 acceptance 가 먼저다)app.core.grounding_moderb  rc  r   r   rd  rg   rO  r   CENTRAL_ROUNDSNotImplementedErrorr   )r   moderb  rc  r   rd  rf  r-  s           r   _executez!ReferenceAcquisitionStep._executeW  s    	B	D
 5d6I6IJK==?"--/
 ,>B'(a$%1M5=(*r-.!+-	/0 0 "Q7|nJLM 	Mr   )returnzList[Dict[str, Any]]rm  r   rm  Dict[str, Any])r   rp  r   r   r   zOptional[int]rm  rp  )r   rp  r   rp  r   r   rm  rp  )r   rp  r   rp  rm  rp  )zjournal.json)r   r   )rm  r   )r   rp  rm  rp  )r   rp  rm  r   )resume)r   r   r   r   r   staticmethodr   r   r   r   r   r  r  r  r  r  r!  r#  r   r   REPLAY_JOURNAL_NAMErL  r   r9  r@  rA  rC  rO  r   rK  rl  r   r   r   r   r      sF   2"H  0: $( $/3	<<  -	< 9G	<$ (,#'6 .6 6 -;6@ I
+I
0>I
 I
^,'\	  Fd
+Gc )NL(A* &("!c *D X X8 M M"Mr   r   ro  rn  )rK   r   rG   r   rH   r   rO   r   )rG   r   rH   r   rO   r   ) r   
__future__r   loggingtypingr   r   r   r   app.core.step_runnerr   app.core.steps.entity_stepsr	   	getLoggerr   loggerr
  ri  r   r   r%   r5   rq   r6   r:   r@   rN   r   1app.modules.pipeline.grounding_outbound_consumersr   rs  r   r   r   r   <module>r|     s    #  , , + 8			8	$
! \< \>8 $Q'  
9
]"P V+ `M/ `Mr   