This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { apiRequest } from "@/lib/api/api-service";
|
||||
import type { SportType } from "@/lib/api/matches/types";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import {
|
||||
MatchPredictionDto,
|
||||
UpcomingPredictionsDto,
|
||||
ValueBetDto,
|
||||
PredictionHistoryResponseDto,
|
||||
AIHealthDto,
|
||||
SmartCouponRequestDto,
|
||||
SmartCouponResponseDto,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Predictions Service
|
||||
* Backend: /api/predictions/*
|
||||
*/
|
||||
|
||||
const getPrediction = (matchId: string) => {
|
||||
return apiRequest<ApiResponse<MatchPredictionDto>>({
|
||||
url: `/predictions/${matchId}`,
|
||||
client: "core",
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
const generatePrediction = (body: { matchId: string; sport?: SportType }) => {
|
||||
return apiRequest<ApiResponse<MatchPredictionDto>>({
|
||||
url: "/predictions/generate",
|
||||
client: "core",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
};
|
||||
|
||||
const getUpcoming = () => {
|
||||
return apiRequest<ApiResponse<UpcomingPredictionsDto>>({
|
||||
url: "/predictions/upcoming",
|
||||
client: "core",
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
const getValueBets = () => {
|
||||
return apiRequest<ApiResponse<ValueBetDto[]>>({
|
||||
url: "/predictions/value-bets",
|
||||
client: "core",
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
const getHistory = () => {
|
||||
return apiRequest<ApiResponse<PredictionHistoryResponseDto>>({
|
||||
url: "/predictions/history",
|
||||
client: "core",
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
const checkHealth = () => {
|
||||
return apiRequest<ApiResponse<AIHealthDto>>({
|
||||
url: "/predictions/health",
|
||||
client: "core",
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
const generateSmartCoupon = (body: SmartCouponRequestDto) => {
|
||||
return apiRequest<ApiResponse<SmartCouponResponseDto>>({
|
||||
url: "/predictions/smart-coupon",
|
||||
client: "core",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
};
|
||||
|
||||
export const predictionsService = {
|
||||
getPrediction,
|
||||
generatePrediction,
|
||||
getUpcoming,
|
||||
getValueBets,
|
||||
getHistory,
|
||||
checkHealth,
|
||||
generateSmartCoupon,
|
||||
};
|
||||
@@ -0,0 +1,230 @@
|
||||
import type { SportType } from "@/lib/api/matches/types";
|
||||
|
||||
// ========================
|
||||
// Sub-DTOs for MatchPredictionDto
|
||||
// ========================
|
||||
|
||||
export interface MatchInfoDto {
|
||||
match_id: string;
|
||||
match_name: string;
|
||||
home_team: string;
|
||||
away_team: string;
|
||||
league: string;
|
||||
match_date_ms: number;
|
||||
league_id?: string | null;
|
||||
is_top_league?: boolean;
|
||||
sport?: SportType;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface DataQualityDto {
|
||||
label: "HIGH" | "MEDIUM" | "LOW";
|
||||
score: number;
|
||||
home_lineup_count: number;
|
||||
away_lineup_count: number;
|
||||
lineup_source: string;
|
||||
flags: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ConfidenceIntervalDto {
|
||||
lower: number;
|
||||
upper: number;
|
||||
width: number;
|
||||
band: "HIGH" | "MEDIUM" | "LOW";
|
||||
threshold_met: boolean;
|
||||
}
|
||||
|
||||
export interface RiskDto {
|
||||
level: "LOW" | "MEDIUM" | "HIGH" | "EXTREME";
|
||||
score: number;
|
||||
is_surprise_risk: boolean;
|
||||
surprise_type: string | null;
|
||||
surprise_score?: number;
|
||||
surprise_comment?: string | null;
|
||||
surprise_reasons?: string[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
export interface EngineBreakdownDto {
|
||||
team: number;
|
||||
player: number;
|
||||
odds: number;
|
||||
referee: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export type BetGrade = "A" | "B" | "C" | "PASS";
|
||||
export type SignalTier = "CORE" | "VALUE" | "LEAN" | "LONGSHOT" | "PASS";
|
||||
|
||||
export interface MatchPickDto {
|
||||
market: string;
|
||||
pick: string;
|
||||
probability: number;
|
||||
confidence: number;
|
||||
odds: number;
|
||||
raw_confidence: number;
|
||||
calibrated_confidence: number;
|
||||
min_required_confidence: number;
|
||||
edge: number;
|
||||
ev_edge: number;
|
||||
implied_prob: number;
|
||||
play_score: number;
|
||||
playable: boolean;
|
||||
bet_grade: BetGrade;
|
||||
stake_units: number;
|
||||
decision_reasons: string[];
|
||||
confidence_interval?: ConfidenceIntervalDto;
|
||||
signal_tier?: SignalTier;
|
||||
}
|
||||
|
||||
export interface MatchBetAdviceDto {
|
||||
playable: boolean;
|
||||
suggested_stake_units: number;
|
||||
reason: string;
|
||||
confidence_band?: "HIGH" | "MEDIUM" | "LOW";
|
||||
min_confidence_for_play?: number;
|
||||
signal_tier?: SignalTier;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface MatchBetSummaryItemDto {
|
||||
market: string;
|
||||
pick: string;
|
||||
raw_confidence: number;
|
||||
calibrated_confidence: number;
|
||||
bet_grade: BetGrade;
|
||||
playable: boolean;
|
||||
stake_units: number;
|
||||
play_score: number;
|
||||
ev_edge: number;
|
||||
implied_prob: number;
|
||||
odds: number;
|
||||
reasons: string[];
|
||||
confidence_interval?: ConfidenceIntervalDto;
|
||||
signal_tier?: SignalTier;
|
||||
}
|
||||
|
||||
export interface AggressivePickDto {
|
||||
market: string;
|
||||
pick: string;
|
||||
probability: number;
|
||||
confidence: number;
|
||||
odds: number | null;
|
||||
confidence_interval?: ConfidenceIntervalDto;
|
||||
}
|
||||
|
||||
export interface ScenarioTop5ItemDto {
|
||||
score: string;
|
||||
prob: number;
|
||||
}
|
||||
|
||||
export interface ScorePredictionDto {
|
||||
ft: string;
|
||||
ht: string;
|
||||
xg_home: number;
|
||||
xg_away: number;
|
||||
xg_total: number;
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Main Prediction DTOs
|
||||
// ========================
|
||||
|
||||
export interface MarketBoardEntryDto {
|
||||
pick?: string;
|
||||
confidence?: number;
|
||||
confidence_band?: "HIGH" | "MEDIUM" | "LOW";
|
||||
confidence_interval?: ConfidenceIntervalDto;
|
||||
playable?: boolean;
|
||||
probs?: Record<string, number>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface MatchPredictionDto {
|
||||
model_version: string;
|
||||
match_info: MatchInfoDto;
|
||||
data_quality: DataQualityDto;
|
||||
risk: RiskDto;
|
||||
engine_breakdown: EngineBreakdownDto;
|
||||
main_pick: MatchPickDto | null;
|
||||
bet_advice: MatchBetAdviceDto;
|
||||
bet_summary: MatchBetSummaryItemDto[];
|
||||
supporting_picks: MatchPickDto[];
|
||||
aggressive_pick: AggressivePickDto | null;
|
||||
scenario_top5: ScenarioTop5ItemDto[];
|
||||
score_prediction: ScorePredictionDto;
|
||||
market_board: Record<string, MarketBoardEntryDto>;
|
||||
reasoning_factors: string[];
|
||||
ai_commentary?: string | null;
|
||||
}
|
||||
|
||||
export interface ValueBetDto {
|
||||
matchId: string;
|
||||
matchName: string;
|
||||
betType: string;
|
||||
prediction: string;
|
||||
confidence: number;
|
||||
odd: number;
|
||||
expectedValue: number;
|
||||
}
|
||||
|
||||
export interface UpcomingPredictionsDto {
|
||||
count: number;
|
||||
matches: MatchPredictionDto[];
|
||||
modelVersion: string;
|
||||
}
|
||||
|
||||
export interface PredictionHistoryStatsDto {
|
||||
totalPredictions: number;
|
||||
correctPredictions: number;
|
||||
accuracy: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface PredictionHistoryResponseDto {
|
||||
stats: PredictionHistoryStatsDto;
|
||||
history: Record<string, unknown>[];
|
||||
}
|
||||
|
||||
export interface AIHealthDto {
|
||||
status: string;
|
||||
modelLoaded: boolean;
|
||||
predictionServiceReady: boolean;
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Smart Coupon Request DTO
|
||||
// ========================
|
||||
|
||||
export type CouponStrategy =
|
||||
| "SAFE"
|
||||
| "BALANCED"
|
||||
| "AGGRESSIVE"
|
||||
| "VALUE"
|
||||
| "MIRACLE";
|
||||
|
||||
export interface SmartCouponRequestDto {
|
||||
matchIds: string[];
|
||||
strategy?: CouponStrategy;
|
||||
maxMatches?: number;
|
||||
minConfidence?: number;
|
||||
}
|
||||
|
||||
export interface SmartCouponResponseDto {
|
||||
coupon: {
|
||||
items: Array<{
|
||||
matchId: string;
|
||||
matchName: string;
|
||||
market: string;
|
||||
pick: string;
|
||||
odd: number;
|
||||
confidence: number;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
totalOdd: number;
|
||||
strategy: CouponStrategy;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import type { SportType } from "@/lib/api/matches/types";
|
||||
import { predictionsService } from "./service";
|
||||
import type { SmartCouponRequestDto } from "./types";
|
||||
|
||||
export const PredictionsQueryKeys = {
|
||||
all: ["predictions"] as const,
|
||||
detail: (matchId: string) =>
|
||||
[...PredictionsQueryKeys.all, "detail", matchId] as const,
|
||||
upcoming: () => [...PredictionsQueryKeys.all, "upcoming"] as const,
|
||||
valueBets: () => [...PredictionsQueryKeys.all, "valueBets"] as const,
|
||||
history: () => [...PredictionsQueryKeys.all, "history"] as const,
|
||||
health: () => [...PredictionsQueryKeys.all, "health"] as const,
|
||||
};
|
||||
|
||||
export const usePrediction = (matchId: string) => {
|
||||
return useQuery({
|
||||
queryKey: PredictionsQueryKeys.detail(matchId),
|
||||
queryFn: () => predictionsService.getPrediction(matchId),
|
||||
enabled: !!matchId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGeneratePrediction = () => {
|
||||
return useMutation({
|
||||
mutationFn: (body: { matchId: string; sport?: SportType }) =>
|
||||
predictionsService.generatePrediction(body),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpcomingPredictions = () => {
|
||||
return useQuery({
|
||||
queryKey: PredictionsQueryKeys.upcoming(),
|
||||
queryFn: () => predictionsService.getUpcoming(),
|
||||
});
|
||||
};
|
||||
|
||||
export const useValueBets = () => {
|
||||
return useQuery({
|
||||
queryKey: PredictionsQueryKeys.valueBets(),
|
||||
queryFn: () => predictionsService.getValueBets(),
|
||||
});
|
||||
};
|
||||
|
||||
export const usePredictionHistory = () => {
|
||||
return useQuery({
|
||||
queryKey: PredictionsQueryKeys.history(),
|
||||
queryFn: () => predictionsService.getHistory(),
|
||||
});
|
||||
};
|
||||
|
||||
export const useAIHealth = () => {
|
||||
return useQuery({
|
||||
queryKey: PredictionsQueryKeys.health(),
|
||||
queryFn: () => predictionsService.checkHealth(),
|
||||
refetchInterval: 30_000, // Auto-refresh every 30s
|
||||
});
|
||||
};
|
||||
|
||||
export const useGenerateSmartCoupon = () => {
|
||||
return useMutation({
|
||||
mutationFn: (body: SmartCouponRequestDto) =>
|
||||
predictionsService.generateSmartCoupon(body),
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user