feat(p49): Telegram 補完 9 頁對應 + daily summary 加商業面未跟進警示
All checks were successful
CD Pipeline / deploy (push) Successful in 2m58s

M-B: Telegram 對應從 6/9 → 9/9
新增 3 個 cmd handler,對應 Phase 45-48 的 3 個新觀測頁:
- cmd:obs_overview — 一頁式總覽(三主機 24h + AI 呼叫 + 月成本 + 待審 episode)
- cmd:obs_orchestration — Agent 編排矩陣(4 Agent × Models 24h 數字)
  本地 Ollama % / RAG 命中 % / 錯誤率 + cost
- cmd:obs_business — 商業面 × AI(價格決策 7d by strategy
  + 未跟進機會 + Outcomes verdict 30d)

services/openclaw_bot/menu_keyboards.py::_submenu_observability 升級為 9 項

M-C: daily summary(每日 09:30)加商業面警示
- 從 ai_price_recommendations × action_plans 跨表 JOIN
  偵測 high-confidence (≥0.7) 卻無對應 action_plan 的「機會流失」
- 7d 內若有未跟進,daily summary 自動標 ⚠️ 警示
- 對應 Phase 48 business_intel 頁同個邏輯,閉環推送

inline keyboard 升級:日報附 6 個入口(總覽/編排/商業面/主機/AI/預算),
不再只有 4 個

Phase 38→49 累計 14 commits。觀測台戰役完整收官:
- 9 頁全部對應 Telegram cmd
- DB 22/22 = 100% 全覆蓋
- 6 個 L2 一鍵 + 3 種主動推送(即時/異常/日常)
- 日報含商業面警示

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
OoO
2026-05-04 20:00:15 +08:00
parent 95db06ad9d
commit 822789c810
3 changed files with 185 additions and 6 deletions

View File

