virtual-insanity
← 리포트 목록

Pipeline Freshness Audit — bond / coal / oil

2026-04-20 pipeline [freshness, stale-data, bond, coal, oil, pipeline]

Pipeline Freshness Audit — 2026-04-20

결론

  • 가장 큰 문제는 bond PDF: 2026-04-20 14:17에 발송된 PDF가 입력일 2026-04-16 기준인데, 본문은 오늘 본문, 오늘 핵심, 오늘 읽을 리포트라고 표시했다.
  • oil 파이프라인은 캐시/이전 state 폴백을 쓰면서도 본문에 실패 카운트를 노출하지 않았다. 오늘 dry-run에서도 WTI/HH 선물 신규 수집 실패가 재현됐다.
  • coal V11은 보고서 날짜는 2026-04-20이지만 일부 시장 데이터가 2026-04-17/04-16 기준이다. 직전 개장일 데이터일 수 있으나 최신 표현에는 as-of 라벨이 더 명확해야 한다.

DM 전송은 하지 않았다.


A. PDF 내용 대 오늘 날짜 대조

1) bond briefing PDF

대상: /Users/ron/.hermes/workspace/memory/bond-briefing/briefing_2026-04-16.pdf
파일 mtime: 2026-04-20 14:17
추출 텍스트: /tmp/freshness_audit/bond_20260416.txt

사례 PDF 텍스트 라인 실제 기대
bond-1 1~2 채권 트레이더 브리핑 | 2026-04-16 바로 다음에 오늘 본문 2026-04-20 발송이면 ⚠️ 입력 자료 4일 전 헤더가 먼저 있어야 함
bond-2 82, 88~89 오늘 읽을 리포트, 오늘의 판단, 오늘 핵심 입력일(2026-04-16) 기준 판단으로 명시
bond-3 51~54 4/20 월, 4/21 화, 4/23 목 발행 캘린더가 4/20 발송분에 그대로 노출 4/16 기준 캘린더임을 표시하거나 지난 항목은 별도 표시
bond-4 120~123 URL 경로가 2026/04/16, 파일명 260417인데 오늘 브리핑과 직결이라고 표시 리포트 원문 기준일을 함께 표시

판정: stale confirmed. 4일 지난 입력 자료가 오늘 자료처럼 읽히는 구조였다.

2) coal V11 PDF

대상: /Users/ron/knowledge-agent/400-reports/260420_석탄_RE-APPROACH_V11.pdf
파일 mtime: 2026-04-20 13:05
추출 텍스트: /tmp/freshness_audit/coal_v11.txt

사례 PDF 텍스트 라인 실제 기대
coal-1 319, 323, 978 최신 Newcastle 132.30~133.75라고 쓰지만 핵심 수치는 2026-04-17 132.30 기반 직전 확인치(2026-04-17) 또는 시장별 as-of 표기
coal-2 1174, 1178 BTU는 2026-04-17 종가, WHC는 2026-04-20 종가로 혼재 기업 비교 테이블 상단에 미국/호주/홍콩/국내 기준일 분리 표기
coal-3 423~449 4/20 값으로 스프레드를 표시하지만 Newcastle/API2/JKM/HCC의 실제 as-of가 동일한지 PDF 안에서 추적 어려움 스프레드 표에 각 원자료 날짜 컬럼 추가

판정: stale 확정이라기보다 freshness label risk. 시장 휴장/직전 개장일 데이터일 수 있으나, 최신/현재/4/20 값 표현은 날짜 혼재를 숨긴다.

3) oil supply monitor 신규 dry-run 산출물

실행 로그: /tmp/freshness_audit/oil_dryrun_after.log
실행: oil_supply_monitor.py --dry-run --full
텔레그램 발송: 스킵 확인

사례 로그/코드 라인 실제 기대
oil-1 실행 로그 52, 65, 75 WTI 선물 신규 수집 실패 → 이전 state 사용. 패치 후 데이터 수집 부분 실패: 2건 노출 확인 이전 state 사용 시 본문 최상단에 실패 카운트와 대상 노출
oil-2 실행 로그 61, 65, 75 HH 선물 신규 수집 실패 → 이전 state 사용. 패치 후 노출 확인 동일
oil-3 oil_supply_monitor.py:1359-1374 URL cache hit이면 캐시 text를 즉시 반환. 본문에는 cache 기준시각이 없음 오래된 캐시 사용 시 N시간/일 전 캐시 표시 또는 빈칸 처리
oil-4 oil_supply_monitor.py:1952-1987 EIA latest.json이 3일+ stale이면 직접 API를 시도하지만, 실패 시 stale 파일 파싱값이 남을 수 있음 직접 fetch 실패 시 해당 지표에 stale-days를 노출

