feat: simplify growth price command panel

This commit is contained in:
ogt
2026-06-25 18:47:26 +08:00
parent 4c89b53550
commit 49ff083512
5 changed files with 152 additions and 36 deletions

View File

@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.692"
SYSTEM_VERSION = "V10.693"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -769,3 +769,4 @@ POSTGRES_HOST=momo-db
| 2026-06-25 | Google Drive 背景匯入不得尋找本機瀏覽器 | V10.690 起 `GoogleDriveService.authenticate()` 預設拒絕背景 OAuth即使人工明確開啟互動授權也使用 `open_browser=False` 並輸出授權網址,不在 scheduler/app 容器內尋找 runnable browser。 |
| 2026-06-25 | 全頁價格方向統一為 PChome 成長視角 | V10.691 起 AI Intelligence、Daily Sales、Growth Analysis、Dashboard、Telegram 與 AI 報告 prompt 不再使用「PChome 價格壓力 / MOMO 價格優勢 / MOMO 更便宜 / PChome 有優勢」等易混淆詞統一為「PChome 價格優勢」與「MOMO 低價壓力」。 |
| 2026-06-25 | 候選比較卡與價格語意必須有測試防線 | V10.692 起 `tests/test_pchome_revenue_growth_service.py` 鎖定 `/ai_intelligence` 模板必須提供 PChome/MOMO 雙賣場連結、雙開賣場操作與白話候選理由,且不得再出現 `variant_selection_review``focused_exact_identity``source_code``momo_reference` 或反向價格詞。 |
| 2026-06-25 | 成長報表不得把比價內部指標整排丟給使用者 | V10.693 起 `/growth_analysis` 的比價品質區改為「PChome 價格作戰可用度」:只呈現可直接決策、同款覆蓋、價格需刷新、待補/待確認四個訊號,並依資料狀態給下一步建議與今日作戰入口;測試禁止回到「比價資料品質、高信心門檻、未知新鮮度、人工否決」這類工程化列表。 |

View File

@@ -137,43 +137,52 @@
<article class="ga-chart-card">
<header class="ga-chart-card__head">
<span class="ga-chart-card__title"><i class="fas fa-bullseye"></i> 比價資料品質</span>
<span class="ga-chart-card__title"><i class="fas fa-bullseye"></i> PChome 價格作戰可用度</span>
</header>
<div class="ga-chart-card__body ga-chart-card__body--md">
<div class="ga-chart-card__body ga-chart-card__body--md ga-chart-card__body--command">
{% set coverage = chart_data.competitor_coverage | default({}) %}
<div class="ga-competitor-quality">
<span>高信心門檻</span>
<strong class="momo-mono">{{ coverage.match_score_floor | default(0.76) }}</strong>
<span>決策支援覆蓋率</span>
<strong class="momo-mono">{{ coverage.decision_support_rate | default(coverage.decision_ready_rate | default(0)) }}%</strong>
<span>精準可告警覆蓋</span>
<strong class="momo-mono">{{ coverage.decision_ready_rate | default(0) }}%</strong>
<span>身份配對</span>
<strong class="momo-mono">{{ coverage.valid_matches | default(0) | number_format }}</strong>
<span>身份覆蓋率</span>
<strong class="momo-mono">{{ coverage.match_rate | default(0) }}%</strong>
<span>價格新鮮</span>
<strong class="momo-mono">{{ coverage.fresh_matches | default(0) | number_format }}</strong>
<span>新鮮率</span>
<strong class="momo-mono">{{ coverage.fresh_match_rate | default(0) }}%</strong>
<span>價格過期</span>
<strong class="momo-mono">{{ coverage.stale_matches | default(0) | number_format }}</strong>
<span>未知新鮮度</span>
<strong class="momo-mono">{{ coverage.unknown_freshness_matches | default(0) | number_format }}</strong>
<span>未形成有效身份配對</span>
<strong class="momo-mono">{{ coverage.pending | default(0) | number_format }}</strong>
<span>需單位價覆核</span>
<strong class="momo-mono">{{ coverage.unit_comparable_count | default(0) | number_format }}</strong>
<span>型錄/任選可比</span>
<strong class="momo-mono">{{ coverage.catalog_comparable_count | default(0) | number_format }}</strong>
<span>重算待人工覆核</span>
<strong class="momo-mono">{{ coverage.rescore_accepted_count | default(0) | number_format }}</strong>
<span>人工採用</span>
<strong class="momo-mono">{{ coverage.manual_accept_count | default(0) | number_format }}</strong>
<span>人工否決</span>
<strong class="momo-mono">{{ coverage.manual_reject_count | default(0) | number_format }}</strong>
<span>人工單位價</span>
<strong class="momo-mono">{{ coverage.manual_unit_price_count | default(0) | number_format }}</strong>
{% set decision_rate = coverage.decision_support_rate | default(coverage.decision_ready_rate | default(0)) | float %}
{% set match_rate = coverage.match_rate | default(0) | float %}
{% set stale_count = coverage.stale_matches | default(0) | int %}
{% set pending_count = coverage.pending | default(0) | int %}
{% set review_count = coverage.actionable_review_count | default(coverage.rescore_accepted_count | default(0)) | int %}
{% set action_count = pending_count + review_count %}
<div class="ga-competitor-command">
<div class="ga-competitor-signal">
<span>可直接決策</span>
<strong class="momo-mono">{{ "{:.1f}%".format(decision_rate) }}</strong>
</div>
<div class="ga-competitor-signal">
<span>同款覆蓋</span>
<strong class="momo-mono">{{ "{:.1f}%".format(match_rate) }}</strong>
</div>
<div class="ga-competitor-signal">
<span>價格需刷新</span>
<strong class="momo-mono">{{ stale_count | number_format }}</strong>
</div>
<div class="ga-competitor-signal">
<span>待補 / 待確認</span>
<strong class="momo-mono">{{ action_count | number_format }}</strong>
</div>
<div class="ga-competitor-next">
<span class="ga-competitor-next__label">下一步</span>
{% if decision_rate < 30 %}
<strong>先補齊高業績商品的 MOMO 對應</strong>
<p>可決策覆蓋偏低,先提高同款覆蓋再判斷價格策略。</p>
{% elif stale_count > 0 %}
<strong>先刷新過期價格</strong>
<p>避免用舊價格誤判,刷新後再看 MOMO 低價壓力。</p>
{% elif action_count > 0 %}
<strong>先處理待補與候選確認</strong>
<p>把候選轉成可用比價,再進入售價、券與組合檢查。</p>
{% else %}
<strong>可進入價格策略檢查</strong>
<p>先防守 MOMO 低價壓力,再放大 PChome 價格優勢。</p>
{% endif %}
<a href="/" class="ga-competitor-next__link">
<i class="fas fa-arrow-right" aria-hidden="true"></i>前往今日作戰
</a>
</div>
</div>
</div>
</article>

