@finbheara/archive-client 0.8.0 → 0.10.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
@@ -216,11 +216,13 @@ const r = await rel.dancers.resolve({
216
216
  age: { band: "U10", year: 2026 }, // or { yob: 2016 }, or { comp: "<competition>" }
217
217
  asOf: "2026-10-17", // the school as of then
218
218
  population: "girls", // or "boys": only a contradiction excludes
219
+ oireachtas: { region: "SRO", year: 2026 }, // the regional the roster is for (below)
219
220
  });
220
221
  r.arm; // "unique" | "ambiguous" | "none"
221
- r.candidates; // [{ dancer_id, display, scope, score, evidence: { nameLevel, age, school }, prints, ... }]
222
- r.excluded; // the candidates a contradiction set aside (why: "age" | "population")
223
- r.preferred; // NON-BINDING: { dancer_id, why } where the school singles one out; never moves arm
222
+ r.candidates; // [{ dancer_id, display, scope, score, evidence: { nameLevel, age, school, population, region }, prints, ... }]
223
+ r.excluded; // the candidates a contradiction set aside (why: "age" | "population" | "region" | "school")
224
+ r.preferred; // NON-BINDING: { dancer_id, why } where the school (else the region, else recent rows) singles one out
225
+ r.initials; // an initials-only name with a school: { open, heldByDanceUp, refuted: [{ dancer_id, why, fact }], year }
224
226
  r.unlinked; // rows printing a matched name that the release linked to no dancer
225
227
 
226
228
  await rel.dancers.search({ surname: "Quellan" }); // or { like: "a* quellan" }, or { name }
@@ -270,6 +272,79 @@ for a caller to weigh, and `arm` stays as counted. A `lineage` match ranks with
270
272
  each singles out neither. The `score` orders candidates and is no
271
273
  probability.
272
274
 
275
+ AN INITIALS-ONLY NAME (`Butler, A`, every given field one letter) with a school the release
276
+ resolves is narrowed by the evidence the question itself carries, where more than one candidate
277
+ is left. A candidate is REFUTED only by a hard fact (`r.initials.refuted`, each with its `fact` in
278
+ words):
279
+
280
+ - `school`: every school row of the dancer up to the asked year reads to a school id (an id
281
+ followed through the redirects, a print read by `rel.schools`), and none of them is the asked
282
+ school or a school its line had joined by that year (lineage edges dated in or before the asked
283
+ year). A dancer with no school row, or a print no school reads, is unknown and never refuted. A
284
+ dancer that ever danced for the asked school's line is never refuted by the school.
285
+ - `age`: the birth window misses the asked band even with one band's dance-up (`DANCE_UP_YEARS`
286
+ of `@finbheara/domain/age`: a dancer may enter the band one year older than the dancer's own). A
287
+ candidate the age set aside that the dance-up holds stays open (`r.initials.heldByDanceUp`).
288
+ - `population`, `region`: the contradictions above.
289
+
290
+ `r.initials.open` is every candidate no hard fact refutes. The arm is `unique` ONLY where exactly
291
+ one candidate is open and the age did not set it aside: the candidates the school refuted then
292
+ move to `excluded` (`why: "school"`). Otherwise the arm stays `ambiguous` with every candidate,
293
+ and where the school and the region single out no one, `preferred` names the only open candidate
294
+ with a row in the asked year or the year before. A full given name is never narrowed this way: two
295
+ full given names that differ are two names, and the school only orders them.
296
+
297
+ THE REGIONAL OIREACHTAS (`oireachtas: { region, year? }`). A dancer enters one
298
+ residence-qualifying regional Oireachtas a year. Two regionals in one year are two dancers, the
299
+ overseas entry excepted. The release already applies that rule to each dancer-year it publishes:
300
+ `dancer_years.region` with `region_source = "current"` is the one regional the dancer attended that
301
+ year, the dancer's own overseas entries left out and a year with two regionals given no `current`
302
+ region. Where a year has no regional, an Australian or New Zealand national stands in. The client
303
+ reads those rows and nothing else (`rel.dancers.years` serves them all).
304
+
305
+ - `region` is the release's own spelling, the codes `dancer_years.region` prints (`SRO`, `MAO`,
306
+ `ENG_SOUTH`, ...), case folded. A region the release reads no attended year in narrows and orders
307
+ nothing, and `r.oireachtas.note` says so.
308
+ - `year` is the regional's own calendar year, `asOf`'s year where none is given. The calendar year
309
+ is the only unit (an age is an age on 1 January of the competition's year, as
310
+ `@finbheara/domain/age` reads it), so a regional held in December 2026 is `year: 2026`, read
311
+ against `dancer_years.year` as it is, never shifted by month.
312
+ - THE DENOMINATOR is the candidate's attended (`current`) dancer-years at or before the asked
313
+ year, counted on the candidate (`region.attended`). Later years are not read. A `carried` or
314
+ `carried-back` row is not read: it repeats an attended year, which is read directly. A `school`
315
+ or `cocompetitor` row is not read: it is a vote of other dancers' regions, a prior for a
316
+ dancer-year with no card of its own, not evidence about this one.
317
+ - A `current` year may rest on a registration alone: a no-show still says where the dancer lives.
318
+ So it decides the region, and never alone a contradiction.
319
+ - A DANCED REGIONAL is a row at a `T3` competition that year whose address is on the floor. The
320
+ release publishes no residence flag, and on the event roster every `T3` event is a residence
321
+ qualifier and every residence qualifier is `T3`, so an attended year with a `T3` row was read from
322
+ a regional, and one without from a national standing in for one. ON THE FLOOR is the release's
323
+ own absence rule, read from the row's `place_printed`, `atts` and `withdrawn`: an address with a
324
+ printed absence (`NS`, `DNS`, `DNA`, `No Show`, `Did Not Attend`, `Did Not Compete`, in the place
325
+ cell or as an `atts` token) or a DNT (`withdrawn` `DNT`, or `did-not-start` in older releases) on
326
+ any of its rows danced nothing there. A bare `NP` or `NR` is a dancer placed nowhere, on the
327
+ floor.
328
+
329
+ `evidence.region` is one of:
330
+
331
+ | word | counts | weight |
332
+ |---|---|---|
333
+ | `contradicts` | the asked year is attended in another region, and the dancer danced a regional that year: set aside (`why: "region"`) | excluded |
334
+ | `same` | the latest attended year's region is the asked one | 2 where that year is the asked year, 1 where earlier |
335
+ | `moved` | the latest attended year's region is another one (an earlier year, or a national in the asked year) | 0 |
336
+ | `unknown` | no attended year at or before the asked year | 0 |
337
+ | `unasked` | no `oireachtas` asked | 0 |
338
+
339
+ `region` on a candidate is the deciding dancer-year (`{ year, region, regional, attended }`): the
340
+ asked year where it contradicts, else the latest attended one. `moved` weighs nothing: dancers
341
+ move, and no rate at which dancers move is measured, so it is said and never scored. Candidates
342
+ sort by the school first, then `same` before every other region word, then `score`. Where the
343
+ school singles out no one, `preferred` names the only candidate of the school's first rank (every
344
+ candidate where no school is asked) whose region is `same`. The asked entry's own printed location
345
+ is not asked, so an overseas entrant is asked without `oireachtas`. A release that publishes no
346
+ dancer-years (feature `dancers.years`) reads every candidate `unknown`, with the note.
347
+
273
348
  A FEIS-LOCAL ID (`FEIS_LOCAL_ID_FLOOR`, 2^34, and up) is valid in its release only: a candidate and a result row
