@finbheara/archive-client 0.12.0 → 0.14.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.
package/README.md CHANGED
@@ -194,14 +194,19 @@ A release holds a feis-year once its results are captured and carries no calenda
194
194
  ```ts
195
195
  const a = await rel.schools.resolve("Ardvarna Academy of Irish Dance (Calgary, AB)", { region: "WCRO" });
196
196
  a.arm; // "unique" | "ambiguous" | "none"
197
- a.candidates; // [{ school_id, display, rows, by, prints, regions, places }]
197
+ a.candidates; // [{ school_id, display, rows, by, prints, regions, places, years? }]
198
198
  ```
199
199
 
200
200
  The rungs, the first that answers wins: a school id, a retired id followed to the live one, the
201
201
  print exactly as the release published it, then the print read by `@finbheara/domain`'s school
202
202
  reader at its filing key (`sn3`) and at its real words. `ambiguous` and `none` are answers: a
203
203
  best guess is never promoted to `unique`. `region` drops only a candidate the registry places in
204
- other regions, and keeps one it places nowhere. A print the reader refuses (a placeholder)
204
+ other regions, and keeps one it places nowhere. `year` reads a print at that year, on a print
205
+ rung only: where the print reaches two or more ids, the ids whose rows printed it that year are
206
+ kept (a print one school took over from another names the one that held it then), else the ids
207
+ that printed it last before that year, and where the year is before every one of them, all stay
208
+ (`a.year`: `{ asked, narrowed }`, each candidate's `years: { lo, hi }`). A dancer question reads
209
+ its school print at its own year. A print the reader refuses (a placeholder)
205
210
  answers `none` with the reason. An id (or a retired id followed) answers with every print its
206
211
  rows were published under and their rows, so it agrees with each print lookup of that school.
207
212
 
@@ -262,8 +267,8 @@ year with the asked age, or a dancer the release publishes in the other populati
262
267
  majors solo competitions and in none of the asked one's (listed in `excluded`, `why: "age"` or
263
268
  `"population"`, and a candidate contradicting both says `age`). A dancer published in both, or in
264
269
  neither, stays. A population word that names no one population (`mixed`) narrows nothing, and the
265
- answer says so (`r.population.note`). The school is evidence and order, never a reason
266
- to drop anyone: candidates whose latest school is the asked one, or one the lineage joins to it,
270
+ answer says so (`r.population.note`). Outside the narrowing below, the school is evidence and
271
+ order, never a reason to drop anyone: candidates whose latest school is the asked one, or one the lineage joins to it,
267
272
  come first, then those that ever danced for it, then the rest. A school row meets the asked school by its id, else by its print
268
273
  (one `rel.schools` resolves to the asked ids), so a latest print the release
269
274
  filed under no id is still `same`. `unique` is one candidate left after the age, never a best
@@ -297,8 +302,31 @@ words):
297
302
  one candidate is open and the age did not set it aside: the candidates the school refuted then
298
303
  move to `excluded` (`why: "school"`). Otherwise the arm stays `ambiguous` with every candidate,
299
304
  and where the school and the region single out no one, `preferred` names the only open candidate
