fix: recalibrate saved recommendation snapshots
This commit is contained in:
@@ -451,6 +451,89 @@ def _finalize_confidence_score(
|
||||
|
||||
return round(_clamp(adjusted, floor, ceiling), 1)
|
||||
|
||||
|
||||
|
||||
def _market_tier_from_item(item: dict[str, Any]) -> str:
|
||||
market_type = str(item.get('market_type') or '')
|
||||
risk_level = str(item.get('risk_level') or '')
|
||||
if market_type == '跨場串關' or risk_level == 'parlay':
|
||||
return 'parlay'
|
||||
if market_type == '同場串關' or risk_level == 'sgp':
|
||||
return 'sgp'
|
||||
if market_type == '正確比分':
|
||||
return 'exact_score'
|
||||
if risk_level == 'speculative':
|
||||
return 'speculative'
|
||||
if market_type.startswith('大小球'):
|
||||
return 'totals'
|
||||
if market_type == '雙方進球':
|
||||
return 'btts'
|
||||
if market_type == '隊伍總進球':
|
||||
return 'team_total'
|
||||
return 'core'
|
||||
|
||||
|
||||
def recalibrate_daily_card_confidence_payload(card_payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Re-label stored pre-match snapshots with the current public confidence scale.
|
||||
|
||||
This intentionally does not create, remove, or reorder picks. It only prevents
|
||||
older saved snapshots from showing stale 0.0 or hard-capped template scores.
|
||||
"""
|
||||
|
||||
payload = dict(card_payload)
|
||||
bucket_names = ('safe_singles', 'high_risk_singles', 'safe_parlays', 'sgp_lotteries')
|
||||
changed_count = 0
|
||||
|
||||
for bucket_name in bucket_names:
|
||||
next_group: list[dict[str, Any]] = []
|
||||
for raw_item in payload.get(bucket_name, []) or []:
|
||||
if not isinstance(raw_item, dict):
|
||||
continue
|
||||
item = dict(raw_item)
|
||||
try:
|
||||
market_tier = _market_tier_from_item(item)
|
||||
risk_level = str(item.get('risk_level') or ('parlay' if market_tier == 'parlay' else 'sgp' if market_tier == 'sgp' else 'core'))
|
||||
has_market_odds = bool(item.get('has_market_odds'))
|
||||
target_odds = _safe_float(item.get('target_odds'), 1.0)
|
||||
true_prob = _safe_float(item.get('win_prob'), 0.0) / 100.0
|
||||
ev_percent = _safe_float(item.get('ev_percent'), 0.0)
|
||||
edge_percent = _safe_float(item.get('edge_percent'), 0.0)
|
||||
previous_score = _safe_float(item.get('confidence_score'), 0.0)
|
||||
next_score = _finalize_confidence_score(
|
||||
previous_score,
|
||||
match_id=str(item.get('match_id') or ''),
|
||||
market_type=str(item.get('market_type') or ''),
|
||||
selection=str(item.get('selection') or ''),
|
||||
recommendation=str(item.get('recommendation') or ''),
|
||||
risk_level=risk_level,
|
||||
market_tier=market_tier,
|
||||
data_quality=str(item.get('data_quality') or 'observed'),
|
||||
has_market_odds=has_market_odds,
|
||||
odds_source_kind=str(item.get('odds_source_kind') or 'market'),
|
||||
target_odds=target_odds,
|
||||
true_prob=true_prob,
|
||||
ev_percent=ev_percent,
|
||||
edge_percent=edge_percent,
|
||||
)
|
||||
if next_score != previous_score:
|
||||
changed_count += 1
|
||||
item['confidence_score'] = next_score
|
||||
item['confidence_band'] = _confidence_band(next_score, has_market_odds, risk_level)
|
||||
factors = list(item.get('confidence_factors') or [])
|
||||
if '信心分數已套用新版資料品質校準' not in factors:
|
||||
factors.append('信心分數已套用新版資料品質校準')
|
||||
item['confidence_factors'] = factors[:8]
|
||||
except Exception:
|
||||
pass
|
||||
next_group.append(item)
|
||||
payload[bucket_name] = next_group
|
||||
|
||||
if changed_count:
|
||||
quality_summary = dict(payload.get('data_quality_summary') or {})
|
||||
quality_summary['confidence_recalibrated_items'] = changed_count
|
||||
payload['data_quality_summary'] = quality_summary
|
||||
return payload
|
||||
|
||||
def _confidence_band(score: float, has_market_odds: bool, risk_level: str) -> str:
|
||||
if not has_market_odds:
|
||||
if score >= 64:
|
||||
|
||||
@@ -20,7 +20,7 @@ from pydantic import BaseModel, Field
|
||||
from redis.asyncio import Redis
|
||||
from .db.base import SessionFactory
|
||||
from .db.models import Bookmaker, DailyRecommendationSnapshot, Match, MatchStatus, OddsHistory, SmartMoneyFlow, Team, Venue
|
||||
from .analytics.daily_card_generator import generate_daily_card
|
||||
from .analytics.daily_card_generator import generate_daily_card, recalibrate_daily_card_confidence_payload
|
||||
from .analytics.localization import (
|
||||
localize_city,
|
||||
localize_country,
|
||||
@@ -1815,7 +1815,7 @@ async def _read_daily_recommendation_snapshot_payload(target_date: str) -> dict[
|
||||
snapshot = await session.get(DailyRecommendationSnapshot, snapshot_id)
|
||||
if snapshot is None or not snapshot.payload:
|
||||
return None
|
||||
payload = dict(snapshot.payload)
|
||||
payload = recalibrate_daily_card_confidence_payload(dict(snapshot.payload))
|
||||
payload.setdefault('snapshot_status', 'saved_snapshot')
|
||||
return payload
|
||||
|
||||
|
||||
Reference in New Issue
Block a user