@finbheara/archive-client 0.52.0 → 0.53.1

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.
package/README.md CHANGED
@@ -90,6 +90,7 @@ c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...
90
90
  | `results.stream`, `majors.stream` | `results.stream`, `results.entries` | 255 |
91
91
  | `results.stream.competition`, `majors.stream.competition` | the competition fields a streamed row carries (`competition_line`, `dance_printed`, `dance`, `recalled_only`, `comp_flags`, `competitors_basis`); absent, never refused, without them | 270 |
92
92
  | `feises.dates` | `feises.dates` | 245 |
93
+ | `feises.name` | the `name` `feises.find` and `feises.series` answer: the release's reading, the label as printed except where it lost a letter (U+FFFD) the feis's own pages print clean; `name_printed` beside it is the label verbatim. Without it `name` is the label, never refused | 350 |
93
94
  | `dancers.records` | `dancers.records` | 215 |
94
95
  | `dancers.records.names` | the `names_printed` a record of `dancers.records` carries; absent, never refused, without it | 215 |
95
96
  | `dancers.years` | `dancers.years` | 245 |
@@ -98,6 +99,7 @@ c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...
98
99
  | `results.major`, `majors.major` | the `major` a row of `results.stream` and `results.entries` carries (1 where its competition is a main event of a T1-T3 event, exactly the rows `majors.sqlite` holds); absent without it, and the stream filter `major` is refused by name without it | 345 |
99
100
  | `results.relative_place_err`, `majors.relative_place_err` | the `relative_place_err` a row of `results.stream` and `results.entries` carries (how far below `relative_place` the true value may sit, 0 over an exact field); absent, never refused, without it | 350 |
100
101
  | `results.marks_printed`, `majors.marks_printed` | the `marks_printed` a row of `results.stream` and `results.entries` carries (the marks total a row served or printed, iFeis's beside its `points` and a Bucharest transcript row's, one string verbatim); absent, never refused, without it | 350 |
