Files
ewoooc/templates/admin/business_intel.html
OoO 95db06ad9d
All checks were successful
CD Pipeline / deploy (push) Successful in 2m38s
feat(p48): 商業面 × AI 編排新頁 — AI 在做什麼生意?實際生效嗎?
新頁 /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>
2026-05-04 19:54:07 +08:00

328 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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 %}