View File

@@ -521,6 +521,28 @@ def test_ai_intelligence_template_uses_pchome_growth_name_and_endpoint():
assert "今日重點總覽" in template
assert "nextActionTitle" in template
assert "商品處理進度" in template
def test_growth_analysis_uses_actionable_price_command_panel():
from pathlib import Path
template = Path("templates/growth_analysis.html").read_text(encoding="utf-8")
css = Path("web/static/css/page-growth-bem.css").read_text(encoding="utf-8")
assert "PChome 價格作戰可用度" in template
assert "ga-chart-card__body--command" in template
assert "ga-competitor-command" in template
assert "可直接決策" in template
assert "待補 / 待確認" in template
assert "下一步" in template
assert "前往今日作戰" in template
assert "ga-competitor-signal" in css
assert "ga-competitor-next" in css
assert ".growth-analysis-page .ga-chart-card__body--command" in css
assert "比價資料品質" not in template
assert "高信心門檻" not in template
assert "未知新鮮度" not in template
assert "人工否決" not in template
assert "價格風險分佈" in template
assert "growthActionHint" in template
assert "growthDataSourceSummary" in template

View File

@@ -241,6 +241,10 @@
.growth-analysis-page .ga-chart-card__body--md {
height: 300px;
}
.growth-analysis-page .ga-chart-card__body--command {
height: auto;
min-height: 300px;
}
.growth-analysis-page .ga-chart-card__body canvas {
display: block;
width: 100% !important;
@@ -383,6 +387,77 @@
overflow-wrap: anywhere;
}
.growth-analysis-page .ga-competitor-command {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
width: 100%;
}
.growth-analysis-page .ga-competitor-signal {
min-width: 0;
border: 1px solid var(--momo-border-muted, rgba(31, 41, 55, 0.12));
border-radius: 8px;
padding: 10px 12px;
background: var(--momo-surface-soft, rgba(255, 255, 255, 0.72));
}
.growth-analysis-page .ga-competitor-signal span,
.growth-analysis-page .ga-competitor-next__label {
display: block;
color: var(--momo-text-secondary);
font-size: var(--momo-text-caption, 12px);
font-weight: 800;
}
.growth-analysis-page .ga-competitor-signal strong {
display: block;
min-width: 0;
margin-top: 4px;
color: var(--momo-text-primary);
font-size: clamp(1.1rem, 1.8vw, 1.55rem);
line-height: 1.1;
overflow-wrap: anywhere;
}
.growth-analysis-page .ga-competitor-next {
grid-column: 1 / -1;
min-width: 0;
border-left: 4px solid var(--momo-page-accent);
border-radius: 8px;
padding: 12px 14px;
background: var(--momo-surface-muted, rgba(248, 250, 252, 0.88));
}
.growth-analysis-page .ga-competitor-next strong {
display: block;
margin-top: 4px;
color: var(--momo-text-primary);
font-size: var(--momo-text-body, 14px);
}
.growth-analysis-page .ga-competitor-next p {
margin: 4px 0 0;
color: var(--momo-text-secondary);
font-size: var(--momo-text-body-sm, 13px);
line-height: 1.45;
}
.growth-analysis-page .ga-competitor-next__link {
display: inline-flex;
align-items: center;
gap: 6px;
margin-top: 10px;
color: var(--momo-page-accent);
font-size: var(--momo-text-body-sm, 13px);
font-weight: 800;
text-decoration: none;
}
.growth-analysis-page .ga-competitor-next__link:hover {
text-decoration: underline;
}
@media (max-width: 640px) {
.growth-analysis-page .ga-page-head {
align-items: flex-start;
@@ -419,4 +494,13 @@
height: 260px !important;
padding: var(--momo-space-3, 12px);
}
.growth-analysis-page .ga-chart-card__body--command {
height: auto !important;
min-height: 0;
}
.growth-analysis-page .ga-competitor-command {
grid-template-columns: 1fr;
}
}