← 리포트 목록
discovery_digest + market_indicator 중복 발송 방지 수정
2026-04-24
discovery
[hermes, telegram, dedup, discovery_digest, market_indicator]
discovery_digest + market_indicator 중복 발송 방지 수정
결론
- 수정 완료:
/Users/ron/.hermes/workspace/scripts/pipeline/discovery_digest.py/Users/ron/.hermes/workspace/scripts/pipeline/market_indicator_tracker.py/Users/ron/.hermes/workspace/scripts/shared/telegram.py— 검증 중 발견된osimport 누락 1줄 보정/Users/ron/.hermes/cron/jobs.json— 내일 새벽 모니터 job 추가- 백업:
discovery_digest.py.bak-dedup-20260424_105240market_indicator_tracker.py.bak-dedup-20260424_105240telegram.py.bak-os-import-20260424_105319jobs.json.bak-dedup-monitor-20260424_105601
근본 원인 한 줄
discovery_digest.py: URL이url이 아니라_url에만 있는 항목을notified_urls에 저장하지 못했고, 병렬 실행 시 state 저장 전 동일 batch가 동시에 발송될 수 있었다.market_indicator_tracker.py: 활성 크론 중복은 없었고, 코드 내부에서send_anomaly_dm()을 무조건 1회 호출한 뒤anomalies가 있으면 같은 함수를 다시 호출해 같은 시장지표를 2회 발송했다.
1. discovery_digest.py 분석 및 수정
확인
- 활성 Hermes job:
ocRESTORE-intelligence-cluster, 매일01:00 KST. - OpenClaw
intelligence_cluster는 disabled. - sector trace에서 같은 호출부가 초 단위로 반복됨:
2026-04-23 01:55:36/37/44 caller=discovery_digest.py:2582026-04-24 01:53:58/58/59 caller=discovery_digest.py:258
원인
기존 필터:
new_discoveries = [d for d in all_discoveries if d.get("url") not in notified_urls]
batch_urls = [d.get("url") for d in batch if d.get("url")]
하지만 extract_url() 결과는 _url에 들어가는 케이스가 있고, 이 경우 발송 후에도 notified_urls에 기록되지 않았다. 실제 4/24 ranto28 블로그 항목이 이 케이스라 dry-run에서 계속 “would be sent”로 남았다.
수정
discovery_identity()추가:url → _url → file → _title → title순서로 canonical identity 생성.batch_fingerprint()+last_sent_fingerprint추가.discovery_digest_state.lock파일락 추가: 병렬 실행 시 state load/filter/send/save를 한 번에 직렬화.- 이미 4/24 01:53에 발송된 현재 항목은 state 보정:
state_repaired_at: 2026-04-24T10:54:30+09:00state_repair_reason기록.
2. market_indicator_tracker.py 분석 및 수정
크론 확인
Hermes live jobs.json에서 market_indicator_tracker.py 활성 job은 1개뿐이다.
ocRESTORE-market-indicator-tracker
schedule: 5 7,9,11,13,15,17 * * 1-5
next_run_at: 2026-04-24T11:05:00+09:00
OpenClaw legacy job은 disabled:
intelligence-market-indicator-tracker enabled=false
원인
기존 notify block:
send_anomaly_dm(...)
if anomalies:
dm_sent = send_anomaly_dm(...)
send_anomaly_dm() 자체가 market 섹터 리포트를 보내는 함수라, anomalies가 있는 날은 같은 리포트가 2회 발송됐다. sector trace의 07:05:35 / 07:05:36 2연속 발송과 일치한다.
수정
send_anomaly_dm()호출을 1회로 축소..dm_sent_today.lock파일락 추가..dm_sent_today.json에report_fingerprint,sent_at저장.- dedup 조건을
date == today또는report_fingerprint동일로 강화.
3. 검증 결과
syntax
python3 -m py_compile shared/telegram.py discovery_digest.py market_indicator_tracker.py
OK
discovery_digest 실제 실행
No new discoveries (all already notified)
sector_trace discovery_digest before=21 after=21
=> 이미 보낸 4/24 항목은 재발송되지 않음.
discovery_digest send-once harness
임시 filtered/state + monkeypatch send_sector로 2회 연속 실행:
Sent 1 discoveries
No new discoveries (all already notified)
send_calls= 1
=> 같은 batch는 1회만 발송.
market_indicator_tracker notify 재실행
Market indicator notification already sent, skipping
sector_trace market_indicator before=6 after=6
{"status": "ok", "dm_sent": false, "charts_sent": 0, ...}
=> 오늘 이미 발송된 시장지표는 재실행해도 추가 발송 없음.
4. 내일 새벽 모니터 트리거
Hermes job 추가:
id: dedup-monitor-discovery-market-20260425
enabled: true
schedule: 10 2 25 4 * Asia/Seoul
next_run_at: 2026-04-25T02:10:00+09:00
output: /Users/ron/knowledge-agent/400-reports/260425_dedup_monitor.log
역할: 4/25 02:10에 sector_trace.log에서 discovery_digest.py와 market_indicator_tracker.py 최근 발송 흔적을 스냅샷으로 남긴다.
5. 남은 리스크
send_sector는 trace를 dedupe 검사 전에 남기므로, sector_trace 한 줄 수와 실제 Telegram 발송 수가 1:1은 아니다. 실제 중복 여부는 이번처럼 state와 send 호출 경로를 같이 봐야 한다.send_document()dedup 미적용은 이번 범위 밖이지만, 이전 감사에서 별도 리스크로 확인됨.
6. 자체평가
- 정확성: 4.7/5 — 로그/코드/크론 직접 확인 후 원인별 수정.
- 완성도: 4.5/5 — 두 파이프라인 모두 중복 방지 추가, 모니터 job 설정.
- 검증: 4.5/5 — py_compile, 실제 재실행 skip, send-once harness 통과.
- 최소 변경: 4.2/5 — 검증 차단 요인으로
shared/telegram.pyimport 1줄 추가가 범위 밖에 가까웠지만 필수 런타임 보정. - 종합: 4.5/5
DONE