V10.588 提升比價救援穩定性與決策覆蓋
All checks were successful
CD Pipeline / deploy (push) Successful in 1m9s

This commit is contained in:
OoO
2026-06-04 21:21:40 +08:00
parent 5347c95b97
commit 58302c9fa7
5 changed files with 81 additions and 15 deletions

View File

@@ -24,7 +24,8 @@ from sqlalchemy import inspect, text
PCHOME_MATCH_SCORE_FLOOR = 0.76
CATALOG_COMPARABLE_SCORE_FLOOR = 0.88
CATALOG_COMPARABLE_SCORE_FLOOR = 0.85
CATALOG_IDENTITY_REVIEW_SCORE_FLOOR = 0.95
UNIT_COMPARABLE_STATUSES = {"unit_comparable", "refresh_unit_comparable"}
UNIT_PRICE_DECISION_STATUSES = UNIT_COMPARABLE_STATUSES | {"manual_unit_price_required"}
CATALOG_COMPARABLE_SIGNAL_REASONS = {
@@ -338,13 +339,21 @@ def _jsonb_any_array_predicate(jsonb_expr: str, values: set[str]) -> str:
def _catalog_comparable_sql(alias: str = "la") -> str:
diagnostic_codes = f"{alias}.diagnostic_codes"
signal_sql = _jsonb_any_array_predicate(diagnostic_codes, CATALOG_COMPARABLE_SIGNAL_REASONS)
identity_sql = _jsonb_any_array_predicate(diagnostic_codes, CATALOG_COMPARABLE_IDENTITY_REASONS)
block_sql = _jsonb_any_array_predicate(diagnostic_codes, CATALOG_COMPARABLE_BLOCK_REASONS)
return f"""(
{alias}.attempt_status = 'true_low_confidence'
AND COALESCE({alias}.hard_veto, false) = false
AND COALESCE({alias}.best_match_score, 0) >= {CATALOG_COMPARABLE_SCORE_FLOOR}
AND {_jsonb_any_array_predicate(diagnostic_codes, CATALOG_COMPARABLE_SIGNAL_REASONS)}
AND {_jsonb_any_array_predicate(diagnostic_codes, CATALOG_COMPARABLE_IDENTITY_REASONS)}
AND NOT {_jsonb_any_array_predicate(diagnostic_codes, CATALOG_COMPARABLE_BLOCK_REASONS)}
AND {identity_sql}
AND NOT {block_sql}
AND (
(
COALESCE({alias}.best_match_score, 0) >= {CATALOG_COMPARABLE_SCORE_FLOOR}
AND {signal_sql}
)
OR COALESCE({alias}.best_match_score, 0) >= {CATALOG_IDENTITY_REVIEW_SCORE_FLOOR}
)
)"""
@@ -1178,7 +1187,7 @@ def _cached_payload(cache_key: str, producer, ttl_seconds: int = COMPETITOR_INTE
def fetch_competitor_coverage(engine) -> dict:
return _cached_payload(
f"coverage:v13:floor={PCHOME_MATCH_SCORE_FLOOR}:catalog_floor={CATALOG_COMPARABLE_SCORE_FLOOR}:manual_reviews=1:rescore=1:review_no_fresh=1:decision_ready=1:open_queue=1:unknown_freshness=1:decision_support=1:catalog_plan=1",
f"coverage:v14:floor={PCHOME_MATCH_SCORE_FLOOR}:catalog_floor={CATALOG_COMPARABLE_SCORE_FLOOR}:identity_floor={CATALOG_IDENTITY_REVIEW_SCORE_FLOOR}:manual_reviews=1:rescore=1:review_no_fresh=1:decision_ready=1:open_queue=1:unknown_freshness=1:decision_support=1:catalog_plan=1",
lambda: _fetch_competitor_coverage_uncached(engine),
)
@@ -1435,6 +1444,7 @@ def _fetch_competitor_coverage_uncached(engine) -> dict:
"manual_accept_rate": manual_review_summary["accept_rate"],
"match_score_floor": PCHOME_MATCH_SCORE_FLOOR,
"catalog_comparable_score_floor": CATALOG_COMPARABLE_SCORE_FLOOR,
"catalog_identity_review_score_floor": CATALOG_IDENTITY_REVIEW_SCORE_FLOOR,
}

View File

@@ -939,6 +939,9 @@ def _recover_low_score_with_fresh_search(
momo_name: str,
momo_price: float = None,
existing_product_id: str = "",
max_terms: int | None = None,
max_pages: int | None = None,
max_seconds: float | None = None,
) -> tuple[Optional[tuple], list[str], int]:
"""
當 legacy / known-id 候選重評仍低分時,再跑一次 fresh keyword search
@@ -950,6 +953,9 @@ def _recover_low_score_with_fresh_search(
momo_name,
keywords=keywords,
momo_price=momo_price,
max_terms=max_terms,
max_pages=max_pages,
max_seconds=max_seconds,
)
if existing_product_id:
existing_key = _product_id_key(existing_product_id)
@@ -3053,6 +3059,7 @@ class CompetitorPriceFeeder:
source: str = "pchome",
label: str = "已確認身份價格刷新",
allow_missing_recovery: bool = True,
bounded_recovery: bool = False,
) -> FeederResult:
start = time.time()
@@ -3064,7 +3071,17 @@ class CompetitorPriceFeeder:
return FeederResult(0, 0, 0, 0, 0, 0.0)
from services.pchome_crawler import PChomeCrawler
crawler = PChomeCrawler(timeout=REQUEST_TIMEOUT, delay=RATE_DELAY)
crawler_timeout = BACKFILL_REQUEST_TIMEOUT if bounded_recovery else REQUEST_TIMEOUT
crawler = PChomeCrawler(timeout=crawler_timeout, delay=RATE_DELAY)
recovery_kwargs = (
{
"max_terms": BACKFILL_MAX_SEARCH_TERMS,
"max_pages": BACKFILL_SEARCH_MAX_PAGES,
"max_seconds": BACKFILL_MAX_SECONDS_PER_SKU,
}
if bounded_recovery
else {}
)
requested_ids = [
str(item.get("competitor_product_id") or "").strip()
@@ -3120,6 +3137,7 @@ class CompetitorPriceFeeder:
momo_name,
momo_price=momo_price,
existing_product_id=competitor_product_id,
**recovery_kwargs,
)
if recovered:
best_product, score, diagnostics = recovered
@@ -3313,6 +3331,7 @@ class CompetitorPriceFeeder:
momo_name,
momo_price=momo_price,
existing_product_id=competitor_product_id,
**recovery_kwargs,
)
if recovered:
recovered_product, recovered_score, recovered_diagnostics = recovered
@@ -3595,6 +3614,7 @@ class CompetitorPriceFeeder:
source=source,
label="identity_v2 過期價格搜尋救援",
allow_missing_recovery=True,
bounded_recovery=True,
)
def run_retryable_candidate_revalidation(