판정: code-level stale risk confirmed. 오늘 실제 수집 실패가 있었고, 패치 전에는 보고서 본문에 실패 카운트가 없었다.


B. freshness 검증 로직 점검

bond_daily_report.py

문제 위치:

  • /Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py:451-475 — 신규 helper 추가 전에는 입력일/발송일 gap 검사 없음.
  • /Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py:512-513오늘 본문 표현이 stale 리스크를 키움.
  • /Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py:1271-1276 — 최종 report header 조립 시 stale warning 삽입 위치.

근본 원인:

  1. 최신 노트를 파일명 역순으로 고르지만, 발송일과 note date 차이를 검사하지 않았다.
  2. PDF/본문이 입력일 기준임에도 오늘이라는 말을 그대로 사용했다.
  3. PDF 발송 시점이 4/20이어도 제목 날짜가 4/16이면 stale을 감지할 수 있는 표시가 없었다.

oil_supply_monitor.py

문제 위치:

  • /Users/ron/.hermes/workspace/scripts/pipeline/oil_supply_monitor.py:426-435 — WTI futures 수집 실패 시 last_wti_curve를 반환하고 error=None으로 둠.
  • /Users/ron/.hermes/workspace/scripts/pipeline/oil_supply_monitor.py:513-522 — HH futures도 동일.
  • /Users/ron/.hermes/workspace/scripts/pipeline/oil_supply_monitor.py:1359-1374 — URL cache hit 시 timestamp 검증 없이 text 반환.
  • /Users/ron/.hermes/workspace/scripts/pipeline/oil_supply_monitor.py:1952-1987 — EIA stale 파일을 먼저 파싱하고 fresh fetch가 성공하면 덮어씀. fresh fetch 실패 시 stale 값이 남을 수 있다.

근본 원인:

  1. fallback 자체는 안정성에는 좋지만, 보고서에 fallback fact가 올라오지 않았다.
  2. 수집 실패와 이전 state 사용이 stdout 경고에만 남고, 해리가 보는 Telegram/PDF 본문에는 없었다.
  3. 캐시 기준일이 본문에 붙지 않아 시장 지표가 live처럼 보일 수 있다.

C. 적용 패치

1) bond: 입력 자료 3일 초과 시 stale header 강제 삽입

변경 파일: /Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py

적용 내용:

  • _bond_input_freshness_warning() 추가.
  • data['date']와 실행일(KST)을 비교해서 age_days > 3이면 본문 맨 위에 다음 경고 삽입:
  • ⚠️ STALE DATA WARNING — 입력 자료 N일 전(YYYY-MM-DD 기준). 발송일 YYYY-MM-DD.
  • 첫 문장 오늘 본문이 본문은 입력일 기준으로 수정.

핵심 diff:

+def _bond_input_freshness_warning(...):
+    ...
+    if age_days > 3:
+        return "⚠️ STALE DATA WARNING — 입력 자료 ...", age_days

-lines.append("요약부터 말할게. 오늘 본문은 확인된 숫자만 사용했다...")
+lines.append("요약부터 말할게. 이 본문은 입력일 기준 확인된 숫자만 사용했다...")

-header = f"📊 채권 트레이더 브리핑 | {date_str}\n\n"
+stale_warning, stale_age_days = _bond_input_freshness_warning(data, note)
+header = f"📊 채권 트레이더 브리핑 | {date_str}\n\n" + stale_warning

검증 결과:

$ /usr/bin/python3 -m py_compile bond_daily_report.py
OK

$ bond_daily_report.py --dry-run
⚠️ STALE DATA WARNING — 입력 자료 4일 전(2026-04-16 기준). 발송일 2026-04-20.
오늘/현재 표현은 입력일 기준으로만 해석하세요.
요약부터 말할게. 이 본문은 입력일 기준 확인된 숫자만 사용했다.

2) oil: 데이터 수집 실패/폴백 카운트 본문 노출

변경 파일: /Users/ron/.hermes/workspace/scripts/pipeline/oil_supply_monitor.py

적용 내용:

  • _build_collection_freshness() 추가.
  • WTI/HH futures가 fallback_from_state=True이거나 error가 있으면 실패 카운트 증가.
  • format_report() 최상단에 ⚠️ 데이터 수집 부분 실패: N건... 경고 삽입.

