This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
================================================================================
|
||||
|
||||
【已完成】
|
||||
- V10.479 依 production audit 再補二階風險:Cetaphil 修護乳 vs 潔膚露改 hard veto;私密防護慕絲二款可選 vs 單一香型、雪芙蘭滋養霜 vs 單側清爽型改走 `variant_selection_review`,避免仍殘留在 accepted queue。
|
||||
- V10.478 補 PChome 高分錯配 / catalog 變體防線:精油/香氛類若兩側明確香味不同(如檸檬草 vs 茶樹)直接 veto;NOW 椰子油膏 vs 乳木果油、港香蘭漢本 vs 艾魔菈爽身粉改為商品線硬擋;多色/多香/數字區間 catalog 對單一款式(粉餅盒、眉筆、眼線膠筆、車用擴香蕊等)只進 `variant_selection_review`,不自動進 accepted queue。
|
||||
- V10.477 補 PChome 高分錯配防線:SPF 數值不同(如 SPF25 vs SPF50)直接 veto;MAKE UP FOR EVER 定妝噴霧 vs 活氧水不同線直接 veto;多款任選對單一款(私密潔浴露、身體去角質、乳液、染眉膏等)與單側色號改送 `variant_selection_review`,避免高分候選誤入 accepted queue。
|
||||
- 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` 通過。
|
||||
|
||||
@@ -350,7 +350,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.477"
|
||||
SYSTEM_VERSION = "V10.479"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
- 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。
|
||||
- 2026-05-25 19:20 CST 起,新增高分負例:Jealousness SPF25 vs SPF50、MAKE UP FOR EVER 超光肌控油定妝噴霧 vs 超光肌活氧水、rom&nd 染眉膏通用 listing vs `03 摩登米`、Lactacyd 多款潔浴露 vs 單一亮肌柔滑、我的心機多款身體去角質 vs 單一香型。前兩者 hard veto,其餘進 identity review。
|
||||
- 2026-05-25 20:05 CST 起,新增高分錯配 / catalog 變體防線:AUS LIFE 檸檬草 vs 茶樹滾珠精油、NOW 椰子油膏 vs 乳木果油、港香蘭漢本 vs 艾魔菈爽身粉改為 hard veto;多色 / 多香 / 數字區間 catalog 對單一款式(KATE 粉餅盒、植村秀眉筆、PERIPERA 01~07 眼線膠筆、Jo Malone 車用擴香蕊芯等)只進 `variant_selection_review`。
|
||||
- 2026-05-25 20:35 CST 起,依 production audit 續補二階風險:同規格但一側為潔膚露、一側為修護乳/乳液直接 `cleanser_lotion_line_conflict` hard veto;私密防護慕絲多款可選 vs 單一香型、滋養霜單側清爽型只進 `variant_selection_review`。
|
||||
|
||||
## 3. 12 Agent 決策信封整合
|
||||
|
||||
|
||||
@@ -475,6 +475,7 @@ FOCUSED_IDENTITY_REVIEW_ONLY_REASONS = {
|
||||
"johnsons_baby_lotion_variant_catalog",
|
||||
"im_meme_fixx_cool_setting_spray",
|
||||
"so_natural_fixx_setting_spray_catalog",
|
||||
"kate_powder_case_catalog",
|
||||
"kate_monster_lipstick_catalog",
|
||||
"opi_gel_polish_series_catalog",
|
||||
"romand_juicy_lip_tint_2_catalog",
|
||||
@@ -527,6 +528,8 @@ VARIANT_SENSITIVE_KEYWORDS = {
|
||||
"私密清潔凝露",
|
||||
"私密潔淨凝露",
|
||||
"私密淨白清潔凝露",
|
||||
"私密防護慕絲",
|
||||
"慕絲",
|
||||
"定妝噴霧",
|
||||
"妝前防護乳",
|
||||
"妝前乳",
|
||||
@@ -543,8 +546,13 @@ VARIANT_SENSITIVE_KEYWORDS = {
|
||||
"腮紅液",
|
||||
"打亮液",
|
||||
"蜜粉餅",
|
||||
"粉餅盒",
|
||||
"粉底棒",
|
||||
"遮瑕棒",
|
||||
"遮瑕蜜",
|
||||
"護手霜",
|
||||
"滋養霜",
|
||||
"修護乳",
|
||||
"修容打亮棒",
|
||||
"防曬",
|
||||
"防曬乳",
|
||||
@@ -556,6 +564,8 @@ VARIANT_SENSITIVE_KEYWORDS = {
|
||||
VARIANT_OPTION_COLOR_WORDS = {
|
||||
"茉莉花",
|
||||
"梔子花",
|
||||
"白茶蘭花",
|
||||
"白茶",
|
||||
"白麝香",
|
||||
"黑麝香",
|
||||
"清新花園",
|
||||
@@ -563,8 +573,18 @@ VARIANT_OPTION_COLOR_WORDS = {
|
||||
"青檸羅勒",
|
||||
"炭木香",
|
||||
"無花果",
|
||||
"鼠尾草",
|
||||
"海鹽",
|
||||
"檸檬草",
|
||||
"茶樹",
|
||||
"櫻花",
|
||||
"繡球花",
|
||||
"魔髮奇緣",
|
||||
"清甜柚香",
|
||||
"杏仁牛奶",
|
||||
"杏仁",
|
||||
"薄荷",
|
||||
"橙花",
|
||||
"完熟白桃",
|
||||
"琥珀橙",
|
||||
"干邑棕",
|
||||
@@ -603,6 +623,12 @@ VARIANT_OPTION_COLOR_WORDS = {
|
||||
"自然色",
|
||||
"明亮色",
|
||||
"透明色",
|
||||
"清爽型",
|
||||
"滋潤型",
|
||||
"橡棕",
|
||||
"暗灰",
|
||||
"灰棕",
|
||||
"淺玫粉",
|
||||
"極光之藍",
|
||||
"月光銀影",
|
||||
}
|
||||
@@ -1976,6 +2002,15 @@ 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")
|
||||
ingredient_line_conflict = _has_core_ingredient_line_conflict(left, right)
|
||||
if ingredient_line_conflict:
|
||||
reasons.append("core_ingredient_line_conflict")
|
||||
branded_powder_line_conflict = _has_branded_powder_line_conflict(left, right)
|
||||
if branded_powder_line_conflict:
|
||||
reasons.append("branded_powder_line_conflict")
|
||||
cleanser_lotion_line_conflict = _has_cleanser_lotion_line_conflict(left, right)
|
||||
if cleanser_lotion_line_conflict:
|
||||
reasons.append("cleanser_lotion_line_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")
|
||||
@@ -2043,6 +2078,12 @@ def score_marketplace_match(
|
||||
hard_veto = True
|
||||
if aroma_scent_variant_conflict:
|
||||
hard_veto = True
|
||||
if ingredient_line_conflict:
|
||||
hard_veto = True
|
||||
if branded_powder_line_conflict:
|
||||
hard_veto = True
|
||||
if cleanser_lotion_line_conflict:
|
||||
hard_veto = True
|
||||
if wax_lamp_size_letter_conflict:
|
||||
hard_veto = True
|
||||
if nitori_diffuser_model_conflict:
|
||||
@@ -3051,7 +3092,26 @@ def _has_hoi_candle_line_conflict(left: ProductIdentity, right: ProductIdentity)
|
||||
|
||||
def _has_aroma_scent_variant_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 ("香氛固體凝膠", "香氛凝膠", "空氣芳香劑", "車用香氛")):
|
||||
if any(term in pair_text for term in ("護手霜", "融蠟燈", "蠟燭暖燈")):
|
||||
return False
|
||||
if not any(
|
||||
term in pair_text
|
||||
for term in (
|
||||
"香氛固體凝膠",
|
||||
"香氛凝膠",
|
||||
"空氣芳香劑",
|
||||
"車用香氛",
|
||||
"車用擴香",
|
||||
"擴香蕊",
|
||||
"擴香罐",
|
||||
"香薰蠟燭",
|
||||
"香氛蠟燭",
|
||||
"蠟燭",
|
||||
"滾珠精油",
|
||||
"香氛精油",
|
||||
"植物精油",
|
||||
)
|
||||
):
|
||||
return False
|
||||
if _is_multi_variant_catalog_listing(left) or _is_multi_variant_catalog_listing(right):
|
||||
return False
|
||||
@@ -3070,8 +3130,19 @@ def _has_aroma_scent_variant_conflict(left: ProductIdentity, right: ProductIdent
|
||||
"青檸羅勒",
|
||||
"炭木香",
|
||||
"無花果",
|
||||
"白茶蘭花",
|
||||
"白茶",
|
||||
"檸檬草",
|
||||
"茶樹",
|
||||
"鼠尾草",
|
||||
"海鹽",
|
||||
"橙花",
|
||||
"薄荷",
|
||||
"杏仁",
|
||||
"薰衣草",
|
||||
"茉莉",
|
||||
"櫻花",
|
||||
"繡球花",
|
||||
"玫瑰",
|
||||
"雪松",
|
||||
"檀香",
|
||||
@@ -3085,6 +3156,52 @@ def _has_aroma_scent_variant_conflict(left: ProductIdentity, right: ProductIdent
|
||||
return False
|
||||
|
||||
|
||||
def _has_core_ingredient_line_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
|
||||
ingredient_groups = {
|
||||
"coconut_oil": ("椰子油", "coconut"),
|
||||
"shea_butter": ("乳木果油", "shea"),
|
||||
}
|
||||
left_groups = {
|
||||
group
|
||||
for group, terms in ingredient_groups.items()
|
||||
if any(term in left.searchable_name for term in terms)
|
||||
}
|
||||
right_groups = {
|
||||
group
|
||||
for group, terms in ingredient_groups.items()
|
||||
if any(term in right.searchable_name for term in terms)
|
||||
}
|
||||
return bool(left_groups and right_groups and not (left_groups & right_groups))
|
||||
|
||||
|
||||
def _has_branded_powder_line_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
if not ({"港香蘭"} & (left.brand_tokens & right.brand_tokens)):
|
||||
return False
|
||||
if "爽身粉" not in left.searchable_name or "爽身粉" not in right.searchable_name:
|
||||
return False
|
||||
named_lines = ("漢本", "艾魔菈")
|
||||
left_lines = {line for line in named_lines if line in left.searchable_name}
|
||||
right_lines = {line for line in named_lines if line in right.searchable_name}
|
||||
return bool(left_lines and right_lines and not (left_lines & right_lines))
|
||||
|
||||
|
||||
def _has_cleanser_lotion_line_conflict(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
if not (left.brand_tokens & right.brand_tokens):
|
||||
return False
|
||||
if not _has_overlapping_base_spec(left, right):
|
||||
return False
|
||||
cleanser_terms = ("潔膚露", "潔膚", "潔淨露", "潔面", "洗面乳", "cleanser")
|
||||
lotion_terms = ("修護乳", "乳液", "身體乳", "潤膚乳", "lotion")
|
||||
left_cleanser = any(term in left.searchable_name for term in cleanser_terms)
|
||||
right_cleanser = any(term in right.searchable_name for term in cleanser_terms)
|
||||
left_lotion = any(term in left.searchable_name for term in lotion_terms)
|
||||
right_lotion = any(term in right.searchable_name for term in lotion_terms)
|
||||
return bool((left_cleanser and right_lotion) or (right_cleanser and left_lotion))
|
||||
|
||||
|
||||
def _standalone_size_letter_tokens(identity: ProductIdentity) -> set[str]:
|
||||
text = identity.searchable_name
|
||||
return {
|
||||
@@ -3174,17 +3291,38 @@ def _has_catalog_specific_variant_selection_gap(left: ProductIdentity, right: Pr
|
||||
"美體主張",
|
||||
"私密潔浴露",
|
||||
"私密潔浴",
|
||||
"私密防護慕絲",
|
||||
"私密慕絲",
|
||||
"慕絲",
|
||||
"嬰兒潤膚乳",
|
||||
"定妝噴霧",
|
||||
"染眉膏",
|
||||
"眼線膠筆",
|
||||
"粉餅盒",
|
||||
"遮瑕蜜",
|
||||
"護手霜",
|
||||
"護唇膏",
|
||||
"護唇棒",
|
||||
"唇釉",
|
||||
"唇膏",
|
||||
"蜜粉",
|
||||
"防曬素顏霜",
|
||||
"車用香氛",
|
||||
"車用擴香",
|
||||
"車用擴香蕊",
|
||||
"香氛擴香罐",
|
||||
"擴香罐",
|
||||
"擴香蕊",
|
||||
"水性指甲油",
|
||||
"指甲油",
|
||||
"融蠟小夜燈",
|
||||
"融蠟燈",
|
||||
"滋養霜",
|
||||
)
|
||||
):
|
||||
return False
|
||||
left_catalog = _is_multi_variant_catalog_listing(left)
|
||||
right_catalog = _is_multi_variant_catalog_listing(right)
|
||||
left_catalog = _is_catalog_or_delimited_variant_listing(left)
|
||||
right_catalog = _is_catalog_or_delimited_variant_listing(right)
|
||||
return left_catalog != right_catalog
|
||||
|
||||
|
||||
@@ -3560,6 +3698,12 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro
|
||||
and "全天候超完美定妝噴霧" in right_text
|
||||
):
|
||||
return "so_natural_fixx_setting_spray_catalog"
|
||||
if (
|
||||
{"kate", "凱婷"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "粉餅盒" in left_text
|
||||
and "粉餅盒" in right_text
|
||||
):
|
||||
return "kate_powder_case_catalog"
|
||||
if (
|
||||
{"kate", "凱婷"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "怪獸級持色唇膏" in left_text
|
||||
@@ -3749,6 +3893,42 @@ def _is_multi_variant_catalog_listing(identity: ProductIdentity) -> bool:
|
||||
return any(phrase in text for phrase in MULTI_VARIANT_LISTING_PHRASES)
|
||||
|
||||
|
||||
def _normalize_variant_option(value: str) -> set[str]:
|
||||
compact = re.sub(r"[^a-z0-9]", "", (value or "").lower())
|
||||
if not compact:
|
||||
return set()
|
||||
return {compact}
|
||||
|
||||
|
||||
def _variant_option_compare_key(option: str) -> str:
|
||||
if option.isdigit():
|
||||
return option.lstrip("0") or "0"
|
||||
return option
|
||||
|
||||
|
||||
def _variant_options_overlap(left_options: set[str], right_options: set[str]) -> bool:
|
||||
if left_options & right_options:
|
||||
return True
|
||||
left_keys = {_variant_option_compare_key(option) for option in left_options}
|
||||
right_keys = {_variant_option_compare_key(option) for option in right_options}
|
||||
return bool(left_keys & right_keys)
|
||||
|
||||
|
||||
def _is_catalog_or_delimited_variant_listing(identity: ProductIdentity) -> bool:
|
||||
if _is_multi_variant_catalog_listing(identity):
|
||||
return True
|
||||
text = identity.searchable_name
|
||||
if re.search(r"(?<![a-z0-9])([a-z]?\d{1,3}[a-z]?)\s*(?:~|~|至|-)\s*([a-z]?\d{1,3}[a-z]?)(?![a-z0-9])", text, re.I):
|
||||
return True
|
||||
options = _explicit_variant_option_tokens(identity)
|
||||
if len(options) < 2:
|
||||
return bool(
|
||||
re.search(r"[//、,,..&&]", text)
|
||||
and any(term in text for term in ("粉餅盒", "眼線膠筆", "眉筆", "唇膏", "唇釉", "遮瑕蜜", "車用擴香", "車用香氛"))
|
||||
)
|
||||
return bool(re.search(r"[//、,,..&&]", text))
|
||||
|
||||
|
||||
def _has_catalog_variant_listing_alignment(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
if not (_is_multi_variant_catalog_listing(left) and _is_multi_variant_catalog_listing(right)):
|
||||
return False
|
||||
@@ -3823,16 +4003,15 @@ def _has_variant_descriptor_conflict(left: ProductIdentity, right: ProductIdenti
|
||||
def _explicit_variant_option_tokens(identity: ProductIdentity) -> set[str]:
|
||||
text = identity.searchable_name
|
||||
options: set[str] = set()
|
||||
for match in re.finditer(r"(?<![a-z0-9])([a-z]?\d{1,3}[a-z]?)\s*(?:~|~|至|-)\s*([a-z]?\d{1,3}[a-z]?)(?![a-z0-9])", text, re.I):
|
||||
for group in (match.group(1), match.group(2)):
|
||||
options.update(_normalize_variant_option(group))
|
||||
for match in re.finditer(r"(?:#|no\.?|色號|號色)\s*([a-z]?\d{1,3}[a-z]?)(?![a-z0-9])", text, re.I):
|
||||
value = re.sub(r"[^a-z0-9]", "", match.group(1).lower())
|
||||
if value:
|
||||
options.add(value)
|
||||
options.update(_normalize_variant_option(match.group(1)))
|
||||
for match in re.finditer(r"(?<![a-z0-9])((?:0?\d){1,2})(?=[\u4e00-\u9fff])", text, re.I):
|
||||
if text[match.end(1):match.end(1) + 4] in {"號護唇膏", "號護脣膏"}:
|
||||
continue
|
||||
value = re.sub(r"[^a-z0-9]", "", match.group(1).lower())
|
||||
if value:
|
||||
options.add(value)
|
||||
options.update(_normalize_variant_option(match.group(1)))
|
||||
for color_word in VARIANT_OPTION_COLOR_WORDS:
|
||||
if color_word in text:
|
||||
options.add(color_word)
|
||||
@@ -3891,9 +4070,21 @@ def _has_explicit_variant_option_conflict(
|
||||
return False
|
||||
if left_options == right_options:
|
||||
return False
|
||||
if left_options & right_options:
|
||||
if _variant_options_overlap(left_options, right_options):
|
||||
if _has_catalog_options_against_generic_count_alignment(left, right, left_options, right_options):
|
||||
return False
|
||||
pair_text = f"{left.searchable_name} {right.searchable_name}"
|
||||
if any(term in pair_text for term in ("眉筆", "眼線膠筆", "唇膏", "唇釉", "粉餅盒", "遮瑕蜜")) and (
|
||||
(
|
||||
len(left_options) > len(right_options)
|
||||
and _is_catalog_or_delimited_variant_listing(left)
|
||||
)
|
||||
or (
|
||||
len(right_options) > len(left_options)
|
||||
and _is_catalog_or_delimited_variant_listing(right)
|
||||
)
|
||||
):
|
||||
return False
|
||||
if (
|
||||
len(left_options) > len(right_options)
|
||||
and _has_variant_option_selection_gap(left, left_options)
|
||||
@@ -3923,15 +4114,37 @@ def _has_named_variant_selection_review(
|
||||
return True
|
||||
left_options = _explicit_variant_option_tokens(left)
|
||||
right_options = _explicit_variant_option_tokens(right)
|
||||
if left_options and right_options:
|
||||
for catalog_identity, catalog_options, specific_options in (
|
||||
(left, left_options, right_options),
|
||||
(right, right_options, left_options),
|
||||
):
|
||||
if (
|
||||
_is_catalog_or_delimited_variant_listing(catalog_identity)
|
||||
and len(catalog_options) > len(specific_options)
|
||||
and _variant_options_overlap(catalog_options, specific_options)
|
||||
and _is_variant_sensitive_identity(left, right, shared_anchor)
|
||||
):
|
||||
return True
|
||||
if bool(left_options) != bool(right_options):
|
||||
option_identity = left if left_options else right
|
||||
catalog_identity = right if left_options else left
|
||||
if (
|
||||
_is_variant_sensitive_identity(left, right, shared_anchor)
|
||||
and _is_multi_variant_catalog_listing(catalog_identity)
|
||||
and _is_catalog_or_delimited_variant_listing(catalog_identity)
|
||||
and _explicit_variant_option_tokens(option_identity)
|
||||
):
|
||||
return True
|
||||
if (
|
||||
_is_variant_sensitive_identity(left, right, shared_anchor)
|
||||
and _has_overlapping_base_spec(left, right)
|
||||
and _explicit_variant_option_tokens(option_identity)
|
||||
and any(
|
||||
term in f"{left.searchable_name} {right.searchable_name}"
|
||||
for term in ("粉餅盒", "護手霜", "護唇膏", "護唇棒", "滋養霜", "眼線膠筆", "遮瑕蜜")
|
||||
)
|
||||
):
|
||||
return True
|
||||
if bool(left_options) == bool(right_options):
|
||||
return False
|
||||
|
||||
|
||||
@@ -2286,6 +2286,91 @@ def test_marketplace_matcher_sends_catalog_specific_variant_gaps_to_review():
|
||||
assert "variant_selection_review" in diagnostics.reasons
|
||||
|
||||
|
||||
def test_marketplace_matcher_blocks_scent_and_core_line_conflicts():
|
||||
from services.marketplace_product_matcher import score_marketplace_match
|
||||
|
||||
rolling_oil_scent_gap = score_marketplace_match(
|
||||
"【AUS LIFE 澳思萊】檸檬草滾珠精油 5.3ml",
|
||||
"【AUS LIFE澳思萊】茶樹滾珠精油5.3ml",
|
||||
)
|
||||
ingredient_gap = score_marketplace_match(
|
||||
"【NOW娜奧】純椰子油膏207ml-Now Foods",
|
||||
"【NOW 娜奧】Now Foods 純乳木果油油膏 207ml",
|
||||
)
|
||||
powder_line_gap = score_marketplace_match(
|
||||
"【港香蘭】漢本爽身粉100g/盒|誠意中西藥局",
|
||||
"【港香蘭】艾魔菈 草本爽身粉(100g/罐)",
|
||||
)
|
||||
|
||||
assert rolling_oil_scent_gap.hard_veto is True
|
||||
assert "aroma_scent_variant_conflict" in rolling_oil_scent_gap.reasons
|
||||
assert ingredient_gap.hard_veto is True
|
||||
assert "core_ingredient_line_conflict" in ingredient_gap.reasons
|
||||
assert powder_line_gap.hard_veto is True
|
||||
assert "branded_powder_line_conflict" in powder_line_gap.reasons
|
||||
|
||||
|
||||
def test_marketplace_matcher_sends_delimited_or_range_catalog_variants_to_review():
|
||||
from services.marketplace_product_matcher import score_marketplace_match
|
||||
|
||||
powder_case = score_marketplace_match(
|
||||
"【KATE 凱婷】零瑕肌密柔焦粉餅盒/極致零瑕光粉餅盒(單售粉盒)",
|
||||
"凱婷 極致零瑕光粉餅盒",
|
||||
)
|
||||
shu_brow = score_marketplace_match(
|
||||
"【Shu uemura 植村秀】武士刀眉筆(平輸航空版/多色任選/橡棕.暗灰. 灰棕)",
|
||||
"《Shu Uemura 植村秀》武士刀眉筆(H9) 4g -#橡棕06",
|
||||
)
|
||||
peripera_range = score_marketplace_match(
|
||||
"【peripera】韓國【PERIPERA 速描眼線膠筆 01~07】現貨 韓國境內版",
|
||||
"PERIPERA 速描眼線膠筆07淺玫粉",
|
||||
)
|
||||
jo_malone = score_marketplace_match(
|
||||
"【Jo Malone】車用擴香蕊芯1入 多款可選(國際航空版)",
|
||||
"JO MALONE 車用擴香蕊心-鼠尾草與海鹽 1入",
|
||||
)
|
||||
|
||||
for diagnostics in (powder_case, shu_brow, peripera_range, jo_malone):
|
||||
assert diagnostics.hard_veto is False
|
||||
assert diagnostics.score >= 0.76
|
||||
assert diagnostics.price_basis == "manual_review"
|
||||
assert diagnostics.alert_tier == "identity_review"
|
||||
assert "variant_selection_review" in diagnostics.reasons
|
||||
|
||||
|
||||
def test_marketplace_matcher_blocks_cleanser_lotion_line_conflict():
|
||||
from services.marketplace_product_matcher import score_marketplace_match
|
||||
|
||||
diagnostics = score_marketplace_match(
|
||||
"【Cetaphil 舒特膚】官方直營 三酸煥膚嫩亮修護乳473ml",
|
||||
"Cetaphil 舒特膚 三酸煥膚嫩亮潔膚露 473ml",
|
||||
)
|
||||
|
||||
assert diagnostics.hard_veto is True
|
||||
assert diagnostics.comparison_mode == "not_comparable"
|
||||
assert "cleanser_lotion_line_conflict" in diagnostics.reasons
|
||||
|
||||
|
||||
def test_marketplace_matcher_sends_single_sided_private_mousse_and_cream_variants_to_review():
|
||||
from services.marketplace_product_matcher import score_marketplace_match
|
||||
|
||||
private_mousse = score_marketplace_match(
|
||||
"【isLeaf】韓國女性私密防護慕絲250ml二款可選",
|
||||
"韓國isLeaf 女性私密防護慕絲250ml 花妍巧語",
|
||||
)
|
||||
cream_variant = score_marketplace_match(
|
||||
"【雪芙蘭】滋養霜60g(撫平粗糙 重現光滑)",
|
||||
"【雪芙蘭】滋養霜《清爽型》60g",
|
||||
)
|
||||
|
||||
for diagnostics in (private_mousse, cream_variant):
|
||||
assert diagnostics.hard_veto is False
|
||||
assert diagnostics.score >= 0.76
|
||||
assert diagnostics.price_basis == "manual_review"
|
||||
assert diagnostics.alert_tier == "identity_review"
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user