diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index f906d56..9937ffb 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.476 補 PChome 商品比對「商業條件差」防線:即期品、效期/保存期限、盒損、福利品等條件若只出現在單側,matcher 會加 `commercial_condition_gap` + `variant_selection_review`,保留高分但不讓 rescore 自動進 accepted queue。這可避免 3W CLINIC 粉底液、KAMERIA 足膜、Sisley 全能乳液這類同名但商品狀態不同的候選被當成一般正品價差。 - V10.475 補 PChome rescore 操作與高分錯配防線:`scripts/audit_competitor_match_attempt_rescore.py` 預設不再只掃 `strong_exact_spec_match`,避免漏掉 `focused_exact_*` 等新版 matcher 理由;matcher 新增暖燈 S/M/L 尺寸差、NITORI 香氛噴霧器型號差的 hard veto,並把彩妝色號單邊出現的高分候選送進 `variant_selection_review`,避免 LA MER 氣墊等色號型商品被誤入 accepted queue。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。 - V10.474 補 PChome near-threshold matcher / feeder 下一階段:新增 HOOOME 白色經典香氛暖燈與 Gdesign Aroma Lava 2.0 的窄範圍 total-price exact 回收;Recipe Box 可撕式水性兒童指甲油只進 identity_review,不自動寫正式價差;Pavaruni 蠟燭 vs 精油、DASHING DIVA 不同款式仍維持 veto/低信心。known-id refresh 現在會對 hard-veto 舊候選執行 fresh search recovery,missing known-id 若 fresh search 只找到低分候選也會保留 best candidate + diagnostics,而非落成 `refresh_no_result`;正式覆寫保護新增 stronger existing guard,避免較弱新候選以高分覆蓋既有強正式配對。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。 - V10.473 補背景 embedding host_health skip:`allow_111_fallback=false` 會讀最近 `host_health_probes`,跳過 runtime unhealthy 的 GCP 節點(預設 20 分鐘,DB fail-open),避免每筆任務都等待已知壞節點 timeout;路由安全不變,不把背景 embedding 落 111。 diff --git a/config.py b/config.py index 05729a2..113c4ab 100644 --- a/config.py +++ b/config.py @@ -350,7 +350,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.475" +SYSTEM_VERSION = "V10.476" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/memory/current_execution_queue_20260524.md b/docs/memory/current_execution_queue_20260524.md index bf8f720..917aa3f 100644 --- a/docs/memory/current_execution_queue_20260524.md +++ b/docs/memory/current_execution_queue_20260524.md @@ -29,6 +29,7 @@ - 2026-05-25 14:12 CST 起,`V10.473` 進行背景 embedding host_health skip:`allow_111_fallback=false` 的背景 embedding 會讀最近 `host_health_probes`,若 GCP-A/GCP-B runtime 已被標 unhealthy,直接跳過該節點並開 GCP circuit,不等待 30 秒 timeout、不落 111;DB 讀取失敗 fail-open。 - 2026-05-25 14:45 CST 起,`V10.474` 補 PChome near-threshold matcher / feeder 下一階段:HOOOME 白色經典香氛暖燈、Gdesign Aroma Lava 2.0 進 total-price exact;Recipe Box 可撕式水性兒童指甲油保留 identity_review,不自動寫正式價差;Pavaruni 蠟燭 vs 精油與 DASHING DIVA 不同款式仍不放行。known-id refresh 會對 hard-veto 舊候選跑 fresh search recovery;missing known-id 若 fresh search 只有低分候選,也保留 best candidate + diagnostics,不再只記 `refresh_no_result`;正式覆寫保護新增 stronger existing guard。 - 2026-05-25 15:20 CST 起,`V10.475` 補 rescore CLI 與高分錯配防線:audit CLI 預設不再只掃 `strong_exact_spec_match`,避免新版 `focused_exact_*` 理由漏掃;matcher 對香氛暖燈 S/M/L 尺寸差、NITORI 香氛噴霧器型號差直接 hard veto,彩妝色號單邊出現時送 `variant_selection_review`,避免高分但不同 variant 的候選被誤推入 accepted queue。 +- 2026-05-25 16:15 CST 起,`V10.476` 補商業條件差防線:即期品、效期/保存期限、盒損、福利品等條件只出現在單側時,matcher 加 `commercial_condition_gap` 並送 `variant_selection_review`,不讓同名但商品狀態不同的候選自動進 accepted queue。 - 2026-05-25 12:05 CST 狀態:`main` 已部署到 188,正式 `/health` 為 `V10.467`,待推 Gitea。兩段變更已合併驗證:V10.466 rescore duplicate 改看 latest-state,7 筆 SKU 最新 attempt 全為 `rescore_accepted_current`,`competitor_prices` / `competitor_price_history` 目標計數未變;V10.467 focused exact matcher 在容器內回 `exact / total_price / price_alert_exact`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三容器 healthy、PChome rescore queue API HTTP 200、Gemini 24 小時無 provider 紀錄、Ollama env 順序維持 GCP-A → GCP-B → 111、3 分鐘三容器 log 未見 Traceback / ERROR / CRITICAL / IntegrityError。 ## 1. MOMO / PChome 核心比價準確率 @@ -68,6 +69,7 @@ - 2026-05-25 12:25 CST production pilot:SKU `6101639`、`10074951`、`7760902`、`TP00074980000005`、`14774766`、`10142589`、`10262470`、`10262471`、`11308520` 已從 `true_low_confidence` materialize 為 `rescore_accepted_current`,全數 `exact/total_price/price_alert_exact` 且理由含 `focused_exact_total_price_safe`。SKU `6101784` 因「即期品」商業條件不同,刻意保留在 `true_low_confidence`,不納入本輪自動入隊。 - 2026-05-25 14:45 CST 起,matcher 擴充至香氛/精油近門檻安全 cohort:HOOOME 白色經典香氛暖燈與 Gdesign Aroma Lava 2.0 可進 `exact/total_price/price_alert_exact`;Recipe Box 可撕式水性兒童指甲油只進 `identity_review`,因兒童指甲油仍可能藏色款/款式。DASHING DIVA 與 Pavaruni cross-type 負例已補測試,避免跨款式、跨劑型誤配。 - 2026-05-25 15:20 CST 起,新增三個正式觀察到的高分負例防線:PRAY 守夜人暖燈 L vs S、NITORI 香氛噴霧器 5510 vs YX168、LA MER 氣墊粉霜通用 listing vs `11 Rosy Ivory` 色號。前兩者 hard veto,後者保留高分但不進 accepted queue。 +- 2026-05-25 16:15 CST 起,新增商業條件差負例:KAMERIA 足膜、3W CLINIC 粉底液、Sisley 全能乳液等若一側標示即期/效期/盒損,仍可顯示高相似度,但只進 identity review,不自動入 accepted queue。 ## 3. 12 Agent 決策信封整合 diff --git a/services/marketplace_product_matcher.py b/services/marketplace_product_matcher.py index 1f6cda8..c7d636b 100644 --- a/services/marketplace_product_matcher.py +++ b/services/marketplace_product_matcher.py @@ -1976,7 +1976,13 @@ def score_marketplace_match( nitori_diffuser_model_conflict = _has_nitori_diffuser_model_conflict(left, right) if nitori_diffuser_model_conflict: reasons.append("nitori_diffuser_model_conflict") - variant_selection_review = _has_named_variant_selection_review(left, right, shared_anchor) + commercial_condition_gap = _has_commercial_condition_gap(left, right) + if commercial_condition_gap: + reasons.append("commercial_condition_gap") + variant_selection_review = ( + _has_named_variant_selection_review(left, right, shared_anchor) + or commercial_condition_gap + ) if variant_selection_review: reasons.append("variant_selection_review") @@ -3070,6 +3076,24 @@ def _has_makeup_shade_selection_gap(left: ProductIdentity, right: ProductIdentit return bool(left_shades) != bool(right_shades) +def _commercial_condition_terms(identity: ProductIdentity) -> set[str]: + text = identity.normalized_name + terms: set[str] = set() + if any(term in text for term in ("即期品", "臨期", "短效", "短效期", "效期", "保存期限", "有效期限")): + terms.add("expiry_sensitive") + if any(term in text for term in ("盒損", "盒損品", "外盒損", "外盒瑕疵")): + terms.add("box_damage") + if any(term in text for term in ("福利品", "瑕疵品")): + terms.add("clearance_condition") + return terms + + +def _has_commercial_condition_gap(left: ProductIdentity, right: ProductIdentity) -> bool: + left_terms = _commercial_condition_terms(left) + right_terms = _commercial_condition_terms(right) + return bool(left_terms or right_terms) and left_terms != right_terms + + def _has_taicend_baby_spray_equivalence(left: ProductIdentity, right: ProductIdentity) -> bool: brand_tokens = {"taicend", "泰陞"} return ( diff --git a/tests/test_marketplace_product_matcher.py b/tests/test_marketplace_product_matcher.py index 849ba92..6f81d71 100644 --- a/tests/test_marketplace_product_matcher.py +++ b/tests/test_marketplace_product_matcher.py @@ -2222,6 +2222,27 @@ def test_marketplace_matcher_sends_single_sided_makeup_shade_to_review(): assert "variant_selection_review" in diagnostics.reasons +def test_marketplace_matcher_sends_single_sided_commercial_condition_to_review(): + from services.marketplace_product_matcher import score_marketplace_match + + expiry_item = score_marketplace_match( + "即期品【3W CLINIC】膠原蛋白粉底液50mlX2入(保存期限:2026/05)", + "【韓國 3W CLINIC】膠原蛋白粉底液50mlx2入", + ) + box_damage_item = score_marketplace_match( + "【Sisley】全能乳液 125ml(新版 國際航空版)", + "sisley 全能乳液 125ml(新) (即期品/盒損品 / 效期至2026年11月)", + ) + + for diagnostics in (expiry_item, box_damage_item): + assert diagnostics.hard_veto is False + assert diagnostics.score >= 0.76 + assert diagnostics.price_basis == "manual_review" + assert diagnostics.alert_tier == "identity_review" + assert "commercial_condition_gap" in diagnostics.reasons + assert "variant_selection_review" in diagnostics.reasons + + def test_marketplace_matcher_promotes_eaoron_classic_tone_up_cream_exact_line(): from services.marketplace_product_matcher import score_marketplace_match