[V10.334] 強化 PChome 比價重評與補抓可觀測性
All checks were successful
CD Pipeline / deploy (push) Successful in 1m5s

This commit is contained in:
OoO
2026-05-20 14:45:41 +08:00
parent 9c45a87723
commit b636303481
19 changed files with 1099 additions and 41 deletions

View File

@@ -79,9 +79,9 @@ def test_dashboard_match_status_explains_identity_veto_reason():
"error_message": "score=0.32; reasons=refill_pack_conflict",
})
assert bundle["label"] == "組合差異待審"
assert bundle["label"] == "組合規格不相容"
assert "組合包/多件組" in bundle["summary"]
assert refill["label"] == "補充包差異待審"
assert refill["label"] == "補充包不相容"
assert "補充瓶/補充包" in refill["summary"]

View File

@@ -29,6 +29,8 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes():
assert "identity_v2" in source
assert "_fetch_expired_identity_skus" in source
assert "run_expired_identity_refresh" in source
assert "_fetch_retryable_candidate_skus" in source
assert "run_retryable_candidate_revalidation" in source
assert "refresh_known_identity" in source
assert 'attempt_status="unit_comparable"' in source
assert 'attempt_status="refresh_unit_comparable"' in source

View File

@@ -383,8 +383,8 @@ def test_dashboard_v2_shows_pchome_competitor_pricing_and_links():
assert "候選:{{ item.pchome_match_attempt.best_competitor_product_name }}" in dashboard
assert "候選價,需單位換算" in dashboard
assert "_load_pchome_match_attempt_map" in route_source
assert "低信心待" in route_source
assert "規格衝突待審" in route_source
assert "低信心待補強" in route_source
assert "規格不符已排除" in route_source
assert "dashboard-review-reasons" in dashboard
assert "series.pchome" in page_js
assert "label: 'PChome'" in page_js
@@ -454,8 +454,12 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
assert "@ai_bp.route('/api/ai/product-picks/generate', methods=['POST'])" in route_source
assert "generate_product_pick_list(engine" in route_source
assert "@ai_bp.route('/api/ai/pchome-match/backfill', methods=['POST'])" in route_source
assert "run_unmatched_priority(limit=limit)" in route_source
assert "@ai_bp.route('/api/ai/pchome-match/backfill/status', methods=['GET'])" in route_source
assert "run_unmatched_priority(limit=unmatched_limit)" in route_source
assert "run_retryable_candidate_revalidation" in route_source
assert "generate_product_pick_list(engine, limit=50)" in route_source
assert "start_pchome_backfill_run" in route_source
assert "finish_pchome_backfill_run" in route_source
assert "payload.get('limit', 50)" in route_source
assert "JSON.stringify({ limit: 50 })" in template
assert "完成後會重算 AI 挑品清單" in route_source
@@ -476,6 +480,7 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
agent_actions_source = (ROOT / "services/agent_actions.py").read_text(encoding="utf-8")
assert "def run_pchome_match_backfill_task" in scheduler_source
assert "_save_stats('pchome_match_backfill'" in scheduler_source
assert "retryable_candidate_revalidation_total" in scheduler_source
assert "run_pchome_match_backfill_task" in run_scheduler_source
assert "每日 10:30pchome_match_backfill" in run_scheduler_source
assert '"run_pchome_match_backfill_task"' in agent_actions_source
@@ -486,6 +491,12 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
assert "backfillPchomeMatches" in template
assert "/api/ai/product-picks/generate" in template
assert "/api/ai/pchome-match/backfill" in template
assert "/api/ai/pchome-match/backfill/status" in dashboard_template
assert "PCHOME MATCH BACKFILL" in dashboard_template
assert "data-pchome-backfill-trigger" in dashboard_template
dashboard_js = (ROOT / "web/static/js/page-dashboard-v2.js").read_text(encoding="utf-8")
assert "loadPchomeBackfillStatus" in dashboard_js
assert "window.backfillPchomeMatches" in dashboard_js
assert "'product_pick':['bg-success'" in template
assert "kpiMatchRate" in template

View File

@@ -337,6 +337,53 @@ def test_marketplace_matcher_accepts_strong_multi_component_line_without_full_sp
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_search_terms_prefer_readable_brand_core_spec():
from services.marketplace_product_matcher import build_search_terms

View File

@@ -0,0 +1,57 @@
from services.pchome_backfill_status import (
PchomeBackfillAlreadyRunning,
fail_pchome_backfill_run,
finish_pchome_backfill_run,
get_pchome_backfill_status,
start_pchome_backfill_run,
update_pchome_backfill_run,
)
def test_pchome_backfill_status_tracks_running_and_completion(tmp_path, monkeypatch):
monkeypatch.setenv("PCHOME_BACKFILL_STATUS_PATH", str(tmp_path / "status.json"))
run = start_pchome_backfill_run(limit=12, operator="tester")
running = get_pchome_backfill_status()
assert running["running"] is True
assert running["status"] == "running"
assert running["current_run"]["run_id"] == run["run_id"]
assert running["current_run"]["limit"] == 12
try:
start_pchome_backfill_run(limit=5)
except PchomeBackfillAlreadyRunning as exc:
assert exc.status["running"] is True
else:
raise AssertionError("expected active PChome backfill guard")
update_pchome_backfill_run(
run["run_id"],
stage="matching",
result={"total_skus": 12, "matched": 3},
)
finish_pchome_backfill_run(
run["run_id"],
result={"total_skus": 12, "matched": 3},
pick_result={"written": 9},
)
completed = get_pchome_backfill_status()
assert completed["running"] is False
assert completed["status"] == "completed"
assert completed["last_result"]["matched"] == 3
assert completed["recent_runs"][0]["pick_result"]["written"] == 9
def test_pchome_backfill_status_records_failure(tmp_path, monkeypatch):
monkeypatch.setenv("PCHOME_BACKFILL_STATUS_PATH", str(tmp_path / "status.json"))
run = start_pchome_backfill_run(limit=8)
fail_pchome_backfill_run(run["run_id"], "crawler timeout")
failed = get_pchome_backfill_status()
assert failed["running"] is False
assert failed["status"] == "failed"
assert failed["last_error"] == "crawler timeout"
assert failed["recent_runs"][0]["last_error"] == "crawler timeout"