diff --git a/config.py b/config.py index 443ead2..5658cbe 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.649" +SYSTEM_VERSION = "V10.650" 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 dea4c8e..c98ca14 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -85,6 +85,7 @@ - V10.647 起 `/ai_intelligence` 的商品明細每一筆都必須能打開單品作戰詳情,詳情需顯示商品、建議動作、近 7 天業績、業績變化、PChome/MOMO 價格證據、價差、可信度、判斷原因與下一步操作;不得只讓使用者看一排文字後自行猜測。 - V10.648 起 `/ai_intelligence` 的商品明細上方必須提供分類策略看板,把商品依分類彙總成可點擊的數據條列;每列至少顯示分類、近 7 天業績、商品數、價格壓力、價格優勢、缺比價、待確認與建議下一步。點擊分類後必須切到該分類商品明細。 - V10.649 起 `/ai_intelligence` 必須提供銷售策略建議看板,把商品分成價格防守、主推曝光、組合/單位價、資料補齊等營運路徑;每張策略卡需顯示件數、近 7 天業績、代表商品與可點擊下一步,點擊後必須切到對應商品明細。 +- V10.650 起 `/ai_intelligence` 必須提供「今日策略動作」清單,從作戰商品中挑出前 5 件具體行動;每列需顯示處理順序、動作、商品、近 7 天業績、原因與可點擊的詳情/處理入口,避免使用者只看到分類與策略後仍不知道下一步要做哪一件商品。 ## 零之一、12 Agent 決策信封(2026-05-24) diff --git a/templates/ai_intelligence.html b/templates/ai_intelligence.html index 2e17cf6..a1abb85 100644 --- a/templates/ai_intelligence.html +++ b/templates/ai_intelligence.html @@ -1023,6 +1023,102 @@ height: 100%; } + .growth-action-board { + border: 1px solid rgba(42, 37, 32, 0.1); + border-radius: 8px; + background: rgba(255, 255, 255, 0.72); + margin-bottom: 10px; + padding: 10px; + } + + .growth-action-board-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 8px; + } + + .growth-action-board-title { + color: var(--momo-text-strong); + font-size: 0.84rem; + font-weight: 950; + margin: 0; + } + + .growth-action-board-note { + color: var(--momo-text-muted); + font-size: 0.68rem; + font-weight: 820; + text-align: right; + } + + .growth-action-list { + display: grid; + gap: 7px; + } + + .growth-action-row { + border: 1px solid rgba(42, 37, 32, 0.08); + border-radius: 8px; + background: rgba(250, 247, 240, 0.58); + display: grid; + grid-template-columns: auto minmax(0, 1.1fr) minmax(170px, 0.55fr) auto; + gap: 10px; + align-items: center; + padding: 9px 10px; + } + + .growth-action-rank { + align-items: center; + background: rgba(172, 92, 58, 0.12); + border-radius: 999px; + color: #8f4d33; + display: inline-flex; + font-family: var(--momo-font-mono); + font-size: 0.72rem; + font-weight: 950; + height: 34px; + justify-content: center; + width: 34px; + } + + .growth-action-title { + color: var(--momo-text-strong); + font-size: 0.8rem; + font-weight: 950; + line-height: 1.3; + margin: 0; + } + + .growth-action-meta, + .growth-action-reason { + color: var(--momo-text-muted); + font-size: 0.68rem; + font-weight: 820; + line-height: 1.35; + margin: 3px 0 0; + } + + .growth-action-pill { + border: 1px solid rgba(172, 92, 58, 0.18); + border-radius: 999px; + color: #8f4d33; + display: inline-flex; + font-size: 0.68rem; + font-weight: 950; + justify-content: center; + padding: 4px 8px; + white-space: nowrap; + } + + .growth-action-buttons { + display: flex; + flex-wrap: wrap; + gap: 6px; + justify-content: flex-end; + } + .growth-decision-panel { display: grid; grid-template-columns: minmax(0, 1fr) auto; @@ -1901,6 +1997,27 @@ min-height: 0; } + .growth-action-board-head { + align-items: flex-start; + flex-direction: column; + } + + .growth-action-board-note { + text-align: left; + } + + .growth-action-row { + grid-template-columns: 1fr; + } + + .growth-action-buttons { + justify-content: stretch; + } + + .growth-action-buttons .table-row-action { + flex: 1 1 100%; + } + .growth-detail-price-grid { grid-template-columns: 1fr; } @@ -2114,6 +2231,15 @@
整理策略中...
+
+
+

今日策略動作

+ 照順序處理最可能影響業績的商品 +
+
+
整理動作中...
+
+

