From 2a3ea6f581106533e6bb765a7513b0417fcf059f Mon Sep 17 00:00:00 2001 From: OoO Date: Mon, 4 May 2026 20:20:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(p52):=20topbar=20=E8=A7=80=E6=B8=AC?= =?UTF-8?q?=E5=8F=B0=E5=81=A5=E5=BA=B7=E6=8C=87=E7=A4=BA=E7=87=88=20+=20RA?= =?UTF-8?q?G=20=E5=8F=8D=E9=A5=8B=E5=9C=93=E9=A4=85=E5=9C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P-1: topbar AI 觀測台 indicator(全頁可見) - ewoooc_base.html topbar 加「🛰 AI 觀測台」icon button - 紅色 badge 顯示告警數量(4 維度任一觸發即計數): • 三主機任一掛掉 • 待審 episode > 0 • 過去 1h 錯誤率 ≥ 30% • 預算任一 ≥ 90% - 新 GET /observability/api/health_indicator 輕量 JSON API(4 query 跨 host_health_probes/learning_episodes/ ai_calls/ai_call_budgets) - topbar polling 每 60s 自動刷新 + tooltip 顯示具體告警內容 - 全部頁面(包括 / 商品看板、所有觀測頁)topbar 都看得到健康狀態 P-2: quality_trend RAG 反饋圓餅圖(doughnut) - 取代原本卡片網格佈局 - 1-5 星依綠→紅漸層著色(5=綠、3=黃、1=紅) - 圓餅 + 右側表格雙視角(chart 配對 raw 數字) - chart.js doughnut + tooltip 顯示筆數+佔比 效益: - 統帥從任何頁面(不限觀測台)都能瞄一眼右上角看當前 AI 健康 - 快樂路徑:「正常」綠色 icon · 異常路徑:「紅色 badge + 數字」立即吸睛 - 圓餅圖比原網格更直觀「分布」感 Phase 38→52 累計 17 commits / 10 觀測頁 / DB 100% / 4 chart.js / 全頁 indicator。 Co-Authored-By: Claude Opus 4.7 (1M context) --- routes/admin_observability_routes.py | 120 +++++++++++++++++++++++++++ templates/admin/quality_trend.html | 72 ++++++++++++---- templates/ewoooc_base.html | 37 +++++++++ 3 files changed, 214 insertions(+), 15 deletions(-) diff --git a/routes/admin_observability_routes.py b/routes/admin_observability_routes.py index b37bcef..0be0aa2 100644 --- a/routes/admin_observability_routes.py +++ b/routes/admin_observability_routes.py @@ -1742,6 +1742,126 @@ def ppt_audit_trigger_aider_heal(): return jsonify({'ok': False, 'error': f'{type(e).__name__}: {str(e)[:200]}'}), 500 +@admin_observability_bp.route('/api/health_indicator') +@login_required +def health_indicator_api(): + """Phase 52 P-1:給 topbar 觀測台 indicator 用的輕量 JSON API。 + + 回傳當前是否有「需要關注」的事件: + - 三主機掛掉 + - 待審 episode > 0 + - 過去 1h 錯誤率 ≥ 30% + - 預算 ≥ 90% + """ + try: + session = get_session() + try: + # 三主機最新狀態 + host_unhealthy = 0 + try: + rows = session.execute( + sa_text(""" + WITH latest AS ( + SELECT host_label, + FIRST_VALUE(healthy) OVER ( + PARTITION BY host_label ORDER BY probed_at DESC + ) AS healthy + FROM host_health_probes + WHERE probed_at >= NOW() - INTERVAL '1 hour' + ) + SELECT host_label, BOOL_AND(NOT healthy) AS down + FROM latest + GROUP BY host_label + """), + ).fetchall() + host_unhealthy = sum(1 for r in rows if r[1]) + except Exception: + pass + + # 待審 episode + ep_pending = 0 + try: + ep_pending = int(session.execute( + sa_text("SELECT COUNT(*) FROM learning_episodes WHERE promotion_status = 'awaiting_review' AND reviewed_at IS NULL"), + ).fetchone()[0] or 0) + except Exception: + pass + + # 1h 錯誤率 + error_rate = 0 + try: + row = session.execute( + sa_text(""" + SELECT COUNT(*), + COUNT(*) FILTER (WHERE status NOT IN ('ok','cache_only')) + FROM ai_calls WHERE called_at >= NOW() - INTERVAL '1 hour' + """), + ).fetchone() + total = int(row[0] or 0) + errs = int(row[1] or 0) + error_rate = (errs / total * 100) if total > 20 else 0 + except Exception: + pass + + # 預算告警(任一 ≥ 90%) + budget_alert = False + try: + from datetime import datetime as _dt + today = _dt.now() + ms = _dt(today.year, today.month, 1) + bgs = session.execute( + sa_text(""" + SELECT b.budget_usd, + COALESCE((SELECT SUM(cost_usd) FROM ai_calls + WHERE called_at >= :ms + AND (b.provider IS NULL OR provider = b.provider)), 0) AS spent + FROM ai_call_budgets b + """), + {'ms': ms}, + ).fetchall() + for budget, spent in bgs: + if budget and float(budget) > 0 and float(spent) / float(budget) >= 0.9: + budget_alert = True + break + except Exception: + pass + + alert_count = ( + host_unhealthy + + (1 if ep_pending > 0 else 0) + + (1 if error_rate >= 30 else 0) + + (1 if budget_alert else 0) + ) + return jsonify({ + 'ok': True, + 'alert_count': alert_count, + 'host_unhealthy': host_unhealthy, + 'ep_pending': ep_pending, + 'error_rate_high': error_rate >= 30, + 'budget_alert': budget_alert, + 'tooltip': _build_indicator_tooltip(host_unhealthy, ep_pending, error_rate, budget_alert), + }) + finally: + session.close() + except Exception as e: + return jsonify({'ok': False, 'error': f'{type(e).__name__}: {str(e)[:200]}'}), 500 + + +def _build_indicator_tooltip(host_unhealthy, ep_pending, error_rate, budget_alert) -> str: + parts = [] + if host_unhealthy: + parts.append(f"{host_unhealthy} 主機異常") + if ep_pending > 0: + parts.append(f"{ep_pending} 待審") + if error_rate >= 30: + parts.append(f"錯誤率 {error_rate:.0f}%") + if budget_alert: + parts.append("預算 ≥ 90%") + if not parts: + return "AI 觀測台(一切正常)" + return "AI 觀測台 — " + " / ".join(parts) + + @admin_observability_bp.route('/playbooks/toggle/', methods=['POST']) @login_required def playbook_toggle(playbook_id: int): diff --git a/templates/admin/quality_trend.html b/templates/admin/quality_trend.html index 2f44caa..00c15a6 100644 --- a/templates/admin/quality_trend.html +++ b/templates/admin/quality_trend.html @@ -150,27 +150,38 @@ - + {% if rag_overall_dist %}
RAG 整體反饋分布(過去 {{ days }} 日) 資料來源:rag_query_log.feedback_score(含全 caller,1-5 分)
-
- {% set total_fb = (rag_overall_dist | sum(attribute='count')) or 1 %} - {% for r in rag_overall_dist %} -
-
- - {% for _ in range(r.score) %}{% endfor %} - {% for _ in range(5 - r.score) %}{% endfor %} - - {{ r.count }} - {{ "%.1f"|format(r.count / total_fb * 100) }}% -
+
+
+ +
+
+ + + + + + {% set total_fb = (rag_overall_dist | sum(attribute='count')) or 1 %} + {% for r in rag_overall_dist %} + + + + + + {% endfor %} + +
星等筆數佔比
+ {% for _ in range(r.score) %}{% endfor %} + {% for _ in range(5 - r.score) %}{% endfor %} + {{ r.score }} 分 + {{ r.count }}{{ "%.1f"|format(r.count / total_fb * 100) }}%
- {% endfor %}
@@ -240,8 +251,39 @@ {% endif %}

- Operation Ollama-First v5.0 / Phase 47 — Caller 反饋趨勢 + Operation Ollama-First v5.0 / Phase 52 — Caller 反饋趨勢(含 RAG 圓餅圖) (6 表深挖:rag_query_log / learning_episodes / ai_insights / action_plans / action_outcomes / agent_strategy_weights)

+ +{% if rag_overall_dist %} + + +{% endif %} {% endblock %} diff --git a/templates/ewoooc_base.html b/templates/ewoooc_base.html index 646b9eb..04c1a74 100644 --- a/templates/ewoooc_base.html +++ b/templates/ewoooc_base.html @@ -46,6 +46,15 @@
{% endif %} + + + + @@ -323,6 +332,34 @@ } })(); + + + {% block extra_js %}{% endblock %}