Jenny Chim Claude Opus 4.7 (1M context) commited on
Commit
2fcae3f
·
1 Parent(s): 9b4cdbb

Add DuckDB shadow-read backend with source-metadata fix

Browse files

Two-part change behind a feature flag.

Backend dispatcher (lib/data-backend.ts) routes 11 API routes + app/page.tsx
to either lib/model-data.ts (default JSON path) or the new lib/duckdb-data.ts
based on DATA_BACKEND=duckdb. The DuckDB path reads payload_json from local
Parquet via @duckdb/node-api; pipeline-side export is gated by
EXPORT_EXPERIMENTAL_PARQUET=1 (lives in eval_cards_backend_pipeline).

For parity testing, lib/hf-data.ts gains HF_DATA_LOCAL_DIR (override the
default .cache/hf-data path) and HF_DATA_OFFLINE=1 (refuse remote fetches so
background refreshes can't poison parity tests). scripts/compare-data-backends.mjs
diffs the two backends endpoint-by-endpoint.

Source-metadata fallback (item #4 in notes/ts-to-pipeline-migration.md)
deleted: pipeline now carries source_metadata on every model_result row
(verified 86,183/86,183 production hierarchy + 43,859/43,859 eval-detail).
Removed getCanonicalSourceMetadata, buildSourceMetadataIndex, and three
inline duplicates in lib/model-data.ts. Added assertSourceMetadata runtime
guard at the four read sites to fail loud if pipeline contract ever breaks.

Category map cleanup (item #11 partial): extended PIPELINE_CATEGORY_MAP
with the 3 missing pipeline keys (coding, instruction_following,
language_understanding), all mapped to "General" to preserve prior
inferCategoryFromBenchmark behaviour. Removed the regex fallback from
mapHFCategories (now `?? "General"`). Other inferCategoryFromBenchmark call
sites kept on a leash until pipeline emits accurate category for the 84%
that currently emit "other".

One small dead-code substitution in lib/model-data.ts:1508: removed the
redundant `getModelFamilyRouteId(card.model_family_id) === modelId` clause
which is provably equivalent to `card.model_route_id === modelId` per the
pipeline contract (verified 5830/5830).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

app/api/backend-manifest/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getBackendManifestStatusData } from "@/lib/model-data"
4
 
5
  export async function GET() {
6
  const manifestStatus = await getBackendManifestStatusData()
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getBackendManifestStatusData } from "@/lib/data-backend"
4
 
5
  export async function GET() {
6
  const manifestStatus = await getBackendManifestStatusData()
app/api/data/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getDashboardData } from "@/lib/model-data"
4
 
5
  export async function GET() {
6
  const data = await getDashboardData()
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getDashboardData } from "@/lib/data-backend"
4
 
5
  export async function GET() {
6
  const data = await getDashboardData()
app/api/developer-summary/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getDeveloperSummaryById } from "@/lib/model-data"
4
 
5
  export async function GET(request: Request) {
6
  const { searchParams } = new URL(request.url)
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getDeveloperSummaryById } from "@/lib/data-backend"
4
 
5
  export async function GET(request: Request) {
6
  const { searchParams } = new URL(request.url)
app/api/developers/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getDeveloperList } from "@/lib/model-data"
4
 
5
  export async function GET() {
6
  const developers = await getDeveloperList()
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getDeveloperList } from "@/lib/data-backend"
4
 
5
  export async function GET() {
6
  const developers = await getDeveloperList()
app/api/eval-hierarchy/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getEvalHierarchyData } from "@/lib/model-data"
4
 
5
  export async function GET() {
6
  const hierarchy = await getEvalHierarchyData()
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getEvalHierarchyData } from "@/lib/data-backend"
4
 
5
  export async function GET() {
6
  const hierarchy = await getEvalHierarchyData()
app/api/eval-list-lite/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getEvalListLiteData } from "@/lib/model-data"
4
 
5
  export async function GET() {
6
  const data = await getEvalListLiteData()
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getEvalListLiteData } from "@/lib/data-backend"
4
 
5
  export async function GET() {
6
  const data = await getEvalListLiteData()
app/api/eval-list/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getEvalListData } from "@/lib/model-data"
4
 
5
  export async function GET() {
6
  const data = await getEvalListData()
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getEvalListData } from "@/lib/data-backend"
4
 
5
  export async function GET() {
6
  const data = await getEvalListData()
app/api/eval-summary/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getEvalSummaryById } from "@/lib/model-data"
4
 
5
  export async function GET(request: Request) {
6
  const { searchParams } = new URL(request.url)
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getEvalSummaryById } from "@/lib/data-backend"
4
 
5
  export async function GET(request: Request) {
6
  const { searchParams } = new URL(request.url)
app/api/model-cards-lite/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getModelCardsLite } from "@/lib/model-data"
4
 
5
  export async function GET() {
6
  const models = await getModelCardsLite()
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getModelCardsLite } from "@/lib/data-backend"
4
 
5
  export async function GET() {
6
  const models = await getModelCardsLite()
app/api/model-cards/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getModelCards } from "@/lib/model-data"
4
 
5
  export async function GET() {
6
  const models = await getModelCards()
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getModelCards } from "@/lib/data-backend"
4
 
5
  export async function GET() {
6
  const models = await getModelCards()
app/api/model-summary/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from "next/server"
2
 
3
- import { getModelSummaryById } from "@/lib/model-data"
4
 
5
  export async function GET(request: Request) {
6
  const { searchParams } = new URL(request.url)
 
1
  import { NextResponse } from "next/server"
2
 
3
+ import { getModelSummaryById } from "@/lib/data-backend"
4
 
5
  export async function GET(request: Request) {
6
  const { searchParams } = new URL(request.url)
app/page.tsx CHANGED
@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button"
3
  import { ArrowRight, BookOpenText, Database, MessageSquare, Scale } from "lucide-react"
4
  import { HomeModeLabel } from "@/components/home-mode-label"
5
  import { Navigation } from "@/components/navigation"
6
- import { getBackendManifestData, getEvalListLiteData, getModelCardsLite } from "@/lib/model-data"
7
 
8
  function formatGeneratedAt(value: string | null | undefined) {
9
  if (!value) return "Unknown"
 
3
  import { ArrowRight, BookOpenText, Database, MessageSquare, Scale } from "lucide-react"
4
  import { HomeModeLabel } from "@/components/home-mode-label"
5
  import { Navigation } from "@/components/navigation"
6
+ import { getBackendManifestData, getEvalListLiteData, getModelCardsLite } from "@/lib/data-backend"
7
 
8
  function formatGeneratedAt(value: string | null | undefined) {
9
  if (!value) return "Unknown"
lib/data-backend.ts ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "server-only"
2
+
3
+ import * as jsonData from "@/lib/model-data"
4
+
5
+ function isDuckDBBackend() {
6
+ return process.env.DATA_BACKEND?.trim().toLowerCase() === "duckdb"
7
+ }
8
+
9
+ async function duckdbData() {
10
+ return import("@/lib/duckdb-data")
11
+ }
12
+
13
+ export async function getDashboardData() {
14
+ if (isDuckDBBackend()) {
15
+ return (await duckdbData()).getDashboardDataFromDuckDB()
16
+ }
17
+
18
+ return jsonData.getDashboardData()
19
+ }
20
+
21
+ export async function getModelCards() {
22
+ if (isDuckDBBackend()) {
23
+ return (await duckdbData()).getModelCardsFromDuckDB()
24
+ }
25
+
26
+ return jsonData.getModelCards()
27
+ }
28
+
29
+ export async function getModelCardsLite() {
30
+ if (isDuckDBBackend()) {
31
+ return (await duckdbData()).getModelCardsLiteFromDuckDB()
32
+ }
33
+
34
+ return jsonData.getModelCardsLite()
35
+ }
36
+
37
+ export async function getEvalListData() {
38
+ if (isDuckDBBackend()) {
39
+ return (await duckdbData()).getEvalListDataFromDuckDB()
40
+ }
41
+
42
+ return jsonData.getEvalListData()
43
+ }
44
+
45
+ export async function getEvalListLiteData() {
46
+ if (isDuckDBBackend()) {
47
+ return (await duckdbData()).getEvalListLiteDataFromDuckDB()
48
+ }
49
+
50
+ return jsonData.getEvalListLiteData()
51
+ }
52
+
53
+ export async function getEvalList() {
54
+ if (isDuckDBBackend()) {
55
+ return (await duckdbData()).getEvalListFromDuckDB()
56
+ }
57
+
58
+ return jsonData.getEvalList()
59
+ }
60
+
61
+ export async function getDeveloperList() {
62
+ if (isDuckDBBackend()) {
63
+ return (await duckdbData()).getDeveloperListFromDuckDB()
64
+ }
65
+
66
+ return jsonData.getDeveloperList()
67
+ }
68
+
69
+ export async function getDeveloperSummaryById(routeId: string) {
70
+ if (isDuckDBBackend()) {
71
+ return (await duckdbData()).getDeveloperSummaryByIdFromDuckDB(routeId)
72
+ }
73
+
74
+ return jsonData.getDeveloperSummaryById(routeId)
75
+ }
76
+
77
+ export async function getModelSummaryById(modelId: string) {
78
+ if (isDuckDBBackend()) {
79
+ return (await duckdbData()).getModelSummaryByIdFromDuckDB(modelId)
80
+ }
81
+
82
+ return jsonData.getModelSummaryById(modelId)
83
+ }
84
+
85
+ export async function getEvalSummaryById(evalId: string) {
86
+ if (isDuckDBBackend()) {
87
+ return (await duckdbData()).getEvalSummaryByIdFromDuckDB(evalId)
88
+ }
89
+
90
+ return jsonData.getEvalSummaryById(evalId)
91
+ }
92
+
93
+ // Metadata-style artifacts are intentionally still read through the existing
94
+ // JSON/HF path during the local DuckDB experiment. They are not request-time
95
+ // processing hotspots, and keeping them unchanged avoids broadening rollout.
96
+ export const getBackendManifestData = jsonData.getBackendManifestData
97
+ export const getBackendManifestStatusData = jsonData.getBackendManifestStatusData
98
+ export const getEvalHierarchyData = jsonData.getEvalHierarchyData
lib/duckdb-data.ts ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "server-only"
2
+
3
+ import { promises as fs } from "fs"
4
+ import path from "path"
5
+
6
+ import { DuckDBConnection, type DuckDBValue } from "@duckdb/node-api"
7
+
8
+ import type { BenchmarkEvalListItem, BenchmarkEvalSummary } from "@/lib/eval-processing"
9
+ import type { EvaluationCardData, ModelEvaluationSummary } from "@/lib/benchmark-schema"
10
+ import { getBenchmarkCard } from "@/lib/benchmark-metadata"
11
+ import type { HFEvalDetail, HFModelCardEntry, HFModelDetail } from "@/lib/hf-data"
12
+ import {
13
+ attachBenchmarkCardToSummary,
14
+ getBenchmarkDisplayName,
15
+ getDeveloperBenchmarkStats,
16
+ getDeveloperRouteId,
17
+ hfDeveloperDetailToSummary,
18
+ hfEvalDetailToSummary,
19
+ hfEvalEntryToListItem,
20
+ hfModelCardToEvaluationCardData,
21
+ normalizeDeveloperName,
22
+ } from "@/lib/model-data"
23
+ import { createModelFamilySummary } from "@/lib/eval-processing"
24
+ import { flattenModelEvaluations } from "@/lib/hf-data"
25
+
26
+ const PARQUET_DIR = path.join("experimental", "parquet")
27
+ const PARQUET_FILES = {
28
+ modelCards: "model_cards.parquet",
29
+ modelCardsLite: "model_cards_lite.parquet",
30
+ evalList: "eval_list.parquet",
31
+ evalListLite: "eval_list_lite.parquet",
32
+ evalSummaries: "eval_summaries.parquet",
33
+ modelSummaries: "model_summaries.parquet",
34
+ developerSummaries: "developer_summaries.parquet",
35
+ } as const
36
+
37
+ type ParquetFileKey = keyof typeof PARQUET_FILES
38
+
39
+ let connectionPromise: Promise<DuckDBConnection> | null = null
40
+
41
+ function getPipelineOutputDir() {
42
+ const configured = process.env.LOCAL_PIPELINE_OUTPUT?.trim()
43
+ if (!configured) {
44
+ throw new Error(
45
+ "DATA_BACKEND=duckdb requires LOCAL_PIPELINE_OUTPUT to point at a local eval_cards_backend_pipeline/output directory"
46
+ )
47
+ }
48
+
49
+ return configured
50
+ }
51
+
52
+ async function getParquetPath(key: ParquetFileKey) {
53
+ const filePath = path.join(getPipelineOutputDir(), PARQUET_DIR, PARQUET_FILES[key])
54
+
55
+ try {
56
+ await fs.access(filePath)
57
+ } catch {
58
+ throw new Error(
59
+ `DuckDB backend expected ${filePath}. Run the backend pipeline with EXPORT_EXPERIMENTAL_PARQUET=1 first.`
60
+ )
61
+ }
62
+
63
+ return filePath
64
+ }
65
+
66
+ async function getConnection() {
67
+ if (!connectionPromise) {
68
+ connectionPromise = DuckDBConnection.create()
69
+ }
70
+
71
+ return connectionPromise
72
+ }
73
+
74
+ function parsePayload<T>(row: Record<string, unknown>): T {
75
+ const raw = row.payload_json
76
+ if (typeof raw !== "string") {
77
+ throw new Error("DuckDB payload row did not include a string payload_json field")
78
+ }
79
+
80
+ return JSON.parse(raw) as T
81
+ }
82
+
83
+ async function readPayloads<T>(key: ParquetFileKey, orderBy?: string): Promise<T[]> {
84
+ const connection = await getConnection()
85
+ const filePath = await getParquetPath(key)
86
+ const sql = `SELECT payload_json FROM read_parquet(?)${orderBy ? ` ${orderBy}` : ""}`
87
+ const reader = await connection.runAndReadAll(sql, [filePath])
88
+ return reader.getRowObjects().map((row) => parsePayload<T>(row))
89
+ }
90
+
91
+ async function readPayloadById<T>(
92
+ key: ParquetFileKey,
93
+ whereSql: string,
94
+ params: DuckDBValue[]
95
+ ): Promise<T | null> {
96
+ const connection = await getConnection()
97
+ const filePath = await getParquetPath(key)
98
+ const reader = await connection.runAndReadAll(
99
+ `SELECT payload_json FROM read_parquet(?) WHERE ${whereSql} LIMIT 1`,
100
+ [filePath, ...params]
101
+ )
102
+ const rows = reader.getRowObjects()
103
+ return rows.length > 0 ? parsePayload<T>(rows[0]) : null
104
+ }
105
+
106
+ async function countRows(key: ParquetFileKey) {
107
+ const connection = await getConnection()
108
+ const filePath = await getParquetPath(key)
109
+ const reader = await connection.runAndReadAll(
110
+ "SELECT count(*) AS row_count FROM read_parquet(?)",
111
+ [filePath]
112
+ )
113
+ const value = reader.getRowObjects()[0]?.row_count
114
+ return typeof value === "bigint" ? Number(value) : Number(value ?? 0)
115
+ }
116
+
117
+ function modelCardSort(a: EvaluationCardData, b: EvaluationCardData) {
118
+ return new Date(b.latest_timestamp).getTime() - new Date(a.latest_timestamp).getTime()
119
+ }
120
+
121
+ function modelCardLiteSort(a: EvaluationCardData, b: EvaluationCardData) {
122
+ return (
123
+ b.benchmarks_count - a.benchmarks_count ||
124
+ b.evaluations_count - a.evaluations_count ||
125
+ a.model_name.localeCompare(b.model_name)
126
+ )
127
+ }
128
+
129
+ function evalListSort(a: BenchmarkEvalListItem, b: BenchmarkEvalListItem) {
130
+ return (a.evaluation_name ?? "").localeCompare(b.evaluation_name ?? "")
131
+ }
132
+
133
+ async function attachBenchmarkCardsToEvalListItems(items: BenchmarkEvalListItem[]) {
134
+ return Promise.all(
135
+ items.map(async (item) => {
136
+ if (item.benchmark_card) {
137
+ return item
138
+ }
139
+
140
+ const candidates = [
141
+ item.evaluation_name,
142
+ item.composite_benchmark_key,
143
+ item.composite_benchmark_name,
144
+ ].filter(Boolean)
145
+
146
+ for (const name of candidates) {
147
+ const card = await getBenchmarkCard(name)
148
+ if (card) {
149
+ return { ...item, benchmark_card: card }
150
+ }
151
+ }
152
+
153
+ return item
154
+ })
155
+ )
156
+ }
157
+
158
+ function toEvaluationCard(entry: HFModelCardEntry | EvaluationCardData): EvaluationCardData {
159
+ if ("evaluations_count" in entry && "benchmarks_count" in entry) {
160
+ return entry as EvaluationCardData
161
+ }
162
+
163
+ return hfModelCardToEvaluationCardData(entry as HFModelCardEntry)
164
+ }
165
+
166
+ function toEvalListItem(entry: unknown): BenchmarkEvalListItem {
167
+ if (entry && typeof entry === "object" && "evaluation_id" in entry && "composite_benchmark_key" in entry) {
168
+ return entry as BenchmarkEvalListItem
169
+ }
170
+
171
+ return hfEvalEntryToListItem(entry as Parameters<typeof hfEvalEntryToListItem>[0])
172
+ }
173
+
174
+ async function toEvalSummary(payload: unknown): Promise<BenchmarkEvalSummary> {
175
+ if (payload && typeof payload === "object" && "model_results" in payload && "evaluation_id" in payload) {
176
+ return payload as BenchmarkEvalSummary
177
+ }
178
+
179
+ return attachBenchmarkCardToSummary(hfEvalDetailToSummary(payload as HFEvalDetail))
180
+ }
181
+
182
+ function toModelSummary(payload: unknown): ModelEvaluationSummary {
183
+ // The pipeline payload carries `evaluations_by_category` already, but with
184
+ // lowercase category keys, raw timestamps, and per-eval benchmark_card
185
+ // duplicates. The JSON path always re-aggregates via flattenModelEvaluations
186
+ // + createModelFamilySummary; mirror that here so parity is exact. Pushing
187
+ // the post-adapter shape into the pipeline is migration item #3 (`hierarchy
188
+ // → flat BenchmarkEvaluation[] rebuild`).
189
+ const evaluations = flattenModelEvaluations(payload as HFModelDetail)
190
+ if (evaluations.length === 0) {
191
+ throw new Error("DuckDB model summary payload did not contain any model evaluations")
192
+ }
193
+
194
+ return createModelFamilySummary(evaluations)
195
+ }
196
+
197
+ export async function getModelCardsFromDuckDB(): Promise<EvaluationCardData[]> {
198
+ const entries = await readPayloads<HFModelCardEntry | EvaluationCardData>("modelCards")
199
+ return entries.map(toEvaluationCard).sort(modelCardSort)
200
+ }
201
+
202
+ export async function getModelCardsLiteFromDuckDB(): Promise<EvaluationCardData[]> {
203
+ const entries = await readPayloads<HFModelCardEntry | EvaluationCardData>("modelCardsLite")
204
+ return entries.map(toEvaluationCard).sort(modelCardLiteSort)
205
+ }
206
+
207
+ export async function getEvalListDataFromDuckDB(): Promise<{
208
+ evals: BenchmarkEvalListItem[]
209
+ totalModels: number
210
+ }> {
211
+ const [entries, totalModels] = await Promise.all([
212
+ readPayloads<unknown>("evalList"),
213
+ countRows("modelCards"),
214
+ ])
215
+ const evals = entries
216
+ .map(toEvalListItem)
217
+ .filter((entry) => !(typeof entry.source_data?.hf_repo === "string" && entry.source_data.hf_repo.startsWith("example://")))
218
+ const evalsWithCards = await attachBenchmarkCardsToEvalListItems(evals)
219
+
220
+ return {
221
+ evals: evalsWithCards.sort(evalListSort),
222
+ totalModels,
223
+ }
224
+ }
225
+
226
+ export async function getEvalListLiteDataFromDuckDB(): Promise<{
227
+ evals: BenchmarkEvalListItem[]
228
+ totalModels: number
229
+ }> {
230
+ const [entries, totalModels] = await Promise.all([
231
+ readPayloads<unknown>("evalListLite"),
232
+ countRows("modelCardsLite"),
233
+ ])
234
+
235
+ return {
236
+ evals: entries
237
+ .map(toEvalListItem)
238
+ .filter((entry) => !(typeof entry.source_data?.hf_repo === "string" && entry.source_data.hf_repo.startsWith("example://")))
239
+ .sort(evalListSort),
240
+ totalModels,
241
+ }
242
+ }
243
+
244
+ export async function getEvalListFromDuckDB() {
245
+ const { evals } = await getEvalListDataFromDuckDB()
246
+ return evals
247
+ }
248
+
249
+ export async function getDashboardDataFromDuckDB() {
250
+ const [models, evals] = await Promise.all([
251
+ getModelCardsFromDuckDB(),
252
+ getEvalListFromDuckDB(),
253
+ ])
254
+ return { models, evals }
255
+ }
256
+
257
+ export async function getEvalSummaryByIdFromDuckDB(evalId: string) {
258
+ const payload = await readPayloadById<unknown>(
259
+ "evalSummaries",
260
+ "eval_summary_id = ?",
261
+ [evalId]
262
+ )
263
+
264
+ return payload ? toEvalSummary(payload) : null
265
+ }
266
+
267
+ export async function getModelSummaryByIdFromDuckDB(modelId: string) {
268
+ const payload = await readPayloadById<unknown>(
269
+ "modelSummaries",
270
+ "model_route_id = ? OR model_family_id = ?",
271
+ [modelId, modelId]
272
+ )
273
+
274
+ return payload ? toModelSummary(payload) : null
275
+ }
276
+
277
+ export async function getDeveloperSummaryByIdFromDuckDB(routeId: string) {
278
+ const payload = await readPayloadById<{ developer: string; models: HFModelCardEntry[] }>(
279
+ "developerSummaries",
280
+ "developer_route_id = ?",
281
+ [routeId]
282
+ )
283
+
284
+ return payload ? hfDeveloperDetailToSummary(payload) : null
285
+ }
286
+
287
+ export async function getDeveloperListFromDuckDB() {
288
+ const summaries = await readPayloads<{ developer: string; models: HFModelCardEntry[] }>("developerSummaries")
289
+
290
+ return summaries
291
+ .map((detail) => {
292
+ const benchmarkCounts = getDeveloperBenchmarkStats(detail.models)
293
+ const evaluationCount = detail.models.reduce(
294
+ (sum, model) => sum + model.total_evaluations,
295
+ 0
296
+ )
297
+ const popularEvals = Array.from(benchmarkCounts.entries())
298
+ .sort((a, b) => b[1] - a[1])
299
+ .slice(0, 3)
300
+ .map(([benchmark, model_count]) => ({
301
+ benchmark: getBenchmarkDisplayName(benchmark),
302
+ model_count,
303
+ }))
304
+
305
+ return {
306
+ developer: normalizeDeveloperName(detail.developer),
307
+ route_id: getDeveloperRouteId(detail.developer),
308
+ model_count: detail.models.length,
309
+ benchmark_count: benchmarkCounts.size,
310
+ evaluation_count: evaluationCount,
311
+ popular_evals: popularEvals,
312
+ }
313
+ })
314
+ .sort((a, b) => a.developer.localeCompare(b.developer))
315
+ }
lib/hf-data.ts CHANGED
@@ -24,7 +24,6 @@ import type {
24
  SourceData,
25
  SourceMetadata,
26
  } from "@/lib/benchmark-schema"
27
- import { inferCategoryFromBenchmark } from "@/lib/benchmark-schema"
28
  import { getCanonicalModelIdentity, getModelFamilyRouteId } from "@/lib/model-family"
29
 
30
  // ---------------------------------------------------------------------------
@@ -38,7 +37,12 @@ const HF_BASE = `https://huggingface.co/datasets/${HF_DATASET}/resolve/main`
38
  // Local disk cache (populated by scripts/cache-hf-data.mjs during build)
39
  // ---------------------------------------------------------------------------
40
 
41
- const LOCAL_CACHE_DIR = path.join(process.cwd(), ".cache", "hf-data")
 
 
 
 
 
42
 
43
  async function readLocalCache<T>(relativePath: string): Promise<T | null> {
44
  try {
@@ -129,7 +133,17 @@ function getManifestSignature(manifest: BackendManifest | null | undefined) {
129
  })
130
  }
131
 
 
 
 
 
 
 
132
  async function fetchRemoteJson<T>(relativePath: string): Promise<T> {
 
 
 
 
133
  const url = `${HF_BASE}/${relativePath}`
134
  let lastError: Error | null = null
135
 
@@ -539,10 +553,9 @@ export interface HFEvalModelResult {
539
  evaluation_id?: string
540
  retrieved_timestamp?: string
541
  source_record_url?: string
542
- // Populated by pipeline versions that copy the parent record's provenance
543
- // straight onto each hierarchy row. Older exports omit these; fall back to
544
- // the evaluations_by_category index when missing.
545
- source_metadata?: SourceMetadata
546
  source_data?: SourceData | string[]
547
  detailed_evaluation_results?: string | null
548
  detailed_evaluation_results_meta?: unknown
@@ -573,6 +586,7 @@ export interface HFEvalDetail extends SignalSummaries {
573
  benchmark_leaf_name: string
574
  benchmark_parent_key?: string
575
  benchmark_parent_name?: string
 
576
  source_data: SourceData
577
  benchmark_card: BenchmarkCard | null
578
  metrics: HFEvalMetric[]
@@ -685,7 +699,7 @@ export interface HFModelHierarchyNode {
685
  }
686
  }
687
 
688
- type HFModelHierarchySubtask = Partial<Omit<HFModelHierarchyNode, "subtasks">> & {
689
  subtask_key?: string
690
  subtask_name?: string
691
  canonical_display_name?: string
@@ -1157,23 +1171,6 @@ function toComparableTimestamp(timestamp: string | undefined) {
1157
  return Number.isFinite(parsedTimestamp) ? parsedTimestamp : Number.NEGATIVE_INFINITY
1158
  }
1159
 
1160
- function getCanonicalSourceMetadata(
1161
- sourceData: SourceData | undefined,
1162
- fallback: { displayName?: string; benchmarkFamilyName?: string }
1163
- ): SourceMetadata {
1164
- const sourceName = sourceData?.hf_repo ?? sourceData?.dataset_name ?? fallback.displayName
1165
- const sourceOrganizationName =
1166
- sourceData?.hf_repo?.split("/")[0] ?? sourceData?.dataset_name ?? fallback.benchmarkFamilyName
1167
-
1168
- return {
1169
- source_name: sourceName,
1170
- source_type: sourceData?.source_type === "url" ? "leaderboard" : "evaluation_run",
1171
- source_organization_name: sourceOrganizationName ?? "Unknown",
1172
- source_organization_url: sourceData?.url?.[0],
1173
- evaluator_relationship: "other",
1174
- }
1175
- }
1176
-
1177
  function buildVariantLookup(detail: HFModelDetail) {
1178
  const variantLookup = new Map<string, { variantKey: string; variantLabel: string }>()
1179
 
@@ -1292,7 +1289,6 @@ interface FlattenHierarchyContext {
1292
  display_name?: string
1293
  canonical_display_name?: string
1294
  sourceData: SourceData
1295
- sourceMetadata: SourceMetadata
1296
  benchmark_family_key?: string
1297
  benchmark_family_name?: string
1298
  benchmark_parent_key?: string
@@ -1331,10 +1327,6 @@ function buildFlattenHierarchyContext(
1331
  display_name: displayName,
1332
  canonical_display_name: canonicalDisplayName,
1333
  sourceData,
1334
- sourceMetadata: getCanonicalSourceMetadata(sourceData, {
1335
- displayName,
1336
- benchmarkFamilyName,
1337
- }),
1338
  benchmark_family_key: node.benchmark_family_key ?? inheritedContext?.benchmark_family_key,
1339
  benchmark_family_name: benchmarkFamilyName,
1340
  benchmark_parent_key: node.benchmark_parent_key ?? inheritedContext?.benchmark_parent_key,
@@ -1350,13 +1342,11 @@ function flattenHierarchyNode(
1350
  category: CategoryType,
1351
  rawModelIds: Set<string>,
1352
  variantLookup: Map<string, { variantKey: string; variantLabel: string }>,
1353
- inheritedContext?: FlattenHierarchyContext,
1354
- sourceMetadataByEvaluationId?: Map<string, SourceMetadata>
1355
  ): BenchmarkEvaluation[] {
1356
  const evaluations: BenchmarkEvaluation[] = []
1357
  const context = buildFlattenHierarchyContext(node, inheritedContext)
1358
  const sourceData = context.sourceData
1359
- const sourceMetadata = context.sourceMetadata
1360
 
1361
  for (const metric of node.metrics ?? []) {
1362
  const relevantResults = (metric.model_results ?? []).filter((result) =>
@@ -1374,22 +1364,24 @@ function flattenHierarchyNode(
1374
  evaluationResults: EvaluationResult[]
1375
  inlineSamples?: SampleResult[]
1376
  latestTimestamp: string
1377
- sourceMetadataOverride?: SourceMetadata
1378
  }
1379
  >()
1380
 
1381
  for (const result of relevantResults) {
 
 
 
 
 
 
 
 
 
1382
  const variantMeta = resolveVariantMeta(detail, variantLookup, result)
1383
  const variantKey = variantMeta.variantKey || "default"
1384
  const modelInfo = buildModelInfoForVariant(detail, result, variantMeta)
1385
  const inlineSamples = parseInstanceLevelData(result.instance_level_data)
1386
- // Prefer source_metadata carried directly on the result row (populated
1387
- // by newer pipeline runs). Fall back to the by-evaluation-id index built
1388
- // from evaluations_by_category, which older exports still need.
1389
- const evaluationIdForResult = result.evaluation_id
1390
- const resolvedSourceMetadata: SourceMetadata | undefined =
1391
- result.source_metadata ??
1392
- (evaluationIdForResult ? sourceMetadataByEvaluationId?.get(evaluationIdForResult) : undefined)
1393
  const evaluationResult: EvaluationResult = {
1394
  evaluation_name: metric.metric_name || metric.evaluation_name || metric.display_name,
1395
  display_name: metric.display_name || metric.metric_name || metric.evaluation_name,
@@ -1418,7 +1410,7 @@ function flattenHierarchyNode(
1418
  evaluationResults: [evaluationResult],
1419
  inlineSamples: inlineSamples.length > 0 ? inlineSamples : undefined,
1420
  latestTimestamp: result.retrieved_timestamp ?? detail.last_updated ?? "",
1421
- sourceMetadataOverride: resolvedSourceMetadata,
1422
  })
1423
  continue
1424
  }
@@ -1431,14 +1423,10 @@ function flattenHierarchyNode(
1431
  toComparableTimestamp(result.retrieved_timestamp) >=
1432
  toComparableTimestamp(existing.latestTimestamp)
1433
  ) {
 
 
1434
  existing.latestTimestamp = result.retrieved_timestamp ?? existing.latestTimestamp
1435
- // Prefer source_metadata from the freshest submission when multiple
1436
- // submissions land in the same variant bucket.
1437
- if (resolvedSourceMetadata) {
1438
- existing.sourceMetadataOverride = resolvedSourceMetadata
1439
- }
1440
- } else if (!existing.sourceMetadataOverride && resolvedSourceMetadata) {
1441
- existing.sourceMetadataOverride = resolvedSourceMetadata
1442
  }
1443
  }
1444
 
@@ -1474,7 +1462,7 @@ function flattenHierarchyNode(
1474
  slice_key: sliceKey,
1475
  slice_name: sliceName,
1476
  source_data: sourceData,
1477
- source_metadata: variantGroup.sourceMetadataOverride ?? sourceMetadata,
1478
  model_info: variantGroup.modelInfo,
1479
  evaluation_results: variantGroup.evaluationResults,
1480
  detailed_evaluation_results_per_samples:
@@ -1493,8 +1481,7 @@ function flattenHierarchyNode(
1493
  category,
1494
  rawModelIds,
1495
  variantLookup,
1496
- context,
1497
- sourceMetadataByEvaluationId
1498
  )
1499
  )
1500
  }
@@ -1519,13 +1506,6 @@ export function flattenModelEvaluations(detail: HFModelDetail): BenchmarkEvaluat
1519
  .filter(Boolean)
1520
  )
1521
  const variantLookup = buildVariantLookup(detail)
1522
- // evaluations_by_category carries the authoritative source_metadata straight
1523
- // from the pipeline. The hierarchy branch of this detail file doesn't, so
1524
- // we build a (evaluation_id -> source_metadata) index here and look values
1525
- // up per-result inside flattenHierarchyNode. Without this the hierarchy
1526
- // fallback below hardcodes evaluator_relationship to "other" and every
1527
- // 1st/3rd-party badge in the UI collapses to "Other".
1528
- const sourceMetadataByEvaluationId = buildSourceMetadataIndex(detail)
1529
 
1530
  for (const [categoryKey, nodes] of Object.entries(detail.hierarchy_by_category ?? {})) {
1531
  const mappedCategory = mapHFCategories([categoryKey])[0]
@@ -1536,9 +1516,7 @@ export function flattenModelEvaluations(detail: HFModelDetail): BenchmarkEvaluat
1536
  node,
1537
  mappedCategory,
1538
  rawModelIds,
1539
- variantLookup,
1540
- undefined,
1541
- sourceMetadataByEvaluationId
1542
  )
1543
  )
1544
  }
@@ -1547,21 +1525,15 @@ export function flattenModelEvaluations(detail: HFModelDetail): BenchmarkEvaluat
1547
  return evaluations
1548
  }
1549
 
1550
- function buildSourceMetadataIndex(detail: HFModelDetail): Map<string, SourceMetadata> {
1551
- const index = new Map<string, SourceMetadata>()
1552
- for (const evals of Object.values(detail.evaluations_by_category ?? {})) {
1553
- for (const evaluation of evals ?? []) {
1554
- if (evaluation?.source_metadata && evaluation.evaluation_id) {
1555
- index.set(evaluation.evaluation_id, evaluation.source_metadata)
1556
- }
1557
- }
1558
- }
1559
- return index
1560
- }
1561
-
1562
  /**
1563
  * Map pipeline category labels to frontend CategoryType.
1564
  */
 
 
 
 
 
 
1565
  const PIPELINE_CATEGORY_MAP: Record<string, CategoryType> = {
1566
  agentic: "Agentic",
1567
  reasoning: "Reasoning",
@@ -1569,13 +1541,16 @@ const PIPELINE_CATEGORY_MAP: Record<string, CategoryType> = {
1569
  safety: "Safety",
1570
  knowledge: "Knowledge",
1571
  other: "General",
 
 
 
1572
  }
1573
 
1574
  export function mapHFCategories(categories: string[]): CategoryType[] {
1575
  const mapped: CategoryType[] = []
1576
  for (const c of categories) {
1577
  if (!c) continue
1578
- const cat = PIPELINE_CATEGORY_MAP[c.toLowerCase()] ?? inferCategoryFromBenchmark(c)
1579
  if (!mapped.includes(cat)) mapped.push(cat)
1580
  }
1581
  return mapped.length > 0 ? mapped : ["General"]
 
24
  SourceData,
25
  SourceMetadata,
26
  } from "@/lib/benchmark-schema"
 
27
  import { getCanonicalModelIdentity, getModelFamilyRouteId } from "@/lib/model-family"
28
 
29
  // ---------------------------------------------------------------------------
 
37
  // Local disk cache (populated by scripts/cache-hf-data.mjs during build)
38
  // ---------------------------------------------------------------------------
39
 
40
+ // HF_DATA_LOCAL_DIR overrides the cache location so the JSON read path can be
41
+ // pointed at a sibling repo's pipeline output for parity testing against the
42
+ // DuckDB backend. Falls back to the cache populated by scripts/cache-hf-data.mjs.
43
+ const LOCAL_CACHE_DIR = process.env.HF_DATA_LOCAL_DIR?.trim()
44
+ ? path.resolve(process.env.HF_DATA_LOCAL_DIR.trim())
45
+ : path.join(process.cwd(), ".cache", "hf-data")
46
 
47
  async function readLocalCache<T>(relativePath: string): Promise<T | null> {
48
  try {
 
133
  })
134
  }
135
 
136
+ // HF_DATA_OFFLINE disables every network fetch, so the read path is fully
137
+ // served by LOCAL_CACHE_DIR. Used by the DuckDB parity setup so two servers
138
+ // reading the same on-disk artifacts cannot diverge mid-test via background
139
+ // refresh, and useful generally for offline development.
140
+ const OFFLINE = process.env.HF_DATA_OFFLINE === "1"
141
+
142
  async function fetchRemoteJson<T>(relativePath: string): Promise<T> {
143
+ if (OFFLINE) {
144
+ throw new Error(`HF_DATA_OFFLINE=1: refusing remote fetch for ${relativePath}`)
145
+ }
146
+
147
  const url = `${HF_BASE}/${relativePath}`
148
  let lastError: Error | null = null
149
 
 
553
  evaluation_id?: string
554
  retrieved_timestamp?: string
555
  source_record_url?: string
556
+ // The pipeline copies the parent record's provenance onto every hierarchy
557
+ // model_result row (commit 9090cc5, 2026-04-26). Required.
558
+ source_metadata: SourceMetadata
 
559
  source_data?: SourceData | string[]
560
  detailed_evaluation_results?: string | null
561
  detailed_evaluation_results_meta?: unknown
 
586
  benchmark_leaf_name: string
587
  benchmark_parent_key?: string
588
  benchmark_parent_name?: string
589
+ category: string
590
  source_data: SourceData
591
  benchmark_card: BenchmarkCard | null
592
  metrics: HFEvalMetric[]
 
699
  }
700
  }
701
 
702
+ export type HFModelHierarchySubtask = Partial<Omit<HFModelHierarchyNode, "subtasks">> & {
703
  subtask_key?: string
704
  subtask_name?: string
705
  canonical_display_name?: string
 
1171
  return Number.isFinite(parsedTimestamp) ? parsedTimestamp : Number.NEGATIVE_INFINITY
1172
  }
1173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1174
  function buildVariantLookup(detail: HFModelDetail) {
1175
  const variantLookup = new Map<string, { variantKey: string; variantLabel: string }>()
1176
 
 
1289
  display_name?: string
1290
  canonical_display_name?: string
1291
  sourceData: SourceData
 
1292
  benchmark_family_key?: string
1293
  benchmark_family_name?: string
1294
  benchmark_parent_key?: string
 
1327
  display_name: displayName,
1328
  canonical_display_name: canonicalDisplayName,
1329
  sourceData,
 
 
 
 
1330
  benchmark_family_key: node.benchmark_family_key ?? inheritedContext?.benchmark_family_key,
1331
  benchmark_family_name: benchmarkFamilyName,
1332
  benchmark_parent_key: node.benchmark_parent_key ?? inheritedContext?.benchmark_parent_key,
 
1342
  category: CategoryType,
1343
  rawModelIds: Set<string>,
1344
  variantLookup: Map<string, { variantKey: string; variantLabel: string }>,
1345
+ inheritedContext?: FlattenHierarchyContext
 
1346
  ): BenchmarkEvaluation[] {
1347
  const evaluations: BenchmarkEvaluation[] = []
1348
  const context = buildFlattenHierarchyContext(node, inheritedContext)
1349
  const sourceData = context.sourceData
 
1350
 
1351
  for (const metric of node.metrics ?? []) {
1352
  const relevantResults = (metric.model_results ?? []).filter((result) =>
 
1364
  evaluationResults: EvaluationResult[]
1365
  inlineSamples?: SampleResult[]
1366
  latestTimestamp: string
1367
+ sourceMetadata: SourceMetadata
1368
  }
1369
  >()
1370
 
1371
  for (const result of relevantResults) {
1372
+ // Pipeline contract (commit 9090cc5): every model_result row carries
1373
+ // source_metadata. Fail loud if a stale dataset breaks the contract —
1374
+ // the UI dereferences source_metadata.* unguarded.
1375
+ if (!result.source_metadata) {
1376
+ throw new Error(
1377
+ `Pipeline contract broken: missing source_metadata on model_result ` +
1378
+ `(model_family=${detail.model_family_id} metric=${metric.metric_summary_id} eval=${result.evaluation_id})`
1379
+ )
1380
+ }
1381
  const variantMeta = resolveVariantMeta(detail, variantLookup, result)
1382
  const variantKey = variantMeta.variantKey || "default"
1383
  const modelInfo = buildModelInfoForVariant(detail, result, variantMeta)
1384
  const inlineSamples = parseInstanceLevelData(result.instance_level_data)
 
 
 
 
 
 
 
1385
  const evaluationResult: EvaluationResult = {
1386
  evaluation_name: metric.metric_name || metric.evaluation_name || metric.display_name,
1387
  display_name: metric.display_name || metric.metric_name || metric.evaluation_name,
 
1410
  evaluationResults: [evaluationResult],
1411
  inlineSamples: inlineSamples.length > 0 ? inlineSamples : undefined,
1412
  latestTimestamp: result.retrieved_timestamp ?? detail.last_updated ?? "",
1413
+ sourceMetadata: result.source_metadata,
1414
  })
1415
  continue
1416
  }
 
1423
  toComparableTimestamp(result.retrieved_timestamp) >=
1424
  toComparableTimestamp(existing.latestTimestamp)
1425
  ) {
1426
+ // When multiple submissions land in the same variant bucket, prefer
1427
+ // provenance from the freshest one.
1428
  existing.latestTimestamp = result.retrieved_timestamp ?? existing.latestTimestamp
1429
+ existing.sourceMetadata = result.source_metadata
 
 
 
 
 
 
1430
  }
1431
  }
1432
 
 
1462
  slice_key: sliceKey,
1463
  slice_name: sliceName,
1464
  source_data: sourceData,
1465
+ source_metadata: variantGroup.sourceMetadata,
1466
  model_info: variantGroup.modelInfo,
1467
  evaluation_results: variantGroup.evaluationResults,
1468
  detailed_evaluation_results_per_samples:
 
1481
  category,
1482
  rawModelIds,
1483
  variantLookup,
1484
+ context
 
1485
  )
1486
  )
1487
  }
 
1506
  .filter(Boolean)
1507
  )
1508
  const variantLookup = buildVariantLookup(detail)
 
 
 
 
 
 
 
1509
 
1510
  for (const [categoryKey, nodes] of Object.entries(detail.hierarchy_by_category ?? {})) {
1511
  const mappedCategory = mapHFCategories([categoryKey])[0]
 
1516
  node,
1517
  mappedCategory,
1518
  rawModelIds,
1519
+ variantLookup
 
 
1520
  )
1521
  )
1522
  }
 
1525
  return evaluations
1526
  }
1527
 
 
 
 
 
 
 
 
 
 
 
 
 
1528
  /**
1529
  * Map pipeline category labels to frontend CategoryType.
1530
  */
1531
+ // Every category key emitted by the pipeline (verified against production
1532
+ // dataset 2026-04-27, 9 distinct keys total). Values for the 3 added keys
1533
+ // (coding, instruction_following, language_understanding) match what the
1534
+ // previous regex fallback returned for them, preserving prior labelling.
1535
+ // Note: `coding` maps to General because "coding" does not contain the
1536
+ // substring "code" — see lib/benchmark-schema.ts inferCategoryFromBenchmark.
1537
  const PIPELINE_CATEGORY_MAP: Record<string, CategoryType> = {
1538
  agentic: "Agentic",
1539
  reasoning: "Reasoning",
 
1541
  safety: "Safety",
1542
  knowledge: "Knowledge",
1543
  other: "General",
1544
+ coding: "General",
1545
+ instruction_following: "General",
1546
+ language_understanding: "General",
1547
  }
1548
 
1549
  export function mapHFCategories(categories: string[]): CategoryType[] {
1550
  const mapped: CategoryType[] = []
1551
  for (const c of categories) {
1552
  if (!c) continue
1553
+ const cat = PIPELINE_CATEGORY_MAP[c.toLowerCase()] ?? "General"
1554
  if (!mapped.includes(cat)) mapped.push(cat)
1555
  }
1556
  return mapped.length > 0 ? mapped : ["General"]
lib/model-data.ts CHANGED
@@ -8,7 +8,6 @@ import type {
8
  EvaluationResult,
9
  ModelInfo,
10
  SourceData,
11
- SourceMetadata,
12
  } from "@/lib/benchmark-schema"
13
  import type { BackendManifest, BackendManifestStatus, EvalHierarchy } from "@/lib/backend-artifacts"
14
  import { inferCategoryFromBenchmark } from "@/lib/benchmark-schema"
@@ -50,6 +49,23 @@ import {
50
  // Helpers
51
  // ---------------------------------------------------------------------------
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  function slugifyEvalId(value: string) {
54
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "")
55
  }
@@ -128,7 +144,7 @@ function normalizeBenchmarkKeyForLookup(key: string) {
128
  return key.toLowerCase().replace(/[-.\s]+/g, "_").replace(/^_+|_+$/g, "")
129
  }
130
 
131
- function getBenchmarkDisplayName(benchmark: string) {
132
  return BENCHMARK_NAMES[normalizeBenchmarkKeyForLookup(benchmark)] ?? humanizeToken(benchmark)
133
  }
134
 
@@ -141,7 +157,7 @@ function pipelineSlugify(text: string) {
141
  )
142
  }
143
 
144
- function getDeveloperRouteId(developer: string) {
145
  return pipelineSlugify(developer.trim().toLowerCase())
146
  }
147
 
@@ -169,7 +185,7 @@ function getModelDetailSlugCandidates(modelId: string): string[] {
169
  return Array.from(candidates)
170
  }
171
 
172
- function getDeveloperSlugCandidates(developerOrRouteId: string): string[] {
173
  const normalized = developerOrRouteId.trim()
174
  const candidates = new Set<string>()
175
  const lowercase = normalized.toLowerCase()
@@ -218,7 +234,7 @@ const KNOWN_DEVELOPER_NAMES: Record<string, string> = {
218
  "x-ai": "xAI",
219
  }
220
 
221
- function normalizeDeveloperName(name: string): string {
222
  const key = name.trim().toLowerCase()
223
  if (KNOWN_DEVELOPER_NAMES[key]) return KNOWN_DEVELOPER_NAMES[key]
224
  // Title-case if the name is all-lowercase and not a compound like "01-ai"
@@ -277,7 +293,7 @@ function getModelCardTopScores(entry: HFModelCardEntry): EvaluationCardData["top
277
  ]
278
  }
279
 
280
- function getDeveloperBenchmarkStats(models: HFModelCardEntry[]) {
281
  const benchmarkCounts = new Map<string, number>()
282
 
283
  for (const model of models) {
@@ -360,7 +376,7 @@ function attachModelSignalSummaries<T extends ReturnType<typeof createModelFamil
360
  // HF model-cards.json → EvaluationCardData
361
  // ---------------------------------------------------------------------------
362
 
363
- function hfModelCardToEvaluationCardData(entry: HFModelCardEntry): EvaluationCardData {
364
  const canonicalIdentity = getCanonicalModelIdentity({
365
  id: entry.model_family_id,
366
  name: entry.model_family_name,
@@ -430,7 +446,7 @@ function hfModelCardToEvaluationCardData(entry: HFModelCardEntry): EvaluationCar
430
  // HF eval-list.json → BenchmarkEvalListItem
431
  // ---------------------------------------------------------------------------
432
 
433
- function hfEvalEntryToListItem(entry: HFEvalListEntry): BenchmarkEvalListItem {
434
  // Use the pipeline's category directly, mapped to our CategoryType
435
  const category = mapHFCategories([entry.category])[0] ?? "General" as CategoryType
436
 
@@ -593,14 +609,6 @@ function extractBenchmarkSubtasks(detail: HFEvalDetail): NonNullable<BenchmarkEv
593
 
594
  function buildBenchmarkLeaderboardMatrix(detail: HFEvalDetail) {
595
  const benchmarkKey = detail.benchmark ?? ""
596
- const sourceName = detail.source_data?.dataset_name || benchmarkKey
597
- const sourceOrganization = detail.source_data?.hf_repo || sourceName
598
- const sourceMetadata: SourceMetadata = {
599
- source_type: "documentation",
600
- source_name: sourceName,
601
- source_organization_name: sourceOrganization,
602
- evaluator_relationship: "other",
603
- }
604
  const sourceData = detail.source_data ?? { dataset_name: benchmarkKey }
605
 
606
  const leaderboardMetrics: NonNullable<BenchmarkEvalSummary["leaderboard_metrics"]> = []
@@ -639,6 +647,7 @@ function buildBenchmarkLeaderboardMatrix(detail: HFEvalDetail) {
639
  if (!modelId) {
640
  continue
641
  }
 
642
 
643
  const nextTimestamp = normalizeEvalTimestamp(modelResult.retrieved_timestamp ?? "")
644
  const existing = rowStates.get(modelId)
@@ -651,7 +660,7 @@ function buildBenchmarkLeaderboardMatrix(detail: HFEvalDetail) {
651
  },
652
  model_route_id: modelResult.model_route_id,
653
  evaluation_timestamp: modelResult.retrieved_timestamp ?? "",
654
- source_metadata: sourceMetadata,
655
  source_data: sourceData,
656
  values: { [columnKey]: modelResult.score ?? null },
657
  annotations_by_metric: { [columnKey]: modelResult.evalcards?.annotations ?? null },
@@ -708,11 +717,10 @@ function toModelResultsForMetric(
708
  metric: HFEvalDetail["metrics"][number]
709
  ): ModelResultForBenchmark[] {
710
  const benchmarkKey = detail.benchmark ?? ""
711
- const sourceName = detail.source_data?.dataset_name || benchmarkKey
712
- const sourceOrganization = detail.source_data?.hf_repo || sourceName
713
  const metricConfig = toSummaryMetricConfig(metric)
714
 
715
  return (metric.model_results ?? []).map((mr) => {
 
716
  const evaluationTimestamp = mr.retrieved_timestamp ?? ""
717
  const modelInfo: ModelInfo = {
718
  name: mr.model_name ?? "",
@@ -741,12 +749,7 @@ function toModelResultsForMetric(
741
  score: mr.score ?? 0,
742
  score_details: { score: mr.score ?? 0 },
743
  evaluation_timestamp: evaluationTimestamp,
744
- source_metadata: {
745
- source_type: "documentation" as const,
746
- source_name: sourceName,
747
- source_organization_name: sourceOrganization,
748
- evaluator_relationship: "other" as const,
749
- },
750
  source_data: detail.source_data ?? { dataset_name: benchmarkKey },
751
  result: evaluationResult,
752
  source_record_url: mr.source_record_url,
@@ -754,7 +757,7 @@ function toModelResultsForMetric(
754
  })
755
  }
756
 
757
- function hfEvalDetailToSummary(detail: HFEvalDetail): BenchmarkEvalSummary {
758
  const evalName = detail.benchmark_leaf_name || detail.eval_summary_id || "Unknown"
759
  const benchmarkKey = detail.benchmark ?? ""
760
  const allMetrics = detail.metrics ?? []
@@ -873,7 +876,7 @@ function hfEvalDetailToSummary(detail: HFEvalDetail): BenchmarkEvalSummary {
873
  // Aggregation (for aggregate eval summaries)
874
  // ---------------------------------------------------------------------------
875
 
876
- async function attachBenchmarkCardToSummary(summary: BenchmarkEvalSummary): Promise<BenchmarkEvalSummary> {
877
  if (summary.benchmark_card) return summary
878
 
879
  const candidates = [
@@ -1128,14 +1131,6 @@ function buildSingleMetricSuiteMatrixSummary(
1128
  })
1129
 
1130
  const benchmarkKey = detail.benchmark ?? suiteKey
1131
- const sourceName = detail.source_data?.dataset_name || benchmarkKey
1132
- const sourceOrganization = detail.source_data?.hf_repo || sourceName
1133
- const sourceMetadata: SourceMetadata = {
1134
- source_type: "documentation",
1135
- source_name: sourceName,
1136
- source_organization_name: sourceOrganization,
1137
- evaluator_relationship: "other",
1138
- }
1139
  const sourceData = detail.source_data ?? { dataset_name: benchmarkKey }
1140
 
1141
  for (const modelResult of metric.model_results ?? []) {
@@ -1143,6 +1138,7 @@ function buildSingleMetricSuiteMatrixSummary(
1143
  if (!modelId) {
1144
  continue
1145
  }
 
1146
 
1147
  const nextTimestamp = normalizeEvalTimestamp(modelResult.retrieved_timestamp ?? "")
1148
  const existing = rowStates.get(modelId)
@@ -1156,7 +1152,7 @@ function buildSingleMetricSuiteMatrixSummary(
1156
  },
1157
  model_route_id: modelResult.model_route_id,
1158
  evaluation_timestamp: modelResult.retrieved_timestamp ?? "",
1159
- source_metadata: sourceMetadata,
1160
  source_data: sourceData,
1161
  values: { [columnKey]: modelResult.score ?? null },
1162
  annotations_by_metric: { [columnKey]: modelResult.evalcards?.annotations ?? null },
@@ -1176,7 +1172,7 @@ function buildSingleMetricSuiteMatrixSummary(
1176
  }
1177
  if (nextTimestamp >= existing._timestampValue) {
1178
  existing.evaluation_timestamp = modelResult.retrieved_timestamp ?? existing.evaluation_timestamp
1179
- existing.source_metadata = sourceMetadata
1180
  existing.source_data = sourceData
1181
  existing._timestampValue = nextTimestamp
1182
  }
@@ -1407,6 +1403,35 @@ export async function getDeveloperList() {
1407
  return details.sort((a, b) => a.developer.localeCompare(b.developer))
1408
  }
1409
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1410
  export async function getDeveloperSummaryById(routeId: string) {
1411
  // Try direct slug lookup
1412
  for (const slug of getDeveloperSlugCandidates(routeId)) {
@@ -1498,13 +1523,16 @@ export async function getModelSummaryById(modelId: string) {
1498
  }
1499
  }
1500
 
1501
- // Try model-cards.json to find the right slug
 
 
 
 
1502
  const modelCards = await fetchModelCardsList()
1503
  const matchedCard = modelCards.find(
1504
  (card) =>
1505
  card.model_family_id === modelId ||
1506
- card.model_route_id === modelId ||
1507
- getModelFamilyRouteId(card.model_family_id) === modelId
1508
  )
1509
 
1510
  if (matchedCard) {
 
8
  EvaluationResult,
9
  ModelInfo,
10
  SourceData,
 
11
  } from "@/lib/benchmark-schema"
12
  import type { BackendManifest, BackendManifestStatus, EvalHierarchy } from "@/lib/backend-artifacts"
13
  import { inferCategoryFromBenchmark } from "@/lib/benchmark-schema"
 
49
  // Helpers
50
  // ---------------------------------------------------------------------------
51
 
52
+ // Pipeline contract: every model_result row carries source_metadata. UI
53
+ // components dereference source_metadata.evaluator_relationship etc. without
54
+ // optional chaining (~30 sites in components/benchmark-detail.tsx), so a
55
+ // silent undefined would surface as a TypeError mid-page-render. Fail loud
56
+ // at the read boundary instead so the offending row is identifiable.
57
+ function assertSourceMetadata(
58
+ result: { source_metadata?: unknown; evaluation_id?: string; model_id?: string },
59
+ context: string
60
+ ): asserts result is typeof result & { source_metadata: NonNullable<typeof result.source_metadata> } {
61
+ if (!result.source_metadata) {
62
+ throw new Error(
63
+ `Pipeline contract broken: missing source_metadata on model_result ` +
64
+ `(${context} model=${result.model_id ?? "?"} eval=${result.evaluation_id ?? "?"})`
65
+ )
66
+ }
67
+ }
68
+
69
  function slugifyEvalId(value: string) {
70
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "")
71
  }
 
144
  return key.toLowerCase().replace(/[-.\s]+/g, "_").replace(/^_+|_+$/g, "")
145
  }
146
 
147
+ export function getBenchmarkDisplayName(benchmark: string) {
148
  return BENCHMARK_NAMES[normalizeBenchmarkKeyForLookup(benchmark)] ?? humanizeToken(benchmark)
149
  }
150
 
 
157
  )
158
  }
159
 
160
+ export function getDeveloperRouteId(developer: string) {
161
  return pipelineSlugify(developer.trim().toLowerCase())
162
  }
163
 
 
185
  return Array.from(candidates)
186
  }
187
 
188
+ export function getDeveloperSlugCandidates(developerOrRouteId: string): string[] {
189
  const normalized = developerOrRouteId.trim()
190
  const candidates = new Set<string>()
191
  const lowercase = normalized.toLowerCase()
 
234
  "x-ai": "xAI",
235
  }
236
 
237
+ export function normalizeDeveloperName(name: string): string {
238
  const key = name.trim().toLowerCase()
239
  if (KNOWN_DEVELOPER_NAMES[key]) return KNOWN_DEVELOPER_NAMES[key]
240
  // Title-case if the name is all-lowercase and not a compound like "01-ai"
 
293
  ]
294
  }
295
 
296
+ export function getDeveloperBenchmarkStats(models: HFModelCardEntry[]) {
297
  const benchmarkCounts = new Map<string, number>()
298
 
299
  for (const model of models) {
 
376
  // HF model-cards.json → EvaluationCardData
377
  // ---------------------------------------------------------------------------
378
 
379
+ export function hfModelCardToEvaluationCardData(entry: HFModelCardEntry): EvaluationCardData {
380
  const canonicalIdentity = getCanonicalModelIdentity({
381
  id: entry.model_family_id,
382
  name: entry.model_family_name,
 
446
  // HF eval-list.json → BenchmarkEvalListItem
447
  // ---------------------------------------------------------------------------
448
 
449
+ export function hfEvalEntryToListItem(entry: HFEvalListEntry): BenchmarkEvalListItem {
450
  // Use the pipeline's category directly, mapped to our CategoryType
451
  const category = mapHFCategories([entry.category])[0] ?? "General" as CategoryType
452
 
 
609
 
610
  function buildBenchmarkLeaderboardMatrix(detail: HFEvalDetail) {
611
  const benchmarkKey = detail.benchmark ?? ""
 
 
 
 
 
 
 
 
612
  const sourceData = detail.source_data ?? { dataset_name: benchmarkKey }
613
 
614
  const leaderboardMetrics: NonNullable<BenchmarkEvalSummary["leaderboard_metrics"]> = []
 
647
  if (!modelId) {
648
  continue
649
  }
650
+ assertSourceMetadata(modelResult, `eval=${detail.eval_summary_id} metric=${metric.metric_summary_id}`)
651
 
652
  const nextTimestamp = normalizeEvalTimestamp(modelResult.retrieved_timestamp ?? "")
653
  const existing = rowStates.get(modelId)
 
660
  },
661
  model_route_id: modelResult.model_route_id,
662
  evaluation_timestamp: modelResult.retrieved_timestamp ?? "",
663
+ source_metadata: modelResult.source_metadata,
664
  source_data: sourceData,
665
  values: { [columnKey]: modelResult.score ?? null },
666
  annotations_by_metric: { [columnKey]: modelResult.evalcards?.annotations ?? null },
 
717
  metric: HFEvalDetail["metrics"][number]
718
  ): ModelResultForBenchmark[] {
719
  const benchmarkKey = detail.benchmark ?? ""
 
 
720
  const metricConfig = toSummaryMetricConfig(metric)
721
 
722
  return (metric.model_results ?? []).map((mr) => {
723
+ assertSourceMetadata(mr, `eval=${detail.eval_summary_id} metric=${metric.metric_summary_id}`)
724
  const evaluationTimestamp = mr.retrieved_timestamp ?? ""
725
  const modelInfo: ModelInfo = {
726
  name: mr.model_name ?? "",
 
749
  score: mr.score ?? 0,
750
  score_details: { score: mr.score ?? 0 },
751
  evaluation_timestamp: evaluationTimestamp,
752
+ source_metadata: mr.source_metadata,
 
 
 
 
 
753
  source_data: detail.source_data ?? { dataset_name: benchmarkKey },
754
  result: evaluationResult,
755
  source_record_url: mr.source_record_url,
 
757
  })
758
  }
759
 
760
+ export function hfEvalDetailToSummary(detail: HFEvalDetail): BenchmarkEvalSummary {
761
  const evalName = detail.benchmark_leaf_name || detail.eval_summary_id || "Unknown"
762
  const benchmarkKey = detail.benchmark ?? ""
763
  const allMetrics = detail.metrics ?? []
 
876
  // Aggregation (for aggregate eval summaries)
877
  // ---------------------------------------------------------------------------
878
 
879
+ export async function attachBenchmarkCardToSummary(summary: BenchmarkEvalSummary): Promise<BenchmarkEvalSummary> {
880
  if (summary.benchmark_card) return summary
881
 
882
  const candidates = [
 
1131
  })
1132
 
1133
  const benchmarkKey = detail.benchmark ?? suiteKey
 
 
 
 
 
 
 
 
1134
  const sourceData = detail.source_data ?? { dataset_name: benchmarkKey }
1135
 
1136
  for (const modelResult of metric.model_results ?? []) {
 
1138
  if (!modelId) {
1139
  continue
1140
  }
1141
+ assertSourceMetadata(modelResult, `suite=${suiteKey} metric=${metric.metric_summary_id}`)
1142
 
1143
  const nextTimestamp = normalizeEvalTimestamp(modelResult.retrieved_timestamp ?? "")
1144
  const existing = rowStates.get(modelId)
 
1152
  },
1153
  model_route_id: modelResult.model_route_id,
1154
  evaluation_timestamp: modelResult.retrieved_timestamp ?? "",
1155
+ source_metadata: modelResult.source_metadata,
1156
  source_data: sourceData,
1157
  values: { [columnKey]: modelResult.score ?? null },
1158
  annotations_by_metric: { [columnKey]: modelResult.evalcards?.annotations ?? null },
 
1172
  }
1173
  if (nextTimestamp >= existing._timestampValue) {
1174
  existing.evaluation_timestamp = modelResult.retrieved_timestamp ?? existing.evaluation_timestamp
1175
+ existing.source_metadata = modelResult.source_metadata
1176
  existing.source_data = sourceData
1177
  existing._timestampValue = nextTimestamp
1178
  }
 
1403
  return details.sort((a, b) => a.developer.localeCompare(b.developer))
1404
  }
1405
 
1406
+ export function hfDeveloperDetailToSummary(detail: {
1407
+ developer: string
1408
+ models: HFModelCardEntry[]
1409
+ }) {
1410
+ const modelCards = detail.models.map(hfModelCardToEvaluationCardData)
1411
+ const benchmarkCounts = getDeveloperBenchmarkStats(detail.models)
1412
+ const evaluationCount = detail.models.reduce(
1413
+ (sum, model) => sum + model.total_evaluations,
1414
+ 0
1415
+ )
1416
+ const popularEvals = Array.from(benchmarkCounts.entries())
1417
+ .sort((a, b) => b[1] - a[1])
1418
+ .slice(0, 3)
1419
+ .map(([benchmark, model_count]) => ({
1420
+ benchmark: getBenchmarkDisplayName(benchmark),
1421
+ model_count,
1422
+ }))
1423
+
1424
+ return {
1425
+ developer: normalizeDeveloperName(detail.developer),
1426
+ route_id: getDeveloperRouteId(detail.developer),
1427
+ model_count: detail.models.length,
1428
+ benchmark_count: benchmarkCounts.size,
1429
+ evaluation_count: evaluationCount,
1430
+ popular_evals: popularEvals,
1431
+ models: modelCards,
1432
+ }
1433
+ }
1434
+
1435
  export async function getDeveloperSummaryById(routeId: string) {
1436
  // Try direct slug lookup
1437
  for (const slug of getDeveloperSlugCandidates(routeId)) {
 
1523
  }
1524
  }
1525
 
1526
+ // Try model-cards.json to find the right slug. Pipeline contract guarantees
1527
+ // model_route_id === model_family_id.replace(/\//g, "__") on every card
1528
+ // (verified in tests/pipeline-contract.test.ts and tests/upstream-drift.test.ts),
1529
+ // so a separate `getModelFamilyRouteId(family_id) === modelId` clause would
1530
+ // be redundant.
1531
  const modelCards = await fetchModelCardsList()
1532
  const matchedCard = modelCards.find(
1533
  (card) =>
1534
  card.model_family_id === modelId ||
1535
+ card.model_route_id === modelId
 
1536
  )
1537
 
1538
  if (matchedCard) {
next.config.mjs CHANGED
@@ -1,6 +1,7 @@
1
  /** @type {import('next').NextConfig} */
2
  const nextConfig = {
3
  // output: 'export',
 
4
  eslint: {
5
  ignoreDuringBuilds: true,
6
  },
 
1
  /** @type {import('next').NextConfig} */
2
  const nextConfig = {
3
  // output: 'export',
4
+ serverExternalPackages: ["@duckdb/node-api"],
5
  eslint: {
6
  ignoreDuringBuilds: true,
7
  },
package.json CHANGED
@@ -9,9 +9,14 @@
9
  "lint": "next lint",
10
  "cache-hf-data": "node scripts/cache-hf-data.mjs",
11
  "start": "next start",
12
- "test": "vitest"
 
 
 
 
13
  },
14
  "dependencies": {
 
15
  "@hookform/resolvers": "^3.10.0",
16
  "@radix-ui/react-accordion": "1.2.2",
17
  "@radix-ui/react-alert-dialog": "1.1.4",
@@ -72,6 +77,7 @@
72
  "@types/react-dom": "^19",
73
  "postcss": "^8.5",
74
  "tailwindcss": "^4.1.9",
 
75
  "tw-animate-css": "1.3.3",
76
  "typescript": "^5",
77
  "vitest": "^1.1.5"
 
9
  "lint": "next lint",
10
  "cache-hf-data": "node scripts/cache-hf-data.mjs",
11
  "start": "next start",
12
+ "test": "vitest",
13
+ "test:drift": "RUN_DRIFT=1 vitest --run tests/upstream-drift.test.ts",
14
+ "compare-data-backends": "node scripts/compare-data-backends.mjs",
15
+ "refresh-fixtures": "node scripts/refresh-fixtures.mjs",
16
+ "audit-adapters": "tsx scripts/audit-adapters.mjs"
17
  },
18
  "dependencies": {
19
+ "@duckdb/node-api": "1.5.2-r.1",
20
  "@hookform/resolvers": "^3.10.0",
21
  "@radix-ui/react-accordion": "1.2.2",
22
  "@radix-ui/react-alert-dialog": "1.1.4",
 
77
  "@types/react-dom": "^19",
78
  "postcss": "^8.5",
79
  "tailwindcss": "^4.1.9",
80
+ "tsx": "^4.21.0",
81
  "tw-animate-css": "1.3.3",
82
  "typescript": "^5",
83
  "vitest": "^1.1.5"
pnpm-lock.yaml CHANGED
@@ -8,6 +8,9 @@ importers:
8
 
9
  .:
10
  dependencies:
 
 
 
11
  '@hookform/resolvers':
12
  specifier: ^3.10.0
13
  version: 3.10.0(react-hook-form@7.62.0(react@19.1.1))
@@ -183,6 +186,9 @@ importers:
183
  tailwindcss:
184
  specifier: ^4.1.9
185
  version: 4.1.12
 
 
 
186
  tw-animate-css:
187
  specifier: 1.3.3
188
  version: 1.3.3
@@ -202,6 +208,42 @@ packages:
202
  '@date-fns/tz@1.2.0':
203
  resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  '@emnapi/runtime@1.4.5':
206
  resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
207
 
@@ -211,138 +253,294 @@ packages:
211
  cpu: [ppc64]
212
  os: [aix]
213
 
 
 
 
 
 
 
214
  '@esbuild/android-arm64@0.21.5':
215
  resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
216
  engines: {node: '>=12'}
217
  cpu: [arm64]
218
  os: [android]
219
 
 
 
 
 
 
 
220
  '@esbuild/android-arm@0.21.5':
221
  resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
222
  engines: {node: '>=12'}
223
  cpu: [arm]
224
  os: [android]
225
 
 
 
 
 
 
 
226
  '@esbuild/android-x64@0.21.5':
227
  resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
228
  engines: {node: '>=12'}
229
  cpu: [x64]
230
  os: [android]
231
 
 
 
 
 
 
 
232
  '@esbuild/darwin-arm64@0.21.5':
233
  resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
234
  engines: {node: '>=12'}
235
  cpu: [arm64]
236
  os: [darwin]
237
 
 
 
 
 
 
 
238
  '@esbuild/darwin-x64@0.21.5':
239
  resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
240
  engines: {node: '>=12'}
241
  cpu: [x64]
242
  os: [darwin]
243
 
 
 
 
 
 
 
244
  '@esbuild/freebsd-arm64@0.21.5':
245
  resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
246
  engines: {node: '>=12'}
247
  cpu: [arm64]
248
  os: [freebsd]
249
 
 
 
 
 
 
 
250
  '@esbuild/freebsd-x64@0.21.5':
251
  resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
252
  engines: {node: '>=12'}
253
  cpu: [x64]
254
  os: [freebsd]
255
 
 
 
 
 
 
 
256
  '@esbuild/linux-arm64@0.21.5':
257
  resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
258
  engines: {node: '>=12'}
259
  cpu: [arm64]
260
  os: [linux]
261
 
 
 
 
 
 
 
262
  '@esbuild/linux-arm@0.21.5':
263
  resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
264
  engines: {node: '>=12'}
265
  cpu: [arm]
266
  os: [linux]
267
 
 
 
 
 
 
 
268
  '@esbuild/linux-ia32@0.21.5':
269
  resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
270
  engines: {node: '>=12'}
271
  cpu: [ia32]
272
  os: [linux]
273
 
 
 
 
 
 
 
274
  '@esbuild/linux-loong64@0.21.5':
275
  resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
276
  engines: {node: '>=12'}
277
  cpu: [loong64]
278
  os: [linux]
279
 
 
 
 
 
 
 
280
  '@esbuild/linux-mips64el@0.21.5':
281
  resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
282
  engines: {node: '>=12'}
283
  cpu: [mips64el]
284
  os: [linux]
285
 
 
 
 
 
 
 
286
  '@esbuild/linux-ppc64@0.21.5':
287
  resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
288
  engines: {node: '>=12'}
289
  cpu: [ppc64]
290
  os: [linux]
291
 
 
 
 
 
 
 
292
  '@esbuild/linux-riscv64@0.21.5':
293
  resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
294
  engines: {node: '>=12'}
295
  cpu: [riscv64]
296
  os: [linux]
297
 
 
 
 
 
 
 
298
  '@esbuild/linux-s390x@0.21.5':
299
  resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
300
  engines: {node: '>=12'}
301
  cpu: [s390x]
302
  os: [linux]
303
 
 
 
 
 
 
 
304
  '@esbuild/linux-x64@0.21.5':
305
  resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
306
  engines: {node: '>=12'}
307
  cpu: [x64]
308
  os: [linux]
309
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  '@esbuild/netbsd-x64@0.21.5':
311
  resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
312
  engines: {node: '>=12'}
313
  cpu: [x64]
314
  os: [netbsd]
315
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  '@esbuild/openbsd-x64@0.21.5':
317
  resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
318
  engines: {node: '>=12'}
319
  cpu: [x64]
320
  os: [openbsd]
321
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  '@esbuild/sunos-x64@0.21.5':
323
  resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
324
  engines: {node: '>=12'}
325
  cpu: [x64]
326
  os: [sunos]
327
 
 
 
 
 
 
 
328
  '@esbuild/win32-arm64@0.21.5':
329
  resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
330
  engines: {node: '>=12'}
331
  cpu: [arm64]
332
  os: [win32]
333
 
 
 
 
 
 
 
334
  '@esbuild/win32-ia32@0.21.5':
335
  resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
336
  engines: {node: '>=12'}
337
  cpu: [ia32]
338
  os: [win32]
339
 
 
 
 
 
 
 
340
  '@esbuild/win32-x64@0.21.5':
341
  resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
342
  engines: {node: '>=12'}
343
  cpu: [x64]
344
  os: [win32]
345
 
 
 
 
 
 
 
346
  '@floating-ui/core@1.7.3':
347
  resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
348
 
@@ -1671,6 +1869,11 @@ packages:
1671
  engines: {node: '>=12'}
1672
  hasBin: true
1673
 
 
 
 
 
 
1674
  escalade@3.2.0:
1675
  resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
1676
  engines: {node: '>=6'}
@@ -1709,6 +1912,9 @@ packages:
1709
  resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
1710
  engines: {node: '>=16'}
1711
 
 
 
 
1712
  graceful-fs@4.2.11:
1713
  resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
1714
 
@@ -2034,6 +2240,9 @@ packages:
2034
  reselect@5.1.1:
2035
  resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
2036
 
 
 
 
2037
  rollup@4.46.2:
2038
  resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==}
2039
  engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -2148,6 +2357,11 @@ packages:
2148
  tslib@2.8.1:
2149
  resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
2150
 
 
 
 
 
 
2151
  tw-animate-css@1.3.3:
2152
  resolution: {integrity: sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==}
2153
 
@@ -2294,6 +2508,37 @@ snapshots:
2294
 
2295
  '@date-fns/tz@1.2.0': {}
2296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2297
  '@emnapi/runtime@1.4.5':
2298
  dependencies:
2299
  tslib: 2.8.1
@@ -2302,72 +2547,150 @@ snapshots:
2302
  '@esbuild/aix-ppc64@0.21.5':
2303
  optional: true
2304
 
 
 
 
2305
  '@esbuild/android-arm64@0.21.5':
2306
  optional: true
2307
 
 
 
 
2308
  '@esbuild/android-arm@0.21.5':
2309
  optional: true
2310
 
 
 
 
2311
  '@esbuild/android-x64@0.21.5':
2312
  optional: true
2313
 
 
 
 
2314
  '@esbuild/darwin-arm64@0.21.5':
2315
  optional: true
2316
 
 
 
 
2317
  '@esbuild/darwin-x64@0.21.5':
2318
  optional: true
2319
 
 
 
 
2320
  '@esbuild/freebsd-arm64@0.21.5':
2321
  optional: true
2322
 
 
 
 
2323
  '@esbuild/freebsd-x64@0.21.5':
2324
  optional: true
2325
 
 
 
 
2326
  '@esbuild/linux-arm64@0.21.5':
2327
  optional: true
2328
 
 
 
 
2329
  '@esbuild/linux-arm@0.21.5':
2330
  optional: true
2331
 
 
 
 
2332
  '@esbuild/linux-ia32@0.21.5':
2333
  optional: true
2334
 
 
 
 
2335
  '@esbuild/linux-loong64@0.21.5':
2336
  optional: true
2337
 
 
 
 
2338
  '@esbuild/linux-mips64el@0.21.5':
2339
  optional: true
2340
 
 
 
 
2341
  '@esbuild/linux-ppc64@0.21.5':
2342
  optional: true
2343
 
 
 
 
2344
  '@esbuild/linux-riscv64@0.21.5':
2345
  optional: true
2346
 
 
 
 
2347
  '@esbuild/linux-s390x@0.21.5':
2348
  optional: true
2349
 
 
 
 
2350
  '@esbuild/linux-x64@0.21.5':
2351
  optional: true
2352
 
 
 
 
 
 
 
2353
  '@esbuild/netbsd-x64@0.21.5':
2354
  optional: true
2355
 
 
 
 
 
 
 
2356
  '@esbuild/openbsd-x64@0.21.5':
2357
  optional: true
2358
 
 
 
 
 
 
 
2359
  '@esbuild/sunos-x64@0.21.5':
2360
  optional: true
2361
 
 
 
 
2362
  '@esbuild/win32-arm64@0.21.5':
2363
  optional: true
2364
 
 
 
 
2365
  '@esbuild/win32-ia32@0.21.5':
2366
  optional: true
2367
 
 
 
 
2368
  '@esbuild/win32-x64@0.21.5':
2369
  optional: true
2370
 
 
 
 
2371
  '@floating-ui/core@1.7.3':
2372
  dependencies:
2373
  '@floating-ui/utils': 0.2.10
@@ -3644,6 +3967,35 @@ snapshots:
3644
  '@esbuild/win32-ia32': 0.21.5
3645
  '@esbuild/win32-x64': 0.21.5
3646
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3647
  escalade@3.2.0: {}
3648
 
3649
  estree-walker@3.0.3:
@@ -3679,6 +4031,10 @@ snapshots:
3679
 
3680
  get-stream@8.0.1: {}
3681
 
 
 
 
 
3682
  graceful-fs@4.2.11: {}
3683
 
3684
  human-signals@5.0.0: {}
@@ -3963,6 +4319,8 @@ snapshots:
3963
 
3964
  reselect@5.1.1: {}
3965
 
 
 
3966
  rollup@4.46.2:
3967
  dependencies:
3968
  '@types/estree': 1.0.8
@@ -4091,6 +4449,13 @@ snapshots:
4091
 
4092
  tslib@2.8.1: {}
4093
 
 
 
 
 
 
 
 
4094
  tw-animate-css@1.3.3: {}
4095
 
4096
  type-detect@4.1.0: {}
 
8
 
9
  .:
10
  dependencies:
11
+ '@duckdb/node-api':
12
+ specifier: 1.5.2-r.1
13
+ version: 1.5.2-r.1
14
  '@hookform/resolvers':
15
  specifier: ^3.10.0
16
  version: 3.10.0(react-hook-form@7.62.0(react@19.1.1))
 
186
  tailwindcss:
187
  specifier: ^4.1.9
188
  version: 4.1.12
189
+ tsx:
190
+ specifier: ^4.21.0
191
+ version: 4.21.0
192
  tw-animate-css:
193
  specifier: 1.3.3
194
  version: 1.3.3
 
208
  '@date-fns/tz@1.2.0':
209
  resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
210
 
211
+ '@duckdb/node-api@1.5.2-r.1':
212
+ resolution: {integrity: sha512-OzBBnS0JGXMoS5mzKNY/Ylr7SshcRQiLFIoxQ4AlePwJ2fNeDL/fbHu/knjxUrXwW1fJBTUgwWftmxDdnZZb3A==}
213
+
214
+ '@duckdb/node-bindings-darwin-arm64@1.5.2-r.1':
215
+ resolution: {integrity: sha512-v35FyKOb8EJCvaiPF7k0gvKiJTXR7PPQDNoWR0Gu+YSX5O9b+DIguzt1348Of3HebHy6ATSMzlUekaVA9YXu+g==}
216
+ cpu: [arm64]
217
+ os: [darwin]
218
+
219
+ '@duckdb/node-bindings-darwin-x64@1.5.2-r.1':
220
+ resolution: {integrity: sha512-SU9dIJ1BluKkkGxi4UsP4keqkkstB2YDySF9KcYu3EZKIVM3FTv2zc7XO38dXnHOq6+F3WqhWWZvD+XU945p7A==}
221
+ cpu: [x64]
222
+ os: [darwin]
223
+
224
+ '@duckdb/node-bindings-linux-arm64@1.5.2-r.1':
225
+ resolution: {integrity: sha512-3Tra9xM3aM3denaER4KhJ6//6PpmPbik9ECBQ+sh9PyKaEgHw/0kAcKnLm5EzWUnXF0qYmZlewvkCrse8KmOYw==}
226
+ cpu: [arm64]
227
+ os: [linux]
228
+
229
+ '@duckdb/node-bindings-linux-x64@1.5.2-r.1':
230
+ resolution: {integrity: sha512-pcQvZRHiIfJ9cq8parkSQczQHEml/IeGfnDCMAbEgD6+jaV9Y9Y5Ph1kP9aR+bm6him1S5ZIEr3kZbihjKnWbA==}
231
+ cpu: [x64]
232
+ os: [linux]
233
+
234
+ '@duckdb/node-bindings-win32-arm64@1.5.2-r.1':
235
+ resolution: {integrity: sha512-Ji8tym+N3LkrhVt0Up3bsacD/kpg4/JXFJQqxswiYvBaNCQOk+D+aiVS0GN5pcqvmnG7V7TpsDRzkLEFaWp1vw==}
236
+ cpu: [arm64]
237
+ os: [win32]
238
+
239
+ '@duckdb/node-bindings-win32-x64@1.5.2-r.1':
240
+ resolution: {integrity: sha512-5XqcqC+4R8ghBEEbnc2a0sqfz1zyPBRb9YcmIWfiuDoCYSYFbKhmHcEyNftZDHcwCoLOHXnUin45jraex4STqQ==}
241
+ cpu: [x64]
242
+ os: [win32]
243
+
244
+ '@duckdb/node-bindings@1.5.2-r.1':
245
+ resolution: {integrity: sha512-bUg3bLVj70YVku6fKyQJS8ASORl7kM7YFVFznsEB9pWbtazPj+ME2x2FUk0WiTzjJdutjzSSGXF066mB4bGGZA==}
246
+
247
  '@emnapi/runtime@1.4.5':
248
  resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
249
 
 
253
  cpu: [ppc64]
254
  os: [aix]
255
 
256
+ '@esbuild/aix-ppc64@0.27.7':
257
+ resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
258
+ engines: {node: '>=18'}
259
+ cpu: [ppc64]
260
+ os: [aix]
261
+
262
  '@esbuild/android-arm64@0.21.5':
263
  resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
264
  engines: {node: '>=12'}
265
  cpu: [arm64]
266
  os: [android]
267
 
268
+ '@esbuild/android-arm64@0.27.7':
269
+ resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
270
+ engines: {node: '>=18'}
271
+ cpu: [arm64]
272
+ os: [android]
273
+
274
  '@esbuild/android-arm@0.21.5':
275
  resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
276
  engines: {node: '>=12'}
277
  cpu: [arm]
278
  os: [android]
279
 
280
+ '@esbuild/android-arm@0.27.7':
281
+ resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
282
+ engines: {node: '>=18'}
283
+ cpu: [arm]
284
+ os: [android]
285
+
286
  '@esbuild/android-x64@0.21.5':
287
  resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
288
  engines: {node: '>=12'}
289
  cpu: [x64]
290
  os: [android]
291
 
292
+ '@esbuild/android-x64@0.27.7':
293
+ resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
294
+ engines: {node: '>=18'}
295
+ cpu: [x64]
296
+ os: [android]
297
+
298
  '@esbuild/darwin-arm64@0.21.5':
299
  resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
300
  engines: {node: '>=12'}
301
  cpu: [arm64]
302
  os: [darwin]
303
 
304
+ '@esbuild/darwin-arm64@0.27.7':
305
+ resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
306
+ engines: {node: '>=18'}
307
+ cpu: [arm64]
308
+ os: [darwin]
309
+
310
  '@esbuild/darwin-x64@0.21.5':
311
  resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
312
  engines: {node: '>=12'}
313
  cpu: [x64]
314
  os: [darwin]
315
 
316
+ '@esbuild/darwin-x64@0.27.7':
317
+ resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
318
+ engines: {node: '>=18'}
319
+ cpu: [x64]
320
+ os: [darwin]
321
+
322
  '@esbuild/freebsd-arm64@0.21.5':
323
  resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
324
  engines: {node: '>=12'}
325
  cpu: [arm64]
326
  os: [freebsd]
327
 
328
+ '@esbuild/freebsd-arm64@0.27.7':
329
+ resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
330
+ engines: {node: '>=18'}
331
+ cpu: [arm64]
332
+ os: [freebsd]
333
+
334
  '@esbuild/freebsd-x64@0.21.5':
335
  resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
336
  engines: {node: '>=12'}
337
  cpu: [x64]
338
  os: [freebsd]
339
 
340
+ '@esbuild/freebsd-x64@0.27.7':
341
+ resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
342
+ engines: {node: '>=18'}
343
+ cpu: [x64]
344
+ os: [freebsd]
345
+
346
  '@esbuild/linux-arm64@0.21.5':
347
  resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
348
  engines: {node: '>=12'}
349
  cpu: [arm64]
350
  os: [linux]
351
 
352
+ '@esbuild/linux-arm64@0.27.7':
353
+ resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
354
+ engines: {node: '>=18'}
355
+ cpu: [arm64]
356
+ os: [linux]
357
+
358
  '@esbuild/linux-arm@0.21.5':
359
  resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
360
  engines: {node: '>=12'}
361
  cpu: [arm]
362
  os: [linux]
363
 
364
+ '@esbuild/linux-arm@0.27.7':
365
+ resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
366
+ engines: {node: '>=18'}
367
+ cpu: [arm]
368
+ os: [linux]
369
+
370
  '@esbuild/linux-ia32@0.21.5':
371
  resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
372
  engines: {node: '>=12'}
373
  cpu: [ia32]
374
  os: [linux]
375
 
376
+ '@esbuild/linux-ia32@0.27.7':
377
+ resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
378
+ engines: {node: '>=18'}
379
+ cpu: [ia32]
380
+ os: [linux]
381
+
382
  '@esbuild/linux-loong64@0.21.5':
383
  resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
384
  engines: {node: '>=12'}
385
  cpu: [loong64]
386
  os: [linux]
387
 
388
+ '@esbuild/linux-loong64@0.27.7':
389
+ resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
390
+ engines: {node: '>=18'}
391
+ cpu: [loong64]
392
+ os: [linux]
393
+
394
  '@esbuild/linux-mips64el@0.21.5':
395
  resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
396
  engines: {node: '>=12'}
397
  cpu: [mips64el]
398
  os: [linux]
399
 
400
+ '@esbuild/linux-mips64el@0.27.7':
401
+ resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
402
+ engines: {node: '>=18'}
403
+ cpu: [mips64el]
404
+ os: [linux]
405
+
406
  '@esbuild/linux-ppc64@0.21.5':
407
  resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
408
  engines: {node: '>=12'}
409
  cpu: [ppc64]
410
  os: [linux]
411
 
412
+ '@esbuild/linux-ppc64@0.27.7':
413
+ resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
414
+ engines: {node: '>=18'}
415
+ cpu: [ppc64]
416
+ os: [linux]
417
+
418
  '@esbuild/linux-riscv64@0.21.5':
419
  resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
420
  engines: {node: '>=12'}
421
  cpu: [riscv64]
422
  os: [linux]
423
 
424
+ '@esbuild/linux-riscv64@0.27.7':
425
+ resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
426
+ engines: {node: '>=18'}
427
+ cpu: [riscv64]
428
+ os: [linux]
429
+
430
  '@esbuild/linux-s390x@0.21.5':
431
  resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
432
  engines: {node: '>=12'}
433
  cpu: [s390x]
434
  os: [linux]
435
 
436
+ '@esbuild/linux-s390x@0.27.7':
437
+ resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
438
+ engines: {node: '>=18'}
439
+ cpu: [s390x]
440
+ os: [linux]
441
+
442
  '@esbuild/linux-x64@0.21.5':
443
  resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
444
  engines: {node: '>=12'}
445
  cpu: [x64]
446
  os: [linux]
447
 
448
+ '@esbuild/linux-x64@0.27.7':
449
+ resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
450
+ engines: {node: '>=18'}
451
+ cpu: [x64]
452
+ os: [linux]
453
+
454
+ '@esbuild/netbsd-arm64@0.27.7':
455
+ resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
456
+ engines: {node: '>=18'}
457
+ cpu: [arm64]
458
+ os: [netbsd]
459
+
460
  '@esbuild/netbsd-x64@0.21.5':
461
  resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
462
  engines: {node: '>=12'}
463
  cpu: [x64]
464
  os: [netbsd]
465
 
466
+ '@esbuild/netbsd-x64@0.27.7':
467
+ resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
468
+ engines: {node: '>=18'}
469
+ cpu: [x64]
470
+ os: [netbsd]
471
+
472
+ '@esbuild/openbsd-arm64@0.27.7':
473
+ resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
474
+ engines: {node: '>=18'}
475
+ cpu: [arm64]
476
+ os: [openbsd]
477
+
478
  '@esbuild/openbsd-x64@0.21.5':
479
  resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
480
  engines: {node: '>=12'}
481
  cpu: [x64]
482
  os: [openbsd]
483
 
484
+ '@esbuild/openbsd-x64@0.27.7':
485
+ resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
486
+ engines: {node: '>=18'}
487
+ cpu: [x64]
488
+ os: [openbsd]
489
+
490
+ '@esbuild/openharmony-arm64@0.27.7':
491
+ resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
492
+ engines: {node: '>=18'}
493
+ cpu: [arm64]
494
+ os: [openharmony]
495
+
496
  '@esbuild/sunos-x64@0.21.5':
497
  resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
498
  engines: {node: '>=12'}
499
  cpu: [x64]
500
  os: [sunos]
501
 
502
+ '@esbuild/sunos-x64@0.27.7':
503
+ resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
504
+ engines: {node: '>=18'}
505
+ cpu: [x64]
506
+ os: [sunos]
507
+
508
  '@esbuild/win32-arm64@0.21.5':
509
  resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
510
  engines: {node: '>=12'}
511
  cpu: [arm64]
512
  os: [win32]
513
 
514
+ '@esbuild/win32-arm64@0.27.7':
515
+ resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
516
+ engines: {node: '>=18'}
517
+ cpu: [arm64]
518
+ os: [win32]
519
+
520
  '@esbuild/win32-ia32@0.21.5':
521
  resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
522
  engines: {node: '>=12'}
523
  cpu: [ia32]
524
  os: [win32]
525
 
526
+ '@esbuild/win32-ia32@0.27.7':
527
+ resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
528
+ engines: {node: '>=18'}
529
+ cpu: [ia32]
530
+ os: [win32]
531
+
532
  '@esbuild/win32-x64@0.21.5':
533
  resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
534
  engines: {node: '>=12'}
535
  cpu: [x64]
536
  os: [win32]
537
 
538
+ '@esbuild/win32-x64@0.27.7':
539
+ resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
540
+ engines: {node: '>=18'}
541
+ cpu: [x64]
542
+ os: [win32]
543
+
544
  '@floating-ui/core@1.7.3':
545
  resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
546
 
 
1869
  engines: {node: '>=12'}
1870
  hasBin: true
1871
 
1872
+ esbuild@0.27.7:
1873
+ resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
1874
+ engines: {node: '>=18'}
1875
+ hasBin: true
1876
+
1877
  escalade@3.2.0:
1878
  resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
1879
  engines: {node: '>=6'}
 
1912
  resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
1913
  engines: {node: '>=16'}
1914
 
1915
+ get-tsconfig@4.14.0:
1916
+ resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
1917
+
1918
  graceful-fs@4.2.11:
1919
  resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
1920
 
 
2240
  reselect@5.1.1:
2241
  resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
2242
 
2243
+ resolve-pkg-maps@1.0.0:
2244
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
2245
+
2246
  rollup@4.46.2:
2247
  resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==}
2248
  engines: {node: '>=18.0.0', npm: '>=8.0.0'}
 
2357
  tslib@2.8.1:
2358
  resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
2359
 
2360
+ tsx@4.21.0:
2361
+ resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
2362
+ engines: {node: '>=18.0.0'}
2363
+ hasBin: true
2364
+
2365
  tw-animate-css@1.3.3:
2366
  resolution: {integrity: sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==}
2367
 
 
2508
 
2509
  '@date-fns/tz@1.2.0': {}
2510
 
2511
+ '@duckdb/node-api@1.5.2-r.1':
2512
+ dependencies:
2513
+ '@duckdb/node-bindings': 1.5.2-r.1
2514
+
2515
+ '@duckdb/node-bindings-darwin-arm64@1.5.2-r.1':
2516
+ optional: true
2517
+
2518
+ '@duckdb/node-bindings-darwin-x64@1.5.2-r.1':
2519
+ optional: true
2520
+
2521
+ '@duckdb/node-bindings-linux-arm64@1.5.2-r.1':
2522
+ optional: true
2523
+
2524
+ '@duckdb/node-bindings-linux-x64@1.5.2-r.1':
2525
+ optional: true
2526
+
2527
+ '@duckdb/node-bindings-win32-arm64@1.5.2-r.1':
2528
+ optional: true
2529
+
2530
+ '@duckdb/node-bindings-win32-x64@1.5.2-r.1':
2531
+ optional: true
2532
+
2533
+ '@duckdb/node-bindings@1.5.2-r.1':
2534
+ optionalDependencies:
2535
+ '@duckdb/node-bindings-darwin-arm64': 1.5.2-r.1
2536
+ '@duckdb/node-bindings-darwin-x64': 1.5.2-r.1
2537
+ '@duckdb/node-bindings-linux-arm64': 1.5.2-r.1
2538
+ '@duckdb/node-bindings-linux-x64': 1.5.2-r.1
2539
+ '@duckdb/node-bindings-win32-arm64': 1.5.2-r.1
2540
+ '@duckdb/node-bindings-win32-x64': 1.5.2-r.1
2541
+
2542
  '@emnapi/runtime@1.4.5':
2543
  dependencies:
2544
  tslib: 2.8.1
 
2547
  '@esbuild/aix-ppc64@0.21.5':
2548
  optional: true
2549
 
2550
+ '@esbuild/aix-ppc64@0.27.7':
2551
+ optional: true
2552
+
2553
  '@esbuild/android-arm64@0.21.5':
2554
  optional: true
2555
 
2556
+ '@esbuild/android-arm64@0.27.7':
2557
+ optional: true
2558
+
2559
  '@esbuild/android-arm@0.21.5':
2560
  optional: true
2561
 
2562
+ '@esbuild/android-arm@0.27.7':
2563
+ optional: true
2564
+
2565
  '@esbuild/android-x64@0.21.5':
2566
  optional: true
2567
 
2568
+ '@esbuild/android-x64@0.27.7':
2569
+ optional: true
2570
+
2571
  '@esbuild/darwin-arm64@0.21.5':
2572
  optional: true
2573
 
2574
+ '@esbuild/darwin-arm64@0.27.7':
2575
+ optional: true
2576
+
2577
  '@esbuild/darwin-x64@0.21.5':
2578
  optional: true
2579
 
2580
+ '@esbuild/darwin-x64@0.27.7':
2581
+ optional: true
2582
+
2583
  '@esbuild/freebsd-arm64@0.21.5':
2584
  optional: true
2585
 
2586
+ '@esbuild/freebsd-arm64@0.27.7':
2587
+ optional: true
2588
+
2589
  '@esbuild/freebsd-x64@0.21.5':
2590
  optional: true
2591
 
2592
+ '@esbuild/freebsd-x64@0.27.7':
2593
+ optional: true
2594
+
2595
  '@esbuild/linux-arm64@0.21.5':
2596
  optional: true
2597
 
2598
+ '@esbuild/linux-arm64@0.27.7':
2599
+ optional: true
2600
+
2601
  '@esbuild/linux-arm@0.21.5':
2602
  optional: true
2603
 
2604
+ '@esbuild/linux-arm@0.27.7':
2605
+ optional: true
2606
+
2607
  '@esbuild/linux-ia32@0.21.5':
2608
  optional: true
2609
 
2610
+ '@esbuild/linux-ia32@0.27.7':
2611
+ optional: true
2612
+
2613
  '@esbuild/linux-loong64@0.21.5':
2614
  optional: true
2615
 
2616
+ '@esbuild/linux-loong64@0.27.7':
2617
+ optional: true
2618
+
2619
  '@esbuild/linux-mips64el@0.21.5':
2620
  optional: true
2621
 
2622
+ '@esbuild/linux-mips64el@0.27.7':
2623
+ optional: true
2624
+
2625
  '@esbuild/linux-ppc64@0.21.5':
2626
  optional: true
2627
 
2628
+ '@esbuild/linux-ppc64@0.27.7':
2629
+ optional: true
2630
+
2631
  '@esbuild/linux-riscv64@0.21.5':
2632
  optional: true
2633
 
2634
+ '@esbuild/linux-riscv64@0.27.7':
2635
+ optional: true
2636
+
2637
  '@esbuild/linux-s390x@0.21.5':
2638
  optional: true
2639
 
2640
+ '@esbuild/linux-s390x@0.27.7':
2641
+ optional: true
2642
+
2643
  '@esbuild/linux-x64@0.21.5':
2644
  optional: true
2645
 
2646
+ '@esbuild/linux-x64@0.27.7':
2647
+ optional: true
2648
+
2649
+ '@esbuild/netbsd-arm64@0.27.7':
2650
+ optional: true
2651
+
2652
  '@esbuild/netbsd-x64@0.21.5':
2653
  optional: true
2654
 
2655
+ '@esbuild/netbsd-x64@0.27.7':
2656
+ optional: true
2657
+
2658
+ '@esbuild/openbsd-arm64@0.27.7':
2659
+ optional: true
2660
+
2661
  '@esbuild/openbsd-x64@0.21.5':
2662
  optional: true
2663
 
2664
+ '@esbuild/openbsd-x64@0.27.7':
2665
+ optional: true
2666
+
2667
+ '@esbuild/openharmony-arm64@0.27.7':
2668
+ optional: true
2669
+
2670
  '@esbuild/sunos-x64@0.21.5':
2671
  optional: true
2672
 
2673
+ '@esbuild/sunos-x64@0.27.7':
2674
+ optional: true
2675
+
2676
  '@esbuild/win32-arm64@0.21.5':
2677
  optional: true
2678
 
2679
+ '@esbuild/win32-arm64@0.27.7':
2680
+ optional: true
2681
+
2682
  '@esbuild/win32-ia32@0.21.5':
2683
  optional: true
2684
 
2685
+ '@esbuild/win32-ia32@0.27.7':
2686
+ optional: true
2687
+
2688
  '@esbuild/win32-x64@0.21.5':
2689
  optional: true
2690
 
2691
+ '@esbuild/win32-x64@0.27.7':
2692
+ optional: true
2693
+
2694
  '@floating-ui/core@1.7.3':
2695
  dependencies:
2696
  '@floating-ui/utils': 0.2.10
 
3967
  '@esbuild/win32-ia32': 0.21.5
3968
  '@esbuild/win32-x64': 0.21.5
3969
 
3970
+ esbuild@0.27.7:
3971
+ optionalDependencies:
3972
+ '@esbuild/aix-ppc64': 0.27.7
3973
+ '@esbuild/android-arm': 0.27.7
3974
+ '@esbuild/android-arm64': 0.27.7
3975
+ '@esbuild/android-x64': 0.27.7
3976
+ '@esbuild/darwin-arm64': 0.27.7
3977
+ '@esbuild/darwin-x64': 0.27.7
3978
+ '@esbuild/freebsd-arm64': 0.27.7
3979
+ '@esbuild/freebsd-x64': 0.27.7
3980
+ '@esbuild/linux-arm': 0.27.7
3981
+ '@esbuild/linux-arm64': 0.27.7
3982
+ '@esbuild/linux-ia32': 0.27.7
3983
+ '@esbuild/linux-loong64': 0.27.7
3984
+ '@esbuild/linux-mips64el': 0.27.7
3985
+ '@esbuild/linux-ppc64': 0.27.7
3986
+ '@esbuild/linux-riscv64': 0.27.7
3987
+ '@esbuild/linux-s390x': 0.27.7
3988
+ '@esbuild/linux-x64': 0.27.7
3989
+ '@esbuild/netbsd-arm64': 0.27.7
3990
+ '@esbuild/netbsd-x64': 0.27.7
3991
+ '@esbuild/openbsd-arm64': 0.27.7
3992
+ '@esbuild/openbsd-x64': 0.27.7
3993
+ '@esbuild/openharmony-arm64': 0.27.7
3994
+ '@esbuild/sunos-x64': 0.27.7
3995
+ '@esbuild/win32-arm64': 0.27.7
3996
+ '@esbuild/win32-ia32': 0.27.7
3997
+ '@esbuild/win32-x64': 0.27.7
3998
+
3999
  escalade@3.2.0: {}
4000
 
4001
  estree-walker@3.0.3:
 
4031
 
4032
  get-stream@8.0.1: {}
4033
 
4034
+ get-tsconfig@4.14.0:
4035
+ dependencies:
4036
+ resolve-pkg-maps: 1.0.0
4037
+
4038
  graceful-fs@4.2.11: {}
4039
 
4040
  human-signals@5.0.0: {}
 
4319
 
4320
  reselect@5.1.1: {}
4321
 
4322
+ resolve-pkg-maps@1.0.0: {}
4323
+
4324
  rollup@4.46.2:
4325
  dependencies:
4326
  '@types/estree': 1.0.8
 
4449
 
4450
  tslib@2.8.1: {}
4451
 
4452
+ tsx@4.21.0:
4453
+ dependencies:
4454
+ esbuild: 0.27.7
4455
+ get-tsconfig: 4.14.0
4456
+ optionalDependencies:
4457
+ fsevents: 2.3.3
4458
+
4459
  tw-animate-css@1.3.3: {}
4460
 
4461
  type-detect@4.1.0: {}
scripts/compare-data-backends.mjs ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ const DEFAULT_BASE_URL = "http://localhost:3000"
4
+
5
+ const args = new Map()
6
+ for (let i = 2; i < process.argv.length; i += 2) {
7
+ const key = process.argv[i]
8
+ const value = process.argv[i + 1]
9
+ if (!key?.startsWith("--") || value == null) {
10
+ throw new Error("Usage: node scripts/compare-data-backends.mjs --json-base http://localhost:3000 --duckdb-base http://localhost:3001")
11
+ }
12
+ args.set(key, value)
13
+ }
14
+
15
+ const jsonBase = args.get("--json-base") ?? process.env.JSON_BASE_URL ?? DEFAULT_BASE_URL
16
+ const duckdbBase = args.get("--duckdb-base") ?? process.env.DUCKDB_BASE_URL ?? jsonBase
17
+
18
+ function endpoint(path) {
19
+ return path.startsWith("/") ? path : `/${path}`
20
+ }
21
+
22
+ async function fetchJson(baseUrl, path) {
23
+ const url = new URL(endpoint(path), baseUrl)
24
+ const response = await fetch(url)
25
+ if (!response.ok) {
26
+ throw new Error(`${url.toString()} returned ${response.status} ${response.statusText}`)
27
+ }
28
+ return response.json()
29
+ }
30
+
31
+ function stableArrayKey(item) {
32
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
33
+ return null
34
+ }
35
+
36
+ const record = item
37
+ return (
38
+ record.eval_summary_id ??
39
+ record.evaluation_id ??
40
+ record.model_route_id ??
41
+ record.route_id ??
42
+ record.developer_route_id ??
43
+ record.model_id ??
44
+ record.id ??
45
+ record.column_key ??
46
+ record.metric_summary_id ??
47
+ record.metric_name ??
48
+ record.name ??
49
+ null
50
+ )
51
+ }
52
+
53
+ function normalize(value) {
54
+ if (Array.isArray(value)) {
55
+ const normalized = value.map(normalize)
56
+ if (normalized.every((item) => stableArrayKey(item) != null)) {
57
+ normalized.sort((a, b) => String(stableArrayKey(a)).localeCompare(String(stableArrayKey(b))))
58
+ }
59
+ return normalized
60
+ }
61
+
62
+ if (!value || typeof value !== "object") {
63
+ return value
64
+ }
65
+
66
+ const entries = Object.entries(value)
67
+ .map(([key, nestedValue]) => [key, normalize(nestedValue)])
68
+ .sort(([a], [b]) => a.localeCompare(b))
69
+ return Object.fromEntries(entries)
70
+ }
71
+
72
+ function diffPaths(left, right, prefix = "") {
73
+ const out = []
74
+ if (left === right) return out
75
+ if (
76
+ left == null || right == null ||
77
+ typeof left !== typeof right ||
78
+ Array.isArray(left) !== Array.isArray(right) ||
79
+ typeof left !== "object"
80
+ ) {
81
+ out.push({ path: prefix || "<root>", left, right })
82
+ return out
83
+ }
84
+
85
+ if (Array.isArray(left)) {
86
+ const max = Math.max(left.length, right.length)
87
+ if (left.length !== right.length) {
88
+ out.push({ path: `${prefix}.length`, left: left.length, right: right.length })
89
+ }
90
+ for (let i = 0; i < max && out.length < 20; i++) {
91
+ out.push(...diffPaths(left[i], right[i], `${prefix}[${i}]`))
92
+ }
93
+ return out
94
+ }
95
+
96
+ const keys = new Set([...Object.keys(left), ...Object.keys(right)])
97
+ for (const key of keys) {
98
+ if (out.length >= 20) break
99
+ out.push(...diffPaths(left[key], right[key], prefix ? `${prefix}.${key}` : key))
100
+ }
101
+ return out
102
+ }
103
+
104
+ const FAIL_FAST = process.env.PARITY_FAIL_FAST !== "0"
105
+ const failures = []
106
+
107
+ function assertEqual(label, left, right) {
108
+ const normalizedLeft = normalize(left)
109
+ const normalizedRight = normalize(right)
110
+ const leftText = JSON.stringify(normalizedLeft)
111
+ const rightText = JSON.stringify(normalizedRight)
112
+
113
+ if (leftText === rightText) {
114
+ console.log(`✓ ${label}`)
115
+ return
116
+ }
117
+
118
+ console.log(`✗ ${label}`)
119
+ const diffs = diffPaths(normalizedLeft, normalizedRight)
120
+ for (const diff of diffs.slice(0, 12)) {
121
+ const left = JSON.stringify(diff.left)
122
+ const right = JSON.stringify(diff.right)
123
+ const truncate = (text) => text != null && text.length > 160 ? `${text.slice(0, 160)}…` : text
124
+ console.log(` ${diff.path}`)
125
+ console.log(` JSON : ${truncate(left)}`)
126
+ console.log(` DuckDB : ${truncate(right)}`)
127
+ }
128
+ if (diffs.length > 12) {
129
+ console.log(` …(${diffs.length - 12} more)`)
130
+ }
131
+
132
+ failures.push(label)
133
+ if (FAIL_FAST) {
134
+ throw new Error(`Mismatch for ${label}`)
135
+ }
136
+ }
137
+
138
+ async function compareEndpoint(path) {
139
+ const [jsonValue, duckdbValue] = await Promise.all([
140
+ fetchJson(jsonBase, path),
141
+ fetchJson(duckdbBase, path),
142
+ ])
143
+ assertEqual(path, jsonValue, duckdbValue)
144
+ return jsonValue
145
+ }
146
+
147
+ const evalListLite = await compareEndpoint("/api/eval-list-lite")
148
+ const modelCardsLite = await compareEndpoint("/api/model-cards-lite")
149
+ await compareEndpoint("/api/eval-list")
150
+ await compareEndpoint("/api/model-cards")
151
+
152
+ const evalId = evalListLite?.evals?.[0]?.evaluation_id ?? evalListLite?.evals?.[0]?.eval_summary_id
153
+ if (evalId) {
154
+ await compareEndpoint(`/api/eval-summary?id=${encodeURIComponent(evalId)}`)
155
+ } else {
156
+ console.warn("No eval id found in /api/eval-list-lite; skipping eval summary parity")
157
+ }
158
+
159
+ const modelId = modelCardsLite?.[0]?.route_id ?? modelCardsLite?.[0]?.model_route_id ?? modelCardsLite?.[0]?.id
160
+ if (modelId) {
161
+ await compareEndpoint(`/api/model-summary?id=${encodeURIComponent(modelId)}`)
162
+ } else {
163
+ console.warn("No model id found in /api/model-cards-lite; skipping model summary parity")
164
+ }
165
+
166
+ try {
167
+ const developers = await compareEndpoint("/api/developers")
168
+ const developerId = developers?.[0]?.route_id
169
+ if (developerId) {
170
+ await compareEndpoint(`/api/developer-summary?id=${encodeURIComponent(developerId)}`)
171
+ } else {
172
+ console.warn("No developer id found in /api/developers; skipping developer summary parity")
173
+ }
174
+ } catch (error) {
175
+ console.warn(`Developer parity skipped: ${error instanceof Error ? error.message : String(error)}`)
176
+ }
177
+
178
+ console.log(`Compared JSON backend ${jsonBase} with DuckDB backend ${duckdbBase}`)
179
+
180
+ if (failures.length > 0) {
181
+ console.error(`\n${failures.length} endpoint(s) failed parity:`)
182
+ for (const label of failures) console.error(` - ${label}`)
183
+ process.exit(1)
184
+ }
tests/duckdb-data.test.ts ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { mkdtemp, mkdir, rm } from "fs/promises"
2
+ import os from "os"
3
+ import path from "path"
4
+
5
+ import { DuckDBConnection } from "@duckdb/node-api"
6
+ import { describe, expect, it } from "vitest"
7
+
8
+ import { getModelCardsLiteFromDuckDB } from "../lib/duckdb-data"
9
+
10
+ function sqlString(value: string) {
11
+ return `'${value.replace(/'/g, "''")}'`
12
+ }
13
+
14
+ async function writeParquetPayload(outputDir: string, fileName: string, payloads: unknown[]) {
15
+ const parquetDir = path.join(outputDir, "experimental", "parquet")
16
+ await mkdir(parquetDir, { recursive: true })
17
+
18
+ const selects = payloads
19
+ .map((payload, index) => {
20
+ const record = payload as Record<string, unknown>
21
+ const payloadJson = JSON.stringify(payload)
22
+ return [
23
+ `SELECT 'model_card_lite' AS record_type`,
24
+ `${sqlString(String(record.model_route_id ?? index))} AS model_route_id`,
25
+ `${sqlString(String(record.model_family_id ?? ""))} AS model_family_id`,
26
+ `${sqlString(String(record.developer ?? ""))} AS developer`,
27
+ `NULL AS eval_summary_id`,
28
+ `NULL AS developer_route_id`,
29
+ `NULL AS category`,
30
+ `NULL AS benchmark_family_key`,
31
+ `${Number(record.benchmark_family_count ?? 0)} AS models_count`,
32
+ `${Number(record.total_evaluations ?? 0)} AS total_evaluations`,
33
+ `${sqlString(String(record.last_updated ?? ""))} AS last_updated`,
34
+ `${sqlString(payloadJson)} AS payload_json`,
35
+ ].join(", ")
36
+ })
37
+ .join(" UNION ALL ")
38
+
39
+ const connection = await DuckDBConnection.create()
40
+ await connection.run(`COPY (${selects}) TO ${sqlString(path.join(parquetDir, fileName))} (FORMAT parquet)`)
41
+ }
42
+
43
+ describe("DuckDB local data backend", () => {
44
+ it("reads model-card lite payloads from local Parquet", async () => {
45
+ const outputDir = await mkdtemp(path.join(os.tmpdir(), "eval-card-duckdb-"))
46
+ const previousOutput = process.env.LOCAL_PIPELINE_OUTPUT
47
+
48
+ try {
49
+ process.env.LOCAL_PIPELINE_OUTPUT = outputDir
50
+ await writeParquetPayload(outputDir, "model_cards_lite.parquet", [
51
+ {
52
+ model_family_id: "openai/gpt-5",
53
+ model_route_id: "openai__gpt-5",
54
+ model_family_name: "GPT 5",
55
+ developer: "openai",
56
+ params_billions: 100,
57
+ total_evaluations: 3,
58
+ benchmark_count: 2,
59
+ benchmark_family_count: 2,
60
+ categories_covered: ["reasoning"],
61
+ last_updated: "2026-01-01T00:00:00Z",
62
+ variants: [],
63
+ score_summary: { count: 1, min: 0.7, max: 0.9, average: 0.8 },
64
+ benchmark_names: ["mmlu"],
65
+ top_benchmark_scores: [
66
+ { benchmark: "mmlu", score: 0.9, metric: "accuracy" },
67
+ ],
68
+ },
69
+ ])
70
+
71
+ const cards = await getModelCardsLiteFromDuckDB()
72
+ expect(cards).toHaveLength(1)
73
+ expect(cards[0]).toMatchObject({
74
+ route_id: "openai__gpt-5",
75
+ model_name: "GPT 5",
76
+ developer: "OpenAI",
77
+ evaluations_count: 3,
78
+ })
79
+ } finally {
80
+ if (previousOutput == null) {
81
+ delete process.env.LOCAL_PIPELINE_OUTPUT
82
+ } else {
83
+ process.env.LOCAL_PIPELINE_OUTPUT = previousOutput
84
+ }
85
+ await rm(outputDir, { recursive: true, force: true })
86
+ }
87
+ })
88
+
89
+ it("fails clearly when the expected Parquet file is missing", async () => {
90
+ const outputDir = await mkdtemp(path.join(os.tmpdir(), "eval-card-duckdb-missing-"))
91
+ const previousOutput = process.env.LOCAL_PIPELINE_OUTPUT
92
+
93
+ try {
94
+ process.env.LOCAL_PIPELINE_OUTPUT = outputDir
95
+ await expect(getModelCardsLiteFromDuckDB()).rejects.toThrow(
96
+ /EXPORT_EXPERIMENTAL_PARQUET=1/
97
+ )
98
+ } finally {
99
+ if (previousOutput == null) {
100
+ delete process.env.LOCAL_PIPELINE_OUTPUT
101
+ } else {
102
+ process.env.LOCAL_PIPELINE_OUTPUT = previousOutput
103
+ }
104
+ await rm(outputDir, { recursive: true, force: true })
105
+ }
106
+ })
107
+ })
tests/server-only-stub.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ export {}
vitest.config.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fileURLToPath } from "url"
2
+
3
+ import { defineConfig } from "vitest/config"
4
+
5
+ export default defineConfig({
6
+ resolve: {
7
+ alias: {
8
+ "@": fileURLToPath(new URL(".", import.meta.url)),
9
+ "server-only": fileURLToPath(new URL("./tests/server-only-stub.ts", import.meta.url)),
10
+ },
11
+ },
12
+ test: {
13
+ // upstream-drift.test.ts walks the full live HF cache (~16s) and is opt-in
14
+ // via `pnpm test:drift`. The drift script sets RUN_DRIFT=1; the test file
15
+ // self-skips otherwise. We DON'T exclude the path here because vitest's
16
+ // exclude wins over an explicit path arg, which would silently make
17
+ // `pnpm test:drift` find zero tests.
18
+ exclude: ["**/node_modules/**", "**/dist/**"],
19
+ },
20
+ })