274
349
  carrying one say `scope: "per-release"` (`dancer_scope` on a row), so a caller never stores one
275
350
  across releases. Every answer carries the release tag.
@@ -485,7 +560,8 @@ feis's championships), the band is read from the row's own titles (`titles`, by
485
560
  domain package's title reader, the youngest again, and `age.from` says `title`. Nothing else
486
561
  stands in for a band. The dancer resolver is the release's own (`rel.dancers`, above), or the
487
562
  `dancerResolver` option. With `dancerResolver: null`, each row's dancer answers `unavailable`
488
- and the competitions still resolve.
563
+ and the competitions still resolve. A row's `oireachtas`, where the list is a regional's roster,
564
+ is passed to the dancer resolver as asked (`rel.dancers`, above).
489
565
 
490
566
  ## The command line
491
567
 
@@ -506,7 +582,7 @@ and the competitions still resolve.
506
582
  idq dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>
507
583
  [--order surname-first|given-first] [--school <print|id>]
508
584
  [--yob <y> | --band <band> [--year <y>] | --comp <competition>] [--as-of <date>]
509
- [--population girls|boys] [--rows]
585
+ [--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]
510
586
  idq results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <c>] [--school <s>] [--year <y>] [--limit <n>]
511
587
  idq majors [--release <spec>] [--event <e>] [--year <y>] [--comp <c>] [--dancer <id>] [--rows]
512
588
  idq stream [--release <spec>] --scope majors|transcribed|platform|all [--entries] [--event <e,...>]
@@ -525,6 +601,7 @@ and the competitions still resolve.
525
601
  idq syllabus [--release <spec>] --for <series> [year]
526
602
  idq resolve comp [<feis> <code>] [--year y] [--title t]
527
603
  idq resolve dancer [<name>] [--school s] [--comp <feis>/<code> | --yob y | --band b --year y]
604
+ [--oireachtas <region>[:<year>]]
528
605
  idq resolve registrations
529
606
 
530
607
  `stream` prints one JSON line per row (or entry), as it reads them. `dancer` prints the results of the one dancer where the answer is unique (every candidate's with
@@ -532,7 +609,8 @@ and the competitions still resolve.
532
609
 
533
610
  `resolve` speaks JSON lines: one answer per line out, and with no positional question the
534
611
  questions are JSON lines on stdin, one per line (`{feis, code, year?, title?}`,
535
- `{name, school?, age?, population?}`, `{name, school?, codes, feis, year?, population?}`). A line that is no JSON object,
612
+ `{name, school?, age?, population?, oireachtas?}`,
613
+ `{name, school?, codes, feis, year?, population?, oireachtas?}`). A line that is no JSON object,
536
614
  or no question of its kind, answers `{tag, line, error}` and the other lines still answer. The
537
615
  exit is non-zero where any line erred. A dancer's age may be given as a comp
538
616
  (`{"comp": "<feis>/<code>"}`, or `--comp` on the command line): `rel.registrations.dancer` reads
@@ -67,7 +67,7 @@ function sha256OfDigest(digest) {
67
67
  return m === null ? null : m[1].toLowerCase();
68
68
  }
69
69
 
70
- import { Age } from "@finbheara/domain/age";
70
+ import { Age, DANCE_UP_YEARS, birthYearsMeet } from "@finbheara/domain/age";
71
71
  import { createIndex, indexSchema, personEncoder } from "@finbheara/domain/noun-index";
72
72
  import domainPkg from "@finbheara/domain/package.json";
73
73
  import { dancerName } from "@finbheara/domain/person";
@@ -315,6 +315,30 @@ class FeisFinder2 {
315
315
  }
316
316
  }
317
317
 
