@finbheara/archive-client 0.8.0 → 0.9.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,12 @@ 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")
224
+ r.preferred; // NON-BINDING: { dancer_id, why } where the school (else the region) singles one out; never moves arm
224
225
  r.unlinked; // rows printing a matched name that the release linked to no dancer
225
226
 
226
227
  await rel.dancers.search({ surname: "Quellan" }); // or { like: "a* quellan" }, or { name }
@@ -270,6 +271,57 @@ for a caller to weigh, and `arm` stays as counted. A `lineage` match ranks with
270
271
  each singles out neither. The `score` orders candidates and is no
271
272
  probability.
272
273
 
274
+ THE REGIONAL OIREACHTAS (`oireachtas: { region, year? }`). A dancer enters one
275
+ residence-qualifying regional Oireachtas a year. Two regionals in one year are two dancers, the
276
+ overseas entry excepted. The release already applies that rule to each dancer-year it publishes:
277
+ `dancer_years.region` with `region_source = "current"` is the one regional the dancer attended that
278
+ year, the dancer's own overseas entries left out and a year with two regionals given no `current`
279
+ region. Where a year has no regional, an Australian or New Zealand national stands in. The client
280
+ reads those rows and nothing else (`rel.dancers.years` serves them all).
281
+
282
+ - `region` is the release's own spelling, the codes `dancer_years.region` prints (`SRO`, `MAO`,
283
+ `ENG_SOUTH`, ...), case folded. A region the release reads no attended year in narrows and orders
284
+ nothing, and `r.oireachtas.note` says so.
285
+ - `year` is the regional's own calendar year, `asOf`'s year where none is given. The calendar year
286
+ is the only unit (an age is an age on 1 January of the competition's year, as
287
+ `@finbheara/domain/age` reads it), so a regional held in December 2026 is `year: 2026`, read
288
+ against `dancer_years.year` as it is, never shifted by month.
289
+ - THE DENOMINATOR is the candidate's attended (`current`) dancer-years at or before the asked
290
+ year, counted on the candidate (`region.attended`). Later years are not read. A `carried` or
291
+ `carried-back` row is not read: it repeats an attended year, which is read directly. A `school`
292
+ or `cocompetitor` row is not read: it is a vote of other dancers' regions, a prior for a
293
+ dancer-year with no card of its own, not evidence about this one.
294
+ - A `current` year may rest on a registration alone: a no-show still says where the dancer lives.
295
+ So it decides the region, and never alone a contradiction.
296
+ - A DANCED REGIONAL is a row at a `T3` competition that year whose address is on the floor. The
297
+ release publishes no residence flag, and on the event roster every `T3` event is a residence
298
+ qualifier and every residence qualifier is `T3`, so an attended year with a `T3` row was read from
299
+ a regional, and one without from a national standing in for one. ON THE FLOOR is the release's
300
+ own absence rule, read from the row's `place_printed`, `atts` and `withdrawn`: an address with a
301
+ printed absence (`NS`, `DNS`, `DNA`, `No Show`, `Did Not Attend`, `Did Not Compete`, in the place
302
+ cell or as an `atts` token) or a DNT (`withdrawn` `DNT`, or `did-not-start` in older releases) on
303
+ any of its rows danced nothing there. A bare `NP` or `NR` is a dancer placed nowhere, on the
304
+ floor.
305
+
306
+ `evidence.region` is one of:
307
+
308
+ | word | counts | weight |
309
+ |---|---|---|
310
+ | `contradicts` | the asked year is attended in another region, and the dancer danced a regional that year: set aside (`why: "region"`) | excluded |
311
+ | `same` | the latest attended year's region is the asked one | 2 where that year is the asked year, 1 where earlier |
312
+ | `moved` | the latest attended year's region is another one (an earlier year, or a national in the asked year) | 0 |
313
+ | `unknown` | no attended year at or before the asked year | 0 |
314
+ | `unasked` | no `oireachtas` asked | 0 |
315
+
316
+ `region` on a candidate is the deciding dancer-year (`{ year, region, regional, attended }`): the
317
+ asked year where it contradicts, else the latest attended one. `moved` weighs nothing: dancers
318
+ move, and no rate at which dancers move is measured, so it is said and never scored. Candidates
319
+ sort by the school first, then `same` before every other region word, then `score`. Where the
320
+ school singles out no one, `preferred` names the only candidate of the school's first rank (every
321
+ candidate where no school is asked) whose region is `same`. The asked entry's own printed location
322
+ is not asked, so an overseas entrant is asked without `oireachtas`. A release that publishes no
323
+ dancer-years (feature `dancers.years`) reads every candidate `unknown`, with the note.
324
+
273
325
  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
