feat(p49): Telegram 補完 9 頁對應 + daily summary 加商業面未跟進警示
All checks were successful
CD Pipeline / deploy (push) Successful in 2m58s
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:
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user