feat(p48): 商業面 × AI 編排新頁 — AI 在做什麼生意?實際生效嗎?
All checks were successful
CD Pipeline / deploy (push) Successful in 2m38s

新頁 /observability/business_intel:把 AI 觀測台從「技術面」延伸到「商業面」。
回答統帥兩大問:
1. 我們的 AI 在做什麼生意?
2. AI 動作真的有用嗎?(閉環追蹤)

新接 5 張未善用的商業面表(DB 利用率 17/22 → 22/22,100%):
- ai_price_recommendations(AI 價格建議完整明細)
- competitor_prices(競品價格快照)
- competitor_price_history(24h 抓取歷史)
- competitor_match_attempts(競品比對失敗追蹤)
- 善用 action_plans × action_outcomes JOIN(閉環)

頁面 widget(7 張卡片):
1. unfollowed alert:high-confidence 但未轉化為 action_plan 的數量
2. AI 決策 by strategy(promote/watch/hold 含平均信心 + gap% + 銷量變化)
3. 最近 20 筆 AI 建議詳細(SKU/商品/MOMO 價/PChome 價/Gap/原因)
4. **閉環學習表**:plan → outcome 全鏈追蹤
   含 verdict/before/after/變化 % — ADR-012 核心 KPI
5. Verdict 分布(effective/neutral/backfired 計數)
6. 競品比對嘗試統計(success/fail/avg_score)
7. 24h 競品價格抓取列表(SKU/商品/MOMO vs PChome gap)

入口:
- sidebar AI 觀測 group 加「商業面 × AI」(07c)
- /observability/overview 入口卡升級為 8 項

DB 全表覆蓋達成:22/22 = 100%
- Phase 47 17 表 → Phase 48 22 表
- 新接:ai_price_recommendations / competitor_prices /
        competitor_price_history / competitor_match_attempts
- 已用:ai_calls / ai_call_budgets / ai_insights / learning_episodes /
        rag_query_log / mcp_calls / incidents / heal_logs / playbooks /
        backup_log / embedding_retry_queue / agent_context /
        agent_strategy_weights / action_plans / action_outcomes /
        host_health_probes / ppt_audit_results

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
OoO
2026-05-04 19:54:07 +08:00
parent 2e124db602
commit 95db06ad9d
4 changed files with 539 additions and 2 deletions

View File

