V10.489 strengthen PChome manual review matching
This commit is contained in:
@@ -1386,6 +1386,58 @@ def _has_pack_quantity_difference(left: ProductIdentity, right: ProductIdentity)
|
||||
return False
|
||||
|
||||
|
||||
NAMED_COMPONENT_QUANTITY_GROUPS = (
|
||||
("嬰兒沐浴精", "嬰幼童洗髮精"),
|
||||
("魅惑麋香", "湛藍海洋", "花妍巧語", "絲絨玫瑰"),
|
||||
)
|
||||
|
||||
|
||||
def _named_component_quantity_map(identity: ProductIdentity, terms: Iterable[str]) -> dict[str, int]:
|
||||
text = identity.searchable_name
|
||||
present_terms = tuple(term for term in terms if term in text)
|
||||
if len(present_terms) < 2:
|
||||
return {}
|
||||
|
||||
quantities: dict[str, int] = {}
|
||||
for term in present_terms:
|
||||
term_index = text.find(term)
|
||||
if term_index < 0:
|
||||
continue
|
||||
suffix = text[term_index + len(term):term_index + len(term) + 28]
|
||||
explicit_count = re.search(
|
||||
r"(?:\d+(?:\.\d+)?\s*(?:ml|g|mg|毫升|公克|毫克))?\s*(?:x|乘)\s*(\d+)",
|
||||
suffix,
|
||||
flags=re.I,
|
||||
)
|
||||
if explicit_count:
|
||||
quantities[term] = int(explicit_count.group(1))
|
||||
|
||||
if len(quantities) == len(present_terms):
|
||||
return quantities
|
||||
|
||||
pack_counts = [
|
||||
count
|
||||
for count, unit in identity.counts
|
||||
if _count_unit_family(unit) in COUNT_UNITS or unit in COUNT_UNITS
|
||||
]
|
||||
if not quantities and pack_counts and max(pack_counts) == len(present_terms) and re.search(r"[++//、]", text):
|
||||
return {term: 1 for term in present_terms}
|
||||
return {}
|
||||
|
||||
|
||||
def _has_named_component_quantity_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
"""同名組合品若命名元件相同但數量反轉,不能視為同一價格標的。"""
|
||||
for terms in NAMED_COMPONENT_QUANTITY_GROUPS:
|
||||
left_quantities = _named_component_quantity_map(left, terms)
|
||||
right_quantities = _named_component_quantity_map(right, terms)
|
||||
shared_terms = set(left_quantities) & set(right_quantities)
|
||||
if len(shared_terms) < 2:
|
||||
continue
|
||||
if any(left_quantities[term] != right_quantities[term] for term in shared_terms):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _spec_score(left: ProductIdentity, right: ProductIdentity) -> tuple[float, bool, tuple[str, ...]]:
|
||||
volume_score, volume_conflict = _spec_component(left.volumes_ml, right.volumes_ml)
|
||||
weight_score, weight_conflict = _spec_component(left.weights_g, right.weights_g)
|
||||
@@ -1977,6 +2029,9 @@ def score_marketplace_match(
|
||||
reasons.append("catalog_count_omission")
|
||||
if _has_pack_quantity_difference(left, right):
|
||||
reasons.append("pack_quantity_difference")
|
||||
named_component_quantity_conflict = _has_named_component_quantity_conflict(left, right)
|
||||
if named_component_quantity_conflict:
|
||||
reasons.append("named_component_quantity_conflict")
|
||||
variant_descriptor_conflict = _has_variant_descriptor_conflict(left, right, shared_anchor)
|
||||
sun_protection_line_conflict = (
|
||||
variant_descriptor_conflict
|
||||
@@ -2037,6 +2092,9 @@ def score_marketplace_match(
|
||||
ingredient_line_conflict = _has_core_ingredient_line_conflict(left, right)
|
||||
if ingredient_line_conflict:
|
||||
reasons.append("core_ingredient_line_conflict")
|
||||
clarins_body_oil_line_conflict = _has_clarins_body_oil_line_conflict(left, right)
|
||||
if clarins_body_oil_line_conflict:
|
||||
reasons.append("clarins_body_oil_line_conflict")
|
||||
branded_powder_line_conflict = _has_branded_powder_line_conflict(left, right)
|
||||
if branded_powder_line_conflict:
|
||||
reasons.append("branded_powder_line_conflict")
|
||||
@@ -2046,6 +2104,9 @@ def score_marketplace_match(
|
||||
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")
|
||||
hooome_wax_lamp_design_gap = _has_hooome_wax_lamp_design_gap(left, right)
|
||||
if hooome_wax_lamp_design_gap:
|
||||
reasons.append("hooome_wax_lamp_design_gap")
|
||||
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")
|
||||
@@ -2085,6 +2146,7 @@ def score_marketplace_match(
|
||||
or relove_private_cleanser_variant_gap
|
||||
or candle_catalog_selection_gap
|
||||
or bath_additive_variant_gap
|
||||
or hooome_wax_lamp_design_gap
|
||||
or makeup_catalog_selection_gap
|
||||
or loreal_serum_variant_gap
|
||||
or sebamed_shampoo_variant_catalog_gap
|
||||
@@ -2101,6 +2163,8 @@ def score_marketplace_match(
|
||||
hard_veto = True
|
||||
if multi_component_count_conflict:
|
||||
hard_veto = True
|
||||
if named_component_quantity_conflict:
|
||||
hard_veto = True
|
||||
if _has_refill_pack(left) != _has_refill_pack(right):
|
||||
hard_veto = True
|
||||
if accessory_case_conflict:
|
||||
@@ -2149,6 +2213,8 @@ def score_marketplace_match(
|
||||
hard_veto = True
|
||||
if ingredient_line_conflict:
|
||||
hard_veto = True
|
||||
if clarins_body_oil_line_conflict:
|
||||
hard_veto = True
|
||||
if branded_powder_line_conflict:
|
||||
hard_veto = True
|
||||
if cleanser_lotion_line_conflict:
|
||||
@@ -3282,6 +3348,29 @@ def _has_core_ingredient_line_conflict(left: ProductIdentity, right: ProductIden
|
||||
return bool(left_groups and right_groups and not (left_groups & right_groups))
|
||||
|
||||
|
||||
def _has_clarins_body_oil_line_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
if not ({"clarins", "克蘭詩"} & (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
|
||||
line_groups = {
|
||||
"contour_lightweight": ("輕盈美體", "美體護理油", "contour"),
|
||||
"tonic_body": ("身體調和", "調和護理油", "孕期身體調和", "tonic"),
|
||||
}
|
||||
left_groups = {
|
||||
group
|
||||
for group, terms in line_groups.items()
|
||||
if any(term in left.searchable_name for term in terms)
|
||||
}
|
||||
right_groups = {
|
||||
group
|
||||
for group, terms in line_groups.items()
|
||||
if any(term in right.searchable_name for term in terms)
|
||||
}
|
||||
return bool(left_groups and right_groups and left_groups.isdisjoint(right_groups))
|
||||
|
||||
|
||||
def _has_branded_powder_line_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
if not ({"港香蘭"} & (left.brand_tokens & right.brand_tokens)):
|
||||
return False
|
||||
@@ -3332,6 +3421,18 @@ def _has_selection1990_wax_lamp_design_conflict(left: ProductIdentity, right: Pr
|
||||
return bool(left_groups and right_groups and left_groups.isdisjoint(right_groups))
|
||||
|
||||
|
||||
def _has_hooome_wax_lamp_design_gap(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
if "hooome" not in (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
|
||||
concrete_design_terms = ("大理石", "雲石", "原木", "半圓罩")
|
||||
left_designs = {term for term in concrete_design_terms if term in left.searchable_name}
|
||||
right_designs = {term for term in concrete_design_terms if term in right.searchable_name}
|
||||
return bool(left_designs or right_designs) and left_designs != right_designs
|
||||
|
||||
|
||||
def _standalone_size_letter_tokens(identity: ProductIdentity) -> set[str]:
|
||||
text = identity.searchable_name
|
||||
return {
|
||||
@@ -3860,6 +3961,16 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro
|
||||
and _has_shared_weight(left, right, 40)
|
||||
):
|
||||
return "ts6_private_elastic_gel_40g"
|
||||
if (
|
||||
{"ts6", "護一生"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "超美" in left_text
|
||||
and "超美" in right_text
|
||||
and "香氛誘霜" in left_text
|
||||
and "香氛誘霜" in right_text
|
||||
and (120.0 in set(left.weights_g) or 120.0 in set(left.volumes_ml))
|
||||
and (120.0 in set(right.weights_g) or 120.0 in set(right.volumes_ml))
|
||||
):
|
||||
return "ts6_private_white_fragrance_cream_120"
|
||||
if (
|
||||
{"ts6", "護一生"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "淨白植感慕斯" in left_text
|
||||
@@ -4037,6 +4148,24 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro
|
||||
and _has_shared_volume(left, right, 150)
|
||||
):
|
||||
return "derma_eco_skin_oil"
|
||||
if (
|
||||
{"derma", "丹麥德瑪"} & (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_exact_count_alignment(left, right)
|
||||
):
|
||||
return "derma_eco_skin_oil_2pack_review"
|
||||
if (
|
||||
{"修護保養"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "蝸牛特潤修護面膜" in left_text
|
||||
and "蝸牛特潤修護面膜" in right_text
|
||||
and _has_shared_count(left, right, 6, "片")
|
||||
):
|
||||
return "w_repair_snail_mask_6pcs_review"
|
||||
if (
|
||||
{"yuskin", "悠斯晶"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "乳霜" in left_text
|
||||
|
||||
Reference in New Issue
Block a user