收斂 PChome gate pass 變體風險
All checks were successful
CD Pipeline / deploy (push) Successful in 1m5s

This commit is contained in:
OoO
2026-05-25 23:08:31 +08:00
parent 64a39a89b3
commit f2aa81815e
5 changed files with 151 additions and 1 deletions

View File

@@ -4,6 +4,7 @@
================================================================================
【已完成】
- V10.483 收斂舊 gate pass 風險NARS 遮瑕蜜任選、LOREAL 玻尿酸啵啵精華水/液態紫熨斗 vs 水光精華、SEBAMED 洗髮乳任選、Schick 舒綺 2-in-1 型號落差、TAICEND 保護膜 vs 噴霧,現在都會保留高分但加 `variant_selection_review` 與專屬 reason不再被 rescore 自動送進 accepted queue。Production 已部署 `/health=V10.483`;目標 5 SKU audit `gate_pass=0 / still_low=5`,並用 `--retract-variant-accepted` 退回 4 筆舊 accepted 變體風險latest accepted audit `scanned=90 / gate_pass=90 / still_low=0`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
- V10.482 補 exact variant-safe 回收LUSH 櫻之花身體噴霧 200ml、ARTMIS 金縷梅/蔓越莓私密清潔慕斯 250ml、SO NATURAL FIXX 120ml plain 與 Baan 原味/草莓同 catalog若雙方同品名、同規格且同明確 variant移除過度保守的 `variant_selection_review` 並進 `exact / total_price / price_alert_exact`SO NATURAL 經典款/光澤款/霧面款/夏日款 catalog 對單款 120ml 仍維持人工覆核。Production 已部署 `/health=V10.482`,並只 materialize 5 筆新增 exact-line SKU 到 `rescore_accepted_current`,最新 accepted audit `scanned=94 / gate_pass=94 / still_low=0`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
- V10.481 補 rescore accepted retraction 工具缺口:`--retract-variant-accepted` 不只看舊 row 已存的 `diagnostic_codes`,也會用當前 matcher 重判 latest `rescore_accepted_current`;若新版規則已變成 `variant_selection_review / low_score_current`,會追加退回 `true_low_confidence`,避免舊 accepted queue 殘留不該採用候選。Production 已先保守 materialize 15 筆安全 SKU再退回 7 筆舊 accepted 變體風險;最終 `rescore_accepted_current=89`accepted audit `gate_pass=89 / still_low=0`。
- V10.480 依 production accepted-current 風險樣本補安全閘門rom&nd 零絲絨/果凍唇釉不可被果汁唇釉多款 listing 誤收為同款Relove 潔淨凝露若一側為傳明酸/淨白活性變體改送 `variant_selection_review`1990 融燭燈不同設計(歐式可彎 vs 韓風原木底座)改 hard veto。此版先清 accepted queue 風險,再做保守 materialize。

View File

@@ -350,7 +350,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.482"
SYSTEM_VERSION = "V10.483"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -34,6 +34,7 @@
- 2026-05-25 21:05 CST 起,`V10.480` 補 accepted-current 風險樣本防線rom&nd 零絲絨/果凍唇釉 vs 果汁唇釉多款 listing 直接 `romand_lip_line_conflict` hard vetoRelove 潔淨凝露若傳明酸/淨白活性只出現在單側,保留高分但進 `variant_selection_review`1990 融燭燈不同設計(歐式可彎 vs 韓風原木底座)直接 `selection1990_wax_lamp_design_conflict` hard veto。
- 2026-05-25 21:25 CST 起,`V10.481` 補 rescore accepted retraction 工具缺口:退回工具會用當前 matcher 重判 latest `rescore_accepted_current`,凡新版已變 `variant_selection_review / low_score_current` 的舊 accepted 會追加退回 `true_low_confidence`避免人工覆核隊列保留舊版安全閘門放行的候選。Production 已部署 `/health=V10.481`;保守 materialize 15 筆安全 SKU 後再退回 7 筆舊 accepted 變體風險,最新 accepted audit 為 `scanned=89 / gate_pass=89 / still_low=0`
- 2026-05-25 21:47 CST 起,`V10.482` 補 exact variant-safe 回收LUSH 櫻之花身體噴霧 200ml、ARTMIS 金縷梅/蔓越莓私密清潔慕斯 250ml、SO NATURAL FIXX 120ml plain 與 Baan 原味/草莓同 catalog若雙方同品名、同規格且同明確 variant移除過度保守的 `variant_selection_review` 並進 `exact / total_price / price_alert_exact`SO NATURAL 經典款/光澤款/霧面款/夏日款 catalog 對單款 120ml 仍維持人工覆核。Production 已部署 `/health=V10.482`,只 materialize 5 筆新增 exact-line SKU 到 `rescore_accepted_current`latest accepted audit 為 `scanned=94 / gate_pass=94 / still_low=0`,三應用容器 healthy、`momo-db` 未 recreate。
- 2026-05-25 23:03 CST 起,`V10.483` 收斂舊 gate pass 風險NARS 遮瑕蜜任選、LOREAL 玻尿酸啵啵精華水/液態紫熨斗 vs 水光精華、SEBAMED 洗髮乳任選、Schick 舒綺 2-in-1 型號落差、TAICEND 保護膜 vs 噴霧,現在都會保留高分但加 `variant_selection_review` 與專屬 reason不再被 rescore 自動送進 accepted queue。Production 已部署 `/health=V10.483`;目標 5 SKU audit `gate_pass=0 / still_low=5`,並用 `--retract-variant-accepted` 退回 4 筆舊 accepted 變體風險latest accepted audit 為 `scanned=90 / gate_pass=90 / still_low=0`
- 2026-05-25 12:05 CST 狀態:`main` 已部署到 188正式 `/health``V10.467`,待推 Gitea。兩段變更已合併驗證V10.466 rescore duplicate 改看 latest-state7 筆 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 核心比價準確率