318
+ var NO_PLACE = new Set(["NP", "NR", "N/A", "NA", "NO RANK", "NORANK", "NO PLACE", "-", "--"]);
319
+ var ABSENT = new Set(["DID NOT COMPETE", "DID NOT ATTEND", "NO SHOW", "DNA", "DNS", "NS"]);
320
+ var ABSENT_WORDS = /not\s*attend|no\s*show/i;
321
+ var DID_NOT_TABULATE = new Set(["DNT", "did-not-start"]);
322
+ function printsAbsence(print) {
323
+ const s = print.trim();
324
+ if (s === "")
325
+ return false;
326
+ const upper = s.toUpperCase();
327
+ if (NO_PLACE.has(upper))
328
+ return false;
329
+ return ABSENT.has(upper) || ABSENT_WORDS.test(s);
330
+ }
331
+ function notOnTheFloor(placePrinted, atts, withdrawn) {
332
+ if (printsAbsence(placePrinted ?? ""))
333
+ return true;
334
+ if (atts !== null && atts !== "") {
335
+ for (const a of atts.split(","))
336
+ if (printsAbsence(a))
337
+ return true;
338
+ }
339
+ return withdrawn !== null && DID_NOT_TABULATE.has(withdrawn);
340
+ }
341
+
318
342
  var ENTRY_RANK = new Map([
319
343
  ["final", 0],
320
344
  ["", 1],
@@ -585,6 +609,21 @@ class MajorsFinder2 {
585
609
  }
586
610
 
587
611
  var LINEAGE_FILE = "school-lineage.jsonl";
612
+ function schoolLine(lineage, ids, year) {
613
+ const reached = new Set(ids);
614
+ const todo = [...ids];
615
+ for (let id = todo.pop();id !== undefined; id = todo.pop())
616
+ for (const [o, y] of lineage.get(id) ?? [])
617
+ if (!reached.has(o) && (year === null || y === null || y <= year)) {
618
+ reached.add(o);
619
+ todo.push(o);
620
+ }
621
+ return reached;
622
+ }
623
+ function initialsOnly2(name) {
624
+ const g = name.givenFields();
625
+ return g.length > 0 && g.every((f) => f.length === 1);
626
+ }
588
627
  function nameOrderOf(s) {
589
628
  return s === "surname-first" || s === "given-first" || s === "unknown" ? s : undefined;
590
629
  }
@@ -597,7 +636,7 @@ var PERSON = { table: "person", value: "id", valueType: "INTEGER PRIMARY KEY" };
597
636
  var ASKED = ["surname_key", "nn4", "blk_surname"];
598
637
  var dancersIndex = {
599
638
  name: "dancers",
600
- version: "2",
639
+ version: "4",
601
640
  inputs: ["results"],
602
641
  keyParts: () => [...PACKAGE_KEY_PARTS],
603
642
  says: "every published dancer print at the names package's keys, its dancers, schools and years",
@@ -608,6 +647,7 @@ var dancersIndex = {
608
647
  out.run("CREATE TABLE dancer(dancer_id INTEGER PRIMARY KEY, display TEXT NOT NULL, names_printed TEXT NOT NULL, genders TEXT NOT NULL, appearances INTEGER NOT NULL, kind TEXT, birth_lo INTEGER, birth_hi INTEGER, birth_source TEXT NOT NULL, birth_conflict INTEGER NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER)");
609
648
  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)");
610
649
  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");
650
+ 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");
611
651
  const src = rel.db("results");
612
652
  const groups = src.query(`SELECT r.name_printed AS p, r.name_order_resolved AS o, r.dancer_id AS d,
613
653
  r.school_printed AS sp, r.school_id AS s,
@@ -682,6 +722,25 @@ var dancersIndex = {
682
722
  if (pop === "girls" || pop === "boys")
683
723
  putPop.run(r.d, pop, r.n);
684
724
  }
725
+ if (src.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'dancer_years'").get() !== null) {
726
+ const cols = new Set(src.query("PRAGMA table_info(results)").all().map((c) => c.name));
727
+ const col = (c) => cols.has(c) ? `r.${c}` : "NULL";
728
+ const rows = src.query(`SELECT r.dancer_id AS d, c.year AS y, r.address AS a, ${col("place_printed")} AS p,
729
+ ${col("atts")} AS t, ${col("withdrawn")} AS w
730
+ FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
731
+ WHERE r.dancer_id IS NOT NULL AND c.tier = 'T3' AND c.year IS NOT NULL`).all();
732
+ const absent = new Set;
733
+ for (const r of rows)
734
+ if (notOnTheFloor(r.p, r.t, r.w))
735
+ absent.add(r.a);
736
+ const t3 = new Set;
737
+ for (const r of rows)
738
+ if (!absent.has(r.a))
739
+ t3.add(`${r.d}\x00${r.y}`);
740
+ const putReg = out.prepare("INSERT INTO dancer_regional VALUES (?, ?, ?, ?)");
741
+ for (const r of src.query("SELECT dancer_id AS d, year AS y, region AS r FROM dancer_years WHERE region_source = 'current' AND region IS NOT NULL").all())
742
+ putReg.run(r.d, r.y, r.r, t3.has(`${r.d}\x00${r.y}`) ? 1 : 0);
743
+ }
685
744
  for (const s of schema.indexes)
686
745
  if (ASKED.some((c) => s.endsWith(`(${c})`)))
687
746
  out.run(s);
@@ -734,6 +793,21 @@ var POPULATION_WEIGHT = {
734
793
  unasked: 0,
735
794
  contradicts: -10
736
795
  };
796
+ var REGION_WEIGHT = {
797
+ same: 1,
798
+ unknown: 0,
799
+ unasked: 0,
800
+ moved: 0,
801
+ contradicts: -10
802
+ };
803
+ var REGION_SAME_THAT_YEAR = 2;
804
+ var REGION_RANK = {
805
+ same: 0,
806
+ unknown: 1,
807
+ unasked: 1,
808
+ moved: 1,
809
+ contradicts: 2
810
+ };
737
811
  var SCHOOL_WEIGHT = {
738
812
  same: 2,
739
813
  lineage: 2,
@@ -752,7 +826,9 @@ function idChunks(ids) {
752
826
  class DancerResolver2 {
753
827
  rel;
754
828
  printIds = new Map;
829
+ rowIds = new Map;
755
830
  lineageOf;
831
+ regions;
756
832
  constructor(rel) {
757
833
  this.rel = rel;
758
834
  }
@@ -762,15 +838,17 @@ class DancerResolver2 {
762
838
  return null;
763
839
  await this.rel.fetch(LINEAGE_FILE);
764
840
  const out = new Map;
765
- const join = (a, b) => {
766
- const s = out.get(a) ?? new Set;
767
- s.add(b);
768
- out.set(a, s);
841
+ const join = (a, b, year) => {
842
+ const m = out.get(a) ?? new Map;
843
+ const was = m.get(b);
844
+ m.set(b, was === undefined ? year : was === null || year === null ? null : Math.min(was, year));
845
+ out.set(a, m);
769
846
  };
770
847
  for (const r of readJsonl(this.rel.path(LINEAGE_FILE)))
771
848
  if (r.id !== r.other) {
772
- join(r.id, r.other);
773
- join(r.other, r.id);
849
+ const year = typeof r.year === "number" ? r.year : null;
850
+ join(r.id, r.other, year);
851
+ join(r.other, r.id, year);
774
852
  }
775
853
  return out;
776
854
  })();
@@ -880,6 +958,7 @@ class DancerResolver2 {
880
958
  const asOf = yearOf(q.asOf);
881
959
  const age = q.age === undefined ? undefined : this.readAge(q.age, asOf);
882
960
  const population = q.population === undefined ? undefined : readPopulation2(q.population);
961
+ const oireachtas = q.oireachtas === undefined ? undefined : await this.readOireachtas(db, q.oireachtas, asOf);
883
962
  let schoolIds = null;
884
963
  let lineageIds = [];
885
964
  let school;
@@ -891,15 +970,7 @@ class DancerResolver2 {
891
970
  school = { arm: s.arm, ids: schoolIds };
892
971
  else {
893
972
  const asked = new Set(schoolIds);
894
- const reached = new Set(schoolIds);
895
- const todo = [...schoolIds];
896
- for (let id = todo.pop();id !== undefined; id = todo.pop())
897
- for (const o of lineage.get(id) ?? [])
898
- if (!reached.has(o)) {
899
- reached.add(o);
900
- todo.push(o);
901
- }
902
- lineageIds = [...reached].filter((id) => !asked.has(id)).sort();
973
+ lineageIds = [...schoolLine(lineage, schoolIds, null)].filter((id) => !asked.has(id)).sort();
903
974
  school = { arm: s.arm, ids: schoolIds, lineage: lineageIds };
904
975
  }
905
976
  }
@@ -929,23 +1000,36 @@ class DancerResolver2 {
929
1000
  const pop = populationOf(db, id);
930
1001
  const popEv = populationEvidence(pop, population?.read ?? null);
931
1002
  const schoolEv = schoolIds === null ? "unasked" : schoolIds.length === 0 ? "unresolved" : await this.schoolEvidence(schools, schoolIds, lineageIds, asOf);
1003
+ const reg = oireachtas === undefined ? null : oireachtas.read === null ? {
1004
+ word: "unknown",
1005
+ region: { year: null, region: null, regional: false, attended: 0 }
1006
+ } : regionEvidence(db, id, oireachtas.read, oireachtas.year);
1007
+ const regionEv = reg === null ? "unasked" : reg.word;
1008
+ const regionWeight = regionEv === "same" && reg?.region.year === oireachtas?.year ? REGION_SAME_THAT_YEAR : REGION_WEIGHT[regionEv];
932
1009
  all.push({
933
1010
  dancer_id: id,
934
1011
  display: d.display,
935
1012
  scope: id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable",
936
1013
  kind: d.kind,
937
- score: NAME_WEIGHT[e.level] + AGE_WEIGHT[ageEv] + SCHOOL_WEIGHT[schoolEv] + POPULATION_WEIGHT[popEv],
938
- evidence: { nameLevel: e.level, age: ageEv, school: schoolEv, population: popEv },
1014
+ score: NAME_WEIGHT[e.level] + AGE_WEIGHT[ageEv] + SCHOOL_WEIGHT[schoolEv] + POPULATION_WEIGHT[popEv] + regionWeight,
1015
+ evidence: {
1016
+ nameLevel: e.level,
1017
+ age: ageEv,
1018
+ school: schoolEv,
1019
+ population: popEv,
1020
+ region: regionEv
1021
+ },
939
1022
  prints: e.prints.sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
940
1023
  rows: d.rows,
941
1024
  year_lo: d.year_lo,
942
1025
  year_hi: d.year_hi,
943
1026
  birth: birthOf(d),
944
1027
  population: pop,
945
- school: latest
1028
+ school: latest,
1029
+ ...reg !== null ? { region: reg.region } : {}
946
1030
  });
947
1031
  }
948
- all.sort((a, b) => SCHOOL_RANK[a.evidence.school] - SCHOOL_RANK[b.evidence.school] || b.score - a.score || b.rows - a.rows || a.dancer_id - b.dancer_id);
1032
+ all.sort((a, b) => SCHOOL_RANK[a.evidence.school] - SCHOOL_RANK[b.evidence.school] || REGION_RANK[a.evidence.region] - REGION_RANK[b.evidence.region] || b.score - a.score || b.rows - a.rows || a.dancer_id - b.dancer_id);
949
1033
  const excluded = [];
950
1034
  let kept = all;
951
1035
  if (narrow)
@@ -954,10 +1038,61 @@ class DancerResolver2 {
954
1038
  excluded.push({ candidate: c, why: "age" });
955
1039
  else if (c.evidence.population === "contradicts")
956
1040
  excluded.push({ candidate: c, why: "population" });
1041
+ else if (c.evidence.region === "contradicts")
1042
+ excluded.push({ candidate: c, why: "region" });
957
1043
  else
958
1044
  return true;
959
1045
  return false;
960
1046
  });
1047
+ const year = askedYear(q, asOf);
1048
+ let initials;
1049
+ if (narrow && kept.length > 1 && schoolIds !== null && schoolIds.length > 0 && initialsOnly2(dancerName(q.name, q.order === "unknown" ? undefined : q.order))) {
1050
+ const lineage = await this.lineage();
1051
+ const line = lineage === null ? new Set(schoolIds) : schoolLine(lineage, schoolIds, year);
1052
+ const band = age?.age?.birthYears() ?? null;
1053
+ const slack = q.age !== undefined && !("yob" in q.age) ? DANCE_UP_YEARS : 0;
1054
+ const open = [];
1055
+ const held = [];
1056
+ const refuted = [];
1057
+ const bySchool = new Set;
1058
+ const schoolFact = async (c) => {
1059
+ const fact = await this.schoolRefutes(schoolsOf(db, c.dancer_id), line, year, q.school);
1060
+ if (fact === null)
1061
+ return false;
1062
+ refuted.push({ dancer_id: c.dancer_id, why: "school", fact });
1063
+ bySchool.add(c.dancer_id);
1064
+ return true;
1065
+ };
1066
+ for (const c of kept)
1067
+ if (!await schoolFact(c))
1068
+ open.push(c);
1069
+ for (const x of excluded) {
1070
+ const c = x.candidate;
1071
+ const own = { from: c.birth.lo, to: c.birth.hi };
1072
+ if (x.why === "age" && band !== null && slack > 0 && birthYearsMeet(band, own, slack)) {
1073
+ if (!await schoolFact(c))
1074
+ held.push(c.dancer_id);
1075
+ continue;
1076
+ }
1077
+ refuted.push({
1078
+ dancer_id: c.dancer_id,
1079
+ why: x.why,
1080
+ fact: contradictionFact(x, age, slack)
1081
+ });
1082
+ }
1083
+ initials = {
1084
+ open: [...open.map((c) => c.dancer_id), ...held],
1085
+ heldByDanceUp: held,
1086
+ refuted,
1087
+ year
1088
+ };
1089
+ if (open.length === 1 && held.length === 0) {
1090
+ for (const c of kept)
1091
+ if (bySchool.has(c.dancer_id))
1092
+ excluded.push({ candidate: c, why: "school" });
1093
+ kept = open;
1094
+ }
1095
+ }
961
1096
  let preferred;
962
1097
  if (kept.length > 1 && school !== undefined)
963
1098
  for (const rank of [SCHOOL_RANK.same, SCHOOL_RANK.moved]) {
@@ -973,6 +1108,28 @@ class DancerResolver2 {
973
1108
  }
974
1109
  break;
975
1110
  }
1111
+ if (preferred === undefined && kept.length > 1 && oireachtas?.read != null) {
1112
+ const best = Math.min(...kept.map((c) => SCHOOL_RANK[c.evidence.school]));
1113
+ const hit = kept.filter((c) => SCHOOL_RANK[c.evidence.school] === best && c.evidence.region === "same");
1114
+ if (hit.length === 1) {
1115
+ const c = hit[0];
1116
+ preferred = {
1117
+ dancer_id: c.dancer_id,
1118
+ why: `the only candidate${school !== undefined ? " of the school's first rank" : ""} whose latest attended region is ${oireachtas.read}: ${c.region?.year} of ${c.region?.attended} attended year(s) to ${oireachtas.year}`
1119
+ };
1120
+ }
1121
+ }
1122
+ if (preferred === undefined && kept.length > 1 && initials !== undefined) {
1123
+ const openIds = new Set(initials.open);
1124
+ const hit = kept.filter((c) => openIds.has(c.dancer_id) && c.year_hi !== null && c.year_hi >= year - 1);
1125
+ if (hit.length === 1) {
1126
+ const c = hit[0];
1127
+ preferred = {
1128
+ dancer_id: c.dancer_id,
1129
+ why: `the only candidate no hard fact refutes with a row in ${year - 1} or ${year}`
1130
+ };
1131
+ }
1132
+ }
976
1133
  return {
977
1134
  tag: this.rel.tag,
978
1135
  asked: q,
@@ -983,9 +1140,33 @@ class DancerResolver2 {
983
1140
  unlinked: [...unlinked].map(([print, rows]) => ({ print, rows })).sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
984
1141
  ...age !== undefined ? { age: age.answer } : {},
985
1142
  ...population !== undefined ? { population } : {},
986
- ...school !== undefined ? { school } : {}
1143
+ ...oireachtas !== undefined ? { oireachtas } : {},
1144
+ ...school !== undefined ? { school } : {},
1145
+ ...initials !== undefined ? { initials } : {}
987
1146
  };
988
1147
  }
1148
+ async readOireachtas(db, o, asOf) {
1149
+ const year = o.year ?? asOf ?? new Date().getFullYear();
1150
+ const asked = o.region;
1151
+ if ((await this.rel.capability("dancers.years")).state !== "full")
1152
+ return {
1153
+ asked,
1154
+ read: null,
1155
+ year,
1156
+ note: "this release publishes no dancer-years: the region narrows and orders nothing"
1157
+ };
1158
+ this.regions ??= db.query("SELECT DISTINCT region FROM dancer_regional ORDER BY region").all().map((r) => r.region);
1159
+ const want = asked.trim().toUpperCase();
1160
+ const hit = this.regions.find((r) => r.toUpperCase() === want);
1161
+ if (hit === undefined)
1162
+ return {
1163
+ asked,
1164
+ read: null,
1165
+ year,
1166
+ note: `the release reads no attended year in a region "${asked}" (its spelling is dancer_years.region): it narrows and orders nothing`
1167
+ };
1168
+ return { asked, read: hit, year };
1169
+ }
989
1170
  async schoolEvidence(schools, ids, lineage, asOf) {
990
1171
  const rows = schools.filter((s) => asOf === null || s.year_lo === null || s.year_lo <= asOf);
991
1172
  const idsOf = async (s) => {
@@ -1014,6 +1195,31 @@ class DancerResolver2 {
1014
1195
  }
1015
1196
  return joined ? "lineage" : earlier ? "moved" : "none";
1016
1197
  }
1198
+ async schoolRefutes(schools, line, year, asked) {
1199
+ const rows = schools.filter((s) => s.year_lo === null || s.year_lo <= year);
1200
+ if (rows.length === 0)
1201
+ return null;
1202
+ const seen = new Set;
1203
+ for (const s of rows) {
1204
+ const own = await this.schoolIdsOf(s);
1205
+ if (own.length === 0 || own.some((id) => line.has(id)))
1206
+ return null;
1207
+ for (const id of own)
1208
+ seen.add(id);
1209
+ }
1210
+ const last = latestSchool(rows, year);
1211
+ return `every school row to ${year} reads ${[...seen].sort().join(", ")} (the latest ${last?.school_id ?? last?.print} in ${last?.year_hi}), none of them ${asked ?? "the asked school"} or a school its line joined by ${year}`;
1212
+ }
1213
+ async schoolIdsOf(s) {
1214
+ const key = s.school_id ?? s.print;
1215
+ let read = this.rowIds.get(key);
1216
+ if (read === undefined) {
1217
+ const got = (await this.rel.schools.resolve(key)).candidates.map((c) => c.school_id);
1218
+ read = s.school_id === null ? got : [...new Set([s.school_id, ...got])];
1219
+ this.rowIds.set(key, read);
1220
+ }
1221
+ return read;
1222
+ }
1017
1223
  readAge(a, asOf) {
1018
1224
  const done = (age, note) => {
1019
1225
  const read = age?.isAge() ? age : null;
@@ -1092,6 +1298,22 @@ function populationEvidence(own, asked) {
1092
1298
  return "unknown";
1093
1299
  return populationRelation(asked, mine) === "disjoint" ? "contradicts" : "same";
1094
1300
  }
1301
+ function regionEvidence(db, dancerId, region, year) {
1302
+ const rows = db.query("SELECT year, region, regional FROM dancer_regional WHERE dancer_id = ? AND year <= ? ORDER BY year").all(dancerId, year);
1303
+ const attended = rows.length;
1304
+ const at = (r) => ({
1305
+ year: r.year,
1306
+ region: r.region,
1307
+ regional: r.regional === 1,
1308
+ attended
1309
+ });
1310
+ const latest = rows.at(-1);
1311
+ if (latest === undefined)
1312
+ return { word: "unknown", region: { year: null, region: null, regional: false, attended } };
1313
+ if (latest.year === year && latest.regional === 1 && latest.region !== region)
1314
+ return { word: "contradicts", region: at(latest) };
1315
+ return { word: latest.region === region ? "same" : "moved", region: at(latest) };
1316
+ }
1095
1317
  function ageEvidence(d, asked) {
1096
1318
  if (asked === null)
1097
1319
  return "unasked";
@@ -1106,6 +1328,26 @@ function ageEvidence(d, asked) {
1106
1328
  return "overlaps";
1107
1329
  return "contradicts";
1108
1330
  }
1331
+ function askedYear(q, asOf) {
1332
+ if (asOf !== null)
1333
+ return asOf;
1334
+ const a = q.age;
1335
+ if (a !== undefined && !("yob" in a) && typeof a.year === "number")
1336
+ return a.year;
1337
+ return new Date().getFullYear();
1338
+ }
1339
+ function contradictionFact(x, age, slack) {
1340
+ const c = x.candidate;
1341
+ if (x.why === "age") {
1342
+ const w = age?.answer.birth;
1343
+ return `born ${c.birth.lo ?? "?"}-${c.birth.hi ?? "?"}, outside ${age?.answer.read ?? "the asked age"} (born ${w?.from ?? "any"}-${w?.to ?? "any"})${slack > 0 ? ` even with ${slack} year's dance-up` : ""}`;
1344
+ }
1345
+ if (x.why === "population")
1346
+ return `published in the other population's majors solo competitions only (girls ${c.population.girls}, boys ${c.population.boys})`;
1347
+ if (x.why === "region")
1348
+ return `danced the ${c.region?.region} regional in ${c.region?.year}, another regional that year`;
1349
+ return "set aside by the school";
1350
+ }
1109
1351
 
1110
1352
  import { Age as Age2 } from "@finbheara/domain/age";
1111
1353
  import {
@@ -2337,7 +2579,8 @@ class RegistrationResolver2 {
2337
2579
  ...row.school !== undefined ? { school: row.school } : {},
2338
2580
  ...age !== null ? { age: { band: age.band, year: age.year } } : {},
2339
2581
  ...row.asOf !== undefined ? { asOf: row.asOf } : {},
2340
- ...population !== null && "asked" in population ? { population: population.asked } : {}
2582
+ ...population !== null && "asked" in population ? { population: population.asked } : {},
2583
+ ...row.oireachtas !== undefined ? { oireachtas: row.oireachtas } : {}
2341
2584
  };
2342
2585
  const { dancer } = await this.dancer(asked);
2343
2586
  out.push({
@@ -3286,4 +3529,4 @@ function releases2(opts = {}) {
3286
3529
  return new ReleaseManager2(opts);
3287
3530
  }
3288
3531
 
3289
- export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, StaffFinder2, SchoolResolver2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, resolveTag2, Release2, openRelease2, openCached2, ReleaseManager2, releases2 };
3532
+ export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, initialsOnly2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, StaffFinder2, SchoolResolver2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, resolveTag2, Release2, openRelease2, openCached2, ReleaseManager2, releases2 };
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  nameOrderOf,
5
5
  openRelease2,
6
6
  releases2
7
- } from "./chunks/index-mg6dw0y5.js";
7
+ } from "./chunks/index-hs72zekf.js";
8
8
  import"./chunks/index-hne1wadw.js";
9
9
  import"./chunks/index-hghdy980.js";
10
10
 
@@ -40,9 +40,12 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
40
40
  dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>
41
41
  [--order surname-first|given-first] [--school <print|id>]
42
42
  [--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]
43
- [--population girls|boys] [--rows]
43
+ [--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]
44
44
  the dancers a name reaches, each with its evidence
45
- (name level, age, school, population) and the arm; the results of
45
+ (name level, age, school, population, region) and the arm;
46
+ --oireachtas is the regional the roster is for, its region in
47
+ the release's spelling (SRO, MAO), its year the --as-of year
48
+ where none is given; the results of
46
49
  the one dancer where one answers (or --rows for each)
47
50
  results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]
48
51
  [--school <print|id>] [--year <y>] [--limit <n>]
@@ -87,12 +90,13 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
87
90
  resolve comp [<feis> <code>] [--year <y>] [--title <t>]
88
91
  resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]
