From d5f4fd719845f4c2a8469a4a2837bac105816b5c Mon Sep 17 00:00:00 2001 From: OoO Date: Wed, 29 Apr 2026 23:57:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5=20AI=20Smoke=20=E6=AF=8F?= =?UTF-8?q?=E6=97=A5=E6=91=98=E8=A6=81=E6=8E=A8=E6=92=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONSTITUTION.md | 2 +- TODO_NEXT_STEPS.txt | 5 +- app.py | 6 +-- config.py | 2 +- docs/AI_INTELLIGENCE_MODULE_SOT.md | 5 +- docs/adr/ADR-012-agent-action-ladder.md | 3 +- docs/guides/ai_automation_session_sop.md | 1 + docs/memory/ai_automation_closure_20260429.md | 3 ++ docs/memory/history_logs.md | 1 + routes/system_public_routes.py | 10 ++++ run_scheduler.py | 6 ++- scheduler.py | 32 ++++++++++++ services/ai_automation_smoke_service.py | 50 +++++++++++++++++++ templates/ai_automation_smoke.html | 17 +++++++ tests/test_ai_automation_smoke_service.py | 40 +++++++++++++++ 15 files changed, 172 insertions(+), 11 deletions(-) diff --git a/CONSTITUTION.md b/CONSTITUTION.md index ed1c264..e073b3c 100644 --- a/CONSTITUTION.md +++ b/CONSTITUTION.md @@ -2,7 +2,7 @@ > 本文件定義專案開發的核心準則與不可違反的規範 > **建立日期**: 2026-01-12 -> **當前版本**: V10.8 (四 AI Agent 自動化 Smoke 趨勢管理版) +> **當前版本**: V10.9 (四 AI Agent 自動化 Smoke 每日摘要版) > **最後更新**: 2026-04-29 --- diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index d4be781..dbcd5cc 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -7,16 +7,17 @@ - ADR-018:四 AI Agent 自動化控制面立案。 - Memory:新增 `docs/memory/ai_automation_closure_20260429.md`。 - Guide/Skills 替代:新增 `docs/guides/ai_automation_session_sop.md`。 - - SOT:更新 `docs/AI_INTELLIGENCE_MODULE_SOT.md` 至 V10.8 AI Automation Smoke Management 架構。 + - SOT:更新 `docs/AI_INTELLIGENCE_MODULE_SOT.md` 至 V10.9 AI Automation Smoke Summary 架構。 - Codex 規則:更新 `AGENTS.md`、`CONSTITUTION.md`、ADR/memory 索引。 - Prometheus 指標化:新增 EventRouter / AutoHeal / safe action / replay in-process metrics,並接入 `/metrics`。 - 線上 smoke dashboard:新增 `/ai_automation_smoke` 與 `/api/ai-automation/smoke`,覆蓋 EventRouter、AutoHeal、NemoTron fallback、OpenClaw embedding queue、ElephantAlpha HITL。 - Smoke 趨勢保存:`/api/ai-automation/smoke` 每次快檢追加 JSONL 精簡紀錄,dashboard 顯示最近趨勢。 - Smoke 趨勢管理:新增 JSONL 匯出、清理與每日摘要。 + - Smoke 每日摘要:新增 Telegram 手動推播 API 與 momo-scheduler 每日 09:10 排程入口。 【下次待辦】 - Superset / Grafana 視覺化:`momo_ai_event_router_dispatch_total`、`momo_ai_event_router_latency_ms_*`、`momo_ai_autoheal_action_total`。 - - Smoke trend 增加每日摘要 Telegram 推播或排程摘要。 + - Grafana/Superset panel 設定與 Smoke 摘要成效觀察。 ================================================================================ 品牌資產最終處理與維護 (Phase 7) [DONE] diff --git a/app.py b/app.py index c27784f..f65340d 100644 --- a/app.py +++ b/app.py @@ -95,9 +95,9 @@ except Exception as e: sys_log.error(f"無法檢測磁碟空間: {e}") # 🚩 系統版本定義 (備份與顯示用) -# 🚩 2026-04-29 V10.8: AI Smoke 趨勢管理 — JSONL 匯出 / 清理 / -# 每日摘要接入 dashboard -SYSTEM_VERSION = "V10.8" +# 🚩 2026-04-29 V10.9: AI Smoke 每日摘要 — Telegram 手動推播 / +# scheduler 09:10 排程入口 +SYSTEM_VERSION = "V10.9" # ========================================== # 🔒 SQL Injection 防護函數 diff --git a/config.py b/config.py index 8d75be7..5fbf508 100644 --- a/config.py +++ b/config.py @@ -253,7 +253,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.8" +SYSTEM_VERSION = "V10.9" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index c03c9b0..4c89d96 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -1,8 +1,8 @@ # MOMO PRO — AI 競價情報模組 Single Source of Truth > **最後更新**: 2026-04-29 (台北時間) -> **狀態**: 🟢 四 AI Agent 自動化閉環已落地 — EventRouter / AutoHeal / OpenClaw Memory / ElephantAlpha bridge / Prometheus metrics / Smoke Dashboard / Smoke Trend Management 具測試覆蓋 -> **適用版本**: V10.8 AI Automation Smoke Management 架構 +> **狀態**: 🟢 四 AI Agent 自動化閉環已落地 — EventRouter / AutoHeal / OpenClaw Memory / ElephantAlpha bridge / Prometheus metrics / Smoke Dashboard / Smoke Trend Management / Telegram Summary 具測試覆蓋 +> **適用版本**: V10.9 AI Automation Smoke Summary 架構 --- @@ -66,6 +66,7 @@ SQL漏斗(~300筆) - `/api/ai-automation/smoke` 提供 read-only JSON 狀態,不做外部網路呼叫。 - Smoke API 會將最近快檢結果保存到 JSONL,dashboard 顯示最近狀態趨勢。 - Smoke history 支援 JSONL 匯出、清理與每日 OK / Warning / Critical 摘要。 +- Smoke 每日摘要支援手動 Telegram 推播,並由 `momo-scheduler` 每日 09:10 呼叫 `run_ai_smoke_daily_summary_task()`。 --- diff --git a/docs/adr/ADR-012-agent-action-ladder.md b/docs/adr/ADR-012-agent-action-ladder.md index e0ec642..a0a735d 100644 --- a/docs/adr/ADR-012-agent-action-ladder.md +++ b/docs/adr/ADR-012-agent-action-ladder.md @@ -147,7 +147,8 @@ L1 Hermes 掛 → L0 模板直出 + 🟡 「AI 分析暫不可用」 - 2026-04-29 已補 `/ai_automation_smoke` 與 `/api/ai-automation/smoke`:EventRouter、AutoHeal、NemoTron fallback、OpenClaw embedding queue、ElephantAlpha HITL 線上快檢。 - 2026-04-29 已補 smoke 結果 JSONL 保存與 dashboard 趨勢視覺化。 - 2026-04-29 已補 smoke history JSONL 匯出、清理與每日摘要。 -- 尚未完成:Grafana/Superset 視覺化面板與每日摘要 Telegram 推播。 +- 2026-04-29 已補 smoke 每日摘要 Telegram 手動推播與 momo-scheduler 09:10 排程入口。 +- 尚未完成:Grafana/Superset 視覺化面板與推播成效觀察。 ## References - `services/event_router.py` — 分流入口(Phase 1) diff --git a/docs/guides/ai_automation_session_sop.md b/docs/guides/ai_automation_session_sop.md index 2396735..c19e333 100644 --- a/docs/guides/ai_automation_session_sop.md +++ b/docs/guides/ai_automation_session_sop.md @@ -28,6 +28,7 @@ - AI 自動化閉環變更必須確認 `/api/ai-automation/smoke` 與 `/ai_automation_smoke` 仍能反映新狀態。 - Smoke dashboard 會保存 JSONL 趨勢;若新增檢查項目,要確保 history compact record 仍保持小而可讀。 - Smoke history 管理只能操作 `MOMO_AI_AUTOMATION_SMOKE_HISTORY` 指向的 JSONL,不得清理 DB 或 EventRouter queue。 +- Smoke 每日摘要推播只讀 history,不得重新執行 smoke,也不得把完整 details 寫進 Telegram。 - L2 action 必須在 `SAFE_ACTIONS` 且可審計、可回放、低副作用。 - AutoHeal 不得 restart / stop / recreate `momo-db` 或 `momo-postgres`。 - raw `ai_insights` 寫入後必須 enqueue embedding;若 enqueue 失敗,必須可 backfill。 diff --git a/docs/memory/ai_automation_closure_20260429.md b/docs/memory/ai_automation_closure_20260429.md index e7eaba5..2bc82f7 100644 --- a/docs/memory/ai_automation_closure_20260429.md +++ b/docs/memory/ai_automation_closure_20260429.md @@ -13,6 +13,7 @@ - 線上 smoke dashboard 已接入 `/ai_automation_smoke`,JSON API 為 `/api/ai-automation/smoke`。 - Smoke API 會保存最近快檢 JSONL 趨勢,dashboard 顯示 OK / Warning / Critical 最近分布。 - Smoke history 已支援 JSONL 匯出、清理與每日摘要;清理只影響 smoke history,不碰 DB 或 EventRouter queue。 +- Smoke 每日摘要已支援手動 Telegram 推播與 scheduler 09:10 排程入口;摘要只讀 JSONL history。 ## 已落地範圍 @@ -28,6 +29,7 @@ - Smoke dashboard read-only 檢查 EventRouter queue、AutoHeal protected resources、NemoTron fallback、OpenClaw embedding queue、ElephantAlpha HITL,不做外部網路呼叫。 - Smoke history 只保存精簡紀錄,不保存完整 details,避免長期檔案膨脹與敏感資訊堆積。 - Export API 回傳 `application/x-ndjson`,clear API 只刪除 `MOMO_AI_AUTOMATION_SMOKE_HISTORY` 指向檔案。 +- Daily summary API:`POST /api/ai-automation/smoke/daily-summary/send`。 ## 驗證紀錄 @@ -35,6 +37,7 @@ - 2026-04-29 AI smoke dashboard 批次:`2 passed`(單檔 smoke service),後續核心組需持續納入。 - 2026-04-29 AI smoke trend 批次:`5 passed`(smoke + metrics)。 - 2026-04-29 AI smoke management 批次:`7 passed`(smoke + metrics)。 +- 2026-04-29 AI smoke summary 批次:`9 passed`(smoke + metrics)。 - 2026-04-29 L2 安全記憶批次:`24 passed`。 - collect-only:`48 tests collected`。 - `git diff --check` 已通過。 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index e70a06e..359f4ad 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -28,6 +28,7 @@ - **Smoke Dashboard**: 新增 `/ai_automation_smoke` 與 `/api/ai-automation/smoke`,提供四 Agent 閉環 read-only 快檢。 - **Smoke 趨勢保存**: Smoke API 追加 JSONL 精簡紀錄,dashboard 顯示最近 OK / Warning / Critical 趨勢。 - **Smoke 趨勢管理**: Dashboard 增加 JSONL 匯出、清理與每日摘要,清理範圍限定 smoke history 檔。 +- **Smoke 每日摘要推播**: 新增 Telegram 手動推播 API 與 momo-scheduler 每日 09:10 摘要任務,只讀 smoke history。 ### 2026-04-28~29:Phase 3e 重構大戰 + daily_sales cache 隱形 bug 根除 - **app.py 縮減 -10.8%**: 7,386 → 6,590 行,11 commits 全綠零 502。 diff --git a/routes/system_public_routes.py b/routes/system_public_routes.py index d5144ce..6b02de9 100644 --- a/routes/system_public_routes.py +++ b/routes/system_public_routes.py @@ -231,6 +231,16 @@ def ai_automation_smoke_history_clear(): return jsonify(clear_smoke_history()) +@system_public_bp.route('/api/ai-automation/smoke/daily-summary/send', methods=['POST']) +@login_required +def ai_automation_smoke_daily_summary_send(): + """Send compact smoke trend daily summary to Telegram.""" + from services.ai_automation_smoke_service import send_smoke_daily_summary + result = send_smoke_daily_summary() + status_code = 200 if result.get("status") == "sent" else 502 + return jsonify(result), status_code + + @system_public_bp.route('/logs') def show_logs(): return render_template('logs.html') diff --git a/run_scheduler.py b/run_scheduler.py index e50e4b7..e5288ce 100644 --- a/run_scheduler.py +++ b/run_scheduler.py @@ -8,7 +8,7 @@ run_scheduler.py — momo-scheduler 容器入口點 每 4 小時:competitor_price_feeder、icaim_analysis 每 6 小時:openclaw_meta_analysis、quality_rescore 每 12 小時:dedup_batch - 每 1 天 :db_backup(03:00)、cleanup_agent_context(03:30)、backup_monitor(04:00)、daily_report(09:00) + 每 1 天 :db_backup(03:00)、cleanup_agent_context(03:30)、backup_monitor(04:00)、daily_report(09:00)、ai_smoke_summary(09:10) 每 1 週 :weekly_strategy(週一 06:00) 每 1 月 :monthly_report(每月1日 07:00) """ @@ -36,6 +36,7 @@ from scheduler import ( run_dedup_batch_task, run_quality_rescore_task, run_daily_report_task, + run_ai_smoke_daily_summary_task, run_monthly_report_task, ) @@ -116,6 +117,9 @@ def _register_schedules(): schedule.every().day.at("09:00").do(run_daily_report_task) logger.info("📅 每日 09:00:daily_report") + schedule.every().day.at("09:10").do(run_ai_smoke_daily_summary_task) + logger.info("📅 每日 09:10:ai_smoke_daily_summary") + # 每月1日 07:00 月報(schedule 不支援 every().month,用每日 07:00 + 日期判斷) def _monthly_report_gate(): from datetime import datetime as _dt diff --git a/scheduler.py b/scheduler.py index 14c4dcd..61571d2 100644 --- a/scheduler.py +++ b/scheduler.py @@ -2566,6 +2566,38 @@ def run_monthly_report_task(): logging.error(f"[Scheduler] [MonthlyReport] auto_heal_service 失敗: {_heal_e}") +def run_ai_smoke_daily_summary_task(): + """每日 AI 自動化 Smoke trend 摘要推播(只讀 history,不重新執行 smoke)。""" + try: + from services.ai_automation_smoke_service import send_smoke_daily_summary + result = send_smoke_daily_summary() + logging.info( + "[Scheduler] [AISmokeSummary] 完成 | status=%s sent=%s failed=%s", + result.get("status"), + result.get("telegram", {}).get("sent", 0), + result.get("telegram", {}).get("failed", 0), + ) + _save_stats('ai_smoke_daily_summary', result) + except Exception as e: + import traceback as _tb + logging.error(f"[Scheduler] [AISmokeSummary] 🚨 摘要推播異常: {e}") + _save_stats('ai_smoke_daily_summary', {"status": "Error", "error": str(e)}) + try: + from services.event_router import notify_failure + notify_failure( + task_name="run_ai_smoke_daily_summary_task", + error=e, + source="Scheduler.AISmokeSummary", + event_type="ai_smoke_summary_failure", + priority="P2", + title="AI Smoke 每日摘要推播異常", + trace=_tb.format_exc(), + dedup_ttl_sec=3600, + ) + except Exception as _router_e: + logging.error(f"[Scheduler] [AISmokeSummary] event_router 失敗: {_router_e}") + + if __name__ == "__main__": # 此檔案現在由 app.py 導入並由其主執行緒管理排程。 # 若需獨立測試,可在此處臨時加入調用程式碼。 diff --git a/services/ai_automation_smoke_service.py b/services/ai_automation_smoke_service.py index 2ee1a8d..44a51e9 100644 --- a/services/ai_automation_smoke_service.py +++ b/services/ai_automation_smoke_service.py @@ -10,6 +10,7 @@ import os import json import threading from datetime import datetime +from html import escape from typing import Any, Dict, List from sqlalchemy import text @@ -156,6 +157,55 @@ def clear_smoke_history() -> Dict[str, Any]: return {"cleared": 0, "path": _HISTORY_PATH, "error": str(exc)[:300]} +def build_smoke_daily_summary_message(history_limit: int = 200) -> str: + records = _load_history(limit=history_limit) + summary = _history_summary(records) + daily_rows = summary.get("daily", []) + latest = summary.get("latest") or {} + counts = summary.get("counts") or {"ok": 0, "warning": 0, "critical": 0} + + lines = [ + "🤖 AI 自動化 Smoke 每日摘要", + "━━━━━━━━━━━━━━━━━━━━", + f"版本:{escape(SYSTEM_VERSION)}", + f"最近狀態:{escape(str(latest.get('status', 'no_data')).upper())}", + f"最近時間:{escape(str(latest.get('generated_at', '尚無紀錄')))}", + "", + "📊 近期統計", + f"✅ OK:{counts.get('ok', 0)}", + f"⚠️ Warning:{counts.get('warning', 0)}", + f"🚨 Critical:{counts.get('critical', 0)}", + ] + + if daily_rows: + lines += ["", "🗓️ 最近每日摘要"] + for row in daily_rows[-5:]: + lines.append( + f"{escape(str(row.get('date')))}|" + f"OK {row.get('ok', 0)} / W {row.get('warning', 0)} / C {row.get('critical', 0)}" + ) + else: + lines += ["", "🗓️ 尚無 smoke history;請先開啟 /ai_automation_smoke 執行快檢。"] + + lines += [ + "━━━━━━━━━━━━━━━━━━━━", + "入口:/ai_automation_smoke", + ] + return "\n".join(lines) + + +def send_smoke_daily_summary(chat_ids: List[Any] | None = None) -> Dict[str, Any]: + from services.telegram_templates import send_telegram_with_result + + message = build_smoke_daily_summary_message() + result = send_telegram_with_result(message, chat_ids=chat_ids) + return { + "status": "sent" if result.get("ok") else "failed", + "telegram": result, + "message_preview": message[:500], + } + + def _event_router_check() -> Dict[str, Any]: try: from services import event_router diff --git a/templates/ai_automation_smoke.html b/templates/ai_automation_smoke.html index e381bb6..d70098d 100644 --- a/templates/ai_automation_smoke.html +++ b/templates/ai_automation_smoke.html @@ -120,6 +120,9 @@ + @@ -292,6 +295,20 @@ document.getElementById('clearHistoryBtn').addEventListener('click', async () => btn.disabled = false; } }); +document.getElementById('sendSummaryBtn').addEventListener('click', async () => { + const btn = document.getElementById('sendSummaryBtn'); + btn.disabled = true; + try { + const res = await fetchWithCSRF('/api/ai-automation/smoke/daily-summary/send', {method: 'POST'}); + const data = await res.json(); + if (!res.ok) throw new Error((data.telegram?.errors || []).join(', ') || `HTTP ${res.status}`); + showToast(`AI Smoke 每日摘要已推播:sent=${data.telegram?.sent || 0}`, 'success'); + } catch (err) { + showToast(`推播失敗:${err.message}`, 'error'); + } finally { + btn.disabled = false; + } +}); loadSmoke(); {% endblock %} diff --git a/tests/test_ai_automation_smoke_service.py b/tests/test_ai_automation_smoke_service.py index 4880d29..dd1d203 100644 --- a/tests/test_ai_automation_smoke_service.py +++ b/tests/test_ai_automation_smoke_service.py @@ -93,3 +93,43 @@ def test_smoke_history_daily_summary(): {"date": "2026-04-28", "ok": 1, "warning": 0, "critical": 0, "total": 1}, {"date": "2026-04-29", "ok": 0, "warning": 1, "critical": 1, "total": 2}, ] + + +def test_build_smoke_daily_summary_message_escapes_history(tmp_path, monkeypatch): + from services import ai_automation_smoke_service as smoke + + history_path = tmp_path / "smoke_history.jsonl" + history_path.write_text( + '{"generated_at":"2026-04-29T01:00:00","status":"warning",' + '"summary":{"ok":0,"warning":1,"critical":0,"total":1},' + '"checks":[{"name":"