[V10.334] 強化 PChome 比價重評與補抓可觀測性
All checks were successful
CD Pipeline / deploy (push) Successful in 1m5s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m5s
This commit is contained in:
@@ -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"]
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:30:pchome_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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
57
tests/test_pchome_backfill_status.py
Normal file
57
tests/test_pchome_backfill_status.py
Normal 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"
|
||||
Reference in New Issue
Block a user