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>
328 lines
14 KiB
HTML
328 lines
14 KiB
HTML
{% 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_outcomes(ADR-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 表跨 JOIN:ai_price_recommendations / action_plans / action_outcomes /
|
||
competitor_price_history / competitor_match_attempts / competitor_prices)
|
||
</small></p>
|
||
</div>
|
||
{% endblock %}
|