export type PlayerPropsRequestPayload = { player_id: string; player_name?: string | null; metric: 'shots' | 'shots_on_target' | 'passes'; baseline_mean: number; line: number; match_minutes: number; team_attack_factor: number; opponent_defence_factor: number; weather_fatigue_factor: number; bookmaker_over_odds?: number | null; simulations?: number; }; export type PlayerPropsResponse = { metric: string; line: number; over_probability: number; under_probability: number; expected_count: number; p5: number; p50: number; p95: number; simulation_runs: number; edge: number | null; top_edge: boolean; bookmaker_over_odds: number | null; implied_prob: number | null; }; export type KellyRequestPayload = { odds: number; true_prob: number; bankroll: number; fractional_kelly_factor: number; risk_tolerance_factor: number; }; export type KellyResponse = { odds: number; true_prob: number; bankroll: number; raw_kelly_fraction: number; fractional_kelly_factor: number; risk_tolerance_factor: number; recommended_fraction: number; recommended_stake: number; }; export type BacktestTrade = { trade_id: string; settled_at: string; odds: number; is_win: boolean; stake: number; altitude_meters: number | null; handicap: number | null; weather: string | null; recent_form_win_rate: number | null; market_type: string; selection: string; }; export type BacktestRequestPayload = { initial_capital: number; strategy: { weather: string | null; altitude_min_meters: number | null; altitude_max_meters: number | null; handicap_min: number | null; handicap_max: number | null; recent_win_rate_min: number | null; recent_win_rate_max: number | null; market_types: string[] | null; start_at: string | null; end_at: string | null; }; historical_trades: BacktestTrade[]; }; export type BacktestPoint = { ts: string; capital: number; }; export type BacktestResponse = { matched: number; total: number; hit_count: number; win_rate: number; final_capital: number; net_profit: number; roi_percent: number; max_drawdown_percent: number; equity_curve: BacktestPoint[]; }; export type MlTrainingRow = { home_rest_days: number; away_rest_days: number; home_travel_distance_km: number; away_travel_distance_km: number; recent_5_xg_home: number; recent_5_xg_away: number; match_result: 'home' | 'draw' | 'away'; }; export type MlTrainRequestPayload = { model_id?: string | null; rows: MlTrainingRow[]; }; export type MlModelEdge = { model_prob: number; implied_prob: number; edge: number; strong_buy: boolean; }; export type MlEdgeResponse = { match_id: string; model_id: string; is_fallback_model: boolean; model_probs: { home: number; draw: number; away: number; }; edges: { home: MlModelEdge; draw: MlModelEdge; away: MlModelEdge; }; strong_buy: boolean; strongest_outcome: 'home' | 'draw' | 'away'; strongest_edge_percent: number; feature_columns: string[]; training_size: number; }; export type MlTrainResponse = { model_id: string; status: string; training_size: number; is_fallback: boolean; accuracy: number | null; }; export type MlEdgeRequestPayload = { model_id?: string; match_id: string; home_rest_days: number; away_rest_days: number; home_travel_distance_km: number; away_travel_distance_km: number; recent_5_xg_home: number; recent_5_xg_away: number; home_implied_odds: number; draw_implied_odds: number; away_implied_odds: number; }; export type MatchConditionRequestPayload = { match_id: string; avg_yellow_cards: number; penalties_per_game: number; cards_ou_line: number; temp_c: number; humidity_pct: number; venue_altitude_meters: number; home_second_half_attack: number; away_second_half_attack: number; }; export type MatchConditionResponse = { match_id: string; strictness_index: number; heat_index: number; cards_pressure_alert: boolean; second_half_home_attack: number; second_half_away_attack: number; second_half_under_recommendation: boolean; attacker_direction: string; }; export type RlmRequestPayload = { match_id: string; market_type: string; selection: string; ticket_threshold?: number; odds_change_threshold?: number; }; export type RlmAlert = { match_id: string; market_type: string; selection: string; opening_odds: number; current_odds: number; ticket_pct: number; handle_pct: number; odds_change_pct: number; smart_money_to: string; is_triggered: boolean; rationale: string; }; export type RlmResponse = { alerts: RlmAlert[]; total: number; }; export type ProofOfYieldSettleItem = { recommendation_id?: string | null; match_id: string; market_type: string; selection: string; stake: number; recommended_odds: number; closing_odds: number; is_win: boolean; settled_at?: string | null; }; export type ProofOfYieldSettleRequestPayload = { items: ProofOfYieldSettleItem[]; }; export type ProofOfYieldRecord = { recommendation_id: string; match_id: string; market_type: string; selection: string; stake: number; recommended_odds: number; closing_odds: number; is_win: boolean; settled_at: string; clv_percent: number | null; pnl: number; }; export type ProofOfYieldSummary = { total_recommendations: number; hit_count: number; win_rate_percent: number; total_stake: number; total_pnl: number; roi_percent: number; avg_clv_percent: number; }; export type ProofOfYieldLedgerResponse = { summary: ProofOfYieldSummary; records: ProofOfYieldRecord[]; }; export type UserBetPayload = { market_type: string; parlay_type?: string | null; odds?: number | null; stake: number; recommended_odds?: number | null; closing_odds?: number | null; is_settled: boolean; is_win: boolean; match_stage?: string | null; stage?: string | null; }; export type PortfolioLeaksRequestPayload = { user_bets: UserBetPayload[]; }; export type PortfolioLeakCluster = { market_type: string; bet_type: string; odds_bucket: string; match_stage: string; bet_count: number; total_stake: number; closed_count: number; win_count: number; total_pnl: number; avg_clv_percent: number; roi_percent: number; hit_rate_percent: number; status: string; }; export type PortfolioHardTruth = { title: string; message: string; cluster: Record; }; export type PortfolioLeaksResponse = { total_bet_count: number; settled_bet_count: number; total_stake: number; total_pnl: number; overall_roi_percent: number; overall_hit_rate_percent: number; clusters: PortfolioLeakCluster[]; hard_truths: PortfolioHardTruth[]; }; export type HedgeRequestPayload = { original_stake: number; parlay_total_odds: number; final_leg_hedge_odds: number; }; export type HedgeResponse = { hedge_stake: number; locked_profit: number; parlay_net_after_hedge_if_win: number; hedge_net_if_win: number; }; export type DailyCardLeg = { match_id: string; selection: string; odds: number; }; export type DailyCardItem = { match_id: string; match_label: string; market_type: string; selection: string; target_odds: number; win_prob: number; ev_percent: number; stake_units: number; recommendation: string; rationale: string; legs?: DailyCardLeg[]; }; export type DailyCardResponse = { date: string; total_daily_unit_recommendation: number; summary: string; safe_singles: DailyCardItem[]; high_risk_singles: DailyCardItem[]; safe_parlays: DailyCardItem[]; sgp_lotteries: DailyCardItem[]; matched_matches: number; stage_distribution: Record; }; export type MatchListItem = { match_id: string; home_team: string; away_team: string; kickoff_utc: string; status: string; venue_name: string | null; venue_city: string | null; venue_country: string | null; }; export type MatchOddsPoint = { recorded_at: string; bookmaker: string; bookmaker_id: string; market_type: string; selection: string; decimal_odds: number; implied_probability: number; }; export type MatchPoisson = { expected_home_goals: number; expected_away_goals: number; score_matrix: number[][]; one_x_two: { home_win: number; draw: number; away_win: number; }; over_under_2_5: { under: number; over: number; }; }; export type MatchConditionsReadout = { strictness_index: number; heat_index: number; cards_pressure_alert: boolean; second_half_home_attack: number; second_half_away_attack: number; second_half_under_recommendation: boolean; attacker_direction: string; }; export type MatchDetail = { match_id: string; home_team: string; away_team: string; home_xg: number; away_xg: number; match_time_utc: string; status: string; venue_name: string; venue_city: string | null; venue_country: string | null; venue_altitude_meters: number | null; odds_series: MatchOddsPoint[]; poisson: MatchPoisson; conditions: MatchConditionsReadout; quant_summary: string; }; type ApiErrorPayload = { message?: string; }; const ANALYTICS_API_BASE = '/api/analytics'; type HttpMethod = 'GET' | 'POST'; async function requestAnalytics( path: string, payload: unknown, method: HttpMethod = 'POST', ): Promise { const response = await fetch(`${ANALYTICS_API_BASE}/${path}`, { method, headers: { 'Content-Type': 'application/json' }, body: method === 'POST' ? JSON.stringify(payload) : undefined, }); if (!response.ok) { let message = `Analytics API 錯誤:${response.status}`; try { const body = (await response.json()) as ApiErrorPayload; if (body?.message) { message = body.message; } } catch (error) { if (error instanceof Error) { message = error.message; } } throw new Error(message); } return response.json() as Promise; } export function calculatePlayerProps(payload: PlayerPropsRequestPayload): Promise { return requestAnalytics('player-props', payload); } export function calculateKelly(payload: KellyRequestPayload): Promise { return requestAnalytics('kelly', payload); } export function runBacktest(payload: BacktestRequestPayload): Promise { return requestAnalytics('backtest', payload); } export function trainMlModel(payload: MlTrainRequestPayload): Promise { return requestAnalytics('ml-edge/train', payload); } export function analyzeMlEdge(payload: MlEdgeRequestPayload): Promise { return requestAnalytics('ml-edge', payload); } export function analyzeMatchConditions(payload: MatchConditionRequestPayload): Promise { return requestAnalytics('match-conditions', payload); } export function detectReverseLineMovement(payload: RlmRequestPayload): Promise { return requestAnalytics('rlm', payload); } export function getProofOfYieldLedger(limit = 200): Promise { return requestAnalytics(`proof-of-yield/ledger?limit=${limit}`, {}, 'GET'); } export function settleProofOfYield(payload: ProofOfYieldSettleRequestPayload): Promise { return requestAnalytics('proof-of-yield/settle', payload); } export function analyzePortfolioLeaks(payload: PortfolioLeaksRequestPayload): Promise { return requestAnalytics('portfolio/leaks', payload); } export function calculateHedge(payload: HedgeRequestPayload): Promise { return requestAnalytics('hedging', payload); } export function getDailyCard(date: string): Promise { return requestAnalytics(`daily-card/${date}`, {}, 'GET'); } export function getAllMatches(): Promise { return requestAnalytics('matches', {}, 'GET'); } export function getMatchById(matchId: string): Promise { return requestAnalytics(`matches/${matchId}`, {}, 'GET'); }