diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 52d2c3e..160027f 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.541 補正式覆核頁高信心 exact 線:The Ordinary 咖啡因 EGCG 單側漏 30ml、Natures Care 綿羊油同入數 125ml/125m、TOMOON 指甲剪同 L/S 尺寸、HH 私密潔淨露+衣物手洗精雙 200ml、SEBAMED 護潔露 200ml x2、YES 德悅氏 9cm 剪刀;都同步進 revalidation SQL,且 TOMOON/O.P.I 不同型號或尺寸仍不得自動通過。 - 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 正規化,避免觀測寫入失敗。 diff --git a/config.py b/config.py index 93bbaa0..ae4a5dd 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.540" +SYSTEM_VERSION = "V10.541" 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 760d93f..0aa31fb 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -104,6 +104,7 @@ SQL漏斗(~300筆) - `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 窄門,但不得變成「同品線即通過」。 +- 其他正式覆核池 focused exact 線只能針對「已在正式頁面反覆出現且有硬規格」的窄範圍族群,例如 The Ordinary 咖啡因 EGCG、Natures Care 綿羊油同入數、TOMOON 指甲剪同尺寸、HH 雙 200ml 組、SEBAMED 200ml x2、YES 9cm 剪刀;同尺寸、同入數、同組合或單側漏規格必須可由 matcher 明確判斷,不能只因同品牌同品線通過。 - `/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 fb93945..c62da16 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-06-01:PChome 比價新鮮度操作閉環 +- **V10.541 高信心覆核池 exact 線再收斂**: 從正式覆核頁抓到多筆 `rescore_accepted_current` 仍卡人工的同款:The Ordinary 咖啡因 EGCG(MOMO 單側漏 30ml)、Natures Care 綿羊油同入數、TOMOON 指甲剪同 L/S 尺寸、HH 私密潔淨露+私密衣物手洗精雙 200ml、SEBAMED 護潔露 200ml x2、YES 德悅氏 9cm 剪刀。新增 focused total-price 規則與 revalidation SQL;TOMOON / O.P.I 仍要求同型號或同尺寸,避免跨款式誤配。 - **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` 僅作遙測分類,不是模型路由目標。 diff --git a/services/competitor_price_feeder.py b/services/competitor_price_feeder.py index 8f1d4ee..cc3f7b0 100644 --- a/services/competitor_price_feeder.py +++ b/services/competitor_price_feeder.py @@ -1388,6 +1388,70 @@ class CompetitorPriceFeeder: 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}' ) + OR ( + lower(COALESCE(p.name, '')) LIKE '%ordinary%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%ordinary%' + AND COALESCE(p.name, '') LIKE '%咖啡因%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%咖啡因%' + AND lower(COALESCE(p.name, '')) LIKE '%egcg%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%egcg%' + AND COALESCE(p.name, '') LIKE '%兒茶眼部配方%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%兒茶眼部配方%' + ) + OR ( + lower(COALESCE(p.name, '')) LIKE '%natures%' + AND lower(COALESCE(p.name, '')) LIKE '%care%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%natures%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%care%' + AND COALESCE(p.name, '') LIKE '%綿羊油%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%綿羊油%' + ) + OR ( + lower(COALESCE(p.name, '')) LIKE '%tomoon%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%tomoon%' + 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 '%指甲%' + ) + OR ( + lower(COALESCE(p.name, '')) LIKE '%hh%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%hh%' + 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 '%sebamed%' + OR COALESCE(p.name, '') LIKE '%施巴%' + ) + AND ( + lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%sebamed%' + 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 '%200%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%200%' + ) + OR ( + ( + lower(COALESCE(p.name, '')) LIKE '%yes%' + OR COALESCE(p.name, '') LIKE '%德悅氏%' + ) + AND ( + lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%yes%' + OR 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, '')) LIKE '%9cm%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%9cm%' + ) ) ) ) diff --git a/services/marketplace_product_matcher.py b/services/marketplace_product_matcher.py index 2c249f8..5dd0bee 100644 --- a/services/marketplace_product_matcher.py +++ b/services/marketplace_product_matcher.py @@ -494,14 +494,19 @@ FOCUSED_IDENTITY_VARIANT_REVIEW_BYPASS_REASONS = { "dhc_olive_lip_1_5g", "flortte_fruit_salad_eyeliner_0_5ml_catalog", "frudia_honey_blueberry_lip_10g", + "hh_private_cleanser_laundry_wash_set", "kanebo_allie_bright_uv_milk_60g_catalog", "laroche_posay_lip_balm_4_7ml", "laroche_posay_repair_lip_balm_7_5ml", "lush_sakura_body_spray", "neutrogena_hand_cream_56g_scent_catalog", + "natures_care_sheep_oil_exact_pack", "opi_gel_polish_exact_model", "sebamed_baby_lip_4_8g_2pack", + "sebamed_ph38_private_wash_200ml_2pack", "so_natural_fixx_setting_spray_120ml_plain", + "tomoon_nail_clipper_luxury_size", + "yes_curved_scissors_9cm", "cetaphil_long_lotion_237ml", "cetaphil_long_lotion_473ml", "clarins_double_serum_eye_20ml", @@ -537,11 +542,16 @@ FOCUSED_IDENTITY_TOTAL_PRICE_REASONS = { "taisu_baby_bath_shampoo_3pc", "arden_eight_hour_lip_spf15_3_7g_3pack", "flortte_fruit_salad_eyeliner_0_5ml_catalog", + "hh_private_cleanser_laundry_wash_set", "kanebo_allie_bright_uv_milk_60g_catalog", "laroche_posay_repair_lip_balm_7_5ml", "neutrogena_hand_cream_56g_scent_catalog", + "natures_care_sheep_oil_exact_pack", "opi_gel_polish_exact_model", + "sebamed_ph38_private_wash_200ml_2pack", "kussen_baby_butt_cream_50ml_3pack", + "tomoon_nail_clipper_luxury_size", + "yes_curved_scissors_9cm", "bone_diffuser_gift_3pack", "selection1990_half_dome_wax_lamp_white", "selection1990_bendable_wax_lamp_white", @@ -4677,9 +4687,61 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro and "egcg" in right_raw and "兒茶眼部配方" in left_text and "兒茶眼部配方" in right_text - and _has_shared_volume(left, right, 30) + and ( + _has_shared_volume(left, right, 30) + or (30.0 in left.volumes_ml and not right.volumes_ml) + or (30.0 in right.volumes_ml and not left.volumes_ml) + ) ): return "the_ordinary_caffeine_egcg_30ml" + if ( + {"natures", "care"} <= (left.brand_tokens & right.brand_tokens) + and "綿羊油" in left_text + and "綿羊油" in right_text + and _has_exact_count_alignment(left, right) + and ( + _has_shared_volume(left, right, 125) + or (125.0 in left.volumes_ml and not right.volumes_ml and "125m" in right_text) + or (125.0 in right.volumes_ml and not left.volumes_ml and "125m" in left_text) + ) + ): + return "natures_care_sheep_oil_exact_pack" + if ( + "tomoon" in (left.brand_tokens & right.brand_tokens) + and "德國奔月" in left_text + and "德國奔月" in right_text + 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 any(size in left_text and size in right_text for size in ("l號", "s號")) + ): + return "tomoon_nail_clipper_luxury_size" + if ( + {"hh", "草本新淨界"} & (left.brand_tokens & right.brand_tokens) + and "私密植萃抗菌潔淨露" in left_text + and "私密植萃抗菌潔淨露" in right_text + and "私密衣物抗菌手洗精" in left_text + and "私密衣物抗菌手洗精" in right_text + and _has_shared_volume(left, right, 200) + ): + return "hh_private_cleanser_laundry_wash_set" + if ( + {"sebamed", "施巴"} & (left.brand_tokens & right.brand_tokens) + and "護潔露" in left_text + and "護潔露" in right_text + and _has_shared_volume(left, right, 200) + and _has_exact_count_alignment(left, right) + ): + return "sebamed_ph38_private_wash_200ml_2pack" + if ( + {"yes", "德悅氏"} & (left.brand_tokens & right.brand_tokens) + and "鋒利窄弧型剪刀" in left_text + and "鋒利窄弧型剪刀" in right_text + and "9cm" in left_text + and "9cm" in right_text + ): + return "yes_curved_scissors_9cm" if ( {"kussen", "葵森"} & (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 0ad0b40..e49a4e3 100644 --- a/tests/test_competitor_match_attempts_persistence.py +++ b/tests/test_competitor_match_attempts_persistence.py @@ -161,6 +161,12 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes(): assert "持采亮化UV防曬水凝乳" in retryable_source assert "類光繚" in retryable_source assert "isl[a-z0-9]*[0-9]{2,3}" in retryable_source + assert "兒茶眼部配方" in retryable_source + assert "綿羊油" in retryable_source + assert "德國奔月" in retryable_source + assert "私密植萃抗菌潔淨露" in retryable_source + assert "護潔露" in retryable_source + assert "鋒利窄弧型剪刀" 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 9ec0fd5..dab5bb4 100644 --- a/tests/test_marketplace_product_matcher.py +++ b/tests/test_marketplace_product_matcher.py @@ -601,6 +601,36 @@ def test_marketplace_matcher_promotes_focused_exact_pack_rows_to_total_price(): "OPI 官方直營.馬拉加葡萄酒類光繚-ISLL87.如膠似漆2.0系列指彩", "focused_exact_identity_opi_gel_polish_exact_model", ), + ( + "【The Ordinary】5%咖啡因 + EGCG兒茶眼部配方", + "The Ordinary 5%咖啡因 + EGCG兒茶眼部配方 (30ml)", + "focused_exact_identity_the_ordinary_caffeine_egcg_30ml", + ), + ( + "【澳洲Natures Care】綿羊油(6 入組 125ml/瓶)", + "【澳洲Natures Care】綿羊油(6 入組 125m/瓶)", + "focused_exact_identity_natures_care_sheep_oil_exact_pack", + ), + ( + "【TOMOON】德國奔月-超省力防飛濺頂級指甲剪/指甲刀(豪華套裝組-L號)", + "TOMOON 德國奔月-超省力防飛濺頂級指甲剪/指甲刀 (豪華套裝組-L號)", + "focused_exact_identity_tomoon_nail_clipper_luxury_size", + ), + ( + "【HH草本新淨界】私密植萃抗菌潔淨露+私密衣物抗菌手洗精(200ml+200ml)", + "HH私密植萃抗菌潔淨露(200ML)+私密衣物抗菌手洗精(200ML)【裡外兼顧組】", + "focused_exact_identity_hh_private_cleanser_laundry_wash_set", + ), + ( + "【SEBAMED 施巴】衛生護潔露200mlx2入(總代理)", + "SEBAMED pH3.8女性私密護潔露200mlx2入", + "focused_exact_identity_sebamed_ph38_private_wash_200ml_2pack", + ), + ( + "【YES 德悅氏】德國進口 鋒利窄弧型剪刀(9cm)", + "【YES 德悅氏】德國製造 鋒利窄弧型剪刀(9cm)", + "focused_exact_identity_yes_curved_scissors_9cm", + ), ( "【The Ordinary】Caffeine Solution 咖啡因 + EGCG兒茶眼部配方30ml", "The Ordinary 5%咖啡因 + EGCG兒茶眼部配方 (30ml)", @@ -680,6 +710,21 @@ def test_marketplace_matcher_blocks_opi_gel_polish_cross_model_autopromotion(): assert "focused_exact_total_price_safe" not in diagnostics.reasons +def test_marketplace_matcher_blocks_tomoon_cross_size_autopromotion(): + from services.marketplace_product_matcher import score_marketplace_match + + diagnostics = score_marketplace_match( + "【TOMOON】德國奔月-超省力防飛濺頂級指甲剪/指甲刀(豪華套裝組-L號)", + "TOMOON 德國奔月-超省力防飛濺頂級指甲剪/指甲刀 (豪華套裝組-S號)", + momo_price=599, + competitor_price=599, + ) + + assert diagnostics.price_basis != "total_price" + assert "focused_exact_identity_tomoon_nail_clipper_luxury_size" 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 @@ -1027,7 +1072,9 @@ def test_marketplace_matcher_ignores_quantity_plus_markers_inside_component_spec assert diagnostics.score >= 0.76 assert diagnostics.hard_veto is False - assert diagnostics.alert_tier == "identity_review" + assert diagnostics.price_basis == "total_price" + assert diagnostics.alert_tier == "price_alert_exact" + assert "focused_exact_identity_hh_private_cleanser_laundry_wash_set" in diagnostics.reasons assert "multi_component_count_conflict" not in diagnostics.reasons