keepset 0.25.0 → 0.26.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.
@@ -529,6 +529,10 @@ const ParserSchema = z.discriminatedUnion("format", [
529
529
  format: z.literal("toptw"),
530
530
  encoding: EncodingSchema
531
531
  }).strict(),
532
+ z.object({
533
+ format: z.literal("tsplib"),
534
+ encoding: EncodingSchema
535
+ }).strict(),
532
536
  z.object({
533
537
  format: z.literal("csv-table"),
534
538
  encoding: EncodingSchema,
@@ -988,7 +992,7 @@ var catalogue_default = {
988
992
  "id": "keepset",
989
993
  "name": "keepset",
990
994
  "description": "The keepset catalogue: datasets with machine-readable recipes that fetch each one and normalise it into its category's target shape.",
991
- "version": "1.13.0",
995
+ "version": "1.14.0",
992
996
  "repository": "https://github.com/Mearman/keepset",
993
997
  "namespaceAliases": {
994
998
  "networkrepository": ["nr"],
@@ -56309,20 +56313,119 @@ var catalogue_default = {
56309
56313
  "territory",
56310
56314
  "travelTime"
56311
56315
  ],
56312
- "category": "or/v1",
56316
+ "category": "or-set/v1",
56313
56317
  "sources": [{
56314
56318
  "url": "http://www.vrp-rep.org/datasets/download/angelelli-et-al-2014.zip",
56315
56319
  "retrieved": "2026-10-19",
56316
56320
  "status": "live"
56321
+ }, {
56322
+ "url": "https://www.vrp-rep.org/datasets/download/angelelli-et-al-2014.zip",
56323
+ "retrieved": "2026-10-19",
56324
+ "status": "live"
56317
56325
  }],
56318
56326
  "licence": { "kind": "unrecorded" },
56319
56327
  "citations": [],
56320
56328
  "recipe": {
56321
- "status": "not-fetchable",
56322
- "reason": "The download is a collection of TSPLIB style instance files whose prizes belong to overlapping clusters of nodes and not to nodes, and a collection is more than one or/v1 document, so no recipe reads it."
56329
+ "status": "fetchable",
56330
+ "source": 1,
56331
+ "archive": {
56332
+ "kind": "zip",
56333
+ "members": { "extensions": [".cop"] }
56334
+ },
56335
+ "parser": {
56336
+ "format": "tsplib",
56337
+ "encoding": "utf-8"
56338
+ }
56323
56339
  },
56324
- "crosswalk": { "file": "crosswalks/or-v1/nodes.json" },
56325
- "expectedCounts": {},
56340
+ "crosswalk": {
56341
+ "source": "tsplib",
56342
+ "target": "or-set/v1",
56343
+ "rules": [
56344
+ {
56345
+ "op": "move",
56346
+ "from": "meta",
56347
+ "to": "meta"
56348
+ },
56349
+ {
56350
+ "op": "move",
56351
+ "from": "members[].id",
56352
+ "to": "instances[].id"
56353
+ },
56354
+ {
56355
+ "op": "move",
56356
+ "from": "members[].meta.name",
56357
+ "to": "instances[].meta.name"
56358
+ },
56359
+ {
56360
+ "op": "move",
56361
+ "from": "members[].meta.type",
56362
+ "to": "instances[].meta.type"
56363
+ },
56364
+ {
56365
+ "op": "move",
56366
+ "from": "members[].meta.budget",
56367
+ "to": "instances[].meta.budget"
56368
+ },
56369
+ {
56370
+ "op": "move",
56371
+ "from": "members[].meta.edgeWeightType",
56372
+ "to": "instances[].meta.edgeWeightType"
56373
+ },
56374
+ {
56375
+ "op": "move",
56376
+ "from": "members[].meta.setPrizes",
56377
+ "to": "instances[].meta.setPrizes"
56378
+ },
56379
+ {
56380
+ "op": "move",
56381
+ "from": "members[].nodes[].id",
56382
+ "to": "instances[].nodes[].id"
56383
+ },
56384
+ {
56385
+ "op": "move",
56386
+ "from": "members[].nodes[].lat",
56387
+ "to": "instances[].nodes[].lat"
56388
+ },
56389
+ {
56390
+ "op": "move",
56391
+ "from": "members[].nodes[].lon",
56392
+ "to": "instances[].nodes[].lon"
56393
+ },
56394
+ {
56395
+ "op": "move",
56396
+ "from": "members[].nodes[].x",
56397
+ "to": "instances[].nodes[].x"
56398
+ },
56399
+ {
56400
+ "op": "move",
56401
+ "from": "members[].nodes[].y",
56402
+ "to": "instances[].nodes[].y"
56403
+ },
56404
+ {
56405
+ "op": "move",
56406
+ "from": "members[].nodes[].clusters",
56407
+ "to": "instances[].nodes[].clusters"
56408
+ }
56409
+ ],
56410
+ "oneWay": [
56411
+ {
56412
+ "op": "drop",
56413
+ "from": "members[].meta.totalPrize",
56414
+ "direction": "encode"
56415
+ },
56416
+ {
56417
+ "op": "drop",
56418
+ "from": "members[].nodes[].prize",
56419
+ "direction": "encode"
56420
+ },
56421
+ {
56422
+ "op": "drop",
56423
+ "from": "members[].nodes[].probability",
56424
+ "direction": "encode"
56425
+ }
56426
+ ]
56427
+ },
56428
+ "expectedCounts": { "instances": 924 },
56326
56429
  "properties": { "surveyId": "or-4" }
56327
56430
  },
56328
56431
  {
@@ -56365,20 +56468,116 @@ var catalogue_default = {
56365
56468
  "location",
56366
56469
  "travelTime"
56367
56470
  ],
56368
- "category": "or/v1",
56471
+ "category": "or-set/v1",
56369
56472
  "sources": [{
56370
56473
  "url": "http://www.vrp-rep.org/datasets/download/angelelli-et-al-2017.zip",
56371
56474
  "retrieved": "2026-10-19",
56372
56475
  "status": "live"
56476
+ }, {
56477
+ "url": "https://www.vrp-rep.org/datasets/download/angelelli-et-al-2017.zip",
56478
+ "retrieved": "2026-10-19",
56479
+ "status": "live"
56373
56480
  }],
56374
56481
  "licence": { "kind": "unrecorded" },
56375
56482
  "citations": [],
56376
56483
  "recipe": {
56377
- "status": "not-fetchable",
56378
- "reason": "The download is a collection of TSPLIB style instance files with a prize and probability section, half of which carry no coordinates and some of which use the degrees and minutes form, and a collection is more than one or/v1 document, so no recipe reads it."
56484
+ "status": "fetchable",
56485
+ "source": 1,
56486
+ "archive": {
56487
+ "kind": "zip",
56488
+ "members": { "extensions": [".pop"] }
56489
+ },
56490
+ "parser": {
56491
+ "format": "tsplib",
56492
+ "encoding": "utf-8"
56493
+ }
56379
56494
  },
56380
- "crosswalk": { "file": "crosswalks/or-v1/nodes.json" },
56381
- "expectedCounts": {},
56495
+ "crosswalk": {
56496
+ "source": "tsplib",
56497
+ "target": "or-set/v1",
56498
+ "rules": [
56499
+ {
56500
+ "op": "move",
56501
+ "from": "meta",
56502
+ "to": "meta"
56503
+ },
56504
+ {
56505
+ "op": "move",
56506
+ "from": "members[].id",
56507
+ "to": "instances[].id"
56508
+ },
56509
+ {
56510
+ "op": "move",
56511
+ "from": "members[].meta.name",
56512
+ "to": "instances[].meta.name"
56513
+ },
56514
+ {
56515
+ "op": "move",
56516
+ "from": "members[].meta.type",
56517
+ "to": "instances[].meta.type"
56518
+ },
56519
+ {
56520
+ "op": "move",
56521
+ "from": "members[].meta.budget",
56522
+ "to": "instances[].meta.budget"
56523
+ },
56524
+ {
56525
+ "op": "move",
56526
+ "from": "members[].meta.edgeWeightType",
56527
+ "to": "instances[].meta.edgeWeightType"
56528
+ },
56529
+ {
56530
+ "op": "move",
56531
+ "from": "members[].meta.totalPrize",
56532
+ "to": "instances[].meta.totalPrize"
56533
+ },
56534
+ {
56535
+ "op": "move",
56536
+ "from": "members[].nodes[].id",
56537
+ "to": "instances[].nodes[].id"
56538
+ },
56539
+ {
56540
+ "op": "move",
56541
+ "from": "members[].nodes[].lat",
56542
+ "to": "instances[].nodes[].lat"
56543
+ },
56544
+ {
56545
+ "op": "move",
56546
+ "from": "members[].nodes[].lon",
56547
+ "to": "instances[].nodes[].lon"
56548
+ },
56549
+ {
56550
+ "op": "move",
56551
+ "from": "members[].nodes[].prize",
56552
+ "to": "instances[].nodes[].prize"
56553
+ },
56554
+ {
56555
+ "op": "move",
56556
+ "from": "members[].nodes[].probability",
56557
+ "to": "instances[].nodes[].prizeProbability"
56558
+ },
56559
+ {
56560
+ "op": "move",
56561
+ "from": "members[].nodes[].x",
56562
+ "to": "instances[].nodes[].x"
56563
+ },
56564
+ {
56565
+ "op": "move",
56566
+ "from": "members[].nodes[].y",
56567
+ "to": "instances[].nodes[].y"
56568
+ }
56569
+ ],
56570
+ "oneWay": [{
56571
+ "op": "drop",
56572
+ "from": "members[].meta.setPrizes",
56573
+ "direction": "encode"
56574
+ }, {
56575
+ "op": "drop",
56576
+ "from": "members[].nodes[].clusters",
56577
+ "direction": "encode"
56578
+ }]
56579
+ },
56580
+ "expectedCounts": { "instances": 264 },
56382
56581
  "properties": { "surveyId": "or-3" }
56383
56582
  },
56384
56583
  {
@@ -158,6 +158,12 @@ declare const ParserSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
158
158
  "utf-8": "utf-8";
159
159
  latin1: "latin1";
160
160
  }>;
161
+ }, z.core.$strict>, z.ZodObject<{
162
+ format: z.ZodLiteral<"tsplib">;
163
+ encoding: z.ZodEnum<{
164
+ "utf-8": "utf-8";
165
+ latin1: "latin1";
166
+ }>;
161
167
  }, z.core.$strict>, z.ZodObject<{
162
168
  format: z.ZodLiteral<"csv-table">;
163
169
  encoding: z.ZodEnum<{
@@ -289,6 +295,12 @@ declare const RecipeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
289
295
  "utf-8": "utf-8";
290
296
  latin1: "latin1";
291
297
  }>;
298
+ }, z.core.$strict>, z.ZodObject<{
299
+ format: z.ZodLiteral<"tsplib">;
300
+ encoding: z.ZodEnum<{
301
+ "utf-8": "utf-8";
302
+ latin1: "latin1";
303
+ }>;
292
304
  }, z.core.$strict>, z.ZodObject<{
293
305
  format: z.ZodLiteral<"csv-table">;
294
306
  encoding: z.ZodEnum<{
@@ -475,6 +487,12 @@ declare const EntrySchema: z.ZodObject<{
475
487
  "utf-8": "utf-8";
476
488
  latin1: "latin1";
477
489
  }>;
490
+ }, z.core.$strict>, z.ZodObject<{
491
+ format: z.ZodLiteral<"tsplib">;
492
+ encoding: z.ZodEnum<{
493
+ "utf-8": "utf-8";
494
+ latin1: "latin1";
495
+ }>;
478
496
  }, z.core.$strict>, z.ZodObject<{
479
497
  format: z.ZodLiteral<"csv-table">;
480
498
  encoding: z.ZodEnum<{
@@ -619,6 +637,9 @@ declare const EntrySchema: z.ZodObject<{
619
637
  } | {
620
638
  format: "toptw";
621
639
  encoding: "utf-8" | "latin1";
640
+ } | {
641
+ format: "tsplib";
642
+ encoding: "utf-8" | "latin1";
622
643
  };
623
644
  paging?: {
624
645
  kind: "arcgis-query";
@@ -880,6 +901,12 @@ declare const CatalogueIndexSchema: z.ZodObject<{
880
901
  "utf-8": "utf-8";
881
902
  latin1: "latin1";
882
903
  }>;
904
+ }, z.core.$strict>, z.ZodObject<{
905
+ format: z.ZodLiteral<"tsplib">;
906
+ encoding: z.ZodEnum<{
907
+ "utf-8": "utf-8";
908
+ latin1: "latin1";
909
+ }>;
883
910
  }, z.core.$strict>, z.ZodObject<{
884
911
  format: z.ZodLiteral<"csv-table">;
885
912
  encoding: z.ZodEnum<{
@@ -1024,6 +1051,9 @@ declare const CatalogueIndexSchema: z.ZodObject<{
1024
1051
  } | {
1025
1052
  format: "toptw";
1026
1053
  encoding: "utf-8" | "latin1";
1054
+ } | {
1055
+ format: "tsplib";
1056
+ encoding: "utf-8" | "latin1";
1027
1057
  };
1028
1058
  paging?: {
1029
1059
  kind: "arcgis-query";
@@ -1171,6 +1201,9 @@ declare const CatalogueIndexSchema: z.ZodObject<{
1171
1201
  } | {
1172
1202
  format: "toptw";
1173
1203
  encoding: "utf-8" | "latin1";
1204
+ } | {
1205
+ format: "tsplib";
1206
+ encoding: "utf-8" | "latin1";
1174
1207
  };
1175
1208
  paging?: {
1176
1209
  kind: "arcgis-query";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as Parser, A as LineageSchema, B as Citation, C as CatalogueIndex, D as NamespaceNode, E as DatasetQuery, F as AliasSchema, G as Entry, H as ColumnNameSchema, I as Archive, J as IdSchema, K as EntrySchema, L as ArchiveMember, M as ManifestSchema, N as SemVerSchema, O as createCatalogue, P as parseManifest, Q as NamespaceSchema, R as ArchiveMemberSchema, S as Catalogue, T as Dataset, U as CrosswalkReference, V as CitationSchema, W as CrosswalkReferenceSchema, X as LicenceSchema, Y as Licence, Z as Namespace, _ as GraphEdgeSchema, a as CategoryTargets, at as Snapshot, b as GraphV1, c as getCategory, ct as SourceSchema, d as listCategories, dt as waybackUrl, et as ParserSchema, f as OrNode, g as GraphEdge, h as OrV1Schema, i as CategoryTarget, it as RelativePathSchema, j as Manifest, k as Lineage, l as getTarget, lt as WaybackTimestampSchema, m as OrV1, n as CategoryId, nt as Recipe, o as CountedKey, ot as SnapshotSchema, p as OrNodeSchema, q as HttpUrlSchema, r as CategoryIdSchema, rt as RecipeSchema, s as countTarget, st as Source, t as Category, tt as RESERVED_META_KEYS, u as kindOf, ut as parseEntry, v as GraphNode, w as CatalogueIndexSchema, x as GraphV1Schema, y as GraphNodeSchema, z as ArchiveSchema } from "./categories-zC2eMs6y.js";
1
+ import { $ as Parser, A as LineageSchema, B as Citation, C as CatalogueIndex, D as NamespaceNode, E as DatasetQuery, F as AliasSchema, G as Entry, H as ColumnNameSchema, I as Archive, J as IdSchema, K as EntrySchema, L as ArchiveMember, M as ManifestSchema, N as SemVerSchema, O as createCatalogue, P as parseManifest, Q as NamespaceSchema, R as ArchiveMemberSchema, S as Catalogue, T as Dataset, U as CrosswalkReference, V as CitationSchema, W as CrosswalkReferenceSchema, X as LicenceSchema, Y as Licence, Z as Namespace, _ as GraphEdgeSchema, a as CategoryTargets, at as Snapshot, b as GraphV1, c as getCategory, ct as SourceSchema, d as listCategories, dt as waybackUrl, et as ParserSchema, f as OrNode, g as GraphEdge, h as OrV1Schema, i as CategoryTarget, it as RelativePathSchema, j as Manifest, k as Lineage, l as getTarget, lt as WaybackTimestampSchema, m as OrV1, n as CategoryId, nt as Recipe, o as CountedKey, ot as SnapshotSchema, p as OrNodeSchema, q as HttpUrlSchema, r as CategoryIdSchema, rt as RecipeSchema, s as countTarget, st as Source, t as Category, tt as RESERVED_META_KEYS, u as kindOf, ut as parseEntry, v as GraphNode, w as CatalogueIndexSchema, x as GraphV1Schema, y as GraphNodeSchema, z as ArchiveSchema } from "./categories-HeWbVPhc.js";
2
2
  import { Crosswalk } from "twindex";
3
3
  import { z } from "zod";
4
4
  //#region src/errors.d.ts
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as NativeParseError, A as toIssues, B as kindOf, C as RESERVED_META_KEYS, D as SourceSchema, E as SnapshotSchema, G as GraphNodeSchema, H as OrNodeSchema, I as CategoryIdSchema, J as ArchiveError, K as GraphV1Schema, L as countTarget, M as NAME_SEPARATOR, N as groupsOf, O as WaybackTimestampSchema, P as namesOf, Q as KeepsetError, R as getCategory, S as ParserSchema, T as RelativePathSchema, U as OrV1Schema, V as listCategories, W as GraphEdgeSchema, X as DatasetAmbiguousError, Y as CatalogueSyntaxError, Z as DatasetNotFoundError, _ as EntrySchema, a as CatalogueIndexSchema, b as LicenceSchema, c as ManifestSchema, d as AliasSchema, et as RecipeError, f as ArchiveMemberSchema, g as CrosswalkReferenceSchema, h as ColumnNameSchema, i as listNamespaces, j as waybackUrl, k as parseEntry, l as SemVerSchema, m as CitationSchema, n as getManifest, o as createCatalogue, p as ArchiveSchema, q as JsonScalarSchema, r as listDatasets, s as LineageSchema, t as getDataset, tt as UnknownCategoryError, u as parseManifest, v as HttpUrlSchema, w as RecipeSchema, x as NamespaceSchema, y as IdSchema, z as getTarget } from "./bundled-Dkvod55t.js";
1
+ import { $ as NativeParseError, A as toIssues, B as kindOf, C as RESERVED_META_KEYS, D as SourceSchema, E as SnapshotSchema, G as GraphNodeSchema, H as OrNodeSchema, I as CategoryIdSchema, J as ArchiveError, K as GraphV1Schema, L as countTarget, M as NAME_SEPARATOR, N as groupsOf, O as WaybackTimestampSchema, P as namesOf, Q as KeepsetError, R as getCategory, S as ParserSchema, T as RelativePathSchema, U as OrV1Schema, V as listCategories, W as GraphEdgeSchema, X as DatasetAmbiguousError, Y as CatalogueSyntaxError, Z as DatasetNotFoundError, _ as EntrySchema, a as CatalogueIndexSchema, b as LicenceSchema, c as ManifestSchema, d as AliasSchema, et as RecipeError, f as ArchiveMemberSchema, g as CrosswalkReferenceSchema, h as ColumnNameSchema, i as listNamespaces, j as waybackUrl, k as parseEntry, l as SemVerSchema, m as CitationSchema, n as getManifest, o as createCatalogue, p as ArchiveSchema, q as JsonScalarSchema, r as listDatasets, s as LineageSchema, t as getDataset, tt as UnknownCategoryError, u as parseManifest, v as HttpUrlSchema, w as RecipeSchema, x as NamespaceSchema, y as IdSchema, z as getTarget } from "./bundled-V1EJ0k9e.js";
2
2
  import { validateCrosswalk } from "twindex";
3
3
  //#region src/serialise.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { G as Entry, T as Dataset, at as Snapshot, i as CategoryTarget, st as Source } from "../categories-zC2eMs6y.js";
1
+ import { G as Entry, T as Dataset, at as Snapshot, i as CategoryTarget, st as Source } from "../categories-HeWbVPhc.js";
2
2
  //#region src/node/fetch-dataset.d.ts
3
3
  /** The network port a recipe downloads through: `downloadRaw` by default, which returns the bytes exactly as served, or a stand-in under test. */
4
4
  export type FetchPort = (url: string, init: {
@@ -1,4 +1,4 @@
1
- import { $ as NativeParseError, F as getOwn, J as ArchiveError, L as countTarget, Q as KeepsetError, et as RecipeError, t as getDataset, z as getTarget } from "../bundled-Dkvod55t.js";
1
+ import { $ as NativeParseError, F as getOwn, J as ArchiveError, L as countTarget, Q as KeepsetError, et as RecipeError, t as getDataset, z as getTarget } from "../bundled-V1EJ0k9e.js";
2
2
  import { RoundTripMismatchError, assertRoundTrip, encode } from "twindex";
3
3
  import { z } from "zod";
4
4
  import { createHash } from "node:crypto";
@@ -104,7 +104,7 @@ function parseEdgeList(text, options) {
104
104
  //#endregion
105
105
  //#region src/parsers/gml.ts
106
106
  const KEY = /[A-Za-z_][A-Za-z0-9_]*/y;
107
- const NUMBER$3 = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?/y;
107
+ const NUMBER$4 = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?/y;
108
108
  /**
109
109
  * Matches a sticky pattern at a position.
110
110
  * @param pattern - A sticky regular expression.
@@ -155,7 +155,7 @@ function tokenise(text) {
155
155
  position = end + 1;
156
156
  } else {
157
157
  const key = matchAt(KEY, text, position);
158
- const number = key === void 0 ? matchAt(NUMBER$3, text, position) : void 0;
158
+ const number = key === void 0 ? matchAt(NUMBER$4, text, position) : void 0;
159
159
  const match = key ?? number;
160
160
  if (match === void 0) throw new NativeParseError("gml", line, `unexpected character ${JSON.stringify(char)}`);
161
161
  tokens.push({
@@ -833,9 +833,9 @@ function parsePajek(text) {
833
833
  }
834
834
  //#endregion
835
835
  //#region src/parsers/solomon.ts
836
- const FORMAT$2 = "solomon";
836
+ const FORMAT$3 = "solomon";
837
837
  /** A line of integers or decimals separated by any run of spaces and tabs, as the files write rows (Solomon's with padded spaces, the Tenerife set with tabs). */
838
- const NUMBER$2 = /^-?\d+(?:\.\d+)?$/;
838
+ const NUMBER$3 = /^-?\d+(?:\.\d+)?$/;
839
839
  /**
840
840
  * Reads a token as a finite number.
841
841
  * @param token - The token.
@@ -844,8 +844,8 @@ const NUMBER$2 = /^-?\d+(?:\.\d+)?$/;
844
844
  * @returns The number.
845
845
  * @throws NativeParseError when the token is not a decimal number.
846
846
  */
847
- function numberOf$1(token, line, what) {
848
- if (!NUMBER$2.test(token)) throw new NativeParseError(FORMAT$2, line, `expected ${what}, found ${JSON.stringify(token)}`);
847
+ function numberOf$2(token, line, what) {
848
+ if (!NUMBER$3.test(token)) throw new NativeParseError(FORMAT$3, line, `expected ${what}, found ${JSON.stringify(token)}`);
849
849
  return Number(token);
850
850
  }
851
851
  /**
@@ -858,7 +858,7 @@ function parseSolomon(text) {
858
858
  const all = lines(text);
859
859
  const present = (index) => (all[index] ?? "").trim();
860
860
  const name = present(0);
861
- if (name === "") throw new NativeParseError(FORMAT$2, 1, "expected the instance name on the first line");
861
+ if (name === "") throw new NativeParseError(FORMAT$3, 1, "expected the instance name on the first line");
862
862
  const meta = { name };
863
863
  const nodes = [];
864
864
  const seen = /* @__PURE__ */ new Set();
@@ -874,7 +874,7 @@ function parseSolomon(text) {
874
874
  awaitingHeader = true;
875
875
  continue;
876
876
  }
877
- if (section === "preamble") throw new NativeParseError(FORMAT$2, line, `expected VEHICLE or CUSTOMER, found ${JSON.stringify(trimmed)}`);
877
+ if (section === "preamble") throw new NativeParseError(FORMAT$3, line, `expected VEHICLE or CUSTOMER, found ${JSON.stringify(trimmed)}`);
878
878
  if (awaitingHeader) {
879
879
  awaitingHeader = false;
880
880
  continue;
@@ -882,29 +882,29 @@ function parseSolomon(text) {
882
882
  const tokens = trimmed.split(/\s+/);
883
883
  if (section === "vehicle") {
884
884
  const [vehicles, capacity, ...extra] = tokens;
885
- if (vehicles === void 0 || capacity === void 0 || extra.length > 0) throw new NativeParseError(FORMAT$2, line, `expected the vehicle count and capacity, found ${JSON.stringify(trimmed)}`);
886
- meta.vehicles = numberOf$1(vehicles, line, "the vehicle count");
887
- meta.capacity = numberOf$1(capacity, line, "the vehicle capacity");
885
+ if (vehicles === void 0 || capacity === void 0 || extra.length > 0) throw new NativeParseError(FORMAT$3, line, `expected the vehicle count and capacity, found ${JSON.stringify(trimmed)}`);
886
+ meta.vehicles = numberOf$2(vehicles, line, "the vehicle count");
887
+ meta.capacity = numberOf$2(capacity, line, "the vehicle capacity");
888
888
  continue;
889
889
  }
890
890
  const [id, x, y, demand, ready, due, service, ...extra] = tokens;
891
- if (id === void 0 || x === void 0 || y === void 0 || demand === void 0 || ready === void 0 || due === void 0 || service === void 0 || extra.length > 0) throw new NativeParseError(FORMAT$2, line, `expected seven numbers (id, x, y, demand, ready, due, service), found ${JSON.stringify(trimmed)}`);
892
- if (!/^\d+$/.test(id)) throw new NativeParseError(FORMAT$2, line, `expected a whole number node id, found ${JSON.stringify(id)}`);
893
- if (seen.has(id)) throw new NativeParseError(FORMAT$2, line, `node ${id} appears twice`);
891
+ if (id === void 0 || x === void 0 || y === void 0 || demand === void 0 || ready === void 0 || due === void 0 || service === void 0 || extra.length > 0) throw new NativeParseError(FORMAT$3, line, `expected seven numbers (id, x, y, demand, ready, due, service), found ${JSON.stringify(trimmed)}`);
892
+ if (!/^\d+$/.test(id)) throw new NativeParseError(FORMAT$3, line, `expected a whole number node id, found ${JSON.stringify(id)}`);
893
+ if (seen.has(id)) throw new NativeParseError(FORMAT$3, line, `node ${id} appears twice`);
894
894
  seen.add(id);
895
895
  nodes.push({
896
896
  id,
897
- x: numberOf$1(x, line, "x"),
898
- y: numberOf$1(y, line, "y"),
899
- demand: numberOf$1(demand, line, "the demand"),
900
- ready: numberOf$1(ready, line, "the ready time"),
901
- due: numberOf$1(due, line, "the due date"),
902
- service: numberOf$1(service, line, "the service time")
897
+ x: numberOf$2(x, line, "x"),
898
+ y: numberOf$2(y, line, "y"),
899
+ demand: numberOf$2(demand, line, "the demand"),
900
+ ready: numberOf$2(ready, line, "the ready time"),
901
+ due: numberOf$2(due, line, "the due date"),
902
+ service: numberOf$2(service, line, "the service time")
903
903
  });
904
904
  }
905
- if (section !== "customer") throw new NativeParseError(FORMAT$2, all.length, "expected a CUSTOMER section");
906
- if (nodes.length === 0) throw new NativeParseError(FORMAT$2, all.length, "the CUSTOMER section has no rows");
907
- if (meta.vehicles === void 0 || meta.capacity === void 0) throw new NativeParseError(FORMAT$2, all.length, "expected a VEHICLE section with the vehicle count and capacity");
905
+ if (section !== "customer") throw new NativeParseError(FORMAT$3, all.length, "expected a CUSTOMER section");
906
+ if (nodes.length === 0) throw new NativeParseError(FORMAT$3, all.length, "the CUSTOMER section has no rows");
907
+ if (meta.vehicles === void 0 || meta.capacity === void 0) throw new NativeParseError(FORMAT$3, all.length, "expected a VEHICLE section with the vehicle count and capacity");
908
908
  return {
909
909
  meta: {
910
910
  name: meta.name,
@@ -916,10 +916,10 @@ function parseSolomon(text) {
916
916
  }
917
917
  //#endregion
918
918
  //#region src/parsers/top.ts
919
- const FORMAT$1 = "top";
919
+ const FORMAT$2 = "top";
920
920
  /** The header lines before the first point: `n`, `m` and `tmax`. */
921
921
  const HEADER_LINES = 3;
922
- const NUMBER$1 = /^-?\d+(?:\.\d+)?$/;
922
+ const NUMBER$2 = /^-?\d+(?:\.\d+)?$/;
923
923
  /**
924
924
  * Reads the value of a header line `<key> <number>`.
925
925
  * @param text - The line.
@@ -930,7 +930,7 @@ const NUMBER$1 = /^-?\d+(?:\.\d+)?$/;
930
930
  */
931
931
  function headerOf(text, key, line) {
932
932
  const [name, value, ...extra] = (text ?? "").trim().split(/\s+/);
933
- if (name !== key || value === void 0 || extra.length > 0 || !NUMBER$1.test(value)) throw new NativeParseError(FORMAT$1, line, `expected "${key} <number>", found ${JSON.stringify((text ?? "").trim())}`);
933
+ if (name !== key || value === void 0 || extra.length > 0 || !NUMBER$2.test(value)) throw new NativeParseError(FORMAT$2, line, `expected "${key} <number>", found ${JSON.stringify((text ?? "").trim())}`);
934
934
  return Number(value);
935
935
  }
936
936
  /**
@@ -949,11 +949,11 @@ function parseTop(text) {
949
949
  const points = headerOf(first?.text, "n", first?.line ?? 1);
950
950
  const paths = headerOf(second?.text, "m", second?.line ?? 2);
951
951
  const budget = headerOf(third?.text, "tmax", third?.line ?? HEADER_LINES);
952
- if (!Number.isInteger(points) || points < 2) throw new NativeParseError(FORMAT$1, first?.line ?? 1, `expected at least a start and an end point, found n ${String(points)}`);
953
- if (rows.length !== points) throw new NativeParseError(FORMAT$1, all[all.length - 1]?.line ?? 1, `the header gives ${String(points)} points, found ${String(rows.length)} rows`);
952
+ if (!Number.isInteger(points) || points < 2) throw new NativeParseError(FORMAT$2, first?.line ?? 1, `expected at least a start and an end point, found n ${String(points)}`);
953
+ if (rows.length !== points) throw new NativeParseError(FORMAT$2, all[all.length - 1]?.line ?? 1, `the header gives ${String(points)} points, found ${String(rows.length)} rows`);
954
954
  const nodes = rows.map((row, index) => {
955
955
  const [x, y, score, ...extra] = row.text.split(/\s+/);
956
- if (x === void 0 || y === void 0 || score === void 0 || extra.length > 0 || !NUMBER$1.test(x) || !NUMBER$1.test(y) || !NUMBER$1.test(score)) throw new NativeParseError(FORMAT$1, row.line, `expected three numbers (x, y, score), found ${JSON.stringify(row.text)}`);
956
+ if (x === void 0 || y === void 0 || score === void 0 || extra.length > 0 || !NUMBER$2.test(x) || !NUMBER$2.test(y) || !NUMBER$2.test(score)) throw new NativeParseError(FORMAT$2, row.line, `expected three numbers (x, y, score), found ${JSON.stringify(row.text)}`);
957
957
  return {
958
958
  id: String(index),
959
959
  x: Number(x),
@@ -973,7 +973,7 @@ function parseTop(text) {
973
973
  }
974
974
  //#endregion
975
975
  //#region src/parsers/toptw.ts
976
- const FORMAT = "toptw";
976
+ const FORMAT$1 = "toptw";
977
977
  /** How many numbers the first header line holds (k, paths, customers, t) and the second (D, capacity). */
978
978
  const FIRST_HEADER_NUMBERS = 4;
979
979
  const SECOND_HEADER_NUMBERS = 2;
@@ -981,7 +981,7 @@ const SECOND_HEADER_NUMBERS = 2;
981
981
  const LIST_LENGTH_AT = 6;
982
982
  const LIST_STARTS_AT = 7;
983
983
  const ROW_NUMBERS_BESIDE_LIST = 9;
984
- const NUMBER = /^-?\d+(?:\.\d+)?$/;
984
+ const NUMBER$1 = /^-?\d+(?:\.\d+)?$/;
985
985
  /**
986
986
  * Reads a token as a finite number.
987
987
  * @param token - The token.
@@ -990,8 +990,8 @@ const NUMBER = /^-?\d+(?:\.\d+)?$/;
990
990
  * @returns The number.
991
991
  * @throws NativeParseError when the token is not a decimal number.
992
992
  */
993
- function numberOf(token, line, what) {
994
- if (token === void 0 || !NUMBER.test(token)) throw new NativeParseError(FORMAT, line, `expected ${what}, found ${JSON.stringify(token)}`);
993
+ function numberOf$1(token, line, what) {
994
+ if (token === void 0 || !NUMBER$1.test(token)) throw new NativeParseError(FORMAT$1, line, `expected ${what}, found ${JSON.stringify(token)}`);
995
995
  return Number(token);
996
996
  }
997
997
  /**
@@ -1005,8 +1005,8 @@ function numberOf(token, line, what) {
1005
1005
  */
1006
1006
  function numbersOf(text, count, line, what) {
1007
1007
  const tokens = text.trim().split(/\s+/);
1008
- if (tokens.length !== count) throw new NativeParseError(FORMAT, line, `expected ${what}, found ${JSON.stringify(text.trim())}`);
1009
- return tokens.map((token) => numberOf(token, line, what));
1008
+ if (tokens.length !== count) throw new NativeParseError(FORMAT$1, line, `expected ${what}, found ${JSON.stringify(text.trim())}`);
1009
+ return tokens.map((token) => numberOf$1(token, line, what));
1010
1010
  }
1011
1011
  /**
1012
1012
  * Parses a team orienteering problem with time windows instance (TOPTW, in the layout of Righini and Salani, as the KU Leuven pages describe it). The first line holds four numbers, of which the second is the number of paths and the third the number of customers, the second line two, of which the second is the vehicle capacity, and then one row per node, the depot first: its id, position, service duration, profit, an unused number, the length of a list of unused numbers, that list, and the opening and closing of its time window. Columns are separated by any run of spaces, and blank or whitespace only lines are skipped. The row count must be the customer count plus one, which catches a truncated file. The time quantities are in the unit of the source's coordinate grid, which the format does not name.
@@ -1020,30 +1020,30 @@ function parseToptw(text) {
1020
1020
  text: raw.trim()
1021
1021
  })).filter((entry) => entry.text !== "");
1022
1022
  const [first, second, ...rows] = all;
1023
- if (first === void 0 || second === void 0) throw new NativeParseError(FORMAT, 1, "expected the two header lines");
1023
+ if (first === void 0 || second === void 0) throw new NativeParseError(FORMAT$1, 1, "expected the two header lines");
1024
1024
  const [, paths, customers] = numbersOf(first.text, FIRST_HEADER_NUMBERS, first.line, "four numbers (k, paths, customers, t)");
1025
1025
  const [, capacity] = numbersOf(second.text, SECOND_HEADER_NUMBERS, second.line, "two numbers (D, capacity)");
1026
- if (paths === void 0 || customers === void 0 || capacity === void 0) throw new NativeParseError(FORMAT, first.line, "the header lacks a number");
1026
+ if (paths === void 0 || customers === void 0 || capacity === void 0) throw new NativeParseError(FORMAT$1, first.line, "the header lacks a number");
1027
1027
  const seen = /* @__PURE__ */ new Set();
1028
1028
  const nodes = rows.map((row) => {
1029
1029
  const tokens = row.text.split(/\s+/);
1030
- const listLength = numberOf(tokens[LIST_LENGTH_AT], row.line, "the length of the list after the unused number");
1031
- if (!Number.isInteger(listLength) || listLength < 0 || tokens.length !== ROW_NUMBERS_BESIDE_LIST + listLength) throw new NativeParseError(FORMAT, row.line, `expected ${String(ROW_NUMBERS_BESIDE_LIST + listLength)} numbers for a list of ${String(listLength)}, found ${String(tokens.length)}`);
1030
+ const listLength = numberOf$1(tokens[LIST_LENGTH_AT], row.line, "the length of the list after the unused number");
1031
+ if (!Number.isInteger(listLength) || listLength < 0 || tokens.length !== ROW_NUMBERS_BESIDE_LIST + listLength) throw new NativeParseError(FORMAT$1, row.line, `expected ${String(ROW_NUMBERS_BESIDE_LIST + listLength)} numbers for a list of ${String(listLength)}, found ${String(tokens.length)}`);
1032
1032
  const [id] = tokens;
1033
- if (id === void 0 || !/^\d+$/.test(id)) throw new NativeParseError(FORMAT, row.line, `expected a whole number node id, found ${JSON.stringify(id)}`);
1034
- if (seen.has(id)) throw new NativeParseError(FORMAT, row.line, `node ${id} appears twice`);
1033
+ if (id === void 0 || !/^\d+$/.test(id)) throw new NativeParseError(FORMAT$1, row.line, `expected a whole number node id, found ${JSON.stringify(id)}`);
1034
+ if (seen.has(id)) throw new NativeParseError(FORMAT$1, row.line, `node ${id} appears twice`);
1035
1035
  seen.add(id);
1036
1036
  return {
1037
1037
  id,
1038
- x: numberOf(tokens[1], row.line, "x"),
1039
- y: numberOf(tokens[2], row.line, "y"),
1040
- service: numberOf(tokens[3], row.line, "the service duration"),
1041
- profit: numberOf(tokens[4], row.line, "the profit"),
1042
- open: numberOf(tokens[LIST_STARTS_AT + listLength], row.line, "the opening of the window"),
1043
- close: numberOf(tokens[8 + listLength], row.line, "the closing of the window")
1038
+ x: numberOf$1(tokens[1], row.line, "x"),
1039
+ y: numberOf$1(tokens[2], row.line, "y"),
1040
+ service: numberOf$1(tokens[3], row.line, "the service duration"),
1041
+ profit: numberOf$1(tokens[4], row.line, "the profit"),
1042
+ open: numberOf$1(tokens[LIST_STARTS_AT + listLength], row.line, "the opening of the window"),
1043
+ close: numberOf$1(tokens[8 + listLength], row.line, "the closing of the window")
1044
1044
  };
1045
1045
  });
1046
- if (nodes.length !== customers + 1) throw new NativeParseError(FORMAT, all.length === 0 ? 1 : all[all.length - 1]?.line ?? 1, `the header gives ${String(customers)} customers, so expected ${String(customers + 1)} rows with the depot, found ${String(nodes.length)}`);
1046
+ if (nodes.length !== customers + 1) throw new NativeParseError(FORMAT$1, all.length === 0 ? 1 : all[all.length - 1]?.line ?? 1, `the header gives ${String(customers)} customers, so expected ${String(customers + 1)} rows with the depot, found ${String(nodes.length)}`);
1047
1047
  return {
1048
1048
  meta: {
1049
1049
  paths,
@@ -1054,6 +1054,206 @@ function parseToptw(text) {
1054
1054
  };
1055
1055
  }
1056
1056
  //#endregion
1057
+ //#region src/parsers/tsplib.ts
1058
+ const FORMAT = "tsplib";
1059
+ /** The instance types the parser reads: the probabilistic and the clustered orienteering problem. */
1060
+ const TYPES = /* @__PURE__ */ new Set(["POP", "COP"]);
1061
+ /** The edge weight types a coordinate section is read under: planar ones keep their coordinates as `x` and `y`, `GEO` converts them to degrees, and `EXPLICIT` has no coordinates. */
1062
+ const PLANAR = /* @__PURE__ */ new Set([
1063
+ "EUC_2D",
1064
+ "CEIL_2D",
1065
+ "ATT"
1066
+ ]);
1067
+ const NUMBER = /^-?\d+(?:\.\d+)?$/;
1068
+ const WHOLE = /^\d+$/;
1069
+ const HEADER$1 = /^([A-Z][A-Z_]*)\s*:\s*(.*)$/;
1070
+ const SECTION = /^[A-Z][A-Z_]*$/;
1071
+ /** Minutes in a degree, which a `DDD.MM` coordinate counts in. */
1072
+ const MINUTES_PER_DEGREE = 60;
1073
+ /** `DDD.MM` has two digits of minutes, so the fraction is read in hundredths. */
1074
+ const MINUTE_SCALE = 100;
1075
+ /** How far a coordinate's hundredths may lie from a whole number of minutes: float error in the text's value, not a third digit. */
1076
+ const MINUTE_TOLERANCE = 1e-6;
1077
+ /**
1078
+ * Converts a TSPLIB `GEO` coordinate to decimal degrees. The value is `DDD.MM`, degrees then minutes in one number (`48.23` is 48 degrees 23 minutes), so the fraction is minutes out of a hundred, not a decimal fraction of a degree. The degrees are truncated, which is what makes the conversion agree with the optimal tour lengths the instances record where the specification's `nint` would round minutes of 50 or more up to the next degree.
1079
+ * @param value - The coordinate as the file writes it.
1080
+ * @param line - The 1-based line, for the error.
1081
+ * @returns Decimal degrees, signed like the input.
1082
+ * @throws NativeParseError when the minutes are not a whole number below sixty.
1083
+ */
1084
+ function degrees(value, line) {
1085
+ const magnitude = Math.abs(value);
1086
+ const whole = Math.trunc(magnitude);
1087
+ const hundredths = (magnitude - whole) * MINUTE_SCALE;
1088
+ const minutes = Math.round(hundredths);
1089
+ if (Math.abs(hundredths - minutes) > MINUTE_TOLERANCE || minutes >= MINUTES_PER_DEGREE) throw new NativeParseError(FORMAT, line, `expected a DDD.MM coordinate with whole minutes below 60, found ${String(value)}`);
1090
+ return Math.sign(value) * (whole + minutes / MINUTES_PER_DEGREE);
1091
+ }
1092
+ /**
1093
+ * Reads a token as a finite number.
1094
+ * @param token - The token.
1095
+ * @param line - The 1-based line, for the error.
1096
+ * @param what - What the token should be, for the error.
1097
+ * @returns The number.
1098
+ * @throws NativeParseError when the token is not a decimal number.
1099
+ */
1100
+ function numberOf(token, line, what) {
1101
+ if (token === void 0 || !NUMBER.test(token)) throw new NativeParseError(FORMAT, line, `expected ${what}, found ${JSON.stringify(token)}`);
1102
+ return Number(token);
1103
+ }
1104
+ /**
1105
+ * Reads a token as a whole number.
1106
+ * @param token - The token.
1107
+ * @param line - The 1-based line, for the error.
1108
+ * @param what - What the token should be, for the error.
1109
+ * @returns The number.
1110
+ * @throws NativeParseError when the token is not a whole number.
1111
+ */
1112
+ function wholeOf(token, line, what) {
1113
+ if (token === void 0 || !WHOLE.test(token)) throw new NativeParseError(FORMAT, line, `expected ${what}, found ${JSON.stringify(token)}`);
1114
+ return Number(token);
1115
+ }
1116
+ /**
1117
+ * Reads the header, the coordinate section, the prize and probability section and the set section of a file, skipping every other section.
1118
+ * @param text - The file text.
1119
+ * @returns What it holds.
1120
+ * @throws NativeParseError naming the line for a repeated key, a malformed row, a repeated node or set id, or a row outside any section.
1121
+ */
1122
+ function collect(text) {
1123
+ const collected = {
1124
+ header: /* @__PURE__ */ new Map(),
1125
+ coords: /* @__PURE__ */ new Map(),
1126
+ prizes: /* @__PURE__ */ new Map(),
1127
+ sets: []
1128
+ };
1129
+ let section;
1130
+ for (const [index, raw] of lines(text).entries()) {
1131
+ const line = index + 1;
1132
+ const trimmed = raw.trim();
1133
+ if (trimmed === "" || trimmed === "EOF") continue;
1134
+ const keyed = HEADER$1.exec(trimmed);
1135
+ const key = keyed?.[1];
1136
+ if (key === "CONSTRAINING_SET_SECTION") {
1137
+ section = "sets";
1138
+ continue;
1139
+ }
1140
+ if (keyed !== null && key !== void 0) {
1141
+ if (collected.header.has(key)) throw new NativeParseError(FORMAT, line, `${key} appears twice`);
1142
+ collected.header.set(key, (keyed[2] ?? "").trim());
1143
+ section = void 0;
1144
+ continue;
1145
+ }
1146
+ if (SECTION.test(trimmed)) {
1147
+ section = trimmed === "NODE_COORD_SECTION" ? "coords" : trimmed === "NODE_PRIZE_PROBABILITY_SECTION" ? "prizes" : "skipped";
1148
+ continue;
1149
+ }
1150
+ const tokens = trimmed.split(/\s+/);
1151
+ if (section === "coords") {
1152
+ const [id, x, y, ...extra] = tokens;
1153
+ if (extra.length > 0) throw new NativeParseError(FORMAT, line, `expected "id x y", found ${JSON.stringify(trimmed)}`);
1154
+ const node = wholeOf(id, line, "a node id");
1155
+ if (collected.coords.has(node)) throw new NativeParseError(FORMAT, line, `node ${String(node)} has two coordinate rows`);
1156
+ collected.coords.set(node, {
1157
+ x: numberOf(x, line, "x"),
1158
+ y: numberOf(y, line, "y"),
1159
+ line
1160
+ });
1161
+ } else if (section === "prizes") {
1162
+ const [id, prize, probability, ...extra] = tokens;
1163
+ if (extra.length > 0) throw new NativeParseError(FORMAT, line, `expected "id prize probability", found ${JSON.stringify(trimmed)}`);
1164
+ const node = wholeOf(id, line, "a node id");
1165
+ if (collected.prizes.has(node)) throw new NativeParseError(FORMAT, line, `node ${String(node)} has two prize rows`);
1166
+ collected.prizes.set(node, {
1167
+ prize: numberOf(prize, line, "the prize"),
1168
+ probability: numberOf(probability, line, "the probability")
1169
+ });
1170
+ } else if (section === "sets") {
1171
+ const [id, prize, ...members] = tokens;
1172
+ const setId = wholeOf(id, line, "a set id");
1173
+ if (collected.sets.some((set) => set.id === setId)) throw new NativeParseError(FORMAT, line, `set ${String(setId)} appears twice`);
1174
+ collected.sets.push({
1175
+ id: setId,
1176
+ prize: numberOf(prize, line, "the set prize"),
1177
+ members: members.map((member) => wholeOf(member, line, "a node id in a set"))
1178
+ });
1179
+ } else if (section === void 0) throw new NativeParseError(FORMAT, line, `expected a KEY : value line or a section, found ${JSON.stringify(trimmed)}`);
1180
+ }
1181
+ return collected;
1182
+ }
1183
+ /**
1184
+ * Reads a header value that must be there.
1185
+ * @param header - The header.
1186
+ * @param key - The key.
1187
+ * @returns Its text.
1188
+ * @throws NativeParseError when the file has no such key.
1189
+ */
1190
+ function required(header, key) {
1191
+ const value = header.get(key);
1192
+ if (value === void 0) throw new NativeParseError(FORMAT, 1, `expected a ${key} line`);
1193
+ return value;
1194
+ }
1195
+ /**
1196
+ * Parses a TSPLIB style orienteering instance of type `POP` (probabilistic orienteering, Angelelli and others) or `COP` (clustered orienteering), in the layout TSPLIB95 (Reinelt) defines: `KEY : value` header lines and bare keyword sections. The nodes are 1 to `DIMENSION`. The planar weight types keep their coordinates as `x` and `y` and `GEO` converts them to `lat` and `lon`; an `EXPLICIT` file has no coordinates, so its nodes have only the prize data. A `POP` file's `NODE_PRIZE_PROBABILITY_SECTION` gives each node its prize and probability. A `COP` file has no prize on a node: its prizes belong to sets of nodes that may overlap, so each node lists the sets it is in under `clusters` and the sets' prizes are the `setPrizes` metadata, and nothing is divided among the members. The weight matrix is not read, as or/v1 has no arcs.
1197
+ * @param text - The file text.
1198
+ * @returns The instance: its header values as `meta` and its nodes in id order.
1199
+ * @throws NativeParseError naming the line for an unsupported type or weight type, a missing key or section, a node id outside 1 to `DIMENSION` or missing from a section that must cover every node, or a set that names a node outside that range.
1200
+ */
1201
+ function parseTsplib(text) {
1202
+ const { header, coords, prizes, sets } = collect(text);
1203
+ const type = required(header, "TYPE");
1204
+ if (!TYPES.has(type)) throw new NativeParseError(FORMAT, 1, `expected TYPE POP or COP, found ${JSON.stringify(type)}`);
1205
+ const weightType = required(header, "EDGE_WEIGHT_TYPE");
1206
+ const geographic = weightType === "GEO";
1207
+ if (!geographic && !PLANAR.has(weightType) && weightType !== "EXPLICIT") throw new NativeParseError(FORMAT, 1, `expected EDGE_WEIGHT_TYPE EUC_2D, CEIL_2D, ATT, GEO or EXPLICIT, found ${JSON.stringify(weightType)}`);
1208
+ const dimension = wholeOf(required(header, "DIMENSION"), 1, "DIMENSION");
1209
+ const budget = numberOf(required(header, "TMAX"), 1, "TMAX");
1210
+ if (weightType === "EXPLICIT" ? coords.size > 0 : coords.size !== dimension) throw new NativeParseError(FORMAT, 1, weightType === "EXPLICIT" ? "an EXPLICIT file has no NODE_COORD_SECTION" : `expected ${String(dimension)} coordinate rows, found ${String(coords.size)}`);
1211
+ const inRange = (id) => id >= 1 && id <= dimension;
1212
+ for (const id of [
1213
+ ...coords.keys(),
1214
+ ...prizes.keys(),
1215
+ ...sets.flatMap((set) => set.members)
1216
+ ]) if (!inRange(id)) throw new NativeParseError(FORMAT, 1, `node ${String(id)} is outside 1 to ${String(dimension)}`);
1217
+ if (type === "POP" && prizes.size !== dimension) throw new NativeParseError(FORMAT, 1, `expected ${String(dimension)} prize rows, found ${String(prizes.size)}`);
1218
+ if (type === "COP") {
1219
+ const declared = wholeOf(required(header, "SETS"), 1, "SETS");
1220
+ if (sets.length !== declared) throw new NativeParseError(FORMAT, 1, `expected ${String(declared)} sets, found ${String(sets.length)}`);
1221
+ }
1222
+ const clustersOf = (node) => {
1223
+ const ids = sets.filter((set) => set.members.includes(node)).map((set) => String(set.id));
1224
+ return ids.length === 0 ? null : ids.join(",");
1225
+ };
1226
+ const nodes = Array.from({ length: dimension }, (_, index) => {
1227
+ const id = index + 1;
1228
+ const position = coords.get(id);
1229
+ const prize = prizes.get(id);
1230
+ const lat = geographic && position !== void 0 ? degrees(position.x, position.line) : null;
1231
+ const lon = geographic && position !== void 0 ? degrees(position.y, position.line) : null;
1232
+ return {
1233
+ id: String(id),
1234
+ x: geographic ? null : position?.x ?? null,
1235
+ y: geographic ? null : position?.y ?? null,
1236
+ lat,
1237
+ lon,
1238
+ prize: prize?.prize ?? null,
1239
+ probability: prize?.probability ?? null,
1240
+ clusters: clustersOf(id)
1241
+ };
1242
+ });
1243
+ const total = header.get("TPRIZE");
1244
+ return {
1245
+ meta: {
1246
+ name: header.get("NAME") ?? null,
1247
+ type,
1248
+ budget,
1249
+ edgeWeightType: weightType,
1250
+ totalPrize: total === void 0 ? null : numberOf(total, 1, "TPRIZE"),
1251
+ setPrizes: type === "COP" ? sets.map((set) => `${String(set.id)}:${String(set.prize)}`).join(",") : null
1252
+ },
1253
+ nodes
1254
+ };
1255
+ }
1256
+ //#endregion
1057
1257
  //#region src/parsers/parse-native.ts
1058
1258
  /** Bytes converted per String.fromCharCode call: spreading a whole file would exceed the engine's argument-count limit, and this many stays well inside every engine's. */
1059
1259
  const LATIN1_CHUNK = 32768;
@@ -1112,6 +1312,7 @@ function parseNative(bytes, parser) {
1112
1312
  if (parser.format === "solomon") return parseSolomon(text);
1113
1313
  if (parser.format === "top") return parseTop(text);
1114
1314
  if (parser.format === "toptw") return parseToptw(text);
1315
+ if (parser.format === "tsplib") return parseTsplib(text);
1115
1316
  return parsePajek(text);
1116
1317
  }
1117
1318
  /** The ustar header layout (POSIX.1-1988, pax(1) "ustar Interchange Format"): each field's byte offset and length. */
@@ -1648,6 +1849,8 @@ const REDIRECTS = new Set(Object.values({
1648
1849
  }));
1649
1850
  /** What the downloader says it is, so a server operator can tell where the traffic comes from. */
1650
1851
  const USER_AGENT = "keepset (+https://github.com/Mearman/keepset)";
1852
+ /** What the downloader accepts: anything, as the file is the recipe's to read. Node's HTTP client sends no `Accept` header of its own, and a server that content-negotiates a download (the VRP-REP host answers 406 to a request without one) refuses it; every other HTTP client sends this, and RFC 9110 reads an absent header as the same. */
1853
+ const ACCEPT = "*/*";
1651
1854
  /** The failure of a connection that sent nothing for the stall time. */
1652
1855
  var StalledError = class extends KeepsetError {};
1653
1856
  /**
@@ -1672,7 +1875,10 @@ async function get(url, signal, stall) {
1672
1875
  let answered;
1673
1876
  const request = send(url, {
1674
1877
  method: "GET",
1675
- headers: { "user-agent": USER_AGENT },
1878
+ headers: {
1879
+ accept: ACCEPT,
1880
+ "user-agent": USER_AGENT
1881
+ },
1676
1882
  ...signal === void 0 ? {} : { signal }
1677
1883
  }, (response) => {
1678
1884
  answered = response;
@@ -4,7 +4,7 @@ import { KeepsetError, NAME_SEPARATOR, countTarget, getDataset, listDatasets, li
4
4
  import { fetchDataset, roundTripDataset } from "keepset/node";
5
5
  import { parseArgs } from "node:util";
6
6
  //#region package.json
7
- var version = "0.25.0";
7
+ var version = "0.26.0";
8
8
  const usage = `Usage: keepset <command> [options]
9
9
 
10
10
  Commands:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "keepset",
3
3
  "description": "Library, CLI and statically generated catalogue for dataset fetch-and-normalise recipes",
4
- "version": "0.25.0",
4
+ "version": "0.26.0",
5
5
  "bin": {
6
6
  "keepset": "./dist-cli/keepset.mjs"
7
7
  },
@@ -1 +1 @@
1
- {"$defs":{"Crosswalk":{"$ref":"https://cdn.jsdelivr.net/npm/twindex@0.1.4/schemas/twindex.schema.json"},"Entry":{"additionalProperties":false,"properties":{"aliases":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*(?:\\/[A-Za-z0-9][A-Za-z0-9._-]*)*$","type":"string"},"minItems":1,"type":"array"},"category":{"enum":["graph/v1","or/v1","or-set/v1"],"type":"string"},"citations":{"items":{"additionalProperties":false,"properties":{"doi":{"pattern":"^10\\.\\d{4,9}\\/\\S+$","type":"string"},"text":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["text"],"type":"object"},"type":"array"},"crosswalk":{"anyOf":[{"$ref":"#/$defs/Crosswalk"},{"additionalProperties":false,"properties":{"file":{"type":"string"}},"required":["file"],"type":"object"}]},"description":{"minLength":1,"type":"string"},"domain":{"minLength":1,"type":"string"},"expectedCounts":{"additionalProperties":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"licence":{"oneOf":[{"additionalProperties":false,"properties":{"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*$","type":"string"},"kind":{"const":"spdx","type":"string"}},"required":["kind","id"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"custom","type":"string"},"name":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["kind","name","url"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"unrecorded","type":"string"}},"required":["kind"],"type":"object"}]},"name":{"minLength":1,"type":"string"},"namespace":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"properties":{"additionalProperties":{"type":["string","number","boolean","null"]},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"recipe":{"oneOf":[{"additionalProperties":false,"properties":{"archive":{"anyOf":[{"additionalProperties":false,"properties":{"kind":{"const":"none","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"gzip","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"enum":["zip","tar","tar.gz"],"type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"enum":["zip","tar","tar.gz"],"type":"string"},"members":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","members"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"xlsx","type":"string"},"sheet":{"minLength":1,"type":"string"}},"required":["kind","sheet"],"type":"object"}]},"paging":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"arcgis-query","type":"string"},"orderBy":{"pattern":"^[A-Za-z_][A-Za-z0-9_]*$","type":"string"}},"required":["kind","orderBy"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"socrata-resource","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"ckan-datastore","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"openfema","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"}]},"parser":{"oneOf":[{"additionalProperties":false,"properties":{"commentPrefixes":{"items":{"minLength":1,"type":"string"},"type":"array"},"delimiter":{"enum":["detect","whitespace","tab","comma","space"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"edge-list","type":"string"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"sourceColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"targetColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","commentPrefixes","sourceColumn","targetColumn"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"matrix-market","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"gml","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"pajek","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"solomon","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"top","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"toptw","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"columns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"type":"array"},"delimiter":{"enum":["comma","tab","semicolon"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"csv-table","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"nullValues":{"items":{"minLength":1,"type":"string"},"type":"array"},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","idColumn","numericColumns"],"type":"object"},{"additionalProperties":false,"properties":{"columns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"minItems":1,"type":"array"},"dateColumns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"type":"array"},"encoding":{"const":"utf-8","type":"string"},"format":{"const":"geojson","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"}},"required":["format","encoding","idColumn","numericColumns","dateColumns","columns"],"type":"object"}]},"source":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"status":{"const":"fetchable","type":"string"}},"required":["status","source","archive","parser"],"type":"object"},{"additionalProperties":false,"properties":{"reason":{"minLength":1,"type":"string"},"status":{"const":"not-fetchable","type":"string"}},"required":["status","reason"],"type":"object"}]},"sources":{"items":{"additionalProperties":false,"properties":{"retrieved":{"format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","type":"string"},"snapshot":{"additionalProperties":false,"properties":{"timestamp":{"pattern":"^\\d{8}(?:\\d{6})?$","type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","timestamp"],"type":"object"},"status":{"enum":["live","dead"],"type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","retrieved","status"],"type":"object"},"minItems":1,"type":"array"},"tags":{"items":{"minLength":1,"type":"string"},"type":"array"}},"required":["id","name","tags","category","sources","licence","citations","recipe","crosswalk","expectedCounts","properties"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.25.0/schemas/entry.schema.json","$ref":"#/$defs/Entry","$schema":"https://json-schema.org/draft/2020-12/schema"}
1
+ {"$defs":{"Crosswalk":{"$ref":"https://cdn.jsdelivr.net/npm/twindex@0.1.4/schemas/twindex.schema.json"},"Entry":{"additionalProperties":false,"properties":{"aliases":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*(?:\\/[A-Za-z0-9][A-Za-z0-9._-]*)*$","type":"string"},"minItems":1,"type":"array"},"category":{"enum":["graph/v1","or/v1","or-set/v1"],"type":"string"},"citations":{"items":{"additionalProperties":false,"properties":{"doi":{"pattern":"^10\\.\\d{4,9}\\/\\S+$","type":"string"},"text":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["text"],"type":"object"},"type":"array"},"crosswalk":{"anyOf":[{"$ref":"#/$defs/Crosswalk"},{"additionalProperties":false,"properties":{"file":{"type":"string"}},"required":["file"],"type":"object"}]},"description":{"minLength":1,"type":"string"},"domain":{"minLength":1,"type":"string"},"expectedCounts":{"additionalProperties":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"licence":{"oneOf":[{"additionalProperties":false,"properties":{"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*$","type":"string"},"kind":{"const":"spdx","type":"string"}},"required":["kind","id"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"custom","type":"string"},"name":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["kind","name","url"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"unrecorded","type":"string"}},"required":["kind"],"type":"object"}]},"name":{"minLength":1,"type":"string"},"namespace":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"properties":{"additionalProperties":{"type":["string","number","boolean","null"]},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"recipe":{"oneOf":[{"additionalProperties":false,"properties":{"archive":{"anyOf":[{"additionalProperties":false,"properties":{"kind":{"const":"none","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"gzip","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"enum":["zip","tar","tar.gz"],"type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"enum":["zip","tar","tar.gz"],"type":"string"},"members":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","members"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"xlsx","type":"string"},"sheet":{"minLength":1,"type":"string"}},"required":["kind","sheet"],"type":"object"}]},"paging":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"arcgis-query","type":"string"},"orderBy":{"pattern":"^[A-Za-z_][A-Za-z0-9_]*$","type":"string"}},"required":["kind","orderBy"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"socrata-resource","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"ckan-datastore","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"openfema","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"}]},"parser":{"oneOf":[{"additionalProperties":false,"properties":{"commentPrefixes":{"items":{"minLength":1,"type":"string"},"type":"array"},"delimiter":{"enum":["detect","whitespace","tab","comma","space"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"edge-list","type":"string"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"sourceColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"targetColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","commentPrefixes","sourceColumn","targetColumn"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"matrix-market","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"gml","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"pajek","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"solomon","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"top","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"toptw","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"tsplib","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"columns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"type":"array"},"delimiter":{"enum":["comma","tab","semicolon"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"csv-table","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"nullValues":{"items":{"minLength":1,"type":"string"},"type":"array"},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","idColumn","numericColumns"],"type":"object"},{"additionalProperties":false,"properties":{"columns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"minItems":1,"type":"array"},"dateColumns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"type":"array"},"encoding":{"const":"utf-8","type":"string"},"format":{"const":"geojson","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"}},"required":["format","encoding","idColumn","numericColumns","dateColumns","columns"],"type":"object"}]},"source":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"status":{"const":"fetchable","type":"string"}},"required":["status","source","archive","parser"],"type":"object"},{"additionalProperties":false,"properties":{"reason":{"minLength":1,"type":"string"},"status":{"const":"not-fetchable","type":"string"}},"required":["status","reason"],"type":"object"}]},"sources":{"items":{"additionalProperties":false,"properties":{"retrieved":{"format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","type":"string"},"snapshot":{"additionalProperties":false,"properties":{"timestamp":{"pattern":"^\\d{8}(?:\\d{6})?$","type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","timestamp"],"type":"object"},"status":{"enum":["live","dead"],"type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","retrieved","status"],"type":"object"},"minItems":1,"type":"array"},"tags":{"items":{"minLength":1,"type":"string"},"type":"array"}},"required":["id","name","tags","category","sources","licence","citations","recipe","crosswalk","expectedCounts","properties"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.26.0/schemas/entry.schema.json","$ref":"#/$defs/Entry","$schema":"https://json-schema.org/draft/2020-12/schema"}
@@ -1 +1 @@
1
- {"$defs":{"Manifest":{"additionalProperties":false,"properties":{"description":{"minLength":1,"type":"string"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"lineage":{"additionalProperties":false,"properties":{"catalogue":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$","type":"string"}},"required":["catalogue","version","repository"],"type":"object"},"name":{"minLength":1,"type":"string"},"namespaceAliases":{"additionalProperties":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"propertyNames":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"type":"object"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$","type":"string"}},"required":["id","name","description","version","repository"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.25.0/schemas/manifest.schema.json","$ref":"#/$defs/Manifest","$schema":"https://json-schema.org/draft/2020-12/schema"}
1
+ {"$defs":{"Manifest":{"additionalProperties":false,"properties":{"description":{"minLength":1,"type":"string"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"lineage":{"additionalProperties":false,"properties":{"catalogue":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$","type":"string"}},"required":["catalogue","version","repository"],"type":"object"},"name":{"minLength":1,"type":"string"},"namespaceAliases":{"additionalProperties":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"propertyNames":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"type":"object"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$","type":"string"}},"required":["id","name","description","version","repository"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.26.0/schemas/manifest.schema.json","$ref":"#/$defs/Manifest","$schema":"https://json-schema.org/draft/2020-12/schema"}