710 lines
23 KiB
HTML
710 lines
23 KiB
HTML
{% extends "ewoooc_base.html" %}
|
||
{% set active_page = 'obs_business_intel' %}
|
||
{% block title %}商業 AI 戰果室{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.biz-warroom {
|
||
--biz-ink: #2f211b;
|
||
--biz-muted: #8a6b5c;
|
||
--biz-card: rgba(255, 252, 247, 0.95);
|
||
--biz-line: rgba(201, 100, 66, 0.16);
|
||
--biz-alert: #b8442f;
|
||
--biz-good: #2f8f6b;
|
||
--biz-warn: #c98a2e;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.biz-warroom::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0 0 auto auto;
|
||
width: min(560px, 58vw);
|
||
height: min(560px, 58vw);
|
||
background: radial-gradient(circle, rgba(201, 100, 66, 0.16), transparent 62%);
|
||
pointer-events: none;
|
||
transform: translate(26%, -34%);
|
||
}
|
||
|
||
.biz-hero {
|
||
position: relative;
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.45fr) minmax(280px, 0.55fr);
|
||
gap: 1.15rem;
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
|
||
.biz-command {
|
||
background:
|
||
linear-gradient(135deg, rgba(53, 35, 27, 0.96), rgba(126, 64, 42, 0.92)),
|
||
radial-gradient(circle at top right, rgba(255, 194, 115, 0.22), transparent 42%);
|
||
color: #fff8ee;
|
||
border-radius: 28px;
|
||
padding: clamp(1.35rem, 2.5vw, 2.25rem);
|
||
box-shadow: 0 28px 60px rgba(78, 45, 31, 0.24);
|
||
min-height: 320px;
|
||
}
|
||
|
||
.biz-kicker {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: .5rem;
|
||
padding: .35rem .65rem;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.12);
|
||
color: #ffd7a2;
|
||
font-size: .82rem;
|
||
font-weight: 800;
|
||
letter-spacing: .08em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.biz-command h1 {
|
||
font-size: clamp(2.05rem, 4vw, 3.25rem);
|
||
line-height: 1.05;
|
||
letter-spacing: 0;
|
||
margin: 1rem 0 .85rem;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.biz-command p {
|
||
max-width: 760px;
|
||
color: rgba(255, 248, 238, 0.78);
|
||
font-size: 1.02rem;
|
||
line-height: 1.75;
|
||
}
|
||
|
||
.biz-meta-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: .7rem;
|
||
margin-top: 1.4rem;
|
||
}
|
||
|
||
.biz-meta-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: .45rem;
|
||
padding: .65rem .85rem;
|
||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
color: rgba(255, 248, 238, 0.9);
|
||
font-weight: 800;
|
||
}
|
||
|
||
.biz-filter-card {
|
||
background: var(--biz-card);
|
||
border: 1px solid var(--biz-line);
|
||
border-radius: 28px;
|
||
padding: 1.2rem;
|
||
box-shadow: 0 18px 46px rgba(93, 57, 42, 0.12);
|
||
align-self: stretch;
|
||
}
|
||
|
||
.biz-filter-card label {
|
||
font-size: .78rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: .08em;
|
||
color: var(--biz-muted);
|
||
font-weight: 900;
|
||
}
|
||
|
||
.biz-filter-card .form-control {
|
||
border-radius: 16px;
|
||
border-color: rgba(201, 100, 66, 0.18);
|
||
min-height: 48px;
|
||
}
|
||
|
||
.biz-signal-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: .9rem;
|
||
margin-bottom: 1.1rem;
|
||
}
|
||
|
||
.biz-signal {
|
||
position: relative;
|
||
overflow: hidden;
|
||
background: var(--biz-card);
|
||
border: 1px solid var(--biz-line);
|
||
border-radius: 24px;
|
||
padding: 1.05rem;
|
||
box-shadow: 0 14px 34px rgba(93, 57, 42, 0.08);
|
||
}
|
||
|
||
.biz-signal::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: auto 0 0 0;
|
||
height: 4px;
|
||
background: linear-gradient(90deg, #c96442, #f1b45a, #2f8f6b);
|
||
opacity: .82;
|
||
}
|
||
|
||
.biz-signal .label {
|
||
color: var(--biz-muted);
|
||
font-size: .8rem;
|
||
font-weight: 900;
|
||
letter-spacing: .04em;
|
||
}
|
||
|
||
.biz-signal .value {
|
||
color: var(--biz-ink);
|
||
font-size: clamp(1.7rem, 3vw, 2.45rem);
|
||
font-weight: 950;
|
||
letter-spacing: 0;
|
||
margin: .3rem 0 .05rem;
|
||
}
|
||
|
||
.biz-signal .note {
|
||
color: var(--biz-muted);
|
||
font-size: .86rem;
|
||
}
|
||
|
||
.biz-alert-strip {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: .9rem;
|
||
padding: 1rem 1.15rem;
|
||
border-radius: 22px;
|
||
border: 1px solid rgba(184, 68, 47, 0.2);
|
||
background: linear-gradient(135deg, rgba(184, 68, 47, 0.12), rgba(255, 248, 238, 0.92));
|
||
color: var(--biz-alert);
|
||
margin-bottom: 1.1rem;
|
||
font-weight: 850;
|
||
}
|
||
|
||
.biz-layout {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.1fr) minmax(340px, .9fr);
|
||
gap: 1rem;
|
||
align-items: start;
|
||
}
|
||
|
||
.biz-panel {
|
||
background: var(--biz-card);
|
||
border: 1px solid var(--biz-line);
|
||
border-radius: 26px;
|
||
box-shadow: 0 14px 38px rgba(93, 57, 42, 0.08);
|
||
overflow: hidden;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.biz-panel-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
padding: 1.05rem 1.15rem .75rem;
|
||
border-bottom: 1px solid rgba(201, 100, 66, 0.1);
|
||
}
|
||
|
||
.biz-panel-head h3 {
|
||
margin: 0;
|
||
color: var(--biz-ink);
|
||
font-weight: 950;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.biz-panel-head p {
|
||
margin: .25rem 0 0;
|
||
color: var(--biz-muted);
|
||
font-size: .88rem;
|
||
}
|
||
|
||
.biz-panel-body {
|
||
padding: 1rem 1.15rem 1.15rem;
|
||
}
|
||
|
||
.biz-strategy-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: .75rem;
|
||
}
|
||
|
||
.biz-strategy-card {
|
||
border: 1px solid rgba(201, 100, 66, 0.14);
|
||
border-radius: 20px;
|
||
padding: .95rem;
|
||
background: linear-gradient(135deg, #fffaf3, #fffdf9);
|
||
}
|
||
|
||
.biz-strategy-card .strategy {
|
||
color: var(--biz-ink);
|
||
font-weight: 950;
|
||
font-size: 1.02rem;
|
||
}
|
||
|
||
.biz-strategy-card .metrics {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: .45rem;
|
||
margin-top: .75rem;
|
||
}
|
||
|
||
.biz-mini-metric {
|
||
padding: .55rem;
|
||
border-radius: 14px;
|
||
background: rgba(201, 100, 66, 0.07);
|
||
}
|
||
|
||
.biz-mini-metric b {
|
||
display: block;
|
||
color: var(--biz-ink);
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.biz-mini-metric span {
|
||
color: var(--biz-muted);
|
||
font-size: .72rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.biz-table {
|
||
width: 100%;
|
||
border-collapse: separate;
|
||
border-spacing: 0 .55rem;
|
||
}
|
||
|
||
.biz-table th {
|
||
color: var(--biz-muted);
|
||
font-size: .76rem;
|
||
letter-spacing: .04em;
|
||
text-transform: uppercase;
|
||
border: 0;
|
||
padding: 0 .7rem .15rem;
|
||
}
|
||
|
||
.biz-table td {
|
||
border: 0;
|
||
padding: .78rem .7rem;
|
||
background: rgba(255, 250, 243, 0.82);
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.biz-table tr td:first-child {
|
||
border-radius: 16px 0 0 16px;
|
||
}
|
||
|
||
.biz-table tr td:last-child {
|
||
border-radius: 0 16px 16px 0;
|
||
}
|
||
|
||
.biz-decision-list {
|
||
display: grid;
|
||
gap: .75rem;
|
||
}
|
||
|
||
.biz-decision-card {
|
||
display: grid;
|
||
grid-template-columns: minmax(82px, .36fr) minmax(240px, 1.15fr) minmax(130px, .46fr);
|
||
gap: .8rem;
|
||
align-items: center;
|
||
padding: .95rem;
|
||
border: 1px solid rgba(201, 100, 66, 0.14);
|
||
border-radius: 18px;
|
||
background:
|
||
linear-gradient(135deg, rgba(255, 252, 247, 0.96), rgba(255, 248, 241, 0.86)),
|
||
radial-gradient(circle, rgba(201, 100, 66, 0.12) 1px, transparent 1.2px);
|
||
background-size: auto, 13px 13px;
|
||
}
|
||
|
||
.biz-decision-time {
|
||
color: var(--biz-muted);
|
||
font-size: .82rem;
|
||
font-weight: 850;
|
||
}
|
||
|
||
.biz-decision-sku,
|
||
.biz-decision-name {
|
||
color: var(--biz-ink);
|
||
font-weight: 900;
|
||
}
|
||
|
||
.biz-decision-name {
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.biz-decision-reason {
|
||
grid-column: 2 / 4;
|
||
color: var(--biz-muted);
|
||
font-size: .82rem;
|
||
line-height: 1.5;
|
||
padding-top: .55rem;
|
||
border-top: 1px dashed rgba(201, 100, 66, 0.18);
|
||
}
|
||
|
||
.biz-price-stack {
|
||
color: var(--biz-ink);
|
||
font-weight: 850;
|
||
}
|
||
|
||
.biz-price-stack small {
|
||
display: block;
|
||
color: var(--biz-muted);
|
||
font-weight: 700;
|
||
}
|
||
|
||
.biz-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: .28rem .55rem;
|
||
border-radius: 999px;
|
||
font-size: .76rem;
|
||
font-weight: 900;
|
||
background: rgba(201, 100, 66, 0.11);
|
||
color: #8e3d28;
|
||
}
|
||
|
||
.biz-badge.good {
|
||
background: rgba(47, 143, 107, 0.12);
|
||
color: var(--biz-good);
|
||
}
|
||
|
||
.biz-badge.warn {
|
||
background: rgba(201, 138, 46, 0.14);
|
||
color: var(--biz-warn);
|
||
}
|
||
|
||
.biz-chart-shell {
|
||
height: 250px;
|
||
padding: .4rem;
|
||
}
|
||
|
||
.biz-empty {
|
||
padding: 1rem;
|
||
border-radius: 18px;
|
||
background: rgba(201, 100, 66, 0.06);
|
||
color: var(--biz-muted);
|
||
font-weight: 800;
|
||
}
|
||
|
||
@media (max-width: 1180px) {
|
||
.biz-hero,
|
||
.biz-layout {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.biz-signal-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 720px) {
|
||
.biz-command h1 {
|
||
font-size: 2.45rem;
|
||
}
|
||
|
||
.biz-signal-grid,
|
||
.biz-strategy-grid,
|
||
.biz-decision-card {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.biz-decision-reason {
|
||
grid-column: auto;
|
||
}
|
||
|
||
.biz-alert-strip {
|
||
align-items: flex-start;
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block ewooo_content %}
|
||
{% import "admin/_observability_labels.html" as obs_label %}
|
||
{% set rec_total = rec_by_strategy|sum(attribute='count') if rec_by_strategy else 0 %}
|
||
{% set ns = namespace(conf_total=0, effective=0, backfired=0, neutral=0, verdict_total=0) %}
|
||
{% for r in rec_by_strategy %}
|
||
{% set ns.conf_total = ns.conf_total + ((r.avg_confidence or 0) * (r.count or 0)) %}
|
||
{% endfor %}
|
||
{% for v in verdict_stats %}
|
||
{% set label = v.verdict or '未分類' %}
|
||
{% set ns.verdict_total = ns.verdict_total + (v.count or 0) %}
|
||
{% if label == 'effective' or label == 'success' or label == 'positive' %}
|
||
{% set ns.effective = ns.effective + (v.count or 0) %}
|
||
{% elif label == 'backfired' or label == 'negative' or label == 'failed' %}
|
||
{% set ns.backfired = ns.backfired + (v.count or 0) %}
|
||
{% else %}
|
||
{% set ns.neutral = ns.neutral + (v.count or 0) %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% set avg_conf = (ns.conf_total / rec_total) if rec_total else 0 %}
|
||
{% set effective_rate = ((ns.effective / ns.verdict_total) * 100) if ns.verdict_total else 0 %}
|
||
|
||
<div class="biz-warroom">
|
||
<div class="biz-hero">
|
||
<section class="biz-command">
|
||
<span class="biz-kicker"><i class="fas fa-store"></i> 商業情報</span>
|
||
<h1 class="biz-title">商業 AI 戰果室</h1>
|
||
<p>
|
||
這一頁不再只是資料列表,而是把價格建議、未跟進警示、閉環學習與競品監測收成一個商業決策控制台。
|
||
先看 AI 是否真的推動結果,再往下追每一筆策略與市場訊號。
|
||
</p>
|
||
<div class="biz-meta-row">
|
||
<span class="biz-meta-pill"><i class="fas fa-calendar-day"></i> 近 {{ days }} 天</span>
|
||
<span class="biz-meta-pill"><i class="fas fa-brain"></i> {{ rec_total }} 筆價格建議</span>
|
||
<span class="biz-meta-pill"><i class="fas fa-rotate"></i> {{ ns.verdict_total }} 筆閉環結果</span>
|
||
</div>
|
||
</section>
|
||
|
||
<aside class="biz-filter-card">
|
||
<form method="get">
|
||
<label for="days">觀測窗口</label>
|
||
<div class="d-flex gap-2 mt-2">
|
||
<input class="form-control" id="days" name="days" type="number" min="1" max="90" value="{{ days }}">
|
||
<button class="btn btn-primary" type="submit">更新</button>
|
||
</div>
|
||
</form>
|
||
<div class="mt-4">
|
||
<div class="text-muted small fw-bold text-uppercase">決策節奏</div>
|
||
<div class="h4 fw-black mb-1">先警示,再追因</div>
|
||
<p class="text-muted mb-0">未跟進高信心建議會被拉到第一層;其餘資訊按策略、閉環、競品三條線拆解。</p>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
|
||
{% if error %}
|
||
<div class="alert alert-danger"><i class="fas fa-triangle-exclamation me-2"></i>{{ error }}</div>
|
||
{% endif %}
|
||
|
||
<section class="biz-signal-grid">
|
||
<article class="biz-signal">
|
||
<div class="label">高信心未跟進</div>
|
||
<div class="value">{{ unfollowed_count }}</div>
|
||
<div class="note">信心分 >= 0.8 且仍未轉行動計畫</div>
|
||
</article>
|
||
<article class="biz-signal">
|
||
<div class="label">平均信心分</div>
|
||
<div class="value">{{ '%.0f'|format(avg_conf * 100) }}%</div>
|
||
<div class="note">依策略建議量加權</div>
|
||
</article>
|
||
<article class="biz-signal">
|
||
<div class="label">有效率</div>
|
||
<div class="value">{{ '%.0f'|format(effective_rate) }}%</div>
|
||
<div class="note">有效 / 已回收結論</div>
|
||
</article>
|
||
<article class="biz-signal">
|
||
<div class="label">競品監測</div>
|
||
<div class="value">{{ recent_competitor_prices|length }}</div>
|
||
<div class="note">近 24h 價格變動樣本</div>
|
||
</article>
|
||
</section>
|
||
|
||
{% if unfollowed_count > 0 %}
|
||
<section class="biz-alert-strip">
|
||
<div><i class="fas fa-bell me-2"></i>{{ unfollowed_count }} 筆高信心 AI 價格建議尚未跟進,建議優先轉為行動計畫或標記原因。</div>
|
||
<span class="biz-badge warn">需人工決策</span>
|
||
</section>
|
||
{% endif %}
|
||
|
||
<div class="biz-layout">
|
||
<main>
|
||
<section class="biz-panel">
|
||
<div class="biz-panel-head">
|
||
<div>
|
||
<h3>策略族群雷達</h3>
|
||
<p>把 AI 價格建議依策略類型聚合,快速判斷目前主攻降價、防守或毛利修復。</p>
|
||
</div>
|
||
<span class="biz-badge">{{ rec_by_strategy|length }} 類策略</span>
|
||
</div>
|
||
<div class="biz-panel-body">
|
||
{% if rec_by_strategy %}
|
||
<div class="biz-strategy-grid">
|
||
{% for r in rec_by_strategy %}
|
||
<article class="biz-strategy-card">
|
||
<div class="strategy">{{ obs_label.strategy(r.strategy) }}</div>
|
||
<div class="metrics">
|
||
<div class="biz-mini-metric"><b>{{ r.count }}</b><span>建議數</span></div>
|
||
<div class="biz-mini-metric"><b>{{ '%.0f'|format((r.avg_confidence or 0) * 100) }}%</b><span>信心</span></div>
|
||
<div class="biz-mini-metric"><b>{{ '%.1f'|format(r.avg_gap_pct or 0) }}%</b><span>價差</span></div>
|
||
</div>
|
||
<div class="mt-2 text-muted small">平均銷量變化 {{ '%.1f'|format(r.avg_sales_delta or 0) }}</div>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="biz-empty">目前觀測窗口沒有策略建議。</div>
|
||
{% endif %}
|
||
</div>
|
||
</section>
|
||
|
||
<section class="biz-panel">
|
||
<div class="biz-panel-head">
|
||
<div>
|
||
<h3>最近 AI 價格建議</h3>
|
||
<p>保留決策原因與競品價差,方便直接追到 SKU 層級。</p>
|
||
</div>
|
||
<span class="biz-badge">Latest 20</span>
|
||
</div>
|
||
<div class="biz-panel-body">
|
||
{% if latest_recommendations %}
|
||
<div class="biz-decision-list">
|
||
{% for r in latest_recommendations %}
|
||
<article class="biz-decision-card">
|
||
<div>
|
||
<div class="biz-decision-time">{{ r.created_at or '-' }}</div>
|
||
<div class="biz-decision-sku">{{ r.sku }}</div>
|
||
</div>
|
||
<div>
|
||
<div class="biz-decision-name">{{ r.name or '-' }}</div>
|
||
<div class="mt-1">
|
||
<span class="biz-badge">{{ obs_label.strategy(r.strategy, '-') }}</span>
|
||
<span class="biz-badge {% if (r.confidence or 0) >= 0.8 %}good{% endif %}">{{ '%.0f'|format((r.confidence or 0) * 100) }}%</span>
|
||
</div>
|
||
</div>
|
||
<div class="biz-price-stack">
|
||
{{ r.momo_price or '-' }} / {{ r.pchome_price or '-' }}
|
||
<small>差距 {{ '%.1f'|format(r.gap_pct or 0) }}%</small>
|
||
</div>
|
||
<div class="biz-decision-reason">{{ r.reason or '尚無原因摘要' }}</div>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="biz-empty">目前沒有最新價格建議。</div>
|
||
{% endif %}
|
||
</div>
|
||
</section>
|
||
|
||
<section class="biz-panel">
|
||
<div class="biz-panel-head">
|
||
<div>
|
||
<h3>閉環學習紀錄</h3>
|
||
<p>追蹤行動計畫到實際結果的真實效果,這是 AI 能不能變聰明的核心證據。</p>
|
||
</div>
|
||
<span class="biz-badge good">{{ loop_records|length }} 筆紀錄</span>
|
||
</div>
|
||
<div class="biz-panel-body table-responsive">
|
||
{% if loop_records %}
|
||
<table class="biz-table">
|
||
<thead>
|
||
<tr>
|
||
<th>計畫</th>
|
||
<th>SKU</th>
|
||
<th>狀態</th>
|
||
<th>建立 / 執行</th>
|
||
<th>結論</th>
|
||
<th>指標</th>
|
||
<th>變化</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for r in loop_records %}
|
||
<tr>
|
||
<td>#{{ r.plan_id }}<br><span class="text-muted small">{{ obs_label.plan_type(r.plan_type, '-') }}</span></td>
|
||
<td><strong>{{ r.sku }}</strong></td>
|
||
<td><span class="biz-badge {% if r.status == 'done' or r.status == 'completed' %}good{% elif r.status == 'pending' %}warn{% endif %}">{{ obs_label.status(r.status, '-') }}</span></td>
|
||
<td>{{ r.created_at or '-' }} / {{ r.executed_at or '-' }}</td>
|
||
<td>{{ obs_label.verdict(r.verdict, '-') }}</td>
|
||
<td>{{ obs_label.metric(r.metric_type, '-') }}<br><span class="text-muted small">{{ r.before or '-' }} → {{ r.after or '-' }}</span></td>
|
||
<td><strong>{{ '%.1f'|format(r.change_pct or 0) }}%</strong></td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<div class="biz-empty">尚未形成行動計畫到實際結果的閉環紀錄。</div>
|
||
{% endif %}
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<aside>
|
||
<section class="biz-panel">
|
||
<div class="biz-panel-head">
|
||
<div>
|
||
<h3>結論戰果分布</h3>
|
||
<p>用結果反校正 AI 建議,不讓漂亮信心分掩蓋真實成效。</p>
|
||
</div>
|
||
</div>
|
||
<div class="biz-panel-body">
|
||
<div class="biz-chart-shell"><canvas id="verdictPieChart"></canvas></div>
|
||
{% if verdict_stats %}
|
||
<table class="biz-table mt-2">
|
||
<thead><tr><th>結論</th><th>數量</th><th>平均變化</th></tr></thead>
|
||
<tbody>
|
||
{% for v in verdict_stats %}
|
||
<tr><td>{{ obs_label.verdict(v.verdict) }}</td><td>{{ v.count }}</td><td>{{ '%.1f'|format(v.avg_delta or 0) }}%</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<div class="biz-empty">尚無結論統計。</div>
|
||
{% endif %}
|
||
</div>
|
||
</section>
|
||
|
||
<section class="biz-panel">
|
||
<div class="biz-panel-head">
|
||
<div>
|
||
<h3>競品比對品質</h3>
|
||
<p>先看比對是否可靠,再拿價格差做策略判斷。</p>
|
||
</div>
|
||
</div>
|
||
<div class="biz-panel-body table-responsive">
|
||
{% if match_stats %}
|
||
<table class="biz-table">
|
||
<thead><tr><th>狀態</th><th>數量</th><th>候選數</th><th>分數</th></tr></thead>
|
||
<tbody>
|
||
{% for m in match_stats %}
|
||
<tr>
|
||
<td><span class="biz-badge {% if m.status == 'matched' or m.status == 'success' %}good{% elif m.status == 'failed' %}warn{% endif %}">{{ obs_label.status(m.status, '-') }}</span></td>
|
||
<td>{{ m.count }}</td>
|
||
<td>{{ '%.1f'|format(m.avg_candidates or 0) }}</td>
|
||
<td>{{ '%.2f'|format(m.avg_score or 0) }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<div class="biz-empty">尚無競品比對統計。</div>
|
||
{% endif %}
|
||
</div>
|
||
</section>
|
||
|
||
<section class="biz-panel">
|
||
<div class="biz-panel-head">
|
||
<div>
|
||
<h3>競品 24h 價格脈動</h3>
|
||
<p>保留最新市場訊號,支援人工快速複核。</p>
|
||
</div>
|
||
</div>
|
||
<div class="biz-panel-body table-responsive">
|
||
{% if recent_competitor_prices %}
|
||
<table class="biz-table">
|
||
<thead><tr><th>時間</th><th>SKU</th><th>PChome / momo</th><th>折扣</th></tr></thead>
|
||
<tbody>
|
||
{% for r in recent_competitor_prices %}
|
||
<tr>
|
||
<td>{{ r.crawled_at or '-' }}</td>
|
||
<td><strong>{{ r.sku }}</strong><br><span class="text-muted small">{{ r.product_name or '-' }}</span></td>
|
||
<td>{{ r.pchome_price or '-' }} / {{ r.momo_price or '-' }}<br><span class="text-muted small">差距 {{ r.gap or '-' }}</span></td>
|
||
<td>{{ '%.1f'|format(r.discount_pct or 0) }}%<br><span class="text-muted small">分數 {{ '%.2f'|format(r.match_score or 0) }}</span></td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<div class="biz-empty">近 24h 尚無競品價格樣本。</div>
|
||
{% endif %}
|
||
</div>
|
||
</section>
|
||
</aside>
|
||
</div>
|
||
|
||
<div class="text-muted small mt-3">資料來源:AI 價格建議、行動計畫、實際結果、競品比對與競品價格歷史。</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<template id="obs-business-verdict-data">{{ verdict_stats | tojson }}</template>
|
||
<script src="{{ url_for('static', filename='js/analysis-chart-theme.js') }}"></script>
|
||
<script src="{{ url_for('static', filename='js/observability-charts.js') }}"></script>
|
||
{% endblock %}
|