This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user