@@ -8318,6 +8318,157 @@ def handle_cmd(cmd, arg, chat_id, reply_to):
except Exception as e:
send_message(chat_id, f"❌ 查詢預算失敗:{e}", reply_to, parse_mode=None)
elif cmd == 'obs_overview':
# Phase 49: 觀測台總覽(一頁式 KPI
try:
from database.manager import DatabaseManager
from sqlalchemy import text as _sa
from datetime import datetime as _dt
today = _dt.now()
month_start = _dt(today.year, today.month, 1)
session = DatabaseManager().get_session()
host_rows = session.execute(_sa("""
SELECT host_label, COUNT(*), COUNT(*) FILTER (WHERE healthy)
FROM host_health_probes
WHERE probed_at >= NOW() - INTERVAL '24 hours'
GROUP BY host_label ORDER BY host_label
""")).fetchall()
ai = session.execute(_sa("""
SELECT COUNT(*), COALESCE(SUM(cost_usd), 0),
COUNT(*) FILTER (WHERE status NOT IN ('ok','cache_only')),
COUNT(*) FILTER (WHERE rag_hit)
FROM ai_calls WHERE called_at >= NOW() - INTERVAL '24 hours'
""")).fetchone()
month_cost = session.execute(
_sa("SELECT COALESCE(SUM(cost_usd), 0) FROM ai_calls WHERE called_at >= :ms"),
{'ms': month_start},
).fetchone()[0] or 0
ep_pending = session.execute(
_sa("SELECT COUNT(*) FROM learning_episodes WHERE promotion_status = 'awaiting_review' AND reviewed_at IS NULL"),
).fetchone()[0] or 0
session.close()
ai_total = int(ai[0] or 0)
err_rate = (int(ai[2] or 0) / ai_total * 100) if ai_total else 0
rag_rate = (int(ai[3] or 0) / ai_total * 100) if ai_total else 0
lines = ["🛰 *觀測台總覽24h*", ""]
lines.append("*三主機在線率:*")
for label, total, up in host_rows:
pct = (float(up) / float(total) * 100) if total else 0
emoji = "" if pct >= 99 else "⚠️" if pct >= 90 else "🚨"
lines.append(f"{emoji} {label}*{pct:.1f}%*")
lines.append("")
lines.append(f"📊 AI 呼叫:*{ai_total:,}* 次(錯誤 {err_rate:.1f}%")
lines.append(f"💰 24h 成本:*${float(ai[1] or 0):.2f}* · 當月 *${float(month_cost):.2f}*")
lines.append(f"💡 RAG 命中率:*{rag_rate:.1f}%*")
if ep_pending:
lines.append(f"📋 待審 episodes*{ep_pending}* 筆")
lines.append("")
lines.append("詳細mo.wooo.work/observability/overview")
kb = [_row(('🤖 Agent 編排', 'cmd:obs_orchestration'), ('💼 商業面 AI', 'cmd:obs_business')),
_row(('🏥 主機健康', 'cmd:obs_health'), ('📊 AI 呼叫', 'cmd:obs_ai_calls')),
_row(('← 返回主選單', 'menu:main'))]
send_message(chat_id, '\n'.join(lines), reply_to, kb, parse_mode='Markdown')
except Exception as e:
send_message(chat_id, f"❌ 查詢觀測台總覽失敗:{e}", reply_to, parse_mode=None)
elif cmd == 'obs_orchestration':
# Phase 49: Agent 編排矩陣4 Agent × Models
try:
from database.manager import DatabaseManager
from sqlalchemy import text as _sa
agent_groups = [
('🤖 OpenClaw', ['openclaw_qa', 'openclaw_daily', 'openclaw_meta', 'openclaw_monthly', 'openclaw_weekly', 'openclaw_bot_main', 'openclaw_bot_gemini', 'openclaw_bot_nim', 'sales_copy', 'code_review_openclaw', 'openclaw_daily_insight']),
('🔍 Hermes', ['hermes_analyst', 'hermes_intent', 'code_review_hermes']),
('🧬 NemoTron', ['nemotron_dispatch']),
('🐘 ElephantAlpha', ['ea_engine', 'code_review_elephant']),
]
session = DatabaseManager().get_session()
lines = ["🌐 *Agent 編排矩陣24h*", ""]
for label, callers in agent_groups:
row = session.execute(_sa("""
SELECT COUNT(*),
COALESCE(SUM(cost_usd), 0),
COUNT(*) FILTER (WHERE provider IN ('gcp_ollama','ollama_secondary','ollama_111','ollama_other')),
COUNT(*) FILTER (WHERE rag_hit),
COUNT(*) FILTER (WHERE status NOT IN ('ok','cache_only'))
FROM ai_calls
WHERE called_at >= NOW() - INTERVAL '24 hours'
AND caller = ANY(:c)
"""), {'c': callers}).fetchone()
calls = int(row[0] or 0)
if calls == 0:
lines.append(f"{label}:(無呼叫)")
continue
cost = float(row[1] or 0)
ollama_pct = float(row[2] or 0) / calls * 100
rag_pct = float(row[3] or 0) / calls * 100
err_pct = float(row[4] or 0) / calls * 100
lines.append(f"{label}*{calls:,}* 次 · ${cost:.2f}")
lines.append(f" 本地 Ollama {ollama_pct:.0f}% · RAG {rag_pct:.0f}% · 錯誤 {err_pct:.1f}%")
session.close()
lines.append("")
lines.append("詳細mo.wooo.work/observability/agent\\_orchestration")
kb = [_row(('🛰 觀測台總覽', 'cmd:obs_overview'), ('💼 商業面 AI', 'cmd:obs_business')),
_row(('← 返回主選單', 'menu:main'))]
send_message(chat_id, '\n'.join(lines), reply_to, kb, parse_mode='Markdown')
except Exception as e:
send_message(chat_id, f"❌ 查詢 Agent 編排失敗:{e}", reply_to, parse_mode=None)
elif cmd == 'obs_business':
# Phase 49: 商業面 × AI 編排AI 在做什麼生意)
try:
from database.manager import DatabaseManager
from sqlalchemy import text as _sa
session = DatabaseManager().get_session()
rec_rows = session.execute(_sa("""
SELECT strategy, COUNT(*), COALESCE(AVG(confidence), 0)
FROM ai_price_recommendations
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY strategy ORDER BY 2 DESC
""")).fetchall()
verdict_rows = session.execute(_sa("""
SELECT verdict, COUNT(*) FROM action_outcomes
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY verdict
""")).fetchall()
unfollowed = session.execute(_sa("""
SELECT COUNT(*) FROM ai_price_recommendations r
WHERE r.created_at >= NOW() - INTERVAL '7 days'
AND r.confidence >= 0.7
AND NOT EXISTS (
SELECT 1 FROM action_plans p
WHERE p.sku = r.sku
AND p.created_at >= r.created_at
AND p.created_at < r.created_at + INTERVAL '7 days'
)
""")).fetchone()[0] or 0
session.close()
lines = ["💼 *商業面 × AI 編排*", ""]
if rec_rows:
lines.append("*AI 價格決策 7d*")
for strategy, cnt, conf in rec_rows:
lines.append(f"{strategy}*{int(cnt):,}* 筆(信心 {float(conf):.2f}")
else:
lines.append("(過去 7 日無 AI 價格決策)")
if unfollowed > 0:
lines.append("")
lines.append(f"⚠️ *未跟進機會:{unfollowed} 筆*high-confidence 卻無 action_plan")
if verdict_rows:
lines.append("")
lines.append("*Outcomes Verdict 30d*")
for v, c in verdict_rows:
icon = "" if v == 'effective' else "" if v == 'backfired' else ""
lines.append(f"{icon} {v}*{int(c):,}*")
lines.append("")
lines.append("詳細mo.wooo.work/observability/business\\_intel")
kb = [_row(('🛰 觀測台總覽', 'cmd:obs_overview'), ('🌐 Agent 編排', 'cmd:obs_orchestration')),
_row(('← 返回主選單', 'menu:main'))]
send_message(chat_id, '\n'.join(lines), reply_to, kb, parse_mode='Markdown')
except Exception as e:
send_message(chat_id, f"❌ 查詢商業面失敗:{e}", reply_to, parse_mode=None)
elif cmd == 'obs_trigger_review':
# Phase 44 (L2)Telegram inline 觸發 Code Review Pipeline
try:

