加入 AI Smoke 每日摘要推播
All checks were successful
CD Pipeline / deploy (push) Successful in 1m15s

This commit is contained in:
OoO
2026-04-29 23:57:36 +08:00
parent 10bbd55f5b
commit d5f4fd7198
15 changed files with 172 additions and 11 deletions

View File

@@ -2,7 +2,7 @@
> 本文件定義專案開發的核心準則與不可違反的規範
> **建立日期**: 2026-01-12
> **當前版本**: V10.8 (四 AI Agent 自動化 Smoke 趨勢管理版)
> **當前版本**: V10.9 (四 AI Agent 自動化 Smoke 每日摘要版)
> **最後更新**: 2026-04-29
---

View File

@@ -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]

6
app.py
View File

@@ -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 防護函數

View File

@@ -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 # 用於模板顯示

View File

@@ -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 會將最近快檢結果保存到 JSONLdashboard 顯示最近狀態趨勢。
- Smoke history 支援 JSONL 匯出、清理與每日 OK / Warning / Critical 摘要。
- Smoke 每日摘要支援手動 Telegram 推播,並由 `momo-scheduler` 每日 09:10 呼叫 `run_ai_smoke_daily_summary_task()`
---

View File

@@ -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

View File

@@ -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。

View File

@@ -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` 已通過。

View File

@@ -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~29Phase 3e 重構大戰 + daily_sales cache 隱形 bug 根除
- **app.py 縮減 -10.8%**: 7,386 → 6,590 行11 commits 全綠零 502。

View File

@@ -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')

View File

@@ -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_backup03:00、cleanup_agent_context03:30、backup_monitor04:00、daily_report09:00
每 1 天 db_backup03:00、cleanup_agent_context03:30、backup_monitor04:00、daily_report09:00、ai_smoke_summary09: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:00daily_report")
schedule.every().day.at("09:10").do(run_ai_smoke_daily_summary_task)
logger.info("📅 每日 09:10ai_smoke_daily_summary")
# 每月1日 07:00 月報schedule 不支援 every().month用每日 07:00 + 日期判斷)
def _monthly_report_gate():
from datetime import datetime as _dt

View File

@@ -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 導入並由其主執行緒管理排程。
# 若需獨立測試,可在此處臨時加入調用程式碼。

View File

@@ -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 = [
"🤖 <b>AI 自動化 Smoke 每日摘要</b>",
"━━━━━━━━━━━━━━━━━━━━",
f"版本:<code>{escape(SYSTEM_VERSION)}</code>",
f"最近狀態:<b>{escape(str(latest.get('status', 'no_data')).upper())}</b>",
f"最近時間:{escape(str(latest.get('generated_at', '尚無紀錄')))}",
"",
"📊 <b>近期統計</b>",
f"✅ OK{counts.get('ok', 0)}",
f"⚠️ Warning{counts.get('warning', 0)}",
f"🚨 Critical{counts.get('critical', 0)}",
]
if daily_rows:
lines += ["", "🗓️ <b>最近每日摘要</b>"]
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

View File

@@ -120,6 +120,9 @@
<button id="clearHistoryBtn" class="btn btn-outline-warning mt-3 ms-lg-2">
<i class="fas fa-broom me-2"></i>清理趨勢
</button>
<button id="sendSummaryBtn" class="btn btn-outline-info mt-3 ms-lg-2">
<i class="fas fa-paper-plane me-2"></i>推播摘要
</button>
</div>
</div>
</div>
@@ -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();
</script>
{% endblock %}

View File

@@ -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":"<script>","status":"warning","summary":"bad"}]}\n',
encoding="utf-8",
)
monkeypatch.setattr(smoke, "_HISTORY_PATH", str(history_path))
message = smoke.build_smoke_daily_summary_message()
assert "AI 自動化 Smoke 每日摘要" in message
assert "WARNING" in message
assert "<script>" not in message
def test_send_smoke_daily_summary_uses_telegram_result(monkeypatch):
from services import ai_automation_smoke_service as smoke
monkeypatch.setattr(smoke, "build_smoke_daily_summary_message", lambda: "hello")
monkeypatch.setattr(
"services.telegram_templates.send_telegram_with_result",
lambda message, chat_ids=None: {
"ok": True,
"sent": 1,
"failed": 0,
"chat_ids": chat_ids or [123],
"errors": [],
},
)
result = smoke.send_smoke_daily_summary(chat_ids=[999])
assert result["status"] == "sent"
assert result["telegram"]["chat_ids"] == [999]