@finbheara/archive-client 0.13.0 → 0.15.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
@@ -238,12 +238,26 @@ NO NEW MATCHER. A print is read in the dancer's role by `@finbheara/domain` over
238
238
  `@finbheara/names`, and a candidate's `nameLevel` is the finest of: `nn0` to `nn4` (equal at that
239
239
  level of the names package: the print, the key, the folded key, the tokens in any order),
240
240
  `initial` (one surname, one given name the other's made poorer: `givenAbbreviates`), `middle`
241
- (a middle name one print omits: `nameRelation`). `age` is how the dancer's published birth window
241
+ (a middle name one print omits: `nameRelation`), `given-middle` (one surname, and the first given
242
+ name one print uses is a later given name of the other's, `Grace Zumock` of `Ailbhe Grace Zumock`:
243
+ asked only where no finer level found anyone), `spanish` (see below). `age` is how the dancer's published birth window
242
244
  meets the asked age (`Age.fitsIn`, `overlaps`): `fits`, `overlaps`, `contradicts`, `unknown`.
243
245
  `school` is how the dancer's schools meet the asked one: `same` (the latest school as of `asOf`),
244
246
  `lineage` (the latest school is one the release's lineage joins to the asked one), `moved` (an
245
247
  earlier one is the asked one), `none`, `unresolved` (the print reached no school).
246
248
 
249
+ A SPANISH NAME, AT A SITE THAT CARRIES `spanishNames`. A register prints the full form, given
250
+ names and a paternal and a maternal surname (`Ana Inés Varrondo Quelmar`), and a results sheet
251
+ often prints one given name and the paternal surname alone (`Inés Varrondo`). A school file, or a
252
+ branch of one, is flagged `spanishNames: true` in the store, and the release's
253
+ `school-registry.jsonl` line carries it as `spanish_names: { file, prints }` (the file's own flag,
254
+ and the prints of its flagged branches). Where the name as asked finds no one and the asked school
255
+ reads that way (`readsSpanishNames(candidates)`: a flagged file, or a print that reached it is one
256
+ of its flagged branches' prints), every print of every dancer at that school is asked under the
257
+ names package's Spanish reading (`createNameKinship({ spanishNames: true })`), and a print it
258
+ relates is a candidate at `nameLevel: "spanish"`. A release with no `spanish_names` reads every
259
+ school the default way. A name the release already finds answers exactly as before.
260
+
247
261
  THE LINEAGE is the release's `school-lineage.jsonl`: every line joins its `id` and its `other`,
248
262
  whatever its direction (`precedes`, `succeeds`) or shape (a rename, a split, a merger, an absorb,
249
263
  a relation), so a file carrying one end of an edge reads as one carrying both. The whole line
@@ -531,12 +545,20 @@ the host likes, so a code is answered from that feis's competitions in the relea
531
545
  another platform's numbering. The sources, in order:
532
546
 
533
547
  - `syllabus`: the feis-year is in the release, and one of its competitions prints the code
534
- - `prior-year`: the feis-year is not (next year's, before its results): the newest earlier year
535
- of the same series printing the code. Give the comp's `title` and `matchTitle` checks it
536
- against the earlier comp (`check`), and with no title the answer says `unchecked`
548
+ - `prior-year`: the feis-year is not (next year's, before its results), and its platform numbers
549
+ every feis by one grammar (QuickFeis: `CODE_READER`, `@finbheara/domain/code`): the newest
550
+ earlier year of the same series printing the code. Give the comp's `title` and `matchTitle`
551
+ checks it against the earlier comp (`check`), and with no title the answer says `unchecked`
537
552
  - `title`: the code answers nothing and a `title` was given: the competitions of that syllabus
538
553
  the title names, by band and population
539
554
 
555
+ AN EARLIER YEAR'S CODE IS NEVER READ ON A FEIS-SPECIFIC PLATFORM. FeisWeb, FeisWorx, iFeis and
556
+ the rest renumber their competitions from one year to the next: measured in October 2026, the
557
+ same code at the same series one held year later names another band or level on 13% to 29% of
558
+ codes, a plausible answer and a wrong one. So an unheld feis-year there answers by its `title`
559
+ over the newest earlier syllabus, and a code with no title is `none`, saying why. The code of a
560
+ held feis-year is still read from that feis-year's own competitions.
561
+
540
562
  A feis the release holds names itself, its year and its series. A series instance or brand id
541
563
  names the series. A feis id the release does not hold is placed in a series by its host code
542
564
  (`<token>:<stem><yy>` where the held codes of that stem carry their own year), and a stem two
@@ -68,7 +68,7 @@ function sha256OfDigest(digest) {
68
68
  }
69
69
 
70
70
  import { Age, DANCE_UP_YEARS, birthYearsMeet } from "@finbheara/domain/age";
71
- import { createIndex, indexSchema, personEncoder } from "@finbheara/domain/noun-index";
71
+ import { createIndex as createIndex2, indexSchema as indexSchema2, personEncoder } from "@finbheara/domain/noun-index";
72
72
  import domainPkg from "@finbheara/domain/package.json";
73
73
  import { dancerName } from "@finbheara/domain/person";
74
74
  import {
@@ -80,20 +80,25 @@ import {
80
80
  composeNames,
81
81
  foldNameKey,
82
82
  nameBlockKeys,
83
- nameKinship,
83
+ nameKinship as nameKinship2,
84
84
  surnameBlock
85
85
  } from "@finbheara/names/normalize";
86
86
  import namesPkg from "@finbheara/names/package.json";
87
87
 
88
88
  import {
89
+ KINSHIP_VIAS,
89
90
  NAME_LEVEL_COLUMNS,
91
+ createNameKinship,
90
92
  givenAbbreviates,
93
+ nameKinship,
91
94
  nameRelation
92
95
  } from "@finbheara/names/normalize";
93
96
  var NAME_LEVELS2 = [
94
97
  ...NAME_LEVEL_COLUMNS,
95
98
  "initial",
96
99
  "middle",
100
+ "given-middle",
101
+ "spanish",
97
102
  "form",
98
103
  "surname",
99
104
  "like"
@@ -113,6 +118,17 @@ function nameLevel2(q, c) {
113
118
  return "middle";
114
119
  return null;
115
120
  }
121
+ function givenMiddleLevel(q, c) {
122
+ if (!q.isPerson() || !c.isPerson())
123
+ return null;
124
+ const k = nameKinship(q, c);
125
+ return k.relation !== "different" && k.via.includes("given-middle") && k.via.every((v) => v === "given-middle" || KINSHIP_VIAS[v].gateSafe) ? "given-middle" : null;
126
+ }
127
+ var spanish = createNameKinship({ spanishNames: true });
128
+ function spanishLevel(q, c) {
129
+ const k = spanish(q, c);
130
+ return k.relation !== "different" && k.via.includes("spanish") ? "spanish" : null;
131
+ }
116
132
 
117
133
  import { readFileSync } from "fs";
118
134
  var SCHEMA = `
@@ -616,6 +632,189 @@ class MajorsFinder2 {
616
632
  }
617
633
  }
618
634
 
635
+ import { createIndex, indexSchema } from "@finbheara/domain/noun-index";
636
+ import {
637
+ DEFAULT_SCHOOL_NAME_READER,
638
+ schoolEncoder
639
+ } from "@finbheara/domain/school";
640
+ function readsSpanishNames2(candidates) {
641
+ return candidates.some((c) => c.spanishNames !== undefined && (c.spanishNames.file || c.prints.some((p) => c.spanishNames?.prints.includes(p.trim()))));
642
+ }
643
+ var GENERIC_SCHOOL_READER = {
644
+ id: "generic",
645
+ reader: DEFAULT_SCHOOL_NAME_READER
646
+ };
647
+ var encoderOf = (r) => schoolEncoder({ reader: r.reader });
648
+ var TABLE = "school_name";
649
+ var schoolsIndex = {
650
+ name: "schools",
651
+ version: "3",
652
+ inputs: ["results", "school-registry.jsonl", "school-redirects.jsonl"],
653
+ says: "every published school print and its ids, read at the school reader's slots",
654
+ keyParts: (rel) => [`reader=${rel.schoolReader().id}`],
655
+ build(rel, out) {
656
+ const choice = rel.schoolReader();
657
+ const encoder = encoderOf(choice);
658
+ out.run("CREATE TABLE print_id(print TEXT NOT NULL, school_id TEXT NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER, PRIMARY KEY(print, school_id)) WITHOUT ROWID");
659
+ out.run("CREATE TABLE registry(school_id TEXT PRIMARY KEY, display TEXT NOT NULL, regions TEXT NOT NULL, places TEXT NOT NULL, spanish TEXT)");
660
+ out.run("CREATE TABLE redirect(old_id TEXT PRIMARY KEY, new_id TEXT NOT NULL, kind TEXT NOT NULL)");
661
+ const schema = indexSchema(encoder, { table: TABLE });
662
+ out.run(schema.table);
663
+ const src = rel.db("results");
664
+ const lined = src.query("PRAGMA table_info(results)").all().some((c) => c.name === "competition_line");
665
+ const pairs = src.query(lined ? `SELECT trim(r.school_printed) AS p, r.school_id AS s, count(*) AS n,
666
+ min(c.year) AS lo, max(c.year) AS hi
667
+ FROM results r LEFT JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
668
+ WHERE r.school_id IS NOT NULL GROUP BY 1, 2 ORDER BY 1, 2` : `SELECT trim(school_printed) AS p, school_id AS s, count(*) AS n, NULL AS lo, NULL AS hi
669
+ FROM results WHERE school_id IS NOT NULL GROUP BY 1, 2 ORDER BY 1, 2`).all();
670
+ const putPair = out.prepare("INSERT INTO print_id VALUES (?, ?, ?, ?, ?)");
671
+ for (const r of pairs)
672
+ putPair.run(r.p, r.s, r.n, r.lo, r.hi);
673
+ out.run("CREATE INDEX print_id_school ON print_id(school_id)");
674
+ const names = createIndex(out, encoder, {
675
+ table: TABLE
676
+ });
677
+ let last = null;
678
+ for (const r of pairs) {
679
+ if (r.p === last)
680
+ continue;
681
+ last = r.p;
682
+ names.add(choice.reader.schoolName(r.p), r.p);
683
+ }
684
+ for (const s of schema.indexes)
685
+ out.run(s);
686
+ const putReg = out.prepare("INSERT OR REPLACE INTO registry VALUES (?, ?, ?, ?, ?)");
687
+ for (const r of readJsonl(rel.path("school-registry.jsonl")))
688
+ putReg.run(r.school_id, r.display, JSON.stringify([...new Set(r.regions.map((x) => x.region))]), JSON.stringify([...new Set(r.locations.map((x) => x.place))]), r.spanish_names === undefined ? null : JSON.stringify({
689
+ file: r.spanish_names.file === true,
690
+ prints: [...new Set(r.spanish_names.prints.map((p) => p.trim()))].sort()
691
+ }));
692
+ const putRed = out.prepare("INSERT OR REPLACE INTO redirect VALUES (?, ?, ?)");
693
+ for (const r of readJsonl(rel.path("school-redirects.jsonl")))
694
+ putRed.run(r.old_id, r.new_id, r.kind);
695
+ }
696
+ };
697
+
698
+ class SchoolResolver2 {
699
+ rel;
700
+ constructor(rel) {
701
+ this.rel = rel;
702
+ }
703
+ async resolve(asked, q = {}) {
704
+ const db = await this.rel.indexDb("schools");
705
+ const reg = db.query("SELECT display, regions, places, spanish FROM registry WHERE school_id = ?");
706
+ const candidate = (id, by, rows, prints) => {
707
+ const r = reg.get(id);
708
+ return {
709
+ school_id: id,
710
+ display: r?.display ?? null,
711
+ rows,
712
+ by,
713
+ prints,
714
+ regions: r === null ? [] : JSON.parse(r.regions),
715
+ places: r === null ? [] : JSON.parse(r.places),
716
+ ...r?.spanish == null ? {} : { spanishNames: JSON.parse(r.spanish) }
717
+ };
718
+ };
719
+ const answer = (candidates, extra = {}) => {
720
+ let cs = candidates.sort((a, b) => b.rows - a.rows || (a.school_id < b.school_id ? -1 : 1));
721
+ let year;
722
+ if (q.year !== undefined && cs.length > 1 && cs.every((c) => c.years !== undefined)) {
723
+ const y = q.year;
724
+ const span = (c) => c.years;
725
+ let kept = cs.filter((c) => {
726
+ const { lo, hi } = span(c);
727
+ return lo !== null && hi !== null && lo <= y && y <= hi;
728
+ });
729
+ if (kept.length === 0) {
730
+ const before = cs.filter((c) => span(c).hi !== null && span(c).hi < y);
731
+ const last = Math.max(...before.map((c) => span(c).hi));
732
+ kept = before.filter((c) => span(c).hi === last);
733
+ }
734
+ year = { asked: y, narrowed: kept.length > 0 && kept.length < cs.length };
735
+ if (kept.length > 0)
736
+ cs = kept;
737
+ }
738
+ let region;
739
+ if (q.region !== undefined) {
740
+ const kept = cs.filter((c) => c.regions.length === 0 || c.regions.includes(q.region));
741
+ region = { asked: q.region, narrowed: kept.length > 0 && kept.length < cs.length };
742
+ if (kept.length > 0)
743
+ cs = kept;
744
+ }
745
+ return {
746
+ tag: this.rel.tag,
747
+ asked,
748
+ arm: cs.length === 0 ? "none" : cs.length === 1 ? "unique" : "ambiguous",
749
+ candidates: cs,
750
+ ...region !== undefined ? { region } : {},
751
+ ...year !== undefined ? { year } : {},
752
+ ...extra
753
+ };
754
+ };
755
+ const byId = (id, by) => {
756
+ const rs = db.query("SELECT print, rows FROM print_id WHERE school_id = ? ORDER BY print").all(id);
757
+ return candidate(id, by, rs.reduce((n, r) => n + r.rows, 0), rs.map((r) => r.print));
758
+ };
759
+ const a = asked.trim();
760
+ if (reg.get(a) !== null)
761
+ return answer([byId(a, "id")]);
762
+ let live = a;
763
+ const seen = new Set;
764
+ for (;; ) {
765
+ const r = db.query("SELECT new_id FROM redirect WHERE old_id = ?").get(live);
766
+ if (r === null || seen.has(r.new_id))
767
+ break;
768
+ seen.add(live);
769
+ live = r.new_id;
770
+ }
771
+ if (live !== a)
772
+ return answer([byId(live, "redirect")], { redirected: { from: a, to: live } });
773
+ const byPrints = (prints, by) => {
774
+ const ids = new Map;
775
+ const q2 = db.query("SELECT school_id, rows, year_lo AS lo, year_hi AS hi FROM print_id WHERE print = ?");
776
+ for (const p of prints)
777
+ for (const r of q2.all(p)) {
778
+ const e = ids.get(r.school_id) ?? {
779
+ rows: 0,
780
+ prints: new Set,
781
+ lo: null,
782
+ hi: null
783
+ };
784
+ e.rows += r.rows;
785
+ e.prints.add(p);
786
+ if (r.lo !== null && (e.lo === null || r.lo < e.lo))
787
+ e.lo = r.lo;
788
+ if (r.hi !== null && (e.hi === null || r.hi > e.hi))
789
+ e.hi = r.hi;
790
+ ids.set(r.school_id, e);
791
+ }
792
+ return [...ids].map(([id, e]) => ({
793
+ ...candidate(id, by, e.rows, [...e.prints].sort()),
794
+ years: { lo: e.lo, hi: e.hi }
795
+ }));
796
+ };
797
+ const exact = byPrints([a], "print");
798
+ if (exact.length > 0)
799
+ return answer(exact);
800
+ const choice = this.rel.schoolReader();
801
+ const read = choice.reader.schoolName(a);
802
+ if (!read.isSchool())
803
+ return answer([], {
804
+ refused: read.refusal?.reason ?? (read.isEmpty() ? "empty" : "not a school")
805
+ });
806
+ const names = createIndex(db, encoderOf(choice), {
807
+ table: TABLE
808
+ });
809
+ for (const level of ["sn3", "realWords"]) {
810
+ const prints = names.get(read, level);
811
+ if (prints.length > 0)
812
+ return answer(byPrints(prints, level));
813
+ }
814
+ return answer([]);
815
+ }
816
+ }
817
+
619
818
  var LINEAGE_FILE = "school-lineage.jsonl";
620
819
  function schoolLine(lineage, ids, year) {
621
820
  const reached = new Set(ids);
@@ -649,7 +848,7 @@ var dancersIndex = {
649
848
  keyParts: () => [...PACKAGE_KEY_PARTS],
650
849
  says: "every published dancer print at the names package's keys, its dancers, schools and years",
651
850
  build(rel, out) {
652
- const schema = indexSchema(ENCODER, PERSON);
851
+ const schema = indexSchema2(ENCODER, PERSON);
653
852
  out.run(schema.table);
654
853
  out.run("CREATE TABLE print_dancer(person INTEGER NOT NULL, dancer_id INTEGER, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER)");
655
854
  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)");
@@ -664,7 +863,7 @@ var dancersIndex = {
664
863
  count(*) AS n, min(c.year) AS lo, max(c.year) AS hi
665
864
  FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
666
865
  GROUP BY 1, 2, 3, 4, 5 ORDER BY 1, 2, 3, 4, 5`).all();
667
- const names = createIndex(out, ENCODER, PERSON);
866
+ const names = createIndex2(out, ENCODER, PERSON);
668
867
  const personOf = new Map;
669
868
  let next = 0;
670
869
  const idOf = (p, o) => {
@@ -776,6 +975,8 @@ var NAME_WEIGHT = {
776
975
  nn4: 3,
777
976
  initial: 2,
778
977
  middle: 1.5,
978
+ "given-middle": 1.25,
979
+ spanish: 1.25,
779
980
  form: 1.25,
780
981
  surname: 1,
781
982
  like: 1
@@ -909,14 +1110,22 @@ class DancerResolver2 {
909
1110
  unlinked: [],
910
1111
  refused: asked.printsNoName() ? "the print names no person" : "the print reads as no person (a bare word is a surname: search it)"
911
1112
  };
912
- const names = createIndex(db, ENCODER, PERSON);
1113
+ const names = createIndex2(db, ENCODER, PERSON);
913
1114
  const hits = new Map;
1115
+ const middleUsed = new Map;
914
1116
  for (const { value, row } of names.rows({ surname_key: [asked.surnameKey()], nn4: [asked.nn4] }, ["print", "reading"])) {
915
1117
  const c = ENCODER.decode(row);
916
1118
  const level = nameLevel2(asked, c);
917
1119
  if (level !== null)
918
1120
  hits.set(value, level);
919
- }
1121
+ else if (givenMiddleLevel(asked, c) !== null)
1122
+ middleUsed.set(value, "given-middle");
1123
+ }
1124
+ if (hits.size === 0)
1125
+ for (const [k, v] of middleUsed)
1126
+ hits.set(k, v);
1127
+ if (hits.size === 0 && q.school !== undefined)
1128
+ await this.spanishHits(db, asked, q.school, hits);
920
1129
  const exact = await this.answer(db, q, hits);
921
1130
  if (exact.arm !== "none")
922
1131
  return exact;
@@ -962,12 +1171,29 @@ class DancerResolver2 {
962
1171
  out.set(id, [...composed.via]);
963
1172
  continue;
964
1173
  }
965
- const kin = nameKinship(asked, c);
1174
+ const kin = nameKinship2(asked, c);
966
1175
  if (kin.relation !== "different")
967
1176
  out.set(id, [...kin.via]);
968
1177
  }
969
1178
  return out;
970
1179
  }
1180
+ async spanishHits(db, asked, school, hits) {
1181
+ const s = await this.rel.schools.resolve(school);
1182
+ if (s.candidates.length === 0)
1183
+ return;
1184
+ if (!readsSpanishNames2(s.candidates))
1185
+ return;
1186
+ const ids = s.candidates.map((c) => c.school_id);
1187
+ const persons = db.query(`SELECT DISTINCT p.id AS id, p.print AS print, p.reading AS reading
1188
+ FROM dancer_school s JOIN print_dancer x ON x.dancer_id = s.dancer_id
1189
+ JOIN person p ON p.id = x.person
1190
+ WHERE s.school_id IN (${ids.map(() => "?").join(",")}) ORDER BY p.id`).all(...ids);
1191
+ for (const r of persons) {
1192
+ const c = ENCODER.decode(r);
1193
+ if (spanishLevel(asked, c) !== null)
1194
+ hits.set(r.id, "spanish");
1195
+ }
1196
+ }
971
1197
  async search(q) {
972
1198
  if (q.name !== undefined) {
973
1199
  const a = await this.resolve({ ...q, name: q.name });
@@ -975,7 +1201,7 @@ class DancerResolver2 {
975
1201
  }
976
1202
  const db = await this.rel.indexDb("dancers");
977
1203
  const hits = new Map;
978
- const names = createIndex(db, ENCODER, PERSON);
1204
+ const names = createIndex2(db, ENCODER, PERSON);
979
1205
  if (q.surname !== undefined) {
980
1206
  const block = surnameBlock(foldNameKey(q.surname).toUpperCase());
981
1207
  if (block !== null)
@@ -1399,7 +1625,7 @@ function otherGiven(db, dancerId, asked) {
1399
1625
  continue;
1400
1626
  if (nameLevel2(asked, p) !== null)
1401
1627
  continue;
1402
- if (nameKinship(asked, p).relation !== "different")
1628
+ if (nameKinship2(asked, p).relation !== "different")
1403
1629
  continue;
1404
1630
  return `printed "${print}", another given name than the asked one, and met it only through an initial`;
1405
1631
  }
@@ -1508,6 +1734,7 @@ function contradictionFact(x, age, slack) {
1508
1734
  }
1509
1735
 
1510
1736
  import { Age as Age2 } from "@finbheara/domain/age";
1737
+ import { createCodeReader } from "@finbheara/domain/code";
1511
1738
  import {
1512
1739
  codePopulation,
1513
1740
  compTitle as compTitle2,
@@ -1613,6 +1840,7 @@ function canonicalOf2(level, age) {
1613
1840
  return null;
1614
1841
  return `${level / 100}${String(n).padStart(2, "0")}${level === 600 ? "PC" : "OC"}`;
1615
1842
  }
1843
+ var CODE_READER = createCodeReader({ standardized: ["quickfeis"] });
1616
1844
  var entryOf = (c) => ({
1617
1845
  title: c.title ?? "",
1618
1846
  ...c.age !== null ? { age: c.age } : {},
@@ -1825,6 +2053,14 @@ class CompResolver2 {
1825
2053
  return answer("none", null, [], {
1826
2054
  why: `the release holds no year of ${place.asked}'s series before ${place.year ?? "?"}`
1827
2055
  });
2056
+ const newestYear = earlier[0];
2057
+ const newestIds = await this.idsOf(place.brand_id, newestYear);
2058
+ const newestComps = await this.compsOf(newestIds);
2059
+ const platform = newestComps[0]?.platform ?? null;
2060
+ if (platform === null || CODE_READER.scheme(platform) === "feis-specific") {
2061
+ const noChamps = newestComps.some((c) => c.kind === "championship") ? "" : `, and the ${newestYear} syllabus, read from results, lists no championship`;
2062
+ return byTitle(newestIds, { feis_ids: newestIds, year: newestYear }, `${platform ?? "the series' platform"} numbers each feis-year's competitions its own way: code ${q.code} is read only in the ${place.year ?? "asked"} syllabus, which the release does not hold${noChamps}`);
2063
+ }
1828
2064
  for (const y of earlier) {
1829
2065
  const ids = await this.idsOf(place.brand_id, y);
1830
2066
  const hits = await this.compsOf(ids, q.code);
@@ -1865,8 +2101,8 @@ import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from
1865
2101
  import { join } from "path";
1866
2102
 
1867
2103
  import {
1868
- createIndex as createIndex2,
1869
- indexSchema as indexSchema2,
2104
+ createIndex as createIndex3,
2105
+ indexSchema as indexSchema3,
1870
2106
  roleEncoder
1871
2107
  } from "@finbheara/domain/noun-index";
1872
2108
  import { roleName } from "@finbheara/domain/person";
@@ -1900,10 +2136,10 @@ var staffIndex = {
1900
2136
  keyParts: () => [...PACKAGE_KEY_PARTS],
1901
2137
  says: "teachers and adjudicators by name and TD number: the schools they taught at, the register, the seats they sat",
1902
2138
  build(rel, out) {
1903
- const schema = indexSchema2(ENCODER2, WHO);
2139
+ const schema = indexSchema3(ENCODER2, WHO);
1904
2140
  out.run(schema.table);
1905
2141
  out.run(SCHEMA3);
1906
- const who = createIndex2(out, ENCODER2, WHO);
2142
+ const who = createIndex3(out, ENCODER2, WHO);
1907
2143
  const putEntry = out.prepare("INSERT INTO entry VALUES (?, ?, ?, ?, ?)");
1908
2144
  let next = 0;
1909
2145
  const add = (role, print, clrg, judgeId, schools, source) => {
@@ -2002,7 +2238,7 @@ class StaffFinder2 {
2002
2238
  if (!q.isPerson())
2003
2239
  refused = "the print reads as no person";
2004
2240
  else {
2005
- const who = createIndex2(db, ENCODER2, WHO);
2241
+ const who = createIndex3(db, ENCODER2, WHO);
2006
2242
  for (const { value, row } of who.rows({ surname_key: [q.surnameKey()], nn4: [q.nn4] })) {
2007
2243
  const p = ENCODER2.decode(row);
2008
2244
  if (p.role !== role)
@@ -2099,182 +2335,6 @@ class StaffFinder2 {
2099
2335
  }
2100
2336
  }
2101
2337
 
2102
- import { createIndex as createIndex3, indexSchema as indexSchema3 } from "@finbheara/domain/noun-index";
2103
- import {
2104
- DEFAULT_SCHOOL_NAME_READER,
2105
- schoolEncoder
2106
- } from "@finbheara/domain/school";
2107
- var GENERIC_SCHOOL_READER = {
2108
- id: "generic",
2109
- reader: DEFAULT_SCHOOL_NAME_READER
2110
- };
2111
- var encoderOf = (r) => schoolEncoder({ reader: r.reader });
2112
- var TABLE = "school_name";
2113
- var schoolsIndex = {
2114
- name: "schools",
2115
- version: "3",
2116
- inputs: ["results", "school-registry.jsonl", "school-redirects.jsonl"],
2117
- says: "every published school print and its ids, read at the school reader's slots",
2118
- keyParts: (rel) => [`reader=${rel.schoolReader().id}`],
2119
- build(rel, out) {
2120
- const choice = rel.schoolReader();
2121
- const encoder = encoderOf(choice);
2122
- out.run("CREATE TABLE print_id(print TEXT NOT NULL, school_id TEXT NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER, PRIMARY KEY(print, school_id)) WITHOUT ROWID");
2123
- out.run("CREATE TABLE registry(school_id TEXT PRIMARY KEY, display TEXT NOT NULL, regions TEXT NOT NULL, places TEXT NOT NULL)");
2124
- out.run("CREATE TABLE redirect(old_id TEXT PRIMARY KEY, new_id TEXT NOT NULL, kind TEXT NOT NULL)");
2125
- const schema = indexSchema3(encoder, { table: TABLE });
2126
- out.run(schema.table);
2127
- const src = rel.db("results");
2128
- const lined = src.query("PRAGMA table_info(results)").all().some((c) => c.name === "competition_line");
2129
- const pairs = src.query(lined ? `SELECT trim(r.school_printed) AS p, r.school_id AS s, count(*) AS n,
2130
- min(c.year) AS lo, max(c.year) AS hi
2131
- FROM results r LEFT JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
2132
- WHERE r.school_id IS NOT NULL GROUP BY 1, 2 ORDER BY 1, 2` : `SELECT trim(school_printed) AS p, school_id AS s, count(*) AS n, NULL AS lo, NULL AS hi
2133
- FROM results WHERE school_id IS NOT NULL GROUP BY 1, 2 ORDER BY 1, 2`).all();
2134
- const putPair = out.prepare("INSERT INTO print_id VALUES (?, ?, ?, ?, ?)");
2135
- for (const r of pairs)
2136
- putPair.run(r.p, r.s, r.n, r.lo, r.hi);
2137
- out.run("CREATE INDEX print_id_school ON print_id(school_id)");
2138
- const names = createIndex3(out, encoder, {
2139
- table: TABLE
2140
- });
2141
- let last = null;
2142
- for (const r of pairs) {
2143
- if (r.p === last)
2144
- continue;
2145
- last = r.p;
2146
- names.add(choice.reader.schoolName(r.p), r.p);
2147
- }
2148
- for (const s of schema.indexes)
2149
- out.run(s);
2150
- const putReg = out.prepare("INSERT OR REPLACE INTO registry VALUES (?, ?, ?, ?)");
2151
- for (const r of readJsonl(rel.path("school-registry.jsonl")))
2152
- putReg.run(r.school_id, r.display, JSON.stringify([...new Set(r.regions.map((x) => x.region))]), JSON.stringify([...new Set(r.locations.map((x) => x.place))]));
2153
- const putRed = out.prepare("INSERT OR REPLACE INTO redirect VALUES (?, ?, ?)");
2154
- for (const r of readJsonl(rel.path("school-redirects.jsonl")))
2155
- putRed.run(r.old_id, r.new_id, r.kind);
2156
- }
2157
- };
2158
-
2159
- class SchoolResolver2 {
2160
- rel;
2161
- constructor(rel) {
2162
- this.rel = rel;
2163
- }
2164
- async resolve(asked, q = {}) {
2165
- const db = await this.rel.indexDb("schools");
2166
- const reg = db.query("SELECT display, regions, places FROM registry WHERE school_id = ?");
2167
- const candidate = (id, by, rows, prints) => {
2168
- const r = reg.get(id);
2169
- return {
2170
- school_id: id,
2171
- display: r?.display ?? null,
2172
- rows,
2173
- by,
2174
- prints,
2175
- regions: r === null ? [] : JSON.parse(r.regions),
2176
- places: r === null ? [] : JSON.parse(r.places)
2177
- };
2178
- };
2179
- const answer = (candidates, extra = {}) => {
2180
- let cs = candidates.sort((a, b) => b.rows - a.rows || (a.school_id < b.school_id ? -1 : 1));
2181
- let year;
2182
- if (q.year !== undefined && cs.length > 1 && cs.every((c) => c.years !== undefined)) {
2183
- const y = q.year;
2184
- const span = (c) => c.years;
2185
- let kept = cs.filter((c) => {
2186
- const { lo, hi } = span(c);
2187
- return lo !== null && hi !== null && lo <= y && y <= hi;
2188
- });
2189
- if (kept.length === 0) {
2190
- const before = cs.filter((c) => span(c).hi !== null && span(c).hi < y);
2191
- const last = Math.max(...before.map((c) => span(c).hi));
2192
- kept = before.filter((c) => span(c).hi === last);
2193
- }
2194
- year = { asked: y, narrowed: kept.length > 0 && kept.length < cs.length };
2195
- if (kept.length > 0)
2196
- cs = kept;
2197
- }
2198
- let region;
2199
- if (q.region !== undefined) {
2200
- const kept = cs.filter((c) => c.regions.length === 0 || c.regions.includes(q.region));
2201
- region = { asked: q.region, narrowed: kept.length > 0 && kept.length < cs.length };
2202
- if (kept.length > 0)
2203
- cs = kept;
2204
- }
2205
- return {
2206
- tag: this.rel.tag,
2207
- asked,
2208
- arm: cs.length === 0 ? "none" : cs.length === 1 ? "unique" : "ambiguous",
2209
- candidates: cs,
2210
- ...region !== undefined ? { region } : {},
2211
- ...year !== undefined ? { year } : {},
2212
- ...extra
2213
- };
2214
- };
2215
- const byId = (id, by) => {
2216
- const rs = db.query("SELECT print, rows FROM print_id WHERE school_id = ? ORDER BY print").all(id);
2217
- return candidate(id, by, rs.reduce((n, r) => n + r.rows, 0), rs.map((r) => r.print));
2218
- };
2219
- const a = asked.trim();
2220
- if (reg.get(a) !== null)
2221
- return answer([byId(a, "id")]);
2222
- let live = a;
2223
- const seen = new Set;
2224
- for (;; ) {
2225
- const r = db.query("SELECT new_id FROM redirect WHERE old_id = ?").get(live);
2226
- if (r === null || seen.has(r.new_id))
2227
- break;
2228
- seen.add(live);
2229
- live = r.new_id;
2230
- }
2231
- if (live !== a)
2232
- return answer([byId(live, "redirect")], { redirected: { from: a, to: live } });
2233
- const byPrints = (prints, by) => {
2234
- const ids = new Map;
2235
- const q2 = db.query("SELECT school_id, rows, year_lo AS lo, year_hi AS hi FROM print_id WHERE print = ?");
2236
- for (const p of prints)
2237
- for (const r of q2.all(p)) {
2238
- const e = ids.get(r.school_id) ?? {
2239
- rows: 0,
2240
- prints: new Set,
2241
- lo: null,
2242
- hi: null
2243
- };
2244
- e.rows += r.rows;
2245
- e.prints.add(p);
2246
- if (r.lo !== null && (e.lo === null || r.lo < e.lo))
2247
- e.lo = r.lo;
2248
- if (r.hi !== null && (e.hi === null || r.hi > e.hi))
2249
- e.hi = r.hi;
2250
- ids.set(r.school_id, e);
2251
- }
2252
- return [...ids].map(([id, e]) => ({
2253
- ...candidate(id, by, e.rows, [...e.prints].sort()),
2254
- years: { lo: e.lo, hi: e.hi }
2255
- }));
2256
- };
2257
- const exact = byPrints([a], "print");
2258
- if (exact.length > 0)
2259
- return answer(exact);
2260
- const choice = this.rel.schoolReader();
2261
- const read = choice.reader.schoolName(a);
2262
- if (!read.isSchool())
2263
- return answer([], {
2264
- refused: read.refusal?.reason ?? (read.isEmpty() ? "empty" : "not a school")
2265
- });
2266
- const names = createIndex3(db, encoderOf(choice), {
2267
- table: TABLE
2268
- });
2269
- for (const level of ["sn3", "realWords"]) {
2270
- const prints = names.get(read, level);
2271
- if (prints.length > 0)
2272
- return answer(byPrints(prints, level));
2273
- }
2274
- return answer([]);
2275
- }
2276
- }
2277
-
2278
2338
  import { parquetMetadata, parquetReadObjects, parquetSchema } from "hyparquet";
2279
2339
  var COMPRESSORS = {
2280
2340
  ZSTD: (input) => new Uint8Array(Bun.zstdDecompressSync(input))
@@ -3723,4 +3783,4 @@ function releases2(opts = {}) {
3723
3783
  return new ReleaseManager2(opts);
3724
3784
  }
3725
3785
 
3726
- 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 };
3786
+ export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, readsSpanishNames2, SchoolResolver2, initialsOnly2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, StaffFinder2, 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-1ty89s8d.js";
7
+ } from "./chunks/index-9r4rw6xq.js";
8
8
  import"./chunks/index-hne1wadw.js";
9
9
  import"./chunks/index-hghdy980.js";
10
10
 
@@ -81,7 +81,8 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
81
81
  the register's and the panels' people: the same answer
82
82
  comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]
83
83
  a feis's own comp code to the comp it names, through that
84
- feis's syllabus (or the prior year's)
84
+ feis's syllabus (the prior year's only where the platform
85
+ numbers every feis alike; elsewhere the title)
85
86
  syllabus [--release <spec>] <feis> [year]
86
87
  one feis-year's comps, in printed order
87
88
  syllabus [--release <spec>] --for <series> [year]
package/dist/index.js CHANGED
@@ -12,13 +12,14 @@ import {
12
12
  FEIS_LOCAL_ID_FLOOR2,
13
13
  ResultFinder2,
14
14
  MajorsFinder2,
15
+ readsSpanishNames2,
16
+ SchoolResolver2,
15
17
  initialsOnly2,
16
18
  DancerResolver2,
17
19
  readPopulation2,
18
20
  canonicalOf2,
19
21
  CompResolver2,
20
22
  StaffFinder2,
21
- SchoolResolver2,
22
23
  TeamFinder2,
23
24
  EventFinder2,
24
25
  defaultBuilders2,
@@ -46,7 +47,7 @@ import {
46
47
  openCached2,
47
48
  ReleaseManager2,
48
49
  releases2
49
- } from "./chunks/index-1ty89s8d.js";
50
+ } from "./chunks/index-9r4rw6xq.js";
50
51
  import {
51
52
  cacheRoot2,
52
53
  repoOf2
@@ -108,6 +109,7 @@ export {
108
109
  planAsset2 as planAsset,
109
110
  readPin2 as readPin,
110
111
  readPopulation2 as readPopulation,
112
+ readsSpanishNames2 as readsSpanishNames,
111
113
  releases2 as releases,
112
114
  renderArchiveTag2 as renderArchiveTag,
113
115
  repoOf2 as repoOf,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import type { OpenOptions } from "./release.js";
3
3
  import type { StreamQuery } from "./stream.js";
4
- export declare const USAGE = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]\n\n config repo <owner/name> set the user-local repository the releases come from\n list [--remote] the cached releases (size, files, indexes, the pin), and\n with --remote the published tags\n fetch [--release <spec>] [--assets results,majors,<file>...]\n download, verify and cache a release (default: results)\n pin <spec> the user-local pin: what a bare open (spec \"pinned\") opens\n unpin remove the pin (a bare open is then \"latest\")\n current the pin, and whether it is cached\n add <dir> --tag <tag> add a release built locally (its MANIFEST.json and files)\n to the cache under its tag\n index [--release <spec>] [<name>...] [--force]\n build the per-tag indexes (default: every one)\n verify [--release <spec>] re-hash every cached file against its receipt\n capabilities [--release <spec>] what the release carries of what the client reads, from\n its own schema: each feature full or missing (its calls\n refuse, naming the first cut that publishes it)\n prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)\n and the pinned one\n rm <tag> [--dry-run] remove one cached release (not the pinned one)\n drop-indexes [<tag>] [--dry-run] remove the index files (of one release, or of every one)\n feis [--release <spec>] [--platform <p>] [--code <c>] [--id <id>] [--name <glob>] [--year <y>]\n the feises a query names\n series [--release <spec>] <name|host code|feis id>\n the series a name, a host code (fweb:ABC) or any feis id\n names: its years, hosts, codes and next instance\n school [--release <spec>] <print|school_id> [--region <code>]\n a school print or id to the published schools\n dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>\n [--order surname-first|given-first] [--school <print|id>]\n [--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]\n [--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]\n the dancers a name reaches, each with its evidence\n (name level, age, school, population, region) and the arm;\n --oireachtas is the regional the roster is for, its region in\n the release's spelling (SRO, MAO), its year the --as-of year\n where none is given; the results of\n the one dancer where one answers (or --rows for each)\n results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]\n [--school <print|id>] [--year <y>] [--limit <n>]\n the published result rows, prints and ids\n majors [--release <spec>] [--event <e>] [--year <y>] [--comp <competition>] [--dancer <id>]\n the majors: every event and its years; an event's\n competitions; a competition's, an event's or a dancer's rows\n stream [--release <spec>] --scope majors|transcribed|platform|all [--entries] [--event <e,...>]\n [--year <y> | --year <from>..<to>] [--tier <t,...>] [--category <c,...>]\n [--level <feis_level,...>] [--dancer <id,...>] [--school <school_id>] [--primary]\n every row a scope holds, one JSON line each, never the\n whole table in memory; --entries one per address (the row\n it stands for, with its recall order)\n records [--release <spec>] [--dancer <id,...>]\n every dancer's record: display, appearances, birth window,\n populations\n records --years [--release <spec>] [--dancer <id,...>] [--year <y>]\n each dancer's region, nationals and place per year\n feis-dates [--release <spec>] every feis's first and last day\n tabulated [--release <spec>] [--min-round <n>]\n the majors addresses the marks sheets tabulate to round n\n or later (default 3)\n teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]\n the team result rows of an event-year; with --rosters its\n roster teams and their dancers; with --joins how each roster\n team meets the results\n events [--release <spec>] [--event <e>] [--year <y>]\n each event-year's dates and place\n teacher [--release <spec>] <name|TD number>\n the staff lists' people: where they taught, the register,\n where they sat\n adjudicator [--release <spec>] <name|TD number>\n the register's and the panels' people: the same answer\n comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]\n a feis's own comp code to the comp it names, through that\n feis's syllabus (or the prior year's)\n syllabus [--release <spec>] <feis> [year]\n one feis-year's comps, in printed order\n syllabus [--release <spec>] --for <series> [year]\n a series' syllabus for a year: that year's where held,\n else the newest, saying which and what it is read from\n resolve comp [<feis> <code>] [--year <y>] [--title <t>]\n resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]\n [--order <o>] [--as-of <date>] [--population girls|boys]\n [--oireachtas <region>[:<year>]]\n resolve registrations\n JSONL out, one answer per line; with no positional the\n questions are JSONL on stdin, one per line (a comp:\n {feis, code, year?, title?}, a dancer: {name, school?, age?,\n population?, oireachtas?}, a registration: {name, school?,\n codes[], feis, year?, population?, oireachtas?})\n\n <spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)";
4
+ export declare const USAGE = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]\n\n config repo <owner/name> set the user-local repository the releases come from\n list [--remote] the cached releases (size, files, indexes, the pin), and\n with --remote the published tags\n fetch [--release <spec>] [--assets results,majors,<file>...]\n download, verify and cache a release (default: results)\n pin <spec> the user-local pin: what a bare open (spec \"pinned\") opens\n unpin remove the pin (a bare open is then \"latest\")\n current the pin, and whether it is cached\n add <dir> --tag <tag> add a release built locally (its MANIFEST.json and files)\n to the cache under its tag\n index [--release <spec>] [<name>...] [--force]\n build the per-tag indexes (default: every one)\n verify [--release <spec>] re-hash every cached file against its receipt\n capabilities [--release <spec>] what the release carries of what the client reads, from\n its own schema: each feature full or missing (its calls\n refuse, naming the first cut that publishes it)\n prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)\n and the pinned one\n rm <tag> [--dry-run] remove one cached release (not the pinned one)\n drop-indexes [<tag>] [--dry-run] remove the index files (of one release, or of every one)\n feis [--release <spec>] [--platform <p>] [--code <c>] [--id <id>] [--name <glob>] [--year <y>]\n the feises a query names\n series [--release <spec>] <name|host code|feis id>\n the series a name, a host code (fweb:ABC) or any feis id\n names: its years, hosts, codes and next instance\n school [--release <spec>] <print|school_id> [--region <code>]\n a school print or id to the published schools\n dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>\n [--order surname-first|given-first] [--school <print|id>]\n [--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]\n [--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]\n the dancers a name reaches, each with its evidence\n (name level, age, school, population, region) and the arm;\n --oireachtas is the regional the roster is for, its region in\n the release's spelling (SRO, MAO), its year the --as-of year\n where none is given; the results of\n the one dancer where one answers (or --rows for each)\n results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]\n [--school <print|id>] [--year <y>] [--limit <n>]\n the published result rows, prints and ids\n majors [--release <spec>] [--event <e>] [--year <y>] [--comp <competition>] [--dancer <id>]\n the majors: every event and its years; an event's\n competitions; a competition's, an event's or a dancer's rows\n stream [--release <spec>] --scope majors|transcribed|platform|all [--entries] [--event <e,...>]\n [--year <y> | --year <from>..<to>] [--tier <t,...>] [--category <c,...>]\n [--level <feis_level,...>] [--dancer <id,...>] [--school <school_id>] [--primary]\n every row a scope holds, one JSON line each, never the\n whole table in memory; --entries one per address (the row\n it stands for, with its recall order)\n records [--release <spec>] [--dancer <id,...>]\n every dancer's record: display, appearances, birth window,\n populations\n records --years [--release <spec>] [--dancer <id,...>] [--year <y>]\n each dancer's region, nationals and place per year\n feis-dates [--release <spec>] every feis's first and last day\n tabulated [--release <spec>] [--min-round <n>]\n the majors addresses the marks sheets tabulate to round n\n or later (default 3)\n teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]\n the team result rows of an event-year; with --rosters its\n roster teams and their dancers; with --joins how each roster\n team meets the results\n events [--release <spec>] [--event <e>] [--year <y>]\n each event-year's dates and place\n teacher [--release <spec>] <name|TD number>\n the staff lists' people: where they taught, the register,\n where they sat\n adjudicator [--release <spec>] <name|TD number>\n the register's and the panels' people: the same answer\n comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]\n a feis's own comp code to the comp it names, through that\n feis's syllabus (the prior year's only where the platform\n numbers every feis alike; elsewhere the title)\n syllabus [--release <spec>] <feis> [year]\n one feis-year's comps, in printed order\n syllabus [--release <spec>] --for <series> [year]\n a series' syllabus for a year: that year's where held,\n else the newest, saying which and what it is read from\n resolve comp [<feis> <code>] [--year <y>] [--title <t>]\n resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]\n [--order <o>] [--as-of <date>] [--population girls|boys]\n [--oireachtas <region>[:<year>]]\n resolve registrations\n JSONL out, one answer per line; with no positional the\n questions are JSONL on stdin, one per line (a comp:\n {feis, code, year?, title?}, a dancer: {name, school?, age?,\n population?, oireachtas?}, a registration: {name, school?,\n codes[], feis, year?, population?, oireachtas?})\n\n <spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)";
5
5
  export interface Parsed {
6
6
  positional: string[];
7
7
  flags: Map<string, string>;
@@ -121,6 +121,7 @@ interface CompRow {
121
121
  }
122
122
  export declare function compOf(r: CompRow, codes?: readonly string[]): Comp;
123
123
  export declare function canonicalOf(level: number | null, age: string | null): string | null;
124
+ export declare const CODE_READER: import("@finbheara/domain/code").CodeReader;
124
125
  export declare class CompResolver {
125
126
  private readonly rel;
126
127
  constructor(rel: Release);
@@ -1,5 +1,7 @@
1
1
  import { type PersonName, type TransitiveLevel } from "@finbheara/names/normalize";
2
- export type NameLevel = TransitiveLevel | "initial" | "middle" | "form" | "surname" | "like";
2
+ export type NameLevel = TransitiveLevel | "initial" | "middle" | "given-middle" | "spanish" | "form" | "surname" | "like";
3
3
  export declare const NAME_LEVELS: readonly NameLevel[];
4
4
  export declare const finer: (a: NameLevel, b: NameLevel) => NameLevel;
5
5
  export declare function nameLevel(q: PersonName, c: PersonName): NameLevel | null;
6
+ export declare function givenMiddleLevel(q: PersonName, c: PersonName): "given-middle" | null;
7
+ export declare function spanishLevel(q: PersonName, c: PersonName): "spanish" | null;
@@ -222,6 +222,7 @@ export declare class DancerResolver {
222
222
  }>;
223
223
  resolve(q: DancerQuery): Promise<DancerAnswer>;
224
224
  private formsOf;
225
+ private spanishHits;
225
226
  search(q: DancerSearchQuery): Promise<DancerAnswer>;
226
227
  private capped;
227
228
  get(dancerId: number): Promise<DancerRecord | null>;
@@ -13,7 +13,7 @@ export { type Remote, type RemoteAsset, fallbackRemote, ghRemote, httpsRemote, s
13
13
  export { type OpenOptions, Release, openCached, openRelease, resolveTag, } from "./release.js";
14
14
  export { type MajorCompetition, type MajorEvent, MajorsFinder, type ResultAnswer, ResultFinder, type ResultQuery, type ResultRow, } from "./results.js";
15
15
  export { type DancerAnswerLike, type DancerQueryLike, type DancerResolverLike, type DancerUnavailable, type RegistrationRow, type ResolvedDancer, RegistrationResolver, type ResolvedRegistration, dancerResolverOf, youngest, } from "./registrations.js";
16
- export { type SchoolAnswer, type SchoolCandidate, type SchoolQuery, SchoolResolver, type SchoolRung, } from "./schools.js";
16
+ export { type SchoolAnswer, type SchoolCandidate, type SchoolQuery, SchoolResolver, type SchoolRung, readsSpanishNames, type SpanishNames, } from "./schools.js";
17
17
  export { type Capability, FEATURES, type Feature, type SchemaProbe, capabilityOf, columnsOf, needsRelease, } from "./schema.js";
18
18
  export { type EntryRow, type StreamQuery, type StreamRow, type StreamScope, entryRank, } from "./stream.js";
19
19
  export { EventFinder, type EventYear, type EventYearQuery, type ResultRoster, type RosterMember, type RosterTeam, TeamFinder, type TeamResult, } from "./teams.js";
@@ -14,6 +14,11 @@ export interface SchoolCandidate {
14
14
  lo: number | null;
15
15
  hi: number | null;
16
16
  };
17
+ spanishNames?: SpanishNames;
18
+ }
19
+ export interface SpanishNames {
20
+ file: boolean;
21
+ prints: string[];
17
22
  }
18
23
  export interface SchoolAnswer {
19
24
  tag: string;
@@ -42,6 +47,7 @@ export interface SchoolReaderChoice {
42
47
  id: string;
43
48
  reader: SchoolNameParser;
44
49
  }
50
+ export declare function readsSpanishNames(candidates: readonly SchoolCandidate[]): boolean;
45
51
  export declare const GENERIC_SCHOOL_READER: SchoolReaderChoice;
46
52
  export declare const schoolsIndex: IndexBuilder;
47
53
  export declare class SchoolResolver {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Open a versioned release of an Irish dance results archive: download, verify, decompress and cache it by tag, keep the cache, and resolve feises and schools against it",
5
5
  "keywords": ["irish-dance", "results", "release", "cache", "sqlite"],
6
6
  "license": "MIT",
@@ -44,8 +44,8 @@
44
44
  "prepublishOnly": "bun scripts/build.ts && bun ../../tools/checks/archive-client-tarball.ts"
45
45
  },
46
46
  "dependencies": {
47
- "@finbheara/domain": "0.50.0",
48
- "@finbheara/names": "0.25.0",
47
+ "@finbheara/domain": "0.53.0",
48
+ "@finbheara/names": "0.26.0",
49
49
  "hyparquet": "1.31.2"
50
50
  },
51
51
  "devDependencies": {