diff --git a/messages/en.json b/messages/en.json index c6efb24..798130a 100644 --- a/messages/en.json +++ b/messages/en.json @@ -201,11 +201,11 @@ "missing_total_odds": "Over/Under odds are missing.", "missing_spread_odds": "Spread (Handicap) odds are missing.", "no_bet_conditions_met": "The algorithm could not find a safe/valuable bet for this match.", - "insufficient_play_score": "Play score is below the playability threshold.", + "insufficient_play_score": "Model signal is below the threshold.", "no_ev_edge_minimum_stake": "Passed safety gates but no mathematical edge — minimum stake applied.", "upset_risk_detected": "High upset risk detected, proceed with caution." }, - "ev-edge": "EV Edge", + "ev-edge": "Theoretical Edge", "implied-prob": "Market Probability", "model-prob": "Model Probability", "kelly-stake": "Kelly Stake", @@ -238,23 +238,23 @@ }, "ui": { "summary-title": "Prediction Summary", - "summary-info": "Shows what stands out first and then explains why it stands out.", - "main-recommendation": "Main Recommendation", + "summary-info": "Shows model signals and uncertainty in a conservative summary.", + "main-recommendation": "Highlighted Signal", "best-market-copy": "is the strongest option in this market.", "confidence-label": "Confidence", "odds-label": "Odds", - "edge-label": "Expected Advantage (Edge)", - "edge-info": "Edge is the gap between the model probability and the market probability. If it is positive, the model sees value in this price.", + "edge-label": "Theoretical Edge", + "edge-info": "The theoretical gap between model probability and market probability; it is not a guarantee or a certain profit expectation.", "stake-label": "Suggested Bet Size (Stake)", "stake-label-short": "Bet Size", "stake-info": "Stake is the suggested bet size. 2.0u means a 2-unit bet in your own bankroll plan.", - "play-score-label": "Playability Score", - "playability-label": "Playability", + "play-score-label": "Model Signal", + "playability-label": "Model signal", "quick-read": "Quick read", "lineup-source": "Lineup Source", "model-label": "Model", "engine-info": "Shows which components influence the prediction the most.", - "best-single-pick": "Best Single Pick", + "best-single-pick": "Strongest Signal", "alternative-markets": "Alternative Markets", "alternative-markets-info": "Options outside the main recommendation.", "alternative": "Alternative", diff --git a/messages/tr.json b/messages/tr.json index 3af689d..b8ace5b 100644 --- a/messages/tr.json +++ b/messages/tr.json @@ -202,10 +202,10 @@ "missing_total_odds": "Alt/Üst oranları eksik.", "missing_spread_odds": "Handikap oranları eksik.", "no_bet_conditions_met": "Algoritma bu maç için güvenli/değerli bir bahis önerisi bulamadı.", - "insufficient_play_score": "Oynanabilirlik skoru eşiğin altında kaldı.", + "insufficient_play_score": "Model sinyali eşiğin altında kaldı.", "no_ev_edge_minimum_stake": "Güvenlik kontrollerini geçti ancak matematik avantaj yok — minimum bahis uygulandı." }, - "ev-edge": "EV Edge", + "ev-edge": "Teorik Avantaj", "implied-prob": "Piyasa Olasılığı", "model-prob": "Model Olasılığı", "kelly-stake": "Kelly Bahis", @@ -238,23 +238,23 @@ }, "ui": { "summary-title": "Tahmin Özeti", - "summary-info": "Önce neyin oynanabileceğini, sonra bunun neden öne çıktığını gösterir.", - "main-recommendation": "Ana Öneri", + "summary-info": "Model sinyallerini ve belirsizlikleri sade şekilde gösterir.", + "main-recommendation": "Öne Çıkan Sinyal", "best-market-copy": "marketinde en güçlü seçim.", "confidence-label": "Güven", "odds-label": "Oran", - "edge-label": "Beklenen Avantaj (Edge)", - "edge-info": "Edge, model olasılığı ile piyasa olasılığı arasındaki farktır. Pozitifse model bu oranı avantajlı görüyor demektir.", + "edge-label": "Teorik Avantaj", + "edge-info": "Model olasılığı ile piyasa olasılığı arasındaki teorik farktır; tutma garantisi veya kesin kazanç beklentisi değildir.", "stake-label": "Önerilen Miktar (Stake)", "stake-label-short": "Bahis Miktarı", "stake-info": "Stake, bu bahis için önerilen bahis birimidir. 2.0u, kendi bankroll planınızdaki 2 birimlik bahis anlamına gelir.", - "play-score-label": "Oynanabilirlik Puanı", - "playability-label": "Oynanabilirlik", + "play-score-label": "Model Sinyali", + "playability-label": "Model sinyali", "quick-read": "Hızlı yorum", "lineup-source": "Kadronun Kaynağı", "model-label": "Model", "engine-info": "Tahmini en çok hangi bileşenlerin etkilediğini gösterir.", - "best-single-pick": "En İyi Tekli Seçim", + "best-single-pick": "En Güçlü Sinyal", "alternative-markets": "Alternatif Marketler", "alternative-markets-info": "Ana tahmin dışındaki seçenekler.", "alternative": "Alternatif", diff --git a/src/components/matches/prediction-card.tsx b/src/components/matches/prediction-card.tsx index 6da3f39..7ac87ad 100644 --- a/src/components/matches/prediction-card.tsx +++ b/src/components/matches/prediction-card.tsx @@ -49,7 +49,7 @@ interface PredictionCardProps { function formatReasonFallback(reason: string): string { if (reason.startsWith("risk:")) return formatReasonFallback(reason.slice(5)); const evMatch = reason.match(/^ev_edge_([+\-][\d.]+%)_grade_(\w)$/); - if (evMatch) return `Beklenen avantaj ${evMatch[1]} (Not ${evMatch[2]})`; + if (evMatch) return `Teorik avantaj sinyali: Not ${evMatch[2]}`; const negMatch = reason.match(/^negative_model_edge_([+\-][\d.]+)$/); if (negMatch) return `Model avantajı negatif (${negMatch[1]})`; const thresholdMatch = reason.match(/^below_market_edge_threshold_([+\-]?[\d.]+)$/); @@ -90,6 +90,24 @@ function formatProbability(value?: number, digits = 1): string { return `${(value * 100).toFixed(digits)}%`; } +function formatSignalScore(value?: number): string { + if (value === undefined || value === null || Number.isNaN(value)) return "-"; + return `${Math.max(0, Math.min(100, value)).toFixed(0)}/100`; +} + +function formatEdgeSignal(value?: number): string { + if (value === undefined || value === null || Number.isNaN(value)) return "-"; + return `${value > 0 ? "+" : ""}${formatPercent(value * 100, 1)}`; +} + +function getEdgePalette(value?: number): string { + if (value === undefined || value === null || Number.isNaN(value)) return "gray"; + if (value <= 0) return "red"; + if (value < 0.08) return "yellow"; + if (value < 0.15) return "orange"; + return "purple"; +} + function formatOdds(value?: number | null): string { if (!value || value <= 1.01) return "-"; return value.toFixed(2); @@ -485,9 +503,8 @@ function PickCard({ {getConfidenceBandLabel(pick.confidence_interval?.band)} - 0 ? "green" : "red"} variant="subtle"> - EV {pick.ev_edge > 0 ? "+" : ""} - {formatPercent(pick.ev_edge * 100, 1)} + + Teorik avantaj {formatEdgeSignal(pick.ev_edge)} @@ -498,7 +515,7 @@ function PickCard({ label={labels.recommendedStake} value={formatUnits(pick.stake_units || stakeFallback)} /> - + - {formatPercent(pick.play_score, 1)} + {formatSignalScore(pick.play_score)} 0 ? "green.400" : "orange.400"} + color={pick.ev_edge > 0 ? "blue.400" : "orange.400"} trackBg={trackBg} /> @@ -596,9 +613,8 @@ function SummaryTable({ {formatOdds(item.odds)} - 0 ? "green.500" : "red.500"} fontWeight="semibold"> - {item.ev_edge > 0 ? "+" : ""} - {formatPercent(item.ev_edge * 100, 1)} + + {formatEdgeSignal(item.ev_edge)} {formatPercent(item.calibrated_confidence, 0)} @@ -867,9 +883,23 @@ export default function PredictionCard({ prediction }: PredictionCardProps) { title={uiText("summary-title", "Tahmin Ozeti")} info={uiText( "summary-info", - "Kullanicinin once neyi oynayacagini, sonra nedenini anlamasi icin sade ozet.", + "Model sinyallerini ve belirsizlikleri sade sekilde gosterir.", )} /> + + + + + Bu bir model sinyalidir; kesin sonuç, garanti veya tutma yüzdesi değildir. Sinyal puanı maç içi varyans, kadro ve veri kalitesi nedeniyle yanılabilir. + + + {recommendedPick ? ( @@ -877,7 +907,7 @@ export default function PredictionCard({ prediction }: PredictionCardProps) { - {uiText("main-recommendation", "Ana Oneri")} + {uiText("main-recommendation", "Öne Çıkan Sinyal")} {recommendedPick.pick} @@ -903,13 +933,13 @@ export default function PredictionCard({ prediction }: PredictionCardProps) { 0 ? "+" : ""}${formatPercent(recommendedPick.ev_edge * 100, 1)}`} + label={uiText("edge-label", "Teorik Avantaj")} + value={formatEdgeSignal(recommendedPick.ev_edge)} helper={uiText( "edge-info", - "Edge, model olasiligi ile piyasa olasiligi arasindaki farktir. Pozitifse model bu orani avantajli buluyor demektir.", + "Model olasiligi ile piyasa olasiligi arasindaki teorik farktir; tutma garantisi veya kesin kazanc beklentisi degildir.", )} - accent={recommendedPick.ev_edge > 0 ? "green.500" : "red.500"} + accent={`${getEdgePalette(recommendedPick.ev_edge)}.500`} /> ) : null} @@ -1064,8 +1094,8 @@ export default function PredictionCard({ prediction }: PredictionCardProps) { confidence: uiText("confidence-label", "Guven"), odds: uiText("odds-label", "Oran"), recommendedStake: uiText("stake-label-short", "Stake"), - playScore: uiText("play-score-label", "Play Score"), - playability: uiText("playability-label", "Oynanabilirlik"), + playScore: uiText("play-score-label", "Model Sinyali"), + playability: uiText("playability-label", "Model sinyali"), }} /> ))} diff --git a/src/lib/api/predictions/types.ts b/src/lib/api/predictions/types.ts index 5aff377..6d6e975 100644 --- a/src/lib/api/predictions/types.ts +++ b/src/lib/api/predictions/types.ts @@ -69,6 +69,13 @@ export interface MatchPickDto { edge: number; ev_edge: number; implied_prob: number; + model_probability?: number; + model_edge?: number; + calibrated_probability?: number; + odds_band_probability?: number; + odds_band_sample?: number; + odds_band_edge?: number; + odds_band_aligned?: boolean; play_score: number; playable: boolean; bet_grade: BetGrade; @@ -76,6 +83,7 @@ export interface MatchPickDto { decision_reasons: string[]; confidence_interval?: ConfidenceIntervalDto; signal_tier?: SignalTier; + is_guaranteed?: boolean; } export interface MatchBetAdviceDto { @@ -99,6 +107,13 @@ export interface MatchBetSummaryItemDto { play_score: number; ev_edge: number; implied_prob: number; + model_probability?: number; + model_edge?: number; + calibrated_probability?: number; + odds_band_probability?: number; + odds_band_sample?: number; + odds_band_edge?: number; + odds_band_aligned?: boolean; odds: number; reasons: string[]; confidence_interval?: ConfidenceIntervalDto;