正在整理處理建議

@@ -3419,6 +3545,109 @@ function showGrowthPlaybookDetail(kind) { showGrowthDetail(target); } +function growthActionPlanForRow(row) { + const flags = classifyGrowthRow(row); + const next = growthRowNextAction(row); + const price = row?.external_price || null; + const gap = price && price.gap_pct !== null && price.gap_pct !== undefined ? Number(price.gap_pct) : null; + if (flags.risk) { + return { + label: '價格防守', + title: '檢查售價、券或活動', + reason: gap === null ? 'MOMO 參考價較低,先檢查價格壓力。' : formatGapDisplay(gap), + next, + }; + } + if (flags.advantage) { + return { + label: '主推曝光', + title: '放大價格優勢', + reason: gap === null ? 'PChome 有外部價格優勢,適合提高曝光。' : formatGapDisplay(gap), + next, + }; + } + if (isBundleOrUnitRow(row)) { + return { + label: '組合 / 單位價', + title: '檢查組合包與單位價', + reason: '先確認容量、入數與單位價,再決定組合或券。', + next, + }; + } + if (flags.review) { + return { + label: '候選確認', + title: '確認 MOMO 候選是否同款', + reason: '同款確認後才會進入價格判斷。', + next, + }; + } + if (flags.needs) { + return { + label: '補資料', + title: '補齊 MOMO 參考商品', + reason: '目前缺外部參考價,先補資料才有可行動建議。', + next, + }; + } + if (Number(row?.sales_delta_pct || 0) < 0) { + return { + label: '找回動能', + title: '檢查下滑商品', + reason: `近 7 天業績變化 ${Number(row.sales_delta_pct || 0).toFixed(1)}%。`, + next, + }; + } + return { + label: '追蹤', + title: '維持追蹤', + reason: '目前沒有明確價格或資料風險。', + next, + }; +} + +function renderGrowthActionBoard() { + const board = document.getElementById('growthActionBoard'); + if (!board) return; + const list = board.querySelector('.growth-action-list'); + if (!list) return; + const rows = (Array.isArray(latestGrowthRows) ? latestGrowthRows : []) + .map((row) => ({ row, plan: growthActionPlanForRow(row) })) + .filter((item) => item.plan.label !== '追蹤') + .sort((a, b) => Number(b.row.priority_score || 0) - Number(a.row.priority_score || 0)) + .slice(0, 5); + + if (!rows.length) { + list.innerHTML = `
目前沒有需要立即處理的商品。
`; + return; + } + + list.innerHTML = rows.map((item, index) => { + const row = item.row; + const plan = item.plan; + const key = escapeHtml(growthRowKey(row)); + const nextAttrs = plan.next.action === 'backfill' + ? 'data-growth-action="backfill"' + : `data-growth-action="${plan.next.action}" data-product-key="${key}"`; + return `
+ ${String(index + 1).padStart(2, '0')} +
+

${escapeHtml(plan.title)}

+

${escapeHtml(row.product_name || '未命名商品')}

+

${escapeHtml(plan.reason)}

+
+
+ ${escapeHtml(plan.label)} +

近 7 天 ${escapeHtml(formatMoney(row.sales_7d))}

+
+
+ + +
+
`; + }).join(''); +} + function renderGrowthStrategySummary() { const box = document.getElementById('growthStrategyGrid'); if (!box) return; @@ -3602,6 +3831,7 @@ function renderGrowthDetail(kind = activeGrowthDetailKind) { renderGrowthStrategySummary(); renderGrowthCategoryBoard(); renderGrowthPlaybookBoard(); + renderGrowthActionBoard(); const [title, subtitle] = growthDetailConfig(activeGrowthDetailKind); const rows = growthDetailRows(activeGrowthDetailKind); diff --git a/tests/test_pchome_revenue_growth_service.py b/tests/test_pchome_revenue_growth_service.py index bafb187..dab4b55 100644 --- a/tests/test_pchome_revenue_growth_service.py +++ b/tests/test_pchome_revenue_growth_service.py @@ -483,6 +483,10 @@ def test_ai_intelligence_template_uses_pchome_growth_name_and_endpoint(): assert "銷售策略建議" in template assert "showGrowthPlaybookDetail" in template assert "組合 / 單位價" in template + assert "growthActionBoard" in template + assert "今日策略動作" in template + assert "renderGrowthActionBoard" in template + assert "growthActionPlanForRow" in template assert "scrollToPanel('externalPricePanel')" in template assert "備援資料檢查" in template assert "外部報價預檢" not in template