[V10.350] add retryable identity recovery loop

This commit is contained in:
OoO
2026-05-20 20:24:40 +08:00
parent cf4d8aedea
commit d030e4cf22
5 changed files with 307 additions and 5 deletions

View File

@@ -618,6 +618,124 @@ def test_competitor_feeder_refreshes_expired_identity_by_known_product_id(monkey
assert attempts[0]["search_terms"] == ["known_product_id:DDAB01-1900ABCD"]
def test_competitor_feeder_refresh_recovers_with_fresh_search_when_known_id_is_low_score(monkeypatch):
from services.competitor_price_feeder import CompetitorPriceFeeder
from services.pchome_crawler import PChomeProduct
stale = PChomeProduct(
product_id="DDAB01-STALE",
name="Panasonic 國際牌 男仕防水美體除毛器 國際版 (ER-GK83)",
price=2290,
original_price=2490,
discount=8,
image_url="",
product_url="https://24h.pchome.com.tw/prod/DDAB01-STALE",
stock=20,
store="24h",
rating=4.7,
review_count=8,
is_on_sale=True,
crawled_at=datetime.now(),
)
recovered = PChomeProduct(
product_id="DDAB01-RECOVERED",
name="Panasonic 國際牌 男士身體除毛器 ER-GK83",
price=2390,
original_price=2490,
discount=4,
image_url="",
product_url="https://24h.pchome.com.tw/prod/DDAB01-RECOVERED",
stock=20,
store="24h",
rating=4.8,
review_count=8,
is_on_sale=True,
crawled_at=datetime.now(),
)
class FakeCrawler:
def __init__(self, *_args, **_kwargs):
pass
def fetch_product_details(self, product_ids, batch_size=20):
assert product_ids == ["DDAB01-STALE"]
return True, "ok", [stale]
def search_products(self, *_args, **_kwargs):
return True, "ok", [stale, recovered]
def fake_score(_momo_name, competitor_name, **_kwargs):
if "RECOVERED" in competitor_name or "男士身體除毛器" in competitor_name:
return SimpleNamespace(
score=0.81,
brand_score=1.0,
token_score=0.8,
spec_score=0.8,
sequence_score=0.72,
type_score=0.55,
price_penalty=0.0,
hard_veto=False,
reasons=("shared_model_token",),
comparison_mode="exact_identity",
tags=["identity_v2", "comparison_exact_identity", "brand_match"],
)
return SimpleNamespace(
score=0.68,
brand_score=1.0,
token_score=0.55,
spec_score=0.55,
sequence_score=0.6,
type_score=0.55,
price_penalty=0.0,
hard_veto=False,
reasons=(),
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,
"_should_upsert_competitor_price",
lambda *_args, **_kwargs: (True, "same_or_empty_existing"),
)
monkeypatch.setattr(
feeder,
"_upsert_competitor_price",
lambda sku, product, score, tags, **kwargs: writes.append({
"sku": sku,
"product_id": product.product_id,
"score": score,
"tags": tags,
**kwargs,
}),
)
monkeypatch.setattr(
feeder,
"_record_match_attempt",
lambda *args, **kwargs: attempts.append(kwargs),
)
result = feeder._run_known_identity_refresh_items([{
"sku": "TP00090100000153",
"name": "【Panasonic 國際牌】男士身體除毛器 2025新款 ER-GK83 日版 日本直送",
"product_id": 1,
"momo_price": 2490,
"competitor_product_id": "DDAB01-STALE",
}])
assert result.matched == 1
assert writes[0]["product_id"] == "DDAB01-RECOVERED"
assert "fresh_search_recovery" in writes[0]["tags"]
assert attempts[0]["attempt_status"] == "matched"
assert "known_product_id:DDAB01-STALE" in attempts[0]["search_terms"]
assert any("Panasonic" in term or "國際牌" in term for term in attempts[0]["search_terms"])
def test_competitor_feeder_records_unit_comparable_without_price_upsert(monkeypatch):
from services.competitor_price_feeder import CompetitorPriceFeeder
from services.pchome_crawler import PChomeProduct

View File

@@ -214,6 +214,50 @@ def test_marketplace_matcher_rejects_product_type_conflict_even_when_line_matche
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_rejects_same_count_different_unit_family():
from services.marketplace_product_matcher import score_marketplace_match