From fdaa4bb2c98db9b5f79daf995f9f944e7deea796 Mon Sep 17 00:00:00 2001 From: ogt Date: Fri, 26 Jun 2026 19:16:25 +0800 Subject: [PATCH] fix: make pchome source exports operator friendly --- config.py | 2 +- docs/AI_INTELLIGENCE_MODULE_SOT.md | 3 +- templates/pchome_crawler.html | 117 +++++++++++++++++++++-------- tests/test_frontend_v2_assets.py | 10 ++- 4 files changed, 99 insertions(+), 33 deletions(-) diff --git a/config.py b/config.py index 0dbdc1b..6034712 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.718" +SYSTEM_VERSION = "V10.719" 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 88a96b6..999ab19 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -2,7 +2,7 @@ > **最後更新**: 2026-06-26 (台北時間) > **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單與價格參考表優先讀取、CSV 備援預檢、前台操作入口、高可見頁面繁中化守門、比價/作戰 UI 工作台化、跨平台來源治理與商品身份 UI 契約已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 -> **適用版本**: V10.718 +> **適用版本**: V10.719 --- @@ -803,3 +803,4 @@ POSTGRES_HOST=momo-db | 2026-06-26 | 首頁覆核候選必須標示為待確認商品 | V10.716 起首頁 PChome 覆核區不再使用「PChome 候選」作為可見主語,統一改為「PChome 待確認商品」與「開 PChome 待確認商品」,避免使用者把未確認同款誤認為正式比價結果。 | | 2026-06-26 | 供貨風險頁不得使用資料表或英文模組名作為主語 | V10.717 起缺貨清單與補貨通知頁統一使用「供貨風險、缺貨處理清單、補貨通知紀錄」等營運語言,不再顯示「缺貨資料表、缺貨資料、Vendor Stockout」等資料庫或英文模組感文案。 | | 2026-06-26 | AI 觀測頁不得外露 caller key | V10.718 起 AI 品質診斷與知識召回頁使用「使用情境」作為可見主語,並透過 `obs_label.caller()` 顯示營運名稱;前台不得直接顯示 `{{ caller }}`、`top_k` 或「全部呼叫端」等工程語言。 | +| 2026-06-26 | 商品來源頁不得提供 raw JSON 匯出 | V10.719 起 `/pchome_crawler` 改為「PChome 商品監控」營運清單,只提供表格與賣場清單 CSV;前台不得出現 `exportJson`、`JSON.stringify(currentProducts)`、`圖片URL`、`商品URL` 或 raw JSON 檔名。 | diff --git a/templates/pchome_crawler.html b/templates/pchome_crawler.html index e1eb3e9..26dfaea 100644 --- a/templates/pchome_crawler.html +++ b/templates/pchome_crawler.html @@ -78,6 +78,31 @@ color: var(--momo-tag-muted-text); } +.pchome-product-title { + color: var(--momo-text-primary); + font-weight: 800; + line-height: 1.45; +} + +.pchome-product-meta { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 6px; + color: var(--momo-text-secondary); + font-size: var(--momo-text-small); +} + +.pchome-product-meta span { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 2px 7px; + border: 1px solid var(--momo-border-light); + border-radius: var(--momo-radius-sm); + background: var(--momo-bg-paper); +} + .pchome-toast { top: 20px; right: 20px; @@ -127,7 +152,7 @@

PChome 24h 商品監控

-

補齊 PChome 商品資料,支援同款與價差判斷。

+

補齊 PChome 商品、售價、庫存與賣場連結,支援同款確認、價差與促銷監控。

