From c0a7a55b727fed7acf681bca5a2ce5b621a46177 Mon Sep 17 00:00:00 2001 From: OoO Date: Mon, 1 Jun 2026 09:41:36 +0800 Subject: [PATCH] =?UTF-8?q?V10.540=20=E8=A3=9C=20OPI=20=E6=8C=87=E5=BD=A9?= =?UTF-8?q?=E5=9E=8B=E8=99=9F=E7=B2=BE=E6=BA=96=E6=95=91=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO_NEXT_STEPS.txt | 1 + config.py | 2 +- docs/AI_INTELLIGENCE_MODULE_SOT.md | 1 + docs/memory/history_logs.md | 1 + services/competitor_price_feeder.py | 22 +++++++++++++++++++ services/marketplace_product_matcher.py | 12 ++++++++++ ...t_competitor_match_attempts_persistence.py | 2 ++ tests/test_marketplace_product_matcher.py | 20 +++++++++++++++++ 8 files changed, 60 insertions(+), 1 deletion(-) diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 7a784c9..52d2c3e 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.540 補 O.P.I 類光繚指彩精準型號線:雙方同為 O.P.I 類光繚 / 如膠似漆指甲油或指彩,且共享 `ISL...` 型號 token 時才允許 total-price;不同型號/色號仍不得自動寫入。同步把此族群接進 `true_low_confidence` revalidation 窄門,降低高信心指彩候選卡在人工覆核池的比例。 - V10.539 補 PChome 任選 catalog focused exact 線:FLORTTE 水果沙拉眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g 雙方皆任選時可走 total-price;同步接進 revalidation queue。focused bypass 新增 commercial condition 防線,`即期品` 等商業狀態差異不會被自動寫入正式價差。 - V10.538 修 ai_calls provider CHECK 對齊:Hermes/Ollama 全失敗或未選定 host 時的 `ollama_other` 只作 telemetry bucket,migration 043 放行此值,`ai_call_logger` 也會將空值/unknown/非白名單 provider 正規化,避免觀測寫入失敗。 - V10.537 將 V10.536 focused exact 線接進 `run_retryable_candidate_revalidation()` 窄門:既有 `true_low_confidence` 舊候選若命中新品線且無 hard veto / 型別、款式、香味、件數、組合阻擋,就可重新走 matcher 寫入正式價差;有色號/香味/即期等阻擋仍不進回刷。 diff --git a/config.py b/config.py index e4d5c1e..93bbaa0 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.539" +SYSTEM_VERSION = "V10.540" 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 008aded..760d93f 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -103,6 +103,7 @@ SQL漏斗(~300筆) - 高分 `true_low_confidence` 的自動救回只能用具名 focused exact 線逐批擴充;同品牌、同品線、同規格/同組合的花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫嬰兒沐浴洗髮、Elizabeth Arden 八小時護唇膏與理膚寶水全面修復潤唇膏可走 total-price,色號、香味、款式、即期品與 catalog selection 仍維持 review / veto。 - `true_low_confidence` focused exact 線必須同步接入 `run_retryable_candidate_revalidation()` 的 SQL 窄門,讓舊候選可被批次回收;該窄門只允許具名品線豁免 `variant_selection_review`,其他 hard veto / 型別、款式、香味、件數、組合、refill、commercial condition 阻擋仍不得回刷。 - 任選 catalog focused exact 只允許雙方都明確是同品線任選賣場且規格一致的窄範圍案例,例如 FLORTTE 眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g;若有 `commercial_condition_gap`(即期品、短效、航空版等狀態差異),focused bypass 不得移除 `variant_selection_review`,不得自動寫正式價差。 +- O.P.I 指彩救回只允許同品牌、同 `類光繚` / `如膠似漆` 指甲油或指彩線,且共享 `ISL...` 精準型號 token 的案例自動走 total-price;不同型號/色號仍維持人工或 veto。此規則可接入 `true_low_confidence` revalidation 窄門,但不得變成「同品線即通過」。 - `/api/ai/pchome-match/backfill/status` 必須把近門檻重評池與過期 identity 救援池以只讀 `revalidation_preview` / `stale_recovery_preview` 曝光給操作員;預覽只復用正式候選 SQL 並受 limit / 60 秒快取限制,不啟動 PChome 搜尋、不呼叫 LLM、不寫 `competitor_match_attempts` / `competitor_prices`。重評 preview 必須先從最新 `competitor_match_attempts` 縮小候選,再用 `JOIN LATERAL` 取單一最新 MOMO 價;救援 preview 必須從過期 `competitor_prices` 小集合出發並用 `JOIN LATERAL` 取最新 MOMO 價,兩者都不得掃全量 `price_records`;Dashboard 只能顯示「可救援」觀測值,不得在未開啟 `PCHOME_STALE_RECOVERY_ENABLED` 時提供 recover-stale 執行按鈕;其中 `review_gated_count` 僅代表窄門 `true_low_confidence` exact 候選,不得被解讀為全量人工池可自動回刷。 - PChome re-score audit 預設必須先取每個 SKU 的最新 `competitor_match_attempts` 狀態,再套用 status / reason 篩選;舊低信心歷史候選只能透過 `--include-historical-candidates` 明確進入考古掃描,避免已入隊、已否決或已修正 SKU 被舊紀錄重新推回報表。 - production re-score `--apply-accepted` 僅可追加 `rescore_accepted_current` attempt 給人工覆核;執行後需清除 Dashboard / competitor intel cache,且必須抽查 `competitor_prices` / `competitor_price_history` 未新增正式價差。 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index aa04946..fb93945 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-06-01:PChome 比價新鮮度操作閉環 +- **V10.540 O.P.I 指彩精準型號救回**: 正式覆核頁面顯示多筆 O.P.I 類光繚 / 如膠似漆指彩因 score 0.76~0.83 停在 `rescore_accepted_current` 人工池,但雙方都有明確 `ISL...` 型號(如 ISLL87、ISLL00、ISLN25)。新增 `opi_gel_polish_exact_model` focused total-price 規則:必須同品牌、同類光繚指彩線、共享型號 token;不同型號/色號不自動通過。同步接入 revalidation SQL,讓舊 `true_low_confidence` 指彩候選可批次重評。 - **V10.539 任選 catalog focused exact 與 commercial 防線**: 新增 FLORTTE 水果沙拉眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g 的 focused total-price 規則,條件是雙方同品牌、同品線、同規格且任選 catalog 語意一致;同步接入 `run_retryable_candidate_revalidation()`。同時修正 focused bypass:若存在 `commercial_condition_gap`(例如即期品),不得移除 `variant_selection_review`,避免商業狀態差異被自動寫入正式價差。 - **V10.538 ai_calls provider CHECK 對齊**: 正式 scheduler Hermes/Ollama 全失敗時會以 `provider=ollama_other` 記錄未知/未選定 host,但 `ai_calls.chk_ai_calls_provider` 舊白名單未包含此 telemetry bucket,導致觀測寫入再撞 CHECK。新增 migration 043 放行 `ollama_other`,並讓 `ai_call_logger` 將空值、unknown、非白名單 provider 字串正規化為允許值;`ollama_other` 僅作遙測分類,不是模型路由目標。 - **V10.537 focused exact revalidation queue 接線**: V10.536 只補 matcher 規則會影響新抓取,但無法批次回收既有 `true_low_confidence` 舊候選。`run_retryable_candidate_revalidation()` 新增 focused true-low 窄門:花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫、Elizabeth Arden 與理膚寶水若無 hard veto 與型別/款式/香味/件數/組合阻擋,可進重評;`variant_selection_review` 僅在這些具名安全線允許重跑,其他人工池不打開。 diff --git a/services/competitor_price_feeder.py b/services/competitor_price_feeder.py index e926aa3..8f1d4ee 100644 --- a/services/competitor_price_feeder.py +++ b/services/competitor_price_feeder.py @@ -1366,6 +1366,28 @@ class CompetitorPriceFeeder: AND COALESCE(p.name, '') LIKE '%任選%' AND COALESCE(la.best_competitor_product_name, '') LIKE '%任選%' ) + OR ( + ( + lower(COALESCE(p.name, '')) LIKE '%o.p.i%' + OR lower(COALESCE(p.name, '')) LIKE '%opi%' + ) + AND ( + lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%o.p.i%' + OR lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%opi%' + ) + AND COALESCE(p.name, '') LIKE '%類光繚%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%類光繚%' + AND ( + COALESCE(p.name, '') LIKE '%指甲油%' + OR COALESCE(p.name, '') LIKE '%指彩%' + ) + AND ( + COALESCE(la.best_competitor_product_name, '') LIKE '%指甲油%' + OR COALESCE(la.best_competitor_product_name, '') LIKE '%指彩%' + ) + AND COALESCE(p.name, '') ~* 'isl[a-z0-9]*[0-9]{2,3}' + AND COALESCE(la.best_competitor_product_name, '') ~* 'isl[a-z0-9]*[0-9]{2,3}' + ) ) ) ) diff --git a/services/marketplace_product_matcher.py b/services/marketplace_product_matcher.py index d5bd06c..2c249f8 100644 --- a/services/marketplace_product_matcher.py +++ b/services/marketplace_product_matcher.py @@ -499,6 +499,7 @@ FOCUSED_IDENTITY_VARIANT_REVIEW_BYPASS_REASONS = { "laroche_posay_repair_lip_balm_7_5ml", "lush_sakura_body_spray", "neutrogena_hand_cream_56g_scent_catalog", + "opi_gel_polish_exact_model", "sebamed_baby_lip_4_8g_2pack", "so_natural_fixx_setting_spray_120ml_plain", "cetaphil_long_lotion_237ml", @@ -539,6 +540,7 @@ FOCUSED_IDENTITY_TOTAL_PRICE_REASONS = { "kanebo_allie_bright_uv_milk_60g_catalog", "laroche_posay_repair_lip_balm_7_5ml", "neutrogena_hand_cream_56g_scent_catalog", + "opi_gel_polish_exact_model", "kussen_baby_butt_cream_50ml_3pack", "bone_diffuser_gift_3pack", "selection1990_half_dome_wax_lamp_white", @@ -4471,6 +4473,16 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro and "怪獸級持色唇膏" in right_text ): return "kate_monster_lipstick_catalog" + if ( + "opi" in (left.brand_tokens & right.brand_tokens) + and _shared_model_tokens(left, right) + and "類光繚" in left_text + and "類光繚" in right_text + and ("指甲油" in left_text or "指彩" in left_text) + and ("指甲油" in right_text or "指彩" in right_text) + and ("如膠似漆" in left_text or "如膠似漆" in right_text) + ): + return "opi_gel_polish_exact_model" if ( "opi" in (left.brand_tokens & right.brand_tokens) and "類光繚指甲油" in left_text diff --git a/tests/test_competitor_match_attempts_persistence.py b/tests/test_competitor_match_attempts_persistence.py index a28b8e0..0ad0b40 100644 --- a/tests/test_competitor_match_attempts_persistence.py +++ b/tests/test_competitor_match_attempts_persistence.py @@ -159,6 +159,8 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes(): assert "水果沙拉系列彩色防水眼線液筆" in retryable_source assert "護手霜" in retryable_source assert "持采亮化UV防曬水凝乳" in retryable_source + assert "類光繚" in retryable_source + assert "isl[a-z0-9]*[0-9]{2,3}" 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 0644812..9ec0fd5 100644 --- a/tests/test_marketplace_product_matcher.py +++ b/tests/test_marketplace_product_matcher.py @@ -596,6 +596,11 @@ def test_marketplace_matcher_promotes_focused_exact_pack_rows_to_total_price(): "【Kanebo 佳麗寶】ALLIE 持采亮化UV防曬水凝乳 60g(3款任選)", "focused_exact_identity_kanebo_allie_bright_uv_milk_60g_catalog", ), + ( + "【O.P.I】馬拉加葡萄酒 類光繚指甲油-ISLL87(小銀蓋/如膠似漆2.0系列指彩/美甲彩繪/官方直營)", + "OPI 官方直營.馬拉加葡萄酒類光繚-ISLL87.如膠似漆2.0系列指彩", + "focused_exact_identity_opi_gel_polish_exact_model", + ), ( "【The Ordinary】Caffeine Solution 咖啡因 + EGCG兒茶眼部配方30ml", "The Ordinary 5%咖啡因 + EGCG兒茶眼部配方 (30ml)", @@ -660,6 +665,21 @@ def test_marketplace_matcher_blocks_focused_total_price_for_commercial_condition assert "focused_exact_total_price_safe" not in diagnostics.reasons +def test_marketplace_matcher_blocks_opi_gel_polish_cross_model_autopromotion(): + from services.marketplace_product_matcher import score_marketplace_match + + diagnostics = score_marketplace_match( + "【O.P.I】馬拉加葡萄酒 類光繚指甲油-ISLL87(小銀蓋/如膠似漆2.0系列指彩)", + "OPI 官方直營.極地白雪類光繚-ISLL00.如膠似漆2.0系列指彩", + momo_price=578, + competitor_price=578, + ) + + assert diagnostics.price_basis != "total_price" + assert "focused_exact_identity_opi_gel_polish_exact_model" not in diagnostics.reasons + assert "focused_exact_total_price_safe" not in diagnostics.reasons + + def test_marketplace_matcher_promotes_recipe_box_marketing_line_drift(): from services.marketplace_product_matcher import score_marketplace_match