View File

@@ -585,6 +585,24 @@ def run_observability_daily_summary():
WHERE audited_at >= NOW() - INTERVAL '7 days'
"""),
).fetchone()
# Phase 49: 商業面未跟進機會high-confidence 卻無 action_plan
unfollowed_count = 0
try:
unfollowed_count = session.execute(
_sa("""
SELECT COUNT(*) FROM ai_price_recommendations r
WHERE r.created_at >= NOW() - INTERVAL '7 days'
AND r.confidence >= 0.7
AND NOT EXISTS (
SELECT 1 FROM action_plans p
WHERE p.sku = r.sku
AND p.created_at >= r.created_at
AND p.created_at < r.created_at + INTERVAL '7 days'
)
"""),
).fetchone()[0] or 0
except Exception:
pass
finally:
session.close()
@@ -632,16 +650,23 @@ def run_observability_daily_summary():
if ppt_failed:
lines.append(f" 失敗:{ppt_failed}")
if unfollowed_count > 0:
lines.append("")
lines.append(f"⚠️ <b>商業面未跟進:{unfollowed_count} 筆</b>"
f"high-confidence AI 建議未轉化為 action_plan")
lines.append("")
lines.append('<a href="https://mo.wooo.work/observability/host_health">→ 開觀測台詳查</a>')
lines.append('<a href="https://mo.wooo.work/observability/overview">→ 開觀測台總覽</a>')
from services.telegram_templates import send_telegram_with_result
reply_markup = {
"inline_keyboard": [
[{"text": "🏥 主機健康", "callback_data": "cmd:obs_health"},
{"text": "📊 AI 呼叫", "callback_data": "cmd:obs_ai_calls"}],
[{"text": "💰 預算", "callback_data": "cmd:obs_budget"},
{"text": "💬 反饋趨勢", "callback_data": "cmd:obs_quality"}],
[{"text": "🛰 觀測台總覽", "callback_data": "cmd:obs_overview"},
{"text": "🌐 Agent 編排", "callback_data": "cmd:obs_orchestration"}],
[{"text": "💼 商業面 AI", "callback_data": "cmd:obs_business"},
{"text": "🏥 主機健康", "callback_data": "cmd:obs_health"}],
[{"text": "📊 AI 呼叫", "callback_data": "cmd:obs_ai_calls"},
{"text": "💰 預算", "callback_data": "cmd:obs_budget"}],
],
}
send_telegram_with_result('\n'.join(lines), reply_markup=reply_markup, parse_mode='HTML')

View File

@@ -271,8 +271,11 @@ def _submenu_competitor_ppt():
def _submenu_observability():
"""Phase 38 AI 觀測台 — 對應 /observability/* 6 頁。"""
"""Phase 38-49 AI 觀測台 — 對應 /observability/* 9 頁。"""
return _menu_with_back([
_row(('🛰 觀測台總覽 (24h)', 'cmd:obs_overview'),
('🌐 Agent 編排矩陣', 'cmd:obs_orchestration')),
_row(('💼 商業面 × AI', 'cmd:obs_business'),),
_row(('📊 AI 呼叫總覽 (24h)', 'cmd:obs_ai_calls'),
('🏥 主機健康狀態', 'cmd:obs_health')),
_row(('💰 預算控管 (當月)', 'cmd:obs_budget'),