This commit is contained in:
@@ -1172,6 +1172,39 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
|
|||||||
const mainBandPalette = getConfidenceBandPalette(
|
const mainBandPalette = getConfidenceBandPalette(
|
||||||
prediction.bet_advice.confidence_band,
|
prediction.bet_advice.confidence_band,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ── Maç Sonucu Tahmini (model'in EN OLASI sonucu) ──────────────────
|
||||||
|
// The value-bet hero below optimizes ROI (often a high-odds draw/underdog,
|
||||||
|
// ~27% hit) which users misread as a "bad prediction". This block instead
|
||||||
|
// shows what the model thinks will actually happen: the highest-probability
|
||||||
|
// 1X2 outcome (~55% hit, on par with the market). Sourced from market_board.MS.
|
||||||
|
const msBoard = prediction.market_board?.MS;
|
||||||
|
const matchResultPrediction = (() => {
|
||||||
|
const probs = msBoard?.probs;
|
||||||
|
if (!probs) return null;
|
||||||
|
const labelMap: Record<string, string> = {
|
||||||
|
"1": prediction.match_info?.home_team || uiText("home", "Ev Sahibi"),
|
||||||
|
X: uiText("draw", "Beraberlik"),
|
||||||
|
"2": prediction.match_info?.away_team || uiText("away", "Deplasman"),
|
||||||
|
};
|
||||||
|
let bestKey = "";
|
||||||
|
let bestProb = -1;
|
||||||
|
for (const [k, v] of Object.entries(probs)) {
|
||||||
|
const p = typeof v === "number" ? v : 0;
|
||||||
|
if (p > bestProb) {
|
||||||
|
bestProb = p;
|
||||||
|
bestKey = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!bestKey) return null;
|
||||||
|
return {
|
||||||
|
pick: bestKey,
|
||||||
|
label: labelMap[bestKey] ?? bestKey,
|
||||||
|
prob: bestProb,
|
||||||
|
all: probs as Record<string, number>,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
const sport = getPredictionSport(prediction);
|
const sport = getPredictionSport(prediction);
|
||||||
const isBasketball = sport === "basketball";
|
const isBasketball = sport === "basketball";
|
||||||
|
|
||||||
@@ -1373,6 +1406,85 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{matchResultPrediction ? (
|
||||||
|
<Box
|
||||||
|
p={4}
|
||||||
|
bg={statCardBg}
|
||||||
|
borderWidth="1px"
|
||||||
|
borderColor={borderColor}
|
||||||
|
borderRadius="2xl"
|
||||||
|
>
|
||||||
|
<HStack justify="space-between" align="start" mb={3}>
|
||||||
|
<VStack align="start" gap={1}>
|
||||||
|
<Badge colorPalette="blue" variant="subtle" borderRadius="full">
|
||||||
|
{uiText("match-result-prediction", "Maç Sonucu Tahmini")}
|
||||||
|
</Badge>
|
||||||
|
<Text fontSize="2xl" fontWeight="bold">
|
||||||
|
{matchResultPrediction.label}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="sm" color="fg.muted">
|
||||||
|
{uiText(
|
||||||
|
"match-result-copy",
|
||||||
|
"Modelin en olası gördüğü sonuç (kim kazanır).",
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
<VStack align="end" gap={0}>
|
||||||
|
<Text fontSize="2xl" fontWeight="bold" color="blue.500">
|
||||||
|
{formatPercent(matchResultPrediction.prob * 100, 0)}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="xs" color="fg.muted">
|
||||||
|
{uiText("probability-short", "olasılık")}
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
{/* 1X2 dağılımı */}
|
||||||
|
<VStack align="stretch" gap={2}>
|
||||||
|
{["1", "X", "2"].map((k) => {
|
||||||
|
const p = matchResultPrediction.all[k] ?? 0;
|
||||||
|
const lbl: Record<string, string> = {
|
||||||
|
"1":
|
||||||
|
prediction.match_info?.home_team ||
|
||||||
|
uiText("home", "Ev Sahibi"),
|
||||||
|
X: uiText("draw", "Beraberlik"),
|
||||||
|
"2":
|
||||||
|
prediction.match_info?.away_team ||
|
||||||
|
uiText("away", "Deplasman"),
|
||||||
|
};
|
||||||
|
const isTop = k === matchResultPrediction.pick;
|
||||||
|
return (
|
||||||
|
<Box key={k}>
|
||||||
|
<Flex justify="space-between" mb={1}>
|
||||||
|
<Text
|
||||||
|
fontSize="sm"
|
||||||
|
fontWeight={isTop ? "semibold" : "normal"}
|
||||||
|
color={isTop ? "blue.500" : "fg.muted"}
|
||||||
|
>
|
||||||
|
{lbl[k]}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="sm" fontWeight={isTop ? "semibold" : "normal"}>
|
||||||
|
{formatPercent(p * 100, 0)}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Bar
|
||||||
|
value={p * 100}
|
||||||
|
color={isTop ? "blue.400" : "gray.400"}
|
||||||
|
trackBg={trackBgColor}
|
||||||
|
height="6px"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</VStack>
|
||||||
|
<Text fontSize="xs" color="fg.muted" mt={3}>
|
||||||
|
{uiText(
|
||||||
|
"match-result-vs-value",
|
||||||
|
"Bu en olası sonuçtur. Aşağıdaki “Değerli Bahis” ise orana göre en kârlı görülen seçimdir — ikisi farklı olabilir.",
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{recommendedPick ? (
|
{recommendedPick ? (
|
||||||
<Grid templateColumns={{ base: "1fr", xl: "1.4fr 1fr" }} gap={4}>
|
<Grid templateColumns={{ base: "1fr", xl: "1.4fr 1fr" }} gap={4}>
|
||||||
<Box
|
<Box
|
||||||
@@ -1389,7 +1501,7 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
|
|||||||
variant="solid"
|
variant="solid"
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
>
|
>
|
||||||
{uiText("main-recommendation", "Öne Çıkan Sinyal")}
|
{uiText("main-recommendation", "Değerli Bahis")}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Text fontSize="2xl" fontWeight="bold">
|
<Text fontSize="2xl" fontWeight="bold">
|
||||||
{recommendedPick.pick}
|
{recommendedPick.pick}
|
||||||
|
|||||||
Reference in New Issue
Block a user