強化 PChome accepted queue 變體防線
All checks were successful
CD Pipeline / deploy (push) Successful in 1m4s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m4s
This commit is contained in:
@@ -350,11 +350,6 @@ def fetch_variant_rescore_accept_review_rows(
|
||||
limit: int = 100,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Fetch latest rescore-accepted rows that should be retracted to review."""
|
||||
reason_predicate = (
|
||||
"diagnostic_codes::text LIKE :reason_filter"
|
||||
if conn.dialect.name == "postgresql"
|
||||
else "CAST(diagnostic_codes AS TEXT) LIKE :reason_filter"
|
||||
)
|
||||
nulls_last = " NULLS LAST" if conn.dialect.name == "postgresql" else ""
|
||||
sql = text(f"""
|
||||
WITH ranked AS (
|
||||
@@ -400,19 +395,30 @@ def fetch_variant_rescore_accept_review_rows(
|
||||
FROM ranked
|
||||
WHERE rn = 1
|
||||
AND attempt_status = :attempt_status
|
||||
AND {reason_predicate}
|
||||
ORDER BY attempted_at DESC{nulls_last}
|
||||
LIMIT :limit
|
||||
""")
|
||||
return [
|
||||
rows = [
|
||||
dict(row)
|
||||
for row in conn.execute(sql, {
|
||||
"source": source,
|
||||
"attempt_status": RESCORE_ACCEPTED_CURRENT_STATUS,
|
||||
"reason_filter": "%variant_selection_review%",
|
||||
"limit": max(1, int(limit)),
|
||||
}).mappings().all()
|
||||
]
|
||||
retractable_rows: list[dict[str, Any]] = []
|
||||
for row in rows:
|
||||
diagnostic_text = json.dumps(row.get("diagnostic_codes"), ensure_ascii=False, default=str)
|
||||
if "variant_selection_review" in diagnostic_text:
|
||||
retractable_rows.append(row)
|
||||
continue
|
||||
decision = classify_match_attempt_row(row)
|
||||
if (
|
||||
"variant_selection_review" in set(decision.reasons)
|
||||
and decision.suggested_status == "low_score_current"
|
||||
):
|
||||
retractable_rows.append(row)
|
||||
return retractable_rows
|
||||
|
||||
|
||||
def retract_variant_rescore_accept_reviews(
|
||||
|
||||
@@ -1978,6 +1978,9 @@ def score_marketplace_match(
|
||||
makeup_spray_line_conflict = _has_makeup_spray_line_conflict(left, right)
|
||||
if makeup_spray_line_conflict:
|
||||
reasons.append("makeup_spray_line_conflict")
|
||||
romand_lip_line_conflict = _has_romand_lip_line_conflict(left, right)
|
||||
if romand_lip_line_conflict:
|
||||
reasons.append("romand_lip_line_conflict")
|
||||
nail_tool_function_conflict = _has_nail_tool_function_conflict(left, right)
|
||||
if nail_tool_function_conflict:
|
||||
reasons.append("nail_tool_function_conflict")
|
||||
@@ -2011,6 +2014,9 @@ def score_marketplace_match(
|
||||
cleanser_lotion_line_conflict = _has_cleanser_lotion_line_conflict(left, right)
|
||||
if cleanser_lotion_line_conflict:
|
||||
reasons.append("cleanser_lotion_line_conflict")
|
||||
selection1990_wax_lamp_design_conflict = _has_selection1990_wax_lamp_design_conflict(left, right)
|
||||
if selection1990_wax_lamp_design_conflict:
|
||||
reasons.append("selection1990_wax_lamp_design_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")
|
||||
@@ -2020,9 +2026,13 @@ def score_marketplace_match(
|
||||
commercial_condition_gap = _has_commercial_condition_gap(left, right)
|
||||
if commercial_condition_gap:
|
||||
reasons.append("commercial_condition_gap")
|
||||
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")
|
||||
variant_selection_review = (
|
||||
_has_named_variant_selection_review(left, right, shared_anchor)
|
||||
or commercial_condition_gap
|
||||
or relove_private_cleanser_variant_gap
|
||||
)
|
||||
if variant_selection_review:
|
||||
reasons.append("variant_selection_review")
|
||||
@@ -2062,6 +2072,8 @@ def score_marketplace_match(
|
||||
hard_veto = True
|
||||
if makeup_spray_line_conflict:
|
||||
hard_veto = True
|
||||
if romand_lip_line_conflict:
|
||||
hard_veto = True
|
||||
if nail_tool_function_conflict:
|
||||
hard_veto = True
|
||||
if schick_razor_line_conflict:
|
||||
@@ -2084,6 +2096,8 @@ def score_marketplace_match(
|
||||
hard_veto = True
|
||||
if cleanser_lotion_line_conflict:
|
||||
hard_veto = True
|
||||
if selection1990_wax_lamp_design_conflict:
|
||||
hard_veto = True
|
||||
if wax_lamp_size_letter_conflict:
|
||||
hard_veto = True
|
||||
if nitori_diffuser_model_conflict:
|
||||
@@ -2973,6 +2987,35 @@ def _has_makeup_spray_variant_selection_gap(left: ProductIdentity, right: Produc
|
||||
return left_groups != right_groups
|
||||
|
||||
|
||||
def _romand_lip_line_groups(identity: ProductIdentity) -> set[str]:
|
||||
text = identity.searchable_name
|
||||
groups: set[str] = set()
|
||||
if "果汁唇釉" in text or "juicy" in text:
|
||||
groups.add("juicy")
|
||||
if "零絲絨" in text or "zero velvet" in text or "霧面唇釉" in text:
|
||||
groups.add("zero_velvet")
|
||||
if "果凍唇釉" in text or "glasting" in text or "唇凍" in text:
|
||||
groups.add("glasting")
|
||||
if "水感唇釉" in text:
|
||||
groups.add("water_gloss")
|
||||
return groups
|
||||
|
||||
|
||||
def _has_romand_lip_line_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
pair_text = f"{left.searchable_name} {right.searchable_name}"
|
||||
if not (
|
||||
{"rom", "romand"} & (left.brand_tokens | right.brand_tokens)
|
||||
or "rom&nd" in pair_text
|
||||
or "romand" in pair_text
|
||||
):
|
||||
return False
|
||||
if "唇" not in left.searchable_name or "唇" not in right.searchable_name:
|
||||
return False
|
||||
left_groups = _romand_lip_line_groups(left)
|
||||
right_groups = _romand_lip_line_groups(right)
|
||||
return bool(left_groups and right_groups and left_groups.isdisjoint(right_groups))
|
||||
|
||||
|
||||
def _has_nail_tool_function_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
left_text = left.searchable_name
|
||||
right_text = right.searchable_name
|
||||
@@ -3202,6 +3245,31 @@ def _has_cleanser_lotion_line_conflict(left: ProductIdentity, right: ProductIden
|
||||
return bool((left_cleanser and right_lotion) or (right_cleanser and left_lotion))
|
||||
|
||||
|
||||
def _selection1990_wax_lamp_design_groups(identity: ProductIdentity) -> set[str]:
|
||||
text = identity.searchable_name
|
||||
groups: set[str] = set()
|
||||
if "現代簡約半圓罩融燭燈" in text or "半圓罩" in text:
|
||||
groups.add("half_dome")
|
||||
if "歐式可彎融燭燈" in text or "可彎融燭燈" in text:
|
||||
groups.add("bendable")
|
||||
if "韓風原木底座融燭燈" in text or "原木底座融燭燈" in text:
|
||||
groups.add("wood_base")
|
||||
if "北歐簡樸融蠟燈" in text or "北歐簡樸" in text:
|
||||
groups.add("nordic")
|
||||
return groups
|
||||
|
||||
|
||||
def _has_selection1990_wax_lamp_design_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
if not ({"1990", "選物"} <= (left.brand_tokens & right.brand_tokens)):
|
||||
return False
|
||||
pair_text = f"{left.searchable_name} {right.searchable_name}"
|
||||
if not any(term in pair_text for term in ("融燭燈", "蠟燭暖燈", "融蠟燈")):
|
||||
return False
|
||||
left_groups = _selection1990_wax_lamp_design_groups(left)
|
||||
right_groups = _selection1990_wax_lamp_design_groups(right)
|
||||
return bool(left_groups and right_groups and left_groups.isdisjoint(right_groups))
|
||||
|
||||
|
||||
def _standalone_size_letter_tokens(identity: ProductIdentity) -> set[str]:
|
||||
text = identity.searchable_name
|
||||
return {
|
||||
@@ -3282,6 +3350,15 @@ def _has_commercial_condition_gap(left: ProductIdentity, right: ProductIdentity)
|
||||
return bool(left_terms or right_terms) and left_terms != right_terms
|
||||
|
||||
|
||||
def _has_relove_private_cleanser_variant_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
if not _is_relove_cleanser_gel_like(left, right):
|
||||
return False
|
||||
brightening_terms = ("傳明酸", "淨白", "美白", "亮白", "菸鹼醯胺", "niacinamide")
|
||||
left_brightening = any(term in left.searchable_name for term in brightening_terms)
|
||||
right_brightening = any(term in right.searchable_name for term in brightening_terms)
|
||||
return left_brightening != right_brightening
|
||||
|
||||
|
||||
def _has_catalog_specific_variant_selection_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
pair_text = f"{left.searchable_name} {right.searchable_name}"
|
||||
if not any(
|
||||
@@ -3888,6 +3965,18 @@ def _is_relove_private_cleanser_line(left: ProductIdentity, right: ProductIdenti
|
||||
)
|
||||
|
||||
|
||||
def _is_relove_cleanser_gel_like(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
if "relove" not in (left.brand_tokens | right.brand_tokens):
|
||||
return False
|
||||
cleanser_terms = ("私密", "潔淨", "清潔")
|
||||
return (
|
||||
"凝露" in left.searchable_name
|
||||
and "凝露" in right.searchable_name
|
||||
and any(term in left.searchable_name for term in cleanser_terms)
|
||||
and any(term in right.searchable_name for term in cleanser_terms)
|
||||
)
|
||||
|
||||
|
||||
def _is_multi_variant_catalog_listing(identity: ProductIdentity) -> bool:
|
||||
text = identity.normalized_name
|
||||
return any(phrase in text for phrase in MULTI_VARIANT_LISTING_PHRASES)
|
||||
|
||||
Reference in New Issue
Block a user