@finbheara/archive-client 0.14.0 → 0.15.1
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 +15 -1
- package/dist/chunks/{index-hhhrh1db.js → index-9r4rw6xq.js} +241 -191
- package/dist/cli.js +1 -1
- package/dist/index.js +4 -2
- package/dist/types/dancers-names.d.ts +3 -1
- package/dist/types/dancers.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/schools.d.ts +6 -0
- package/package.json +3 -3
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`)
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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
|
|
1879
|
-
indexSchema as
|
|
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 =
|
|
2139
|
+
const schema = indexSchema3(ENCODER2, WHO);
|
|
1914
2140
|
out.run(schema.table);
|
|
1915
2141
|
out.run(SCHEMA3);
|
|
1916
|
-
const 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 =
|
|
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,
|
|
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
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-
|
|
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;
|
package/dist/types/dancers.d.ts
CHANGED
|
@@ -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>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/types/schools.d.ts
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.15.1",
|
|
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.
|
|
48
|
-
"@finbheara/names": "0.
|
|
47
|
+
"@finbheara/domain": "0.54.1",
|
|
48
|
+
"@finbheara/names": "0.27.1",
|
|
49
49
|
"hyparquet": "1.31.2"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|