326
  carrying one say `scope: "per-release"` (`dancer_scope` on a row), so a caller never stores one
275
327
  across releases. Every answer carries the release tag.
@@ -485,7 +537,8 @@ feis's championships), the band is read from the row's own titles (`titles`, by
485
537
  domain package's title reader, the youngest again, and `age.from` says `title`. Nothing else
486
538
  stands in for a band. The dancer resolver is the release's own (`rel.dancers`, above), or the
487
539
  `dancerResolver` option. With `dancerResolver: null`, each row's dancer answers `unavailable`
488
- and the competitions still resolve.
540
+ and the competitions still resolve. A row's `oireachtas`, where the list is a regional's roster,
541
+ is passed to the dancer resolver as asked (`rel.dancers`, above).
489
542
 
490
543
  ## The command line
491
544
 
@@ -506,7 +559,7 @@ and the competitions still resolve.
506
559
  idq dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>
507
560
  [--order surname-first|given-first] [--school <print|id>]
508
561
  [--yob <y> | --band <band> [--year <y>] | --comp <competition>] [--as-of <date>]
509
- [--population girls|boys] [--rows]
562
+ [--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]
510
563
  idq results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <c>] [--school <s>] [--year <y>] [--limit <n>]
511
564
  idq majors [--release <spec>] [--event <e>] [--year <y>] [--comp <c>] [--dancer <id>] [--rows]
512
565
  idq stream [--release <spec>] --scope majors|transcribed|platform|all [--entries] [--event <e,...>]
@@ -525,6 +578,7 @@ and the competitions still resolve.
525
578
  idq syllabus [--release <spec>] --for <series> [year]
526
579
  idq resolve comp [<feis> <code>] [--year y] [--title t]
527
580
  idq resolve dancer [<name>] [--school s] [--comp <feis>/<code> | --yob y | --band b --year y]
581
+ [--oireachtas <region>[:<year>]]
528
582
  idq resolve registrations
529
583
 
530
584
  `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 +586,8 @@ and the competitions still resolve.
532
586
 
533
587
  `resolve` speaks JSON lines: one answer per line out, and with no positional question the
534
588
  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,
589
+ `{name, school?, age?, population?, oireachtas?}`,
590
+ `{name, school?, codes, feis, year?, population?, oireachtas?}`). A line that is no JSON object,
536
591
  or no question of its kind, answers `{tag, line, error}` and the other lines still answer. The
537
592
  exit is non-zero where any line erred. A dancer's age may be given as a comp
538
593
  (`{"comp": "<feis>/<code>"}`, or `--comp` on the command line): `rel.registrations.dancer` reads
@@ -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],
@@ -597,7 +621,7 @@ var PERSON = { table: "person", value: "id", valueType: "INTEGER PRIMARY KEY" };
597
621
  var ASKED = ["surname_key", "nn4", "blk_surname"];
598
622
  var dancersIndex = {
599
623
  name: "dancers",
600
- version: "2",
624
+ version: "4",
601
625
  inputs: ["results"],
602
626
  keyParts: () => [...PACKAGE_KEY_PARTS],
603
627
  says: "every published dancer print at the names package's keys, its dancers, schools and years",
@@ -608,6 +632,7 @@ var dancersIndex = {
608
632
  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
633
  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
634
  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");
635
+ 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
636
  const src = rel.db("results");
612
637
  const groups = src.query(`SELECT r.name_printed AS p, r.name_order_resolved AS o, r.dancer_id AS d,
613
638
  r.school_printed AS sp, r.school_id AS s,
@@ -682,6 +707,25 @@ var dancersIndex = {
682
707
  if (pop === "girls" || pop === "boys")
683
708
  putPop.run(r.d, pop, r.n);
684
709
  }