@@ -237,8 +262,8 @@ -
@@ -343,7 +368,7 @@ // 匯出 document.getElementById('exportExcelBtn').addEventListener('click', exportExcel); - document.getElementById('exportJsonBtn').addEventListener('click', exportJson); + document.getElementById('exportStoreLinksBtn').addEventListener('click', exportStoreLinks); } async function crawlRegion() { @@ -465,33 +490,47 @@ tbody.innerHTML = ''; for (const p of products) { - const discount = p.discount ? `-${p.discount}%` : '-'; - const stockBadge = p.stock > 0 - ? `${p.stock}` + const productName = String(p.name || 'PChome 商品'); + const productId = String(p.product_id || '').trim(); + const productUrl = String(p.product_url || '').trim(); + const imageUrl = String(p.image_url || '').trim(); + const price = toNumber(p.price); + const originalPrice = toNumber(p.original_price); + const discountValue = toNumber(p.discount); + const stock = toNumber(p.stock); + const discount = discountValue > 0 ? `-${discountValue}%` : '-'; + const stockBadge = stock > 0 + ? `${stock}` : `缺貨`; + const imageSrc = imageUrl ? `${escapeHtml(imageUrl)}?width=80` : '/static/images/no-image.png'; + const productTitle = `${escapeHtml(productName.substring(0, 50))}${productName.length > 50 ? '...' : ''}`; + const productLink = productUrl + ? `${productTitle}` + : `${productTitle}`; + const storeAction = productUrl + ? `賣場` + : ``; const row = document.createElement('tr'); row.innerHTML = ` - - - ${escapeHtml(p.name.substring(0, 50))}${p.name.length > 50 ? '...' : ''} - -
- ${p.product_id} + ${productLink} +
+ 商品編號 ${escapeHtml(productId || '待補')} + PChome 24h 官方賣場 +
- $${p.price.toLocaleString()} - $${p.original_price.toLocaleString()} + ${formatMoney(price)} + ${originalPrice ? `${formatMoney(originalPrice)}` : '-'} ${discount} ${stockBadge} - - - + ${storeAction} `; tbody.appendChild(row); @@ -504,31 +543,36 @@ return; } - // 建立 CSV - const headers = ['商品ID', '名稱', '售價', '原價', '折扣%', '庫存', '圖片URL', '商品URL']; + const headers = ['商品編號', '商品名稱', 'PChome 售價', 'PChome 原價', '折扣', '庫存狀態', '商品圖片', 'PChome 賣場']; const rows = currentProducts.map(p => [ - p.product_id, - `"${p.name.replace(/"/g, '""')}"`, - p.price, - p.original_price, + csvCell(p.product_id || ''), + csvCell(p.name || ''), + toNumber(p.price), + toNumber(p.original_price), p.discount || '', - p.stock, - p.image_url, - p.product_url + toNumber(p.stock), + csvCell(p.image_url || ''), + csvCell(p.product_url || '') ]); const csv = '\uFEFF' + [headers.join(','), ...rows.map(r => r.join(','))].join('\n'); downloadFile(csv, 'pchome_products.csv', 'text/csv;charset=utf-8'); } - function exportJson() { + function exportStoreLinks() { if (!currentProducts.length) { showToast('沒有資料可匯出', 'warning'); return; } - const json = JSON.stringify(currentProducts, null, 2); - downloadFile(json, 'pchome_products.json', 'application/json'); + const headers = ['商品編號', '商品名稱', 'PChome 賣場']; + const rows = currentProducts.map(p => [ + csvCell(p.product_id || ''), + csvCell(p.name || ''), + csvCell(p.product_url || '') + ]); + const csv = '\uFEFF' + [headers.join(','), ...rows.map(r => r.join(','))].join('\n'); + downloadFile(csv, 'pchome_store_links.csv', 'text/csv;charset=utf-8'); } function downloadFile(content, filename, type) { @@ -547,6 +591,19 @@ return div.innerHTML; } + function toNumber(value) { + const number = Number(value); + return Number.isFinite(number) ? number : 0; + } + + function formatMoney(value) { + return `$${toNumber(value).toLocaleString()}`; + } + + function csvCell(value) { + return `"${String(value).replace(/"/g, '""')}"`; + } + function showToast(message, type = 'info') { // 簡易 Toast const toast = document.createElement('div'); diff --git a/tests/test_frontend_v2_assets.py b/tests/test_frontend_v2_assets.py index 9dbf740..35eed09 100644 --- a/tests/test_frontend_v2_assets.py +++ b/tests/test_frontend_v2_assets.py @@ -197,8 +197,16 @@ def test_growth_workflow_pages_hide_raw_export_and_fallback_content(): assert "PChome 商品監控" in pchome_crawler assert "商品清單" in pchome_crawler - assert "下載完整清單" in pchome_crawler + assert "下載賣場清單" in pchome_crawler + assert "商品編號" in pchome_crawler + assert "PChome 24h 官方賣場" in pchome_crawler assert "匯出 JSON" not in pchome_crawler + assert "下載完整清單" not in pchome_crawler + assert "exportJson" not in pchome_crawler + assert "JSON.stringify(currentProducts" not in pchome_crawler + assert "pchome_products.json" not in pchome_crawler + assert "圖片URL" not in pchome_crawler + assert "商品URL" not in pchome_crawler assert "PChome 爬蟲" not in pchome_crawler assert "爬蟲" not in pchome_crawler