핵심 diff:

+def _format_freshness_warning(cl):
+    return "⚠️ <b>데이터 수집 부분 실패</b>: ..."
+
+def _build_collection_freshness(raw, futures, ng_futures):
+    if futures.get("fallback_from_state"):
+        add("WTI 선물커브", "신규 수집 실패 → 이전 state 사용")
+    if ng_futures.get("fallback_from_state"):
+        add("HH 선물커브", "신규 수집 실패 → 이전 state 사용")

+checklist["_freshness"] = _build_collection_freshness(raw, futures, ng_futures)
+freshness_warning = _format_freshness_warning(cl)

검증 결과:

$ /opt/homebrew/bin/python3.11 -m py_compile oil_supply_monitor.py
OK

$ oil_supply_monitor.py --dry-run --full
[freshness] 수집 실패/폴백 2건: WTI 선물커브: 신규 수집 실패 → 이전 state 사용; HH 선물커브: 신규 수집 실패 → 이전 state 사용
⚠️ 데이터 수집 부분 실패: 2건. 일부 지표는 이전 state/cache 기준일 수 있음 — WTI 선물커브: 신규 수집 실패 → 이전 state 사용; HH 선물커브: 신규 수집 실패 → 이전 state 사용
[6/6] 텔레그램 발송...
[dry-run] 발송 스킵

주의: oil dry-run은 기존 스크립트 설계상 Telegram은 보내지 않았지만 500-signals/에너지/260420_oil-supply-snapshot.md는 갱신했다.


D. 패치 후 남은 수정 제안

우선순위 1 — 공통 PDF freshness block

모든 PDF/리포트 상단에 아래 고정 블록을 넣는 공통 helper 필요.

데이터 기준일
- 본문 생성일: YYYY-MM-DD HH:mm KST
- 시장가격 기준: YYYY-MM-DD / 소스
- 입력 노트 기준: YYYY-MM-DD / 파일명
- 3일+ 경과 데이터: N건

적용 후보:

  • bond_daily_report.py
  • build_coal_pdf_v11.py
  • oil_supply_monitor.py
  • 향후 PDF builder 공통 템플릿

우선순위 2 — oil cache age 강제 표시

현재 _cached_fetch()는 cache timestamp를 저장하지만 cache hit 시 age를 본문으로 올리지 않는다. 다음 패치가 필요하다.

-if url in cache:
-    return cache[url].get("text", "")
+if url in cache:
+    age_hours = ...
+    raw["_cache_freshness"].append({"url": url, "age_hours": age_hours})
+    return cache[url].get("text", "")

우선순위 3 — coal PDF as-of matrix

V11은 데이터가 풍부하지만 최신/현재 표현 주변에 시장별 기준일이 완전히 고정되어 있지 않다. 각 가격/기업 표에 as_of 컬럼을 붙여야 한다.

예:

데이터 기준일 소스
Newcastle 132.30 2026-04-17 ICE/front
BTU 25.65 2026-04-17 NYSE close
WHC 7.78 2026-04-20 ASX close

생성/수정 파일

수정:

  • /Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py
  • /Users/ron/.hermes/workspace/scripts/pipeline/oil_supply_monitor.py

백업:

  • /Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py.bak-20260420-freshness
  • /Users/ron/.hermes/workspace/scripts/pipeline/oil_supply_monitor.py.bak-20260420-freshness

감사 산출물:

  • /tmp/freshness_audit/bond_20260416.txt
  • /tmp/freshness_audit/coal_v11.txt
  • /tmp/freshness_audit/bond_dryrun_after.txt
  • /tmp/freshness_audit/oil_dryrun_after.log
  • /tmp/freshness_audit/bond.diff
  • /tmp/freshness_audit/oil.diff

자체평가

  • 정확성: 4.5/5 — 대상 PDF와 코드 경로를 직접 열어 stale 근거를 특정했고 bond/oil에 실제 방지 장치를 넣었다.
  • 완성도: 4.3/5 — bond는 즉시 방지 완료. oil은 WTI/HH fallback은 방지했지만 URL cache age 표시는 다음 패치가 필요하다.
  • 검증: 4.5/5 — py_compile, bond dry-run, oil dry-run 모두 확인. DM 발송 없음.
  • 최소 변경: 4.6/5 — bond/oil 두 파일의 freshness 표시만 변경했다.

종합: 4.5/5