Spaces:
Running
Running
| 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<string, unknown> { | |
| if (typeof value !== 'object' || value === null || Array.isArray(value)) { | |
| throw new Error(`Field "${field}" must be an object.`); | |
| } | |
| return value as Record<string, unknown>; | |
| } | |
| 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<BucketScatterRowId, Record<string, number | null>> { | |
| const item = record(value, field); | |
| const values: Partial<Record<BucketScatterRowId, Record<string, number | null>>> = {}; | |
| 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<BucketScatterRowId, Record<string, number | null>>; | |
| } | |
| 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) | |
| }; | |
| } | |