import { SUPPORTED_BUCKET_SCHEMA_VERSION } from './types'; import type { LocalizedString } from '$lib/types/i18n'; import type { BucketDrilldownMetric, BucketGroupedBars, BucketGroupedBarsModel, BucketHeatmap, BucketLatencyMs, BucketMetricMap, BucketPareto, BucketParetoModel, BucketPerformance, BucketPerformanceRow, BucketRadar, BucketRobustness, BucketRobustnessRow, BucketScatter, BucketScatterPoint, BucketScatterRowId, BucketSchemaVersion, BucketVisualizationBase, BucketVisualizationWithSubtitle } from './types'; function record(value: unknown, field: string): Record { if (typeof value !== 'object' || value === null || Array.isArray(value)) { throw new Error(`Field "${field}" must be an object.`); } return value as Record; } function array(value: unknown, field: string): unknown[] { if (!Array.isArray(value)) throw new Error(`Field "${field}" must be an array.`); return value; } function string(value: unknown, field: string) { if (typeof value !== 'string' || value.trim() === '') { throw new Error(`Field "${field}" must be a non-empty string.`); } return value; } function nullableString(value: unknown, field: string) { return value === null ? null : string(value, field); } function stringArray(value: unknown, field: string) { return array(value, field).map((item, index) => string(item, `${field}[${index}]`)); } function finiteNumber(value: unknown, field: string) { if (typeof value !== 'number' || !Number.isFinite(value)) { throw new Error(`Field "${field}" must be a finite number.`); } return value; } function nullableNumber(value: unknown, field: string) { return value === null ? null : finiteNumber(value, field); } function positiveInteger(value: unknown, field: string) { const parsed = finiteNumber(value, field); if (!Number.isInteger(parsed) || parsed < 1) { throw new Error(`Field "${field}" must be a positive integer.`); } return parsed; } function localized(value: unknown, field: string): LocalizedString { const item = record(value, field); return { en: string(item.en, `${field}.en`), ru: string(item.ru, `${field}.ru`) }; } function optionalLocalized(value: unknown, field: string) { return value === undefined ? undefined : localized(value, field); } function schema(value: unknown, field: string): BucketSchemaVersion { const version = string(value, field); if (version !== SUPPORTED_BUCKET_SCHEMA_VERSION) { throw new Error( `Unsupported schema "${version}" in ${field}; expected "${SUPPORTED_BUCKET_SCHEMA_VERSION}".` ); } return version as BucketSchemaVersion; } function base(value: unknown, field: string): BucketVisualizationBase { const item = record(value, field); return { schema_version: schema(item.schema_version, `${field}.schema_version`), title: localized(item.title, `${field}.title`) }; } function baseWithSubtitle(value: unknown, field: string): BucketVisualizationWithSubtitle { const item = record(value, field); return { ...base(item, field), subtitle: optionalLocalized(item.subtitle, `${field}.subtitle`) }; } function metricMap(value: unknown, field: string): BucketMetricMap { return Object.fromEntries( Object.entries(record(value, field)).map(([rowId, row]) => [ rowId, Object.fromEntries( Object.entries(record(row, `${field}.${rowId}`)).map(([modelId, metric]) => [ modelId, nullableNumber(metric, `${field}.${rowId}.${modelId}`) ]) ) ]) ); } function latency(value: unknown, field: string): BucketLatencyMs { const item = record(value, field); return { p50: nullableNumber(item.p50, `${field}.p50`), p95: nullableNumber(item.p95, `${field}.p95`), p99: nullableNumber(item.p99, `${field}.p99`) }; } function drilldownMetric(value: unknown, field: string): BucketDrilldownMetric { const metric = string(value, field); if (metric !== 'score' && metric !== 'f1' && metric !== 'fpr' && metric !== 'fnr') { throw new Error(`Field "${field}" has an unsupported metric.`); } return metric; } function scatterRowId(value: unknown, field: string): BucketScatterRowId { const rowId = string(value, field); if (rowId !== 'fpr' && rowId !== 'fnr' && rowId !== 'overall') { throw new Error(`Field "${field}" has an unsupported row id.`); } return rowId; } function parseScatterValues( value: unknown, rowIds: readonly BucketScatterRowId[], field: string ): Record> { const item = record(value, field); const values: Partial>> = {}; for (const rowId of rowIds) { values[rowId] = Object.fromEntries( Object.entries(record(item[rowId], `${field}.${rowId}`)).map(([modelId, metric]) => [ modelId, nullableNumber(metric, `${field}.${rowId}.${modelId}`) ]) ); } return values as Record>; } function parseScatterPoint(value: unknown, index: number): BucketScatterPoint { const field = `scatter.points[${index}]`; const item = record(value, field); return { model_id: string(item.model_id, `${field}.model_id`), fpr: nullableNumber(item.fpr, `${field}.fpr`), fnr: nullableNumber(item.fnr, `${field}.fnr`), tooltip: nullableString(item.tooltip, `${field}.tooltip`) }; } function parseGroupedBarsModel(value: unknown, index: number): BucketGroupedBarsModel { const field = `grouped_bars.models[${index}]`; const item = record(value, field); return { model_id: string(item.model_id, `${field}.model_id`), groups: Object.fromEntries( Object.entries(record(item.groups, `${field}.groups`)).map(([groupId, groupMetrics]) => { const metrics = record(groupMetrics, `${field}.groups.${groupId}`); return [ groupId, { recall: nullableNumber(metrics.recall, `${field}.groups.${groupId}.recall`), precision: nullableNumber(metrics.precision, `${field}.groups.${groupId}.precision`), f1: nullableNumber(metrics.f1, `${field}.groups.${groupId}.f1`) } ]; }) ) }; } function parseParetoModel(value: unknown, modelId: string): BucketParetoModel { const field = `pareto.by_model_id.${modelId}`; const item = record(value, field); return { latency_ms: latency(item.latency_ms, `${field}.latency_ms`), integral: nullableNumber(item.integral, `${field}.integral`), fpr: nullableNumber(item.fpr, `${field}.fpr`) }; } function parsePerformanceRow(value: unknown, index: number): BucketPerformanceRow { const field = `performance.rows[${index}]`; const item = record(value, field); return { model_id: string(item.model_id, `${field}.model_id`), rank: positiveInteger(item.rank, `${field}.rank`), latency_ms: latency(item.latency_ms, `${field}.latency_ms`), error_rate: nullableNumber(item.error_rate ?? null, `${field}.error_rate`), integral: nullableNumber(item.integral, `${field}.integral`) }; } function parseRobustnessRow(value: unknown, index: number): BucketRobustnessRow { const field = `robustness.rows[${index}]`; const item = record(value, field); return { model_id: string(item.model_id, `${field}.model_id`), score_real: nullableNumber(item.score_real, `${field}.score_real`), score_robust: nullableNumber(item.score_robust, `${field}.score_robust`), delta_score: nullableNumber(item.delta_score, `${field}.delta_score`), fnr_real: nullableNumber(item.fnr_real, `${field}.fnr_real`), fnr_robust: nullableNumber(item.fnr_robust, `${field}.fnr_robust`), delta_fnr: nullableNumber(item.delta_fnr, `${field}.delta_fnr`), fpr_real: nullableNumber(item.fpr_real ?? null, `${field}.fpr_real`), fpr_robust: nullableNumber(item.fpr_robust ?? null, `${field}.fpr_robust`), delta_fpr: nullableNumber(item.delta_fpr ?? null, `${field}.delta_fpr`), category_label: localized(item.category_label, `${field}.category_label`) }; } export function parseBucketRadar(value: unknown): BucketRadar { const item = record(value, 'radar'); return { ...baseWithSubtitle(item, 'radar'), model_ids: stringArray(item.model_ids, 'radar.model_ids'), group_ids: stringArray(item.group_ids, 'radar.group_ids'), default_model_ids: stringArray(item.default_model_ids, 'radar.default_model_ids'), values: metricMap(item.values, 'radar.values'), f1_values: metricMap(item.f1_values, 'radar.f1_values') }; } export function parseBucketScatter(value: unknown): BucketScatter { const item = record(value, 'scatter'); const rowIds = array(item.row_ids, 'scatter.row_ids').map((rowId, index) => scatterRowId(rowId, `scatter.row_ids[${index}]`) ); return { ...base(item, 'scatter'), x_axis: localized(item.x_axis, 'scatter.x_axis'), y_axis: localized(item.y_axis, 'scatter.y_axis'), points: array(item.points, 'scatter.points').map(parseScatterPoint), row_ids: rowIds, values: parseScatterValues(item.values, rowIds, 'scatter.values') }; } export function parseBucketHeatmap(value: unknown): BucketHeatmap { const item = record(value, 'heatmap'); return { ...baseWithSubtitle(item, 'heatmap'), metric: drilldownMetric(item.metric, 'heatmap.metric'), row_ids: stringArray(item.row_ids, 'heatmap.row_ids'), column_ids: stringArray(item.column_ids, 'heatmap.column_ids'), values: metricMap(item.values, 'heatmap.values') }; } export function parseBucketGroupedBars(value: unknown): BucketGroupedBars { const item = record(value, 'grouped_bars'); return { ...baseWithSubtitle(item, 'grouped_bars'), group_ids: stringArray(item.group_ids, 'grouped_bars.group_ids'), models: array(item.models, 'grouped_bars.models').map(parseGroupedBarsModel) }; } export function parseBucketPareto(value: unknown): BucketPareto { const item = record(value, 'pareto'); return { ...baseWithSubtitle(item, 'pareto'), x_axis: localized(item.x_axis, 'pareto.x_axis'), y_axis: localized(item.y_axis, 'pareto.y_axis'), by_model_id: Object.fromEntries( Object.entries(record(item.by_model_id, 'pareto.by_model_id')).map(([modelId, model]) => [ modelId, parseParetoModel(model, modelId) ]) ) }; } export function parseBucketPerformance(value: unknown): BucketPerformance { const item = record(value, 'performance'); return { ...base(item, 'performance'), rows: array(item.rows, 'performance.rows').map(parsePerformanceRow) }; } export function parseBucketRobustness(value: unknown): BucketRobustness { const item = record(value, 'robustness'); return { ...base(item, 'robustness'), rows: array(item.rows, 'robustness.rows').map(parseRobustnessRow) }; }