300
- with a row in the asked year or the year before. A full given name is never narrowed this way: two
301
- full given names that differ are two names, and the school only orders them.
305
+ with a row in the asked year or the year before.
306
+
307
+ A FULL GIVEN NAME with a school the release resolves is narrowed the same way (`r.fullName`, the
308
+ same shape as `r.initials`), with one more hard fact:
309
+
310
+ - `given`: a candidate the name met only through an initial print (`Quellan, A.`) that also
311
+ printed a full given name the names package relates in no way to the asked one is another
312
+ dancer. An initial-only candidate, a variant or a diminutive (`nameKinship`) stays.
313
+
314
+ A candidate at another school is set aside only by the hard school fact, never for being
315
+ elsewhere: a dancer who ever danced for the asked school's line stays open.
316
+
317
+ THE WIDENED PASSES (`r.widened`), only where the name as asked is left with no one, so a widened
318
+ match never answers over an exact one:
319
+
320
+ - `birth-conflict`: a birth window the release flags (`birth.conflict`) is read one year wider on
321
+ each side, and the candidate says so (`candidate.widened`).
322
+ - `name-form`: the name read in its other forms by `@finbheara/names`: `composeNames` (a print
323
+ that left out a second surname, a middle name or a given name's letters, or a double-barrelled
324
+ surname read as its parts) or else `nameKinship` (a given name's variant or diminutive). Such a
325
+ candidate's `nameLevel` is `form` and `evidence.form` lists the tolerances. A form is set
326
+ aside only by the hard school fact (`why: "name-form"`, the fact in `r.fullName.refuted` or
327
+ `r.initials.refuted` where the answer was narrowed): every school row of the dancer, to the
328
+ asked year, reads outside the asked school's line. A dancer with no school row, or a print no
329
+ school reads, is unknown and stays.
302
330
 
303
331
  THE REGIONAL OIREACHTAS (`oireachtas: { region, year? }`). A dancer enters one
304
332
  residence-qualifying regional Oireachtas a year. Two regionals in one year are two dancers, the
@@ -503,12 +531,20 @@ the host likes, so a code is answered from that feis's competitions in the relea
503
531
  another platform's numbering. The sources, in order:
504
532
 
505
533
  - `syllabus`: the feis-year is in the release, and one of its competitions prints the code
506
- - `prior-year`: the feis-year is not (next year's, before its results): the newest earlier year
507
- of the same series printing the code. Give the comp's `title` and `matchTitle` checks it
508
- against the earlier comp (`check`), and with no title the answer says `unchecked`
534
+ - `prior-year`: the feis-year is not (next year's, before its results), and its platform numbers
535
+ every feis by one grammar (QuickFeis: `CODE_READER`, `@finbheara/domain/code`): the newest
536
+ earlier year of the same series printing the code. Give the comp's `title` and `matchTitle`
537
+ checks it against the earlier comp (`check`), and with no title the answer says `unchecked`
509
538
  - `title`: the code answers nothing and a `title` was given: the competitions of that syllabus
510
539
  the title names, by band and population
511
540
 
541
+ AN EARLIER YEAR'S CODE IS NEVER READ ON A FEIS-SPECIFIC PLATFORM. FeisWeb, FeisWorx, iFeis and
542
+ the rest renumber their competitions from one year to the next: measured in October 2026, the
543
+ same code at the same series one held year later names another band or level on 13% to 29% of
544
+ codes, a plausible answer and a wrong one. So an unheld feis-year there answers by its `title`
545
+ over the newest earlier syllabus, and a code with no title is `none`, saying why. The code of a
546
+ held feis-year is still read from that feis-year's own competitions.
547
+
512
548
  A feis the release holds names itself, its year and its series. A series instance or brand id
513
549
  names the series. A feis id the release does not hold is placed in a series by its host code
514
550
  (`<token>:<stem><yy>` where the held codes of that stem carry their own year), and a stem two
@@ -77,7 +77,10 @@ import {
77
77
  populationRelation
78
78
  } from "@finbheara/domain/title";
79
79
  import {
80
+ composeNames,
80
81
  foldNameKey,
82
+ nameBlockKeys,
83
+ nameKinship,
81
84
  surnameBlock
82
85
  } from "@finbheara/names/normalize";
83
86
  import namesPkg from "@finbheara/names/package.json";
@@ -91,6 +94,7 @@ var NAME_LEVELS2 = [
91
94
  ...NAME_LEVEL_COLUMNS,
92
95
  "initial",
93
96
  "middle",
97
+ "form",
94
98
  "surname",
95
99
  "like"
96
100
  ];
@@ -640,7 +644,7 @@ var PERSON = { table: "person", value: "id", valueType: "INTEGER PRIMARY KEY" };
640
644
  var ASKED = ["surname_key", "nn4", "blk_surname"];
641
645
  var dancersIndex = {
642
646
  name: "dancers",
643
- version: "4",
647
+ version: "5",
644
648
  inputs: ["results"],
645
649
  keyParts: () => [...PACKAGE_KEY_PARTS],
646
650
  says: "every published dancer print at the names package's keys, its dancers, schools and years",
@@ -652,6 +656,8 @@ var dancersIndex = {
652
656
  out.run("CREATE TABLE dancer_school(dancer_id INTEGER NOT NULL, school_id TEXT, print TEXT NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER)");
653
657
  out.run("CREATE TABLE dancer_population(dancer_id INTEGER NOT NULL, population TEXT NOT NULL, rows INTEGER NOT NULL, PRIMARY KEY(dancer_id, population)) WITHOUT ROWID");
654
658
  out.run("CREATE TABLE dancer_regional(dancer_id INTEGER NOT NULL, year INTEGER NOT NULL, region TEXT NOT NULL, regional INTEGER NOT NULL, PRIMARY KEY(dancer_id, year)) WITHOUT ROWID");
659
+ out.run("CREATE TABLE person_block(block TEXT NOT NULL, person INTEGER NOT NULL)");
660
+ const putBlock = out.prepare("INSERT INTO person_block VALUES (?, ?)");
655
661
  const src = rel.db("results");
656
662
  const groups = src.query(`SELECT r.name_printed AS p, r.name_order_resolved AS o, r.dancer_id AS d,
657
663
  r.school_printed AS sp, r.school_id AS s,
@@ -673,6 +679,8 @@ var dancersIndex = {
673
679
  id = ++next;
674
680
  names.add(name, id);
675
681
  personOf.set(reading, id);
682
+ for (const b of nameBlockKeys(name))
683
+ putBlock.run(b, id);
676
684
  }
677
685
  personOf.set(k, id);
678
686
  }
@@ -748,6 +756,7 @@ var dancersIndex = {
748
756
  for (const s of schema.indexes)
749
757
  if (ASKED.some((c) => s.endsWith(`(${c})`)))
750
758
  out.run(s);
759
+ out.run("CREATE INDEX person_block_block ON person_block(block)");
751
760
  out.run("CREATE INDEX print_dancer_person ON print_dancer(person)");
752
761
  out.run("CREATE INDEX print_dancer_dancer ON print_dancer(dancer_id)");
753
762
  out.run("CREATE INDEX dancer_school_dancer ON dancer_school(dancer_id)");
@@ -767,6 +776,7 @@ var NAME_WEIGHT = {
767
776
  nn4: 3,
768
777
  initial: 2,
769
778
  middle: 1.5,
779
+ form: 1.25,
770
780
  surname: 1,
771
781
  like: 1
772
782
  };
@@ -907,7 +917,56 @@ class DancerResolver2 {
907
917
  if (level !== null)
908
918
  hits.set(value, level);
909
919
  }
910
- return this.answer(db, q, hits);
920
+ const exact = await this.answer(db, q, hits);
921
+ if (exact.arm !== "none")
922
+ return exact;
923
+ const born = await this.answer(db, q, hits, true, { birthSlack: true });
924
+ if (born.arm !== "none")
925
+ return { ...born, widened: "birth-conflict" };
926
+ const forms = this.formsOf(db, names, asked, hits);
927
+ if (forms.size === 0)
928
+ return exact;
929
+ const all = new Map(hits);
930
+ for (const id of forms.keys())
931
+ all.set(id, "form");
932
+ const formed = await this.answer(db, q, all, true, { birthSlack: true, forms });
933
+ return formed.arm === "none" ? exact : { ...formed, widened: "name-form" };
934
+ }
935
+ formsOf(db, names, asked, hits) {
936
+ const out = new Map;
937
+ const keys = nameBlockKeys(asked);
938
+ const persons = new Set;
939
+ if (keys.length > 0)
940
+ for (const r of db.query(`SELECT DISTINCT person FROM person_block WHERE block IN (${keys.map(() => "?").join(",")})`).all(...keys))
941
+ persons.add(r.person);
942
+ const rows = new Map;
943
+ for (const { value, row } of names.rows({ surname_key: [asked.surnameKey()] }, [
944
+ "print",
945
+ "reading"
946
+ ]))
947
+ rows.set(value, row);
948
+ const ids = [...persons].filter((id) => !hits.has(id) && !rows.has(id));
949
+ for (let i = 0;i < ids.length; i += 500) {
950
+ const chunk = ids.slice(i, i + 500);
951
+ for (const r of db.query(`SELECT id, print, reading FROM person WHERE id IN (${chunk.map(() => "?").join(",")})`).all(...chunk))
952
+ rows.set(r.id, { print: r.print, reading: r.reading });
953
+ }
954
+ for (const [id, row] of rows) {
955
+ if (hits.has(id))
956
+ continue;
957
+ const c = ENCODER.decode(row);
958
+ if (!c.isPerson())
959
+ continue;
960
+ const composed = composeNames(asked, c);
961
+ if (composed.composition !== "conflict" && composed.composition !== "silent") {
962
+ out.set(id, [...composed.via]);
963
+ continue;
964
+ }
965
+ const kin = nameKinship(asked, c);
966
+ if (kin.relation !== "different")
967
+ out.set(id, [...kin.via]);
968
+ }
969
+ return out;
911
970
  }
912
971
  async search(q) {
913
972
  if (q.name !== undefined) {
@@ -958,7 +1017,7 @@ class DancerResolver2 {
958
1017
  schools: schoolsOf(db, dancerId)
959
1018
  };
960
1019
  }
961
- async answer(db, q, hits, narrow = true) {
1020
+ async answer(db, q, hits, narrow = true, widen = {}) {
962
1021
  const asOf = yearOf(q.asOf);
963
1022
  const age = q.age === undefined ? undefined : this.readAge(q.age, asOf);
964
1023
  const population = q.population === undefined ? undefined : readPopulation2(q.population);
@@ -967,7 +1026,7 @@ class DancerResolver2 {
967
1026
  let lineageIds = [];
968
1027
  let school;
969
1028
  if (q.school !== undefined) {
970
- const s = await this.rel.schools.resolve(q.school);
1029
+ const s = await this.rel.schools.resolve(q.school, { year: askedYear(q, asOf) });
971
1030
  schoolIds = s.candidates.map((c) => c.school_id);
972
1031
  const lineage = await this.lineage();
973
1032
  if (lineage === null)
@@ -987,9 +1046,11 @@ class DancerResolver2 {
987
1046
  unlinked.set(r.print, (unlinked.get(r.print) ?? 0) + r.n);
988
1047
  continue;
989
1048
  }
990
- const e = byDancer.get(r.d) ?? { level, prints: [] };
1049
+ const e = byDancer.get(r.d) ?? { level, prints: [], via: new Set };
991
1050
  e.level = finer(e.level, level);
992
1051
  e.prints.push({ print: r.print, rows: r.n, level });
1052
+ for (const v of widen.forms?.get(person) ?? [])
1053
+ e.via.add(v);
993
1054
  byDancer.set(r.d, e);
994
1055
  }
995
1056
  const dq = db.query("SELECT * FROM dancer WHERE dancer_id = ?");
@@ -1000,7 +1061,15 @@ class DancerResolver2 {
1000
1061
  continue;
1001
1062
  const schools = schoolsOf(db, id);
1002
1063
  const latest = latestSchool(schools, asOf);
1003
- const ageEv = ageEvidence(d, age?.age ?? null);
1064
+ let ageEv = ageEvidence(d, age?.age ?? null);
1065
+ let widenedBirth = false;
1066
+ if (widen.birthSlack && ageEv === "contradicts" && d.birth_conflict !== 0) {
1067
+ const wider = ageEvidence(d, age?.age ?? null, 1);
1068
+ if (wider !== "contradicts") {
1069
+ ageEv = wider;
1070
+ widenedBirth = true;
1071
+ }
1072
+ }
1004
1073
  const pop = populationOf(db, id);
1005
1074
  const popEv = populationEvidence(pop, population?.read ?? null);
1006
1075
  const schoolEv = schoolIds === null ? "unasked" : schoolIds.length === 0 ? "unresolved" : await this.schoolEvidence(schools, schoolIds, lineageIds, asOf);
@@ -1021,8 +1090,10 @@ class DancerResolver2 {
1021
1090
  age: ageEv,
1022
1091
  school: schoolEv,
1023
1092
  population: popEv,
1024
- region: regionEv
1093
+ region: regionEv,
1094
+ ...e.level === "form" ? { form: [...e.via] } : {}
1025
1095
  },
1096
+ ...widenedBirth ? { widened: "birth-conflict" } : {},
1026
1097
  prints: e.prints.sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
1027
1098
  rows: d.rows,
1028
1099
  year_lo: d.year_lo,
@@ -1049,21 +1120,54 @@ class DancerResolver2 {
1049
1120
  return false;
1050
1121
  });
1051
1122
  const year = askedYear(q, asOf);
1052
- let initials;
1053
- if (narrow && kept.length > 1 && schoolIds !== null && schoolIds.length > 0 && initialsOnly2(dancerName(q.name, q.order === "unknown" ? undefined : q.order))) {
1123
+ const band = age?.age?.birthYears() ?? null;
1124
+ const adultBy = adultBornBy(year);
1125
+ const adultAsked = adultBy !== null && adultCategory(q.age, age?.age ?? null);
1126
+ const returningOf = (c) => adultAsked && c.birth.hi !== null && c.birth.hi <= adultBy ? c.birth.lo : null;
1127
+ let line = null;
1128
+ if (schoolIds !== null && schoolIds.length > 0) {
1054
1129
  const lineage = await this.lineage();
1055
- const line = lineage === null ? new Set(schoolIds) : schoolLine(lineage, schoolIds, year);
1056
- const band = age?.age?.birthYears() ?? null;
1130
+ line = lineage === null ? new Set(schoolIds) : schoolLine(lineage, schoolIds, year);
1131
+ }
1132
+ const formFacts = new Map;
1133
+ if (narrow && widen.forms !== undefined && line !== null) {
1134
+ const next = [];
1135
+ for (const c of kept) {
1136
+ if (c.evidence.nameLevel === "form") {
1137
+ const fact = await this.schoolRefutes(schoolsOf(db, c.dancer_id), line, year, q.school, returningOf(c));
1138
+ if (fact !== null) {
1139
+ formFacts.set(c.dancer_id, fact);
1140
+ excluded.push({ candidate: c, why: "name-form" });
1141
+ continue;
1142
+ }
1143
+ }
1144
+ next.push(c);
1145
+ }
1146
+ kept = next;
1147
+ }
1148
+ const askedName = dancerName(q.name, q.order === "unknown" ? undefined : q.order);
1149
+ const byInitials = initialsOnly2(askedName);
1150
+ let narrowing;
1151
+ if (narrow && kept.length > 1 && line !== null) {
1057
1152
  const slack = q.age !== undefined && !("yob" in q.age) ? DANCE_UP_YEARS : 0;
1058
1153
  const open = [];
1059
1154
  const held = [];
1060
1155
  const refuted = [];
1061
1156
  const bySchool = new Set;
1062
- const adultBy = adultBornBy(year);
1063
- const adultAsked = adultBy !== null && adultCategory(q.age, age?.age ?? null);
1157
+ const givenFact = (c) => {
1158
+ if (byInitials || c.evidence.nameLevel !== "initial")
1159
+ return false;
1160
+ const fact = otherGiven(db, c.dancer_id, askedName);
1161
+ if (fact === null)
1162
+ return false;
1163
+ refuted.push({ dancer_id: c.dancer_id, why: "given", fact });
1164
+ bySchool.add(c.dancer_id);
1165
+ return true;
1166
+ };
1064
1167
  const schoolFact = async (c) => {
1065
- const returning = adultAsked && c.birth.hi !== null && c.birth.hi <= adultBy ? c.birth.lo : null;
1066
- const fact = await this.schoolRefutes(schoolsOf(db, c.dancer_id), line, year, q.school, returning);
1168
+ if (givenFact(c))
1169
+ return true;
1170
+ const fact = await this.schoolRefutes(schoolsOf(db, c.dancer_id), line, year, q.school, returningOf(c));
1067
1171
  if (fact === null)
1068
1172
  return false;
1069
1173
  refuted.push({ dancer_id: c.dancer_id, why: "school", fact });
@@ -1084,10 +1188,10 @@ class DancerResolver2 {
1084
1188
  refuted.push({
1085
1189
  dancer_id: c.dancer_id,
1086
1190
  why: x.why,
1087
- fact: contradictionFact(x, age, slack)
1191
+ fact: formFacts.get(c.dancer_id) ?? contradictionFact(x, age, slack)
1088
1192
  });
1089
1193
  }
1090
- initials = {
1194
+ narrowing = {
1091
1195
  open: [...open.map((c) => c.dancer_id), ...held],
1092
1196
  heldByDanceUp: held,
1093
1197
  refuted,
@@ -1096,7 +1200,10 @@ class DancerResolver2 {
1096
1200
  if (open.length === 1 && held.length === 0) {
1097
1201
  for (const c of kept)
1098
1202
  if (bySchool.has(c.dancer_id))
1099
- excluded.push({ candidate: c, why: "school" });
1203
+ excluded.push({
1204
+ candidate: c,
1205
+ why: refuted.find((r) => r.dancer_id === c.dancer_id)?.why === "given" ? "given" : "school"
1206
+ });
1100
1207
  kept = open;
1101
1208
  }
1102
1209
  }
@@ -1126,8 +1233,8 @@ class DancerResolver2 {
1126
1233
  };
1127
1234
  }
1128
1235
  }
1129
- if (preferred === undefined && kept.length > 1 && initials !== undefined) {
1130
- const openIds = new Set(initials.open);
1236
+ if (preferred === undefined && kept.length > 1 && narrowing !== undefined) {
1237
+ const openIds = new Set(narrowing.open);
1131
1238
  const hit = kept.filter((c) => openIds.has(c.dancer_id) && c.year_hi !== null && c.year_hi >= year - 1);
1132
1239
  if (hit.length === 1) {
1133
1240
  const c = hit[0];
@@ -1149,7 +1256,7 @@ class DancerResolver2 {
1149
1256
  ...population !== undefined ? { population } : {},
1150
1257
  ...oireachtas !== undefined ? { oireachtas } : {},
1151
1258
  ...school !== undefined ? { school } : {},
1152
- ...initials !== undefined ? { initials } : {}
1259
+ ...narrowing !== undefined ? byInitials ? { initials: narrowing } : { fullName: narrowing } : {}
1153
1260
  };
1154
1261
  }
1155
1262
  async readOireachtas(db, o, asOf) {
@@ -1282,6 +1389,22 @@ function youthRow(s, bornLo) {
1282
1389
  const by = adultBornBy(s.year_hi);
1283
1390
  return by !== null && bornLo > by;
1284
1391
  }
1392
+ function otherGiven(db, dancerId, asked) {
1393
+ const r = db.query("SELECT names_printed FROM dancer WHERE dancer_id = ?").get(dancerId);
1394
+ if (r === null)
1395
+ return null;
1396
+ for (const print of JSON.parse(r.names_printed)) {
1397
+ const p = dancerName(print);
1398
+ if (!p.isPerson() || (p.givenFields()[0]?.length ?? 0) < 2)
1399
+ continue;
1400
+ if (nameLevel2(asked, p) !== null)
1401
+ continue;
1402
+ if (nameKinship(asked, p).relation !== "different")
1403
+ continue;
1404
+ return `printed "${print}", another given name than the asked one, and met it only through an initial`;
1405
+ }
1406
+ return null;
1407
+ }
1285
1408
  function birthOf(d) {
1286
1409
  return {
1287
1410
  lo: d.birth_lo,
@@ -1349,12 +1472,12 @@ function regionEvidence(db, dancerId, region, year) {
1349
1472
  return { word: "contradicts", region: at(latest) };
1350
1473
  return { word: latest.region === region ? "same" : "moved", region: at(latest) };
1351
1474
  }
1352
- function ageEvidence(d, asked) {
1475
+ function ageEvidence(d, asked, slack = 0) {
1353
1476
  if (asked === null)
1354
1477
  return "unasked";
1355
1478
  if (d.birth_lo === null && d.birth_hi === null)
1356
1479
  return "unknown";
1357
- const own = Age.fromYOB(d.birth_lo, d.birth_hi);
1480
+ const own = Age.fromYOB(d.birth_lo === null ? null : d.birth_lo - slack, d.birth_hi === null ? null : d.birth_hi + slack);
1358
1481
  if (!own.isAge())
1359
1482
  return "unknown";
1360
1483
  if (own.fitsIn(asked))
@@ -1385,6 +1508,7 @@ function contradictionFact(x, age, slack) {
1385
1508
  }
1386
1509
 
1387
1510
  import { Age as Age2 } from "@finbheara/domain/age";
1511
+ import { createCodeReader } from "@finbheara/domain/code";
1388
1512
  import {
1389
1513
  codePopulation,
1390
1514
  compTitle as compTitle2,
@@ -1490,6 +1614,7 @@ function canonicalOf2(level, age) {
1490
1614
  return null;
1491
1615
  return `${level / 100}${String(n).padStart(2, "0")}${level === 600 ? "PC" : "OC"}`;
1492
1616
  }
1617
+ var CODE_READER = createCodeReader({ standardized: ["quickfeis"] });
1493
1618
  var entryOf = (c) => ({
1494
1619
  title: c.title ?? "",
1495
1620
  ...c.age !== null ? { age: c.age } : {},
@@ -1702,6 +1827,14 @@ class CompResolver2 {
1702
1827
  return answer("none", null, [], {
1703
1828
  why: `the release holds no year of ${place.asked}'s series before ${place.year ?? "?"}`
1704
1829
  });
1830
+ const newestYear = earlier[0];
1831
+ const newestIds = await this.idsOf(place.brand_id, newestYear);
1832
+ const newestComps = await this.compsOf(newestIds);
1833
+ const platform = newestComps[0]?.platform ?? null;
1834
+ if (platform === null || CODE_READER.scheme(platform) === "feis-specific") {
1835
+ const noChamps = newestComps.some((c) => c.kind === "championship") ? "" : `, and the ${newestYear} syllabus, read from results, lists no championship`;
1836
+ return byTitle(newestIds, { feis_ids: newestIds, year: newestYear }, `${platform ?? "the series' platform"} numbers each feis-year's competitions its own way: code ${q.code} is read only in the ${place.year ?? "asked"} syllabus, which the release does not hold${noChamps}`);
1837
+ }
1705
1838
  for (const y of earlier) {
1706
1839
  const ids = await this.idsOf(place.brand_id, y);
1707
1840
  const hits = await this.compsOf(ids, q.code);
@@ -1989,22 +2122,28 @@ var encoderOf = (r) => schoolEncoder({ reader: r.reader });
1989
2122
  var TABLE = "school_name";
1990
2123
  var schoolsIndex = {
1991
2124
  name: "schools",
1992
- version: "2",
2125
+ version: "3",
1993
2126
  inputs: ["results", "school-registry.jsonl", "school-redirects.jsonl"],
1994
2127
  says: "every published school print and its ids, read at the school reader's slots",
1995
2128
  keyParts: (rel) => [`reader=${rel.schoolReader().id}`],
1996
2129
  build(rel, out) {
1997
2130
  const choice = rel.schoolReader();
1998
2131
  const encoder = encoderOf(choice);
1999
- out.run("CREATE TABLE print_id(print TEXT NOT NULL, school_id TEXT NOT NULL, rows INTEGER NOT NULL, PRIMARY KEY(print, school_id)) WITHOUT ROWID");
2132
+ out.run("CREATE TABLE print_id(print TEXT NOT NULL, school_id TEXT NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER, PRIMARY KEY(print, school_id)) WITHOUT ROWID");
2000
2133
  out.run("CREATE TABLE registry(school_id TEXT PRIMARY KEY, display TEXT NOT NULL, regions TEXT NOT NULL, places TEXT NOT NULL)");
2001
2134
  out.run("CREATE TABLE redirect(old_id TEXT PRIMARY KEY, new_id TEXT NOT NULL, kind TEXT NOT NULL)");
2002
2135
  const schema = indexSchema3(encoder, { table: TABLE });
2003
2136
  out.run(schema.table);
2004
- const pairs = rel.db("results").query("SELECT trim(school_printed) AS p, school_id AS s, count(*) AS n FROM results WHERE school_id IS NOT NULL GROUP BY 1, 2 ORDER BY 1, 2").all();
2005
- const putPair = out.prepare("INSERT INTO print_id VALUES (?, ?, ?)");
2137
+ const src = rel.db("results");
2138
+ const lined = src.query("PRAGMA table_info(results)").all().some((c) => c.name === "competition_line");
2139
+ const pairs = src.query(lined ? `SELECT trim(r.school_printed) AS p, r.school_id AS s, count(*) AS n,
2140
+ min(c.year) AS lo, max(c.year) AS hi
2141
+ FROM results r LEFT JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
2142
+ WHERE r.school_id IS NOT NULL GROUP BY 1, 2 ORDER BY 1, 2` : `SELECT trim(school_printed) AS p, school_id AS s, count(*) AS n, NULL AS lo, NULL AS hi
2143
+ FROM results WHERE school_id IS NOT NULL GROUP BY 1, 2 ORDER BY 1, 2`).all();
2144
+ const putPair = out.prepare("INSERT INTO print_id VALUES (?, ?, ?, ?, ?)");
2006
2145
  for (const r of pairs)
2007
- putPair.run(r.p, r.s, r.n);
2146
+ putPair.run(r.p, r.s, r.n, r.lo, r.hi);
2008
2147
  out.run("CREATE INDEX print_id_school ON print_id(school_id)");
2009
2148
  const names = createIndex3(out, encoder, {
2010
2149
  table: TABLE
@@ -2049,6 +2188,23 @@ class SchoolResolver2 {
2049
2188
  };
2050
2189
  const answer = (candidates, extra = {}) => {
2051
2190
  let cs = candidates.sort((a, b) => b.rows - a.rows || (a.school_id < b.school_id ? -1 : 1));
2191
+ let year;
2192
+ if (q.year !== undefined && cs.length > 1 && cs.every((c) => c.years !== undefined)) {
2193
+ const y = q.year;
2194
+ const span = (c) => c.years;
2195
+ let kept = cs.filter((c) => {
2196
+ const { lo, hi } = span(c);
2197
+ return lo !== null && hi !== null && lo <= y && y <= hi;
2198
+ });
2199
+ if (kept.length === 0) {
2200
+ const before = cs.filter((c) => span(c).hi !== null && span(c).hi < y);
2201
+ const last = Math.max(...before.map((c) => span(c).hi));
2202
+ kept = before.filter((c) => span(c).hi === last);
2203
+ }
2204
+ year = { asked: y, narrowed: kept.length > 0 && kept.length < cs.length };
2205
+ if (kept.length > 0)
2206
+ cs = kept;
2207
+ }
2052
2208
  let region;
2053
2209
  if (q.region !== undefined) {
2054
2210
  const kept = cs.filter((c) => c.regions.length === 0 || c.regions.includes(q.region));
@@ -2062,6 +2218,7 @@ class SchoolResolver2 {
2062
2218
  arm: cs.length === 0 ? "none" : cs.length === 1 ? "unique" : "ambiguous",
2063
2219
  candidates: cs,
2064
2220
  ...region !== undefined ? { region } : {},
2221
+ ...year !== undefined ? { year } : {},
2065
2222
  ...extra
2066
2223
  };
2067
2224
  };
@@ -2085,15 +2242,27 @@ class SchoolResolver2 {
2085
2242
  return answer([byId(live, "redirect")], { redirected: { from: a, to: live } });
2086
2243
  const byPrints = (prints, by) => {
2087
2244
  const ids = new Map;
2088
- const q2 = db.query("SELECT school_id, rows FROM print_id WHERE print = ?");
2245
+ const q2 = db.query("SELECT school_id, rows, year_lo AS lo, year_hi AS hi FROM print_id WHERE print = ?");
2089
2246
  for (const p of prints)
2090
2247
  for (const r of q2.all(p)) {
2091
- const e = ids.get(r.school_id) ?? { rows: 0, prints: new Set };
2248
+ const e = ids.get(r.school_id) ?? {
2249
+ rows: 0,
2250
+ prints: new Set,
2251
+ lo: null,
2252
+ hi: null
2253
+ };
2092
2254
  e.rows += r.rows;
2093
2255
  e.prints.add(p);
2256
+ if (r.lo !== null && (e.lo === null || r.lo < e.lo))
2257
+ e.lo = r.lo;
2258
+ if (r.hi !== null && (e.hi === null || r.hi > e.hi))
2259
+ e.hi = r.hi;
2094
2260
  ids.set(r.school_id, e);
2095
2261
  }
2096
- return [...ids].map(([id, e]) => candidate(id, by, e.rows, [...e.prints].sort()));
2262
+ return [...ids].map(([id, e]) => ({
2263
+ ...candidate(id, by, e.rows, [...e.prints].sort()),
2264
+ years: { lo: e.lo, hi: e.hi }
2265
+ }));
2097
2266
  };
2098
2267
  const exact = byPrints([a], "print");
2099
2268
  if (exact.length > 0)
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  nameOrderOf,
5
5
  openRelease2,
6
6
  releases2
7
- } from "./chunks/index-xtv455ft.js";
7
+ } from "./chunks/index-hhhrh1db.js";
8
8
  import"./chunks/index-hne1wadw.js";
9
9
  import"./chunks/index-hghdy980.js";
10
10
 
@@ -81,7 +81,8 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
81
81
  the register's and the panels' people: the same answer
82
82
  comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]
83
83
  a feis's own comp code to the comp it names, through that
84
- feis's syllabus (or the prior year's)
84
+ feis's syllabus (the prior year's only where the platform
85
+ numbers every feis alike; elsewhere the title)
85
86
  syllabus [--release <spec>] <feis> [year]
86
87
  one feis-year's comps, in printed order
87
88
  syllabus [--release <spec>] --for <series> [year]
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ import {
46
46
  openCached2,
47
47
  ReleaseManager2,
48
48
  releases2
49
- } from "./chunks/index-xtv455ft.js";
49
+ } from "./chunks/index-hhhrh1db.js";
50
50
  import {
51
51
  cacheRoot2,
52
52
  repoOf2
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import type { OpenOptions } from "./release.js";
3
3
  import type { StreamQuery } from "./stream.js";
4
- export declare const USAGE = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]\n\n config repo <owner/name> set the user-local repository the releases come from\n list [--remote] the cached releases (size, files, indexes, the pin), and\n with --remote the published tags\n fetch [--release <spec>] [--assets results,majors,<file>...]\n download, verify and cache a release (default: results)\n pin <spec> the user-local pin: what a bare open (spec \"pinned\") opens\n unpin remove the pin (a bare open is then \"latest\")\n current the pin, and whether it is cached\n add <dir> --tag <tag> add a release built locally (its MANIFEST.json and files)\n to the cache under its tag\n index [--release <spec>] [<name>...] [--force]\n build the per-tag indexes (default: every one)\n verify [--release <spec>] re-hash every cached file against its receipt\n capabilities [--release <spec>] what the release carries of what the client reads, from\n its own schema: each feature full or missing (its calls\n refuse, naming the first cut that publishes it)\n prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)\n and the pinned one\n rm <tag> [--dry-run] remove one cached release (not the pinned one)\n drop-indexes [<tag>] [--dry-run] remove the index files (of one release, or of every one)\n feis [--release <spec>] [--platform <p>] [--code <c>] [--id <id>] [--name <glob>] [--year <y>]\n the feises a query names\n series [--release <spec>] <name|host code|feis id>\n the series a name, a host code (fweb:ABC) or any feis id\n names: its years, hosts, codes and next instance\n school [--release <spec>] <print|school_id> [--region <code>]\n a school print or id to the published schools\n dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>\n [--order surname-first|given-first] [--school <print|id>]\n [--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]\n [--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]\n the dancers a name reaches, each with its evidence\n (name level, age, school, population, region) and the arm;\n --oireachtas is the regional the roster is for, its region in\n the release's spelling (SRO, MAO), its year the --as-of year\n where none is given; the results of\n the one dancer where one answers (or --rows for each)\n results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]\n [--school <print|id>] [--year <y>] [--limit <n>]\n the published result rows, prints and ids\n majors [--release <spec>] [--event <e>] [--year <y>] [--comp <competition>] [--dancer <id>]\n the majors: every event and its years; an event's\n competitions; a competition's, an event's or a dancer's rows\n stream [--release <spec>] --scope majors|transcribed|platform|all [--entries] [--event <e,...>]\n [--year <y> | --year <from>..<to>] [--tier <t,...>] [--category <c,...>]\n [--level <feis_level,...>] [--dancer <id,...>] [--school <school_id>] [--primary]\n every row a scope holds, one JSON line each, never the\n whole table in memory; --entries one per address (the row\n it stands for, with its recall order)\n records [--release <spec>] [--dancer <id,...>]\n every dancer's record: display, appearances, birth window,\n populations\n records --years [--release <spec>] [--dancer <id,...>] [--year <y>]\n each dancer's region, nationals and place per year\n feis-dates [--release <spec>] every feis's first and last day\n tabulated [--release <spec>] [--min-round <n>]\n the majors addresses the marks sheets tabulate to round n\n or later (default 3)\n teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]\n the team result rows of an event-year; with --rosters its\n roster teams and their dancers; with --joins how each roster\n team meets the results\n events [--release <spec>] [--event <e>] [--year <y>]\n each event-year's dates and place\n teacher [--release <spec>] <name|TD number>\n the staff lists' people: where they taught, the register,\n where they sat\n adjudicator [--release <spec>] <name|TD number>\n the register's and the panels' people: the same answer\n comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]\n a feis's own comp code to the comp it names, through that\n feis's syllabus (or the prior year's)\n syllabus [--release <spec>] <feis> [year]\n one feis-year's comps, in printed order\n syllabus [--release <spec>] --for <series> [year]\n a series' syllabus for a year: that year's where held,\n else the newest, saying which and what it is read from\n resolve comp [<feis> <code>] [--year <y>] [--title <t>]\n resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]\n [--order <o>] [--as-of <date>] [--population girls|boys]\n [--oireachtas <region>[:<year>]]\n resolve registrations\n JSONL out, one answer per line; with no positional the\n questions are JSONL on stdin, one per line (a comp:\n {feis, code, year?, title?}, a dancer: {name, school?, age?,\n population?, oireachtas?}, a registration: {name, school?,\n codes[], feis, year?, population?, oireachtas?})\n\n <spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)";
4
+ export declare const USAGE = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]\n\n config repo <owner/name> set the user-local repository the releases come from\n list [--remote] the cached releases (size, files, indexes, the pin), and\n with --remote the published tags\n fetch [--release <spec>] [--assets results,majors,<file>...]\n download, verify and cache a release (default: results)\n pin <spec> the user-local pin: what a bare open (spec \"pinned\") opens\n unpin remove the pin (a bare open is then \"latest\")\n current the pin, and whether it is cached\n add <dir> --tag <tag> add a release built locally (its MANIFEST.json and files)\n to the cache under its tag\n index [--release <spec>] [<name>...] [--force]\n build the per-tag indexes (default: every one)\n verify [--release <spec>] re-hash every cached file against its receipt\n capabilities [--release <spec>] what the release carries of what the client reads, from\n its own schema: each feature full or missing (its calls\n refuse, naming the first cut that publishes it)\n prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)\n and the pinned one\n rm <tag> [--dry-run] remove one cached release (not the pinned one)\n drop-indexes [<tag>] [--dry-run] remove the index files (of one release, or of every one)\n feis [--release <spec>] [--platform <p>] [--code <c>] [--id <id>] [--name <glob>] [--year <y>]\n the feises a query names\n series [--release <spec>] <name|host code|feis id>\n the series a name, a host code (fweb:ABC) or any feis id\n names: its years, hosts, codes and next instance\n school [--release <spec>] <print|school_id> [--region <code>]\n a school print or id to the published schools\n dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>\n [--order surname-first|given-first] [--school <print|id>]\n [--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]\n [--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]\n the dancers a name reaches, each with its evidence\n (name level, age, school, population, region) and the arm;\n --oireachtas is the regional the roster is for, its region in\n the release's spelling (SRO, MAO), its year the --as-of year\n where none is given; the results of\n the one dancer where one answers (or --rows for each)\n results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]\n [--school <print|id>] [--year <y>] [--limit <n>]\n the published result rows, prints and ids\n majors [--release <spec>] [--event <e>] [--year <y>] [--comp <competition>] [--dancer <id>]\n the majors: every event and its years; an event's\n competitions; a competition's, an event's or a dancer's rows\n stream [--release <spec>] --scope majors|transcribed|platform|all [--entries] [--event <e,...>]\n [--year <y> | --year <from>..<to>] [--tier <t,...>] [--category <c,...>]\n [--level <feis_level,...>] [--dancer <id,...>] [--school <school_id>] [--primary]\n every row a scope holds, one JSON line each, never the\n whole table in memory; --entries one per address (the row\n it stands for, with its recall order)\n records [--release <spec>] [--dancer <id,...>]\n every dancer's record: display, appearances, birth window,\n populations\n records --years [--release <spec>] [--dancer <id,...>] [--year <y>]\n each dancer's region, nationals and place per year\n feis-dates [--release <spec>] every feis's first and last day\n tabulated [--release <spec>] [--min-round <n>]\n the majors addresses the marks sheets tabulate to round n\n or later (default 3)\n teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]\n the team result rows of an event-year; with --rosters its\n roster teams and their dancers; with --joins how each roster\n team meets the results\n events [--release <spec>] [--event <e>] [--year <y>]\n each event-year's dates and place\n teacher [--release <spec>] <name|TD number>\n the staff lists' people: where they taught, the register,\n where they sat\n adjudicator [--release <spec>] <name|TD number>\n the register's and the panels' people: the same answer\n comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]\n a feis's own comp code to the comp it names, through that\n feis's syllabus (the prior year's only where the platform\n numbers every feis alike; elsewhere the title)\n syllabus [--release <spec>] <feis> [year]\n one feis-year's comps, in printed order\n syllabus [--release <spec>] --for <series> [year]\n a series' syllabus for a year: that year's where held,\n else the newest, saying which and what it is read from\n resolve comp [<feis> <code>] [--year <y>] [--title <t>]\n resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]\n [--order <o>] [--as-of <date>] [--population girls|boys]\n [--oireachtas <region>[:<year>]]\n resolve registrations\n JSONL out, one answer per line; with no positional the\n questions are JSONL on stdin, one per line (a comp:\n {feis, code, year?, title?}, a dancer: {name, school?, age?,\n population?, oireachtas?}, a registration: {name, school?,\n codes[], feis, year?, population?, oireachtas?})\n\n <spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)";
5
5
  export interface Parsed {
6
6
  positional: string[];
7
7
  flags: Map<string, string>;
@@ -121,6 +121,7 @@ interface CompRow {
121
121
  }
122
122
  export declare function compOf(r: CompRow, codes?: readonly string[]): Comp;
123
123
  export declare function canonicalOf(level: number | null, age: string | null): string | null;
124
+ export declare const CODE_READER: import("@finbheara/domain/code").CodeReader;
124
125
  export declare class CompResolver {
125
126
  private readonly rel;
126
127
  constructor(rel: Release);
@@ -1,5 +1,5 @@
1
1
  import { type PersonName, type TransitiveLevel } from "@finbheara/names/normalize";
2
- export type NameLevel = TransitiveLevel | "initial" | "middle" | "surname" | "like";
2
+ export type NameLevel = TransitiveLevel | "initial" | "middle" | "form" | "surname" | "like";
3
3
  export declare const NAME_LEVELS: readonly NameLevel[];
4
4
  export declare const finer: (a: NameLevel, b: NameLevel) => NameLevel;
5
5
  export declare function nameLevel(q: PersonName, c: PersonName): NameLevel | null;
@@ -52,7 +52,9 @@ export interface DancerCandidate {
52
52
  school: SchoolEvidence;
53
53
  population: PopulationEvidence;
54
54
  region: RegionEvidence;
55
+ form?: string[];
55
56
  };
57
+ widened?: "birth-conflict";
56
58
  prints: {
57
59
  print: string;
58
60
  rows: number;
@@ -82,13 +84,19 @@ export interface DancerRegion {
82
84
  }
83
85
  export interface ExcludedCandidate {
84
86
  candidate: DancerCandidate;
85
- why: "age" | "population" | "region" | "school";
87
+ why: "age" | "population" | "region" | "school" | "given" | "name-form";
86
88
  }
87
89
  export interface InitialsRefuted {
88
90
  dancer_id: number;
89
- why: "school" | "age" | "population" | "region";
91
+ why: "school" | "age" | "population" | "region" | "given" | "name-form";
90
92
  fact: string;
91
93
  }
94
+ export interface Narrowing {
95
+ open: number[];
96
+ heldByDanceUp: number[];
97
+ refuted: InitialsRefuted[];
98
+ year: number;
99
+ }
92
100
  export interface DancerAnswer {
93
101
  tag: string;
94
102
  asked: DancerQuery;
@@ -128,12 +136,9 @@ export interface DancerAnswer {
128
136
  lineage?: string[];
129
137
  };
130
138
  refused?: string;
131
- initials?: {
132
- open: number[];
133
- heldByDanceUp: number[];
134
- refuted: InitialsRefuted[];
135
- year: number;
136
- };
139
+ initials?: Narrowing;
140
+ fullName?: Narrowing;
141
+ widened?: "birth-conflict" | "name-form";
137
142
  }
138
143
  export interface DancerSearchQuery extends Omit<DancerQuery, "name"> {
139
144
  name?: string;
@@ -216,6 +221,7 @@ export declare class DancerResolver {
216
221
  years: DancerYear[];
217
222
  }>;
218
223
  resolve(q: DancerQuery): Promise<DancerAnswer>;
224
+ private formsOf;
219
225
  search(q: DancerSearchQuery): Promise<DancerAnswer>;
220
226
  private capped;
221
227
  get(dancerId: number): Promise<DancerRecord | null>;
@@ -1,6 +1,6 @@
1
1
  export { type AssetPlan, DATABASES, type DatabaseName, type LandedFile, type Receipt, type ReleaseManifest, planAsset, } from "./assets.js";
2
2
  export { sha256File, unzstdTo } from "./bytes.js";
3
- export { type AgeAsked, type AgeEvidence, type DancerAnswer, type DancerBulkRecord, type DancerCandidate, type DancerQuery, type DancerRecord, DancerResolver, type DancerRegion, type DancerSchool, type DancerSearchQuery, type DancerYear, type ExcludedCandidate, FEIS_LOCAL_ID_FLOOR, type InitialsRefuted, initialsOnly, type OireachtasAsked, type PopulationEvidence, type RegionEvidence, type SchoolEvidence, readPopulation, } from "./dancers.js";
3
+ export { type AgeAsked, type AgeEvidence, type DancerAnswer, type DancerBulkRecord, type DancerCandidate, type DancerQuery, type DancerRecord, DancerResolver, type DancerRegion, type DancerSchool, type DancerSearchQuery, type DancerYear, type ExcludedCandidate, FEIS_LOCAL_ID_FLOOR, type InitialsRefuted, type Narrowing, initialsOnly, type OireachtasAsked, type PopulationEvidence, type RegionEvidence, type SchoolEvidence, readPopulation, } from "./dancers.js";
4
4
  export { NAME_LEVELS, type NameLevel, nameLevel } from "./dancers-names.js";
5
5
  export { type Comp, type CompAnswer, type CompDance, type CompKind, type CompQuery, CompResolver, type CompSource, type FeisPlace, type SyllabusAnswer, type SyllabusForAnswer, type SyllabusSource, type TitleCheck, canonicalOf, } from "./comps.js";
6
6
  export { type ClientOptions, cacheRoot, repoOf } from "./config.js";
@@ -10,6 +10,10 @@ export interface SchoolCandidate {
10
10
  prints: string[];
11
11
  regions: string[];
12
12
  places: string[];
13
+ years?: {
14
+ lo: number | null;
15
+ hi: number | null;
16
+ };
13
17
  }
14
18
  export interface SchoolAnswer {
15
19
  tag: string;
@@ -20,6 +24,10 @@ export interface SchoolAnswer {
20
24
  asked: string;
21
25
  narrowed: boolean;
22
26
  };
27
+ year?: {
28
+ asked: number;
29
+ narrowed: boolean;
30
+ };
23
31
  redirected?: {
24
32
  from: string;
25
33
  to: string;
@@ -28,6 +36,7 @@ export interface SchoolAnswer {
28
36
  }
29
37
  export interface SchoolQuery {
30
38
  region?: string;
39
+ year?: number;
31
40
  }
32
41
  export interface SchoolReaderChoice {
33
42
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
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,7 +44,7 @@
44
44
  "prepublishOnly": "bun scripts/build.ts && bun ../../tools/checks/archive-client-tarball.ts"
45
45
  },
46
46
  "dependencies": {
47
- "@finbheara/domain": "0.50.0",
47
+ "@finbheara/domain": "0.53.0",
48
48
  "@finbheara/names": "0.25.0",
49
49
  "hyparquet": "1.31.2"
50
50
  },