gg
Deploy Iddaai Frontend / build-and-deploy (push) Successful in 4m15s

This commit is contained in:
2026-05-06 17:50:02 +03:00
parent 4df27e3e6d
commit 6dadc5f613
4 changed files with 84 additions and 39 deletions
+9 -9
View File
@@ -201,11 +201,11 @@
"missing_total_odds": "Over/Under odds are missing.", "missing_total_odds": "Over/Under odds are missing.",
"missing_spread_odds": "Spread (Handicap) 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.", "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.", "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." "upset_risk_detected": "High upset risk detected, proceed with caution."
}, },
"ev-edge": "EV Edge", "ev-edge": "Theoretical Edge",
"implied-prob": "Market Probability", "implied-prob": "Market Probability",
"model-prob": "Model Probability", "model-prob": "Model Probability",
"kelly-stake": "Kelly Stake", "kelly-stake": "Kelly Stake",
@@ -238,23 +238,23 @@
}, },
"ui": { "ui": {
"summary-title": "Prediction Summary", "summary-title": "Prediction Summary",
"summary-info": "Shows what stands out first and then explains why it stands out.", "summary-info": "Shows model signals and uncertainty in a conservative summary.",
"main-recommendation": "Main Recommendation", "main-recommendation": "Highlighted Signal",
"best-market-copy": "is the strongest option in this market.", "best-market-copy": "is the strongest option in this market.",
"confidence-label": "Confidence", "confidence-label": "Confidence",
"odds-label": "Odds", "odds-label": "Odds",
"edge-label": "Expected Advantage (Edge)", "edge-label": "Theoretical 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-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": "Suggested Bet Size (Stake)",
"stake-label-short": "Bet Size", "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.", "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", "play-score-label": "Model Signal",
"playability-label": "Playability", "playability-label": "Model signal",
"quick-read": "Quick read", "quick-read": "Quick read",
"lineup-source": "Lineup Source", "lineup-source": "Lineup Source",
"model-label": "Model", "model-label": "Model",
"engine-info": "Shows which components influence the prediction the most.", "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": "Alternative Markets",
"alternative-markets-info": "Options outside the main recommendation.", "alternative-markets-info": "Options outside the main recommendation.",
"alternative": "Alternative", "alternative": "Alternative",
+9 -9
View File
@@ -202,10 +202,10 @@
"missing_total_odds": "Alt/Üst oranları eksik.", "missing_total_odds": "Alt/Üst oranları eksik.",
"missing_spread_odds": "Handikap 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ı.", "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ı." "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ığı", "implied-prob": "Piyasa Olasılığı",
"model-prob": "Model Olasılığı", "model-prob": "Model Olasılığı",
"kelly-stake": "Kelly Bahis", "kelly-stake": "Kelly Bahis",
@@ -238,23 +238,23 @@
}, },
"ui": { "ui": {
"summary-title": "Tahmin Özeti", "summary-title": "Tahmin Özeti",
"summary-info": "Önce neyin oynanabileceğini, sonra bunun neden öne çıktığını gösterir.", "summary-info": "Model sinyallerini ve belirsizlikleri sade şekilde gösterir.",
"main-recommendation": "Ana Öneri", "main-recommendation": "Öne Çıkan Sinyal",
"best-market-copy": "marketinde en güçlü seçim.", "best-market-copy": "marketinde en güçlü seçim.",
"confidence-label": "Güven", "confidence-label": "Güven",
"odds-label": "Oran", "odds-label": "Oran",
"edge-label": "Beklenen Avantaj (Edge)", "edge-label": "Teorik Avantaj",
"edge-info": "Edge, model olasılığı ile piyasa olasılığı arasındaki farktır. Pozitifse model bu oranı avantajlı görüyor demektir.", "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": "Önerilen Miktar (Stake)",
"stake-label-short": "Bahis Miktarı", "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.", "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ı", "play-score-label": "Model Sinyali",
"playability-label": "Oynanabilirlik", "playability-label": "Model sinyali",
"quick-read": "Hızlı yorum", "quick-read": "Hızlı yorum",
"lineup-source": "Kadronun Kaynağı", "lineup-source": "Kadronun Kaynağı",
"model-label": "Model", "model-label": "Model",
"engine-info": "Tahmini en çok hangi bileşenlerin etkilediğini gösterir.", "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": "Alternatif Marketler",
"alternative-markets-info": "Ana tahmin dışındaki seçenekler.", "alternative-markets-info": "Ana tahmin dışındaki seçenekler.",
"alternative": "Alternatif", "alternative": "Alternatif",
+51 -21
View File
@@ -49,7 +49,7 @@ interface PredictionCardProps {
function formatReasonFallback(reason: string): string { function formatReasonFallback(reason: string): string {
if (reason.startsWith("risk:")) return formatReasonFallback(reason.slice(5)); if (reason.startsWith("risk:")) return formatReasonFallback(reason.slice(5));
const evMatch = reason.match(/^ev_edge_([+\-][\d.]+%)_grade_(\w)$/); 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.]+)$/); const negMatch = reason.match(/^negative_model_edge_([+\-][\d.]+)$/);
if (negMatch) return `Model avantajı negatif (${negMatch[1]})`; if (negMatch) return `Model avantajı negatif (${negMatch[1]})`;
const thresholdMatch = reason.match(/^below_market_edge_threshold_([+\-]?[\d.]+)$/); 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)}%`; 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 { function formatOdds(value?: number | null): string {
if (!value || value <= 1.01) return "-"; if (!value || value <= 1.01) return "-";
return value.toFixed(2); return value.toFixed(2);
@@ -485,9 +503,8 @@ function PickCard({
<Badge colorPalette={confidenceBandPalette} variant="subtle"> <Badge colorPalette={confidenceBandPalette} variant="subtle">
{getConfidenceBandLabel(pick.confidence_interval?.band)} {getConfidenceBandLabel(pick.confidence_interval?.band)}
</Badge> </Badge>
<Badge colorPalette={pick.ev_edge > 0 ? "green" : "red"} variant="subtle"> <Badge colorPalette={getEdgePalette(pick.ev_edge)} variant="subtle">
EV {pick.ev_edge > 0 ? "+" : ""} Teorik avantaj {formatEdgeSignal(pick.ev_edge)}
{formatPercent(pick.ev_edge * 100, 1)}
</Badge> </Badge>
</HStack> </HStack>
</VStack> </VStack>
@@ -498,7 +515,7 @@ function PickCard({
label={labels.recommendedStake} label={labels.recommendedStake}
value={formatUnits(pick.stake_units || stakeFallback)} value={formatUnits(pick.stake_units || stakeFallback)}
/> />
<MetricTile label={labels.playScore} value={formatPercent(pick.play_score, 0)} /> <MetricTile label={labels.playScore} value={formatSignalScore(pick.play_score)} />
<MetricTile label="Guven Araligi" value={formatInterval(pick.confidence_interval)} /> <MetricTile label="Guven Araligi" value={formatInterval(pick.confidence_interval)} />
<MetricTile <MetricTile
label="Band" label="Band"
@@ -514,12 +531,12 @@ function PickCard({
{labels.playability} {labels.playability}
</Text> </Text>
<Text fontSize="sm" color="fg.muted"> <Text fontSize="sm" color="fg.muted">
{formatPercent(pick.play_score, 1)} {formatSignalScore(pick.play_score)}
</Text> </Text>
</HStack> </HStack>
<Bar <Bar
value={pick.play_score} value={pick.play_score}
color={pick.ev_edge > 0 ? "green.400" : "orange.400"} color={pick.ev_edge > 0 ? "blue.400" : "orange.400"}
trackBg={trackBg} trackBg={trackBg}
/> />
</Box> </Box>
@@ -596,9 +613,8 @@ function SummaryTable({
</HStack> </HStack>
<HStack gap={5} fontSize="sm"> <HStack gap={5} fontSize="sm">
<Text minW="48px">{formatOdds(item.odds)}</Text> <Text minW="48px">{formatOdds(item.odds)}</Text>
<Text minW="68px" color={item.ev_edge > 0 ? "green.500" : "red.500"} fontWeight="semibold"> <Text minW="96px" color={`${getEdgePalette(item.ev_edge)}.500`} fontWeight="semibold">
{item.ev_edge > 0 ? "+" : ""} {formatEdgeSignal(item.ev_edge)}
{formatPercent(item.ev_edge * 100, 1)}
</Text> </Text>
<Text minW="48px">{formatPercent(item.calibrated_confidence, 0)}</Text> <Text minW="48px">{formatPercent(item.calibrated_confidence, 0)}</Text>
<Badge colorPalette={getConfidenceBandPalette(item.confidence_interval?.band)} variant="subtle"> <Badge colorPalette={getConfidenceBandPalette(item.confidence_interval?.band)} variant="subtle">
@@ -867,9 +883,23 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
title={uiText("summary-title", "Tahmin Ozeti")} title={uiText("summary-title", "Tahmin Ozeti")}
info={uiText( info={uiText(
"summary-info", "summary-info",
"Kullanicinin once neyi oynayacagini, sonra nedenini anlamasi icin sade ozet.", "Model sinyallerini ve belirsizlikleri sade sekilde gosterir.",
)} )}
/> />
<Box
p={3}
bg={useColorModeValue("orange.50", "orange.950")}
borderWidth="1px"
borderColor={useColorModeValue("orange.200", "orange.800")}
borderRadius="xl"
>
<HStack align="start" gap={2}>
<Icon as={LuShieldAlert} boxSize={4.5} color="orange.500" mt={0.5} />
<Text fontSize="sm" color="fg.muted" lineHeight="tall">
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.
</Text>
</HStack>
</Box>
{recommendedPick ? ( {recommendedPick ? (
<Grid templateColumns={{ base: "1fr", xl: "1.4fr 1fr" }} gap={4}> <Grid templateColumns={{ base: "1fr", xl: "1.4fr 1fr" }} gap={4}>
@@ -877,7 +907,7 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
<HStack justify="space-between" align="start" mb={4}> <HStack justify="space-between" align="start" mb={4}>
<VStack align="start" gap={2}> <VStack align="start" gap={2}>
<Badge colorPalette="green" variant="solid" borderRadius="full"> <Badge colorPalette="green" variant="solid" borderRadius="full">
{uiText("main-recommendation", "Ana Oneri")} {uiText("main-recommendation", "Öne Çıkan Sinyal")}
</Badge> </Badge>
<Text fontSize="2xl" fontWeight="bold"> <Text fontSize="2xl" fontWeight="bold">
{recommendedPick.pick} {recommendedPick.pick}
@@ -903,13 +933,13 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
<MetricTile label={uiText("odds-label", "Oran")} value={formatOdds(recommendedPick.odds)} /> <MetricTile label={uiText("odds-label", "Oran")} value={formatOdds(recommendedPick.odds)} />
<MetricTile label="Guven Araligi" value={formatInterval(recommendedPick.confidence_interval)} /> <MetricTile label="Guven Araligi" value={formatInterval(recommendedPick.confidence_interval)} />
<MetricTile <MetricTile
label={uiText("edge-label", "Beklenen Avantaj (Edge)")} label={uiText("edge-label", "Teorik Avantaj")}
value={`${recommendedPick.ev_edge > 0 ? "+" : ""}${formatPercent(recommendedPick.ev_edge * 100, 1)}`} value={formatEdgeSignal(recommendedPick.ev_edge)}
helper={uiText( helper={uiText(
"edge-info", "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`}
/> />
<MetricTile <MetricTile
label={uiText("stake-label", "Onerilen Miktar (Stake)")} label={uiText("stake-label", "Onerilen Miktar (Stake)")}
@@ -1028,7 +1058,7 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
{recommendedPick ? ( {recommendedPick ? (
<PickCard <PickCard
pick={recommendedPick} pick={recommendedPick}
title={uiText("best-single-pick", "En iyi tekli secim")} title={uiText("best-single-pick", "En güçlü sinyal")}
resolveReason={resolveReason} resolveReason={resolveReason}
palette="green" palette="green"
stakeFallback={prediction.bet_advice.suggested_stake_units} stakeFallback={prediction.bet_advice.suggested_stake_units}
@@ -1037,8 +1067,8 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
confidence: uiText("confidence-label", "Guven"), confidence: uiText("confidence-label", "Guven"),
odds: uiText("odds-label", "Oran"), odds: uiText("odds-label", "Oran"),
recommendedStake: uiText("stake-label-short", "Stake"), recommendedStake: uiText("stake-label-short", "Stake"),
playScore: uiText("play-score-label", "Play Score"), playScore: uiText("play-score-label", "Model Sinyali"),
playability: uiText("playability-label", "Oynanabilirlik"), playability: uiText("playability-label", "Model sinyali"),
}} }}
/> />
) : null} ) : null}
@@ -1064,8 +1094,8 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
confidence: uiText("confidence-label", "Guven"), confidence: uiText("confidence-label", "Guven"),
odds: uiText("odds-label", "Oran"), odds: uiText("odds-label", "Oran"),
recommendedStake: uiText("stake-label-short", "Stake"), recommendedStake: uiText("stake-label-short", "Stake"),
playScore: uiText("play-score-label", "Play Score"), playScore: uiText("play-score-label", "Model Sinyali"),
playability: uiText("playability-label", "Oynanabilirlik"), playability: uiText("playability-label", "Model sinyali"),
}} }}
/> />
))} ))}
+15
View File
@@ -69,6 +69,13 @@ export interface MatchPickDto {
edge: number; edge: number;
ev_edge: number; ev_edge: number;
implied_prob: 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; play_score: number;
playable: boolean; playable: boolean;
bet_grade: BetGrade; bet_grade: BetGrade;
@@ -76,6 +83,7 @@ export interface MatchPickDto {
decision_reasons: string[]; decision_reasons: string[];
confidence_interval?: ConfidenceIntervalDto; confidence_interval?: ConfidenceIntervalDto;
signal_tier?: SignalTier; signal_tier?: SignalTier;
is_guaranteed?: boolean;
} }
export interface MatchBetAdviceDto { export interface MatchBetAdviceDto {
@@ -99,6 +107,13 @@ export interface MatchBetSummaryItemDto {
play_score: number; play_score: number;
ev_edge: number; ev_edge: number;
implied_prob: 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; odds: number;
reasons: string[]; reasons: string[];
confidence_interval?: ConfidenceIntervalDto; confidence_interval?: ConfidenceIntervalDto;