This commit is contained in:
@@ -373,6 +373,7 @@ export default function DashboardContent() {
|
|||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
>
|
>
|
||||||
{Math.round(
|
{Math.round(
|
||||||
|
pred.main_pick.unified_score ??
|
||||||
pred.main_pick.calibrated_confidence ??
|
pred.main_pick.calibrated_confidence ??
|
||||||
pred.main_pick.confidence,
|
pred.main_pick.confidence,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -643,7 +643,7 @@ function PickCard({
|
|||||||
<SimpleGrid columns={2} gap={3} minW={{ base: "full", md: "320px" }}>
|
<SimpleGrid columns={2} gap={3} minW={{ base: "full", md: "320px" }}>
|
||||||
<MetricTile
|
<MetricTile
|
||||||
label={labels.confidence}
|
label={labels.confidence}
|
||||||
value={formatPercent(pick.calibrated_confidence, 0)}
|
value={formatPercent(pick.unified_score ?? pick.calibrated_confidence, 0)}
|
||||||
/>
|
/>
|
||||||
<MetricTile label={labels.odds} value={formatOdds(pick.odds)} />
|
<MetricTile label={labels.odds} value={formatOdds(pick.odds)} />
|
||||||
<MetricTile
|
<MetricTile
|
||||||
@@ -742,7 +742,7 @@ function SummaryTable({
|
|||||||
right.signal_tier || "PASS",
|
right.signal_tier || "PASS",
|
||||||
);
|
);
|
||||||
if (leftIndex !== rightIndex) return leftIndex - rightIndex;
|
if (leftIndex !== rightIndex) return leftIndex - rightIndex;
|
||||||
return right.calibrated_confidence - left.calibrated_confidence;
|
return (right.unified_score ?? right.calibrated_confidence) - (left.unified_score ?? left.calibrated_confidence);
|
||||||
})
|
})
|
||||||
.map((item) => (
|
.map((item) => (
|
||||||
<Flex
|
<Flex
|
||||||
@@ -778,6 +778,13 @@ 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="64px" color="fg.muted">
|
||||||
|
{formatPercent(
|
||||||
|
(item.model_probability ?? 0) * 100,
|
||||||
|
0,
|
||||||
|
)}{" "}
|
||||||
|
{getUiText(ui, "probability-short", "olasılık")}
|
||||||
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
minW="96px"
|
minW="96px"
|
||||||
color={`${getEdgePalette(item.ev_edge)}.500`}
|
color={`${getEdgePalette(item.ev_edge)}.500`}
|
||||||
@@ -786,7 +793,7 @@ function SummaryTable({
|
|||||||
{formatEdgeSignal(item.ev_edge)}
|
{formatEdgeSignal(item.ev_edge)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text minW="48px">
|
<Text minW="48px">
|
||||||
{formatPercent(item.calibrated_confidence, 0)}
|
{formatPercent(item.unified_score ?? item.calibrated_confidence, 0)}
|
||||||
</Text>
|
</Text>
|
||||||
<Badge
|
<Badge
|
||||||
colorPalette={getConfidenceBandPalette(
|
colorPalette={getConfidenceBandPalette(
|
||||||
@@ -881,9 +888,18 @@ function MarketBoardSection({
|
|||||||
|
|
||||||
if (!marketBoard || !Object.keys(marketBoard).length) return null;
|
if (!marketBoard || !Object.keys(marketBoard).length) return null;
|
||||||
|
|
||||||
|
// Key by market:pick so each card resolves the summary row for the EXACT
|
||||||
|
// outcome it displays (graph pick). Keying by market alone collided on
|
||||||
|
// multi-row markets (MS 1/X/2) and surfaced the wrong odds + confidence.
|
||||||
const summaryByMarket = new Map(
|
const summaryByMarket = new Map(
|
||||||
(betSummary || []).map((item) => [item.market, item]),
|
(betSummary || []).map((item) => [`${item.market}:${item.pick}`, item]),
|
||||||
);
|
);
|
||||||
|
// Fallback: first row of a market, for cards whose pick has no exact row.
|
||||||
|
for (const item of betSummary || []) {
|
||||||
|
if (!summaryByMarket.has(item.market)) {
|
||||||
|
summaryByMarket.set(item.market, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
const orderedEntries = Object.entries(marketBoard).sort(([left], [right]) => {
|
const orderedEntries = Object.entries(marketBoard).sort(([left], [right]) => {
|
||||||
const leftIndex = MARKET_ORDER.indexOf(left);
|
const leftIndex = MARKET_ORDER.indexOf(left);
|
||||||
const rightIndex = MARKET_ORDER.indexOf(right);
|
const rightIndex = MARKET_ORDER.indexOf(right);
|
||||||
@@ -899,9 +915,18 @@ function MarketBoardSection({
|
|||||||
<SimpleGrid columns={{ base: 1, xl: 2 }} gap={4}>
|
<SimpleGrid columns={{ base: 1, xl: 2 }} gap={4}>
|
||||||
{orderedEntries.map(([market, entry]) => {
|
{orderedEntries.map(([market, entry]) => {
|
||||||
if (!entry?.probs) return null;
|
if (!entry?.probs) return null;
|
||||||
const summary = summaryByMarket.get(market);
|
const summary =
|
||||||
|
summaryByMarket.get(`${market}:${entry.pick}`) ??
|
||||||
|
summaryByMarket.get(market);
|
||||||
const interval =
|
const interval =
|
||||||
summary?.confidence_interval || entry.confidence_interval;
|
summary?.confidence_interval || entry.confidence_interval;
|
||||||
|
// Hit probability == the dominant (green) bar in the graph below,
|
||||||
|
// so the headline never contradicts the distribution it sits on.
|
||||||
|
const pickProbPct =
|
||||||
|
Math.max(
|
||||||
|
0,
|
||||||
|
...Object.values(entry.probs).map((p) => Number(p) || 0),
|
||||||
|
) * 100;
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
key={market}
|
key={market}
|
||||||
@@ -951,25 +976,25 @@ function MarketBoardSection({
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
>
|
>
|
||||||
{entry.pick} ({formatPercent(entry.confidence, 0)})
|
{entry.pick} ({formatPercent(pickProbPct, 0)})
|
||||||
</Badge>
|
</Badge>
|
||||||
) : null}
|
) : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
<SimpleGrid columns={3} gap={2} mb={3}>
|
<SimpleGrid columns={3} gap={2} mb={3}>
|
||||||
<MetricTile
|
<MetricTile
|
||||||
label={getUiText(ui, "hit-probability", "Tutma Olasılığı")}
|
label={getUiText(ui, "hit-probability", "Tutma Olasılığı")}
|
||||||
value={formatPercent(entry.confidence, 0)}
|
value={formatPercent(pickProbPct, 0)}
|
||||||
accent="green.500"
|
accent="green.500"
|
||||||
/>
|
/>
|
||||||
<MetricTile
|
<MetricTile
|
||||||
label={getUiText(
|
label={getUiText(
|
||||||
ui,
|
ui,
|
||||||
"calibrated-confidence",
|
"unified-confidence",
|
||||||
"Kalibre Güven",
|
"Güven Skoru",
|
||||||
)}
|
)}
|
||||||
value={
|
value={
|
||||||
summary
|
summary
|
||||||
? formatPercent(summary.calibrated_confidence, 0)
|
? formatPercent(summary.unified_score ?? summary.calibrated_confidence, 0)
|
||||||
: "-"
|
: "-"
|
||||||
}
|
}
|
||||||
accent={summary?.playable ? "green.500" : "orange.500"}
|
accent={summary?.playable ? "green.500" : "orange.500"}
|
||||||
@@ -999,8 +1024,7 @@ function MarketBoardSection({
|
|||||||
<Bar
|
<Bar
|
||||||
value={probability * 100}
|
value={probability * 100}
|
||||||
color={
|
color={
|
||||||
entry.pick === outcome ||
|
(Number(probability) || 0) * 100 >= pickProbPct - 1e-6
|
||||||
entry.pick?.toUpperCase() === outcome.toUpperCase()
|
|
||||||
? "green.400"
|
? "green.400"
|
||||||
: "blue.400"
|
: "blue.400"
|
||||||
}
|
}
|
||||||
@@ -1349,7 +1373,7 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
|
|||||||
<MetricTile
|
<MetricTile
|
||||||
label={uiText("confidence-label", "Güven")}
|
label={uiText("confidence-label", "Güven")}
|
||||||
value={formatPercent(
|
value={formatPercent(
|
||||||
recommendedPick.calibrated_confidence,
|
recommendedPick.unified_score ?? recommendedPick.calibrated_confidence,
|
||||||
0,
|
0,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ export default function PredictionsContent() {
|
|||||||
color="primary.fg"
|
color="primary.fg"
|
||||||
>
|
>
|
||||||
{Math.round(
|
{Math.round(
|
||||||
|
pred.main_pick.unified_score ??
|
||||||
pred.main_pick.calibrated_confidence ??
|
pred.main_pick.calibrated_confidence ??
|
||||||
pred.main_pick.confidence,
|
pred.main_pick.confidence,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ export interface EngineBreakdownDto {
|
|||||||
export type BetGrade = "A" | "B" | "C" | "PASS";
|
export type BetGrade = "A" | "B" | "C" | "PASS";
|
||||||
export type SignalTier = "CORE" | "VALUE" | "LEAN" | "LONGSHOT" | "PASS";
|
export type SignalTier = "CORE" | "VALUE" | "LEAN" | "LONGSHOT" | "PASS";
|
||||||
|
|
||||||
|
export type UnifiedScoreLabel = "very_reliable" | "reliable" | "moderate" | "low";
|
||||||
|
|
||||||
export interface MatchPickDto {
|
export interface MatchPickDto {
|
||||||
market: string;
|
market: string;
|
||||||
pick: string;
|
pick: string;
|
||||||
@@ -98,6 +100,8 @@ export interface MatchPickDto {
|
|||||||
odds: number;
|
odds: number;
|
||||||
raw_confidence: number;
|
raw_confidence: number;
|
||||||
calibrated_confidence: number;
|
calibrated_confidence: number;
|
||||||
|
unified_score?: number;
|
||||||
|
unified_score_label?: UnifiedScoreLabel;
|
||||||
min_required_confidence: number;
|
min_required_confidence: number;
|
||||||
edge: number;
|
edge: number;
|
||||||
ev_edge: number;
|
ev_edge: number;
|
||||||
@@ -158,6 +162,8 @@ export interface MatchBetSummaryItemDto {
|
|||||||
pick: string;
|
pick: string;
|
||||||
raw_confidence: number;
|
raw_confidence: number;
|
||||||
calibrated_confidence: number;
|
calibrated_confidence: number;
|
||||||
|
unified_score?: number;
|
||||||
|
unified_score_label?: UnifiedScoreLabel;
|
||||||
bet_grade: BetGrade;
|
bet_grade: BetGrade;
|
||||||
playable: boolean;
|
playable: boolean;
|
||||||
stake_units: number;
|
stake_units: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user