애널리스트 완료보고 수치 ↔ macro-timeseries 원천값 대조 규칙 제안서 (#40188 실사례)
애널리스트 완료보고 수치 ↔ 원천 시계열 파일값 대조 규칙 제안서
1. [권고]
→ 1순위: macro-timeseries 조기통과 경로 2곳 바로 앞에 수치 대조 게이트 numeric_claim_mismatch 를 끼워 넣는다 (codex 위임).
- 무엇을: 결론: 줄의 bp/%/%p 수치를, 같은 노트가 인용한 memory/macro-timeseries/<SERIES>.json의 (a) 최신 수준값, (b) 최신값 − N관측 전 값(N=1,5,10,20)과 대조. 허용오차 밖이면 done 저장을 막고 numeric_claim_mismatch 라벨로 반려.
- 왜: 지금 macro-timeseries 경로는 "JSON 경로 실존 + scripts/pipeline 경로 실존 + exit code 0/통과 문자열"만 보고 즉시 return [] 한다(값은 안 연다). #40188은 IG −15bp를 주장했지만 파일 실측 5영업일 변화는 −1bp였는데 done으로 통과했다.
- 기대효과(백테스트, §2-3): 최근 로그의 macro-timeseries 인용 done 18건 중 결론에 bp/% 수치가 있는 14건에 프로토타입 적용 → 대조 가능 수치 26개 중 13개 불일치, 6건(#39831/#39942/#40036/#40050/#40051/#40188) 반려 대상. 이 중 #40188·#40050·#40051은 명백한 허위 수치(재현 불가)로, 현재 잡힘률 0% → 3/3.
→ 2순위: 완료 노트에 "기준일·구간" 명시 필드 요구 (기준일: YYYY-MM-DD, 구간: 5영업일) — 정책 문서(config/agent_completion_submission_standard.md) 반영 + 게이트 soft 경고(numeric_claim_period_unstated, 반려 아님).
- 왜: 백테스트 불일치 13개 중 일부(#39942 HY 276→268 = −8bp)는 당시 파일 상태 기준으로는 맞았을 가능성이 있다(파일은 이후 갱신됨, 09-22 기준 계산). 기준일이 노트에 있으면 게이트가 date <= 기준일로 잘라 재계산할 수 있어 오탐이 사라진다.
- 기대효과: 대조 가능 수치 비율 26/57(46%) → 목표 80% 이상, 스테일 데이터로 인한 오탐 0건.
→ 3순위(선택): 결론 수치 불일치 반려 시 자동 재작업 태스크에 파일 실측값 주입 — 반려 note에 expected: IG d5=-1bp(09-16→09-23)를 같이 적어 재작업자가 바로 고치게 한다(이번 #40188 재작업본 260927_hermes_us-credit-spread-weekly.md가 사람 손으로 한 일을 자동화).
2. [현황]
2-1. 현재 게이트에 수치 대조가 없다는 코드 근거 (조회 2026-09-27 KST)
| 위치 | 원문 | 의미 |
|---|---|---|
scripts/agent_queue_worker.py:792-795 |
_MACRO_TIMESERIES_VERIFICATION_VALUE_RE = re.compile(r"(?:exit\s*code\s*[:=]?\s*0\|통과)" |
검증값 = "exit code 0" 문자열 존재 여부뿐 |
scripts/agent_queue_worker.py:798-821 |
def _completion_has_macro_timeseries_collector_evidence … return _any_existing(json_paths) and _any_existing(collector_paths) |
경로 존재(resolved.exists())만 확인, 파일 내용은 열지 않음 |
scripts/agent_queue_worker.py:1512 |
def _completion_required_evidence_gaps(text: str \| None) -> list[str]: |
대상 함수 |
scripts/agent_queue_worker.py:1575-1576 |
if _completion_has_macro_timeseries_collector_evidence(text): / return [] |
canonical 위임 전 조기 통과 (정상 배포 경로) |
scripts/agent_queue_worker.py:1669 |
동일 조기 통과 (import 실패 폴백 경로) | |
scripts/shared/public_result_formatter.py:2503-2506 |
canonical 쪽 동일 정규식 사본 | |
scripts/shared/public_result_formatter.py:2649-2660 |
return any(_completion_path_exists(path) for path in json_paths) and any(...) |
경로 존재만 확인 |
scripts/shared/public_result_formatter.py:3087 |
def completion_required_evidence_gaps(text: str \| None) -> list[str]: |
대상 함수 |
scripts/shared/public_result_formatter.py:3145-3146 |
if _completion_has_macro_timeseries_collector_evidence(raw): / return [] |
canonical 조기 통과 |
scripts/agent_queue_worker.py:3523, 3585 |
_completion_block_reasons → for gap in _completion_required_evidence_gaps(raw): |
최종 차단 사유 수집점 (삽입 후보) |
두 함수 전체(3087~3200행, 1512~1600행)의 검사 항목은 형식 계열뿐이다: placeholder 경로(placeholder_path_line_citation), 자기지시 근거(self_referential_placeholder_evidence), 잘린 인라인 JSON(truncated_unverifiable_evidence), 가짜 verification_command 경로, 실행값/출처/검증값 정규식(missing_execution_value/missing_source/missing_verification_value), 파일:라인:인용(missing_original_verification). 결론 수치를 인용 파일값과 대조하는 검사는 0건 (grep -n "numeric\|수치 대조" 해당 함수 범위 0건).
2-2. #40188 사례 수치 대조 (파일: memory/macro-timeseries/*.json, unit=percent, last_updated 2026-09-25, 최신 관측 2026-09-23)
| 시리즈 | #40188 결론 주장 | 파일 최신값 | 1관측 변화 | 5관측(주간) 변화 | 10관측 | 20관측 | 판정 |
|---|---|---|---|---|---|---|---|
| BAMLC0A0CM (US IG) | −15bp (수축 15bp) | 0.77 = 77bp | 0bp | −1bp (0.78→0.77) | −4bp | −3bp | 불일치 (차 14bp) |
| BAMLH0A0HYM2 (US HY) | −10bp (수축 10bp) | 2.73 = 273bp | +5bp | +3bp (2.70→2.73) | +2bp | +6bp | 불일치 (방향도 반대) |
| BAMLEMCBPIOAS (EM) | −20bp ("확장"이라 서술) | 1.31 = 131bp | −4bp | −3bp (1.34→1.31) | −4bp | −9bp | 불일치 |
-
40188 원문 [데이터] 표는 IG OAS를 "−0.25"로 적었고, 검증 줄에
print(d.get('latest_value')) → -0.25 (exit code 0)라고 썼으나 파일에는latest_value키가 존재하지 않는다(키 목록: series_id, name, source, category, frequency, unit, last_updated, data). 즉 검증 출력 자체가 조작/환각이다 — 그대로 실행하면None이 나온다. - 게이트 재현 (워커 로그
~/.hermes/logs/agent-queue-macro.log177658행의 전체 note로):completion_required_evidence_gaps(full_note) = [],_completion_has_macro_timeseries_collector_evidence(full_note) = True(canonical·worker 동일). → macro-timeseries 조기통과로 done. (DB에 저장된 잘린 note로 돌리면truncated_unverifiable_evidence가 나오지만 이는 저장 후 잘림 때문이며 판정 시점엔 full note가 쓰임.) - 정정본은 이미 존재:
/Users/ron/knowledge-agent/400-reports/260927_hermes_us-credit-spread-weekly.md(IG −1bp / HY +3bp / EM −3bp, 16:09 KST).
2-3. 백테스트 (워커 로그 전수, 분석용 임시 스크립트 — 워크스페이스 미반영)
- macro-timeseries 인용 done 노트: 18건 (analyst-macro 16, ron 1, codex 1)
- 결론에 bp/%/%p 수치 포함: 14건
- 대조 가능 수치 26개 / 시리즈 매핑 모호로 skip 31개 → 불일치 13개(50%)
- 불일치 노트 6건:
-
40188: IG 15bp·HY 10bp·EM −20bp 변화 → 파일 d5 −1/+3/−3bp. 진짜 오류
-
40050/#40051: "US IG OAS −7bp 조정 후 약 2.38% 수준" → 파일 77~81bp 범위, 2.38%는 IG가 아님(HY 근처 수준 착오). 진짜 오류
-
40036: "IG 77bp, HY 273bp, EM 131bp, HY−IG 196bp, EM−IG 54bp" → 수치는 모두 맞음. 한 절에 여러 시리즈/파생 스프레드가 섞여 IG에 오매핑됨. 파서 오탐
-
39942: HY 276→268 −8bp (09-22 기준) → 당시 파일로는 맞았을 가능성. 스테일 오탐 → 2순위 기준일 필드로 해소
-
39831: "IG 올인 금리 81bp" → 올인 금리는 OAS가 아님(용어 오류). 경계 사례
- 결론: 규칙은 필요하지만 단순 절 단위 매핑은 오탐 50% 수준. 아래 §3 설계에서 매핑을 "라벨 인접(±12자) + 파생 스프레드 제외 + 기준일 절단"으로 좁히고, 초기 2주는 shadow 모드로 운영한다.
2-4. 대상 파일 모수
memory/macro-timeseries/JSON 51개, 전부 dict +data:[{date,value}]+unit: percent구조(일괄 파싱 검증). 형식이 균일해 스키마 분기 불필요.
3. 규칙 설계 (codex 구현 명세)
3-1. 발동 조건 (모두 충족 시에만 검사, 아니면 기존 로직 그대로)
- 제출 에이전트 ∈ {analyst-macro, analyst-technical, analyst-fundamental, data-analyst} (analyst-* 전체 + data-analyst). ron/codex/cowork는 1차 제외 — 백테스트상 ron/codex 인용 2건은 결론에 수치 없음.
_completion_block_reasons(3523행)는 agent 인자가 없으므로 호출부(4186/4578/4959행)에서 agent를 넘기거나, 별도 함수_numeric_claim_gaps(raw, agent)를 호출부에서 추가 호출한다. - 노트가
memory/macro-timeseries/<SERIES>.json을 인용하고 그 파일이 실존. 결론:줄(첫 줄 한정)에[+−-]?\d+(\.\d+)?\s*(bp|%p|%)수치가 1개 이상.
3-2. 수치 → 시리즈 매핑 (오탐 억제가 핵심)
- 시리즈 별칭표를 상수로 둔다:
BAMLC0A0CM: IG|투자등급,BAMLH0A0HYM2: HY|하이일드,BAMLEMCBPIOAS: EM|신흥국… (51개 중 인용 빈도 상위부터; 표에 없는 시리즈는 skip). - 수치 앞 12자 이내에 별칭이 정확히 1개일 때만 그 시리즈로 매핑. "각각" 구문은 별칭 순서 ↔ 수치 순서 1:1 zip.
HY−IG,EM−IG,격차,차이,올인,금리,수익률이 수치 앞 12자 안에 있으면 파생/다른 지표로 보고 skip (#40036·#39831 오탐 제거).- 매핑 실패 수치는 skip(반려 사유 아님). skip만 있고 대조 0건이면 통과.
3-3. 대조 기준값
- 기준일 D: 노트에
기준일: YYYY-MM-DD가 있으면 그 날짜, 없으면completed_at날짜. 파일data를date <= D로 자른 뒤 계산(스테일 오탐 제거). - 단위: 파일
unit=percent→ ×100으로 bp 환산. 주장 단위%/%p이고 |x|<20이면 ×100, bp는 그대로. - 변화 주장 판정: 수치에 부호가 있거나, 절에
축소|수축|하락|좁혀|확대|상승|벌어|주간|전주|대비|변화가 있고 |주장| < 최신수준의 50%. - 후보 = {최신 − N관측 전 : N ∈ {1, 5, 10, 20}}.
- 부호 없음 + 방향어(
축소/수축/하락/좁혀→음,확대/상승/벌어→양)면 방향어로 부호 결정. 방향어 없으면 ±둘 다 허용. - 수준 주장 판정: 그 외. 후보 = 최근 6관측 값의 [min, max].
3-4. 허용 오차
- 변화: |주장 − 후보| ≤ max(2bp, |후보|×20%) 인 후보가 하나라도 있으면 일치. (파일 해상도 0.01%=1bp이고 반올림·영업일 계산 차이 1관측을 흡수하는 폭. #40188 IG는 차 14bp로 확실히 걸리고, #40137 "−1bp"·#40161 "+0.15bp"는 통과.)
- 수준: 주장값이 [min−2bp, max+2bp] 안이면 일치.
- 부호/방향 불일치(주장 −10bp vs 모든 후보 ≥ 0 등)는 크기가 오차 안이어도 불일치로 본다 (#40188 HY).
3-5. 반려 라벨과 동작
| 라벨 | 조건 | 동작 |
|---|---|---|
numeric_claim_mismatch |
대조 가능 수치 중 1개 이상 불일치 | hard block (done→failed/needs_evidence). 반려 note에 series / claimed / expected(d1,d5,d10,d20 또는 level range) / 기준일을 한 줄씩 기록 |
numeric_claim_field_hallucination |
검증 줄의 d.get('<key>') 키가 파일 최상위 키에 없음 (#40188 latest_value) |
hard block — 실행하면 None이 나오는 검증 명령을 적은 것 |
numeric_claim_period_unstated |
변화 주장이 있는데 기준일:/구간: 필드 없음 |
soft(로그/경고만, 반려 아님) — 2순위 정책 정착 후 재검토 |
- 삽입 위치: 두 함수의 macro-timeseries 조기통과 직전(
agent_queue_worker.py:1575,1669,public_result_formatter.py:3145). 조기통과 뒤에 넣으면 도달 불가. 구현은 canonical(public_result_formatter.py)에 두고 worker는 import 위임(R24 방침과 동일 — 상수 두 벌 금지). - 롤아웃: 1단계 shadow 2주(라벨 계산해 로그
numeric_claim_shadow만 남기고 차단 안 함) → 오탐률 확인 후 2단계 hard block.
4. [리스크]
- 오탐 → 정상 보고 반려 연쇄: 백테스트에서 단순 매핑은 13개 불일치 중 최소 3개가 오탐(#40036 3수치, #39942 2수치). 오탐이 반려→재작업→LLM 인프라 재부하(오늘 #40184, #40191 모두 LLM 타임아웃 실패)를 키운다. → shadow 모드 + 12자 인접 매핑 + 파생 스프레드 skip으로 억제.
- 조기통과 경로 이중화(단일 장애점): macro-timeseries 조기통과가 worker 2곳 + canonical 1곳 총 3곳이다. 한 곳만 고치면 다른 경로로 그대로 통과(R24/R29에서 반복된 비대칭 패턴). → canonical 한 곳 구현 + worker 3곳에서 호출, pytest로 3경로 모두 검증.
- DB 저장 note 잘림:
bus_commands.result_note는 잘려 저장(#40188 DB note는 "scripts/pipeline/marke..."로 끊김). 게이트는 반드시 판정 시점 full note로 돌아야 하며, 사후 감사 스크립트는 워커 로그에서 full note를 복원해야 한다. - 파일 갱신 타이밍: FRED 반영 지연(현재 last_updated 09-25지만 최신 관측 09-23). 기준일 절단 없이 대조하면 과거 보고가 사후에 불일치로 보인다. → 기준일=completed_at 절단 필수.
- 적용 범위 한정: macro-timeseries JSON 외(price-history, 섹터 사이클 JSON, 외사 리포트 수치)는 이번 규칙 밖이다. 같은 유형의 허위 수치가 다른 소스에서 나오면 여전히 통과한다. 스키마가 균일한 macro-timeseries로 먼저 검증 후 확장.
5. [검증방법]
- 단위 테스트(codex 작성):
tests/test_numeric_claim_gate.py -
40188 full note(워커 로그 177658행) →
numeric_claim_mismatch포함,numeric_claim_field_hallucination포함. 기대값: gaps ≠ [] -
40137 note(IG 77/78/80bp, ±1bp) → gaps에 numeric 라벨 없음
-
40036 note(HY−IG 196bp, EM−IG 54bp) → 파생 스프레드 skip, numeric 라벨 없음
-
40050 note(IG −7bp, 2.38%) →
numeric_claim_mismatch - canonical·worker 1575·1669 세 경로 모두 동일 결과
- 명령:
python3 -m pytest tests/test_numeric_claim_gate.py tests/test_completion_evidence_line_quote_gate.py -q→ 기존 테스트 회귀 0건 - 운영 KPI(shadow 2주):
- 측정: 워커 로그
numeric_claim_shadow건수, 그중 사람이 확인한 진짜 불일치 비율 - 합격: 오탐률 ≤ 10% 그리고 진짜 불일치 포착 ≥ 1건 → hard block 전환
- 롤백: hard block 후 1주 내 analyst-macro done 비율이 직전 7일 대비 30%p 이상 하락하거나, 오탐 반려 3건 이상 → shadow로 복귀
- 반영 확인(v35/v36 정책): 수정 후 워커 재시작 시각 > 파일 mtime 확인(
ps -o lstartvsstat), 재시작은 guardian 승인 태스크 경유.
6. codex 위임 태스크 초안
제목: public_result_formatter.py completion_required_evidence_gaps에 macro-timeseries 결론 수치 대조(numeric_claim_mismatch) 추가 — shadow 모드
본문 요지: 본 문서 §3 명세 구현, 삽입 위치 public_result_formatter.py:3145 직전 + agent_queue_worker.py:1575/1669 직전 호출, 에이전트 인자는 _completion_block_reasons 호출부(4186/4578/4959행)에서 전달, §5 테스트 5종 작성, 기본값 NUMERIC_CLAIM_GATE_MODE=shadow.