/** * Typed fetch wrappers for the aggregate-first mega-scale evaluation API * ("megaeval"). Mirrors the style of services/labeleval.ts. * * Async run semantics: POST /runs returns immediately with status "queued"; * results endpoints return HTTP 409 until the run is "published". */ import type { CacheStats, CompareRequest, CompareResponse, ContainerObjectsResponse, ContainerSortPreset, ContainersResponse, CreateRunRequest, DimName, DistributionsResponse, ErrorSearchRequest, ErrorSearchResponse, EvaluationQueryRequest, EvaluationQueryResponse, EvaluationRunInfo, GeneratePopulationRequest, MegaevalStatus, PopulationMeta, QualityFunnelResponse, ReviewState, ShiftResponse, SimilarityRequest, SimilarityResponse, WhyRequest, WhyResponse, } from '../types/megaeval'; async function handle(res: Response): Promise { if (!res.ok) { const body = await res.text().catch(() => ''); throw new Error(`API ${res.status}: ${body || res.statusText}`); } return res.json() as Promise; } const JSON_HEADERS = { 'Content-Type': 'application/json' }; function get(url: string): Promise { return fetch(url).then((res) => handle(res)); } function post(url: string, body?: unknown): Promise { return fetch(url, { method: 'POST', headers: JSON_HEADERS, body: body === undefined ? undefined : JSON.stringify(body), }).then((res) => handle(res)); } // ---------------------------------------------------------------- populations export const generatePopulation = (body: GeneratePopulationRequest) => post('/api/megaeval/population/generate', body); export const getPopulations = () => get<{ populations: PopulationMeta[] }>('/api/megaeval/populations'); export const getDimensions = () => get<{ dimensions: Record }>('/api/megaeval/dimensions'); // ---------------------------------------------------------------- runs export const createRun = (body: CreateRunRequest) => post('/api/megaeval/runs', body); export const getRuns = () => get<{ runs: EvaluationRunInfo[] }>('/api/megaeval/runs'); export const getRun = (runId: string) => get(`/api/megaeval/runs/${encodeURIComponent(runId)}`); export const getMegaevalStatus = () => get('/api/megaeval/status'); // ---------------------------------------------------------------- query / cache export const queryEvaluations = (body: EvaluationQueryRequest) => post('/api/evaluations/query', body); export const getCacheStats = () => get('/api/megaeval/cache'); // ---------------------------------------------------------------- run results export const getRunFunnel = (runId: string) => get(`/api/megaeval/runs/${encodeURIComponent(runId)}/funnel`); export const getRunContainers = ( runId: string, sort: ContainerSortPreset = 'highest_risk', limit = 50, offset = 0 ) => get( `/api/megaeval/runs/${encodeURIComponent(runId)}/containers?sort=${encodeURIComponent( sort )}&limit=${limit}&offset=${offset}` ); export const getContainerObjects = (runId: string, containerId: number) => get( `/api/megaeval/runs/${encodeURIComponent(runId)}/containers/${containerId}/objects` ); export const searchErrors = (body: ErrorSearchRequest) => post('/api/megaeval/errors/search', body); export const compareRuns = (body: CompareRequest) => post('/api/megaeval/compare', body); export const getRunShift = (runId: string) => get(`/api/megaeval/runs/${encodeURIComponent(runId)}/shift`); export const whyAnalysis = (body: WhyRequest) => post('/api/megaeval/why', body); export const findSimilarContainers = (body: SimilarityRequest) => post('/api/megaeval/similarity', body); // ---------------------------------------------------------------- review sampling export const getRunReview = (runId: string) => get(`/api/megaeval/runs/${encodeURIComponent(runId)}/review`); export const planReview = (runId: string, targetN?: number) => post( `/api/megaeval/runs/${encodeURIComponent(runId)}/review/plan`, targetN !== undefined ? { target_n: targetN } : {} ); export const executeReview = (runId: string) => post(`/api/megaeval/runs/${encodeURIComponent(runId)}/review/execute`); // ---------------------------------------------------------------- distributions export const getRunDistributions = (runId: string) => get(`/api/megaeval/runs/${encodeURIComponent(runId)}/distributions`); // ---------------------------------------------------------------- formatting /** Compact count formatting for large aggregates: 950 -> "950", 214_700 -> "214.7K", 1_300_000 -> "1.3M". */ export function fmtCompact(n: number | null | undefined): string { if (n === null || n === undefined || Number.isNaN(n)) return '—'; const abs = Math.abs(n); if (abs >= 1e9) return `${(n / 1e9).toFixed(1)}B`; if (abs >= 1e6) return `${(n / 1e6).toFixed(1)}M`; if (abs >= 1e3) return `${(n / 1e3).toFixed(1)}K`; return Math.round(n).toLocaleString(); }