744 lines
19 KiB
TypeScript
744 lines
19 KiB
TypeScript
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;
|
||
confidence_score?: number;
|
||
risk_level?: 'core' | 'speculative' | 'parlay' | 'sgp' | string;
|
||
market_implied_prob?: number;
|
||
edge_percent?: number;
|
||
data_checks?: 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<string, string | number>;
|
||
};
|
||
|
||
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;
|
||
stake_amount_twd?: number;
|
||
unit_size_twd?: number;
|
||
recommendation: string;
|
||
rationale: string;
|
||
confidence_score?: number;
|
||
confidence_band?: string;
|
||
confidence_factors?: string[];
|
||
data_quality?: string;
|
||
has_market_odds?: boolean;
|
||
odds_source_label?: string;
|
||
odds_source_kind?: string;
|
||
risk_level?: 'core' | 'speculative' | 'parlay' | 'sgp' | string;
|
||
market_implied_prob?: number;
|
||
edge_percent?: number;
|
||
data_checks?: string[];
|
||
legs?: DailyCardLeg[];
|
||
};
|
||
|
||
export type DailyCardResponse = {
|
||
date: string;
|
||
generated_at?: string;
|
||
total_daily_unit_recommendation: number;
|
||
total_daily_amount_twd?: number;
|
||
unit_size_twd?: number;
|
||
summary: string;
|
||
market_data_status?: string | null;
|
||
data_quality_summary?: Record<string, number>;
|
||
execution_policy?: string | null;
|
||
auto_refresh_seconds?: number;
|
||
safe_singles: DailyCardItem[];
|
||
high_risk_singles: DailyCardItem[];
|
||
safe_parlays: DailyCardItem[];
|
||
sgp_lotteries: DailyCardItem[];
|
||
matched_matches: number;
|
||
stage_distribution: Record<string, number>;
|
||
};
|
||
|
||
export type DailyCardCalendarDate = {
|
||
date: string;
|
||
match_count: number;
|
||
matched_matches: number;
|
||
recommendation_count: number;
|
||
live_count: number;
|
||
watch_count: number;
|
||
safe_single_count: number;
|
||
high_risk_single_count: number;
|
||
safe_parlay_count: number;
|
||
sgp_lottery_count: number;
|
||
total_amount_twd: number;
|
||
market_data_status?: string | null;
|
||
snapshot_status?: string | null;
|
||
snapshot_item_count?: number | null;
|
||
snapshot_preserved_count?: number | null;
|
||
summary?: string | null;
|
||
};
|
||
|
||
export type DailyCardCalendarResponse = {
|
||
generated_at: string;
|
||
start_date: string;
|
||
end_date: string;
|
||
dates: DailyCardCalendarDate[];
|
||
};
|
||
|
||
export type RecommendationPerformanceItem = {
|
||
match_id: string;
|
||
match_label: string;
|
||
market_type: string;
|
||
selection: string;
|
||
recommendation: string;
|
||
result_score: string;
|
||
outcome: 'hit' | 'miss' | 'push' | 'not_evaluable' | string;
|
||
outcome_label: string;
|
||
target_odds: number;
|
||
win_prob: number;
|
||
ev_percent: number;
|
||
stake_units: number;
|
||
stake_amount_twd?: number | null;
|
||
confidence_score?: number | null;
|
||
confidence_band?: string | null;
|
||
has_market_odds?: boolean | null;
|
||
odds_source_label?: string | null;
|
||
odds_source_kind?: string | null;
|
||
lesson: string;
|
||
};
|
||
|
||
export type RecommendationPerformanceBucket = {
|
||
market_type: string;
|
||
recommendation_count: number;
|
||
settled_count: number;
|
||
hit_count: number;
|
||
miss_count: number;
|
||
push_count: number;
|
||
hit_rate_percent: number;
|
||
};
|
||
|
||
export type RecommendationPerformanceSourceBucket = {
|
||
source_label: string;
|
||
source_kind: string;
|
||
recommendation_count: number;
|
||
settled_count: number;
|
||
hit_count: number;
|
||
miss_count: number;
|
||
push_count: number;
|
||
hit_rate_percent: number;
|
||
};
|
||
|
||
export type RecommendationPerformanceResponse = {
|
||
generated_at: string;
|
||
days_back: number;
|
||
finished_match_count: number;
|
||
rebuilt_recommendation_count: number;
|
||
settled_recommendation_count: number;
|
||
hit_count: number;
|
||
miss_count: number;
|
||
push_count: number;
|
||
hit_rate_percent: number;
|
||
summary: string;
|
||
methodology_note: string;
|
||
improvement_actions: string[];
|
||
by_market_type: RecommendationPerformanceBucket[];
|
||
by_odds_source: RecommendationPerformanceSourceBucket[];
|
||
items: RecommendationPerformanceItem[];
|
||
};
|
||
|
||
export type AgentVerificationCheck = {
|
||
agent: string;
|
||
role: string;
|
||
status: string;
|
||
status_label: string;
|
||
evidence: string[];
|
||
next_action?: string | null;
|
||
last_checked_at: string;
|
||
};
|
||
|
||
export type AgentVerificationResponse = {
|
||
generated_at: string;
|
||
overall_status: string;
|
||
overall_label: string;
|
||
production_ready: boolean;
|
||
decision_policy: string;
|
||
calibration_summary: Record<string, unknown>;
|
||
checks: AgentVerificationCheck[];
|
||
};
|
||
|
||
export type GeminiUsageResponse = {
|
||
generated_at: string;
|
||
month: string;
|
||
status: string;
|
||
status_label: string;
|
||
paused: boolean;
|
||
cap_usd: number;
|
||
estimated_cost_usd: number;
|
||
remaining_usd: number;
|
||
request_count: number;
|
||
input_tokens: number;
|
||
output_tokens: number;
|
||
grounded_query_count: number;
|
||
pricing_note: string;
|
||
next_action?: string | null;
|
||
};
|
||
|
||
export type SourceHealthResponse = {
|
||
status: string;
|
||
odds_coverage_status?: string;
|
||
upcoming_odds_matches?: number;
|
||
stale_unsettled_matches?: number;
|
||
stale_unsettled_threshold_hours?: number;
|
||
odds_rows: number;
|
||
matches: number;
|
||
finished_matches: number;
|
||
venues: number;
|
||
high_altitude_venues: number;
|
||
latest_odds_recorded_at: string | null;
|
||
latest_result_synced_at: string | null;
|
||
ingestion_status?: {
|
||
status?: string;
|
||
source?: string;
|
||
run_at?: string;
|
||
events?: number;
|
||
odds_rows?: number;
|
||
bookmakers?: number;
|
||
message?: string;
|
||
} | null;
|
||
fixtures_status?: {
|
||
status?: string;
|
||
source?: string;
|
||
run_at?: string;
|
||
fixtures?: number;
|
||
upserted?: number;
|
||
skipped?: number;
|
||
interval_seconds?: number;
|
||
message?: string;
|
||
} | null;
|
||
news_status?: {
|
||
status?: string;
|
||
source?: string;
|
||
run_at?: string;
|
||
items?: number;
|
||
interval_seconds?: number;
|
||
message?: string;
|
||
} | null;
|
||
provider_requirements?: {
|
||
primary_odds_provider?: string;
|
||
required_markets?: string[];
|
||
taiwan_sports_lottery?: string;
|
||
current_limitation?: string;
|
||
};
|
||
};
|
||
|
||
export type MatchListItem = {
|
||
match_id: string;
|
||
home_team: string;
|
||
away_team: string;
|
||
home_score: number | null;
|
||
away_score: number | null;
|
||
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_score: number | null;
|
||
away_score: number | null;
|
||
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[];
|
||
odds_quality?: string;
|
||
xg_quality?: string;
|
||
poisson: MatchPoisson;
|
||
conditions: MatchConditionsReadout;
|
||
quant_summary: string;
|
||
};
|
||
|
||
type ApiErrorPayload = {
|
||
message?: string;
|
||
detail?: string;
|
||
error?: {
|
||
message?: string;
|
||
detail?: string;
|
||
};
|
||
};
|
||
|
||
const ANALYTICS_API_BASE = '/api/analytics';
|
||
|
||
type HttpMethod = 'GET' | 'POST';
|
||
|
||
async function requestAnalytics<T>(
|
||
path: string,
|
||
payload: unknown,
|
||
method: HttpMethod = 'POST',
|
||
): Promise<T> {
|
||
try {
|
||
const response = await fetch(`${ANALYTICS_API_BASE}/${path}`, {
|
||
method,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: method === 'POST' ? JSON.stringify(payload) : undefined,
|
||
...(method === 'GET' ? { cache: 'no-store' as RequestCache } : {}),
|
||
});
|
||
|
||
if (response.ok) {
|
||
return response.json() as Promise<T>;
|
||
}
|
||
const contentType = response.headers.get('content-type') || '';
|
||
const errorPayload = contentType.includes('application/json')
|
||
? ((await response.json().catch(() => null)) as ApiErrorPayload | null)
|
||
: null;
|
||
const textPayload = errorPayload ? '' : await response.text().catch(() => '');
|
||
const backendMessage =
|
||
errorPayload?.error?.message ||
|
||
errorPayload?.error?.detail ||
|
||
errorPayload?.message ||
|
||
errorPayload?.detail ||
|
||
textPayload ||
|
||
'資料服務暫時無法回應';
|
||
throw new Error(`${backendMessage}(狀態碼 ${response.status})`);
|
||
} catch (error) {
|
||
console.error(`[API Error] Fetch failed for ${path}`, error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
export function calculatePlayerProps(payload: PlayerPropsRequestPayload): Promise<PlayerPropsResponse> {
|
||
return requestAnalytics<PlayerPropsResponse>('player-props', payload);
|
||
}
|
||
|
||
export function calculateKelly(payload: KellyRequestPayload): Promise<KellyResponse> {
|
||
return requestAnalytics<KellyResponse>('kelly', payload);
|
||
}
|
||
|
||
export function runBacktest(payload: BacktestRequestPayload): Promise<BacktestResponse> {
|
||
return requestAnalytics<BacktestResponse>('backtest', payload);
|
||
}
|
||
|
||
export function trainMlModel(payload: MlTrainRequestPayload): Promise<MlTrainResponse> {
|
||
return requestAnalytics<MlTrainResponse>('ml-edge/train', payload);
|
||
}
|
||
|
||
export function analyzeMlEdge(payload: MlEdgeRequestPayload): Promise<MlEdgeResponse> {
|
||
return requestAnalytics<MlEdgeResponse>('ml-edge', payload);
|
||
}
|
||
|
||
export function analyzeMatchConditions(payload: MatchConditionRequestPayload): Promise<MatchConditionResponse> {
|
||
return requestAnalytics<MatchConditionResponse>('match-conditions', payload);
|
||
}
|
||
|
||
export function detectReverseLineMovement(payload: RlmRequestPayload): Promise<RlmResponse> {
|
||
return requestAnalytics<RlmResponse>('rlm', payload);
|
||
}
|
||
|
||
export function getProofOfYieldLedger(limit = 200): Promise<ProofOfYieldLedgerResponse> {
|
||
return requestAnalytics<ProofOfYieldLedgerResponse>(`proof-of-yield/ledger?limit=${limit}`, {}, 'GET');
|
||
}
|
||
|
||
export function settleProofOfYield(payload: ProofOfYieldSettleRequestPayload): Promise<ProofOfYieldLedgerResponse> {
|
||
return requestAnalytics<ProofOfYieldLedgerResponse>('proof-of-yield/settle', payload);
|
||
}
|
||
|
||
export function analyzePortfolioLeaks(payload: PortfolioLeaksRequestPayload): Promise<PortfolioLeaksResponse> {
|
||
return requestAnalytics<PortfolioLeaksResponse>('portfolio/leaks', payload);
|
||
}
|
||
|
||
export function calculateHedge(payload: HedgeRequestPayload): Promise<HedgeResponse> {
|
||
return requestAnalytics<HedgeResponse>('hedging', payload);
|
||
}
|
||
|
||
export function getDailyCard(date: string): Promise<DailyCardResponse> {
|
||
return requestAnalytics<DailyCardResponse>(`daily-card/${date}`, {}, 'GET');
|
||
}
|
||
|
||
export function getDailyCardCalendar(startDate = '2026-06-11', endDate?: string): Promise<DailyCardCalendarResponse> {
|
||
const params = new URLSearchParams({ start_date: startDate });
|
||
if (endDate) params.set('end_date', endDate);
|
||
return requestAnalytics<DailyCardCalendarResponse>(`daily-card-calendar?${params.toString()}`, {}, 'GET');
|
||
}
|
||
|
||
export function getRecommendationPerformance(daysBack = 7): Promise<RecommendationPerformanceResponse> {
|
||
return requestAnalytics<RecommendationPerformanceResponse>(`recommendation-performance?days_back=${daysBack}`, {}, 'GET');
|
||
}
|
||
|
||
export function getAgentVerification(): Promise<AgentVerificationResponse> {
|
||
return requestAnalytics<AgentVerificationResponse>('agent-verification', {}, 'GET');
|
||
}
|
||
|
||
export function getGeminiUsage(): Promise<GeminiUsageResponse> {
|
||
return requestAnalytics<GeminiUsageResponse>('gemini-usage', {}, 'GET');
|
||
}
|
||
|
||
export function getSourceHealth(): Promise<SourceHealthResponse> {
|
||
return requestAnalytics<SourceHealthResponse>('source-health', {}, 'GET');
|
||
}
|
||
|
||
export function getAllMatches(): Promise<MatchListItem[]> {
|
||
return requestAnalytics<MatchListItem[]>('matches', {}, 'GET');
|
||
}
|
||
|
||
export function getMatchById(matchId: string): Promise<MatchDetail> {
|
||
return requestAnalytics<MatchDetail>(`matches/${matchId}`, {}, 'GET');
|
||
}
|