強化 PChome rescore 漏掃與錯配防線
All checks were successful
CD Pipeline / deploy (push) Successful in 1m4s

This commit is contained in:
OoO
2026-05-25 16:05:08 +08:00
parent dab782cc6d
commit bd33f7ff60
7 changed files with 140 additions and 2 deletions

View File

@@ -1970,6 +1970,12 @@ def score_marketplace_match(
aroma_scent_variant_conflict = _has_aroma_scent_variant_conflict(left, right)
if aroma_scent_variant_conflict:
reasons.append("aroma_scent_variant_conflict")
wax_lamp_size_letter_conflict = _has_wax_lamp_size_letter_conflict(left, right)
if wax_lamp_size_letter_conflict:
reasons.append("size_letter_variant_conflict")
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)
if variant_selection_review:
reasons.append("variant_selection_review")
@@ -2021,6 +2027,10 @@ def score_marketplace_match(
hard_veto = True
if aroma_scent_variant_conflict:
hard_veto = True
if wax_lamp_size_letter_conflict:
hard_veto = True
if nitori_diffuser_model_conflict:
hard_veto = True
focused_exact_line_reason = _has_focused_low_score_exact_identity_line(left, right)
if focused_exact_line_reason in FOCUSED_IDENTITY_REVIEW_ONLY_REASONS:
@@ -3007,6 +3017,59 @@ def _has_aroma_scent_variant_conflict(left: ProductIdentity, right: ProductIdent
return False
def _standalone_size_letter_tokens(identity: ProductIdentity) -> set[str]:
text = identity.searchable_name
return {
match.group(1).lower()
for match in re.finditer(r"(?<![a-z0-9])([sml])(?![a-z0-9])", text, re.I)
}
def _has_wax_lamp_size_letter_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
pair_text = f"{left.searchable_name} {right.searchable_name}"
if not any(term in pair_text for term in ("香氛蠟燭暖燈", "蠟燭暖燈", "融蠟燈")):
return False
left_sizes = _standalone_size_letter_tokens(left)
right_sizes = _standalone_size_letter_tokens(right)
return bool(left_sizes and right_sizes and not (left_sizes & right_sizes))
def _has_nitori_diffuser_model_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
if not ({"nitori", "宜得利家居"} & (left.brand_tokens & right.brand_tokens)):
return False
if "香氛噴霧器" not in left.searchable_name or "香氛噴霧器" not in right.searchable_name:
return False
left_models = _extract_model_tokens(left.searchable_name) | set(
re.findall(r"(?<![a-z0-9])\d{3,5}(?![a-z0-9])", left.searchable_name)
)
right_models = _extract_model_tokens(right.searchable_name) | set(
re.findall(r"(?<![a-z0-9])\d{3,5}(?![a-z0-9])", right.searchable_name)
)
return bool(left_models and right_models and not (left_models & right_models))
def _makeup_shade_tokens(identity: ProductIdentity) -> set[str]:
text = identity.searchable_name
tokens = set(_explicit_variant_option_tokens(identity))
shade_pattern = (
r"(?<![a-z0-9])(?:#|no\.?|色號|號色)?\s*(\d{1,3})\s+"
r"(rosy ivory|ivory|beige|sand|fair|light|medium|porcelain|rose)(?![a-z0-9])"
)
for match in re.finditer(shade_pattern, text, re.I):
tokens.add(match.group(1).lower())
tokens.add(match.group(2).lower().replace(" ", "_"))
return tokens
def _has_makeup_shade_selection_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
pair_text = f"{left.searchable_name} {right.searchable_name}"
if not any(term in pair_text for term in ("氣墊粉霜", "粉底", "粉霜", "蜜粉", "唇釉", "唇膏")):
return False
left_shades = _makeup_shade_tokens(left)
right_shades = _makeup_shade_tokens(right)
return bool(left_shades) != bool(right_shades)
def _has_taicend_baby_spray_equivalence(left: ProductIdentity, right: ProductIdentity) -> bool:
brand_tokens = {"taicend", "泰陞"}
return (
@@ -3734,6 +3797,8 @@ def _has_named_variant_selection_review(
right: ProductIdentity,
shared_anchor: str,
) -> bool:
if _has_makeup_shade_selection_gap(left, right):
return True
left_options = _explicit_variant_option_tokens(left)
right_options = _explicit_variant_option_tokens(right)
if bool(left_options) != bool(right_options):