89
92
  [--order <o>] [--as-of <date>] [--population girls|boys]
93
+ [--oireachtas <region>[:<year>]]
90
94
  resolve registrations
91
95
  JSONL out, one answer per line; with no positional the
92
96
  questions are JSONL on stdin, one per line (a comp:
93
97
  {feis, code, year?, title?}, a dancer: {name, school?, age?,
94
- population?}, a registration: {name, school?, codes[], feis,
95
- year?, population?})
98
+ population?, oireachtas?}, a registration: {name, school?,
99
+ codes[], feis, year?, population?, oireachtas?})
96
100
 
97
101
  <spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)`;
98
102
  var VALUE = new Set([
@@ -117,6 +121,7 @@ var VALUE = new Set([
117
121
  "--comp",
118
122
  "--as-of",
119
123
  "--population",
124
+ "--oireachtas",
120
125
  "--dancer",
121
126
  "--feis",
122
127
  "--limit",
@@ -169,7 +174,9 @@ function printCached(c, say) {
169
174
  }
170
175
  var span = (lo, hi) => lo === null ? "-" : lo === hi ? `${lo}` : `${lo}-${hi ?? "?"}`;
171
176
  function printCandidate(c, say, pad = " ") {
172
- say(`${pad}${c.dancer_id} ${c.display}${c.scope === "per-release" ? " [per-release id]" : ""} score ${c.score} name ${c.evidence.nameLevel} age ${c.evidence.age} school ${c.evidence.school} population ${c.evidence.population}`);
177
+ say(`${pad}${c.dancer_id} ${c.display}${c.scope === "per-release" ? " [per-release id]" : ""} score ${c.score} name ${c.evidence.nameLevel} age ${c.evidence.age} school ${c.evidence.school} population ${c.evidence.population} region ${c.evidence.region}`);
178
+ if (c.region !== undefined)
179
+ say(`${pad} region ${c.region.region === null ? "-" : `${c.region.region} ${c.region.year}${c.region.regional ? " (regional)" : ""}`} attended ${c.region.attended} year(s)`);
173
180
  say(`${pad} ${c.rows} rows ${span(c.year_lo, c.year_hi)} born ${span(c.birth.lo, c.birth.hi)} (${c.birth.source}) majors girls ${c.population.girls} boys ${c.population.boys} school ${c.school === null ? "-" : `${c.school.school_id ?? "(no id)"} "${c.school.print}" ${span(c.school.year_lo, c.school.year_hi)}`}`);
174
181
  say(`${pad} prints ${c.prints.slice(0, 5).map((p) => `"${p.print}" (${p.rows}, ${p.level})`).join(", ")}${c.prints.length > 5 ? `, +${c.prints.length - 5} more` : ""}`);
175
182
  }
@@ -181,6 +188,8 @@ function printDancers(a, say) {
181
188
  say(` school asked: ${a.school.arm} ${a.school.ids.join(", ") || "-"}${a.school.lineage !== undefined && a.school.lineage.length > 0 ? ` (lineage: ${a.school.lineage.join(", ")})` : ""}`);
182
189
  if (a.population !== undefined)
183
190
  say(` population asked: ${a.population.read ?? "(unread)"}${a.population.note !== undefined ? ` (${a.population.note})` : ""}`);
191
+ if (a.oireachtas !== undefined)
192
+ say(` oireachtas asked: ${a.oireachtas.read ?? "(unread)"} ${a.oireachtas.year}${a.oireachtas.note !== undefined ? ` (${a.oireachtas.note})` : ""}`);
184
193
  for (const c of a.candidates)
185
194
  printCandidate(c, say);
186
195
  if (a.preferred !== undefined)
@@ -253,6 +262,14 @@ function jsonLines(text, check) {
253
262
  return out;
254
263
  }
255
264
  var checkComp = (q) => typeof q.feis !== "string" || q.feis.trim() === "" ? "a comp question needs feis" : q.code === undefined || q.code === null || String(q.code).trim() === "" ? "a comp question needs code" : null;
265
+ function oireachtasFlag(v) {
266
+ if (v === undefined)
267
+ return {};
268
+ const m = /^(.*?):(\d{4})$/.exec(v.trim());
269
+ return {
270
+ oireachtas: m === null ? { region: v.trim() } : { region: m[1], year: Number(m[2]) }
271
+ };
272
+ }
256
273
  var checkDancer = (q) => typeof q.name !== "string" || q.name.trim() === "" ? "a dancer question needs name" : null;
257
274
  var checkRegistration = (r) => typeof r.name !== "string" || r.name.trim() === "" ? "a registration row needs name" : typeof r.feis !== "string" || r.feis.trim() === "" ? "a registration row needs feis" : !Array.isArray(r.codes) ? "a registration row needs codes[]" : null;
258
275
  async function resolveVerb(rel, what, args, p, say, stdin) {
@@ -312,7 +329,8 @@ async function resolveVerb(rel, what, args, p, say, stdin) {
312
329
  ...p.flags.has("--school") ? { school: p.flags.get("--school") } : {},
313
330
  ...age !== undefined ? { age } : {},
314
331
  ...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {},
315
- ...p.flags.has("--population") ? { population: p.flags.get("--population") } : {}
332
+ ...p.flags.has("--population") ? { population: p.flags.get("--population") } : {},
333
+ ...oireachtasFlag(p.flags.get("--oireachtas"))
316
334
  }
317
335
  }
318
336
  ];
@@ -619,6 +637,7 @@ async function main(argv, io = {}) {
619
637
  ...age !== undefined ? { age } : {},
620
638
  ...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {},
621
639
  ...p.flags.has("--population") ? { population: p.flags.get("--population") } : {},
640
+ ...oireachtasFlag(p.flags.get("--oireachtas")),
622
641
  ...p.flags.has("--limit") ? { limit: Number(p.flags.get("--limit")) } : {}
623
642
  };
624
643
  const surname = p.flags.get("--surname");
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  FEIS_LOCAL_ID_FLOOR2,
13
13
  ResultFinder2,
14
14
  MajorsFinder2,
15
+ initialsOnly2,
15
16
  DancerResolver2,
16
17
  readPopulation2,
17
18
  canonicalOf2,
@@ -45,7 +46,7 @@ import {
45
46
  openCached2,
46
47
  ReleaseManager2,
47
48
  releases2
48
- } from "./chunks/index-mg6dw0y5.js";
49
+ } from "./chunks/index-hs72zekf.js";
49
50
  import {
50
51
  cacheRoot2,
51
52
  repoOf2
@@ -94,6 +95,7 @@ export {
94
95
  httpsRemote2 as httpsRemote,
95
96
  indexKey2 as indexKey,
96
97
  indexPath2 as indexPath,
98
+ initialsOnly2 as initialsOnly,
97
99
  isCurrent2 as isCurrent,
98
100
  listIndexes2 as listIndexes,
99
101
  nameLevel2 as nameLevel,
@@ -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] [--rows]\n the dancers a name reaches, each with its evidence\n (name level, age, school, population) and the arm; 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 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?}, a registration: {name, school?, codes[], feis,\n year?, population?})\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 (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)";
5
5
  export interface Parsed {
6
6
  positional: string[];
7
7
  flags: Map<string, string>;
@@ -1,7 +1,9 @@
1
+ import { type PersonName } from "@finbheara/names/normalize";
1
2
  import { type NameLevel } from "./dancers-names.js";
2
3
  import type { IndexBuilder } from "./indexes.js";
3
4
  import type { Release } from "./release.js";
4
5
  import { FEIS_LOCAL_ID_FLOOR } from "./results.js";
6
+ export declare function initialsOnly(name: PersonName): boolean;
5
7
  export { FEIS_LOCAL_ID_FLOOR };
6
8
  export type NameOrderAsked = "surname-first" | "given-first" | "unknown";
7
9
  export type AgeAsked = {
@@ -20,8 +22,14 @@ export interface DancerQuery {
20
22
  school?: string;
21
23
  age?: AgeAsked;
22
24
  asOf?: string;
25
+ oireachtas?: OireachtasAsked;
23
26
  population?: string;
24
27
  }
28
+ export interface OireachtasAsked {
29
+ region: string;
30
+ year?: number;
31
+ }
32
+ export type RegionEvidence = "same" | "moved" | "contradicts" | "unknown" | "unasked";
25
33
  export type AgeEvidence = "fits" | "overlaps" | "contradicts" | "unknown" | "unasked";
26
34
  export type PopulationEvidence = "same" | "contradicts" | "both" | "unknown" | "unasked";
27
35
  export type SchoolEvidence = "same" | "lineage" | "moved" | "none" | "unresolved" | "unasked";
@@ -43,6 +51,7 @@ export interface DancerCandidate {
43
51
  age: AgeEvidence;
44
52
  school: SchoolEvidence;
45
53
  population: PopulationEvidence;
54
+ region: RegionEvidence;
46
55
  };
47
56
  prints: {
48
57
  print: string;
@@ -63,10 +72,22 @@ export interface DancerCandidate {
63
72
  boys: number;
64
73
  };
65
74
  school: DancerSchool | null;
75
+ region?: DancerRegion;
76
+ }
77
+ export interface DancerRegion {
78
+ year: number | null;
79
+ region: string | null;
80
+ regional: boolean;
81
+ attended: number;
66
82
  }
67
83
  export interface ExcludedCandidate {
68
84
  candidate: DancerCandidate;
69
- why: "age" | "population";
85
+ why: "age" | "population" | "region" | "school";
86
+ }
87
+ export interface InitialsRefuted {
88
+ dancer_id: number;
89
+ why: "school" | "age" | "population" | "region";
90
+ fact: string;
70
91
  }
71
92
  export interface DancerAnswer {
72
93
  tag: string;
@@ -95,12 +116,24 @@ export interface DancerAnswer {
95
116
  read: "girls" | "boys" | null;
96
117
  note?: string;
97
118
  };
119
+ oireachtas?: {
120
+ asked: string;
121
+ read: string | null;
122
+ year: number;
123
+ note?: string;
124
+ };
98
125
  school?: {
99
126
  arm: string;
100
127
  ids: string[];
101
128
  lineage?: string[];
102
129
  };
103
130
  refused?: string;
131
+ initials?: {
132
+ open: number[];
133
+ heldByDanceUp: number[];
134
+ refuted: InitialsRefuted[];
135
+ year: number;
136
+ };
104
137
  }
105
138
  export interface DancerSearchQuery extends Omit<DancerQuery, "name"> {
106
139
  name?: string;
@@ -164,7 +197,9 @@ export interface DancerYear {
164
197
  export declare class DancerResolver {
165
198
  private readonly rel;
166
199
  private readonly printIds;
200
+ private readonly rowIds;
167
201
  private lineageOf;
202
+ private regions;
168
203
  constructor(rel: Release);
169
204
  private lineage;
170
205
  records(q?: {
@@ -185,7 +220,10 @@ export declare class DancerResolver {
185
220
  private capped;
186
221
  get(dancerId: number): Promise<DancerRecord | null>;
187
222
  private answer;
223
+ private readOireachtas;
188
224
  private schoolEvidence;
225
+ private schoolRefutes;
226
+ private schoolIdsOf;
189
227
  private readAge;
190
228
  }
191
229
  export declare function readPopulation(asked: string): NonNullable<DancerAnswer["population"]>;
@@ -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 DancerSchool, type DancerSearchQuery, type DancerYear, type ExcludedCandidate, FEIS_LOCAL_ID_FLOOR, type PopulationEvidence, 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, 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";
@@ -0,0 +1,2 @@
1
+ export declare function printsAbsence(print: string): boolean;
2
+ export declare function notOnTheFloor(placePrinted: string | null, atts: string | null, withdrawn: string | null): boolean;
@@ -1,5 +1,5 @@
1
1
  import type { CompAnswer, CompResolver } from "./comps.js";
2
- import { type DancerAnswer, type DancerQuery } from "./dancers.js";
2
+ import { type DancerAnswer, type DancerQuery, type OireachtasAsked } from "./dancers.js";
3
3
  import type { Release } from "./release.js";
4
4
  export interface RegistrationRow {
5
5
  name: string;
@@ -11,6 +11,7 @@ export interface RegistrationRow {
11
11
  titles?: Readonly<Record<string, string>>;
12
12
  asOf?: string;
13
13
  population?: string;
14
+ oireachtas?: OireachtasAsked;
14
15
  [k: string]: unknown;
15
16
  }
16
17
  export type DancerQueryLike = DancerQuery;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.8.0",
3
+ "version": "0.10.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",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@finbheara/domain": "0.50.0",
48
- "@finbheara/names": "0.23.0",
48
+ "@finbheara/names": "0.24.0",
49
49
  "hyparquet": "1.31.2"
50
50
  },
51
51
  "devDependencies": {