@@ -247,6 +247,205 @@ def observability_overview():
)
# ─────────────────────────────────────────────────────────────────────────────
# /observability/business_intel — Phase 48 商業面 × AI 編排
# ─────────────────────────────────────────────────────────────────────────────
@admin_observability_bp.route('/business_intel')
@login_required
def business_intel_dashboard():
"""Phase 48 — 商業面 × AI 編排:把 AI 觀測台延伸到商業層級。
展現「AI 在做什麼生意」:
- ai_price_recommendations × competitor_prices: AI 看到什麼定價機會
- action_plans × action_outcomes: 計畫到 verdict 的閉環
- competitor_match_attempts: 競品比對失敗追蹤
"""
days = int(request.args.get('days', '7'))
session = get_session()
try:
# 1. ai_price_recommendations 30d 總覽
rec_summary = session.execute(
sa_text(f"""
SELECT strategy, COUNT(*) AS cnt,
COALESCE(AVG(confidence), 0) AS avg_conf,
COALESCE(AVG(gap_pct), 0) AS avg_gap_pct,
COALESCE(AVG(sales_7d_delta), 0) AS avg_sales_delta
FROM ai_price_recommendations
WHERE created_at >= NOW() - INTERVAL '{int(days)} days'
GROUP BY strategy ORDER BY cnt DESC
"""),
).fetchall()
rec_by_strategy = [
{
'strategy': r[0], 'count': int(r[1] or 0),
'avg_confidence': round(float(r[2] or 0), 3),
'avg_gap_pct': round(float(r[3] or 0), 2),
'avg_sales_delta': round(float(r[4] or 0), 2),
}
for r in rec_summary
]
# 2. ai_price_recommendations 最近 20 筆詳細
latest_recs = session.execute(
sa_text("""
SELECT id, sku, LEFT(name, 50), strategy, confidence,
momo_price, pchome_price, gap_pct, sales_7d_delta,
LEFT(reason, 120), created_at
FROM ai_price_recommendations
ORDER BY created_at DESC LIMIT 20
"""),
).fetchall()
latest_recommendations = [
{
'id': r[0], 'sku': r[1], 'name': r[2], 'strategy': r[3],
'confidence': round(float(r[4] or 0), 3),
'momo_price': float(r[5] or 0),
'pchome_price': float(r[6] or 0) if r[6] else None,
'gap_pct': round(float(r[7] or 0), 2),
'sales_delta': round(float(r[8] or 0), 2) if r[8] is not None else None,
'reason': r[9] or '',
'created_at': r[10].strftime('%m-%d %H:%M') if r[10] else '',
}
for r in latest_recs
]
# 3. action_plans × action_outcomes 閉環30d
closed_loops = session.execute(
sa_text(f"""
SELECT p.id, p.sku, p.plan_type, p.status,
p.created_by, p.created_at, p.executed_at,
o.verdict, o.metric_type, o.before_val, o.after_val
FROM action_plans p
LEFT JOIN action_outcomes o ON o.plan_id = p.id
WHERE p.created_at >= NOW() - INTERVAL '{int(days)} days'
ORDER BY p.created_at DESC LIMIT 25
"""),
).fetchall()
loop_records = []
for r in closed_loops:
before = float(r[9]) if r[9] is not None else None
after = float(r[10]) if r[10] is not None else None
change_pct = None
if before and before != 0 and after is not None:
change_pct = (after - before) / abs(before) * 100
loop_records.append({
'plan_id': r[0], 'sku': r[1], 'plan_type': r[2],
'status': r[3], 'created_by': r[4],
'created_at': r[5].strftime('%m-%d %H:%M') if r[5] else '',
'executed_at': r[6].strftime('%m-%d %H:%M') if r[6] else None,
'verdict': r[7], 'metric_type': r[8],
'before': before, 'after': after, 'change_pct': change_pct,
})
# 4. action_outcomes verdict 統計
verdict_summary = session.execute(
sa_text(f"""
SELECT verdict, COUNT(*) AS cnt,
AVG(after_val - before_val) AS avg_delta
FROM action_outcomes
WHERE created_at >= NOW() - INTERVAL '{int(days)} days'
AND before_val IS NOT NULL AND after_val IS NOT NULL
GROUP BY verdict ORDER BY cnt DESC
"""),
).fetchall()
verdict_stats = [
{
'verdict': r[0] or 'unknown', 'count': int(r[1] or 0),
'avg_delta': round(float(r[2] or 0), 2),
}
for r in verdict_summary
]
# 5. competitor_match_attempts 失敗統計30d
match_attempts = session.execute(
sa_text(f"""
SELECT attempt_status, COUNT(*) AS cnt,
COALESCE(AVG(candidate_count), 0) AS avg_candidates,
COALESCE(AVG(best_match_score), 0) AS avg_score
FROM competitor_match_attempts
WHERE attempted_at >= NOW() - INTERVAL '{int(days)} days'
GROUP BY attempt_status ORDER BY cnt DESC
"""),
).fetchall()
match_stats = [
{
'status': r[0], 'count': int(r[1] or 0),
'avg_candidates': round(float(r[2] or 0), 1),
'avg_score': round(float(r[3] or 0), 3),
}
for r in match_attempts
]
# 6. competitor_prices 24h 變動 TOP 10
recent_competitor = session.execute(
sa_text("""
SELECT cph.sku, cph.competitor_product_name, cph.price,
cph.momo_price, cph.discount_pct, cph.match_score,
cph.crawled_at
FROM competitor_price_history cph
WHERE cph.crawled_at >= NOW() - INTERVAL '24 hours'
AND cph.match_score >= 0.7
ORDER BY cph.crawled_at DESC LIMIT 12
"""),
).fetchall()
recent_competitor_prices = [
{
'sku': r[0],
'product_name': (r[1] or '')[:50],
'pchome_price': float(r[2] or 0),
'momo_price': float(r[3] or 0) if r[3] else None,
'discount_pct': int(r[4]) if r[4] else None,
'match_score': round(float(r[5] or 0), 3),
'gap': (float(r[3]) - float(r[2])) if (r[2] and r[3]) else None,
'crawled_at': r[6].strftime('%m-%d %H:%M') if r[6] else '',
}
for r in recent_competitor
]
# 7. 高 confidence 但未 follow-through (recommendation 沒對應 action_plan)
unfollowed = session.execute(
sa_text(f"""
SELECT COUNT(*)
FROM ai_price_recommendations r
WHERE r.created_at >= NOW() - INTERVAL '{int(days)} 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()
unfollowed_count = int(unfollowed[0] or 0) if unfollowed else 0
return render_template(
'admin/business_intel.html',
active_page='obs_business_intel',
days=days,
rec_by_strategy=rec_by_strategy,
latest_recommendations=latest_recommendations,
loop_records=loop_records,
verdict_stats=verdict_stats,
match_stats=match_stats,
recent_competitor_prices=recent_competitor_prices,
unfollowed_count=unfollowed_count,
error=None,
)
except Exception as e:
return render_template(
'admin/business_intel.html',
active_page='obs_business_intel', days=days,
rec_by_strategy=[], latest_recommendations=[], loop_records=[],
verdict_stats=[], match_stats=[], recent_competitor_prices=[],
unfollowed_count=0,
error=f'查詢失敗: {type(e).__name__}: {str(e)[:200]}',
)
finally:
session.close()
# ─────────────────────────────────────────────────────────────────────────────
# /observability/agent_orchestration — Phase 46 編排矩陣
# ─────────────────────────────────────────────────────────────────────────────

View File

@@ -0,0 +1,327 @@
{% extends "ewoooc_base.html" %}
{% block title %}商業面 × AI 編排{% endblock %}
{% block ewooo_content %}
<div class="container-fluid mt-3">
<h2 class="mb-3"><i class="fas fa-briefcase me-2"></i>商業面 × AI 編排
<small class="text-muted">過去 {{ days }} 日 · AI 在做什麼生意?實際生效嗎?</small>
</h2>
{% if error %}
<div class="alert alert-warning"><strong><i class="fas fa-exclamation-triangle me-1"></i></strong> {{ error }}</div>
{% endif %}
<form method="get" class="row g-2 mb-3">
<div class="col-auto">
<select name="days" class="form-select form-select-sm" onchange="this.form.submit()">
{% for d in [7, 14, 30, 90] %}
<option value="{{ d }}" {% if days == d %}selected{% endif %}>過去 {{ d }} 日</option>
{% endfor %}
</select>
</div>
</form>
{% if unfollowed_count > 0 %}
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-1"></i>
過去 {{ days }} 日有 <strong>{{ unfollowed_count }}</strong> 筆 high-confidence (≥0.7) AI 建議
<strong>未轉化為 action_plan</strong> — 機會流失!
</div>
{% endif %}
<!-- AI 價格決策 by strategy -->
{% if rec_by_strategy %}
<div class="card mb-3">
<div class="card-header">
<strong><i class="fas fa-tag me-2"></i>AI 價格決策 by strategy{{ days }} 日)</strong>
<small class="text-muted">資料來源ai_price_recommendations</small>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0" style="font-size: 0.9em;">
<thead class="table-light">
<tr>
<th>策略</th>
<th class="text-end">數量</th>
<th class="text-end">平均信心</th>
<th class="text-end">平均 gap %</th>
<th class="text-end">平均 7d 銷量變化 %</th>
</tr>
</thead>
<tbody>
{% for s in rec_by_strategy %}
<tr>
<td>
{% if s.strategy == 'promote' %}<span class="badge bg-success">promote 推廣</span>
{% elif s.strategy == 'watch' %}<span class="badge bg-warning text-dark">watch 觀察</span>
{% elif s.strategy == 'hold' %}<span class="badge bg-secondary">hold 持平</span>
{% else %}<span class="badge bg-info text-dark">{{ s.strategy }}</span>{% endif %}
</td>
<td class="text-end"><strong>{{ s.count }}</strong></td>
<td class="text-end">{{ "%.2f"|format(s.avg_confidence) }}</td>
<td class="text-end">
<span class="{% if s.avg_gap_pct > 5 %}text-danger{% elif s.avg_gap_pct < -5 %}text-success{% endif %}">
{{ "%.1f"|format(s.avg_gap_pct) }}%
</span>
</td>
<td class="text-end">
<span class="{% if s.avg_sales_delta > 0 %}text-success{% elif s.avg_sales_delta < 0 %}text-danger{% endif %}">
{{ "%+.1f"|format(s.avg_sales_delta) }}%
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% else %}
<div class="alert alert-info">
<i class="fas fa-info-circle me-1"></i>
過去 {{ days }} 日無 AI 價格決策資料ai_price_recommendations 表)。
</div>
{% endif %}
<!-- 最近 20 筆 AI 建議 -->
{% if latest_recommendations %}
<div class="card mb-3">
<div class="card-header">
<strong><i class="fas fa-history me-2"></i>最近 20 筆 AI 價格建議</strong>
<small class="text-muted">資料來源ai_price_recommendations含競品快照</small>
</div>
<div class="card-body p-0" style="max-height: 480px; overflow-y: auto;">
<table class="table table-sm mb-0" style="font-size: 0.85em;">
<thead class="table-light" style="position: sticky; top: 0;">
<tr>
<th>時間</th><th>SKU</th><th>商品</th><th>策略</th>
<th class="text-end">信心</th>
<th class="text-end">MOMO 價</th>
<th class="text-end">PChome 價</th>
<th class="text-end">Gap</th>
<th class="text-end">7d 銷量</th>
<th>原因</th>
</tr>
</thead>
<tbody>
{% for r in latest_recommendations %}
<tr>
<td><small>{{ r.created_at }}</small></td>
<td><code>{{ r.sku }}</code></td>
<td><small>{{ r.name }}{% if r.name|length >= 50 %}…{% endif %}</small></td>
<td>
{% if r.strategy == 'promote' %}<span class="badge bg-success"></span>
{% elif r.strategy == 'watch' %}<span class="badge bg-warning text-dark"></span>
{% elif r.strategy == 'hold' %}<span class="badge bg-secondary"></span>
{% else %}<span class="badge bg-info text-dark">{{ r.strategy }}</span>{% endif %}
</td>
<td class="text-end">
<strong class="{% if r.confidence >= 0.8 %}text-success{% elif r.confidence >= 0.6 %}text-warning{% else %}text-muted{% endif %}">
{{ "%.2f"|format(r.confidence) }}
</strong>
</td>
<td class="text-end">${{ "%.0f"|format(r.momo_price) }}</td>
<td class="text-end">
{% if r.pchome_price %}${{ "%.0f"|format(r.pchome_price) }}{% else %}—{% endif %}
</td>
<td class="text-end">
<span class="{% if r.gap_pct > 5 %}text-danger{% elif r.gap_pct < -5 %}text-success{% endif %}">
{{ "%+.1f"|format(r.gap_pct) }}%
</span>
</td>
<td class="text-end">
{% if r.sales_delta is not none %}
<span class="{% if r.sales_delta > 0 %}text-success{% elif r.sales_delta < 0 %}text-danger{% endif %}">
{{ "%+.1f"|format(r.sales_delta) }}%
</span>
{% else %}<small class="text-muted"></small>{% endif %}
</td>
<td><small class="text-muted">{{ r.reason }}{% if r.reason|length >= 120 %}…{% endif %}</small></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- 閉環學習plan × outcome -->
{% if loop_records %}
<div class="card mb-3" style="border-left: 4px solid #6f42c1;">
<div class="card-header bg-light">
<strong><i class="fas fa-sync-alt me-2"></i>閉環學習plan → outcome 全鏈追蹤({{ days }} 日)</strong>
<small class="text-muted">資料來源action_plans LEFT JOIN action_outcomesADR-012 核心)</small>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0" style="font-size: 0.85em;">
<thead class="table-light">
<tr>
<th>Plan</th><th>SKU</th><th>類型</th><th>狀態</th>
<th>建立者</th><th>建立時間</th><th>執行時間</th>
<th>Verdict</th><th>指標</th>
<th class="text-end">Before</th><th class="text-end">After</th>
<th class="text-end">變化</th>
</tr>
</thead>
<tbody>
{% for l in loop_records %}
<tr>
<td><code>#{{ l.plan_id }}</code></td>
<td><small>{{ l.sku or '—' }}</small></td>
<td><span class="badge bg-info text-dark">{{ l.plan_type or '—' }}</span></td>
<td>
{% if l.status == 'pending' %}<span class="badge bg-warning text-dark">待審</span>
{% elif l.status == 'approved' %}<span class="badge bg-success">已批</span>
{% elif l.status == 'executed' %}<span class="badge bg-primary">已執</span>
{% elif l.status == 'rejected' %}<span class="badge bg-danger">拒絕</span>
{% else %}<span class="badge bg-secondary">{{ l.status }}</span>{% endif %}
</td>
<td><small>{{ l.created_by or '—' }}</small></td>
<td><small>{{ l.created_at }}</small></td>
<td><small>{{ l.executed_at or '—' }}</small></td>
<td>
{% if l.verdict == 'effective' %}<span class="badge bg-success">有效</span>
{% elif l.verdict == 'backfired' %}<span class="badge bg-danger">適得其反</span>
{% elif l.verdict == 'neutral' %}<span class="badge bg-secondary">無變</span>
{% else %}<small class="text-muted"></small>{% endif %}
</td>
<td><small>{{ l.metric_type or '—' }}</small></td>
<td class="text-end">{% if l.before is not none %}{{ "%.1f"|format(l.before) }}{% else %}—{% endif %}</td>
<td class="text-end">{% if l.after is not none %}{{ "%.1f"|format(l.after) }}{% else %}—{% endif %}</td>
<td class="text-end">
{% if l.change_pct is not none %}
<strong class="{% if l.change_pct > 0 %}text-success{% elif l.change_pct < 0 %}text-danger{% endif %}">
{{ "%+.1f"|format(l.change_pct) }}%
</strong>
{% else %}<small class="text-muted"></small>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- Verdict 統計 -->
{% if verdict_stats %}
<div class="card mb-3">
<div class="card-header">
<strong><i class="fas fa-trophy me-2"></i>Outcomes Verdict 分布</strong>
<small class="text-muted">{{ days }} 日 · AI 動作的實際成效閉環</small>
</div>
<div class="card-body">
<div class="row g-2">
{% set total_v = (verdict_stats | sum(attribute='count')) or 1 %}
{% for v in verdict_stats %}
<div class="col-md-3 col-sm-6">
<div class="border rounded p-2 text-center"
style="border-left-width: 4px !important;
border-left-color: {% if v.verdict == 'effective' %}#198754
{% elif v.verdict == 'backfired' %}#dc3545
{% else %}#6c757d{% endif %} !important;">
<small class="text-muted d-block">
{% if v.verdict == 'effective' %}<i class="fas fa-check-circle text-success"></i> 有效
{% elif v.verdict == 'backfired' %}<i class="fas fa-times-circle text-danger"></i> 適得其反
{% elif v.verdict == 'neutral' %}<i class="fas fa-minus text-secondary"></i> 無變化
{% else %}{{ v.verdict }}{% endif %}
</small>
<strong style="font-size: 1.4em;">{{ v.count }}</strong>
<small class="d-block text-muted">{{ "%.0f"|format(v.count / total_v * 100) }}% · Δ {{ "%+.1f"|format(v.avg_delta) }}</small>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- 競品比對失敗統計 -->
{% if match_stats %}
<div class="card mb-3">
<div class="card-header">
<strong><i class="fas fa-search-minus me-2"></i>競品比對嘗試({{ days }} 日)</strong>
<small class="text-muted">資料來源competitor_match_attempts — AI 找不到對應商品時的失敗追蹤</small>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0" style="font-size: 0.9em;">
<thead class="table-light">
<tr>
<th>狀態</th>
<th class="text-end">次數</th>
<th class="text-end">平均候選數</th>
<th class="text-end">平均匹配分</th>
</tr>
</thead>
<tbody>
{% for m in match_stats %}
<tr>
<td>
{% if 'success' in (m.status or '').lower() %}<span class="badge bg-success">{{ m.status }}</span>
{% elif 'fail' in (m.status or '').lower() or 'no_' in (m.status or '').lower() %}<span class="badge bg-danger">{{ m.status }}</span>
{% else %}<span class="badge bg-warning text-dark">{{ m.status }}</span>{% endif %}
</td>
<td class="text-end">{{ "{:,}".format(m.count) }}</td>
<td class="text-end">{{ m.avg_candidates }}</td>
<td class="text-end">{{ "%.2f"|format(m.avg_score) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- 競品 24h 變動 -->
{% if recent_competitor_prices %}
<div class="card mb-3">
<div class="card-header">
<strong><i class="fas fa-balance-scale me-2"></i>過去 24h 競品價格抓取match_score ≥ 0.7</strong>
<small class="text-muted">資料來源competitor_price_history — AI 看到的競品價格全景</small>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0" style="font-size: 0.85em;">
<thead class="table-light">
<tr>
<th>時間</th><th>SKU</th><th>競品商品</th>
<th class="text-end">PChome</th><th class="text-end">MOMO</th>
<th class="text-end">Gap</th><th class="text-end">折扣</th>
<th class="text-end">匹配</th>
</tr>
</thead>
<tbody>
{% for c in recent_competitor_prices %}
<tr>
<td><small>{{ c.crawled_at }}</small></td>
<td><code>{{ c.sku }}</code></td>
<td><small>{{ c.product_name }}</small></td>
<td class="text-end">${{ "%.0f"|format(c.pchome_price) }}</td>
<td class="text-end">
{% if c.momo_price %}${{ "%.0f"|format(c.momo_price) }}{% else %}<small class="text-muted"></small>{% endif %}
</td>
<td class="text-end">
{% if c.gap is not none %}
<span class="{% if c.gap > 0 %}text-danger{% elif c.gap < 0 %}text-success{% endif %}">
{{ "%+.0f"|format(c.gap) }}
</span>
{% else %}<small class="text-muted"></small>{% endif %}
</td>
<td class="text-end">
{% if c.discount_pct %}<span class="badge bg-warning text-dark">-{{ c.discount_pct }}%</span>
{% else %}<small class="text-muted"></small>{% endif %}
</td>
<td class="text-end">{{ "%.2f"|format(c.match_score) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<p class="text-muted mt-3"><small>
<i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 / Phase 48 — 商業面 × AI 編排
6 表跨 JOINai_price_recommendations / action_plans / action_outcomes /
competitor_price_history / competitor_match_attempts / competitor_prices
</small></p>
</div>
{% endblock %}

View File

@@ -219,9 +219,9 @@
</div>
{% endif %}
<!-- 7 大入口 -->
<!-- 8 大入口 -->
<div class="card">
<div class="card-header"><strong><i class="fas fa-th me-1"></i>7 大子頁入口</strong></div>
<div class="card-header"><strong><i class="fas fa-th me-1"></i>8 大子頁入口</strong></div>
<div class="card-body">
<div class="row g-2">
<div class="col-lg-4 col-md-6">
@@ -230,6 +230,12 @@
<small class="d-block text-muted ms-4">4 Agent × Ollama × Gemini × MCP × RAG 全景 + 自動建議</small>
</a>
</div>
<div class="col-lg-4 col-md-6">
<a href="/observability/business_intel" class="btn btn-outline-warning w-100 text-start" style="border-width: 2px;">
<i class="fas fa-briefcase me-2"></i><strong>商業面 × AI 編排</strong>
<small class="d-block text-muted ms-4">AI 價格決策 + 閉環學習 + 競品比對全景</small>
</a>
</div>
<div class="col-lg-4 col-md-6">
<a href="/observability/host_health" class="btn btn-outline-primary w-100 text-start">
<i class="fas fa-heartbeat me-2"></i>主機健康監控

View File

@@ -86,6 +86,11 @@
<span class="momo-nav-label">Agent 編排矩陣</span>
<span class="momo-nav-code momo-mono">07b</span>
</a>
<a class="momo-nav-link {% if _active_page == 'obs_business_intel' %}is-active{% endif %}" href="/observability/business_intel">
<span class="momo-nav-icon"><i class="fas fa-briefcase"></i></span>
<span class="momo-nav-label">商業面 × AI</span>
<span class="momo-nav-code momo-mono">07c</span>
</a>
<a class="momo-nav-link {% if _active_page == 'obs_host_health' %}is-active{% endif %}" href="/observability/host_health">
<span class="momo-nav-icon"><i class="fas fa-heartbeat"></i></span>
<span class="momo-nav-label">主機健康</span>