@@ -0,0 +1,174 @@
|
||||
"""Utility Mixin — generic helpers (safe_float, label normalisation, JSON parsing).
|
||||
|
||||
Auto-extracted mixin module — split from services/single_match_orchestrator.py.
|
||||
All methods here are composed into SingleMatchOrchestrator via inheritance.
|
||||
`self` attributes (self.dsn, self.enrichment, self.v25_predictor, etc.) are
|
||||
initialised in the main __init__.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import math
|
||||
import os
|
||||
import pickle
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, overload
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
from data.db import get_clean_dsn
|
||||
from schemas.prediction import FullMatchPrediction
|
||||
from schemas.match_data import MatchData
|
||||
from models.v25_ensemble import V25Predictor, get_v25_predictor
|
||||
try:
|
||||
from models.v27_predictor import V27Predictor, compute_divergence, compute_value_edge
|
||||
except ImportError:
|
||||
class V27Predictor: # type: ignore[no-redef]
|
||||
def __init__(self): self.models = {}
|
||||
def load_models(self): return False
|
||||
def predict_all(self, features): return {}
|
||||
def compute_divergence(*args, **kwargs):
|
||||
return {}
|
||||
def compute_value_edge(*args, **kwargs):
|
||||
return {}
|
||||
from features.odds_band_analyzer import OddsBandAnalyzer
|
||||
try:
|
||||
from models.basketball_v25 import (
|
||||
BasketballMatchPrediction,
|
||||
get_basketball_v25_predictor,
|
||||
)
|
||||
except ImportError:
|
||||
BasketballMatchPrediction = Any # type: ignore[misc]
|
||||
def get_basketball_v25_predictor() -> Any:
|
||||
raise ImportError("Basketball predictor is not available")
|
||||
from core.engines.player_predictor import PlayerPrediction, get_player_predictor
|
||||
from services.feature_enrichment import FeatureEnrichmentService
|
||||
from services.betting_brain import BettingBrain
|
||||
from services.v26_shadow_engine import V26ShadowEngine, get_v26_shadow_engine
|
||||
from services.match_commentary import generate_match_commentary
|
||||
from utils.top_leagues import load_top_league_ids
|
||||
from utils.league_reliability import load_league_reliability
|
||||
from config.config_loader import build_threshold_dict, get_threshold_default
|
||||
from models.calibration import get_calibrator
|
||||
|
||||
|
||||
class UtilsMixin:
|
||||
@staticmethod
|
||||
@overload
|
||||
def _safe_float(value: Any, default: float) -> float: ...
|
||||
|
||||
@staticmethod
|
||||
@overload
|
||||
def _safe_float(value: Any, default: None = ...) -> Optional[float]: ...
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(value: Any, default: Optional[float] = None) -> Optional[float]:
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(value: Any, default: float = 0.0) -> float:
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def _calibrator_key(market: str, pick: str) -> Optional[str]:
|
||||
"""Map (market, pick) → trained-calibrator key in models/calibration."""
|
||||
m = (market or "").upper()
|
||||
p = (pick or "").strip().casefold()
|
||||
if m == "MS":
|
||||
if p == "1":
|
||||
return "ms_home"
|
||||
if p == "x" or p == "0":
|
||||
return "ms_draw"
|
||||
if p == "2":
|
||||
return "ms_away"
|
||||
return None
|
||||
if m == "DC":
|
||||
return "dc"
|
||||
if m == "OU15" and ("over" in p or "üst" in p or "ust" in p):
|
||||
return "ou15"
|
||||
if m == "OU25" and ("over" in p or "üst" in p or "ust" in p):
|
||||
return "ou25"
|
||||
if m == "OU35" and ("over" in p or "üst" in p or "ust" in p):
|
||||
return "ou35"
|
||||
if m == "BTTS" and ("yes" in p or "var" in p):
|
||||
return "btts"
|
||||
if m == "HT":
|
||||
if p == "1":
|
||||
return "ht_home"
|
||||
if p == "x" or p == "0":
|
||||
return "ht_draw"
|
||||
if p == "2":
|
||||
return "ht_away"
|
||||
return None
|
||||
if m == "HTFT":
|
||||
return "ht_ft"
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _confidence_label(score: float) -> Tuple[str, str]:
|
||||
"""Turkish UX label + interpretation for a 0-100 confidence score."""
|
||||
if score >= 75:
|
||||
return "YUKSEK", "Bu sinyal güçlü ve güvenilir"
|
||||
if score >= 60:
|
||||
return "ORTA", "Sinyal makul, çelişen veri yok"
|
||||
if score >= 45:
|
||||
return "DUSUK", "Sinyal zayıf, dikkatli yorumla"
|
||||
return "COK_DUSUK", "Veri yetersiz veya çelişkili — bu motoru bu maç için ihmal et"
|
||||
|
||||
@staticmethod
|
||||
def _to_float(value: Any, default: float) -> float:
|
||||
try:
|
||||
if value is None:
|
||||
return default
|
||||
return float(value)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def _normalize_text(value: Any) -> str:
|
||||
text = str(value or "").casefold().replace("i̇", "i")
|
||||
return " ".join(text.split())
|
||||
|
||||
def _selection_value(
|
||||
self,
|
||||
selections: Dict[str, Any],
|
||||
aliases: Tuple[str, ...],
|
||||
default: float,
|
||||
) -> float:
|
||||
if not isinstance(selections, dict):
|
||||
return default
|
||||
|
||||
normalized_aliases = {self._normalize_text(alias) for alias in aliases}
|
||||
for key, value in selections.items():
|
||||
key_norm = self._normalize_text(key)
|
||||
if key_norm in normalized_aliases:
|
||||
return self._to_float(value, default)
|
||||
|
||||
# Secondary match for entries like "2,5 Üst" or "Toplam Alt"
|
||||
for key, value in selections.items():
|
||||
key_norm = self._normalize_text(key)
|
||||
if any(alias in key_norm for alias in normalized_aliases):
|
||||
return self._to_float(value, default)
|
||||
|
||||
return default
|
||||
|
||||
def _parse_json_dict(self, payload: Any) -> Optional[Dict[str, Any]]:
|
||||
if isinstance(payload, str):
|
||||
try:
|
||||
payload = json.loads(payload)
|
||||
except Exception:
|
||||
return None
|
||||
return payload if isinstance(payload, dict) else None
|
||||
Reference in New Issue
Block a user