diff --git a/config.py b/config.py index 62140b1..4e944d9 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.622" +SYSTEM_VERSION = "V10.623" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index 987dab7..8e0951a 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -1,8 +1,8 @@ # PChome 業績成長自動化作戰系統 — AI 競價情報模組 Single Source of Truth > **最後更新**: 2026-06-16 (台北時間) -> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單與價格參考表優先讀取、CSV 備援預檢、前台操作入口與高可見頁面繁中化守門已建立 -> **適用版本**: V10.622 +> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單與價格參考表優先讀取、CSV 備援預檢、前台操作入口、高可見頁面繁中化守門與比價/作戰 UI 工作台化已建立 +> **適用版本**: V10.623 --- @@ -69,6 +69,7 @@ - V10.620 起 `unit_comparable` 不再一律丟人工確認:若 `build_unit_price_comparison()` 可產生明確容量/數量、MOMO 單位價、PChome 單位價與差距百分比,候選需標為「自動單位價比較」並回傳 `auto_compare_type=unit_price`。此類候選可自動呈現價格壓力,但不得混入舊總價同款比價表,也不得直接寫入正式價差或自動改價;無法產生單位證據時才維持「需人工確認」。 - V10.621 起 `/price_comparison` 的「自動找 MOMO 候選」會把可直接總價比價與自動單位價候選同步到 `external_offers`,`ingestion_method='targeted_momo_search'`,人工確認候選不得寫入。`external_offers.raw_payload_json.price_basis='unit_price'` 時,作戰清單必須使用 `unit_price_comparison` 的 MOMO / PChome 單位價與 `unit_gap_pct` 判斷價格壓力;不得把 MOMO 組合總價與 PChome 單品總價直接相減。此同步只影響外部價格參考與作戰清單,不寫 `competitor_prices`,也不自動改價。 - V10.622 起任何 `external_offers` 自動同步成功寫入後,必須呼叫 `mark_pchome_growth_cache_stale()` 寫入共享 cache epoch;`/api/ai/pchome-growth/opportunities` 讀快取前必須比對 `get_pchome_growth_cache_epoch()`。這是跨 Gunicorn worker 的可見性保護,避免自動候選已進外部價格參考,但 AI 情報頁仍回 120 秒舊作戰清單。 +- V10.623 起 `/price_comparison` 與 `/ai_intelligence` 不得只靠大段文字說明流程:比價頁第一屏必須有主 KPI、目前卡點、四步流程與結果決策摘要;作戰頁第一屏必須有今日任務、可立即處理、待補比價與最新業績日。所有狀態都要由實際 API/前端狀態驅動,讓使用者一眼知道下一步要按哪個動作。 ## 零之一、12 Agent 決策信封(2026-05-24) diff --git a/docs/memory/current_execution_queue_20260524.md b/docs/memory/current_execution_queue_20260524.md index 12f92d5..db0ee00 100644 --- a/docs/memory/current_execution_queue_20260524.md +++ b/docs/memory/current_execution_queue_20260524.md @@ -296,3 +296,10 @@ - `sync_legacy_momo_reference_offers()` 與 `sync_targeted_momo_candidates_to_external_offers()` 只要成功寫入 `external_offers`,就呼叫 `mark_pchome_growth_cache_stale()`。 - `/api/ai/pchome-growth/opportunities` 的 in-memory cache 會記住建立時的 epoch;讀快取前若發現共享 epoch 較新,會直接重建,不再讓使用者看到 120 秒舊清單。 - 這讓「自動找 MOMO 候選 → 同步外部價格參考 → AI 情報頁作戰清單」變成同一條即時資料流,減少使用者手動重新整理或等待快取過期。 + +## 26. 2026-06-16 V10.623 比價與作戰頁工作台化 + +- 使用者指出前端仍像文字堆疊,無法快速知道怎麼操作;V10.623 將 `/price_comparison` 第一屏改為主 KPI、目前卡點、四步流程與下一步 CTA。 +- `/price_comparison` 的結果區新增決策摘要,先顯示「需檢查售價或活動 / 可主推曝光 / 觀察賣點」三類數字與建議,再往下看明細表。 +- `/ai_intelligence` 第一屏新增今日任務摘要,直接顯示今日任務、可立即處理、待補比價與最新業績日;資料來自現有 PChome growth API stats。 +- 測試守門新增 `priceDecisionGrid`、`price-workflow-strip`、`price-result-summary-grid`、`growth-executive-strip` 與 `renderGrowthExecutiveSummary`,避免頁面退回只有文字說明的狀態。 diff --git a/templates/ai_intelligence.html b/templates/ai_intelligence.html index 00dd2c1..2c6b443 100644 --- a/templates/ai_intelligence.html +++ b/templates/ai_intelligence.html @@ -239,6 +239,73 @@ gap: 14px; } + .growth-executive-strip { + display: grid; + grid-template-columns: minmax(0, 1.25fr) repeat(3, minmax(0, 0.75fr)); + gap: 10px; + } + + .growth-exec-card { + min-height: 118px; + border: 1px solid var(--momo-border-subtle); + border-radius: 8px; + background: rgba(255, 255, 255, 0.82); + box-shadow: var(--momo-shadow-soft); + padding: 13px; + } + + .growth-exec-card.is-primary { + border-color: rgba(172, 92, 58, 0.22); + background: rgba(242, 178, 90, 0.15); + } + + .growth-exec-card.is-ready { + border-color: rgba(42, 134, 96, 0.24); + background: rgba(235, 248, 241, 0.76); + } + + .growth-exec-card.is-gap { + border-color: rgba(188, 78, 67, 0.22); + background: rgba(255, 244, 239, 0.78); + } + + .growth-exec-label { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + color: var(--momo-text-muted); + font-size: 0.74rem; + font-weight: 900; + } + + .growth-exec-label i { + color: var(--momo-warm-rust); + } + + .growth-exec-value { + margin-top: 11px; + color: var(--momo-text-strong); + font-family: var(--momo-font-display); + font-size: 1.05rem; + font-weight: 900; + line-height: 1.28; + } + + .growth-exec-card:not(.is-primary) .growth-exec-value { + font-family: var(--momo-font-mono); + font-size: 1.72rem; + line-height: 1; + } + + .growth-exec-detail { + margin-top: 7px; + color: var(--momo-text-muted); + font-size: 0.76rem; + font-weight: 760; + line-height: 1.4; + } + .ops-flow { border: 1px solid var(--momo-border-subtle); border-radius: 8px; @@ -940,6 +1007,7 @@ } .growth-ops-grid, + .growth-executive-strip, .offer-dryrun-grid, .growth-metric-row, .ops-flow-grid, @@ -988,6 +1056,41 @@ +
+
+
+ 今日任務 + +
+
整理中
+
正在讀取 PChome 業績與 MOMO 外部價格。
+
+
+
+ 可立即處理 + +
+
+
已有可用比價資料
+
+
+
+ 待補比價 + +
+
+
有業績但缺外部參考
+
+
+
+ 最新業績日 + +
+
+
等待資料
+
+
+
@@ -1487,6 +1590,7 @@ function renderOpsCommandDashboard(stats, scope = {}) { setWidth('opsFunnelMappedBar', candidateCount ? (mappedCount / candidateCount) * 100 : 0); setWidth('opsFunnelNeedsBar', candidateCount ? (needsMapping / candidateCount) * 100 : 0); renderOpsSourceBars(stats.external_data_source_counts || {}, scope); + renderGrowthExecutiveSummary(stats); } function renderNextAction(candidateCount, mappedCount, needsMapping) { @@ -1529,6 +1633,48 @@ function renderNextAction(candidateCount, mappedCount, needsMapping) { button.onclick = () => scrollToPanel('externalPricePanel'); } +function renderGrowthExecutiveSummary(stats = {}) { + const candidateCount = Number(stats.candidate_count || 0); + const mappedCount = Number(stats.mapped_count || 0); + const needsMapping = Number(stats.needs_mapping_count || 0); + const latestSalesDate = String(stats.latest_sales_date || '').slice(0, 10); + + document.getElementById('growthExecReady').textContent = formatCount(mappedCount); + document.getElementById('growthExecGap').textContent = formatCount(needsMapping); + document.getElementById('growthExecLatestDate').textContent = latestSalesDate || '—'; + document.getElementById('growthExecLatestDetail').textContent = latestSalesDate ? '已接到 PChome 業績' : '尚未確認業績日期'; + + const task = document.getElementById('growthExecTask'); + const detail = document.getElementById('growthExecTaskDetail'); + const gapCard = document.getElementById('growthExecGapCard'); + if (!task || !detail) return; + + if (!candidateCount) { + task.textContent = '先更新 PChome 業績'; + detail.textContent = '目前還沒有足夠資料,請確認最新業績檔是否已匯入。'; + gapCard?.classList.remove('is-gap'); + return; + } + + if (needsMapping > 0 && mappedCount === 0) { + task.textContent = `先補 ${formatCount(needsMapping)} 件 MOMO 參考`; + detail.textContent = '高業績商品還不能比價,先補對應資料才會有可行動建議。'; + gapCard?.classList.add('is-gap'); + return; + } + + if (needsMapping > mappedCount) { + task.textContent = `先補比價,再處理 ${formatCount(mappedCount)} 件`; + detail.textContent = '待補比價比可處理商品多,先擴大 MOMO 對應覆蓋率。'; + gapCard?.classList.add('is-gap'); + return; + } + + task.textContent = '先檢查價格風險'; + detail.textContent = `已有 ${formatCount(mappedCount)} 件商品可直接處理,先看 PChome 是否被外部低價壓住。`; + gapCard?.classList.toggle('is-gap', needsMapping > 0); +} + function renderOpsSourceBars(counts, scope = {}) { const box = document.getElementById('opsSourceBars'); const totalBox = document.getElementById('opsSourceTotal'); diff --git a/templates/price_comparison.html b/templates/price_comparison.html index a0e4f85..39f1e2f 100644 --- a/templates/price_comparison.html +++ b/templates/price_comparison.html @@ -9,6 +9,10 @@ } .price-tool-head { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(360px, 0.82fr); + gap: 18px; + align-items: stretch; padding: var(--momo-space-4) var(--momo-space-5); background: var(--momo-bg-surface); border: 1px solid var(--momo-border-light); @@ -29,6 +33,45 @@ color: var(--momo-text-secondary) !important; } +.price-hero-copy { + display: flex; + flex-direction: column; + justify-content: center; + min-width: 0; +} + +.price-hero-kpis { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; +} + +.price-hero-kpi { + min-height: 92px; + padding: 12px; + border: 1px solid rgba(42, 37, 32, 0.1); + border-radius: var(--momo-radius-md); + background: rgba(250, 247, 240, 0.72); +} + +.price-hero-kpi strong { + display: block; + color: var(--momo-text-primary); + font-family: var(--momo-font-mono, monospace); + font-size: 1.55rem; + font-weight: 900; + line-height: 1; +} + +.price-hero-kpi span { + display: block; + margin-top: 7px; + color: var(--momo-text-secondary); + font-size: 0.76rem; + font-weight: 850; + line-height: 1.35; +} + .price-command-grid { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(280px, 0.9fr); @@ -87,6 +130,134 @@ padding: 14px; } +.price-decision-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; + margin-bottom: var(--momo-space-4); +} + +.price-decision-card { + display: grid; + gap: 8px; + min-height: 126px; + padding: 13px; + border: 1px solid var(--momo-border-light); + border-radius: var(--momo-radius-md); + background: rgba(255, 255, 255, 0.82); + box-shadow: var(--momo-shadow-soft); +} + +.price-decision-card.is-active { + border-color: rgba(172, 92, 58, 0.34); + background: rgba(242, 178, 90, 0.16); +} + +.price-decision-card.is-ready { + border-color: rgba(46, 125, 91, 0.24); + background: rgba(235, 248, 241, 0.72); +} + +.price-decision-card.is-blocked { + border-color: rgba(200, 81, 58, 0.24); + background: rgba(255, 244, 239, 0.78); +} + +.price-decision-top { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + color: var(--momo-text-secondary); + font-size: 0.73rem; + font-weight: 900; +} + +.price-decision-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 999px; + background: rgba(42, 37, 32, 0.08); + color: var(--momo-warm-rust); +} + +.price-decision-value { + color: var(--momo-text-primary); + font-family: var(--momo-font-display); + font-size: 0.98rem; + font-weight: 900; + line-height: 1.3; +} + +.price-decision-detail { + color: var(--momo-text-secondary); + font-size: 0.76rem; + font-weight: 760; + line-height: 1.42; +} + +.price-workflow-strip { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; + margin-bottom: var(--momo-space-4); +} + +.price-workflow-step { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 10px; + align-items: center; + min-height: 70px; + padding: 11px; + border: 1px solid rgba(42, 37, 32, 0.1); + border-radius: var(--momo-radius-md); + background: rgba(250, 247, 240, 0.6); +} + +.price-workflow-step.is-current { + border-color: rgba(172, 92, 58, 0.34); + background: rgba(242, 178, 90, 0.16); +} + +.price-workflow-step.is-done { + border-color: rgba(46, 125, 91, 0.24); + background: rgba(235, 248, 241, 0.72); +} + +.price-workflow-step strong { + display: block; + color: var(--momo-text-primary); + font-size: 0.82rem; + font-weight: 900; + line-height: 1.25; +} + +.price-workflow-step span:last-child { + display: block; + margin-top: 2px; + color: var(--momo-text-secondary); + font-size: 0.7rem; + font-weight: 780; +} + +.price-workflow-index { + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border-radius: 999px; + background: rgba(172, 92, 58, 0.13); + color: var(--momo-warm-rust); + font-family: var(--momo-font-mono, monospace); + font-size: 0.78rem; + font-weight: 900; +} + .price-panel-title { display: flex; align-items: center; @@ -226,6 +397,68 @@ color: var(--momo-text-primary); } +.price-result-summary-grid { + display: grid; + grid-template-columns: minmax(260px, 0.92fr) minmax(0, 1.35fr); + gap: 12px; + margin-bottom: 14px; +} + +.price-result-callout { + display: grid; + gap: 8px; + padding: 14px; + border: 1px solid rgba(172, 92, 58, 0.22); + border-radius: var(--momo-radius-md); + background: rgba(242, 178, 90, 0.14); +} + +.price-result-callout strong { + color: var(--momo-text-primary); + font-size: 1rem; + font-weight: 900; + line-height: 1.35; +} + +.price-result-callout span { + color: var(--momo-text-secondary); + font-size: 0.8rem; + font-weight: 760; + line-height: 1.45; +} + +.price-result-matrix { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; +} + +.price-result-matrix-card { + min-height: 92px; + padding: 11px; + border: 1px solid rgba(42, 37, 32, 0.1); + border-radius: var(--momo-radius-md); + background: rgba(255, 255, 255, 0.72); +} + +.price-result-matrix-card strong { + display: block; + color: var(--momo-text-primary); + font-family: var(--momo-font-mono, monospace); + font-size: 1.45rem; + font-weight: 900; + line-height: 1; +} + +.price-result-matrix-card span { + display: block; + margin-top: 7px; + color: var(--momo-text-secondary); + font-size: 0.74rem; + font-weight: 850; + line-height: 1.35; +} + .price-note { padding: 10px 12px; background: var(--momo-info-bg); @@ -352,9 +585,18 @@ @media (max-width: 760px) { .price-tool-head { + grid-template-columns: 1fr; padding: var(--momo-space-4); } + .price-hero-kpis, + .price-decision-grid, + .price-workflow-strip, + .price-result-summary-grid, + .price-result-matrix { + grid-template-columns: 1fr; + } + .price-command-grid, .price-next-action { grid-template-columns: 1fr; @@ -379,8 +621,24 @@ {% block content %}
+

PChome 商品比價決策台

-

先確認兩邊資料是否齊,再找出 PChome 價格偏高、可主推或需要補資料的商品。

+

用 PChome 業績商品反查 MOMO,先找同款,再判斷售價、活動與曝光下一步。

+
+
+
+ 0 + PChome 商品 +
+
+ 0 + MOMO 可用候選 +
+
+ 0 + 已產生決策 +
+
@@ -410,6 +668,60 @@
+
+
+
+ 檢查範圍 + +
+
尚未選擇
+
先選品牌或輸入商品關鍵字。
+
+
+
+ PChome + +
+
等待商品
+
取得商品後,系統才知道要比哪一批。
+
+
+
+ MOMO + +
+
等待候選
+
會自動分成同款、單位價、需確認。
+
+
+
+ 比價結果 + +
+
尚未判讀
+
結果會分成檢查售價、主推曝光、觀察賣點。
+
+
+ +
+
+ 1 + 選範圍品牌或關鍵字 +
+
+ 2 + 抓 PChome取得主場商品 +
+
+ 3 + 找 MOMO同款與單位價 +
+
+ 4 + 做決策價格與曝光 +
+
+
@@ -517,6 +829,26 @@ 比價結果判讀 等待比價結果
+
+
+ 尚未產生判讀 + 取得 PChome 與 MOMO 商品後,系統會直接整理下一步。 +
+
+
+ 0 + 需檢查售價或活動 +
+
+ 0 + 可主推曝光 +
+
+ 0 + 觀察賣點 +
+
+
@@ -1051,6 +1383,11 @@ La Roche-Posay 安得利防曬液 50ml,920 comparisonResult = null; document.getElementById('resultSection').style.display = 'none'; setText('priceResultSummary', '等待比價結果'); + setText('priceResultHeadline', '尚未產生判讀'); + setText('priceResultAdvice', '取得 PChome 與 MOMO 商品後,系統會直接整理下一步。'); + setText('priceUrgentMetric', '0'); + setText('priceGoodMetric', '0'); + setText('priceWatchMetric', '0'); } function renderPriceCommandDashboard() { @@ -1065,7 +1402,11 @@ La Roche-Posay 安得利防曬液 50ml,920 const urgentCount = Number(stats.momo_cheaper_count || 0); const goodCount = Number(stats.pchome_cheaper_count || 0); const watchCount = Math.max(0, matchedCount - urgentCount - goodCount); + const usableMomoCount = momoCount + unitCount; + setText('heroPchomeCount', pchomeCount.toLocaleString()); + setText('heroMomoCount', usableMomoCount.toLocaleString()); + setText('heroDecisionCount', matchedCount.toLocaleString()); setText('pricePchomeReadyText', `${pchomeCount} 筆`); setText( 'priceMomoReadyText', @@ -1075,6 +1416,8 @@ La Roche-Posay 安得利防曬液 50ml,920 ); setWidth('pricePchomeReadyBar', Math.min(100, pchomeCount)); setWidth('priceMomoReadyBar', Math.min(100, momoCount + unitCount)); + renderPriceDecisionCards({ keyword, pchomeCount, momoCount, unitCount, reviewCount, matchedCount, urgentCount, goodCount, watchCount }); + renderPriceWorkflow({ keyword, pchomeCount, momoCount, unitCount, matchedCount }); if (!keyword && !pchomeCount && !momoCount && !unitCount) { setText('priceReadySummary', '請先選範圍'); @@ -1126,6 +1469,97 @@ La Roche-Posay 安得利防曬液 50ml,920 setNextAction('今天先做:檢查商品賣點與活動位置', '目前價格接近,差異不大,下一步看曝光、文案和活動組合。', '查看比價結果', 'focus-results'); } + function renderPriceDecisionCards(state) { + const keyword = state.keyword || ''; + setDecisionCard( + 'decisionScopeCard', + 'decisionScopeValue', + 'decisionScopeDetail', + keyword ? `檢查「${keyword}」` : '尚未選擇', + keyword ? '範圍已確認,可以取得 PChome 商品。' : '先選品牌或輸入商品關鍵字。', + keyword ? 'ready' : 'active' + ); + setDecisionCard( + 'decisionPchomeCard', + 'decisionPchomeValue', + 'decisionPchomeDetail', + state.pchomeCount ? `${state.pchomeCount.toLocaleString()} 筆商品` : '等待商品', + state.pchomeCount ? '已可用這批商品反查 MOMO。' : '取得商品後,系統才知道要比哪一批。', + state.pchomeCount ? 'ready' : (keyword ? 'active' : '') + ); + + const momoValue = state.momoCount + ? `${state.momoCount.toLocaleString()} 筆同款` + : state.unitCount + ? `${state.unitCount.toLocaleString()} 筆單位價` + : state.reviewCount + ? `${state.reviewCount.toLocaleString()} 筆待確認` + : '等待候選'; + const momoDetail = state.momoCount + ? '同款可直接進入總價比對。' + : state.unitCount + ? '單品與組合不同,已改用單位價判讀。' + : state.reviewCount + ? '需要確認單品、組合或容量後再使用。' + : '會自動分成同款、單位價、需確認。'; + setDecisionCard( + 'decisionMomoCard', + 'decisionMomoValue', + 'decisionMomoDetail', + momoValue, + momoDetail, + state.momoCount || state.unitCount ? 'ready' : (state.pchomeCount ? 'active' : '') + ); + + const resultValue = state.matchedCount + ? `${state.matchedCount.toLocaleString()} 筆判讀` + : '尚未判讀'; + let resultDetail = '結果會分成檢查售價、主推曝光、觀察賣點。'; + let resultState = state.momoCount ? 'active' : ''; + if (state.matchedCount) { + if (state.urgentCount) { + resultDetail = `${state.urgentCount.toLocaleString()} 筆需先檢查售價或活動。`; + resultState = 'blocked'; + } else if (state.goodCount) { + resultDetail = `${state.goodCount.toLocaleString()} 筆可主推曝光。`; + resultState = 'ready'; + } else { + resultDetail = '價格接近,改看賣點、活動位置與庫存。'; + resultState = 'ready'; + } + } + setDecisionCard('decisionResultCard', 'decisionResultValue', 'decisionResultDetail', resultValue, resultDetail, resultState); + } + + function setDecisionCard(cardId, valueId, detailId, value, detail, state) { + const card = document.getElementById(cardId); + if (card) { + card.className = 'price-decision-card' + ( + state === 'ready' ? ' is-ready' : + state === 'blocked' ? ' is-blocked' : + state === 'active' ? ' is-active' : '' + ); + } + setText(valueId, value); + setText(detailId, detail); + } + + function renderPriceWorkflow(state) { + setWorkflowState('workflowStepScope', state.keyword ? 'done' : 'current'); + setWorkflowState('workflowStepPchome', state.pchomeCount ? 'done' : (state.keyword ? 'current' : '')); + setWorkflowState('workflowStepMomo', state.momoCount || state.unitCount ? 'done' : (state.pchomeCount ? 'current' : '')); + setWorkflowState('workflowStepResult', state.matchedCount ? 'done' : (state.momoCount ? 'current' : '')); + } + + function setWorkflowState(id, state) { + const el = document.getElementById(id); + if (!el) return; + el.className = 'price-workflow-step' + ( + state === 'done' ? ' is-done' : + state === 'current' ? ' is-current' : '' + ); + } + function setNextAction(title, reason, label, action) { setText('priceNextActionTitle', title); setText('priceNextActionReason', reason); @@ -1225,7 +1659,23 @@ La Roche-Posay 安得利防曬液 50ml,920 setText('priceUrgentText', `${urgentCount} 筆`); setText('priceGoodText', `${goodCount} 筆`); setText('priceWatchText', `${watchCount} 筆`); + setText('priceUrgentMetric', urgentCount.toLocaleString()); + setText('priceGoodMetric', goodCount.toLocaleString()); + setText('priceWatchMetric', watchCount.toLocaleString()); setText('priceResultSummary', matches.length ? `共找到 ${matches.length} 筆同款` : '尚未找到同款'); + if (urgentCount > 0) { + setText('priceResultHeadline', `先處理 ${urgentCount.toLocaleString()} 筆 PChome 價格偏高商品`); + setText('priceResultAdvice', '建議先檢查售價、折扣、組合內容與活動曝光,避免高業績商品被外部低價壓住。'); + } else if (goodCount > 0) { + setText('priceResultHeadline', `可主推 ${goodCount.toLocaleString()} 筆 PChome 價格有利商品`); + setText('priceResultAdvice', '建議安排首頁曝光、搜尋關鍵字、活動文案或 EDM,把價格優勢轉成流量。'); + } else if (matches.length) { + setText('priceResultHeadline', '價格差距不大,改看內容與曝光'); + setText('priceResultAdvice', '下一步檢查商品頁賣點、圖片、庫存與活動位置,找出非價格因素。'); + } else { + setText('priceResultHeadline', '尚未找到可判讀同款'); + setText('priceResultAdvice', '請改用更精準的商品名稱、型號或容量搜尋,或先檢查 MOMO 候選。'); + } const denominator = Math.max(matches.length, 1); setWidth('priceUrgentBar', (urgentCount / denominator) * 100); setWidth('priceGoodBar', (goodCount / denominator) * 100); diff --git a/tests/test_frontend_v2_assets.py b/tests/test_frontend_v2_assets.py index cb00f86..3c115ff 100644 --- a/tests/test_frontend_v2_assets.py +++ b/tests/test_frontend_v2_assets.py @@ -445,6 +445,10 @@ def test_ai_intelligence_uses_v2_shell_and_real_runtime_apis(): assert "PChome 業績成長自動化作戰系統" in template assert "MOMO 外部價格參考" in template assert "今日重點總覽" in template + assert "今日任務摘要" in template + assert "growth-executive-strip" in template + assert "growthExecTask" in template + assert "renderGrowthExecutiveSummary" in template assert "商品處理進度" in template assert "外部價格來源" in template assert "nextActionTitle" in template @@ -518,6 +522,15 @@ def test_price_comparison_page_is_action_oriented_and_plain_chinese(): assert "{% extends \"ewoooc_base.html\" %}" in template assert "PChome 商品比價決策台" in template + assert "price-hero-kpis" in template + assert "priceDecisionGrid" in template + assert "檢查範圍" in template + assert "比價流程" in template + assert "price-workflow-strip" in template + assert "price-result-summary-grid" in template + assert "priceResultHeadline" in template + assert "renderPriceDecisionCards" in template + assert "renderPriceWorkflow" in template assert "今天先做:選擇要檢查的商品範圍" in template assert "資料準備狀態" in template assert "priceNextActionButton" in template