710
+ if (src.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'dancer_years'").get() !== null) {
711
+ const cols = new Set(src.query("PRAGMA table_info(results)").all().map((c) => c.name));
712
+ const col = (c) => cols.has(c) ? `r.${c}` : "NULL";
713
+ const rows = src.query(`SELECT r.dancer_id AS d, c.year AS y, r.address AS a, ${col("place_printed")} AS p,
714
+ ${col("atts")} AS t, ${col("withdrawn")} AS w
715
+ FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
716
+ WHERE r.dancer_id IS NOT NULL AND c.tier = 'T3' AND c.year IS NOT NULL`).all();
717
+ const absent = new Set;
718
+ for (const r of rows)
719
+ if (notOnTheFloor(r.p, r.t, r.w))
720
+ absent.add(r.a);
721
+ const t3 = new Set;
722
+ for (const r of rows)
723
+ if (!absent.has(r.a))
724
+ t3.add(`${r.d}\x00${r.y}`);
725
+ const putReg = out.prepare("INSERT INTO dancer_regional VALUES (?, ?, ?, ?)");
726
+ 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())
727
+ putReg.run(r.d, r.y, r.r, t3.has(`${r.d}\x00${r.y}`) ? 1 : 0);
728
+ }
685
729
  for (const s of schema.indexes)
686
730
  if (ASKED.some((c) => s.endsWith(`(${c})`)))
687
731
  out.run(s);
@@ -734,6 +778,21 @@ var POPULATION_WEIGHT = {
734
778
  unasked: 0,
735
779
  contradicts: -10
736
780
  };
