From e820fa2e80d254867413c600fabe81ab0dbb7083 Mon Sep 17 00:00:00 2001 From: OoO Date: Mon, 1 Jun 2026 14:34:49 +0800 Subject: [PATCH] =?UTF-8?q?V10.554=20=E6=8E=A5=E7=B7=9A=E9=A6=99=E6=B0=9B?= =?UTF-8?q?=E7=B2=BE=E6=B2=B9=20focused=20=E5=9B=9E=E5=88=B7?= 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_price_feeder.py | 86 +++++++++++++++++-- services/marketplace_product_matcher.py | 4 + ...t_competitor_match_attempts_persistence.py | 9 ++ tests/test_marketplace_product_matcher.py | 9 ++ 7 files changed, 105 insertions(+), 7 deletions(-) diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 82f5a57..57be58b 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.554 接線香氛 / 精油 focused exact 回刷:Herb24 晨霧純精油擴香儀黑色、Pavaruni 40 香味 10ml 精油、Pavaruni 20 香味 450g 香氛蠟燭、Derma 大地有機植萃護膚油 150ml 現在明確標記 `focused_exact_total_price_safe`,並接進 `_fetch_retryable_candidate_skus()` 近門檻舊候選回刷。此入口只收 `low_score / refresh_low_score / true_low_confidence` 中命中精準名稱錨點、無 hard veto、`exact_identity` 且沒有變體 / 商業條件 / 件數衝突的候選;Laundrin、好物良品融蠟燈、Yuskin 等仍保留人工覆核,不為了拉覆蓋率強推自動價差。 - 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 的安全召回詞先用在最可能被救回的商品。 diff --git a/config.py b/config.py index 6774b72..32aeddb 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.553" +SYSTEM_VERSION = "V10.554" 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 7480691..fb720c4 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-06-01:PChome 比價新鮮度操作閉環 +- **V10.554 香氛 / 精油 focused exact 回刷接線**: Herb24 晨霧純精油擴香儀黑色、Pavaruni 40 香味 10ml 精油、Pavaruni 20 香味 450g 香氛蠟燭、Derma 大地有機植萃護膚油 150ml 已明確列入 matcher 的 `focused_exact_total_price_safe`,並接入 `_fetch_retryable_candidate_skus()` 的近門檻舊候選回刷入口。SQL 仍要求 `low_score / refresh_low_score / true_low_confidence`、分數下限、無 hard veto、`exact_identity`,且排除變體、商業條件與件數衝突;Laundrin、好物良品融蠟燈、Yuskin 等帶人工覆核訊號的品線不納入本輪自動回刷。 - **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。 diff --git a/services/competitor_price_feeder.py b/services/competitor_price_feeder.py index 28af955..0ca3e9a 100644 --- a/services/competitor_price_feeder.py +++ b/services/competitor_price_feeder.py @@ -1394,7 +1394,7 @@ class CompetitorPriceFeeder: AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%beauty foot%' AND COALESCE(p.name, '') LIKE '%足膜%' AND COALESCE(la.best_competitor_product_name, '') LIKE '%足膜%' - ) + ) OR ( COALESCE(p.name, '') LIKE '%足足稱奇%' AND COALESCE(la.best_competitor_product_name, '') LIKE '%足足稱奇%' @@ -1427,13 +1427,87 @@ class CompetitorPriceFeeder: AND COALESCE(p.name, '') LIKE '%嬰兒高純修護凝膠%' AND COALESCE(la.best_competitor_product_name, '') LIKE '%嬰兒高純修護凝膠%' ) + ) ) - ) - OR ( - ( + OR ( + la.attempt_status IN ( + 'low_score', + 'refresh_low_score', + 'true_low_confidence' + ) + AND COALESCE(la.best_match_score, 0) >= :min_score + AND COALESCE(la.hard_veto, false) = false + AND COALESCE(la.match_diagnostic_json->>'comparison_mode', 'exact_identity') = 'exact_identity' + AND NOT ( + COALESCE(la.match_diagnostic_json->'reasons', '[]'::jsonb) + ?| array[{REVALIDATABLE_REVIEW_BLOCK_SQL_REASON_LIST}] + ) + AND ( + ( + ( + lower(COALESCE(p.name, '')) LIKE '%herb24%' + OR COALESCE(p.name, '') LIKE '%草本24%' + ) + AND ( + lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%herb24%' + OR COALESCE(la.best_competitor_product_name, '') LIKE '%草本24%' + ) + AND COALESCE(p.name, '') LIKE '%晨霧純精油擴香儀%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%晨霧純精油擴香儀%' + AND COALESCE(p.name, '') LIKE '%黑%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%黑%' + ) + OR ( + lower(COALESCE(p.name, '')) LIKE '%pavaruni%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%pavaruni%' + AND COALESCE(p.name, '') LIKE '%天然植物%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%天然植物%' + AND COALESCE(p.name, '') LIKE '%精油%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%精油%' + AND COALESCE(p.name, '') LIKE '%40%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%40%' + AND lower(COALESCE(p.name, '')) ~* '10\\s*ml' + AND lower(COALESCE(la.best_competitor_product_name, '')) ~* '10\\s*ml' + ) + OR ( + lower(COALESCE(p.name, '')) LIKE '%pavaruni%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%pavaruni%' + AND COALESCE(p.name, '') LIKE '%香氛蠟燭%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%香氛蠟燭%' + AND COALESCE(p.name, '') LIKE '%20%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%20%' + AND lower(COALESCE(p.name, '')) ~* '450\\s*g' + AND lower(COALESCE(la.best_competitor_product_name, '')) ~* '450\\s*g' + ) + OR ( + ( + lower(COALESCE(p.name, '')) LIKE '%derma%' + OR COALESCE(p.name, '') LIKE '%德瑪%' + ) + AND ( + lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%derma%' + OR COALESCE(la.best_competitor_product_name, '') LIKE '%德瑪%' + ) + AND COALESCE(p.name, '') LIKE '%大地%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%大地%' + AND COALESCE(p.name, '') LIKE '%有機植萃%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%有機植萃%' + AND COALESCE(p.name, '') LIKE '%護膚油%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%護膚油%' + AND lower(COALESCE(p.name, '')) ~* '150\\s*ml' + AND lower(COALESCE(la.best_competitor_product_name, '')) ~* '150\\s*ml' + AND NOT ( + COALESCE(p.name, '') ~* '(2\\s*入|2\\s*瓶|x\\s*2|\\*\\s*2|*\\s*2)' + OR COALESCE(la.best_competitor_product_name, '') ~* '(2\\s*入|2\\s*瓶|x\\s*2|\\*\\s*2|*\\s*2)' + ) + ) + ) + ) + OR ( ( - la.attempt_status = 'true_low_confidence' - AND COALESCE(la.best_match_score, 0) >= 0.95 + ( + la.attempt_status = 'true_low_confidence' + AND COALESCE(la.best_match_score, 0) >= 0.95 ) OR ( la.attempt_status = 'rescore_accepted_current' diff --git a/services/marketplace_product_matcher.py b/services/marketplace_product_matcher.py index 1ea7741..a27df00 100644 --- a/services/marketplace_product_matcher.py +++ b/services/marketplace_product_matcher.py @@ -568,10 +568,14 @@ FOCUSED_IDENTITY_TOTAL_PRICE_REASONS = { "canmake_tear_bag_palette", "gdesign_aroma_lava_lamp_2", "hooome_classic_white_wax_lamp_bulbs_giftbox", + "herb24_mist_diffuser_black", + "pavaruni_40_scent_oil", + "pavaruni_20_scent_candle", "artmis_cranberry_private_mousse_250ml", "artmis_witch_hazel_private_mousse_250ml", "baan_baby_lip_original_strawberry_catalog", "dhc_olive_lip_1_5g", + "derma_eco_skin_oil", "frudia_honey_blueberry_lip_10g", "laroche_posay_lip_balm_4_7ml", "lush_sakura_body_spray", diff --git a/tests/test_competitor_match_attempts_persistence.py b/tests/test_competitor_match_attempts_persistence.py index aa313b1..7487785 100644 --- a/tests/test_competitor_match_attempts_persistence.py +++ b/tests/test_competitor_match_attempts_persistence.py @@ -203,6 +203,15 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes(): assert "私密植萃抗菌潔淨露" in retryable_source assert "護潔露" in retryable_source assert "鋒利窄弧型剪刀" in retryable_source + assert "晨霧純精油擴香儀" in retryable_source + assert "pavaruni" in retryable_source + assert "天然植物" in retryable_source + assert "香氛蠟燭" in retryable_source + assert "450\\\\s*g" in retryable_source + assert "大地" in retryable_source + assert "有機植萃" in retryable_source + assert "護膚油" in retryable_source + assert "2\\\\s*入" in retryable_source assert "COALESCE(la.hard_veto, false) = false" in retryable_source assert "match_diagnostic_json->>'comparison_mode'" in retryable_source assert "?| array[" in retryable_source diff --git a/tests/test_marketplace_product_matcher.py b/tests/test_marketplace_product_matcher.py index c4c333c..af7c76a 100644 --- a/tests/test_marketplace_product_matcher.py +++ b/tests/test_marketplace_product_matcher.py @@ -1945,6 +1945,15 @@ def test_marketplace_matcher_promotes_focused_low_score_exact_identity_lines(): assert diagnostics.score >= 0.76, (momo_name, diagnostics) assert diagnostics.hard_veto is False assert expected_reason in diagnostics.reasons + if expected_reason in { + "focused_exact_identity_herb24_mist_diffuser_black", + "focused_exact_identity_pavaruni_40_scent_oil", + "focused_exact_identity_pavaruni_20_scent_candle", + "focused_exact_identity_derma_eco_skin_oil", + }: + assert "focused_exact_total_price_safe" in diagnostics.reasons + assert diagnostics.price_basis == "total_price" + assert diagnostics.alert_tier == "price_alert_exact" def test_marketplace_matcher_keeps_high_variant_low_score_lines_outside_focused_promotion():