@struktur/benchmarks 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +108 -0
  3. package/dist/cache.d.ts +5 -0
  4. package/dist/cache.d.ts.map +1 -0
  5. package/dist/cli.d.ts +3 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +961 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/config.d.ts +13 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/datasets/index.d.ts +5 -0
  12. package/dist/datasets/index.d.ts.map +1 -0
  13. package/dist/datasets/longdoc.d.ts +34 -0
  14. package/dist/datasets/longdoc.d.ts.map +1 -0
  15. package/dist/datasets/source.d.ts +48 -0
  16. package/dist/datasets/source.d.ts.map +1 -0
  17. package/dist/datasets/sroie.d.ts +14 -0
  18. package/dist/datasets/sroie.d.ts.map +1 -0
  19. package/dist/datasets/synthetic.d.ts +27 -0
  20. package/dist/datasets/synthetic.d.ts.map +1 -0
  21. package/dist/datasets.js +392 -0
  22. package/dist/datasets.js.map +1 -0
  23. package/dist/expose/gold-score.d.ts +15 -0
  24. package/dist/expose/gold-score.d.ts.map +1 -0
  25. package/dist/expose/gold.d.ts +8 -0
  26. package/dist/expose/gold.d.ts.map +1 -0
  27. package/dist/expose/instructions.d.ts +2 -0
  28. package/dist/expose/instructions.d.ts.map +1 -0
  29. package/dist/expose/metric.d.ts +7 -0
  30. package/dist/expose/metric.d.ts.map +1 -0
  31. package/dist/expose/schema.d.ts +304 -0
  32. package/dist/expose/schema.d.ts.map +1 -0
  33. package/dist/index.d.ts +9 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +686 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/pricing.d.ts +15 -0
  38. package/dist/pricing.d.ts.map +1 -0
  39. package/dist/report.d.ts +6 -0
  40. package/dist/report.d.ts.map +1 -0
  41. package/dist/runner.d.ts +81 -0
  42. package/dist/runner.d.ts.map +1 -0
  43. package/dist/scoring/normalize.d.ts +20 -0
  44. package/dist/scoring/normalize.d.ts.map +1 -0
  45. package/dist/scoring/score.d.ts +39 -0
  46. package/dist/scoring/score.d.ts.map +1 -0
  47. package/dist/tracks.d.ts +16 -0
  48. package/dist/tracks.d.ts.map +1 -0
  49. package/dist/types.d.ts +88 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/package.json +40 -0
