fix: calibrate recommendation confidence scores
All checks were successful
2026 World Cup Quant Platform - Production Deployment / Code Quality, Security Gate & Testing (push) Successful in 4m3s
2026 World Cup Quant Platform - Production Deployment / Deploy to Production VM via Gitea CD (push) Successful in 1m17s

This commit is contained in:
wooo
2026-06-18 15:41:27 +08:00
parent 1e082333af
commit f767bb2fbb

View File

@@ -354,6 +354,103 @@ def _confidence_score(
return round(_clamp(raw, 0, 96), 1)
def _stable_confidence_jitter(*parts: object) -> float:
"""Deterministic tie-breaker so equal model priors do not all display the same score."""
seed = '|'.join(str(part or '') for part in parts)
checksum = sum((index + 1) * ord(char) for index, char in enumerate(seed))
return ((checksum % 49) - 24) / 10.0
def _finalize_confidence_score(
score: float,
*,
match_id: str,
market_type: str,
selection: str,
recommendation: str,
risk_level: str,
market_tier: str,
data_quality: str,
has_market_odds: bool,
odds_source_kind: str,
target_odds: float,
true_prob: float,
ev_percent: float,
edge_percent: float,
) -> float:
"""Turn raw model score into a public-facing confidence rating.
The score is not a hit probability. It is a review priority that must reflect
source quality, market availability, play volatility, and model/value strength.
"""
adjusted = float(score)
if odds_source_kind == 'conditional_threshold':
adjusted -= 3.2
elif odds_source_kind == 'reference_market':
adjusted -= 1.7
elif odds_source_kind in {'mixed_market_source', 'single_provider_market'}:
adjusted -= 2.4
elif odds_source_kind in {'market', 'multi_book_market'}:
adjusted += 2.0
if data_quality == 'fallback_prior':
adjusted -= 4.5
elif data_quality == 'rank_elo_prior':
adjusted -= 1.6
elif data_quality == 'observed':
adjusted += 1.5
if not has_market_odds:
adjusted -= 1.8
if risk_level in {'speculative', 'sgp'} or market_tier in {'exact_score', 'speculative', 'sgp'}:
adjusted -= 3.5
elif risk_level == 'parlay' or market_tier == 'parlay':
adjusted -= 1.8
adjusted -= _clamp(max(target_odds - 3.2, 0.0) * 1.4, 0.0, 6.0)
adjusted += _clamp((true_prob - 0.56) * 10.0, -2.5, 3.0)
adjusted += _clamp((ev_percent - 3.0) * 0.10, -1.2, 2.6)
adjusted += _clamp((edge_percent - 2.0) * 0.08, -1.0, 2.4)
adjusted += _stable_confidence_jitter(match_id, market_type, selection, recommendation, target_odds)
if market_tier in {'exact_score', 'speculative', 'sgp'} or risk_level in {'speculative', 'sgp'}:
floor = 12.0
elif risk_level == 'parlay' or market_tier == 'parlay':
floor = 18.0
else:
floor = 24.0
if has_market_odds:
ceiling = 92.0
elif odds_source_kind == 'reference_market':
ceiling = 66.5
elif odds_source_kind == 'conditional_threshold':
ceiling = 61.5
else:
ceiling = 64.0
if data_quality == 'fallback_prior':
ceiling = min(ceiling, 56.0)
elif data_quality == 'rank_elo_prior':
ceiling = min(ceiling, 66.0)
if market_tier in {'exact_score', 'speculative'}:
ceiling = min(ceiling, 46.0)
elif market_tier == 'sgp':
ceiling = min(ceiling, 44.0)
elif market_tier == 'parlay':
ceiling = min(ceiling, 54.0 if not has_market_odds else 68.0)
if ceiling < floor:
floor = max(8.0, ceiling - 8.0)
return round(_clamp(adjusted, floor, ceiling), 1)
def _confidence_band(score: float, has_market_odds: bool, risk_level: str) -> str:
if not has_market_odds:
if score >= 64:
@@ -584,6 +681,22 @@ def _build_pick(
has_market_odds=has_market_odds,
market_tier=market_tier,
)
confidence = _finalize_confidence_score(
confidence,
match_id=match_id,
market_type=market_type,
selection=selection,
recommendation=recommendation,
risk_level=risk_level,
market_tier=market_tier,
data_quality=data_quality,
has_market_odds=has_market_odds,
odds_source_kind=odds_source_kind,
target_odds=odds,
true_prob=true_prob,
ev_percent=ev,
edge_percent=edge,
)
implied = (1.0 / odds) * 100
fair_note = ''
enriched_checks = list(data_checks)
@@ -691,6 +804,22 @@ def _build_conditional_pick(
has_market_odds=has_market_odds,
market_tier=market_tier,
)
confidence = _finalize_confidence_score(
confidence,
match_id=match_id,
market_type=market_type,
selection=selection,
recommendation=recommendation,
risk_level=risk_level,
market_tier=market_tier,
data_quality=data_quality,
has_market_odds=has_market_odds,
odds_source_kind=odds_source_kind,
target_odds=target_odds,
true_prob=true_prob,
ev_percent=min_ev,
edge_percent=edge,
)
quality_note = _quality_note(data_quality, has_market_odds)
confidence_band = _confidence_band(confidence, has_market_odds, risk_level)
confidence_factors = _confidence_factors(
@@ -830,6 +959,22 @@ def _build_cross_match_parlays(safe_singles: list[dict[str, Any]]) -> list[dict[
has_market_odds=combo_has_market_odds,
market_tier='parlay',
)
confidence = _finalize_confidence_score(
confidence,
match_id=f'PARLAY-CROSS-MATCH-{size}-{len(parlays) + 1}',
market_type='跨場串關',
selection=f'{size} 串 1',
recommendation='SAFE_PARLAY',
risk_level='parlay',
market_tier='parlay',
data_quality=combo_data_quality,
has_market_odds=combo_has_market_odds,
odds_source_kind=combo_source_kind,
target_odds=target_combo_odds,
true_prob=combined_prob,
ev_percent=ev,
edge_percent=edge,
)
parlays.append(
{
'match_id': f'PARLAY-CROSS-MATCH-{size}-{len(parlays) + 1}',
@@ -939,6 +1084,22 @@ def _build_same_game_parlays(picks_by_match: dict[str, list[dict[str, Any]]]) ->
has_market_odds=sgp_has_market_odds,
market_tier='sgp',
)
confidence = _finalize_confidence_score(
confidence,
match_id=match_id,
market_type='同場串關',
selection=f"{first['selection']} + {second['selection']}",
recommendation='SGP_LOTTERY',
risk_level='sgp',
market_tier='sgp',
data_quality=sgp_data_quality,
has_market_odds=sgp_has_market_odds,
odds_source_kind=sgp_source_kind,
target_odds=target_combo_odds,
true_prob=combo_prob,
ev_percent=ev,
edge_percent=edge,
)
sgps.append(
{