View File

@@ -2042,10 +2042,30 @@ def score_marketplace_match(
relove_private_cleanser_variant_gap = _has_relove_private_cleanser_variant_gap(left, right)
if relove_private_cleanser_variant_gap:
reasons.append("relove_private_cleanser_variant_gap")
makeup_catalog_selection_gap = _has_makeup_catalog_selection_gap(left, right)
if makeup_catalog_selection_gap:
reasons.append("makeup_catalog_selection_gap")
loreal_serum_variant_gap = _has_loreal_serum_variant_gap(left, right)
if loreal_serum_variant_gap:
reasons.append("loreal_serum_variant_gap")
sebamed_shampoo_variant_catalog_gap = _has_sebamed_shampoo_variant_catalog_gap(left, right)
if sebamed_shampoo_variant_catalog_gap:
reasons.append("sebamed_shampoo_variant_catalog_gap")
schick_2in1_model_gap = _has_schick_2in1_model_gap(left, right)
if schick_2in1_model_gap:
reasons.append("schick_2in1_model_gap")
taicend_protection_form_gap = _has_taicend_protection_form_gap(left, right)
if taicend_protection_form_gap:
reasons.append("taicend_protection_form_gap")
variant_selection_review = (
_has_named_variant_selection_review(left, right, shared_anchor)
or commercial_condition_gap
or relove_private_cleanser_variant_gap
or makeup_catalog_selection_gap
or loreal_serum_variant_gap
or sebamed_shampoo_variant_catalog_gap
or schick_2in1_model_gap
or taicend_protection_form_gap
)
if variant_selection_review:
reasons.append("variant_selection_review")
@@ -3375,6 +3395,93 @@ def _has_relove_private_cleanser_variant_gap(left: ProductIdentity, right: Produ
return left_brightening != right_brightening
def _has_makeup_catalog_selection_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
pair_text = f"{left.searchable_name} {right.searchable_name}"
sensitive_terms = (
"遮瑕蜜",
"遮瑕",
"粉底",
"粉霜",
"氣墊",
"蜜粉",
"腮紅",
"眼線",
"眉筆",
"染眉膏",
"唇膏",
"唇釉",
"唇蜜",
)
if not any(term in pair_text for term in sensitive_terms):
return False
if not (_is_catalog_or_delimited_variant_listing(left) or _is_catalog_or_delimited_variant_listing(right)):
return False
left_shades = _makeup_shade_tokens(left)
right_shades = _makeup_shade_tokens(right)
if left_shades and right_shades and _variant_options_overlap(left_shades, right_shades):
return False
return True
def _has_loreal_serum_variant_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
pair_text = f"{left.searchable_name} {right.searchable_name}"
if not ({"loreal", "巴黎萊雅"} & (left.brand_tokens | right.brand_tokens)):
return False
if "玻尿酸瞬效保濕" not in pair_text:
return False
variant_terms = ("啵啵精華水", "液態紫熨斗", "水光精華", "修護晶露", "保濕水光")
left_terms = {term for term in variant_terms if term in left.searchable_name}
right_terms = {term for term in variant_terms if term in right.searchable_name}
if not (left_terms or right_terms):
return False
return left_terms != right_terms or _is_catalog_or_delimited_variant_listing(left) != _is_catalog_or_delimited_variant_listing(right)
def _has_sebamed_shampoo_variant_catalog_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
if not ({"sebamed", "施巴"} & (left.brand_tokens | right.brand_tokens)):
return False
if "洗髮乳" not in left.searchable_name or "洗髮乳" not in right.searchable_name:
return False
variant_terms = ("溫和", "油性抗屑", "抗屑", "乾性", "敏感")
left_terms = {term for term in variant_terms if term in left.searchable_name}
right_terms = {term for term in variant_terms if term in right.searchable_name}
if _is_catalog_or_delimited_variant_listing(left) != _is_catalog_or_delimited_variant_listing(right):
return True
return bool(left_terms or right_terms) and left_terms != right_terms
def _has_schick_2in1_model_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
if not ({"schick", "舒適牌"} & (left.brand_tokens & right.brand_tokens)):
return False
pair_text = f"{left.searchable_name} {right.searchable_name}"
if "舒綺" not in pair_text or "美型刀" not in pair_text:
return False
left_2in1 = bool(re.search(r"2\s*(?:-?in-?|合)?\s*1", left.searchable_name, re.I))
right_2in1 = bool(re.search(r"2\s*(?:-?in-?|合)?\s*1", right.searchable_name, re.I))
return left_2in1 != right_2in1
def _has_taicend_protection_form_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
if not ({"taicend", "泰陞"} & (left.brand_tokens & right.brand_tokens)):
return False
pair_text = f"{left.searchable_name} {right.searchable_name}"
if "保護膜" not in pair_text and "保護噴霧" not in pair_text and "液態皮膚保護膜" not in pair_text:
return False
if "屁屁噴" in left.searchable_name and "屁屁噴" in right.searchable_name:
return False
left_terms = {
term
for term in ("寶貝液體保護膜", "液態皮膚保護膜", "皮膚保護噴霧", "保護噴霧")
if term in left.searchable_name
}
right_terms = {
term
for term in ("寶貝液體保護膜", "液態皮膚保護膜", "皮膚保護噴霧", "保護噴霧")
if term in right.searchable_name
}
return bool(left_terms or right_terms) and left_terms != right_terms
def _has_catalog_specific_variant_selection_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
pair_text = f"{left.searchable_name} {right.searchable_name}"
if not any(

View File

@@ -2344,6 +2344,47 @@ def test_marketplace_matcher_sends_catalog_specific_variant_gaps_to_review():
assert "variant_selection_review" in diagnostics.reasons
def test_marketplace_matcher_sends_legacy_gate_pass_risks_to_review():
from services.marketplace_product_matcher import score_marketplace_match
cases = [
(
"【NARS】官方直營 妝點甜心遮瑕蜜(任選)",
"NARS 妝點甜心遮瑕蜜 1.4ml 多款可選",
"makeup_catalog_selection_gap",
),
(
"【LOREAL Paris 巴黎萊雅】玻尿酸瞬效保濕修護晶露2入組(啵啵精華水/液態紫熨斗/保濕)",
"(2入組)【LOREAL Paris 巴黎萊雅】玻尿酸瞬效保濕水光精華 30ml",
"loreal_serum_variant_gap",
),
(
"【SEBAMED 施巴】洗髮乳1000ml+安絲洗髮乳400ml(總代理)",
"施巴5.5 sebamed (溫和/油性抗屑)洗髮乳1000ml任選x1+安絲洗髮乳400ml",
"sebamed_shampoo_variant_catalog_gap",
),
(
"【Schick 舒適牌】舒綺美型刀 除毛刀(1刀把1刀片)",
"【Schick 舒適牌】舒綺2-in-1美型刀 ( 1刀把1刀片 )",
"schick_2in1_model_gap",
),
(
"【TAICEND 泰陞】寶貝液體保護膜(70ml)",
"【TAICEND泰陞】皮膚保護噴霧(70ml)",
"taicend_protection_form_gap",
),
]
for momo_name, competitor_name, expected_reason in cases:
diagnostics = score_marketplace_match(momo_name, competitor_name)
assert diagnostics.score >= 0.76
assert diagnostics.hard_veto is False
assert diagnostics.price_basis == "manual_review"
assert diagnostics.alert_tier == "identity_review"
assert "variant_selection_review" in diagnostics.reasons
assert expected_reason in diagnostics.reasons
def test_marketplace_matcher_blocks_scent_and_core_line_conflicts():
from services.marketplace_product_matcher import score_marketplace_match