feat(telegram): ADR-019 Phase 6 - daily data freshness probe + cron 09:05
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

ADR-019 Phase 6:每日 09:05 OpenClawBot scheduler 主動巡檢 realtime_sales_monthly
最新資料日期,落後超過閾值時透過 EventRouter 發 Telegram 警告。

新增 services/data_freshness_probe.py:
- gap == 0:靜默不發(資料齊全)
- gap == 1:info(昨日資料齊,正常)
- gap == 2~3:warning
- gap >= 4:alert(P2,ETL 大概率出問題)
- latest_date 取不到:alert(DB 連線異常)

routes/openclaw_bot_routes.py 加 cron job openclaw_data_freshness_probe
(hour=9 minute=5,避開 09:00 的其他既有 job)。

從『用戶月初按按鈕踩坑才發現資料缺口』的被動模式,轉成 agent 主動巡檢通知。
配合 Phase 1(PPT freshness gate)+ Phase 2(agent tool)+ Phase 3(cmd 路徑
agent dispatch)+ Phase 4(對話 state),Telegram Bot 互動層的『rigid default
+ 靜默空白』反模式根除。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
OoO
2026-05-02 13:07:30 +08:00
parent 14195b65fd
commit e12e6a8f96
2 changed files with 140 additions and 1 deletions

View File

@@ -3323,8 +3323,19 @@ def start_scheduler():
id="openclaw_check_anomalies",
replace_existing=True,
)
# ADR-019 Phase 6: 每日 09:00 主動巡檢資料新鮮度,缺資料時透過 EventRouter 發警告
try:
from services.data_freshness_probe import run_data_freshness_probe
_scheduler.add_job(
run_data_freshness_probe,
CronTrigger(hour=9, minute=5),
id="openclaw_data_freshness_probe",
replace_existing=True,
)
except ImportError as _e:
sys_log.warning(f"[OpenClawBot] data_freshness_probe 未安裝,跳過:{_e}")
_scheduler.start()
sys_log.info("[OpenClawBot] Scheduler started ✓ (competitor/morning/excel/evening/weekly/anomaly)")
sys_log.info("[OpenClawBot] Scheduler started ✓ (competitor/morning/excel/evening/weekly/anomaly/freshness)")
except ImportError:
sys_log.warning("[OpenClawBot] APScheduler 未安裝 — 排程功能停用")
except Exception as e:

View File

@@ -0,0 +1,128 @@
"""ADR-019 Phase 6每日資料新鮮度主動巡檢。
每天 09:00 由 OpenClawBot scheduler 觸發,檢查 realtime_sales_monthly 最新資料
日期,若落後超過閾值就透過 EventRouter 主動發 Telegram 警告,避免用戶月初/
ETL 卡住才靠按按鈕踩坑發現。
EventRouter severity 對應:
- gap == 0今日資料已進靜默不發
- gap == 1昨日資料齊全info不發或只 log
- gap == 2~3warning可能 ETL 跑慢
- gap >= 4alertP2ETL 大概率出問題
"""
from __future__ import annotations
from datetime import datetime, date
import logging
from services.event_router import dispatch_sync
logger = logging.getLogger("DataFreshnessProbe")
_DEFAULT_WARN_GAP_DAYS = 2
_DEFAULT_ALERT_GAP_DAYS = 4
def _query_latest_date():
"""獨立 import 避免循環依賴。回傳 'YYYY-MM-DD' 字串或 None。"""
try:
from routes.openclaw_bot_routes import latest_date
return latest_date()
except Exception as e:
logger.warning(f"[DataFreshnessProbe] 取得 latest_date 失敗:{e}")
return None
def _parse_date(s: str) -> date | None:
if not s:
return None
try:
return datetime.strptime(s.replace('/', '-'), '%Y-%m-%d').date()
except (ValueError, AttributeError):
return None
def run_data_freshness_probe(
warn_gap_days: int = _DEFAULT_WARN_GAP_DAYS,
alert_gap_days: int = _DEFAULT_ALERT_GAP_DAYS,
) -> dict:
"""主動巡檢一次,缺資料時透過 EventRouter 發通知。回傳 probe 結果摘要。"""
today = date.today()
latest_str = _query_latest_date()
latest_dt = _parse_date(latest_str)
result = {
'today': today.isoformat(),
'latest_date': latest_str,
'gap_days': None,
'severity': 'unknown',
'notified': False,
}
if latest_dt is None:
logger.error("[DataFreshnessProbe] 無法取得 latest_date — 視為嚴重異常")
result['severity'] = 'alert'
try:
dispatch_sync({
'source': 'OpenClawBot.DataFreshnessProbe',
'event_type': 'data_freshness_unknown',
'severity': 'alert',
'title': '⚠️ 資料新鮮度巡檢失敗',
'status': '無法取得 latest_date',
'impact': 'P2 — 業績資料源不可達,所有報表 / NL 查詢可能失準',
'summary': '請檢查 realtime_sales_monthly 表與 DB 連線。',
})
result['notified'] = True
except Exception as e:
logger.error(f"[DataFreshnessProbe] EventRouter dispatch 失敗:{e}")
return result
gap = (today - latest_dt).days
result['gap_days'] = gap
if gap <= 0:
result['severity'] = 'success'
logger.info(f"[DataFreshnessProbe] 資料齊全latest={latest_dt}, today={today}")
return result
if gap == 1:
result['severity'] = 'info'
logger.info(f"[DataFreshnessProbe] 落後 1 天正常作息ETL 通常隔天匯入)")
return result
if gap < warn_gap_days:
result['severity'] = 'info'
return result
severity = 'alert' if gap >= alert_gap_days else 'warning'
result['severity'] = severity
try:
dispatch_sync({
'source': 'OpenClawBot.DataFreshnessProbe',
'event_type': 'data_freshness_lag',
'severity': severity,
'title': f'📊 業績資料落後 {gap}',
'status': f'最新資料:{latest_str}(今日:{today.isoformat()}',
'impact': (
f'P2 — 月報/日報 PPT 與 NL 業績查詢可能空白或失準,'
f'請檢查 realtime_sales_monthly ETL 任務run_momo_task / run_auto_import_task'
if severity == 'alert' else
f'P3 — 資料落後 {gap} 天,請確認 ETL 是否正常排程'
),
'summary': f'gap={gap}d, latest={latest_str}',
'payload': {
'gap_days': gap,
'latest_date': latest_str,
'probe_at': datetime.now().isoformat(timespec='seconds'),
},
})
result['notified'] = True
logger.warning(
f"[DataFreshnessProbe] {severity}: gap={gap}d, latest={latest_str} → notified"
)
except Exception as e:
logger.error(f"[DataFreshnessProbe] EventRouter dispatch 失敗:{e}")
return result