Pipeline Freshness Audit — bond / coal / oil
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 삽입 위치.
근본 원인:
- 최신 노트를 파일명 역순으로 고르지만, 발송일과 note date 차이를 검사하지 않았다.
- PDF/본문이 입력일 기준임에도
오늘이라는 말을 그대로 사용했다. - 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 값이 남을 수 있다.
근본 원인:
- fallback 자체는 안정성에는 좋지만, 보고서에 fallback fact가 올라오지 않았다.
- 수집 실패와 이전 state 사용이
stdout 경고에만 남고, 해리가 보는 Telegram/PDF 본문에는 없었다. - 캐시 기준일이 본문에 붙지 않아 시장 지표가 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.pybuild_coal_pdf_v11.pyoil_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