This commit is contained in:
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.610"
|
||||
SYSTEM_VERSION = "V10.611"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# PChome 業績成長自動化作戰系統 — AI 競價情報模組 Single Source of Truth
|
||||
|
||||
> **最後更新**: 2026-06-15 (台北時間)
|
||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單優先讀取與 CSV 備援預檢已建立
|
||||
> **適用版本**: V10.610
|
||||
> **最後更新**: 2026-06-16 (台北時間)
|
||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單優先讀取、CSV 備援預檢與前台操作入口已建立
|
||||
> **適用版本**: V10.611
|
||||
|
||||
---
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
- V10.608 新增 `/api/ai/pchome-growth/external-offers/csv-dry-run` 與 AI 情報頁「外部報價預檢」。CSV 預檢只讀、不寫 DB;逐列回報「可使用」「需人工確認」「不能使用」,並支援中文表頭,避免格式小錯造成整批匯入失敗。
|
||||
- V10.609 明確把外部報價主路徑改為自動化:`run_external_offer_sync_task` 每 4 小時將已確認同款的既有比價快取同步進 `external_offers`。CSV 只保留為 API / crawler / provider 失敗時的備援預檢入口,不是日常營運主流程。
|
||||
- V10.610 起 `/api/ai/pchome-growth/opportunities` 優先讀取 `external_offers` 的自動同步資料;只有新資料層缺資料時才 fallback 舊 `competitor_prices`。API stats 會回傳資料來源計數,方便確認作戰清單是否已走新資料層。
|
||||
- V10.611 起 `/ai_intelligence` 是營運使用者主入口:頁首提供「今日作戰入口」,依序連到 PChome 成長作戰、補商品對應、MOMO 外部價格參考與外部報價預檢;作戰清單左側會直接顯示今日優先動作與資料來源摘要。
|
||||
|
||||
## 零之一、12 Agent 決策信封(2026-05-24)
|
||||
|
||||
|
||||
@@ -212,3 +212,10 @@
|
||||
- `external_offers.raw_payload_json` 會保留舊比價快取中的 PChome 公開價,讓新資料層仍可算出 MOMO / PChome 價差。
|
||||
- API stats 新增 `external_data_source_counts`,可看到「自動同步資料層」與「舊比價快取」各有多少筆。
|
||||
- 下一步:把其他比價報表與 AI 告警逐步改讀 `external_offers`,讓 `competitor_prices` 降為 bridge/cache。
|
||||
|
||||
## 14. 2026-06-16 V10.611 作戰入口與資料來源可見化
|
||||
|
||||
- `/ai_intelligence` 是 PChome 業績成長自動化作戰系統的營運主入口,頁首新增「今日作戰入口」:看作戰清單、補商品對應、看可比價商品、預檢備援資料。
|
||||
- PChome 成長作戰區塊會依 API stats 顯示今日優先動作,例如先補商品對應、先處理可直接比價商品,避免使用者只看到數字不知道下一步。
|
||||
- 同區塊新增資料來源摘要,直接顯示「自動同步資料層」或「舊比價快取」各有多少筆,方便確認 V10.610 新資料層是否真的被作戰清單採用。
|
||||
- CSV 預檢在 UI 文案上維持備援定位;日常主流程仍是自動同步外部報價。
|
||||
|
||||
@@ -239,6 +239,92 @@
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.ops-flow {
|
||||
border: 1px solid var(--momo-border-subtle);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
box-shadow: var(--momo-shadow-soft);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.ops-flow-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.ops-flow-note {
|
||||
color: var(--momo-text-muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.ops-flow-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ops-flow-item {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
border: 1px solid rgba(42, 37, 32, 0.1);
|
||||
border-radius: 8px;
|
||||
background: rgba(250, 247, 240, 0.52);
|
||||
color: var(--momo-text-strong);
|
||||
padding: 11px;
|
||||
text-align: left;
|
||||
transition: transform 0.16s ease, border-color 0.16s ease, background-color 0.16s ease;
|
||||
}
|
||||
|
||||
.ops-flow-item:hover,
|
||||
.ops-flow-item:focus {
|
||||
border-color: rgba(172, 92, 58, 0.34);
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.ops-flow-step {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 999px;
|
||||
background: rgba(172, 92, 58, 0.12);
|
||||
color: var(--momo-warm-rust);
|
||||
font-family: var(--momo-font-mono);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.ops-flow-title {
|
||||
margin: 0;
|
||||
color: var(--momo-text-strong);
|
||||
font-size: 0.86rem;
|
||||
font-weight: 900;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.ops-flow-copy,
|
||||
.ops-flow-target {
|
||||
margin: 4px 0 0;
|
||||
color: var(--momo-text-muted);
|
||||
font-size: 0.74rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ops-flow-target {
|
||||
color: var(--momo-warm-rust);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.growth-metric-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@@ -273,6 +359,29 @@
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.growth-action-hint {
|
||||
margin: 10px 0 0;
|
||||
border: 1px solid rgba(172, 92, 58, 0.16);
|
||||
border-radius: 8px;
|
||||
background: rgba(242, 178, 90, 0.14);
|
||||
color: var(--momo-text-strong);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.5;
|
||||
padding: 9px 10px;
|
||||
}
|
||||
|
||||
.growth-data-summary {
|
||||
margin: 8px 0 0;
|
||||
border: 1px solid rgba(42, 37, 32, 0.08);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.68);
|
||||
color: var(--momo-text-muted);
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.45;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.growth-source-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
@@ -574,7 +683,8 @@
|
||||
|
||||
.growth-ops-grid,
|
||||
.offer-dryrun-grid,
|
||||
.growth-metric-row {
|
||||
.growth-metric-row,
|
||||
.ops-flow-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -606,18 +716,62 @@
|
||||
<button class="btn btn-outline-danger btn-sm ai-action-btn" id="btnTrigger" onclick="triggerAnalysis()">
|
||||
<i class="fas fa-bolt me-1"></i>整理建議
|
||||
</button>
|
||||
<button class="btn btn-outline-primary btn-sm ai-action-btn" id="btnPickList" onclick="generatePickList()">
|
||||
<button class="btn btn-outline-primary btn-sm ai-action-btn" id="btnPickList" onclick="generatePickList()" title="依目前業績與比價資料整理商品處理清單">
|
||||
<i class="fas fa-wand-magic-sparkles me-1"></i>產生作戰商品
|
||||
</button>
|
||||
<button class="btn btn-outline-warning btn-sm ai-action-btn" id="btnBackfill" onclick="backfillPchomeMatches()">
|
||||
<button class="btn btn-outline-warning btn-sm ai-action-btn" id="btnBackfill" onclick="backfillPchomeMatches()" title="替還不能比價的 PChome 商品尋找 MOMO 參考">
|
||||
<i class="fas fa-magnifying-glass-chart me-1"></i>補商品對應
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary btn-sm ai-action-btn" onclick="loadDashboard()">
|
||||
<button class="btn btn-outline-secondary btn-sm ai-action-btn" onclick="loadDashboard()" title="重新載入畫面資料">
|
||||
<i class="fas fa-redo me-1"></i>重新整理
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── 今日作戰入口 ── -->
|
||||
<section class="ops-flow" aria-label="今日作戰入口">
|
||||
<div class="ops-flow-head">
|
||||
<span class="ai-panel-title">
|
||||
<i class="fas fa-route"></i>今日作戰入口
|
||||
</span>
|
||||
<span class="ops-flow-note">依清單、對應、比價、備援資料的順序處理</span>
|
||||
</div>
|
||||
<div class="ops-flow-grid">
|
||||
<button type="button" class="ops-flow-item" onclick="scrollToPanel('growthOpsPanel')">
|
||||
<span class="ops-flow-step">1</span>
|
||||
<span>
|
||||
<span class="ops-flow-title">看作戰清單</span>
|
||||
<span class="ops-flow-copy d-block">先看高業績商品與建議動作。</span>
|
||||
<span class="ops-flow-target d-block">PChome 成長作戰</span>
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" class="ops-flow-item" onclick="backfillPchomeMatches()">
|
||||
<span class="ops-flow-step">2</span>
|
||||
<span>
|
||||
<span class="ops-flow-title">補商品對應</span>
|
||||
<span class="ops-flow-copy d-block">待補對應太多時,先接上外部參考價。</span>
|
||||
<span class="ops-flow-target d-block">啟動補商品對應</span>
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" class="ops-flow-item" onclick="scrollToPanel('externalPricePanel')">
|
||||
<span class="ops-flow-step">3</span>
|
||||
<span>
|
||||
<span class="ops-flow-title">看可比價商品</span>
|
||||
<span class="ops-flow-copy d-block">只看已確認同款的 MOMO 價格參考。</span>
|
||||
<span class="ops-flow-target d-block">MOMO 外部價格參考</span>
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" class="ops-flow-item" onclick="scrollToPanel('externalOfferDryRunPanel')">
|
||||
<span class="ops-flow-step">4</span>
|
||||
<span>
|
||||
<span class="ops-flow-title">預檢備援資料</span>
|
||||
<span class="ops-flow-copy d-block">自動來源缺資料時,再檢查 CSV 可不可用。</span>
|
||||
<span class="ops-flow-target d-block">外部報價預檢</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── PChome 成長作戰清單 ── -->
|
||||
<section class="card shadow-sm ai-panel" id="growthOpsPanel">
|
||||
<div class="card-header d-flex justify-content-between align-items-center py-2 bg-white border-bottom">
|
||||
@@ -646,6 +800,8 @@
|
||||
<span>待補對應</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="growth-action-hint" id="growthActionHint">正在判斷今天優先處理順序...</p>
|
||||
<p class="growth-data-summary" id="growthDataSourceSummary">資料來源整理中...</p>
|
||||
<p class="growth-source-note" id="growthSourceNote">來源整理中...</p>
|
||||
<div class="growth-source-list" id="growthSourceReadiness">
|
||||
<div class="growth-source-chip">
|
||||
@@ -764,7 +920,7 @@
|
||||
|
||||
<!-- ── 左:外部價格參考 ── -->
|
||||
<div class="col-xl-7">
|
||||
<div class="card shadow-sm h-100 ai-panel">
|
||||
<div class="card shadow-sm h-100 ai-panel" id="externalPricePanel">
|
||||
<div class="card-header d-flex justify-content-between align-items-center py-2 bg-white border-bottom">
|
||||
<span class="ai-panel-title">
|
||||
<i class="fas fa-balance-scale text-warning me-2"></i>MOMO 外部價格參考
|
||||
@@ -915,6 +1071,12 @@ function escapeHtml(value) {
|
||||
}[ch]));
|
||||
}
|
||||
|
||||
function scrollToPanel(panelId) {
|
||||
const panel = document.getElementById(panelId);
|
||||
if (!panel) return;
|
||||
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
async function loadGrowthOps(forceRefresh = false) {
|
||||
const list = document.getElementById('growthOpsList');
|
||||
if (forceRefresh) {
|
||||
@@ -933,6 +1095,8 @@ async function loadGrowthOps(forceRefresh = false) {
|
||||
document.getElementById('growthCandidateCount').textContent = (stats.candidate_count || 0).toLocaleString();
|
||||
document.getElementById('growthMappedCount').textContent = (stats.mapped_count || 0).toLocaleString();
|
||||
document.getElementById('growthNeedsMapping').textContent = (stats.needs_mapping_count || 0).toLocaleString();
|
||||
renderGrowthActionHint(stats);
|
||||
renderGrowthDataSourceSummary(stats);
|
||||
|
||||
const scope = data.source_scope || {};
|
||||
const active = (scope.active_external_sources || []).join('、') || '尚未接入';
|
||||
@@ -944,6 +1108,8 @@ async function loadGrowthOps(forceRefresh = false) {
|
||||
renderGrowthOps(data.opportunities || []);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
renderGrowthActionHint({ candidate_count: 0, mapped_count: 0, needs_mapping_count: 0 });
|
||||
renderGrowthDataSourceSummary({});
|
||||
list.innerHTML = `<div class="text-center py-4 text-muted">
|
||||
<i class="fas fa-circle-exclamation d-block mb-2"></i>
|
||||
PChome 成長作戰清單暫時無法讀取,請稍後再試。
|
||||
@@ -951,6 +1117,49 @@ async function loadGrowthOps(forceRefresh = false) {
|
||||
}
|
||||
}
|
||||
|
||||
function renderGrowthActionHint(stats) {
|
||||
const hint = document.getElementById('growthActionHint');
|
||||
if (!hint) return;
|
||||
|
||||
const candidateCount = Number(stats.candidate_count || 0);
|
||||
const mappedCount = Number(stats.mapped_count || 0);
|
||||
const needsMapping = Number(stats.needs_mapping_count || 0);
|
||||
|
||||
if (!candidateCount) {
|
||||
hint.textContent = '目前沒有可處理的作戰商品,請先確認 PChome 業績資料已更新。';
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsMapping > 0 && mappedCount === 0) {
|
||||
hint.textContent = `今天先補商品對應:目前 ${needsMapping.toLocaleString()} 件高業績商品還不能直接比價。`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsMapping > 0) {
|
||||
hint.textContent = `先處理 ${mappedCount.toLocaleString()} 件可直接比價商品,再補 ${needsMapping.toLocaleString()} 件商品對應。`;
|
||||
return;
|
||||
}
|
||||
|
||||
hint.textContent = '目前商品都可直接比價,先檢查售價偏高或可以放大價格優勢的項目。';
|
||||
}
|
||||
|
||||
function renderGrowthDataSourceSummary(stats) {
|
||||
const summary = document.getElementById('growthDataSourceSummary');
|
||||
if (!summary) return;
|
||||
|
||||
const counts = stats.external_data_source_counts || {};
|
||||
const entries = Object.entries(counts)
|
||||
.filter(([, count]) => Number(count || 0) > 0)
|
||||
.map(([label, count]) => `${label} ${Number(count).toLocaleString()} 筆`);
|
||||
|
||||
if (!entries.length) {
|
||||
summary.textContent = '資料來源:目前作戰清單尚未接到外部參考價,請先補商品對應。';
|
||||
return;
|
||||
}
|
||||
|
||||
summary.textContent = `資料來源:${entries.join('、')}。`;
|
||||
}
|
||||
|
||||
function renderGrowthSourceReadiness(sources) {
|
||||
const box = document.getElementById('growthSourceReadiness');
|
||||
if (!box) return;
|
||||
|
||||
@@ -155,5 +155,10 @@ def test_ai_intelligence_template_uses_pchome_growth_name_and_endpoint():
|
||||
assert "/api/ai/pchome-growth/opportunities" in template
|
||||
assert "/api/ai/pchome-growth/external-offers/csv-dry-run" in template
|
||||
assert "growthSourceReadiness" in template
|
||||
assert "今日作戰入口" in template
|
||||
assert "growthActionHint" in template
|
||||
assert "growthDataSourceSummary" in template
|
||||
assert "external_data_source_counts" in template
|
||||
assert "scrollToPanel('externalPricePanel')" in template
|
||||
assert "外部報價預檢" in template
|
||||
assert "待補對應" in template
|
||||
|
||||
Reference in New Issue
Block a user