[V10.355] rescue near-threshold identity cohorts
This commit is contained in:
@@ -18,14 +18,16 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes():
|
||||
assert "INSERT INTO competitor_match_attempts" in source
|
||||
assert "CAST(:search_terms AS jsonb)" in source
|
||||
assert 'attempt_status="matched"' in source
|
||||
assert '"low_score"' in source
|
||||
assert '"recoverable_low_score"' in source
|
||||
assert '"true_low_confidence"' in source
|
||||
assert '"identity_veto"' in source
|
||||
assert 'attempt_status="no_result"' in source
|
||||
assert 'attempt_status="no_match"' in source
|
||||
assert 'attempt_status="error"' in source
|
||||
assert "_search_pchome_candidates(crawler, momo_name, search_terms, momo_price=momo_price)" in source
|
||||
assert 'attempt_status="needs_review"' in source
|
||||
assert 'attempt_status="protected_existing_match"' in source
|
||||
assert "_should_upsert_competitor_price" in source
|
||||
assert "_classify_low_score_attempt" in source
|
||||
assert "replace_legacy_unverified" in source
|
||||
assert "identity_v2" in source
|
||||
assert "_fetch_expired_identity_skus" in source
|
||||
@@ -35,8 +37,7 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes():
|
||||
retryable_source = source.split("def _fetch_retryable_candidate_skus", 1)[1].split(
|
||||
"def _fetch_expired_identity_skus", 1
|
||||
)[0]
|
||||
assert "la.attempt_status = 'low_score'" in retryable_source
|
||||
assert "refresh_low_score')" not in retryable_source
|
||||
assert "la.attempt_status IN ('low_score', 'refresh_low_score', 'recoverable_low_score')" in retryable_source
|
||||
latest_attempt_source = retryable_source.split("latest_attempt AS", 1)[1].split(
|
||||
"SELECT\n lm.product_id", 1
|
||||
)[0]
|
||||
@@ -144,7 +145,7 @@ def test_reject_review_expires_current_formal_price():
|
||||
best_competitor_price, best_match_score, error_message, attempted_at)
|
||||
VALUES
|
||||
('A005', 'pchome', 1, '舒特膚 AD 乳液 200ml', 980,
|
||||
'[]', 1, 'needs_review',
|
||||
'[]', 1, 'protected_existing_match',
|
||||
'DDAB01-REJECT', '舒特膚 AD 乳液 200ml', 899, 0.84,
|
||||
'score=0.84', '2026-05-20 09:10:00')
|
||||
"""))
|
||||
@@ -408,6 +409,134 @@ def test_competitor_feeder_splits_hard_veto_from_low_score(monkeypatch):
|
||||
assert attempts[0]["diagnostics"].hard_veto is True
|
||||
|
||||
|
||||
def test_competitor_feeder_marks_near_threshold_same_line_as_recoverable(monkeypatch):
|
||||
from services.competitor_price_feeder import CompetitorPriceFeeder
|
||||
from services.pchome_crawler import PChomeProduct
|
||||
|
||||
product = PChomeProduct(
|
||||
product_id="DDAB01-RECOVERABLE",
|
||||
name="Recipe Box 韓兔 兒童防曬氣墊粉餅",
|
||||
price=699,
|
||||
original_price=799,
|
||||
discount=12,
|
||||
image_url="",
|
||||
product_url="https://24h.pchome.com.tw/prod/DDAB01-RECOVERABLE",
|
||||
stock=20,
|
||||
store="24h",
|
||||
rating=4.7,
|
||||
review_count=8,
|
||||
is_on_sale=True,
|
||||
crawled_at=datetime.now(),
|
||||
)
|
||||
|
||||
class FakeCrawler:
|
||||
def __init__(self, *_args, **_kwargs):
|
||||
pass
|
||||
|
||||
def search_products(self, *_args, **_kwargs):
|
||||
return True, "ok", [product]
|
||||
|
||||
def fake_score(*_args, **_kwargs):
|
||||
return SimpleNamespace(
|
||||
score=0.754,
|
||||
brand_score=1.0,
|
||||
token_score=0.59,
|
||||
spec_score=0.55,
|
||||
sequence_score=0.53,
|
||||
type_score=1.0,
|
||||
price_penalty=0.0,
|
||||
hard_veto=False,
|
||||
reasons=("strong_product_line_match",),
|
||||
comparison_mode="exact_identity",
|
||||
tags=["identity_v2", "comparison_exact_identity", "brand_match"],
|
||||
)
|
||||
|
||||
monkeypatch.setattr("services.pchome_crawler.PChomeCrawler", FakeCrawler)
|
||||
monkeypatch.setattr("services.marketplace_product_matcher.score_marketplace_match", fake_score)
|
||||
feeder = CompetitorPriceFeeder(engine=object())
|
||||
attempts = []
|
||||
monkeypatch.setattr(
|
||||
feeder,
|
||||
"_record_match_attempt",
|
||||
lambda *args, **kwargs: attempts.append(kwargs),
|
||||
)
|
||||
|
||||
result = feeder._run_sku_items([{
|
||||
"sku": "RB001",
|
||||
"name": "【Recipebox】Recipe Box兒童防曬氣墊粉餅(兒童化妝品/無毒防曬粉餅/天然彩妝)",
|
||||
"product_id": 8,
|
||||
"momo_price": 699,
|
||||
}])
|
||||
|
||||
assert result.matched == 0
|
||||
assert result.skipped_low_score == 1
|
||||
assert attempts[0]["attempt_status"] == "recoverable_low_score"
|
||||
|
||||
|
||||
def test_competitor_feeder_marks_weak_identity_as_true_low_confidence(monkeypatch):
|
||||
from services.competitor_price_feeder import CompetitorPriceFeeder
|
||||
from services.pchome_crawler import PChomeProduct
|
||||
|
||||
product = PChomeProduct(
|
||||
product_id="DDAB01-WEAK",
|
||||
name="韓系彩妝 十色眼影盤",
|
||||
price=499,
|
||||
original_price=699,
|
||||
discount=28,
|
||||
image_url="",
|
||||
product_url="https://24h.pchome.com.tw/prod/DDAB01-WEAK",
|
||||
stock=20,
|
||||
store="24h",
|
||||
rating=4.2,
|
||||
review_count=8,
|
||||
is_on_sale=True,
|
||||
crawled_at=datetime.now(),
|
||||
)
|
||||
|
||||
class FakeCrawler:
|
||||
def __init__(self, *_args, **_kwargs):
|
||||
pass
|
||||
|
||||
def search_products(self, *_args, **_kwargs):
|
||||
return True, "ok", [product]
|
||||
|
||||
def fake_score(*_args, **_kwargs):
|
||||
return SimpleNamespace(
|
||||
score=0.733,
|
||||
brand_score=0.95,
|
||||
token_score=0.51,
|
||||
spec_score=0.45,
|
||||
sequence_score=0.44,
|
||||
type_score=0.55,
|
||||
price_penalty=0.0,
|
||||
hard_veto=False,
|
||||
reasons=(),
|
||||
comparison_mode="exact_identity",
|
||||
tags=["identity_v2", "comparison_exact_identity"],
|
||||
)
|
||||
|
||||
monkeypatch.setattr("services.pchome_crawler.PChomeCrawler", FakeCrawler)
|
||||
monkeypatch.setattr("services.marketplace_product_matcher.score_marketplace_match", fake_score)
|
||||
feeder = CompetitorPriceFeeder(engine=object())
|
||||
attempts = []
|
||||
monkeypatch.setattr(
|
||||
feeder,
|
||||
"_record_match_attempt",
|
||||
lambda *args, **kwargs: attempts.append(kwargs),
|
||||
)
|
||||
|
||||
result = feeder._run_sku_items([{
|
||||
"sku": "RM001",
|
||||
"name": "【rom&nd】勝過眼皮十色眼影盤",
|
||||
"product_id": 9,
|
||||
"momo_price": 499,
|
||||
}])
|
||||
|
||||
assert result.matched == 0
|
||||
assert result.skipped_low_score == 1
|
||||
assert attempts[0]["attempt_status"] == "true_low_confidence"
|
||||
|
||||
|
||||
def test_should_upsert_allows_same_identity_candidate_to_replace_lower_score():
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
@@ -455,6 +584,83 @@ def test_should_upsert_allows_same_identity_candidate_to_replace_lower_score():
|
||||
assert reason.startswith("replace_same_identity_better_score=0.788->0.811")
|
||||
|
||||
|
||||
def test_competitor_feeder_marks_existing_stronger_match_as_protected(monkeypatch):
|
||||
from services.competitor_price_feeder import CompetitorPriceFeeder
|
||||
from services.pchome_crawler import PChomeProduct
|
||||
|
||||
product = PChomeProduct(
|
||||
product_id="DDAB01-NEW",
|
||||
name="PONY EFFECT 絕對持久定妝噴霧",
|
||||
price=599,
|
||||
original_price=699,
|
||||
discount=14,
|
||||
image_url="",
|
||||
product_url="https://24h.pchome.com.tw/prod/DDAB01-NEW",
|
||||
stock=20,
|
||||
store="24h",
|
||||
rating=4.7,
|
||||
review_count=8,
|
||||
is_on_sale=True,
|
||||
crawled_at=datetime.now(),
|
||||
)
|
||||
|
||||
class FakeCrawler:
|
||||
def __init__(self, *_args, **_kwargs):
|
||||
pass
|
||||
|
||||
def search_products(self, *_args, **_kwargs):
|
||||
return True, "ok", [product]
|
||||
|
||||
def fake_score(*_args, **_kwargs):
|
||||
return SimpleNamespace(
|
||||
score=0.781,
|
||||
brand_score=1.0,
|
||||
token_score=0.79,
|
||||
spec_score=0.55,
|
||||
sequence_score=0.68,
|
||||
type_score=0.55,
|
||||
price_penalty=0.0,
|
||||
hard_veto=False,
|
||||
reasons=("shared_identity_anchor_packaging_variant",),
|
||||
comparison_mode="exact_identity",
|
||||
tags=["identity_v2", "comparison_exact_identity", "brand_match"],
|
||||
)
|
||||
|
||||
monkeypatch.setattr("services.pchome_crawler.PChomeCrawler", FakeCrawler)
|
||||
monkeypatch.setattr("services.marketplace_product_matcher.score_marketplace_match", fake_score)
|
||||
feeder = CompetitorPriceFeeder(engine=object())
|
||||
attempts = []
|
||||
writes = []
|
||||
monkeypatch.setattr(
|
||||
feeder,
|
||||
"_record_match_attempt",
|
||||
lambda *args, **kwargs: attempts.append(kwargs),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
feeder,
|
||||
"_should_upsert_competitor_price",
|
||||
lambda *_args, **_kwargs: (False, "existing_match_conflict;existing_score=0.948;incoming_score=0.781"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
feeder,
|
||||
"_upsert_competitor_price",
|
||||
lambda *args, **kwargs: writes.append((args, kwargs)),
|
||||
)
|
||||
|
||||
result = feeder._run_sku_items([{
|
||||
"sku": "14133077",
|
||||
"name": "【PONY EFFECT】絕對持久定妝噴霧",
|
||||
"product_id": 10,
|
||||
"momo_price": 599,
|
||||
}])
|
||||
|
||||
assert result.matched == 0
|
||||
assert result.skipped_low_score == 1
|
||||
assert writes == []
|
||||
assert attempts[0]["attempt_status"] == "protected_existing_match"
|
||||
assert "existing_match_conflict" in attempts[0]["error_message"]
|
||||
|
||||
|
||||
def test_search_candidates_does_not_stop_on_merely_acceptable_match(monkeypatch):
|
||||
from services.competitor_price_feeder import _search_pchome_candidates
|
||||
from services.pchome_crawler import PChomeProduct
|
||||
|
||||
@@ -573,6 +573,22 @@ def test_marketplace_matcher_does_not_promote_different_option_without_spec():
|
||||
|
||||
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_promotes_shared_identity_anchor_near_threshold():
|
||||
@@ -649,6 +665,36 @@ def test_marketplace_matcher_promotes_shared_anchor_without_spec_conflict():
|
||||
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_shared_model_token_for_exact_model():
|
||||
from services.marketplace_product_matcher import score_marketplace_match
|
||||
|
||||
@@ -735,6 +781,16 @@ def test_marketplace_search_terms_keep_professional_product_phrase():
|
||||
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_prefer_specific_line_over_generic_usage_words():
|
||||
from services.marketplace_product_matcher import build_search_terms
|
||||
|
||||
|
||||
Reference in New Issue
Block a user