diff --git a/config.py b/config.py index 320e0a3..395bd10 100644 --- a/config.py +++ b/config.py @@ -325,7 +325,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.433" +SYSTEM_VERSION = "V10.434" 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 3ec7731..c96472c 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -2,7 +2,7 @@ > **最後更新**: 2026-05-24 (台北時間) > **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯,Gemini 備援預設關閉 -> **適用版本**: V10.433 +> **適用版本**: V10.434 --- @@ -80,7 +80,7 @@ SQL漏斗(~300筆) - 比對覆蓋率補強入口:`POST /api/ai/pchome-match/backfill`,優先補抓仍無有效 PChome 配對的高價 ACTIVE 商品,完成後自動重算 AI 挑品清單。 - 排程閉環:`run_pchome_match_backfill_task` 每日 10:30 執行,補抓 PChome 待比對商品、寫入歷史價格,再重算 `strategy='product_pick'` 清單。 - PChome / MOMO 競價摘要出口 `services/competitor_intel_repository.py` 使用 30 分鐘共享快取(`COMPETITOR_INTEL_CACHE_TTL_SECONDS` 可調),避免 `/growth_analysis`、`/daily_sales`、PPT/AI 報表每次請求重跑昂貴覆蓋率與價差趨勢查詢;`run_competitor_price_feeder_task` 與 PChome backfill 完成後會主動清除快取。快取只包摘要輸出,不改 matcher 的高信心門檻與 identity_v2 準確性規則。 -- 商品看板第一屏:`/` 的 V2 看板直接以 `products`、`price_records`、`competitor_prices`、`competitor_match_attempts`、`competitor_match_reviews`、`ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品、待比對優先清單與 PChome 覆核隊列;`filter=ai_picks` 可查看 50 品 AI 挑品列表,`filter=pchome_review` 可直接查看需人工處理的比價覆核 SKU,並以 DB 分頁支援 search/category/status 後的完整隊列,不得只截前 50 筆。覆核狀態篩選必須至少包含全部、需單位價、已排除、低信心、價格過期與找不到同款,讓人工可依 matcher 診斷類型分批處理。列內顯示候選 PChome 商品、候選價、match score、單位價換算摘要、人工動作與 matcher 診斷原因標籤(品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等),不得只顯示籠統「待比對」。`/api/export/excel/pchome-review` 必須匯出同一套覆核隊列、人工處置、候選 PChome、單位價比較與原始診斷,讓人工覆核、簡報與後續 AI 分析共用同一份證據。`/api/pchome-review//decision` 是人工閉環入口:`accept_identity` 才可把候選寫入 `competitor_prices` 與 `competitor_price_history` 並打上 `manual_review/manual_accept/identity_v2`;`reject_identity` 與 `unit_price_required` 只寫 `competitor_match_reviews` 並追加 manual attempt,不得把不同販售組合或否決候選灌入正式價差。PChome feeder 後續搜尋同一候選時必須讀取 `competitor_match_reviews`:已否決候選寫 `manual_rejected` 並跳過正式寫入,且必須繼續評估下一個候選,不能讓已否決候選長期阻塞同 SKU;已標記單位價候選寫 `manual_unit_price_required`;已採用候選可保守補到最低門檻並保留 `manual_review/manual_accept` 標籤。搜尋候選池只有強同款分數達 `0.90` 才可提前停止,避免 0.76 灰區候選卡掉後續更精準搜尋詞。人工 `reject_identity`、`unit_price_required`、`needs_research` 若命中當前正式候選,必須將同候選 `competitor_prices` 過期,不得繼續顯示正式總價差。商品列表必須將 `manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 顯示為明確人工閉環狀態,不可回落成籠統「待比對」。`fetch_competitor_coverage()` 必須輸出人工採用、人工否決、人工單位價與採用率,daily/growth/PPT 共用 payload 必須顯示人工閉環成效,避免只呈現待審數。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。 +- 商品看板第一屏:`/` 的 V2 看板直接以 `products`、`price_records`、`competitor_prices`、`competitor_match_attempts`、`competitor_match_reviews`、`ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品、待比對優先清單與 PChome 覆核隊列;`filter=ai_picks` 可查看 50 品 AI 挑品列表,`filter=pchome_review` 可直接查看需人工處理的比價覆核 SKU,並以 DB 分頁支援 search/category/status 後的完整隊列,不得只截前 50 筆。覆核狀態篩選必須至少包含全部、需單位價、已排除、低信心、價格過期、找不到同款與人工閉環,讓人工可依 matcher 診斷類型分批處理。列內顯示候選 PChome 商品、候選價、match score、單位價換算摘要、人工動作與 matcher 診斷原因標籤(品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等),不得只顯示籠統「待比對」。`/api/export/excel/pchome-review` 必須匯出同一套覆核隊列、人工處置、候選 PChome、單位價比較與原始診斷,讓人工覆核、簡報與後續 AI 分析共用同一份證據。`/api/pchome-review//decision` 是人工閉環入口:`accept_identity` 才可把候選寫入 `competitor_prices` 與 `competitor_price_history` 並打上 `manual_review/manual_accept/identity_v2`;`reject_identity`、`unit_price_required` 與 `needs_research` 只寫 `competitor_match_reviews` 並追加 manual attempt,不得把不同販售組合或否決候選灌入正式價差。PChome feeder 後續搜尋同一候選時必須讀取 `competitor_match_reviews`:已否決候選寫 `manual_rejected` 並跳過正式寫入,且必須繼續評估下一個候選,不能讓已否決候選長期阻塞同 SKU;已標記單位價候選寫 `manual_unit_price_required`;已要求補搜尋候選寫 `manual_needs_research` 並停留在覆核隊列;已採用候選可保守補到最低門檻並保留 `manual_review/manual_accept` 標籤。搜尋候選池只有強同款分數達 `0.90` 才可提前停止,避免 0.76 灰區候選卡掉後續更精準搜尋詞。人工 `reject_identity`、`unit_price_required`、`needs_research` 若命中當前正式候選,必須將同候選 `competitor_prices` 過期,不得繼續顯示正式總價差。商品列表必須將 `manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 顯示為明確人工閉環狀態,不可回落成籠統「待比對」。`fetch_competitor_coverage()` 必須輸出人工採用、人工否決、人工單位價與採用率,daily/growth/PPT 共用 payload 必須顯示人工閉環成效,避免只呈現待審數。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。 | 角色 | 模型 | 主機 | 成本 | 每日限額 | |------|------|------|------|---------| diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index 00be519..58bd62b 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-05-24:PChome 近門檻身份回收第二輪 +- **V10.434 PChome 人工覆核閉環補搜尋**: 商品看板 PChome review queue 新增「補搜尋」人工決策按鈕,對應 `needs_research` → `manual_needs_research`;`manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 納入全部覆核隊列與「人工閉環」篩選,避免操作員按完否決/單位價/補搜尋後項目從列表消失、後續無法追蹤。 - **V10.433 PChome 覆核診斷標籤與 variant 回刷補強**: `competitor_intel_repository` 的 review queue / 商品看板 / Excel export 改為優先讀取 `match_diagnostic_json.reasons`,再 fallback 文字版 `error_message`;同步補 `makeup_finish_conflict`、`nail_tool_function_conflict`、`schick_razor_line_conflict`、`variant_descriptor_conflict` 等操作員可讀標籤,讓商品列表顯示「妝效質地不同、工具功能不同、除毛刀品線不同」而不是 raw machine code。matcher 另補 MUJI 精油芬香護手霜的 brandless exact recovery,PChome 標題缺品牌但身份詞與 50g 規格一致時可進 manual-review identity;peripera 多色任選 vs 單一色號會標記 `variant_selection_review` 並留在 `true_low_confidence`,避免被誤列為可批次救回。 - **V10.432 近門檻比價 hard-veto 補強**: marketplace matcher 不放寬 `MIN_MATCH_SCORE`,針對正式 `true_low_confidence` 前段新增窄範圍防錯配:M.A.C `MACximal` 柔霧唇膏 vs 緞光唇膏標記 `makeup_finish_conflict`、ERBE 指甲清垢棒 vs 指甲緣刨刀標記 `nail_tool_function_conflict`、Schick 舒芙 vs 舒綺仕女除毛刀標記 `schick_razor_line_conflict`,三者皆進 hard veto;同時把 `潤膚乳` / `身體乳` / `嬰兒乳液` / `寶寶乳液` 納入乳液型別,讓慕之幼爽身潤膚乳等真同款回刷更穩定。新增測試鎖住 MUJI 護手霜、Mustela 慕之幼潤膚乳、Herbacin 小甘菊護手霜可 exact,並確保高 variant 錯配不被 focused rule 推進。 - **V10.431 Telegram callback byte-safe**: `triaged_alert()` 的 `momo:eig:*` HITL callback 改為依 UTF-8 byte 長度截斷,不再用字元數截斷;中文或過長 `event.id` / `decision_envelope.decision_id` 仍會保留可追蹤 payload,且保證 `callback_data` 不超過 Telegram 64-byte 限制,避免專業排版告警因 callback 太長而整則送出失敗。 diff --git a/routes/dashboard_routes.py b/routes/dashboard_routes.py index 74d0717..ff1bcae 100644 --- a/routes/dashboard_routes.py +++ b/routes/dashboard_routes.py @@ -58,18 +58,26 @@ REVIEW_STATUS_OPTIONS = [ 'expired_match', 'refresh_no_result', 'no_result', + 'manual_rejected', + 'manual_unit_price_required', + 'manual_needs_research', ), }, { 'key': 'unit_comparable', 'label': '需單位價', - 'statuses': ('unit_comparable', 'refresh_unit_comparable'), + 'statuses': ('unit_comparable', 'refresh_unit_comparable', 'manual_unit_price_required'), }, {'key': 'identity_veto', 'label': '已排除', 'statuses': ('identity_veto',)}, {'key': 'low_score', 'label': '低信心', 'statuses': ('low_score', 'refresh_low_score', 'recoverable_low_score', 'true_low_confidence')}, {'key': 'protected_existing_match', 'label': '既有保護', 'statuses': ('protected_existing_match',)}, {'key': 'expired_match', 'label': '價格過期', 'statuses': ('expired_match',)}, {'key': 'no_result', 'label': '找不到同款', 'statuses': ('no_result', 'refresh_no_result')}, + { + 'key': 'manual_closed', + 'label': '人工閉環', + 'statuses': ('manual_rejected', 'manual_unit_price_required', 'manual_needs_research'), + }, ] diff --git a/services/competitor_intel_repository.py b/services/competitor_intel_repository.py index 01f244d..695bf3a 100644 --- a/services/competitor_intel_repository.py +++ b/services/competitor_intel_repository.py @@ -36,14 +36,18 @@ ACTIONABLE_ATTEMPT_STATUSES = { "expired_match", "refresh_no_result", "no_result", + "manual_rejected", + "manual_unit_price_required", + "manual_needs_research", } REVIEW_STATUS_FILTER_GROUPS = { - "unit_comparable": ("unit_comparable", "refresh_unit_comparable"), + "unit_comparable": ("unit_comparable", "refresh_unit_comparable", "manual_unit_price_required"), "identity_veto": ("identity_veto",), "low_score": ("low_score", "refresh_low_score", "recoverable_low_score", "true_low_confidence"), "protected_existing_match": ("protected_existing_match",), "expired_match": ("expired_match",), "no_result": ("no_result", "refresh_no_result"), + "manual_closed": ("manual_rejected", "manual_unit_price_required", "manual_needs_research"), } ATTEMPT_STATUS_LABELS = { "unit_comparable": "需單位價比較", diff --git a/templates/dashboard_v2.html b/templates/dashboard_v2.html index 135f0e5..fe1c9a3 100644 --- a/templates/dashboard_v2.html +++ b/templates/dashboard_v2.html @@ -612,6 +612,13 @@ data-review-confirm="確認標記為需單位價比較?"> 標記單位價 + {% endif %} diff --git a/tests/test_competitor_intel_cache.py b/tests/test_competitor_intel_cache.py index 6b30060..fe0b1bc 100644 --- a/tests/test_competitor_intel_cache.py +++ b/tests/test_competitor_intel_cache.py @@ -105,6 +105,10 @@ def test_competitor_review_queue_is_canonical_unit_price_handoff(): assert "manual_accept_count" in source assert "manual_reject_count" in source assert "manual_unit_price_count" in source + assert "manual_rejected" in source + assert "manual_unit_price_required" in source + assert "manual_needs_research" in source + assert "manual_closed" in source assert "competitor_match_reviews" in source assert "\"status_label\"" in source assert "\"action_label\"" in source diff --git a/tests/test_frontend_v2_assets.py b/tests/test_frontend_v2_assets.py index d82aaae..2b9eddd 100644 --- a/tests/test_frontend_v2_assets.py +++ b/tests/test_frontend_v2_assets.py @@ -166,6 +166,9 @@ def test_dashboard_v2_is_production_default_and_uses_real_dashboard_data(): assert "採用同款" in dashboard assert "否決候選" in dashboard assert "標記單位價" in dashboard + assert "補搜尋" in dashboard + assert 'data-review-action="needs_research"' in dashboard + assert "人工閉環" in route_source assert "AI 挑品清單" in dashboard assert "比價覆核隊列" in dashboard assert "覆核動作" in dashboard @@ -223,6 +226,7 @@ def test_pchome_review_export_and_diagnostics_use_real_queue_data(): assert "dashboard-review-actions" in dashboard assert ".dashboard-review-reasons" in dashboard_css assert ".dashboard-review-actions" in dashboard_css + assert ".dashboard-review-action.is-research" in dashboard_css def test_ai_intelligence_uses_v2_shell_and_real_runtime_apis(): diff --git a/web/static/css/page-dashboard-v2.css b/web/static/css/page-dashboard-v2.css index 96993e9..b7f5c9f 100644 --- a/web/static/css/page-dashboard-v2.css +++ b/web/static/css/page-dashboard-v2.css @@ -1007,6 +1007,18 @@ filter: brightness(0.94); } + .dashboard-review-action.is-research { + color: var(--momo-accent-strong); + border-color: rgba(188, 117, 48, 0.42); + background: rgba(188, 117, 48, 0.08); + } + + .dashboard-review-action.is-research:hover { + color: var(--momo-text-inverse); + background: var(--momo-accent-strong); + border-color: var(--momo-accent-strong); + } + .dashboard-review-action:disabled { cursor: wait; opacity: 0.58;