102
+ | `results.status_printed`, `majors.status_printed` | the `status_printed` a row of `results.stream` and `results.entries` carries (a transcribed row's status cell as printed, verbatim, in printed order, `[]` where none); absent, never refused, without it | 350 |
101
103
  | `results.round_points_printed`, `majors.round_points_printed` | the `round_points_printed` a row of `results.stream` and `results.entries` carries (the per-round points a platform served, FeisFWD's, in its slot order, a slot served nothing in null); absent, never refused, without it | 350 |
102
104
  | `dancers.records.birth_consistency`, `dancers.years.birth_consistency` | the `birth_consistency` (0-100, floored: the percent of the dancer's, or the dancer-year's, readable rows that fit the birth window) and its denominator `birth_consistency_rows` a record of `dancers.records` and a dancer-year of `dancers.years` carry; absent, never refused, without it | 345 |
103
105
  | `dancers.records.birth_deadband`, `dancers.years.birth_deadband` | the `birth_deadband_rows` a record of `dancers.records` and a dancer-year of `dancers.years` carry (the readable rows a one-year dance-up alone keeps off the birth window, outside `birth_consistency` and its denominator); absent, never refused, without it | 345 |
@@ -122,6 +124,7 @@ c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...
122
124
  | `schools.registry` | `schools.list` | 260 |
123
125
  | `schools.prints`, `schools.redirects` | `schools.prints`; `schools.redirects` | 184 |
124
126
  | `schools.lineage` | `schools.links`, `schools.line`, `schools.lineage` | 184 |
127
+ | `school-graph` | the tables `schools.redirects`, `schools.lineage`, `schools.links`, `schools.line` and a dancer question's lineage read in place of the jsonl, the same answers; the jsonl, never refused, without them | none yet |
125
128
  | `schools.associations` | `schools.associations` | 215 |
126
129
 
127
130
  A spec is `pinned` (the default: the user-local pin, else the latest), `latest`, a cut number
@@ -258,8 +261,8 @@ await rel.feises.find({ id: "<feis, series instance or brand id>" }); // a reti
258
261
  await rel.feises.find({ name: "*Spring*", year: 2024 });
259
262
  ```
260
263
 
261
- One answer per feis id: dates, location, series ids, regions, and how many competitions, rows
262
- and captures it has.
264
+ One answer per feis id: its `name` and the label as printed (`name_printed`), dates, location,
265
+ series ids, regions, and how many competitions, rows and captures it has. A name glob reads both.
263
266
 
264
267
  ```ts
265
268
  const s = await rel.feises.series("Ardvarna Spring"); // a name, a host code ("fweb:ABC", "fweb:ABC26"), any feis id
@@ -323,6 +326,20 @@ await rel.schools.associations({ school_id: "<id>" });
323
326
  // association_type, src_type, src, candidate_school_id, candidate_rung, ... }] }
324
327
  ```
325
328
 
329
+ THE SCHOOL GRAPH'S TABLES (feature `school-graph`): a release whose `results.sqlite` carries
330
+ `school_nodes`, `school_edges` and `school_aliases` answers `redirects`, `lineage`, `links`,
331
+ `line` and a dancer question's lineage from them, and one without them from
332
+ `school-redirects.jsonl` and `school-lineage.jsonl`, as before. The answers are the same rows in
333
+ the same order: a redirect is an alias of kind `fold` or `reslug`, or a former school (a node
334
+ with `closed_into`) to the school that absorbed it, each landing on a live school in one hop; a
335
+ lineage edge is a `school_edges` row of a lineage kind (`rename`, `absorb`, `merger`, `split`,
336
+ `branch`, `related`) with no `lag_months`, given as its two ends, its `decision` the first of its
337
+ `decision_rows`. A former school's absorbed-by line (the one with `lag_months`), a `distinct`,
338
+ `disbanded`, banner or tie line, and a `shelf`, `site_fold`, path, print or `retired` alias are
339
+ none of these, as the jsonl publishes none of them: an id the tables hold only that way answers
340
+ nothing, never a guess. The tables round a `confidence` to 6 places, and the jsonl writes
341
+ `1 - posterior` unrounded, so the two can differ past the sixth place.
342
+
326
343
  `prints` is every (print, school id) pair the release's result rows attach in an event-year (the
327
344
  print trimmed), with the rows that carry it: an event's (case-blind), a year's, both or every one.
328
345
  A row attached to no school is in no pair, and a team result's school is `rel.teams.results`'.
@@ -380,6 +397,8 @@ r.unlinked; // rows printing a matched name that the release linked to no dan
380
397
  await rel.dancers.search({ surname: "Quellan" }); // or { like: "a* quellan" }, or { name }
381
398
  // at most `limit` (default 50, 0 for all);
382
399
  // `truncated` counts the rest
400
+ // a surname folds as the print's key does:
401
+ // `Karłowska` and `Karlowska` ask one key
383
402
  await rel.dancers.get(1001); // every print and school of one dancer
384
403
  ```
385
404
 
@@ -407,13 +426,14 @@ names package's Spanish reading (`createNameKinship({ spanishNames: true })`), a
407
426
  relates is a candidate at `nameLevel: "spanish"`. A release with no `spanish_names` reads every
408
427
  school the default way. A name the release already finds answers exactly as before.
409
428
 
410
- THE LINEAGE is the release's `school-lineage.jsonl`: every line joins its `id` and its `other`,
429
+ THE LINEAGE is the release's `school-lineage.jsonl` (or its `school_edges`, where it carries the
430
+ school graph's tables, the same edges): every line joins its `id` and its `other`,
411
431
  whatever its direction (`precedes`, `succeeds`) or shape (a rename, a split, a merger, an absorb,
412
432
  a relation), so a file carrying one end of an edge reads as one carrying both. The whole line
413
433
  joins: every school reached through any number of edges, in either direction (the two halves of a
414
434
  split are joined through their predecessor, the schools of a merger through their successor). An
415
435
  earlier row at a joined school alone is no match. `r.school.lineage` lists the ids joined to the
416
- asked ones. A release that publishes no lineage (feature `schools.lineage`) joins
436
+ asked ones. A release that publishes no lineage (feature `schools.lineage`, nor `school-graph`) joins
417
437
  nothing and its answer carries no `lineage`, never refused. The line is read on the same school
418
438
  graph as `rel.schools.line` (`@finbheara/domain/school-graph`): untimed, every path, and as of a
419
439
  year, every path none of whose steps is dated after it, a rename at any year (`LINE_AS_OF`).
@@ -653,6 +673,12 @@ or summed, and the places follow the points, not the marks. A Bucharest transcri
653
673
  carries its printed mark the same way, one adjudicator's mark and no `points` (`"80"`). Null where
654
674
  the row carries none and on every other source's row.
655
675
 
676
+ THE STATUS CELL AS PRINTED, where the release carries it
677
+ (`rel.capability("results.status_printed")`): `status_printed`, a transcribed row's status cell
678
+ verbatim, in printed order (`["Not Recalled"]`, `["-"]`, `["*"]`, `["WQ"]`). `atts` is the
679
+ reading of the same cell, so a cell it reads as nothing (`-`, `Not Recalled`) is told apart from
680
+ a blank cell here. `[]` where the row prints none, and on every platform row.
681
+
656
682
  THE COMPETITION FIELDS, where the release carries them (`rel.capability("results.stream.competition")`,
657
683
  `"majors.stream.competition"` for scope `majors`), are on every row, null meaning no value:
658
684
 
@@ -141,7 +141,6 @@ import {
141
141
  } from "@finbheara/domain/title";
142
142
  import {
143
143
  composeNames,
144
- foldNameKey,
145
144
  nameBlockKeys,
146
145
  nameKinship as nameKinship2,
147
146
  surnameBlock
@@ -606,6 +605,18 @@ var FEATURES2 = [
606
605
  since: 350,
607
606
  calls: "the same on a row of results.stream and results.entries (scope majors)"
608
607
  },
608
+ {
609
+ feature: "results.status_printed",
610
+ needs: ["results:results.status_printed"],
611
+ since: 350,
612
+ calls: "the `status_printed` a row of results.stream and results.entries carries (a transcribed row's status cell as printed, verbatim, in printed order, `[]` where none): absent, never refused, on a release without it"
613
+ },
614
+ {
615
+ feature: "majors.status_printed",
616
+ needs: ["majors:results.status_printed"],
617
+ since: 350,
618
+ calls: "the same on a row of results.stream and results.entries (scope majors)"
619
+ },
609
620
  {
610
621
  feature: "majors.attach_basis",
611
622
  needs: ["majors:results.attach_basis"],
@@ -624,6 +635,12 @@ var FEATURES2 = [
624
635
  since: 245,
625
636
  calls: "feises.dates"
626
637
  },
638
+ {
639
+ feature: "feises.name",
640
+ needs: ["results:feises.name", "results:feises.name_source"],
641
+ since: 350,
642
+ calls: "the `name` feises.find and feises.series answer (the release's reading: the label as printed, except where it lost a letter the feis's own pages print); on a release without it, `name` is the label as printed, never refused"
643
+ },
627
644
  {
628
645
  feature: "dancers.records",
629
646
  needs: [
@@ -1061,6 +1078,25 @@ var FEATURES2 = [
1061
1078
  since: 184,
1062
1079
  calls: "schools.redirects"
1063
1080
  },
1081
+ {
1082
+ feature: "school-graph",
1083
+ needs: [
1084
+ ...["alias", "kind", "node_id", "school_id"].map((c) => `results:school_aliases.${c}`),
1085
+ ...["node_id", "kind", "minted_reslug_from", "minted_reslug_pass", "closed_into"].map((c) => `results:school_nodes.${c}`),
1086
+ ...[
1087
+ "from_id",
1088
+ "to_id",
1089
+ "kind",
1090
+ "valid_from",
1091
+ "provisional",
1092
+ "confidence",
1093
+ "decision_rows",
1094
+ "lag_months"
1095
+ ].map((c) => `results:school_edges.${c}`)
1096
+ ],
1097
+ since: 365,
1098
+ calls: "schools.redirects, schools.lineage, schools.links, schools.line and the `lineage` school evidence of dancers.resolve and dancers.search read the tables, the same answers as the jsonl; without them, the jsonl, never refused"
1099
+ },
1064
1100
  {
1065
1101
  feature: "schools.associations",
1066
1102
  needs: ["observed-associations.jsonl"],
@@ -1159,7 +1195,7 @@ var describeEvent = (e) => {
1159
1195
  var SCHEMA = `
1160
1196
  CREATE TABLE feis(
1161
1197
  feis_id TEXT PRIMARY KEY, source TEXT NOT NULL, token TEXT NOT NULL, host_id TEXT NOT NULL,
1162
- name TEXT NOT NULL, date_lo TEXT, date_hi TEXT, date_printed TEXT, year INTEGER,
1198
+ name TEXT NOT NULL, name_printed TEXT NOT NULL, date_lo TEXT, date_hi TEXT, date_printed TEXT, year INTEGER,
1163
1199
  location TEXT, instance_id TEXT, brand_id TEXT, regions TEXT NOT NULL,
1164
1200
  competitions INTEGER NOT NULL, rows INTEGER NOT NULL, captures INTEGER NOT NULL
1165
1201
  );
@@ -1179,7 +1215,7 @@ function readJsonl(path) {
1179
1215
  }
1180
1216
  var feisesIndex = {
1181
1217
  name: "feises",
1182
- version: "2",
1218
+ version: "3",
1183
1219
  inputs: ["results", "feis-redirects.jsonl"],
1184
1220
  says: "every feis by id, platform, code, series and name, with its counts and regions",
1185
1221
  build(rel, out) {
@@ -1194,15 +1230,16 @@ var feisesIndex = {
1194
1230
  const rows = new Map;
1195
1231
  for (const r of src.query("SELECT c.event AS event, sum(r.n) AS n FROM (SELECT competition, count(*) AS n FROM results GROUP BY competition) r JOIN (SELECT DISTINCT competition, event FROM competitions) c ON c.competition = r.competition GROUP BY c.event").all())
1196
1232
  rows.set(r.event, r.n);
1197
- const put = out.prepare("INSERT OR IGNORE INTO feis VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1233
+ const put = out.prepare("INSERT OR IGNORE INTO feis VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1198
1234
  const captures = new Map;
1199
- const feisRows = src.query("SELECT feis_id, source, host_id, name_printed, date_lo, date_hi, date_printed, year, location_printed, feis_instance_id, feis_brand_id FROM feises ORDER BY file, line").all();
1235
+ const named = columnsOf2(src, "feises").has("name");
1236
+ const feisRows = src.query(`SELECT feis_id, source, host_id, name_printed, ${named ? "name" : "name_printed AS name"}, date_lo, date_hi, date_printed, year, location_printed, feis_instance_id, feis_brand_id FROM feises ORDER BY file, line`).all();
1200
1237
  for (const f of feisRows)
1201
1238
  captures.set(f.feis_id, (captures.get(f.feis_id) ?? 0) + 1);
1202
1239
  for (const f of feisRows) {
1203
1240
  const c = counts.get(f.feis_id);
1204
1241
  const colon = f.feis_id.indexOf(":");
1205
- put.run(f.feis_id, f.source, colon === -1 ? f.source : f.feis_id.slice(0, colon), f.host_id, f.name_printed, f.date_lo, f.date_hi, f.date_printed, f.year, f.location_printed, f.feis_instance_id, f.feis_brand_id, JSON.stringify([...c?.regions ?? []].sort()), c?.comps ?? 0, rows.get(f.feis_id) ?? 0, captures.get(f.feis_id) ?? 0);
1242
+ put.run(f.feis_id, f.source, colon === -1 ? f.source : f.feis_id.slice(0, colon), f.host_id, f.name, f.name_printed, f.date_lo, f.date_hi, f.date_printed, f.year, f.location_printed, f.feis_instance_id, f.feis_brand_id, JSON.stringify([...c?.regions ?? []].sort()), c?.comps ?? 0, rows.get(f.feis_id) ?? 0, captures.get(f.feis_id) ?? 0);
1206
1243
  }
1207
1244
  const red = out.prepare("INSERT OR REPLACE INTO redirect VALUES (?, ?, ?)");
1208
1245
  for (const r of readJsonl(rel.path("feis-redirects.jsonl")))
@@ -1255,8 +1292,8 @@ class FeisFinder2 {
1255
1292
  args.push(id, id, id);
1256
1293
  }
1257
1294
  if (q.name !== undefined) {
1258
- where.push("name LIKE ? ESCAPE '\\'");
1259
- args.push(globToLike(q.name));
1295
+ where.push("(name LIKE ? ESCAPE '\\' OR name_printed LIKE ? ESCAPE '\\')");
1296
+ args.push(globToLike(q.name), globToLike(q.name));
1260
1297
  }
1261
1298
  if (q.year !== undefined) {
1262
1299
  where.push("year = ?");
@@ -1564,7 +1601,7 @@ function streamSql(q, shape = {}) {
1564
1601
  if (q.major !== undefined)
1565
1602
  where.push(q.major ? "r.major = 1" : "r.major = 0");
1566
1603
  return {
1567
- sql: `SELECT ${COLUMNS}${shape.competition === true ? COMPETITION_COLUMNS : ""}${shape.competitorsPrinted === true ? ", c.competitors_printed AS competitors_printed" : ""}${shape.attachBasis === true ? ", r.attach_basis AS attach_basis" : ""}${shape.major === true ? ", r.major AS major" : ""}${shape.relativePlaceErr === true ? ", r.relative_place_err AS relative_place_err" : ""}${shape.roundPoints === true ? ", r.round_points_printed AS round_points_printed" : ""}${shape.marks === true ? ", r.marks_printed AS marks_printed" : ""} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line${where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`} ORDER BY r.file, r.line`,
1604
+ sql: `SELECT ${COLUMNS}${shape.competition === true ? COMPETITION_COLUMNS : ""}${shape.competitorsPrinted === true ? ", c.competitors_printed AS competitors_printed" : ""}${shape.attachBasis === true ? ", r.attach_basis AS attach_basis" : ""}${shape.major === true ? ", r.major AS major" : ""}${shape.relativePlaceErr === true ? ", r.relative_place_err AS relative_place_err" : ""}${shape.roundPoints === true ? ", r.round_points_printed AS round_points_printed" : ""}${shape.marks === true ? ", r.marks_printed AS marks_printed" : ""}${shape.status === true ? ", r.status_printed AS status_printed" : ""} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line${where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`} ORDER BY r.file, r.line`,
1568
1605
  args
1569
1606
  };
1570
1607
  }
@@ -1576,7 +1613,7 @@ function feisDates(db) {
1576
1613
  }
1577
1614
  var text = (v) => v === null || v === "" ? null : v;
1578
1615
  function rowOf(r, dates) {
1579
- const { atts, withdrawn, recalled_only, round_points_printed, ...rest } = r;
1616
+ const { atts, withdrawn, recalled_only, round_points_printed, status_printed, ...rest } = r;
1580
1617
  const d = dates.get(r.event);
1581
1618
  return {
1582
1619
  ...rest,
@@ -1593,6 +1630,7 @@ function rowOf(r, dates) {
1593
1630
  ...round_points_printed === undefined ? {} : {
1594
1631
  round_points_printed: round_points_printed === null ? null : JSON.parse(round_points_printed)
1595
1632
  },
1633
+ ...status_printed === undefined ? {} : { status_printed: JSON.parse(status_printed) },
1596
1634
  withdrawn: text(withdrawn),
1597
1635
  qualifier: text(rest.qualifier),
1598
1636
  primary_source: text(rest.primary_source),
@@ -1718,6 +1756,7 @@ class ResultFinder2 {
1718
1756
  const relativePlaceErr = (await this.rel.capability(`${db}.relative_place_err`)).state === "full";
1719
1757
  const roundPoints = (await this.rel.capability(`${db}.round_points_printed`)).state === "full";
1720
1758
  const marks = (await this.rel.capability(`${db}.marks_printed`)).state === "full";
1759
+ const status = (await this.rel.capability(`${db}.status_printed`)).state === "full";
1721
1760
  return {
1722
1761
  db: this.rel.db(db),
1723
1762
  shape: {
@@ -1727,7 +1766,8 @@ class ResultFinder2 {
1727
1766
  major,
1728
1767
  relativePlaceErr,
1729
1768
  roundPoints,
1730
- marks
1769
+ marks,
1770
+ status
1731
1771
  }
1732
1772
  };
1733
1773
  }
@@ -1801,6 +1841,81 @@ class MajorsFinder2 {
1801
1841
  import {
1802
1842
  schoolGraph
1803
1843
  } from "@finbheara/domain/school-graph";
1844
+
1845
+ var SCHOOL_GRAPH_FEATURE = "school-graph";
1846
+ var LINEAGE_KINDS = [
1847
+ "rename",
1848
+ "absorb",
1849
+ "merger",
1850
+ "split",
1851
+ "branch",
1852
+ "related"
1853
+ ];
1854
+ async function schoolTables(rel) {
1855
+ if ((await rel.capability(SCHOOL_GRAPH_FEATURE)).state !== "full")
1856
+ return null;
1857
+ await rel.fetch("results");
1858
+ return rel.db("results");
1859
+ }
1860
+ var num = (v) => typeof v === "number" ? v : null;
1861
+ var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
1862
+ var suffixOf = (id) => id.slice(id.lastIndexOf("-") + 1);
1863
+ function redirectsOfTables(db) {
1864
+ const rows = db.query(`SELECT a.alias AS old_id, a.school_id AS new_id, a.kind AS kind,
1865
+ n.minted_reslug_from AS reslug_from, n.minted_reslug_pass AS reslug_pass
1866
+ FROM school_aliases a LEFT JOIN school_nodes n ON n.node_id = a.node_id
1867
+ WHERE a.kind IN ('fold', 'reslug') AND a.school_id IS NOT NULL
1868
+ UNION ALL
1869
+ SELECT f.node_id, f.closed_into, NULL,
1870
+ n.minted_reslug_from, n.minted_reslug_pass
1871
+ FROM school_nodes f LEFT JOIN school_nodes n ON n.node_id = f.closed_into
1872
+ WHERE f.kind = 'school' AND f.closed_into IS NOT NULL`).all();
1873
+ const absorber = new Map;
1874
+ for (const r of rows)
1875
+ if (r.kind === null)
1876
+ absorber.set(r.old_id, r.new_id);
1877
+ const live = (id) => {
1878
+ const seen = new Set;
1879
+ let at = id;
1880
+ for (let next = absorber.get(at);next !== undefined && !seen.has(at); next = absorber.get(at)) {
1881
+ seen.add(at);
1882
+ at = next;
1883
+ }
1884
+ return at;
1885
+ };
1886
+ return rows.map((r) => {
1887
+ const named = r.reslug_from === r.old_id;
1888
+ return {
1889
+ old_id: r.old_id,
1890
+ new_id: live(r.new_id),
1891
+ kind: r.kind ?? (named || suffixOf(r.old_id) === suffixOf(r.new_id) ? "reslug" : "fold"),
1892
+ since: num(named ? r.reslug_pass : null)
1893
+ };
1894
+ }).sort((a, b) => cmp(a.old_id, b.old_id) || cmp(a.new_id, b.new_id));
1895
+ }
1896
+ function lineageOfTables(db) {
1897
+ const rows = db.query(`SELECT from_id, to_id, kind, valid_from, provisional, confidence, decision_rows
1898
+ FROM school_edges
1899
+ WHERE kind IN (${LINEAGE_KINDS.map(() => "?").join(", ")}) AND lag_months IS NULL`).all(...LINEAGE_KINDS);
1900
+ const out = [];
1901
+ for (const r of rows) {
1902
+ const rowsBehind = r.decision_rows === null ? [] : JSON.parse(r.decision_rows).map(String);
1903
+ const decision = rowsBehind[0] ?? null;
1904
+ const end = (id, direction, other) => ({
1905
+ id,
1906
+ direction,
1907
+ other,
1908
+ shape: r.kind,
1909
+ year: num(r.valid_from),
1910
+ provisional: r.provisional === 1,
1911
+ decision,
1912
+ confidence: decision === null ? null : num(r.confidence)
1913
+ });
1914
+ out.push(end(r.from_id, "precedes", r.to_id), end(r.to_id, "succeeds", r.from_id));
1915
+ }
1916
+ return out.sort((a, b) => cmp(a.id, b.id) || cmp(a.year ?? -1, b.year ?? -1) || cmp(a.direction, b.direction) || cmp(a.other, b.other) || cmp(a.shape, b.shape));
1917
+ }
1918
+
1804
1919
  var LINEAGE_FILE2 = "school-lineage.jsonl";
1805
1920
  var REDIRECTS_FILE = "school-redirects.jsonl";
1806
1921
  function linksOfLineage2(lines) {
@@ -1839,6 +1954,14 @@ function backingOf(r) {
1839
1954
  };
1840
1955
  }
1841
1956
  async function releaseSchoolGraph2(rel) {
1957
+ const tables = await schoolTables(rel);
1958
+ if (tables !== null) {
1959
+ const to = new Map(redirectsOfTables(tables).map((r) => [r.old_id, r.new_id]));
1960
+ return schoolGraph({
1961
+ links: linksOfLineage2(lineageOfTables(tables)),
1962
+ redirect: (id) => to.get(id) ?? null
1963
+ });
1964
+ }
1842
1965
  await rel.require("schools.lineage");
1843
1966
  await rel.fetch(LINEAGE_FILE2);
1844
1967
  const links = linksOfLineage2(readJsonl(rel.path(LINEAGE_FILE2)));
@@ -1858,7 +1981,7 @@ import {
1858
1981
  } from "@finbheara/domain/school";
1859
1982
 
1860
1983
  var str = (v) => typeof v === "string" && v !== "" ? v : null;
1861
- var num = (v) => typeof v === "number" ? v : null;
1984
+ var num2 = (v) => typeof v === "number" ? v : null;
1862
1985
  var uniq = (xs) => [...new Set(xs)];
1863
1986
  async function registrySchools(rel) {
1864
1987
  await rel.require("schools.registry");
@@ -1891,17 +2014,23 @@ async function schoolPrints(rel, q) {
1891
2014
  return { tag: rel.tag, asked: q, prints };
1892
2015
  }
1893
2016
  async function schoolRedirects(rel) {
2017
+ const tables = await schoolTables(rel);
2018
+ if (tables !== null)
2019
+ return { tag: rel.tag, redirects: redirectsOfTables(tables) };
1894
2020
  await rel.require("schools.redirects");
1895
2021
  await rel.fetch("school-redirects.jsonl");
1896
2022
  const redirects = readJsonl(rel.path("school-redirects.jsonl")).map((r) => ({
1897
2023
  old_id: String(r.old_id),
1898
2024
  new_id: String(r.new_id),
1899
2025
  kind: String(r.kind),
1900
- since: num(r.since)
2026
+ since: num2(r.since)
1901
2027
  }));
1902
2028
  return { tag: rel.tag, redirects };
1903
2029
  }
1904
2030
  async function schoolLineage(rel) {
2031
+ const tables = await schoolTables(rel);
2032
+ if (tables !== null)
2033
+ return { tag: rel.tag, edges: lineageOfTables(tables) };
1905
2034
  await rel.require("schools.lineage");
1906
2035
  await rel.fetch("school-lineage.jsonl");
1907
2036
  const edges = readJsonl(rel.path("school-lineage.jsonl")).map((r) => ({
@@ -1909,10 +2038,10 @@ async function schoolLineage(rel) {
1909
2038
  direction: String(r.direction),
1910
2039
  other: String(r.other),
1911
2040
  shape: String(r.shape),
1912
- year: num(r.year),
2041
+ year: num2(r.year),
1913
2042
  provisional: r.provisional === true,
1914
2043
  decision: str(r.decision),
1915
- confidence: num(r.confidence)
2044
+ confidence: num2(r.confidence)
1916
2045
  }));
1917
2046
  return { tag: rel.tag, edges };
1918
2047
  }
@@ -1926,7 +2055,7 @@ async function observedAssociations(rel, q) {
1926
2055
  continue;
1927
2056
  associations.push({
1928
2057
  at: String(r.at),
1929
- seq: num(r.seq) ?? 0,
2058
+ seq: num2(r.seq) ?? 0,
1930
2059
  date: str(r.date),
1931
2060
  person: str(r.person),
1932
2061
  person_printed: typeof r.personPrinted === "string" ? r.personPrinted : "",
@@ -2171,6 +2300,7 @@ var PACKAGE_KEY_PARTS = [
2171
2300
  `domain=${domainPkg.version}`
2172
2301
  ];
2173
2302
  var ENCODER = personEncoder({ read: (p, o) => dancerName(p, o) });
2303
+ var surnameBlockOf = (surname) => surnameBlock(dancerName(`${surname}, X`, "surname-first").surnameKey());
2174
2304
  var PERSON = { table: "person", value: "id", valueType: "INTEGER PRIMARY KEY" };
2175
2305
  var ASKED = ["surname_key", "nn4", "blk_surname"];
2176
2306
  var dancersIndex = {
@@ -2381,6 +2511,9 @@ class DancerResolver2 {
2381
2511
  }
2382
2512
  lineage() {
2383
2513
  this.lineageOf ??= (async () => {
2514
+ const tables = await schoolTables(this.rel);
2515
+ if (tables !== null)
2516
+ return schoolGraph2({ links: linksOfLineage2(lineageOfTables(tables)) });
2384
2517
  if ((await this.rel.capability("schools.lineage")).state !== "full")
2385
2518
  return null;
2386
2519
  await this.rel.fetch(LINEAGE_FILE2);
@@ -2535,7 +2668,7 @@ class DancerResolver2 {
2535
2668
  const hits = new Map;
2536
2669
  const names = createIndex2(db, ENCODER, PERSON);
2537
2670
  if (q.surname !== undefined) {
2538
- const block = surnameBlock(foldNameKey(q.surname).toUpperCase());
2671
+ const block = surnameBlockOf(q.surname);
2539
2672
  if (block !== null)
2540
2673
  for (const { value } of names.rows({ blk_surname: [block] }, ["print"]))
2541
2674
  hits.set(value, "surname");
@@ -3785,7 +3918,7 @@ async function parquetColumns(path) {
3785
3918
  }
3786
3919
 
3787
3920
  var str2 = (v) => typeof v === "string" && v !== "" ? v : null;
3788
- var num2 = (v) => typeof v === "number" ? v : null;
3921
+ var num3 = (v) => typeof v === "number" ? v : null;
3789
3922
  var strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
3790
3923
  var scope = (id) => id === null ? null : id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable";
3791
3924
  var TEAM_TABLES = [
@@ -3842,7 +3975,7 @@ var teamsIndex = {
3842
3975
  for (const r of t.event_years) {
3843
3976
  const k = str2(r.event_year);
3844
3977
  const event = str2(r.event);
3845
- const year = num2(r.year);
3978
+ const year = num3(r.year);
3846
3979
  if (k === null || event === null || year === null)
3847
3980
  continue;
3848
3981
  ey.set(k, { event, year });
@@ -3851,19 +3984,19 @@ var teamsIndex = {
3851
3984
  const putR = out.prepare("INSERT OR IGNORE INTO result VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
3852
3985
  let ord = 0;
3853
3986
  for (const r of t.team_results)
3854
- putR.run(str2(r.address), str2(r.slug), str2(r.event), num2(r.year), str2(r.card), str2(r.comp_no), str2(r.title), str2(r.category), num2(r.hands), str2(r.letter), str2(r.label), str2(r.name_printed), str2(r.school_printed), str2(r.school_loc), str2(r.school_id), num2(r.place), str2(r.place_printed), num2(r.tie), num2(r.competitors), JSON.stringify(strs(r.atts)), str2(r.age), str2(r.gender), JSON.stringify(strs(r.printed_status)), num2(r.competitors_printed), num2(r.recalled_only), ord++);
3987
+ putR.run(str2(r.address), str2(r.slug), str2(r.event), num3(r.year), str2(r.card), str2(r.comp_no), str2(r.title), str2(r.category), num3(r.hands), str2(r.letter), str2(r.label), str2(r.name_printed), str2(r.school_printed), str2(r.school_loc), str2(r.school_id), num3(r.place), str2(r.place_printed), num3(r.tie), num3(r.competitors), JSON.stringify(strs(r.atts)), str2(r.age), str2(r.gender), JSON.stringify(strs(r.printed_status)), num3(r.competitors_printed), num3(r.recalled_only), ord++);
3855
3988
  const putT = out.prepare("INSERT OR IGNORE INTO roster_team VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
3856
3989
  for (const r of t.roster_teams) {
3857
3990
  const e = ey.get(str2(r.event_year) ?? "");
3858
- putT.run(str2(r.team), str2(r.kind), e?.event ?? null, num2(r.year) ?? e?.year ?? null, str2(r.comp_no), str2(r.title), str2(r.team_index), num2(r.seq), str2(r.school_id), str2(r.school_print_materialized), str2(r.dance), str2(r.school_print_as_of), ord++);
3991
+ putT.run(str2(r.team), str2(r.kind), e?.event ?? null, num3(r.year) ?? e?.year ?? null, str2(r.comp_no), str2(r.title), str2(r.team_index), num3(r.seq), str2(r.school_id), str2(r.school_print_materialized), str2(r.dance), str2(r.school_print_as_of), ord++);
3859
3992
  }
3860
3993
  const putM = out.prepare("INSERT INTO roster_member VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
3861
3994
  for (const r of t.roster_members)
3862
- putM.run(str2(r.team), num2(r.seq), typeof r.name_printed === "string" ? r.name_printed : "", str2(r.name), num2(r.yob), num2(r.dancer_id), str2(r.gender), ord++);
3995
+ putM.run(str2(r.team), num3(r.seq), typeof r.name_printed === "string" ? r.name_printed : "", str2(r.name), num3(r.yob), num3(r.dancer_id), str2(r.gender), ord++);
3863
3996
  const putJ = out.prepare("INSERT INTO result_roster VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
3864
3997
  for (const r of t.team_result_rosters) {
3865
3998
  const e = ey.get(str2(r.event_year) ?? "");
3866
- putJ.run(str2(r.team), e?.event ?? null, e?.year ?? null, str2(r.comp_no), str2(r.school_id), str2(r.result_address), str2(r.join_kind), num2(r.via_split) === 1 ? 1 : 0, num2(r.roster_teams), num2(r.result_teams), str2(r.roster_index), str2(r.result_letter), ord++);
3999
+ putJ.run(str2(r.team), e?.event ?? null, e?.year ?? null, str2(r.comp_no), str2(r.school_id), str2(r.result_address), str2(r.join_kind), num3(r.via_split) === 1 ? 1 : 0, num3(r.roster_teams), num3(r.result_teams), str2(r.roster_index), str2(r.result_letter), ord++);
3867
4000
  }
3868
4001
  for (const s of TEAMS_INDEXES)
3869
4002
  out.run(s);
@@ -3884,10 +4017,10 @@ var eventsIndex = {
3884
4017
  const put = out.prepare("INSERT OR IGNORE INTO event_year VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
3885
4018
  for (const r of rows) {
3886
4019
  const event = str2(r.event);
3887
- const year = num2(r.year);
4020
+ const year = num3(r.year);
3888
4021
  if (event === null || year === null)
3889
4022
  continue;
3890
- put.run(event, year, str2(r.date_lo), str2(r.date_hi), str2(r.resolution), str2(r.source), JSON.stringify(strs(r.date_printed)), str2(r.place), JSON.stringify(strs(r.place_printed)), str2(r.published), num2(r.listed));
4023
+ put.run(event, year, str2(r.date_lo), str2(r.date_hi), str2(r.resolution), str2(r.source), JSON.stringify(strs(r.date_printed)), str2(r.place), JSON.stringify(strs(r.place_printed)), str2(r.published), num3(r.listed));
3891
4024
  }
3892
4025
  out.run("CREATE INDEX event_year_event ON event_year(event COLLATE NOCASE)");
3893
4026
  }
package/dist/cli.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  staleWarning2,
24
24
  progressMode2,
25
25
  progressRenderer2
26
- } from "./chunks/index-csh5d5m4.js";
26
+ } from "./chunks/index-aj1n1q7j.js";
27
27
  import"./chunks/index-hghdy980.js";
28
28
 
29
29
  var USAGE_HEADER = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json] [--live] [--progress tty|plain|none]";
package/dist/index.js CHANGED
@@ -85,7 +85,7 @@ import {
85
85
  PROGRESS_MODES2,
86
86
  progressMode2,
87
87
  progressRenderer2
88
- } from "./chunks/index-csh5d5m4.js";
88
+ } from "./chunks/index-aj1n1q7j.js";
89
89
  import {
90
90
  cacheRoot2,
91
91
  repoOf2
@@ -182,6 +182,7 @@ export interface DancerRecord {
182
182
  schools: DancerSchool[];
183
183
  }
184
184
  export declare const PACKAGE_KEY_PARTS: readonly string[];
185
+ export declare const surnameBlockOf: (surname: string) => string | null;
185
186
  export declare const dancersIndex: IndexBuilder;
186
187
  export declare function yearOf(asOf: string | undefined): number | null;
187
188
  export interface DancerBulkRecord {
@@ -8,6 +8,7 @@ export interface Feis {
8
8
  token: string;
9
9
  host_id: string;
10
10
  name: string;
11
+ name_printed: string;
11
12
  date_lo: string | null;
12
13
  date_hi: string | null;
13
14
  date_printed: string | null;
@@ -0,0 +1,8 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { Release } from "./release.js";
3
+ import type { LineageEdge, SchoolRedirect } from "./school-bulk.js";
4
+ export declare const SCHOOL_GRAPH_FEATURE = "school-graph";
5
+ export declare const LINEAGE_KINDS: readonly string[];
6
+ export declare function schoolTables(rel: Release): Promise<Database | null>;
7
+ export declare function redirectsOfTables(db: Database): SchoolRedirect[];
8
+ export declare function lineageOfTables(db: Database): LineageEdge[];
@@ -66,6 +66,7 @@ export interface StreamRow extends Partial<StreamCompetition> {
66
66
  points_printed: string | null;
67
67
  round_points_printed?: (string | null)[] | null;
68
68
  marks_printed?: string | null;
69
+ status_printed?: string[];
69
70
  platform_dancer_id: string | null;
70
71
  primary_source: string | null;
71
72
  file: string;
@@ -83,6 +84,7 @@ export interface StreamShape {
83
84
  relativePlaceErr?: boolean;
84
85
  roundPoints?: boolean;
85
86
  marks?: boolean;
87
+ status?: boolean;
86
88
  }
87
89
  export declare const scopeDb: (s: StreamScope) => "majors" | "results";
88
90
  export declare function streamSql(q: StreamQuery, shape?: StreamShape): {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.52.0",
3
+ "version": "0.53.1",
4
4
  "description": "Open a versioned release of an Irish dance results archive: download, verify, decompress and cache it by tag, keep the cache, and resolve feises and schools against it",
5
5
  "keywords": ["irish-dance", "results", "release", "cache", "sqlite"],
6
6
  "license": "MIT",
@@ -44,8 +44,8 @@
44
44
  "prepublishOnly": "bun scripts/build.ts && bun ../../tools/checks/archive-client-tarball.ts"
45
45
  },
46
46
  "dependencies": {
47
- "@finbheara/domain": "0.59.0",
48
- "@finbheara/names": "0.30.0",
47
+ "@finbheara/domain": "0.60.1",
48
+ "@finbheara/names": "0.31.0",
49
49
  "hyparquet": "1.31.2"
50
50
  },
51
51
  "devDependencies": {