@@ -0,0 +1,392 @@
1
+ // src/datasets/source.ts
2
+ import { mkdir, readFile, writeFile } from "fs/promises";
3
+ import path from "path";
4
+
5
+ // src/config.ts
6
+ var defaultDatasetDir = () => process.env.STRUKTUR_BENCHMARKS_DIR ?? `${process.env.HOME ?? "."}/.struktur/benchmarks`;
7
+
8
+ // src/datasets/source.ts
9
+ var readCachedCases = async (file) => {
10
+ try {
11
+ return JSON.parse(await readFile(file, "utf8"));
12
+ } catch {
13
+ return void 0;
14
+ }
15
+ };
16
+ async function loadDataset(dataset, options = {}) {
17
+ const base = path.join(options.cacheDir ?? defaultDatasetDir(), dataset.id, dataset.version);
18
+ const casesFile = path.join(base, "cases.json");
19
+ const rawDir = path.join(base, "raw");
20
+ const limit = options.limit;
21
+ const offset = options.offset ?? 0;
22
+ let cases = options.force ? void 0 : await readCachedCases(casesFile);
23
+ if (!cases) {
24
+ if (dataset.source.kind === "manual") {
25
+ const hint = dataset.source.hint ?? `download the dataset into ${rawDir}`;
26
+ throw new Error(`Dataset "${dataset.id}" requires manual download. ${hint}`);
27
+ }
28
+ await mkdir(rawDir, { recursive: true });
29
+ await dataset.download(rawDir, options.fetchImpl);
30
+ cases = await dataset.convert(rawDir);
31
+ await mkdir(base, { recursive: true });
32
+ await writeFile(casesFile, JSON.stringify(cases, null, 2), "utf8");
33
+ await writeFile(
34
+ path.join(base, "manifest.json"),
35
+ JSON.stringify(
36
+ {
37
+ id: dataset.id,
38
+ version: dataset.version,
39
+ license: dataset.license,
40
+ attribution: dataset.attribution,
41
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
42
+ count: cases.length
43
+ },
44
+ null,
45
+ 2
46
+ ),
47
+ "utf8"
48
+ );
49
+ }
50
+ return cases.slice(offset, limit !== void 0 ? offset + limit : void 0);
51
+ }
52
+ var DEFAULT_HUB_BASE = "https://datasets-server.huggingface.co";
53
+ async function downloadHubRows(source, fetchImpl = fetch) {
54
+ const base = source.baseUrl ?? DEFAULT_HUB_BASE;
55
+ const repo = encodeURIComponent(source.repo);
56
+ const config = source.config ?? "default";
57
+ const split = source.split ?? "train";
58
+ const pageSize = 100;
59
+ const rows = [];
60
+ let offset = 0;
61
+ for (; ; ) {
62
+ const url = `${base}/rows?dataset=${repo}&config=${config}&split=${split}&offset=${offset}&length=${pageSize}`;
63
+ const res = await fetchImpl(url);
64
+ if (!res.ok) {
65
+ throw new Error(`Hugging Face datasets-server error ${res.status} for ${url}`);
66
+ }
67
+ const body = await res.json();
68
+ const page = body.rows ?? [];
69
+ if (page.length === 0) break;
70
+ rows.push(...page.map((r) => r.row));
71
+ if (page.length < pageSize) break;
72
+ offset += page.length;
73
+ }
74
+ return rows;
75
+ }
76
+
77
+ // src/datasets/synthetic.ts
78
+ function textArtifact(text, id) {
79
+ return {
80
+ id: id ?? `artifact-${crypto.randomUUID()}`,
81
+ type: "text",
82
+ raw: async () => Buffer.from(text, "utf8"),
83
+ contents: [{ text }]
84
+ };
85
+ }
86
+ function syntheticCases(datasetId, schema, records, opts = {}) {
87
+ return records.map((r, i) => ({
88
+ id: r.id ?? `${datasetId}-${i}`,
89
+ schema,
90
+ gold: r.gold,
91
+ artifacts: [textArtifact(r.text)],
92
+ tracks: opts.tracks ?? ["text"],
93
+ metrics: opts.metrics,
94
+ source: { dataset: datasetId, license: "synthetic" }
95
+ }));
96
+ }
97
+ var mulberry32 = (seed) => {
98
+ let a = seed >>> 0;
99
+ return () => {
100
+ a |= 0;
101
+ a = a + 1831565813 | 0;
102
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
103
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
104
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
105
+ };
106
+ };
107
+ var DEFAULT_STRING_VALUES = ["Acme Corp", "Example Ltd", "Northwind", "Globex", "Initech"];
108
+ var DEFAULT_NUMBER_VALUES = ["42", "100.5", "7", "1500", "0.75"];
109
+ function generateKeyValueCases(datasetId, fields, count, seed = 1) {
110
+ const rnd = mulberry32(seed);
111
+ const pick = (arr) => arr[Math.floor(rnd() * arr.length)];
112
+ const schema = {
113
+ type: "object",
114
+ properties: Object.fromEntries(fields.map((f) => [f.name, { type: f.type ?? "string" }])),
115
+ required: fields.map((f) => f.name),
116
+ additionalProperties: false
117
+ };
118
+ const cases = [];
119
+ for (let i = 0; i < count; i++) {
120
+ const lines = [];
121
+ const gold = {};
122
+ for (const f of fields) {
123
+ const values = f.values ?? (f.type === "number" ? DEFAULT_NUMBER_VALUES : DEFAULT_STRING_VALUES);
124
+ const raw = pick(values);
125
+ const value = f.type === "number" ? Number.parseFloat(raw) : raw;
126
+ lines.push(`${f.name}: ${raw}`);
127
+ gold[f.name] = value;
128
+ }
129
+ const text = lines.join("\n");
130
+ cases.push({
131
+ id: `${datasetId}-${i}`,
132
+ schema,
133
+ gold,
134
+ artifacts: [textArtifact(text, `artifact-${datasetId}-${i}`)],
135
+ tracks: ["text"],
136
+ source: { dataset: datasetId, license: "synthetic" }
137
+ });
138
+ }
139
+ return cases;
140
+ }
141
+
142
+ // src/datasets/longdoc.ts
143
+ var mulberry322 = (seed) => {
144
+ let a = seed >>> 0;
145
+ return () => {
146
+ a |= 0;
147
+ a = a + 1831565813 | 0;
148
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
149
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
150
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
151
+ };
152
+ };
153
+ var SCHEMA = {
154
+ type: "object",
155
+ properties: {
156
+ total_records: { type: "number" },
157
+ total_amount: { type: "number" },
158
+ records: {
159
+ type: "array",
160
+ items: {
161
+ type: "object",
162
+ properties: {
163
+ id: { type: "string" },
164
+ customer: { type: "string" },
165
+ amount: { type: "number" },
166
+ notes: { type: "string" }
167
+ },
168
+ required: ["id", "customer", "amount", "notes"],
169
+ additionalProperties: false
170
+ }
171
+ }
172
+ },
173
+ required: ["total_records", "total_amount", "records"],
174
+ additionalProperties: false
175
+ };
176
+ var CUSTOMERS = [
177
+ "Acme Corp",
178
+ "Globex Inc",
179
+ "Initech LLC",
180
+ "Umbrella Corp",
181
+ "Soylent Corp",
182
+ "Hooli",
183
+ "Pied Piper",
184
+ "Stark Industries",
185
+ "Wayne Enterprises",
186
+ "Aperture Science"
187
+ ];
188
+ var NOTES = [
189
+ "priority order",
190
+ "standard delivery",
191
+ "expedited shipping",
192
+ "payment pending",
193
+ "payment received",
194
+ "on hold \u2014 awaiting review",
195
+ "partially shipped",
196
+ "fully delivered",
197
+ "return requested",
198
+ "refund issued",
199
+ "backordered",
200
+ "customer pickup"
201
+ ];
202
+ var buildRecords = (count, seed) => {
203
+ const rnd = mulberry322(seed);
204
+ const records = [];
205
+ for (let i = 0; i < count; i++) {
206
+ records.push({
207
+ id: `REC-${String(i + 1).padStart(5, "0")}`,
208
+ customer: CUSTOMERS[Math.floor(rnd() * CUSTOMERS.length)],
209
+ amount: Math.round(rnd() * 1e5) / 100,
210
+ notes: NOTES[Math.floor(rnd() * NOTES.length)]
211
+ });
212
+ }
213
+ return records;
214
+ };
215
+ var buildText = (records) => records.map(
216
+ (r) => `Record ${r.id}
217
+ Customer: ${r.customer}
218
+ Amount: ${r.amount.toFixed(2)}
219
+ Notes: ${r.notes}
220
+ `
221
+ ).join("\n---\n");
222
+ var longDocArtifact = (id, text) => textArtifact(text, id);
223
+ function longDocCase(spec) {
224
+ const records = buildRecords(spec.recordCount, spec.seed ?? 42);
225
+ const text = buildText(records);
226
+ const total = records.reduce((a, r) => a + r.amount, 0);
227
+ return {
228
+ id: `longdoc-${spec.id}`,
229
+ schema: SCHEMA,
230
+ gold: {
231
+ total_records: records.length,
232
+ total_amount: Math.round(total * 100) / 100,
233
+ records
234
+ },
235
+ artifacts: [longDocArtifact(`longdoc-${spec.id}`, text)],
236
+ tracks: ["text"],
237
+ metrics: {
238
+ default: "normalized",
239
+ fields: { amount: "tolerance", total_amount: "tolerance" },
240
+ arrays: [{ path: "records", key: "id" }]
241
+ },
242
+ source: { dataset: "longdoc-synthetic" }
243
+ };
244
+ }
245
+ var longDocCases = () => [
246
+ longDocCase({ id: "5k", recordCount: 220, seed: 1 }),
247
+ longDocCase({ id: "10k", recordCount: 440, seed: 2 }),
248
+ longDocCase({ id: "20k", recordCount: 880, seed: 3 })
249
+ ];
250
+
251
+ // src/datasets/sroie.ts
252
+ var REPO = "darentang/sroie";
253
+ var CONFIG = "sroie";
254
+ var LABELS = [
255
+ "O",
256
+ "B-COMPANY",
257
+ "I-COMPANY",
258
+ "B-DATE",
259
+ "I-DATE",
260
+ "B-ADDRESS",
261
+ "I-ADDRESS",
262
+ "B-TOTAL",
263
+ "I-TOTAL"
264
+ ];
265
+ var SCHEMA2 = {
266
+ type: "object",
267
+ properties: {
268
+ company: { type: ["string", "null"] },
269
+ date: { type: ["string", "null"] },
270
+ address: { type: ["string", "null"] },
271
+ total: { type: ["string", "null"] }
272
+ },
273
+ additionalProperties: false
274
+ };
275
+ function reconstructEntities(words, tags) {
276
+ const out = {
277
+ company: null,
278
+ date: null,
279
+ address: null,
280
+ total: null
281
+ };
282
+ let current = null;
283
+ let buffer = [];
284
+ const flush = () => {
285
+ if (current && buffer.length > 0) {
286
+ out[current.toLowerCase()] = buffer.join(" ");
287
+ }
288
+ current = null;
289
+ buffer = [];
290
+ };
291
+ for (let i = 0; i < words.length; i++) {
292
+ const tag = LABELS[tags[i] ?? 0] ?? "O";
293
+ if (tag.startsWith("B-")) {
294
+ flush();
295
+ current = tag.slice(2);
296
+ buffer = [words[i] ?? ""];
297
+ } else if (tag.startsWith("I-") && current === tag.slice(2)) {
298
+ buffer.push(words[i] ?? "");
299
+ } else {
300
+ flush();
301
+ }
302
+ }
303
+ flush();
304
+ return out;
305
+ }
306
+ var imageUrl = (imagePath) => `https://huggingface.co/datasets/${REPO}/resolve/main/${imagePath}`;
307
+ var imageArtifact = (id, url) => ({
308
+ id: `artifact-${id}`,
309
+ type: "image",
310
+ raw: async () => Buffer.from(""),
311
+ contents: [{ media: [{ type: "image", url }] }]
312
+ });
313
+ function sroieToCases(rows) {
314
+ const cases = [];
315
+ for (const row of rows) {
316
+ const words = row.words ?? [];
317
+ const tags = row.ner_tags ?? [];
318
+ const imagePath = row.image_path;
319
+ if (!imagePath) continue;
320
+ const gold = reconstructEntities(words, tags);
321
+ const id = row.id ?? imagePath;
322
+ const url = imageUrl(imagePath);
323
+ const text = words.join(" ");
324
+ cases.push({
325
+ id: `sroie-${id}`,
326
+ schema: SCHEMA2,
327
+ gold,
328
+ artifacts: [imageArtifact(id, url)],
329
+ tracks: ["text", "text+embedded"],
330
+ artifactsByTrack: {
331
+ text: [
332
+ {
333
+ id: `artifact-${id}-text`,
334
+ type: "text",
335
+ raw: async () => Buffer.from(text, "utf8"),
336
+ contents: [{ text }]
337
+ }
338
+ ],
339
+ "text+embedded": [imageArtifact(id, url)]
340
+ },
341
+ metrics: { default: "normalized" },
342
+ source: {
343
+ dataset: "sroie",
344
+ license: "ICDAR 2019 SROIE (research use)",
345
+ url: "https://github.com/zzzDavid/ICDAR-2019-SROIE",
346
+ attribution: "ICDAR 2019 SROIE (Huang et al., 2019)"
347
+ }
348
+ });
349
+ }
350
+ return cases;
351
+ }
352
+ var download = async (rawDir, fetchImpl) => {
353
+ const { writeFile: writeFile2 } = await import("fs/promises");
354
+ const path2 = await import("path");
355
+ const all = [];
356
+ for (const split of ["train", "test"]) {
357
+ const rows = await downloadHubRows(
358
+ { kind: "hub", repo: REPO, config: CONFIG, split },
359
+ fetchImpl
360
+ );
361
+ all.push(...rows);
362
+ }
363
+ await writeFile2(path2.join(rawDir, "rows.json"), JSON.stringify(all), "utf8");
364
+ };
365
+ var convert = async (rawDir) => {
366
+ const { readFile: readFile2 } = await import("fs/promises");
367
+ const path2 = await import("path");
368
+ const rows = JSON.parse(await readFile2(path2.join(rawDir, "rows.json"), "utf8"));
369
+ return sroieToCases(rows);
370
+ };
371
+ var sroie = {
372
+ id: "sroie",
373
+ version: "1",
374
+ source: { kind: "hub", repo: REPO, config: CONFIG },
375
+ license: "ICDAR 2019 SROIE (research use)",
376
+ attribution: "ICDAR 2019 SROIE (Huang et al., 2019)",
377
+ download,
378
+ convert
379
+ };
380
+ export {
381
+ downloadHubRows,
382
+ generateKeyValueCases,
383
+ loadDataset,
384
+ longDocCase,
385
+ longDocCases,
386
+ reconstructEntities,
387
+ sroie,
388
+ sroieToCases,
389
+ syntheticCases,
390
+ textArtifact
391
+ };
392
+ //# sourceMappingURL=datasets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/datasets/source.ts","../src/config.ts","../src/datasets/synthetic.ts","../src/datasets/longdoc.ts","../src/datasets/sroie.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { BenchmarkCase } from \"../types\";\nimport { defaultDatasetDir } from \"../config\";\n\n/** Injectable fetch for tests. */\nexport type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\n\n/** Where a dataset's raw files come from. */\nexport type DatasetSource =\n | { kind: \"hub\"; repo: string; config?: string; split?: string; baseUrl?: string }\n | { kind: \"url\"; url: string }\n | { kind: \"manual\"; hint?: string };\n\nexport type Dataset = {\n id: string;\n /** Bump to invalidate the local cache. */\n version: string;\n source: DatasetSource;\n license?: string;\n attribution?: string;\n /** Download raw files into `rawDir`. */\n download: (rawDir: string, fetchImpl?: FetchLike) => Promise<void>;\n /** Convert raw files in `rawDir` into benchmark cases. */\n convert: (rawDir: string) => Promise<BenchmarkCase[]>;\n};\n\nexport type LoadDatasetOptions = {\n cacheDir?: string;\n /** Return only this many cases (defaults to all). */\n limit?: number;\n offset?: number;\n fetchImpl?: FetchLike;\n /** Re-download + re-convert even if cached. */\n force?: boolean;\n};\n\nconst readCachedCases = async (file: string): Promise<BenchmarkCase[] | undefined> => {\n try {\n return JSON.parse(await readFile(file, \"utf8\")) as BenchmarkCase[];\n } catch {\n return undefined;\n }\n};\n\n/**\n * Load a dataset's cases, downloading and converting on first use and caching\n * the converted result under `~/.struktur/benchmarks/<id>/<version>/`.\n */\nexport async function loadDataset(\n dataset: Dataset,\n options: LoadDatasetOptions = {},\n): Promise<BenchmarkCase[]> {\n const base = path.join(options.cacheDir ?? defaultDatasetDir(), dataset.id, dataset.version);\n const casesFile = path.join(base, \"cases.json\");\n const rawDir = path.join(base, \"raw\");\n const limit = options.limit;\n const offset = options.offset ?? 0;\n\n let cases = options.force ? undefined : await readCachedCases(casesFile);\n\n if (!cases) {\n if (dataset.source.kind === \"manual\") {\n const hint = dataset.source.hint ?? `download the dataset into ${rawDir}`;\n throw new Error(`Dataset \"${dataset.id}\" requires manual download. ${hint}`);\n }\n\n await mkdir(rawDir, { recursive: true });\n await dataset.download(rawDir, options.fetchImpl);\n cases = await dataset.convert(rawDir);\n\n await mkdir(base, { recursive: true });\n await writeFile(casesFile, JSON.stringify(cases, null, 2), \"utf8\");\n await writeFile(\n path.join(base, \"manifest.json\"),\n JSON.stringify(\n {\n id: dataset.id,\n version: dataset.version,\n license: dataset.license,\n attribution: dataset.attribution,\n generatedAt: new Date().toISOString(),\n count: cases.length,\n },\n null,\n 2,\n ),\n \"utf8\",\n );\n }\n\n return cases.slice(offset, limit !== undefined ? offset + limit : undefined);\n}\n\n// ---------------------------------------------------------------------------\n// Hugging Face datasets-server helpers\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_HUB_BASE = \"https://datasets-server.huggingface.co\";\n\n/** Fetch every row of a hub dataset via the datasets-server `/rows` endpoint. */\nexport async function downloadHubRows(\n source: Extract<DatasetSource, { kind: \"hub\" }>,\n fetchImpl: FetchLike = fetch,\n): Promise<Record<string, unknown>[]> {\n const base = source.baseUrl ?? DEFAULT_HUB_BASE;\n const repo = encodeURIComponent(source.repo);\n const config = source.config ?? \"default\";\n const split = source.split ?? \"train\";\n const pageSize = 100;\n\n const rows: Record<string, unknown>[] = [];\n let offset = 0;\n for (;;) {\n const url = `${base}/rows?dataset=${repo}&config=${config}&split=${split}&offset=${offset}&length=${pageSize}`;\n const res = await fetchImpl(url);\n if (!res.ok) {\n throw new Error(`Hugging Face datasets-server error ${res.status} for ${url}`);\n }\n const body = (await res.json()) as { rows?: { row: Record<string, unknown> }[] };\n const page = body.rows ?? [];\n if (page.length === 0) break;\n rows.push(...page.map((r) => r.row));\n if (page.length < pageSize) break;\n offset += page.length;\n }\n return rows;\n}\n","/**\n * The benchmark measures the harness, not the model — the model is a fixed baseline.\n *\n * deepseek-v4-flash-vision-exp: ~$0.60/M tokens, vision-capable, supports\n * structured output (json_schema) via OpenRouter. Cheap enough for frequent\n * benchmark runs while testing the full matrix of text + image variants.\n */\nexport const BENCHMARK_MODEL = \"openrouter/deepseek/deepseek-v4-flash-vision-exp\";\n\n/** Default cache root for downloaded datasets. */\nexport const defaultDatasetDir = (): string =>\n process.env.STRUKTUR_BENCHMARKS_DIR ?? `${process.env.HOME ?? \".\"}/.struktur/benchmarks`;\n\n/** Default cache root for extraction result cells. */\nexport const defaultResultsDir = (): string => `${defaultDatasetDir()}/results`;\n","import type { Artifact } from \"@struktur/sdk\";\nimport type { BenchmarkCase, FieldMetricSpec, Track } from \"../types\";\n\n/** Build a plain text artifact (no media). */\nexport function textArtifact(text: string, id?: string): Artifact {\n return {\n id: id ?? `artifact-${crypto.randomUUID()}`,\n type: \"text\",\n raw: async () => Buffer.from(text, \"utf8\"),\n contents: [{ text }],\n };\n}\n\nexport type SyntheticRecord = {\n text: string;\n gold: unknown;\n id?: string;\n};\n\n/** Wrap user-supplied `(text, gold)` records into benchmark cases. */\nexport function syntheticCases(\n datasetId: string,\n schema: unknown,\n records: SyntheticRecord[],\n opts: { metrics?: FieldMetricSpec; tracks?: Track[] } = {},\n): BenchmarkCase[] {\n return records.map((r, i) => ({\n id: r.id ?? `${datasetId}-${i}`,\n schema,\n gold: r.gold,\n artifacts: [textArtifact(r.text)],\n tracks: opts.tracks ?? [\"text\"],\n metrics: opts.metrics,\n source: { dataset: datasetId, license: \"synthetic\" },\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Deterministic key-value generator\n// ---------------------------------------------------------------------------\n\nexport type SimpleField = {\n name: string;\n type?: \"string\" | \"number\";\n /** Candidate values; picked deterministically. */\n values?: string[];\n};\n\n/** Mulberry32 seeded PRNG for reproducible generation. */\nconst mulberry32 = (seed: number) => {\n let a = seed >>> 0;\n return () => {\n a |= 0;\n a = (a + 0x6d2b79f5) | 0;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n};\n\nconst DEFAULT_STRING_VALUES = [\"Acme Corp\", \"Example Ltd\", \"Northwind\", \"Globex\", \"Initech\"];\nconst DEFAULT_NUMBER_VALUES = [\"42\", \"100.5\", \"7\", \"1500\", \"0.75\"];\n\n/**\n * Generate N deterministic text→JSON cases for schema/prompt comparison.\n * Each case renders `<Field Name>: <value>` lines and a gold object, so it\n * exercises flat key-value extraction with no network or files.\n */\nexport function generateKeyValueCases(\n datasetId: string,\n fields: SimpleField[],\n count: number,\n seed = 1,\n): BenchmarkCase[] {\n const rnd = mulberry32(seed);\n const pick = <T>(arr: readonly T[]): T => arr[Math.floor(rnd() * arr.length)]!;\n\n const schema = {\n type: \"object\",\n properties: Object.fromEntries(fields.map((f) => [f.name, { type: f.type ?? \"string\" }])),\n required: fields.map((f) => f.name),\n additionalProperties: false,\n };\n\n const cases: BenchmarkCase[] = [];\n for (let i = 0; i < count; i++) {\n const lines: string[] = [];\n const gold: Record<string, unknown> = {};\n for (const f of fields) {\n const values =\n f.values ?? (f.type === \"number\" ? DEFAULT_NUMBER_VALUES : DEFAULT_STRING_VALUES);\n const raw = pick(values);\n const value = f.type === \"number\" ? Number.parseFloat(raw) : raw;\n lines.push(`${f.name}: ${raw}`);\n gold[f.name] = value;\n }\n const text = lines.join(\"\\n\");\n cases.push({\n id: `${datasetId}-${i}`,\n schema,\n gold,\n artifacts: [textArtifact(text, `artifact-${datasetId}-${i}`)],\n tracks: [\"text\"],\n source: { dataset: datasetId, license: \"synthetic\" },\n });\n }\n return cases;\n}\n","import type { Artifact } from \"@struktur/sdk\";\nimport type { BenchmarkCase } from \"../types\";\nimport { textArtifact } from \"./synthetic\";\n\n/**\n * Synthetic long documents: `recordCount` self-contained records with a\n * repeating structure. Document length scales with record count, so total\n * length is controllable and the document actually splits into chunks.\n *\n * The schema requires collecting/counting across the whole document\n * (arrays + aggregates: total_records, total_amount), which exercises\n * chunking + merge quality — not just per-record extraction. Notes are short\n * meaningful phrases (no filler) so output tokens stay proportional to\n * content, keeping cost/latency measurements honest.\n */\n\nexport type LongDocSpec = {\n id: string;\n recordCount: number;\n /** Random seed for deterministic generation. */\n seed?: number;\n};\n\nconst mulberry32 = (seed: number) => {\n let a = seed >>> 0;\n return () => {\n a |= 0;\n a = (a + 0x6d2b79f5) | 0;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n};\n\nconst SCHEMA = {\n type: \"object\",\n properties: {\n total_records: { type: \"number\" },\n total_amount: { type: \"number\" },\n records: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n id: { type: \"string\" },\n customer: { type: \"string\" },\n amount: { type: \"number\" },\n notes: { type: \"string\" },\n },\n required: [\"id\", \"customer\", \"amount\", \"notes\"],\n additionalProperties: false,\n },\n },\n },\n required: [\"total_records\", \"total_amount\", \"records\"],\n additionalProperties: false,\n} as const;\n\nconst CUSTOMERS = [\n \"Acme Corp\",\n \"Globex Inc\",\n \"Initech LLC\",\n \"Umbrella Corp\",\n \"Soylent Corp\",\n \"Hooli\",\n \"Pied Piper\",\n \"Stark Industries\",\n \"Wayne Enterprises\",\n \"Aperture Science\",\n] as const;\n\nconst NOTES = [\n \"priority order\",\n \"standard delivery\",\n \"expedited shipping\",\n \"payment pending\",\n \"payment received\",\n \"on hold — awaiting review\",\n \"partially shipped\",\n \"fully delivered\",\n \"return requested\",\n \"refund issued\",\n \"backordered\",\n \"customer pickup\",\n] as const;\n\nexport type LongDocRecord = { id: string; customer: string; amount: number; notes: string };\n\nconst buildRecords = (count: number, seed: number): LongDocRecord[] => {\n const rnd = mulberry32(seed);\n const records: LongDocRecord[] = [];\n for (let i = 0; i < count; i++) {\n records.push({\n id: `REC-${String(i + 1).padStart(5, \"0\")}`,\n customer: CUSTOMERS[Math.floor(rnd() * CUSTOMERS.length)]!,\n amount: Math.round(rnd() * 100000) / 100,\n notes: NOTES[Math.floor(rnd() * NOTES.length)]!,\n });\n }\n return records;\n};\n\nconst buildText = (records: LongDocRecord[]): string =>\n records\n .map(\n (r) =>\n `Record ${r.id}\\nCustomer: ${r.customer}\\nAmount: ${r.amount.toFixed(2)}\\nNotes: ${r.notes}\\n`,\n )\n .join(\"\\n---\\n\");\n\nexport const longDocArtifact = (id: string, text: string): Artifact => textArtifact(text, id);\n\n/** Create a single long-document benchmark case. */\nexport function longDocCase(spec: LongDocSpec): BenchmarkCase {\n const records = buildRecords(spec.recordCount, spec.seed ?? 42);\n const text = buildText(records);\n const total = records.reduce((a, r) => a + r.amount, 0);\n\n return {\n id: `longdoc-${spec.id}`,\n schema: SCHEMA,\n gold: {\n total_records: records.length,\n total_amount: Math.round(total * 100) / 100,\n records,\n },\n artifacts: [longDocArtifact(`longdoc-${spec.id}`, text)],\n tracks: [\"text\"],\n metrics: {\n default: \"normalized\",\n fields: { amount: \"tolerance\", total_amount: \"tolerance\" },\n arrays: [{ path: \"records\", key: \"id\" }],\n },\n source: { dataset: \"longdoc-synthetic\" },\n };\n}\n\n/**\n * Long-document cases at ~5k / 10k / 20k tokens (4 chars/token).\n * Record count = targetTokens * 4 / ~90 chars per record.\n */\nexport const longDocCases = (): BenchmarkCase[] => [\n longDocCase({ id: \"5k\", recordCount: 220, seed: 1 }),\n longDocCase({ id: \"10k\", recordCount: 440, seed: 2 }),\n longDocCase({ id: \"20k\", recordCount: 880, seed: 3 }),\n];\n","import type { Artifact } from \"@struktur/sdk\";\nimport type { BenchmarkCase } from \"../types\";\nimport { downloadHubRows, type Dataset, type FetchLike } from \"./source\";\n\n/**\n * SROIE (ICDAR 2019, scanned receipts) — key information extraction:\n * company, date, address, total.\n *\n * Backed by the Hugging Face mirror `darentang/sroie`, which exposes per-word\n * BIO tags. Rows are downloaded via the datasets-server API; receipt images\n * are referenced by URL (fetched by the model at inference time), so no bulk\n * image download is needed.\n */\n\nconst REPO = \"darentang/sroie\";\nconst CONFIG = \"sroie\";\n\nconst LABELS = [\n \"O\",\n \"B-COMPANY\",\n \"I-COMPANY\",\n \"B-DATE\",\n \"I-DATE\",\n \"B-ADDRESS\",\n \"I-ADDRESS\",\n \"B-TOTAL\",\n \"I-TOTAL\",\n] as const;\n\nconst SCHEMA = {\n type: \"object\",\n properties: {\n company: { type: [\"string\", \"null\"] },\n date: { type: [\"string\", \"null\"] },\n address: { type: [\"string\", \"null\"] },\n total: { type: [\"string\", \"null\"] },\n },\n additionalProperties: false,\n} as const;\n\nexport type SroieRow = {\n id?: string;\n words?: string[];\n ner_tags?: number[];\n image_path?: string;\n};\n\n/** Reconstruct `company/date/address/total` from BIO-tagged words. */\nexport function reconstructEntities(\n words: string[],\n tags: number[],\n): Record<string, string | null> {\n const out: Record<string, string | null> = {\n company: null,\n date: null,\n address: null,\n total: null,\n };\n let current: string | null = null;\n let buffer: string[] = [];\n const flush = () => {\n if (current && buffer.length > 0) {\n out[current.toLowerCase()] = buffer.join(\" \");\n }\n current = null;\n buffer = [];\n };\n\n for (let i = 0; i < words.length; i++) {\n const tag = LABELS[tags[i] ?? 0] ?? \"O\";\n if (tag.startsWith(\"B-\")) {\n flush();\n current = tag.slice(2);\n buffer = [words[i] ?? \"\"];\n } else if (tag.startsWith(\"I-\") && current === tag.slice(2)) {\n buffer.push(words[i] ?? \"\");\n } else {\n flush();\n }\n }\n flush();\n return out;\n}\n\nconst imageUrl = (imagePath: string): string =>\n `https://huggingface.co/datasets/${REPO}/resolve/main/${imagePath}`;\n\nconst imageArtifact = (id: string, url: string): Artifact => ({\n id: `artifact-${id}`,\n type: \"image\",\n raw: async () => Buffer.from(\"\"),\n contents: [{ media: [{ type: \"image\", url }] }],\n});\n\n/** Convert raw HF rows into benchmark cases. */\nexport function sroieToCases(rows: SroieRow[]): BenchmarkCase[] {\n const cases: BenchmarkCase[] = [];\n for (const row of rows) {\n const words = row.words ?? [];\n const tags = row.ner_tags ?? [];\n const imagePath = row.image_path;\n if (!imagePath) continue;\n\n const gold = reconstructEntities(words, tags);\n const id = row.id ?? imagePath;\n const url = imageUrl(imagePath);\n\n // OCR text variant: words joined — a fair text-only baseline.\n const text = words.join(\" \");\n\n cases.push({\n id: `sroie-${id}`,\n schema: SCHEMA,\n gold,\n artifacts: [imageArtifact(id, url)],\n tracks: [\"text\", \"text+embedded\"],\n artifactsByTrack: {\n text: [\n {\n id: `artifact-${id}-text`,\n type: \"text\",\n raw: async () => Buffer.from(text, \"utf8\"),\n contents: [{ text }],\n },\n ],\n \"text+embedded\": [imageArtifact(id, url)],\n },\n metrics: { default: \"normalized\" },\n source: {\n dataset: \"sroie\",\n license: \"ICDAR 2019 SROIE (research use)\",\n url: \"https://github.com/zzzDavid/ICDAR-2019-SROIE\",\n attribution: \"ICDAR 2019 SROIE (Huang et al., 2019)\",\n },\n });\n }\n return cases;\n}\n\nconst download = async (rawDir: string, fetchImpl?: FetchLike): Promise<void> => {\n const { writeFile } = await import(\"node:fs/promises\");\n const path = await import(\"node:path\");\n const all: SroieRow[] = [];\n for (const split of [\"train\", \"test\"]) {\n const rows = await downloadHubRows(\n { kind: \"hub\", repo: REPO, config: CONFIG, split },\n fetchImpl,\n );\n all.push(...(rows as SroieRow[]));\n }\n await writeFile(path.join(rawDir, \"rows.json\"), JSON.stringify(all), \"utf8\");\n};\n\nconst convert = async (rawDir: string): Promise<BenchmarkCase[]> => {\n const { readFile } = await import(\"node:fs/promises\");\n const path = await import(\"node:path\");\n const rows = JSON.parse(await readFile(path.join(rawDir, \"rows.json\"), \"utf8\")) as SroieRow[];\n return sroieToCases(rows);\n};\n\nexport const sroie: Dataset = {\n id: \"sroie\",\n version: \"1\",\n source: { kind: \"hub\", repo: REPO, config: CONFIG },\n license: \"ICDAR 2019 SROIE (research use)\",\n attribution: \"ICDAR 2019 SROIE (Huang et al., 2019)\",\n download,\n convert,\n};\n"],"mappings":"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","names":["mulberry32","SCHEMA","writeFile","path","readFile"]}
@@ -0,0 +1,15 @@
1
+ import { type ExposeGold } from "./schema";
2
+ /**
3
+ * Remove `images` / `floorplans` fields from a gold (or prediction) so a
4
+ * text-only run isn't penalised for a field it cannot legitimately populate.
5
+ *
6
+ * For gold: wraps in `real_estate_property` first (matching the schema), then
7
+ * strips image fields. For predictions: strips image fields as-is.
8
+ */
9
+ export declare const stripImages: (gold: ExposeGold | unknown, isGold?: boolean) => unknown;
10
+ /**
11
+ * `stripImages` + feature level-merge, applied to both gold and prediction
12
+ * before scoring. Call this instead of `stripImages` in scoring paths.
13
+ */
14
+ export declare const normalizeForScoring: (gold: ExposeGold | unknown, isGold?: boolean) => unknown;
15
+ //# sourceMappingURL=gold-score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gold-score.d.ts","sourceRoot":"","sources":["../../src/expose/gold-score.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAmDrD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,UAAU,GAAG,OAAO,EAAE,SAAS,OAAO,KAAG,OACe,CAAC;AAE3F;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,UAAU,GAAG,OAAO,EAAE,SAAS,OAAO,KAAG,OAChC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { ExposeGold } from "./schema";
2
+ export declare const sandstrasse: ExposeGold;
3
+ export declare const primePulse: ExposeGold;
4
+ export declare const flottenstrasse: ExposeGold;
5
+ export declare const dock100: ExposeGold;
6
+ export declare const holzhauser: ExposeGold;
7
+ export declare const elsenstrasse: ExposeGold;
8
+ //# sourceMappingURL=gold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gold.d.ts","sourceRoot":"","sources":["../../src/expose/gold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAO3C,eAAO,MAAM,WAAW,EAAE,UAuDzB,CAAC;AAKF,eAAO,MAAM,UAAU,EAAE,UA4ExB,CAAC;AAKF,eAAO,MAAM,cAAc,EAAE,UA8H5B,CAAC;AAKF,eAAO,MAAM,OAAO,EAAE,UAoJrB,CAAC;AAKF,eAAO,MAAM,UAAU,EAAE,UAqGxB,CAAC;AAKF,eAAO,MAAM,YAAY,EAAE,UAmJ1B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function buildEstateInstructions(): string;
2
+ //# sourceMappingURL=instructions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/expose/instructions.ts"],"names":[],"mappings":"AAMA,wBAAgB,uBAAuB,IAAI,MAAM,CA6IhD"}
@@ -0,0 +1,7 @@
1
+ import type { FieldMetricSpec } from "../types";
2
+ /**
3
+ * Scoring rules for the estate exposes. usages + features are order-insensitive
4
+ * sets; numeric fields use tolerance; prose fields use content-word coverage.
5
+ */
6
+ export declare const exposeMetric: FieldMetricSpec;
7
+ //# sourceMappingURL=metric.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metric.d.ts","sourceRoot":"","sources":["../../src/expose/metric.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,eAqB1B,CAAC"}