126 lines
4.1 KiB
Python
126 lines
4.1 KiB
Python
"""
|
|
Pydantic v2 response schemas for the V2 Betting Engine.
|
|
Strictly mirrors the NestJS DTO contract for SingleMatchPredictionPackage.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
# ── Sub-models ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
class MatchInfo(BaseModel):
|
|
match_id: str
|
|
match_name: str = ""
|
|
home_team: str = ""
|
|
away_team: str = ""
|
|
league: str = ""
|
|
match_date_ms: int = 0
|
|
|
|
|
|
class DataQuality(BaseModel):
|
|
label: str = Field(default="MEDIUM", description="HIGH | MEDIUM | LOW")
|
|
score: float = Field(default=0.5, ge=0.0, le=1.0)
|
|
flags: list[str] = Field(default_factory=list)
|
|
home_lineup_count: int = 0
|
|
away_lineup_count: int = 0
|
|
|
|
|
|
class RiskAssessment(BaseModel):
|
|
level: str = Field(default="MEDIUM", description="LOW | MEDIUM | HIGH | EXTREME")
|
|
score: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
is_surprise_risk: bool = False
|
|
surprise_type: str | None = None
|
|
warnings: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class PickDetail(BaseModel):
|
|
market: str = Field(..., description="MS, OU25, BTTS, DC, HT, HTFT, etc.")
|
|
pick: str = Field(..., description="1, X, 2, Over, Under, Yes, No, 1/1, etc.")
|
|
probability: float = Field(..., ge=0.0, le=1.0)
|
|
confidence: float = Field(default=0.0, description="Percentage 0-100")
|
|
odds: float | None = Field(default=None, gt=0.0)
|
|
raw_confidence: float = 0.0
|
|
calibrated_confidence: float = 0.0
|
|
min_required_confidence: float = 0.0
|
|
edge: float = Field(default=0.0, description="Model prob minus implied prob")
|
|
play_score: float = Field(default=0.0, ge=0.0, le=100.0)
|
|
playable: bool = False
|
|
bet_grade: str = Field(default="PASS", description="A | B | C | PASS")
|
|
stake_units: float = Field(default=0.0, ge=0.0)
|
|
decision_reasons: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class BetAdvice(BaseModel):
|
|
playable: bool = False
|
|
suggested_stake_units: float = 0.0
|
|
reason: str = "no_playable_pick"
|
|
|
|
|
|
class BetSummaryRow(BaseModel):
|
|
market: str
|
|
pick: str
|
|
raw_confidence: float = 0.0
|
|
calibrated_confidence: float = 0.0
|
|
bet_grade: str = "PASS"
|
|
playable: bool = False
|
|
stake_units: float = 0.0
|
|
play_score: float = 0.0
|
|
reasons: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ScoreScenario(BaseModel):
|
|
score: str
|
|
prob: float
|
|
|
|
|
|
class ScorePrediction(BaseModel):
|
|
ft: str = "0-0"
|
|
ht: str = "0-0"
|
|
xg_home: float = 0.0
|
|
xg_away: float = 0.0
|
|
xg_total: float = 0.0
|
|
|
|
|
|
class EngineBreakdown(BaseModel):
|
|
team: float = 0.0
|
|
player: float = 0.0
|
|
odds: float = 0.0
|
|
referee: float = 0.0
|
|
|
|
|
|
class MarketProbs(BaseModel):
|
|
pick: str = ""
|
|
confidence: float = 0.0
|
|
probs: dict[str, float] = Field(default_factory=dict)
|
|
|
|
|
|
# ── Root Response ───────────────────────────────────────────────────────────
|
|
|
|
|
|
class PredictionResponse(BaseModel):
|
|
"""
|
|
Root API contract. Every field matches the NestJS
|
|
`SingleMatchPredictionPackage` DTO exactly.
|
|
"""
|
|
|
|
model_version: str = "v2.betting_engine"
|
|
match_info: MatchInfo
|
|
data_quality: DataQuality = Field(default_factory=DataQuality)
|
|
risk: RiskAssessment = Field(default_factory=RiskAssessment)
|
|
engine_breakdown: EngineBreakdown = Field(default_factory=EngineBreakdown)
|
|
main_pick: PickDetail | None = None
|
|
value_pick: PickDetail | None = None
|
|
bet_advice: BetAdvice = Field(default_factory=BetAdvice)
|
|
bet_summary: list[BetSummaryRow] = Field(default_factory=list)
|
|
supporting_picks: list[PickDetail] = Field(default_factory=list)
|
|
aggressive_pick: PickDetail | None = None
|
|
scenario_top5: list[ScoreScenario] = Field(default_factory=list)
|
|
score_prediction: ScorePrediction = Field(default_factory=ScorePrediction)
|
|
market_board: dict[str, Any] = Field(default_factory=dict)
|
|
reasoning_factors: list[str] = Field(default_factory=list)
|