fix: route formal homepage to growth command center
All checks were successful
CD Pipeline / deploy (push) Successful in 1m3s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m3s
This commit is contained in:
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.651"
|
||||
SYSTEM_VERSION = "V10.652"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
- V10.649 起 `/ai_intelligence` 必須提供銷售策略建議看板,把商品分成價格防守、主推曝光、組合/單位價、資料補齊等營運路徑;每張策略卡需顯示件數、近 7 天業績、代表商品與可點擊下一步,點擊後必須切到對應商品明細。
|
||||
- V10.650 起 `/ai_intelligence` 必須提供「今日策略動作」清單,從作戰商品中挑出前 5 件具體行動;每列需顯示處理順序、動作、商品、近 7 天業績、原因與可點擊的詳情/處理入口,避免使用者只看到分類與策略後仍不知道下一步要做哪一件商品。
|
||||
- V10.651 起從「今日策略動作」或其他非明細列入口打開單品作戰詳情時,商品明細列表中的對應商品仍必須標示為目前選取;使用者需能看出詳情與明細列的關聯。
|
||||
- V10.652 起正式首頁 `/` 必須導向「PChome 業績成長自動化作戰系統」,舊商品看板僅保留在 `/dashboard` 或 `/product-dashboard`;「今日策略動作」必須放在首屏任務摘要後方,不能只藏在商品明細區;每列必須直接顯示價格證據,至少包含 PChome、MOMO、差距與可信度四格。候選待確認或缺資料時需以待確認/待補呈現,不得要求使用者先打開詳情才知道判斷依據。
|
||||
|
||||
## 零之一、12 Agent 決策信封(2026-05-24)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import pickle
|
||||
import threading
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from types import SimpleNamespace
|
||||
from flask import Blueprint, request, render_template, jsonify
|
||||
from flask import Blueprint, request, render_template, jsonify, redirect, url_for
|
||||
from sqlalchemy import func, and_, text, bindparam
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
@@ -2624,6 +2624,14 @@ def get_pchome_review_queue_api():
|
||||
@dashboard_bp.route('/')
|
||||
@login_required
|
||||
def index():
|
||||
"""正式首頁:PChome 業績成長自動化作戰系統。"""
|
||||
return redirect(url_for('ai.ai_intelligence'))
|
||||
|
||||
|
||||
@dashboard_bp.route('/dashboard')
|
||||
@dashboard_bp.route('/product-dashboard')
|
||||
@login_required
|
||||
def product_dashboard():
|
||||
"""商品看板首頁"""
|
||||
db = DatabaseManager()
|
||||
session = db.get_session()
|
||||
|
||||
@@ -1119,6 +1119,40 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.growth-action-evidence {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 5px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.growth-action-evidence-chip {
|
||||
border: 1px solid rgba(42, 37, 32, 0.08);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.64);
|
||||
min-width: 0;
|
||||
padding: 5px 6px;
|
||||
}
|
||||
|
||||
.growth-action-evidence-label {
|
||||
color: var(--momo-text-muted);
|
||||
display: block;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 950;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.growth-action-evidence-value {
|
||||
color: var(--momo-text-strong);
|
||||
display: block;
|
||||
font-family: var(--momo-font-mono);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 950;
|
||||
line-height: 1.25;
|
||||
margin-top: 2px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.growth-decision-panel {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
@@ -2018,6 +2052,10 @@
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.growth-action-evidence {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.growth-detail-price-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -2092,6 +2130,16 @@
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="growth-action-board" id="growthActionBoard" aria-label="今日策略動作">
|
||||
<div class="growth-action-board-head">
|
||||
<h3 class="growth-action-board-title">今日策略動作</h3>
|
||||
<span class="growth-action-board-note">照順序處理最可能影響業績的商品</span>
|
||||
</div>
|
||||
<div class="growth-action-list">
|
||||
<div class="text-center py-3 text-muted">整理動作中...</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── 今日重點總覽 ── -->
|
||||
<section class="ops-flow" aria-label="今日重點總覽">
|
||||
<div class="ops-flow-head">
|
||||
@@ -2231,15 +2279,6 @@
|
||||
<div class="text-center py-3 text-muted">整理策略中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="growth-action-board" id="growthActionBoard" aria-label="今日策略動作">
|
||||
<div class="growth-action-board-head">
|
||||
<h3 class="growth-action-board-title">今日策略動作</h3>
|
||||
<span class="growth-action-board-note">照順序處理最可能影響業績的商品</span>
|
||||
</div>
|
||||
<div class="growth-action-list">
|
||||
<div class="text-center py-3 text-muted">整理動作中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="growth-decision-panel" id="growthDecisionSummary">
|
||||
<div>
|
||||
<h3 class="growth-decision-title">正在整理處理建議</h3>
|
||||
@@ -3606,6 +3645,40 @@ function growthActionPlanForRow(row) {
|
||||
};
|
||||
}
|
||||
|
||||
function growthActionEvidence(row) {
|
||||
const price = row?.external_price || null;
|
||||
const candidate = row?.review_candidate || null;
|
||||
const gap = price && price.gap_pct !== null && price.gap_pct !== undefined ? Number(price.gap_pct) : null;
|
||||
const quality = Math.round(rowQualityScore(row));
|
||||
return [
|
||||
{
|
||||
label: 'PChome',
|
||||
value: price ? formatGrowthDetailPrice(price, 'pchome') : candidate ? formatPriceAmount(candidate.pchome_price) : '待補',
|
||||
},
|
||||
{
|
||||
label: 'MOMO',
|
||||
value: price ? formatGrowthDetailPrice(price, 'momo') : candidate ? formatPriceAmount(candidate.momo_price) : '待補',
|
||||
},
|
||||
{
|
||||
label: '差距',
|
||||
value: candidate ? '候選待確認' : gap === null ? '待判斷' : formatGapDisplay(gap),
|
||||
},
|
||||
{
|
||||
label: '可信度',
|
||||
value: quality ? `${quality}%` : '待補',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function renderGrowthActionEvidence(row) {
|
||||
return `<div class="growth-action-evidence">
|
||||
${growthActionEvidence(row).map((item) => `<span class="growth-action-evidence-chip">
|
||||
<span class="growth-action-evidence-label">${escapeHtml(item.label)}</span>
|
||||
<span class="growth-action-evidence-value">${escapeHtml(item.value)}</span>
|
||||
</span>`).join('')}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderGrowthActionBoard() {
|
||||
const board = document.getElementById('growthActionBoard');
|
||||
if (!board) return;
|
||||
@@ -3639,6 +3712,7 @@ function renderGrowthActionBoard() {
|
||||
<div>
|
||||
<span class="growth-action-pill">${escapeHtml(plan.label)}</span>
|
||||
<p class="growth-action-meta">近 7 天 ${escapeHtml(formatMoney(row.sales_7d))}</p>
|
||||
${renderGrowthActionEvidence(row)}
|
||||
</div>
|
||||
<div class="growth-action-buttons">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary table-row-action" data-growth-action="show-product-detail" data-product-key="${key}">詳情</button>
|
||||
|
||||
@@ -57,6 +57,7 @@ def _seed_growth_external_offers(engine):
|
||||
source_product_id TEXT,
|
||||
source_offer_key TEXT,
|
||||
title TEXT,
|
||||
product_url TEXT,
|
||||
price REAL,
|
||||
observed_at TEXT,
|
||||
expires_at TEXT,
|
||||
@@ -73,13 +74,13 @@ def _seed_growth_external_offers(engine):
|
||||
conn.execute(text("""
|
||||
INSERT INTO external_offers (
|
||||
id, source_code, platform_code, source_product_id, source_offer_key,
|
||||
title, price, observed_at, expires_at, ingestion_method,
|
||||
title, product_url, price, observed_at, expires_at, ingestion_method,
|
||||
pchome_product_id, momo_sku, match_status, quality_score,
|
||||
data_quality_status, quality_notes_json, raw_payload_json
|
||||
)
|
||||
VALUES (
|
||||
1, 'momo_reference', 'momo', 'MOMO-NEW', 'momo_reference:MOMO-NEW:PCH-1',
|
||||
'MOMO 新資料層商品', 870, '2026-06-14 12:00:00', NULL, 'legacy_competitor_cache',
|
||||
'MOMO 新資料層商品', 'https://www.momoshop.com.tw/goods/MOMO-NEW', 870, '2026-06-14 12:00:00', NULL, 'legacy_competitor_cache',
|
||||
'PCH-1', 'MOMO-NEW', 'verified', 92,
|
||||
'verified', '["自動同步"]',
|
||||
'{"pchome_public_price": 1000, "pchome_public_name": "PChome 公開商品"}'
|
||||
@@ -97,6 +98,7 @@ def _seed_growth_unit_price_external_offer(engine):
|
||||
source_product_id TEXT,
|
||||
source_offer_key TEXT,
|
||||
title TEXT,
|
||||
product_url TEXT,
|
||||
price REAL,
|
||||
observed_at TEXT,
|
||||
expires_at TEXT,
|
||||
@@ -113,13 +115,13 @@ def _seed_growth_unit_price_external_offer(engine):
|
||||
conn.execute(text("""
|
||||
INSERT INTO external_offers (
|
||||
id, source_code, platform_code, source_product_id, source_offer_key,
|
||||
title, price, observed_at, expires_at, ingestion_method,
|
||||
title, product_url, price, observed_at, expires_at, ingestion_method,
|
||||
pchome_product_id, momo_sku, match_status, quality_score,
|
||||
data_quality_status, quality_notes_json, raw_payload_json
|
||||
)
|
||||
VALUES (
|
||||
1, 'momo_reference', 'momo', 'MOMO-UNIT', 'momo_reference:MOMO-UNIT:PCH-1:unit_price',
|
||||
'MOMO 單位價商品', 468, '2026-06-14 12:00:00', NULL, 'targeted_momo_search',
|
||||
'MOMO 單位價商品', 'https://www.momoshop.com.tw/goods/MOMO-UNIT', 468, '2026-06-14 12:00:00', NULL, 'targeted_momo_search',
|
||||
'PCH-1', 'MOMO-UNIT', 'verified', 82,
|
||||
'verified', '["自動單位價比較"]',
|
||||
'{"price_basis": "unit_price", "pchome_public_price": 920, "pchome_public_name": "PChome 公開商品", "tags": ["identity_v2", "price_basis_unit_price"], "unit_price_comparison": {"unit_label": "ml", "momo_unit_price": 11.7, "competitor_unit_price": 23.0, "momo_total_quantity": 40, "competitor_total_quantity": 40, "unit_gap_pct": -49.13}}'
|
||||
@@ -487,6 +489,8 @@ def test_ai_intelligence_template_uses_pchome_growth_name_and_endpoint():
|
||||
assert "今日策略動作" in template
|
||||
assert "renderGrowthActionBoard" in template
|
||||
assert "growthActionPlanForRow" in template
|
||||
assert "growthActionEvidence" in template
|
||||
assert "growth-action-evidence-chip" in template
|
||||
assert "document.querySelectorAll('.growth-detail-row')" in template
|
||||
assert "scrollToPanel('externalPricePanel')" in template
|
||||
assert "備援資料檢查" in template
|
||||
@@ -495,3 +499,14 @@ def test_ai_intelligence_template_uses_pchome_growth_name_and_endpoint():
|
||||
assert "鎖定商品" in template
|
||||
assert "無法比價" in template
|
||||
assert "補齊比價資料" in template
|
||||
|
||||
|
||||
def test_formal_homepage_routes_to_growth_command_center():
|
||||
from pathlib import Path
|
||||
|
||||
route_source = Path("routes/dashboard_routes.py").read_text(encoding="utf-8")
|
||||
|
||||
assert "@dashboard_bp.route('/')" in route_source
|
||||
assert "url_for('ai.ai_intelligence')" in route_source
|
||||
assert "@dashboard_bp.route('/dashboard')" in route_source
|
||||
assert "@dashboard_bp.route('/product-dashboard')" in route_source
|
||||
|
||||
Reference in New Issue
Block a user