200 lines
7.2 KiB
Python
200 lines
7.2 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
|
|
def test_revalidator_promotes_legacy_same_product_without_refreshing_expired_price():
|
|
from services.competitor_identity_revalidator import classify_legacy_competitor_row
|
|
|
|
row = {
|
|
"sku": "10950080",
|
|
"momo_name": "【台酒生技】黑酵母酒粕逆齡活膚青春露5入-(120ml/入)",
|
|
"momo_price": 999,
|
|
"pchome_price": 899,
|
|
"competitor_product_id": "PC-1",
|
|
"competitor_product_name": "【台酒生技】金粹黑酵母酒粕逆齡活膚青春露120ml_5入",
|
|
"tags": ["discount_10pct"],
|
|
"is_expired": True,
|
|
"expires_at": datetime.utcnow() - timedelta(hours=1),
|
|
}
|
|
|
|
decision = classify_legacy_competitor_row(row)
|
|
|
|
assert decision.accepted is True
|
|
assert decision.status == "expired_match"
|
|
assert decision.score >= 0.76
|
|
assert "identity_v2" in decision.tags
|
|
assert "legacy_revalidated" in decision.tags
|
|
|
|
|
|
def test_revalidator_rejects_legacy_brand_conflict():
|
|
from services.competitor_identity_revalidator import classify_legacy_competitor_row
|
|
|
|
row = {
|
|
"sku": "BAD-1",
|
|
"momo_name": "【蘭蔻】官方直營 玫瑰霜60ml+玫瑰精露150ml",
|
|
"momo_price": 18765,
|
|
"pchome_price": 1249,
|
|
"competitor_product_id": "PC-BAD",
|
|
"competitor_product_name": "LOREAL Paris 巴黎萊雅 金致臻顏花蜜奢養膠原輕盈乳霜_60ml",
|
|
"tags": [],
|
|
"is_expired": False,
|
|
}
|
|
|
|
decision = classify_legacy_competitor_row(row)
|
|
|
|
assert decision.accepted is False
|
|
assert decision.status == "identity_veto"
|
|
assert decision.hard_veto is True
|
|
assert "brand_conflict" in decision.diagnostic
|
|
|
|
|
|
def test_dashboard_match_status_distinguishes_expired_and_legacy_rows():
|
|
from routes.dashboard_routes import _build_pchome_match_status
|
|
|
|
expired = _build_pchome_match_status(
|
|
None,
|
|
ineligible={"reason": "expired_match", "match_score": 0.91},
|
|
)
|
|
legacy = _build_pchome_match_status(
|
|
None,
|
|
ineligible={"reason": "legacy_without_identity_v2", "match_score": 0.82},
|
|
)
|
|
|
|
assert expired["label"] == "價格過期待刷新"
|
|
assert expired["tone"] == "watch"
|
|
assert legacy["label"] == "舊版配對待重驗"
|
|
assert "identity_v2" in legacy["summary"]
|
|
|
|
|
|
def test_dashboard_match_status_explains_identity_veto_reason():
|
|
from routes.dashboard_routes import _build_pchome_match_status
|
|
|
|
bundle = _build_pchome_match_status({
|
|
"attempt_status": "identity_veto",
|
|
"best_match_score": 0.32,
|
|
"error_message": "score=0.32; reasons=bundle_offer_conflict,product_line_conflict",
|
|
})
|
|
refill = _build_pchome_match_status({
|
|
"attempt_status": "identity_veto",
|
|
"best_match_score": 0.32,
|
|
"error_message": "score=0.32; reasons=refill_pack_conflict",
|
|
})
|
|
|
|
assert bundle["label"] == "組合規格不相容"
|
|
assert "組合包/多件組" in bundle["summary"]
|
|
assert refill["label"] == "補充包不相容"
|
|
assert "補充瓶/補充包" in refill["summary"]
|
|
|
|
|
|
def test_dashboard_match_status_marks_very_low_similarity_as_no_credible_match():
|
|
from routes.dashboard_routes import _build_pchome_match_status
|
|
|
|
status = _build_pchome_match_status({
|
|
"attempt_status": "low_score",
|
|
"candidate_count": 20,
|
|
"best_match_score": 0.33,
|
|
"error_message": "score=0.33; reasons=",
|
|
})
|
|
|
|
assert status["label"] == "未找到可信同款"
|
|
assert "相似度不足" in status["summary"]
|
|
|
|
|
|
def test_dashboard_match_status_explains_unit_comparable_bundle():
|
|
from routes.dashboard_routes import _build_pchome_match_status
|
|
|
|
status = _build_pchome_match_status({
|
|
"attempt_status": "unit_comparable",
|
|
"best_match_score": 0.74,
|
|
"error_message": "score=0.74; reasons=bundle_offer_conflict,unit_comparable",
|
|
"unit_comparison": {
|
|
"comparable": True,
|
|
"summary": "MOMO $14.99/ml vs PChome $16.98/ml (-11.7%)",
|
|
},
|
|
})
|
|
|
|
assert status["label"] == "需單位價比較"
|
|
assert status["tone"] == "watch"
|
|
assert "已換算單位價" in status["summary"]
|
|
|
|
|
|
def test_dashboard_match_status_shows_manual_review_closure_states():
|
|
from routes.dashboard_routes import _build_competitor_decision, _build_pchome_match_status
|
|
|
|
rejected = _build_pchome_match_status({
|
|
"attempt_status": "manual_rejected",
|
|
"best_match_score": 0.71,
|
|
})
|
|
unit_price = _build_pchome_match_status({
|
|
"attempt_status": "manual_unit_price_required",
|
|
"best_match_score": 0.74,
|
|
})
|
|
needs_research = _build_pchome_match_status({
|
|
"attempt_status": "manual_needs_research",
|
|
"best_match_score": 0.68,
|
|
})
|
|
|
|
assert rejected["label"] == "人工已否決"
|
|
assert "跳過正式價差寫入" in rejected["summary"]
|
|
assert unit_price["label"] == "人工標記單位價"
|
|
assert "總價不可直接比較" in unit_price["summary"]
|
|
assert needs_research["label"] == "人工要求補搜尋"
|
|
assert "重新抓取" in needs_research["summary"]
|
|
|
|
decision = _build_competitor_decision(980, 899, match_status=rejected)
|
|
assert decision["label"] == "人工已否決"
|
|
assert decision["gap_amount"] is None
|
|
|
|
|
|
def test_dashboard_match_status_shows_rescore_accepted_review_state():
|
|
from routes.dashboard_routes import _build_pchome_match_status
|
|
|
|
status = _build_pchome_match_status({
|
|
"attempt_status": "rescore_accepted_current",
|
|
"best_match_score": 0.801,
|
|
"error_message": "matcher_rescore=accepted_current; reasons=strong_exact_spec_match",
|
|
})
|
|
|
|
assert status["label"] == "重算待人工覆核"
|
|
assert status["tone"] == "watch"
|
|
assert "人工確認身份後才可寫入正式 PChome 價差" in status["summary"]
|
|
|
|
|
|
def test_dashboard_match_status_uses_specific_matcher_reason_labels():
|
|
from routes.dashboard_routes import _build_pchome_match_status
|
|
|
|
finish_gap = _build_pchome_match_status({
|
|
"attempt_status": "identity_veto",
|
|
"best_match_score": 0.32,
|
|
"error_message": "score=0.32; reasons=makeup_finish_conflict",
|
|
})
|
|
variant_review = _build_pchome_match_status({
|
|
"attempt_status": "identity_veto",
|
|
"best_match_score": 0.78,
|
|
"error_message": "score=0.78; reasons=variant_selection_review",
|
|
})
|
|
|
|
assert finish_gap["label"] == "妝效質地不同"
|
|
assert "妝效質地不同" in finish_gap["summary"]
|
|
assert variant_review["label"] == "多款任選待確認"
|
|
assert "需人工確認" in variant_review["summary"]
|
|
|
|
|
|
def test_dashboard_match_status_uses_focused_marketplace_reason_labels():
|
|
from routes.dashboard_routes import _build_pchome_match_status
|
|
|
|
line_gap = _build_pchome_match_status({
|
|
"attempt_status": "identity_veto",
|
|
"best_match_score": 0.32,
|
|
"error_message": "score=0.32; reasons=lancome_line_conflict",
|
|
})
|
|
variant_gap = _build_pchome_match_status({
|
|
"attempt_status": "identity_veto",
|
|
"best_match_score": 0.32,
|
|
"error_message": "score=0.32; reasons=saugella_variant_conflict",
|
|
})
|
|
|
|
assert line_gap["label"] == "商品線不符已排除"
|
|
assert "商品線或用途不同" in line_gap["summary"]
|
|
assert variant_gap["label"] == "款式版本不符"
|
|
assert "款式不同" in variant_gap["summary"]
|