781
+ var REGION_WEIGHT = {
782
+ same: 1,
783
+ unknown: 0,
784
+ unasked: 0,
785
+ moved: 0,
786
+ contradicts: -10
787
+ };
788
+ var REGION_SAME_THAT_YEAR = 2;
789
+ var REGION_RANK = {
790
+ same: 0,
791
+ unknown: 1,
792
+ unasked: 1,
793
+ moved: 1,
794
+ contradicts: 2
795
+ };
737
796
  var SCHOOL_WEIGHT = {
738
797
  same: 2,
739
798
  lineage: 2,
@@ -753,6 +812,7 @@ class DancerResolver2 {
753
812
  rel;
754
813
  printIds = new Map;
755
814
  lineageOf;
815
+ regions;
756
816
  constructor(rel) {
757
817
  this.rel = rel;
758
818
  }
@@ -880,6 +940,7 @@ class DancerResolver2 {
880
940
  const asOf = yearOf(q.asOf);
881
941
  const age = q.age === undefined ? undefined : this.readAge(q.age, asOf);
882
942
  const population = q.population === undefined ? undefined : readPopulation2(q.population);
943
+ const oireachtas = q.oireachtas === undefined ? undefined : await this.readOireachtas(db, q.oireachtas, asOf);
883
944
  let schoolIds = null;
884
945
  let lineageIds = [];
885
946
  let school;
@@ -929,23 +990,36 @@ class DancerResolver2 {
929
990
  const pop = populationOf(db, id);
930
991
  const popEv = populationEvidence(pop, population?.read ?? null);
931
992
  const schoolEv = schoolIds === null ? "unasked" : schoolIds.length === 0 ? "unresolved" : await this.schoolEvidence(schools, schoolIds, lineageIds, asOf);
993
+ const reg = oireachtas === undefined ? null : oireachtas.read === null ? {
994
+ word: "unknown",
995
+ region: { year: null, region: null, regional: false, attended: 0 }
996
+ } : regionEvidence(db, id, oireachtas.read, oireachtas.year);
997
+ const regionEv = reg === null ? "unasked" : reg.word;
998
+ const regionWeight = regionEv === "same" && reg?.region.year === oireachtas?.year ? REGION_SAME_THAT_YEAR : REGION_WEIGHT[regionEv];
932
999
  all.push({
933
1000
  dancer_id: id,
934
1001
  display: d.display,
935
1002
  scope: id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable",
936
1003
  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 },
1004
+ score: NAME_WEIGHT[e.level] + AGE_WEIGHT[ageEv] + SCHOOL_WEIGHT[schoolEv] + POPULATION_WEIGHT[popEv] + regionWeight,
1005
+ evidence: {
1006
+ nameLevel: e.level,
1007
+ age: ageEv,
1008
+ school: schoolEv,
1009
+ population: popEv,
1010
+ region: regionEv
1011
+ },
939
1012
  prints: e.prints.sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
940
1013
  rows: d.rows,
941
1014
  year_lo: d.year_lo,
942
1015
  year_hi: d.year_hi,
943
1016
  birth: birthOf(d),
944
1017
  population: pop,
945
- school: latest
1018
+ school: latest,
1019
+ ...reg !== null ? { region: reg.region } : {}
946
1020
  });
947
1021
  }
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);
1022
+ 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
1023
  const excluded = [];
950
1024
  let kept = all;
951
1025
  if (narrow)
@@ -954,6 +1028,8 @@ class DancerResolver2 {
954
1028
  excluded.push({ candidate: c, why: "age" });
955
1029
  else if (c.evidence.population === "contradicts")
956
1030
  excluded.push({ candidate: c, why: "population" });
1031
+ else if (c.evidence.region === "contradicts")
1032
+ excluded.push({ candidate: c, why: "region" });
957
1033
  else
958
1034
  return true;
959
1035
  return false;
@@ -973,6 +1049,17 @@ class DancerResolver2 {
973
1049
  }
974
1050
  break;
975
1051
  }
1052
+ if (preferred === undefined && kept.length > 1 && oireachtas?.read != null) {
1053
+ const best = Math.min(...kept.map((c) => SCHOOL_RANK[c.evidence.school]));
1054
+ const hit = kept.filter((c) => SCHOOL_RANK[c.evidence.school] === best && c.evidence.region === "same");
1055
+ if (hit.length === 1) {
1056
+ const c = hit[0];
1057
+ preferred = {
1058
+ dancer_id: c.dancer_id,
1059
+ 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}`
1060
+ };
1061
+ }
1062
+ }
976
1063
  return {
977
1064
  tag: this.rel.tag,
978
1065
  asked: q,
@@ -983,9 +1070,32 @@ class DancerResolver2 {
983
1070
  unlinked: [...unlinked].map(([print, rows]) => ({ print, rows })).sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
984
1071
  ...age !== undefined ? { age: age.answer } : {},
985
1072
  ...population !== undefined ? { population } : {},
1073
+ ...oireachtas !== undefined ? { oireachtas } : {},
986
1074
  ...school !== undefined ? { school } : {}
987
1075
  };
988
1076
  }
1077
+ async readOireachtas(db, o, asOf) {
1078
+ const year = o.year ?? asOf ?? new Date().getFullYear();
1079
+ const asked = o.region;
1080
+ if ((await this.rel.capability("dancers.years")).state !== "full")
1081
+ return {
1082
+ asked,
1083
+ read: null,
1084
+ year,
1085
+ note: "this release publishes no dancer-years: the region narrows and orders nothing"
1086
+ };
1087
+ this.regions ??= db.query("SELECT DISTINCT region FROM dancer_regional ORDER BY region").all().map((r) => r.region);
1088
+ const want = asked.trim().toUpperCase();
1089
+ const hit = this.regions.find((r) => r.toUpperCase() === want);
1090
+ if (hit === undefined)
1091
+ return {
1092
+ asked,
1093
+ read: null,
1094
+ year,
1095
+ note: `the release reads no attended year in a region "${asked}" (its spelling is dancer_years.region): it narrows and orders nothing`
1096
+ };
1097
+ return { asked, read: hit, year };
1098
+ }
989
1099
  async schoolEvidence(schools, ids, lineage, asOf) {
990
1100
  const rows = schools.filter((s) => asOf === null || s.year_lo === null || s.year_lo <= asOf);
991
1101
  const idsOf = async (s) => {
@@ -1092,6 +1202,22 @@ function populationEvidence(own, asked) {
1092
1202
  return "unknown";
1093
1203
  return populationRelation(asked, mine) === "disjoint" ? "contradicts" : "same";
1094
1204
  }
1205
+ function regionEvidence(db, dancerId, region, year) {
1206
+ const rows = db.query("SELECT year, region, regional FROM dancer_regional WHERE dancer_id = ? AND year <= ? ORDER BY year").all(dancerId, year);
1207
+ const attended = rows.length;
1208
+ const at = (r) => ({
1209
+ year: r.year,
1210
+ region: r.region,
1211
+ regional: r.regional === 1,
1212
+ attended
1213
+ });
1214
+ const latest = rows.at(-1);
1215
+ if (latest === undefined)
1216
+ return { word: "unknown", region: { year: null, region: null, regional: false, attended } };
1217
+ if (latest.year === year && latest.regional === 1 && latest.region !== region)
1218
+ return { word: "contradicts", region: at(latest) };
1219
+ return { word: latest.region === region ? "same" : "moved", region: at(latest) };
1220
+ }
1095
1221
  function ageEvidence(d, asked) {
1096
1222
  if (asked === null)
1097
1223
  return "unasked";
@@ -2337,7 +2463,8 @@ class RegistrationResolver2 {
2337
2463
  ...row.school !== undefined ? { school: row.school } : {},
2338
2464
  ...age !== null ? { age: { band: age.band, year: age.year } } : {},
2339
2465
  ...row.asOf !== undefined ? { asOf: row.asOf } : {},
2340
- ...population !== null && "asked" in population ? { population: population.asked } : {}
2466
+ ...population !== null && "asked" in population ? { population: population.asked } : {},
2467
+ ...row.oireachtas !== undefined ? { oireachtas: row.oireachtas } : {}
2341
2468
  };
2342
2469
  const { dancer } = await this.dancer(asked);
2343
2470
  out.push({
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-1sj5zr8k.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
@@ -45,7 +45,7 @@ import {
45
45
  openCached2,
46
46
  ReleaseManager2,
47
47
  releases2
48
- } from "./chunks/index-mg6dw0y5.js";
48
+ } from "./chunks/index-1sj5zr8k.js";
49
49
  import {
50
50
  cacheRoot2,
51
51
  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] [--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>;
@@ -20,8 +20,14 @@ export interface DancerQuery {
20
20
  school?: string;
21
21
  age?: AgeAsked;
22
22
  asOf?: string;
23
+ oireachtas?: OireachtasAsked;
23
24
  population?: string;
24
25
  }
26
+ export interface OireachtasAsked {
27
+ region: string;
28
+ year?: number;
29
+ }
30
+ export type RegionEvidence = "same" | "moved" | "contradicts" | "unknown" | "unasked";
25
31
  export type AgeEvidence = "fits" | "overlaps" | "contradicts" | "unknown" | "unasked";
26
32
  export type PopulationEvidence = "same" | "contradicts" | "both" | "unknown" | "unasked";
27
33
  export type SchoolEvidence = "same" | "lineage" | "moved" | "none" | "unresolved" | "unasked";
@@ -43,6 +49,7 @@ export interface DancerCandidate {
43
49
  age: AgeEvidence;
44
50
  school: SchoolEvidence;
45
51
  population: PopulationEvidence;
52
+ region: RegionEvidence;
46
53
  };
47
54
  prints: {
48
55
  print: string;
@@ -63,10 +70,17 @@ export interface DancerCandidate {
63
70
  boys: number;
64
71
  };
65
72
  school: DancerSchool | null;
73
+ region?: DancerRegion;
74
+ }
75
+ export interface DancerRegion {
76
+ year: number | null;
77
+ region: string | null;
78
+ regional: boolean;
79
+ attended: number;
66
80
  }
67
81
  export interface ExcludedCandidate {
68
82
  candidate: DancerCandidate;
69
- why: "age" | "population";
83
+ why: "age" | "population" | "region";
70
84
  }
71
85
  export interface DancerAnswer {
72
86
  tag: string;
@@ -95,6 +109,12 @@ export interface DancerAnswer {
95
109
  read: "girls" | "boys" | null;
96
110
  note?: string;
97
111
  };
112
+ oireachtas?: {
113
+ asked: string;
114
+ read: string | null;
115
+ year: number;
116
+ note?: string;
117
+ };
98
118
  school?: {
99
119
  arm: string;
100
120
  ids: string[];
@@ -165,6 +185,7 @@ export declare class DancerResolver {
165
185
  private readonly rel;
166
186
  private readonly printIds;
167
187
  private lineageOf;
188
+ private regions;
168
189
  constructor(rel: Release);
169
190
  private lineage;
170
191
  records(q?: {
@@ -185,6 +206,7 @@ export declare class DancerResolver {
185
206
  private capped;
186
207
  get(dancerId: number): Promise<DancerRecord | null>;
187
208
  private answer;
209
+ private readOireachtas;
188
210
  private schoolEvidence;
189
211
  private readAge;
190
212
  }
@@ -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 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.9.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",