1010 lines
38 KiB
Python
1010 lines
38 KiB
Python
from sqlalchemy import create_engine, text
|
||
|
||
|
||
def test_marketplace_matcher_accepts_same_product_identity():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"理膚寶水 B5 修復霜 40ml",
|
||
"理膚寶水 全面修復霜 B5 40ml",
|
||
momo_price=699,
|
||
competitor_price=679,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.9
|
||
assert diagnostics.hard_veto is False
|
||
assert "brand_match" in diagnostics.tags
|
||
assert "spec_match" in diagnostics.tags
|
||
|
||
|
||
def test_marketplace_matcher_rejects_brand_conflict_even_when_volume_matches():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【蘭蔻】官方直營 玫瑰霜60ml+玫瑰精露150ml",
|
||
"LOREAL Paris 巴黎萊雅 金致臻顏花蜜奢養膠原輕盈乳霜_60ml",
|
||
momo_price=18765,
|
||
competitor_price=1249,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "brand_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_rejects_volume_conflict_for_same_brand():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【CLARINS 克蘭詩】黃金亮眼萃20mlX2囤貨組",
|
||
"【CLARINS克蘭詩】黃金雙萃精華 75ml",
|
||
momo_price=4500,
|
||
competitor_price=3549,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "volume_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_handles_bundle_piece_count():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Relove】馬甲纖纖飲-莓果風味X3盒 共72包",
|
||
"【3入超值組】Relove 馬甲纖纖飲 24包/7克",
|
||
momo_price=4590,
|
||
competitor_price=4590,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
|
||
|
||
def test_marketplace_matcher_accepts_strong_model_line_without_specs():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Stadler Form】Sophie 無線香氛水氧機 水氧機 香氛機",
|
||
"【瑞士Stadler Form】無線香氛水氧機 露營燈造型 Sophie",
|
||
momo_price=3780,
|
||
competitor_price=3980,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "strong_product_line_match" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_rejects_bundle_to_single_even_when_brand_matches():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【NARS】小白餅閨蜜分享組(裸光蜜粉餅/定妝蜜粉)",
|
||
"【NARS】裸光蜜粉餅(小白餅) 10g",
|
||
momo_price=3300,
|
||
competitor_price=1099,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "bundle_offer_conflict" in diagnostics.reasons
|
||
assert diagnostics.comparison_mode == "not_comparable"
|
||
|
||
|
||
def test_marketplace_matcher_marks_bundle_single_as_unit_comparable_not_exact():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"理膚寶水 B5 全面修復霜 40ml x2 超值組",
|
||
"理膚寶水 全面修復霜 B5 40ml",
|
||
momo_price=1199,
|
||
competitor_price=679,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert diagnostics.comparison_mode == "unit_comparable"
|
||
assert "unit_comparable" in diagnostics.reasons
|
||
assert "comparison_unit_comparable" in diagnostics.tags
|
||
|
||
|
||
def test_unit_price_comparison_builds_normalized_evidence():
|
||
from services.marketplace_product_matcher import build_unit_price_comparison
|
||
|
||
comparison = build_unit_price_comparison(
|
||
"理膚寶水 B5 全面修復霜 40ml x2 超值組",
|
||
"理膚寶水 全面修復霜 B5 40ml",
|
||
momo_price=1199,
|
||
competitor_price=679,
|
||
)
|
||
|
||
assert comparison["comparable"] is True
|
||
assert comparison["unit_label"] == "ml"
|
||
assert comparison["momo_total_quantity"] == 80
|
||
assert comparison["competitor_total_quantity"] == 40
|
||
assert comparison["momo_unit_price"] == 14.9875
|
||
assert comparison["competitor_unit_price"] == 16.975
|
||
assert comparison["unit_gap_pct"] < 0
|
||
|
||
|
||
def test_marketplace_matcher_does_not_unit_compare_multi_component_set():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【蘭蔻】官方直營 玫瑰霜60ml+玫瑰精露150ml",
|
||
"【蘭蔻】絕對完美玫瑰霜 60ml",
|
||
momo_price=18765,
|
||
competitor_price=5349,
|
||
)
|
||
|
||
assert diagnostics.hard_veto is True
|
||
assert diagnostics.comparison_mode == "not_comparable"
|
||
assert "unit_comparable" not in diagnostics.reasons
|
||
|
||
comparison = score_marketplace_match(
|
||
"【蘭蔻】官方直營 玫瑰霜60ml+玫瑰精露150ml",
|
||
"【蘭蔻】絕對完美玫瑰霜 60ml",
|
||
momo_price=18765,
|
||
competitor_price=5349,
|
||
)
|
||
assert comparison.comparison_mode == "not_comparable"
|
||
|
||
|
||
def test_marketplace_matcher_does_not_promote_wide_price_refill_candidate():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【蘭蔻】官方直營 絕對完美永生玫瑰逆齡乳霜60ml補充瓶",
|
||
"LANCOME蘭蔻 絕對完美永生玫瑰逆齡乳霜 60ml",
|
||
momo_price=11205,
|
||
competitor_price=5349,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "refill_pack_conflict" in diagnostics.reasons
|
||
assert "strong_product_line_match" not in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_rejects_partial_overlap_in_multi_spec_set():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【蘭蔻】玫瑰霜60ml+玫瑰精露150ml",
|
||
"【蘭蔻】玫瑰霜60ml+玫瑰精露20ml",
|
||
momo_price=18765,
|
||
competitor_price=7999,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "volume_conflict" in diagnostics.reasons
|
||
assert diagnostics.comparison_mode == "not_comparable"
|
||
|
||
|
||
def test_marketplace_matcher_rejects_dosage_conflict():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"品牌 葉黃素 10mg 60錠",
|
||
"品牌 葉黃素 20mg 60錠",
|
||
momo_price=990,
|
||
competitor_price=890,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "dosage_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_rejects_product_type_conflict_even_when_line_matches():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"理膚寶水 MelaB3 淡斑精華 30ml",
|
||
"理膚寶水 MelaB3 淡斑化妝水 30ml",
|
||
momo_price=1280,
|
||
competitor_price=1180,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "type_conflict" in diagnostics.reasons
|
||
assert diagnostics.comparison_mode == "not_comparable"
|
||
|
||
|
||
def test_marketplace_matcher_rejects_foundation_stick_vs_foundation_liquid():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【蘭蔻】零粉感超持久粉底棒9.5g",
|
||
"【LANCOME 蘭蔻】零粉感超持久粉底 30ml",
|
||
momo_price=1620,
|
||
competitor_price=1580,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "type_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_nivea_deodorant_spray_identity():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【NIVEA 妮維雅】男士 止汗爽身噴霧 無印乾爽-清新海洋",
|
||
"NIVEA 妮維雅 男士止汗爽身噴霧 無印乾爽-清新海洋150ml",
|
||
momo_price=159,
|
||
competitor_price=169,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
|
||
|
||
def test_marketplace_matcher_promotes_packaging_variant_for_same_nars_powder():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【NARS】官方直營 裸光蜜粉餅(璀璨奢金限定版/星沙金小白餅)",
|
||
"【NARS】裸光蜜粉餅(小白餅) 10g",
|
||
momo_price=1050,
|
||
competitor_price=1050,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_identity_anchor_packaging_variant" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_private_wash_same_identity():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
summer = score_marketplace_match(
|
||
"【Summer’s Eve 舒摩兒】浴潔露237ml 單入任選(私密清潔 經典防護王)",
|
||
"eve舒摩兒 賦活美學浴潔露-全肌防護 237ml",
|
||
momo_price=441,
|
||
competitor_price=441,
|
||
)
|
||
femfresh = score_marketplace_match(
|
||
"【femfresh 芳芯】弱酸性植萃複方溫和潤澤護理私密肌潔膚露250ml/瓶(pH值平衡護潔露淨味沐浴乳香氛凝膠)",
|
||
"【femfresh芳芯 官方直營】私密潔膚露250ml (任選)",
|
||
momo_price=399,
|
||
competitor_price=399,
|
||
)
|
||
|
||
for diagnostics in (summer, femfresh):
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
|
||
|
||
def test_marketplace_matcher_rejects_private_spray_vs_private_gel():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【isLeaf】韓國isLeaf男性私密醒肌抑菌噴霧60ml-夏夜微醺(SGS 24小時抑菌)",
|
||
"韓國 isLeaf 男性私密激淨凝露 湛藍海洋 60ml",
|
||
momo_price=299,
|
||
competitor_price=299,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "type_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_marketing_variant_same_maybelline_lip_tint():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【MAYBELLINE 媚比琳】超持久水光鎖吻唇釉(絲絨甜點新色/鎖吻棒/水光持色)",
|
||
"MAYBELLINE 媚比琳 超持久水光鎖吻唇釉 4.2ml",
|
||
momo_price=399,
|
||
competitor_price=399,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_identity_anchor_marketing_variant" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_besthot_aroma_machine_marketing_variant():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【BESTHOT】天然陶瓷精油香薰機-贈精油一瓶(水氧機 加濕器 精油機 香薰機 擴香機 小夜燈)",
|
||
"Besthot 天然陶瓷超聲波大噴霧精油香薰機-贈送薰衣草精油 水氧機 加濕器 精油機",
|
||
momo_price=899,
|
||
competitor_price=899,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
|
||
|
||
def test_marketplace_matcher_promotes_precise_cosmetics_and_skincare_lines():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
samples = [
|
||
(
|
||
"【NARS】官方直營 裸光幻閃亮采餅",
|
||
"NARS 裸光幻閃亮采餅 2g #Heavenly",
|
||
),
|
||
(
|
||
"【PONY EFFECT】絕對持久定妝噴霧 100ml(持久定妝/控油特霧)",
|
||
"【PONY EFFECT】絕對持久定妝噴霧(控油特霧版)",
|
||
),
|
||
(
|
||
"【植村秀】官方直營 自動武士刀眉筆(Shu uemura/眉筆)",
|
||
"《Shu Uemura 植村秀》自動武士刀眉筆 0.3g",
|
||
),
|
||
(
|
||
"【SHISEIDO 資生堂國際櫃】超進化光感輕潤遮瑕棒(遮瑕/修容/打亮/新品)",
|
||
"【資生堂國際櫃】超進化光感輕潤遮瑕棒2.7g",
|
||
),
|
||
(
|
||
"【LANCOME 蘭蔻】唯我玫瑰裸光潤唇膏(多款任選 國際航空版 送禮)",
|
||
"LANCOME 唯我玫瑰裸光潤唇膏 3g",
|
||
),
|
||
(
|
||
"【Les nez 香鼻子】晨曦冷香儀贈複方擴香精油 30ml(無水擴香 智能霧化)",
|
||
"les nez 晨曦冷香儀(無水擴香、智能霧化)",
|
||
),
|
||
(
|
||
"【Mustela 慕之恬廊】舒恬良 修護霜 40ml(Vit.B5 嬰兒界萬用霜 醫師好辣推薦)",
|
||
"Mustela 慕之恬廊舒恬良修護霜40ml",
|
||
),
|
||
]
|
||
|
||
for left, right in samples:
|
||
diagnostics = score_marketplace_match(left, right, momo_price=399, competitor_price=399)
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
|
||
|
||
def test_marketplace_matcher_rejects_same_count_different_unit_family():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"品牌 保濕面膜10片",
|
||
"品牌 保濕面膜10盒",
|
||
momo_price=399,
|
||
competitor_price=1990,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "count_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_marks_generic_bundle_words_as_unit_comparable():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【NARS】裸光蜜粉餅 雙件組 10g",
|
||
"【NARS】裸光蜜粉餅 10g",
|
||
momo_price=1999,
|
||
competitor_price=1099,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert "bundle_offer_conflict" in diagnostics.reasons
|
||
assert diagnostics.comparison_mode == "unit_comparable"
|
||
|
||
|
||
def test_marketplace_matcher_ignores_non_brand_bracket_copy():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【保濕組】理膚寶水 B5 修復霜 40ml",
|
||
"理膚寶水 B5 修復霜 40ml",
|
||
momo_price=699,
|
||
competitor_price=679,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "brand_conflict" not in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_uses_brand_alias_and_chinese_line_signal():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【CLARINS 克蘭詩】黃金雙激萃50ml",
|
||
"克蘭詩 黃金雙萃精華 50ml",
|
||
momo_price=4500,
|
||
competitor_price=3549,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "brand_match" in diagnostics.tags
|
||
assert "spec_match" in diagnostics.tags
|
||
|
||
|
||
def test_marketplace_matcher_matches_cetaphil_alias():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"舒特膚 AD 乳液 200ml",
|
||
"Cetaphil AD 乳液 200ml",
|
||
momo_price=980,
|
||
competitor_price=899,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.9
|
||
assert diagnostics.hard_veto is False
|
||
assert "brand_match" in diagnostics.tags
|
||
|
||
|
||
def test_marketplace_matcher_treats_piece_unit_synonyms_as_same_pack():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"品牌 葉黃素 10mg 60粒",
|
||
"品牌 葉黃素 10毫克 60錠",
|
||
momo_price=990,
|
||
competitor_price=890,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "count_conflict" not in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_normalizes_buy_get_pack_evidence():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"NARS 裸光蜜粉餅 買1送1 10g",
|
||
"NARS 裸光蜜粉餅 10g x2",
|
||
momo_price=1999,
|
||
competitor_price=1099,
|
||
)
|
||
|
||
assert diagnostics.comparison_mode == "unit_comparable"
|
||
assert "unit_comparable" in diagnostics.reasons
|
||
assert "count_conflict" not in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_accepts_strong_multi_component_line_without_full_specs():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Gennies 奇妮】COSVITAL乳頭霜50ml+滋潤彈力霜100ml+修復彈力霜100ml(金燦極緻法國頂級孕期產後保養組)",
|
||
"Gennies奇妮 COSVITAL 金燦極緻法國頂級保養(乳頭霜+滋潤彈力霜+修復彈力霜)(801062+801081+801082)",
|
||
momo_price=6980,
|
||
competitor_price=6980,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert diagnostics.comparison_mode == "exact_identity"
|
||
assert "strong_component_line_match" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_accepts_known_brand_alias_and_option_copy():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【OBgE】韓國 OBgE 男士自然遮瑕粉底棒13g",
|
||
"OBgE/自然遮瑕粉底棒13g - 多款可選",
|
||
momo_price=765,
|
||
competitor_price=1099,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert diagnostics.comparison_mode == "exact_identity"
|
||
|
||
|
||
def test_marketplace_matcher_accepts_same_pack_with_chinese_count_wording():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【SEBAMED】潔膚露1000ml共2入(大容量 平行輸入)",
|
||
"Sebamed施巴 潔膚露1000ml 兩入組",
|
||
momo_price=799,
|
||
competitor_price=899,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "brand_match" in diagnostics.tags
|
||
|
||
|
||
def test_marketplace_matcher_rejects_razor_series_and_blade_count_conflict():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Gillette 吉列】SkinGuard 紳適系列刮鬍刀頭(4刀頭)",
|
||
"【Gillette 吉列 】Fusion鋒隱系列刮鬍刀頭(8刀頭)",
|
||
momo_price=499,
|
||
competitor_price=906,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is True
|
||
assert diagnostics.comparison_mode == "not_comparable"
|
||
assert "count_conflict" in diagnostics.reasons
|
||
assert "model_line_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_safe_exact_spec_near_threshold():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
opi = score_marketplace_match(
|
||
"【O.P.I】Top Coat 持久閃耀保色護甲油15mL-IST31(類光繚指甲油專用亮油/小銀蓋/如膠似漆/美甲/官方直營)",
|
||
"OPI 官方直營.持久閃耀保色護甲油15mL-IST31.如膠似漆閃耀系列",
|
||
momo_price=578,
|
||
competitor_price=680,
|
||
)
|
||
mustela = score_marketplace_match(
|
||
"【Mustela 慕之恬廊】慕之幼 免用水潔淨液 300ml(外出清潔 卸除髒汙 卸除防曬 卸防曬)",
|
||
"Mustela慕之恬廊 慕之幼免用水潔淨液300ml",
|
||
momo_price=275,
|
||
competitor_price=398,
|
||
)
|
||
romand = score_marketplace_match(
|
||
"【rom&nd】韓國 Rom&nd 果汁唇釉 5.5g(韓國彩妝 唇釉 唇彩 果汁 水光感)",
|
||
"rom&nd 果汁唇釉5.5g_多款可選",
|
||
momo_price=211,
|
||
competitor_price=319,
|
||
)
|
||
|
||
for diagnostics in (opi, mustela, romand):
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "strong_exact_spec_match" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_does_not_promote_different_option_without_spec():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【DASHING DIVA】MAGICPRESS時尚潮流美甲片_極光之藍",
|
||
"Dashing Diva/F 時尚潮流美甲片-月光銀影 MDF5F010AG",
|
||
momo_price=331,
|
||
competitor_price=420,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert "strong_exact_spec_match" not in diagnostics.reasons
|
||
assert "variant_descriptor_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_variant_safe_exact_option():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【DASHING DIVA】MAGICPRESS時尚潮流美甲片_極光之藍",
|
||
"Dashing Diva/F 時尚潮流美甲片-極光之藍 MDF5F001AG",
|
||
momo_price=331,
|
||
competitor_price=420,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_identity_anchor_variant_safe" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_allows_catalog_piece_count_for_exact_dashing_diva_variant():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【DASHING DIVA】MAGICPRESS 時尚潮流美甲片-銀河綠波(貓眼 漸層)",
|
||
"Dashing Diva/F 時尚潮流美甲片-銀河綠波 30片/盒 MDF5F012AG",
|
||
momo_price=399,
|
||
competitor_price=420,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "count_conflict" not in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_rejects_explicit_shade_option_mismatch():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
lipstick = score_marketplace_match(
|
||
"【Maybelline 媚比琳】超持久水光鎖吻唇釉#62 4.2ml",
|
||
"MAYBELLINE 媚比琳 超持久水光鎖吻唇釉 #120 4.2ml",
|
||
momo_price=399,
|
||
competitor_price=399,
|
||
)
|
||
primer = score_marketplace_match(
|
||
"【植村秀】水凝光透妝前防護乳 紫色 30ml",
|
||
"植村秀 水凝光透妝前防護乳 粉色 30ml",
|
||
momo_price=1200,
|
||
competitor_price=1100,
|
||
)
|
||
|
||
for diagnostics in (lipstick, primer):
|
||
assert diagnostics.hard_veto is True
|
||
assert diagnostics.comparison_mode == "not_comparable"
|
||
assert diagnostics.score < 0.76
|
||
assert "variant_option_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_accepts_same_explicit_shade_option():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Maybelline 媚比琳】超持久水光鎖吻唇釉#62 4.2ml",
|
||
"MAYBELLINE 媚比琳 超持久水光鎖吻唇釉 #62 4.2ml",
|
||
momo_price=399,
|
||
competitor_price=399,
|
||
)
|
||
|
||
assert diagnostics.hard_veto is False
|
||
assert diagnostics.comparison_mode == "exact_identity"
|
||
assert diagnostics.score >= 0.76
|
||
assert "variant_option_conflict" not in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_shared_identity_anchor_near_threshold():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
obge = score_marketplace_match(
|
||
"【OBgE】自然遮瑕素顏霜 50g",
|
||
"OBgE/自然遮瑕素顏霜50g",
|
||
momo_price=699,
|
||
competitor_price=699,
|
||
)
|
||
unicat = score_marketplace_match(
|
||
"【UNICAT 變臉貓】超持久細滑眼線筆1.5ml",
|
||
"【UNICAT】超持久細滑眼線筆 1.5ml 新品搶先優惠",
|
||
momo_price=399,
|
||
competitor_price=399,
|
||
)
|
||
|
||
for diagnostics in (obge, unicat):
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_identity_anchor" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_contained_identity_anchor_with_same_spec():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
sebamed = score_marketplace_match(
|
||
"【Sebamed】女性私密處護潔露200ml",
|
||
"Sebamed私密護潔露(pH6.8)黃金女郎型200ml",
|
||
momo_price=399,
|
||
competitor_price=399,
|
||
)
|
||
lab52 = score_marketplace_match(
|
||
"【Lab52 齒妍堂】兒童口腔清潔棒30入/盒",
|
||
"Lab52齒妍堂 兒童口腔清潔棒30入",
|
||
momo_price=299,
|
||
competitor_price=299,
|
||
)
|
||
|
||
for diagnostics in (sebamed, lab52):
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_identity_anchor" in lab52.reasons or "shared_identity_anchor" in sebamed.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_same_spec_name_alignment_near_threshold():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Sebamed】女性私密處護潔露200ml(國際航空版)",
|
||
"Sebamed私密護潔露(pH6.8)黃金女郎型200ml",
|
||
momo_price=399,
|
||
competitor_price=399,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "spec_name_alignment" in diagnostics.reasons or "shared_identity_anchor" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_shared_anchor_without_spec_conflict():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Les nez 香鼻子】水晶香氛能量寶盒禮盒組(運財、智慧、愛情、守護、和諧、療癒、悟性 聖誕禮物)",
|
||
"【Les nez 香鼻子】水晶香氛能量寶盒禮盒組",
|
||
momo_price=2480,
|
||
competitor_price=2480,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_identity_anchor_no_spec" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_recipe_box_near_threshold_with_variant_safe_anchor():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Recipebox】Recipe Box兒童防曬氣墊粉餅(兒童化妝品/無毒防曬粉餅/天然彩妝)",
|
||
"Recipe Box 韓兔 兒童防曬氣墊粉餅",
|
||
momo_price=699,
|
||
competitor_price=699,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_identity_anchor_variant_safe" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_romand_palette_exact_line():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【rom&nd】勝過眼皮十色眼影盤",
|
||
"rom&nd X ZO&FRIENDS 勝過眼皮十色眼影盤 8g/7g",
|
||
momo_price=499,
|
||
competitor_price=499,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_identity_anchor_variant_safe" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_lactacyd_private_wash_exact_line():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Lactacyd 立朵舒】私密潔浴露250ml - 滋潤緊緻/加倍修護(私密清潔 產後私密保養 更年期乾癢修護)",
|
||
"Lactacyd 立朵舒私密潔浴露-滋潤緊緻250ml",
|
||
momo_price=399,
|
||
competitor_price=399,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_identity_anchor" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_eaoron_classic_tone_up_cream_exact_line():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Eaoron】澳洲 經典素顏霜 50ml#0000199#(面霜 素顏霜 懶人霜 打造素顏女神)",
|
||
"【澳洲 EAORON】第三代經典版白素顏霜 50ml",
|
||
momo_price=399,
|
||
competitor_price=399,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "strong_exact_spec_match" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_promotes_shared_model_token_for_exact_model():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Panasonic 國際牌】男士身體除毛器 2025新款 ER-GK83 日版 日本直送",
|
||
"Panasonic 國際牌 男仕防水美體除毛器 國際版 (ER-GK83)",
|
||
momo_price=2490,
|
||
competitor_price=2290,
|
||
)
|
||
|
||
assert diagnostics.score >= 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "shared_model_token" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_matcher_keeps_variant_warmer_low_score():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Les nez 香鼻子】香氛融蠟燈 閃耀琥珀(附燈泡 定時 調節亮度 聖誕節禮物)",
|
||
"【Les nez 香鼻子】香氛融蠟燈 雲石玫瑰琥珀 贈燈泡(定時 可調光 香氛蠟燭 香氛)",
|
||
momo_price=1980,
|
||
competitor_price=1980,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is False
|
||
|
||
|
||
def test_marketplace_matcher_keeps_generic_vs_specific_warmer_low_score():
|
||
from services.marketplace_product_matcher import score_marketplace_match
|
||
|
||
diagnostics = score_marketplace_match(
|
||
"【Les nez 香鼻子】北歐香氛融蠟燈(附燈泡 定時 調節亮度)",
|
||
"【Les nez 香鼻子】香氛融蠟燈 星夜款",
|
||
momo_price=1980,
|
||
competitor_price=1980,
|
||
)
|
||
|
||
assert diagnostics.score < 0.76
|
||
assert diagnostics.hard_veto is False
|
||
assert "variant_descriptor_conflict" in diagnostics.reasons
|
||
|
||
|
||
def test_marketplace_search_terms_prefer_readable_brand_core_spec():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
terms = build_search_terms("【LA ROCHE-POSAY 理膚寶水】B5全面修復霜 40ml")
|
||
|
||
assert terms[0] == "理膚寶水 全面修復霜 b5 40ml"
|
||
assert not any(term.endswith(" l") for term in terms)
|
||
|
||
|
||
def test_marketplace_search_terms_prioritize_identity_phrase_over_ambiguous_copy():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
terms = build_search_terms("【TAICEND 泰陞】寶貝液體保護膜 屁屁噴 100ml", max_terms=5)
|
||
|
||
assert terms[0] == "泰陞 屁屁噴 100ml"
|
||
assert "保護膜" not in terms[0]
|
||
assert "屁屁噴" in " ".join(terms[:3])
|
||
|
||
|
||
def test_marketplace_search_terms_drop_option_and_marketing_noise():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
terms = build_search_terms("【YSL】情挑誘光嫩唇凍6ml(任選一款/新品上市)", max_terms=5)
|
||
|
||
assert terms[0] == "ysl 情挑誘光嫩唇凍 6ml"
|
||
assert not any("一款" in term or "上市" in term for term in terms)
|
||
|
||
|
||
def test_marketplace_search_terms_keep_professional_product_phrase():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
abysse_terms = build_search_terms("【Abysse】天然植萃身體按摩精油550ml", max_terms=5)
|
||
mustela_terms = build_search_terms(
|
||
"【Mustela 慕之恬廊】慕之幼 免用水潔淨液 300ml(外出清潔 卸除髒汙 卸除防曬 卸防曬)",
|
||
max_terms=5,
|
||
)
|
||
|
||
assert abysse_terms[0] == "abysse 身體按摩精油 550ml"
|
||
assert mustela_terms[0] == "慕之恬廊 免用水潔淨液 300ml"
|
||
assert not any("卸除防曬" in term or "外出清潔" in term for term in mustela_terms)
|
||
|
||
|
||
def test_marketplace_search_terms_keep_variant_descriptor_for_sensitive_lines():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
dashing_terms = build_search_terms("【DASHING DIVA】MAGICPRESS時尚潮流美甲片_極光之藍", max_terms=5)
|
||
romand_terms = build_search_terms("【rom&nd】勝過眼皮十色眼影盤", max_terms=5)
|
||
|
||
assert dashing_terms[0] == "dashing diva 時尚潮流美甲片 極光之藍"
|
||
assert romand_terms[0] == "romand 勝過眼皮十色眼影盤"
|
||
|
||
|
||
def test_marketplace_search_terms_prioritize_exact_dashing_diva_variant_name():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
moon_terms = build_search_terms(
|
||
"【DASHING DIVA】MAGICPRESS 時尚潮流美甲片-月影柔霧(貓眼 金色 漸層)",
|
||
max_terms=5,
|
||
)
|
||
silk_terms = build_search_terms(
|
||
"【DASHING DIVA】MAGICPRESS 時尚潮流美甲片-銀絲柔彩(紫色 漸層 貓眼)",
|
||
max_terms=5,
|
||
)
|
||
|
||
assert moon_terms[0] == "dashing diva 時尚潮流美甲片 月影柔霧"
|
||
assert silk_terms[0] == "dashing diva 時尚潮流美甲片 銀絲柔彩"
|
||
|
||
|
||
def test_marketplace_search_terms_prioritize_private_wash_identity_phrase():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
terms = build_search_terms(
|
||
"【Lactacyd 立朵舒】私密潔浴露250ml - 滋潤緊緻/加倍修護(私密清潔 產後私密保養 更年期乾癢修護)",
|
||
max_terms=5,
|
||
)
|
||
|
||
assert terms[0] == "立朵舒 私密潔浴露 250ml"
|
||
assert "滋潤緊緻" in terms[1]
|
||
|
||
|
||
def test_marketplace_search_terms_prioritize_precise_primer_identity_phrase():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
shu_terms = build_search_terms(
|
||
"【植村秀】官方直營 無極限保濕防曬妝前乳30ml(Shu uemura/防曬/保濕/校色",
|
||
max_terms=5,
|
||
)
|
||
meme_terms = build_search_terms(
|
||
"【im meme】我愛修膚/水凝光透/控油/好氣色妝前防護乳(SPF50+PA++++)",
|
||
max_terms=5,
|
||
)
|
||
eaoron_terms = build_search_terms(
|
||
"【Eaoron】澳洲 經典素顏霜 50ml#0000199#(面霜 素顏霜 懶人霜 打造素顏女神)",
|
||
max_terms=5,
|
||
)
|
||
|
||
assert shu_terms[0] == "植村秀 無極限保濕防曬妝前乳 30ml"
|
||
assert "校色" not in " ".join(shu_terms[:3])
|
||
assert any("水凝光透 妝前防護乳" in term for term in meme_terms[:3])
|
||
assert "好氣色" not in " ".join(meme_terms[:3])
|
||
assert eaoron_terms[0] == "eaoron 素顏霜 50ml"
|
||
assert "懶人霜" not in " ".join(eaoron_terms[:3])
|
||
|
||
|
||
def test_marketplace_search_terms_prefer_exact_identity_for_nail_foam_and_foot_mask():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
opi_terms = build_search_terms(
|
||
"【O.P.I】Top Coat 持久閃耀保色護甲油15mL-IST31(類光繚指甲油專用亮油/小銀蓋/如膠似漆/美甲/官方直營)",
|
||
max_terms=5,
|
||
)
|
||
arau_terms = build_search_terms(
|
||
"【arau baby】愛樂寶 溫和洗手慕斯300ml (溫和不乾澀;寶寶共和國)",
|
||
max_terms=5,
|
||
)
|
||
kameria_terms = build_search_terms(
|
||
"【KAMERIA】凱蜜菈 足足稱奇足膜17ml*2枚入(任選三款)",
|
||
max_terms=5,
|
||
)
|
||
|
||
assert opi_terms[0] == "ist31 閃耀保色護甲油 15ml"
|
||
assert "小銀蓋" not in " ".join(opi_terms[:3])
|
||
assert arau_terms[0] == "愛樂寶 溫和洗手慕斯 300ml"
|
||
assert "溫和不乾澀" not in " ".join(arau_terms[:3])
|
||
assert kameria_terms[0] == "凱蜜菈 足足稱奇足膜 17ml 2包"
|
||
assert "枚入" not in " ".join(kameria_terms[:3])
|
||
|
||
|
||
def test_marketplace_search_terms_preserve_decimal_spec_and_shade_option():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
terms = build_search_terms(
|
||
"【Maybelline 媚比琳】超持久水光鎖吻唇釉#62 4.2ml",
|
||
max_terms=5,
|
||
)
|
||
|
||
assert terms[0] == "媚比琳 超持久水光鎖吻唇釉 62 4.2ml"
|
||
assert "4 2ml" not in " ".join(terms)
|
||
|
||
|
||
def test_marketplace_search_terms_prefer_specific_line_over_generic_usage_words():
|
||
from services.marketplace_product_matcher import build_search_terms
|
||
|
||
mask_terms = build_search_terms("【SK-II】官方直營 青春敷面膜10+6+6送12(保濕/面膜/超品日)", max_terms=5)
|
||
bottle_terms = build_search_terms(
|
||
"【LANCOME 蘭蔻】官方直營 經典款小黑瓶100ml雙入組(LANCOME/經典款/抗老)",
|
||
max_terms=5,
|
||
)
|
||
rose_terms = build_search_terms(
|
||
"【蘭蔻】官方直營 玫瑰霜60ml+玫瑰精露150ml(LANCOME/永生玫瑰霜/抗老/PDRN)",
|
||
max_terms=5,
|
||
)
|
||
|
||
assert mask_terms[0] == "sk ii 青春敷面膜"
|
||
assert bottle_terms[0] == "蘭蔻 小黑瓶 100ml"
|
||
assert rose_terms[0] == "蘭蔻 永生玫瑰霜 60ml"
|
||
assert "抗老" not in " ".join(bottle_terms[:3] + rose_terms[:3])
|
||
assert "pdrn" not in " ".join(term.lower() for term in rose_terms[:3])
|
||
|
||
|
||
def test_batch_compare_top_uses_latest_momo_price_not_revenue(monkeypatch):
|
||
from services import pchome_crawler
|
||
|
||
engine = create_engine("sqlite:///:memory:")
|
||
with engine.begin() as conn:
|
||
conn.execute(text("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, i_code TEXT)"))
|
||
conn.execute(text("CREATE TABLE daily_sales (product_id INTEGER, revenue NUMERIC, date TEXT)"))
|
||
conn.execute(text("CREATE TABLE price_records (id INTEGER PRIMARY KEY, product_id INTEGER, price NUMERIC, timestamp TEXT)"))
|
||
conn.execute(text("INSERT INTO products VALUES (1, '理膚寶水 B5 修復霜 40ml', 'SKU001')"))
|
||
conn.execute(text("INSERT INTO daily_sales VALUES (1, 999999, '2026-05-19')"))
|
||
conn.execute(text("INSERT INTO price_records VALUES (1, 1, 699, '2026-05-19 10:00:00')"))
|
||
|
||
calls = []
|
||
|
||
def fake_compare_product(name, momo_price, momo_icode=""):
|
||
calls.append((name, momo_price, momo_icode))
|
||
return {"found": True, "momo_price": momo_price}
|
||
|
||
monkeypatch.setattr(pchome_crawler, "compare_product", fake_compare_product)
|
||
|
||
results = pchome_crawler.batch_compare_top(engine, top_n=1)
|
||
|
||
assert results == [{"found": True, "momo_price": 699.0}]
|
||
assert calls == [("理膚寶水 B5 修復霜 40ml", 699.0, "SKU001")]
|