diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 60456b8..7a784c9 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - 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 寫入正式價差;有色號/香味/即期等阻擋仍不進回刷。 - V10.536 補 PChome 高分 `true_low_confidence` 安全救回線:新增花美水 Relax 薰衣草潤滑凝膠 1.7g x3、St.Clare 私密呼呼慕斯 x2 / 慕斯+噴霧組、BIOPEUTIC 果酸煥膚水凝乳 20% 150ml、台塑生醫嬰兒沐浴洗髮 3 件組、Elizabeth Arden 八小時護唇膏 SPF15 3.7g x3、理膚寶水全面修復潤唇膏 7.5ml focused total-price 規則;這些都要求同品牌、同品線與同規格/同組合,仍保留色號、香味、款式敏感品的 `variant_selection_review` 防線。 diff --git a/config.py b/config.py index 073241b..e4d5c1e 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.538" +SYSTEM_VERSION = "V10.539" 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 52f5166..008aded 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -102,6 +102,7 @@ SQL漏斗(~300筆) - `run_retryable_candidate_revalidation()` 的自動回刷主戰場仍限 `low_score` / `refresh_low_score` / `recoverable_low_score`;`true_low_confidence` 只有在已補 focused exact 規則的窄範圍品線、舊分數 >= 0.95、`comparison_mode='exact_identity'`、含 `strong_exact_spec_match` 且不含 commercial / variant / count / bundle / refill 等阻擋理由時,才可進入重評,不得全面打開人工審核池。 - 高分 `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`,不得自動寫正式價差。 - `/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 4d3de3e..aa04946 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-06-01:PChome 比價新鮮度操作閉環 +- **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` 僅在這些具名安全線允許重跑,其他人工池不打開。 - **V10.536 高分 true_low_confidence focused exact 救回**: production 抽樣顯示剩餘未覆蓋集中在 `identity_veto` 與 `true_low_confidence`,其中部分 1.0 分樣本是同品牌、同品線、同規格/同組合,但因多件組或護唇/私密護理類型辨識保守而停在 manual review。新增花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫嬰兒沐浴洗髮、Elizabeth Arden 八小時護唇膏、理膚寶水全面修復潤唇膏的 focused total-price 規則;不放寬 `MIN_MATCH_SCORE`,也不移除色號/香味/款式防線。 diff --git a/services/competitor_price_feeder.py b/services/competitor_price_feeder.py index d74f68c..e926aa3 100644 --- a/services/competitor_price_feeder.py +++ b/services/competitor_price_feeder.py @@ -1320,6 +1320,52 @@ class CompetitorPriceFeeder: AND COALESCE(p.name, '') LIKE '%全面修復潤唇膏%' AND COALESCE(la.best_competitor_product_name, '') LIKE '%全面修復潤唇膏%' ) + OR ( + ( + lower(COALESCE(p.name, '')) LIKE '%flortte%' + OR COALESCE(p.name, '') LIKE '%花洛莉亞%' + ) + AND ( + lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%flortte%' + 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 '%色號任選%' + ) + OR ( + ( + lower(COALESCE(p.name, '')) LIKE '%neutrogena%' + OR COALESCE(p.name, '') LIKE '%露得清%' + ) + AND ( + lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%neutrogena%' + 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 '%有香%' + ) + OR ( + ( + lower(COALESCE(p.name, '')) LIKE '%kanebo%' + OR COALESCE(p.name, '') LIKE '%佳麗寶%' + ) + AND ( + lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%kanebo%' + OR COALESCE(la.best_competitor_product_name, '') LIKE '%佳麗寶%' + ) + AND lower(COALESCE(p.name, '')) LIKE '%allie%' + AND lower(COALESCE(la.best_competitor_product_name, '')) LIKE '%allie%' + AND COALESCE(p.name, '') LIKE '%持采亮化UV防曬水凝乳%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%持采亮化UV防曬水凝乳%' + AND COALESCE(p.name, '') LIKE '%任選%' + AND COALESCE(la.best_competitor_product_name, '') LIKE '%任選%' + ) ) ) ) diff --git a/services/marketplace_product_matcher.py b/services/marketplace_product_matcher.py index 6ca69c7..d5bd06c 100644 --- a/services/marketplace_product_matcher.py +++ b/services/marketplace_product_matcher.py @@ -492,10 +492,13 @@ FOCUSED_IDENTITY_VARIANT_REVIEW_BYPASS_REASONS = { "arden_eight_hour_lip_spf15_3_7g_3pack", "baan_baby_lip_original_strawberry_catalog", "dhc_olive_lip_1_5g", + "flortte_fruit_salad_eyeliner_0_5ml_catalog", "frudia_honey_blueberry_lip_10g", + "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", "sebamed_baby_lip_4_8g_2pack", "so_natural_fixx_setting_spray_120ml_plain", "cetaphil_long_lotion_237ml", @@ -532,7 +535,10 @@ FOCUSED_IDENTITY_TOTAL_PRICE_REASONS = { "biopeutic_plus_aha_lotion_20_150ml", "taisu_baby_bath_shampoo_3pc", "arden_eight_hour_lip_spf15_3_7g_3pack", + "flortte_fruit_salad_eyeliner_0_5ml_catalog", + "kanebo_allie_bright_uv_milk_60g_catalog", "laroche_posay_repair_lip_balm_7_5ml", + "neutrogena_hand_cream_56g_scent_catalog", "kussen_baby_butt_cream_50ml_3pack", "bone_diffuser_gift_3pack", "selection1990_half_dome_wax_lamp_white", @@ -2317,7 +2323,10 @@ def score_marketplace_match( focused_exact_line_reason = _has_focused_low_score_exact_identity_line(left, right) if focused_exact_line_reason in FOCUSED_IDENTITY_REVIEW_ONLY_REASONS: reasons.append("variant_selection_review") - if focused_exact_line_reason in FOCUSED_IDENTITY_VARIANT_REVIEW_BYPASS_REASONS: + if ( + focused_exact_line_reason in FOCUSED_IDENTITY_VARIANT_REVIEW_BYPASS_REASONS + and not commercial_condition_gap + ): reasons = [reason for reason in reasons if reason != "variant_selection_review"] variant_selection_review = False focused_exact_price_safe = ( @@ -2360,6 +2369,7 @@ def score_marketplace_match( or focused_exact_line_reason == "hanamisui_inclear_private_gel_1_7g_3pack" ) and "variant_selection_review" not in reasons + and "commercial_condition_gap" not in reasons ) if focused_exact_total_price_safe: reasons.append("focused_exact_total_price_safe") @@ -4614,6 +4624,39 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro and _has_shared_volume(left, right, 7.5) ): return "laroche_posay_repair_lip_balm_7_5ml" + if ( + {"flortte", "花洛莉亞"} & (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_raw + and "任選" in right_raw + and _has_shared_volume(left, right, 0.5) + ): + return "flortte_fruit_salad_eyeliner_0_5ml_catalog" + if ( + {"neutrogena", "露得清"} & (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 + and "有香" in right_text + and _has_shared_weight(left, right, 56) + ): + return "neutrogena_hand_cream_56g_scent_catalog" + if ( + {"kanebo", "佳麗寶"} & (left.brand_tokens & right.brand_tokens) + and "allie" in left_raw + and "allie" in right_raw + and "持采亮化uv防曬水凝乳" in left_text + and "持采亮化uv防曬水凝乳" in right_text + and "任選" in left_raw + and "任選" in right_raw + and _has_shared_weight(left, right, 60) + ): + return "kanebo_allie_bright_uv_milk_60g_catalog" if ( "ordinary" 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 548c5b6..a28b8e0 100644 --- a/tests/test_competitor_match_attempts_persistence.py +++ b/tests/test_competitor_match_attempts_persistence.py @@ -156,6 +156,9 @@ 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 "護手霜" in retryable_source + assert "持采亮化UV防曬水凝乳" 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 4111fa3..0644812 100644 --- a/tests/test_marketplace_product_matcher.py +++ b/tests/test_marketplace_product_matcher.py @@ -581,6 +581,21 @@ def test_marketplace_matcher_promotes_focused_exact_pack_rows_to_total_price(): "理膚寶水 全面修復潤唇膏 7.5ml", "focused_exact_identity_laroche_posay_repair_lip_balm_7_5ml", ), + ( + "【FLORTTE花洛莉亞】水果沙拉系列彩色防水眼線液筆0.5ml(色號任選)", + "【FLORTTE花洛莉亞】水果沙拉系列彩色防水眼線液筆0.5ml(色號任選)", + "focused_exact_identity_flortte_fruit_salad_eyeliner_0_5ml_catalog", + ), + ( + "【Neutrogena露得清】護手霜-深層滋潤配方56g(無香/有香)", + "露得清 護手霜56g(任選_無香/有香)", + "focused_exact_identity_neutrogena_hand_cream_56g_scent_catalog", + ), + ( + "【Kanebo 佳麗寶】ALLIE 持采亮化UV防曬水凝乳 60g* (多款任選)", + "【Kanebo 佳麗寶】ALLIE 持采亮化UV防曬水凝乳 60g(3款任選)", + "focused_exact_identity_kanebo_allie_bright_uv_milk_60g_catalog", + ), ( "【The Ordinary】Caffeine Solution 咖啡因 + EGCG兒茶眼部配方30ml", "The Ordinary 5%咖啡因 + EGCG兒茶眼部配方 (30ml)", @@ -629,6 +644,22 @@ def test_marketplace_matcher_promotes_focused_exact_pack_rows_to_total_price(): assert "focused_exact_total_price_safe" in diagnostics.reasons +def test_marketplace_matcher_blocks_focused_total_price_for_commercial_condition_gap(): + from services.marketplace_product_matcher import score_marketplace_match + + diagnostics = score_marketplace_match( + "即期品【Kanebo 佳麗寶】ALLIE 持采亮化UV防曬水凝乳 60g* (多款任選)", + "【Kanebo 佳麗寶】ALLIE 持采亮化UV防曬水凝乳 60g(3款任選)", + momo_price=499, + competitor_price=499, + ) + + assert "commercial_condition_gap" in diagnostics.reasons + assert diagnostics.price_basis == "manual_review" + assert diagnostics.alert_tier == "identity_review" + 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