@finbheara/archive-client 0.14.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
@@ -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
  }
@@ -1875,8 +2101,8 @@ import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from
1875
2101
  import { join } from "path";
1876
2102
 
1877
2103
  import {
1878
- createIndex as createIndex2,
1879
- indexSchema as indexSchema2,
2104
+ createIndex as createIndex3,
2105
+ indexSchema as indexSchema3,
1880
2106
  roleEncoder
1881
2107
  } from "@finbheara/domain/noun-index";
1882
2108
  import { roleName } from "@finbheara/domain/person";
@@ -1910,10 +2136,10 @@ var staffIndex = {
1910
2136
  keyParts: () => [...PACKAGE_KEY_PARTS],
1911
2137
  says: "teachers and adjudicators by name and TD number: the schools they taught at, the register, the seats they sat",
1912
2138
  build(rel, out) {
1913
- const schema = indexSchema2(ENCODER2, WHO);
2139
+ const schema = indexSchema3(ENCODER2, WHO);
1914
2140
  out.run(schema.table);
1915
2141
  out.run(SCHEMA3);
1916
- const who = createIndex2(out, ENCODER2, WHO);
2142
+ const who = createIndex3(out, ENCODER2, WHO);
1917
2143
  const putEntry = out.prepare("INSERT INTO entry VALUES (?, ?, ?, ?, ?)");
1918
2144
  let next = 0;
1919
2145
  const add = (role, print, clrg, judgeId, schools, source) => {
@@ -2012,7 +2238,7 @@ class StaffFinder2 {
2012
2238
  if (!q.isPerson())
2013
2239
  refused = "the print reads as no person";
2014
2240
  else {
2015
- const who = createIndex2(db, ENCODER2, WHO);
2241
+ const who = createIndex3(db, ENCODER2, WHO);
2016
2242
  for (const { value, row } of who.rows({ surname_key: [q.surnameKey()], nn4: [q.nn4] })) {
2017
2243
  const p = ENCODER2.decode(row);
2018
2244
  if (p.role !== role)
@@ -2109,182 +2335,6 @@ class StaffFinder2 {
2109
2335
  }
2110
2336
  }
2111
2337
 
2112
- import { createIndex as createIndex3, indexSchema as indexSchema3 } from "@finbheara/domain/noun-index";
2113
- import {
2114
- DEFAULT_SCHOOL_NAME_READER,
2115
- schoolEncoder
2116
- } from "@finbheara/domain/school";
2117
- var GENERIC_SCHOOL_READER = {
2118
- id: "generic",
2119
- reader: DEFAULT_SCHOOL_NAME_READER
2120
- };
2121
- var encoderOf = (r) => schoolEncoder({ reader: r.reader });
2122
- var TABLE = "school_name";
2123
- var schoolsIndex = {
2124
- name: "schools",
2125
- version: "3",
2126
- inputs: ["results", "school-registry.jsonl", "school-redirects.jsonl"],
2127
- says: "every published school print and its ids, read at the school reader's slots",
2128
- keyParts: (rel) => [`reader=${rel.schoolReader().id}`],
2129
- build(rel, out) {
2130
- const choice = rel.schoolReader();
2131
- const encoder = encoderOf(choice);
2132
- out.run("CREATE TABLE print_id(print TEXT NOT NULL, school_id TEXT NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER, PRIMARY KEY(print, school_id)) WITHOUT ROWID");
2133
- out.run("CREATE TABLE registry(school_id TEXT PRIMARY KEY, display TEXT NOT NULL, regions TEXT NOT NULL, places TEXT NOT NULL)");
2134
- out.run("CREATE TABLE redirect(old_id TEXT PRIMARY KEY, new_id TEXT NOT NULL, kind TEXT NOT NULL)");
2135
- const schema = indexSchema3(encoder, { table: TABLE });
2136
- out.run(schema.table);
2137
- const src = rel.db("results");
2138
- const lined = src.query("PRAGMA table_info(results)").all().some((c) => c.name === "competition_line");
2139
- const pairs = src.query(lined ? `SELECT trim(r.school_printed) AS p, r.school_id AS s, count(*) AS n,
2140
- min(c.year) AS lo, max(c.year) AS hi
2141
- FROM results r LEFT JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
2142
- WHERE r.school_id IS NOT NULL GROUP BY 1, 2 ORDER BY 1, 2` : `SELECT trim(school_printed) AS p, school_id AS s, count(*) AS n, NULL AS lo, NULL AS hi
2143
- FROM results WHERE school_id IS NOT NULL GROUP BY 1, 2 ORDER BY 1, 2`).all();
2144
- const putPair = out.prepare("INSERT INTO print_id VALUES (?, ?, ?, ?, ?)");
2145
- for (const r of pairs)
2146
- putPair.run(r.p, r.s, r.n, r.lo, r.hi);
2147
- out.run("CREATE INDEX print_id_school ON print_id(school_id)");
2148
- const names = createIndex3(out, encoder, {
2149
- table: TABLE
2150
- });
2151
- let last = null;
2152
- for (const r of pairs) {
2153
- if (r.p === last)
2154
- continue;
2155
- last = r.p;
2156
- names.add(choice.reader.schoolName(r.p), r.p);
2157
- }
2158
- for (const s of schema.indexes)
2159
- out.run(s);
2160
- const putReg = out.prepare("INSERT OR REPLACE INTO registry VALUES (?, ?, ?, ?)");
2161
- for (const r of readJsonl(rel.path("school-registry.jsonl")))
2162
- 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))]));
2163
- const putRed = out.prepare("INSERT OR REPLACE INTO redirect VALUES (?, ?, ?)");
2164
- for (const r of readJsonl(rel.path("school-redirects.jsonl")))
2165
- putRed.run(r.old_id, r.new_id, r.kind);
2166
- }
2167
- };
2168
-
2169
- class SchoolResolver2 {
2170
- rel;
2171
- constructor(rel) {
2172
- this.rel = rel;
2173
- }
2174
- async resolve(asked, q = {}) {
2175
- const db = await this.rel.indexDb("schools");
2176
- const reg = db.query("SELECT display, regions, places FROM registry WHERE school_id = ?");
2177
- const candidate = (id, by, rows, prints) => {
2178
- const r = reg.get(id);
2179
- return {
2180
- school_id: id,
2181
- display: r?.display ?? null,
2182
- rows,
2183
- by,
2184
- prints,
2185
- regions: r === null ? [] : JSON.parse(r.regions),
2186
- places: r === null ? [] : JSON.parse(r.places)
2187
- };
2188
- };
2189
- const answer = (candidates, extra = {}) => {
2190
- let cs = candidates.sort((a, b) => b.rows - a.rows || (a.school_id < b.school_id ? -1 : 1));
2191
- let year;
2192
- if (q.year !== undefined && cs.length > 1 && cs.every((c) => c.years !== undefined)) {
2193
- const y = q.year;
2194
- const span = (c) => c.years;
2195
- let kept = cs.filter((c) => {
2196
- const { lo, hi } = span(c);
2197
- return lo !== null && hi !== null && lo <= y && y <= hi;
2198
- });
2199
- if (kept.length === 0) {
2200
- const before = cs.filter((c) => span(c).hi !== null && span(c).hi < y);
2201
- const last = Math.max(...before.map((c) => span(c).hi));
2202
- kept = before.filter((c) => span(c).hi === last);
2203
- }
2204
- year = { asked: y, narrowed: kept.length > 0 && kept.length < cs.length };
2205
- if (kept.length > 0)
2206
- cs = kept;
2207
- }
2208
- let region;
2209
- if (q.region !== undefined) {
2210
- const kept = cs.filter((c) => c.regions.length === 0 || c.regions.includes(q.region));
2211
- region = { asked: q.region, narrowed: kept.length > 0 && kept.length < cs.length };
2212
- if (kept.length > 0)
2213
- cs = kept;
2214
- }
2215
- return {
2216
- tag: this.rel.tag,
2217
- asked,
2218
- arm: cs.length === 0 ? "none" : cs.length === 1 ? "unique" : "ambiguous",
2219
- candidates: cs,
2220
- ...region !== undefined ? { region } : {},
2221
- ...year !== undefined ? { year } : {},
2222
- ...extra
2223
- };
2224
- };
2225
- const byId = (id, by) => {
2226
- const rs = db.query("SELECT print, rows FROM print_id WHERE school_id = ? ORDER BY print").all(id);
2227
- return candidate(id, by, rs.reduce((n, r) => n + r.rows, 0), rs.map((r) => r.print));
2228
- };
2229
- const a = asked.trim();
2230
- if (reg.get(a) !== null)
2231
- return answer([byId(a, "id")]);
2232
- let live = a;
2233
- const seen = new Set;
2234
- for (;; ) {
2235
- const r = db.query("SELECT new_id FROM redirect WHERE old_id = ?").get(live);
2236
- if (r === null || seen.has(r.new_id))
2237
- break;
2238
- seen.add(live);
2239
- live = r.new_id;
2240
- }
2241
- if (live !== a)
2242
- return answer([byId(live, "redirect")], { redirected: { from: a, to: live } });
2243
- const byPrints = (prints, by) => {
2244
- const ids = new Map;
2245
- const q2 = db.query("SELECT school_id, rows, year_lo AS lo, year_hi AS hi FROM print_id WHERE print = ?");
2246
- for (const p of prints)
2247
- for (const r of q2.all(p)) {
2248
- const e = ids.get(r.school_id) ?? {
2249
- rows: 0,
2250
- prints: new Set,
2251
- lo: null,
2252
- hi: null
2253
- };
2254
- e.rows += r.rows;
2255
- e.prints.add(p);
2256
- if (r.lo !== null && (e.lo === null || r.lo < e.lo))
2257
- e.lo = r.lo;
2258
- if (r.hi !== null && (e.hi === null || r.hi > e.hi))
2259
- e.hi = r.hi;
2260
- ids.set(r.school_id, e);
2261
- }
2262
- return [...ids].map(([id, e]) => ({
2263
- ...candidate(id, by, e.rows, [...e.prints].sort()),
2264
- years: { lo: e.lo, hi: e.hi }
2265
- }));
2266
- };
2267
- const exact = byPrints([a], "print");
2268
- if (exact.length > 0)
2269
- return answer(exact);
2270
- const choice = this.rel.schoolReader();
2271
- const read = choice.reader.schoolName(a);
2272
- if (!read.isSchool())
2273
- return answer([], {
2274
- refused: read.refusal?.reason ?? (read.isEmpty() ? "empty" : "not a school")
2275
- });
2276
- const names = createIndex3(db, encoderOf(choice), {
2277
- table: TABLE
2278
- });
2279
- for (const level of ["sn3", "realWords"]) {
2280
- const prints = names.get(read, level);
2281
- if (prints.length > 0)
2282
- return answer(byPrints(prints, level));
2283
- }
2284
- return answer([]);
2285
- }
2286
- }
2287
-
2288
2338
  import { parquetMetadata, parquetReadObjects, parquetSchema } from "hyparquet";
2289
2339
  var COMPRESSORS = {
2290
2340
  ZSTD: (input) => new Uint8Array(Bun.zstdDecompressSync(input))
@@ -3733,4 +3783,4 @@ function releases2(opts = {}) {
3733
3783
  return new ReleaseManager2(opts);
3734
3784
  }
3735
3785
 
3736
- 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-hhhrh1db.js";
7
+ } from "./chunks/index-9r4rw6xq.js";
8
8
  import"./chunks/index-hne1wadw.js";
9
9
  import"./chunks/index-hghdy980.js";
10
10
 
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-hhhrh1db.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,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.14.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",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@finbheara/domain": "0.53.0",
48
- "@finbheara/names": "0.25.0",
48
+ "@finbheara/names": "0.26.0",
49
49
  "hyparquet": "1.31.2"
50
50
  },
51
51
  "devDependencies": {