From 47d5c8043b02570d6c76d189752b4406ba204746 Mon Sep 17 00:00:00 2001 From: OoO Date: Mon, 1 Jun 2026 13:17:06 +0800 Subject: [PATCH] =?UTF-8?q?V10.553=20=E7=98=A6=E8=BA=AB=20current=20?= =?UTF-8?q?=E6=AF=94=E5=83=B9=E7=B5=90=E6=9E=9C=E6=9F=A5=E8=A9=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO_NEXT_STEPS.txt | 1 + config.py | 2 +- docs/memory/history_logs.md | 1 + services/competitor_intel_repository.py | 16 ++++++++++------ tests/test_competitor_intel_cache.py | 8 ++++++++ 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 9fcb0be..82f5a57 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.553 優化 current PPT/AI 比價結果查詢:`fetch_competitor_comparison_results()` 的 current/latest MOMO 價格改用 `JOIN LATERAL` 取單品最新價,移除 `ROW_NUMBER() OVER (PARTITION BY p.id ...)` window scan;歷史報表的 `end_date` cutoff 仍保留在 lateral 子查詢內,維持「指定期間截止日前最新 MOMO 價」語意不變。這能降低簡報、OpenClaw/AI payload 與比價匯出在大量 price_records 下的查詢成本。 - V10.552 收斂決策查詢的新鮮度口徑:`fetch_top_competitor_risks()`、PChome review queue、review sample 與 current PPT/AI 比價結果都不再把 `expires_at IS NULL` 當成有效現價,只接受 `expires_at > CURRENT_TIMESTAMP` 的 PChome identity_v2 價格。未知新鮮度只留在 coverage 的診斷欄位與 V10.551 刷新入口,不再進入價格風險、簡報、AI 決策或覆核排除條件。 - V10.551 收斂未知新鮮度刷新與補抓排序:`_fetch_expired_identity_skus()` / `_fetch_expired_identity_recovery_skus()` 將 `expires_at IS NULL` 視為必須刷新或可搜尋救援的未知新鮮度 identity,和 V10.549「未知新鮮度不算可決策覆蓋率」口徑對齊;兩條路徑改用 `JOIN LATERAL` 取最新 MOMO 價,移除 product-wide window scan。`_fetch_unmatched_priority_skus()` 也改用 lateral 最新價,並優先重搜低風險 `no_result / refresh_no_result`,讓 V10.550 的安全召回詞先用在最可能被救回的商品。 - V10.550 補安全搜尋召回詞:`_build_variant_recall_search_plan()` 對低風險穩定品類新增 `品牌 + 品類` 的補搜尋詞,讓 `no_result / refresh_no_result` 更有機會找到 PChome 候選後再交給 matcher 安全判斷;美甲片、指甲油、唇彩、香氛/精油、粉底、防曬、任選/色號/款式等高 variant 風險商品不走通用召回,DASHING DIVA 仍只走既有 line-specific recall + sort fallback。此變更不改 `MIN_MATCH_SCORE`、hard veto、fresh-search write safety 或 stronger existing match 覆寫保護。 diff --git a/config.py b/config.py index 099ec47..6774b72 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.552" +SYSTEM_VERSION = "V10.553" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index e66c997..7480691 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-06-01:PChome 比價新鮮度操作閉環 +- **V10.553 current PPT/AI 比價結果查詢瘦身**: `fetch_competitor_comparison_results()` 不再用 `ROW_NUMBER() OVER (PARTITION BY p.id ...)` 掃 `price_records` 取得 current/latest MOMO 價格,改為 `JOIN LATERAL` 逐商品取最新價;有指定 `end_date` 的歷史報表仍把 `pr.timestamp < DATE(:end_date) + INTERVAL '1 day'` 放在 lateral 子查詢中,保留「截止日前最新 MOMO 價」語意。這降低簡報、OpenClaw/AI payload 與比價匯出在大量價格紀錄下的查詢成本。 - **V10.552 決策查詢新鮮度口徑收斂**: Top competitor risks、PChome review queue、review sample 與 current PPT/AI 比價結果全部改成只吃 `cp.expires_at > CURRENT_TIMESTAMP` 的有效 PChome identity_v2 價格,不再把 `expires_at IS NULL` 當作有效現價。未知新鮮度現在只作 coverage 診斷與刷新入口,不會被用來產生價格風險、簡報、AI 決策或從覆核隊列中排除。 - **V10.551 未知新鮮度刷新與補抓排序收斂**: `expires_at IS NULL` 的 identity_v2 價格現在會被 `_fetch_expired_identity_skus()` 與 `_fetch_expired_identity_recovery_skus()` 視為需要刷新 / 可搜尋救援的未知新鮮度資料,避免 V10.549 已排除出可決策覆蓋率後卻沒有刷新入口;兩條路徑都改用 `JOIN LATERAL` 取最新 MOMO 價,不再做 product-wide window scan。`_fetch_unmatched_priority_skus()` 同步改為 lateral latest price,且把低風險 `no_result / refresh_no_result` 排到補抓前段,讓安全召回詞優先投入最可能回收的 SKU。 - **V10.550 安全搜尋召回詞補強**: `competitor_price_feeder` 在既有精準搜尋詞之外,對低風險穩定品類補上一組 `品牌 + 品類` recall keyword,提升 `no_result / refresh_no_result` 找到候選的機率;高 variant 風險商品如美甲片、指甲油、唇彩、香氛/精油、粉底、防曬與含任選/色號/款式/香味的商品不走通用召回。DASHING DIVA 仍保留既有 line-specific recall 與 PChome sort fallback;本次不更動 `MIN_MATCH_SCORE`、hard veto、auto write safety 或 stronger existing match 保護。 diff --git a/services/competitor_intel_repository.py b/services/competitor_intel_repository.py index 982fb5d..f61b153 100644 --- a/services/competitor_intel_repository.py +++ b/services/competitor_intel_repository.py @@ -1717,12 +1717,17 @@ def fetch_competitor_comparison_results( p.id AS product_id, p.i_code AS sku, p.name, - pr.price AS momo_price, - ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY pr.timestamp DESC, pr.id DESC) AS rn + latest_price.price AS momo_price FROM products p - JOIN price_records pr ON pr.product_id = p.id + JOIN LATERAL ( + SELECT pr.price + FROM price_records pr + WHERE pr.product_id = p.id + {momo_price_cutoff} + ORDER BY pr.timestamp DESC, pr.id DESC + LIMIT 1 + ) latest_price ON TRUE WHERE p.status = 'ACTIVE' - {momo_price_cutoff} ), {valid_competitor_cte}, {attempt_cte} @@ -1756,8 +1761,7 @@ def fetch_competitor_comparison_results( LEFT JOIN valid_competitor vc ON vc.sku = lm.sku LEFT JOIN latest_attempt la ON la.sku = lm.sku {sales_join} - WHERE lm.rn = 1 - AND lm.momo_price > 0 + WHERE lm.momo_price > 0 ORDER BY {order_expr} LIMIT :limit """) diff --git a/tests/test_competitor_intel_cache.py b/tests/test_competitor_intel_cache.py index f708ab9..26a0d2b 100644 --- a/tests/test_competitor_intel_cache.py +++ b/tests/test_competitor_intel_cache.py @@ -56,6 +56,9 @@ def test_competitor_ppt_results_keep_pending_diagnostics_in_export(): def test_competitor_ppt_results_use_history_for_dated_reports(): source = (ROOT / "services" / "competitor_intel_repository.py").read_text(encoding="utf-8") route_source = (ROOT / "routes" / "openclaw_bot_routes.py").read_text(encoding="utf-8") + comparison_source = source.split("def fetch_competitor_comparison_results", 1)[1].split( + "def build_competitor_intel_payload", 1 + )[0] assert "requested_historical_prices" in source assert "use_history_prices" in source @@ -63,6 +66,11 @@ def test_competitor_ppt_results_use_history_for_dated_reports(): assert "cph.crawled_at >= DATE(:start_date)" in source assert "cph.crawled_at < DATE(:end_date) + INTERVAL '1 day'" in source assert "pr.timestamp < DATE(:end_date) + INTERVAL '1 day'" in source + assert "FROM products p\n JOIN LATERAL" in comparison_source + assert "WHERE pr.product_id = p.id" in comparison_source + assert "ORDER BY pr.timestamp DESC, pr.id DESC" in comparison_source + assert "ROW_NUMBER() OVER (PARTITION BY p.id" not in comparison_source + assert "WHERE lm.rn = 1" not in comparison_source assert "'competitor_price_history' AS competitor_source" in source assert "\"competitor_source\"" in source assert "\"pc_crawled_at\"" in source