@finbheara/archive-client 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -10
- package/dist/chunks/{index-4ph8n4ga.js → index-1sj5zr8k.js} +421 -237
- package/dist/cli.js +27 -8
- package/dist/index.js +3 -3
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/dancers.d.ts +27 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/presence.d.ts +2 -0
- package/dist/types/registrations.d.ts +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -216,11 +216,12 @@ const r = await rel.dancers.resolve({
|
|
|
216
216
|
age: { band: "U10", year: 2026 }, // or { yob: 2016 }, or { comp: "<competition>" }
|
|
217
217
|
asOf: "2026-10-17", // the school as of then
|
|
218
218
|
population: "girls", // or "boys": only a contradiction excludes
|
|
219
|
+
oireachtas: { region: "SRO", year: 2026 }, // the regional the roster is for (below)
|
|
219
220
|
});
|
|
220
221
|
r.arm; // "unique" | "ambiguous" | "none"
|
|
221
|
-
r.candidates; // [{ dancer_id, display, scope, score, evidence: { nameLevel, age, school }, prints, ... }]
|
|
222
|
-
r.excluded; // the candidates a contradiction set aside (why: "age" | "population")
|
|
223
|
-
r.preferred; // NON-BINDING: { dancer_id, why } where the school singles one out; never moves arm
|
|
222
|
+
r.candidates; // [{ dancer_id, display, scope, score, evidence: { nameLevel, age, school, population, region }, prints, ... }]
|
|
223
|
+
r.excluded; // the candidates a contradiction set aside (why: "age" | "population" | "region")
|
|
224
|
+
r.preferred; // NON-BINDING: { dancer_id, why } where the school (else the region) singles one out; never moves arm
|
|
224
225
|
r.unlinked; // rows printing a matched name that the release linked to no dancer
|
|
225
226
|
|
|
226
227
|
await rel.dancers.search({ surname: "Quellan" }); // or { like: "a* quellan" }, or { name }
|
|
@@ -234,7 +235,17 @@ level of the names package: the print, the key, the folded key, the tokens in an
|
|
|
234
235
|
(a middle name one print omits: `nameRelation`). `age` is how the dancer's published birth window
|
|
235
236
|
meets the asked age (`Age.fitsIn`, `overlaps`): `fits`, `overlaps`, `contradicts`, `unknown`.
|
|
236
237
|
`school` is how the dancer's schools meet the asked one: `same` (the latest school as of `asOf`),
|
|
237
|
-
`
|
|
238
|
+
`lineage` (the latest school is one the release's lineage joins to the asked one), `moved` (an
|
|
239
|
+
earlier one is the asked one), `none`, `unresolved` (the print reached no school).
|
|
240
|
+
|
|
241
|
+
THE LINEAGE is the release's `school-lineage.jsonl`: every line joins its `id` and its `other`,
|
|
242
|
+
whatever its direction (`precedes`, `succeeds`) or shape (a rename, a split, a merger, an absorb,
|
|
243
|
+
a relation), so a file carrying one end of an edge reads as one carrying both. The whole line
|
|
244
|
+
joins: every school reached through any number of edges, in either direction (the two halves of a
|
|
245
|
+
split are joined through their predecessor, the schools of a merger through their successor). An
|
|
246
|
+
earlier row at a joined school alone is no match. `r.school.lineage` lists the ids joined to the
|
|
247
|
+
asked ones. A release that publishes no lineage (feature `schools.lineage`) joins
|
|
248
|
+
nothing and its answer carries no `lineage`, never refused.
|
|
238
249
|
|
|
239
250
|
`population` is how the dancer's published majors population meets the asked one: `same`,
|
|
240
251
|
`contradicts`, `both`, `unknown`, `unasked`. A dancer's population is read only from its rows at
|
|
@@ -251,14 +262,66 @@ majors solo competitions and in none of the asked one's (listed in `excluded`, `
|
|
|
251
262
|
`"population"`, and a candidate contradicting both says `age`). A dancer published in both, or in
|
|
252
263
|
neither, stays. A population word that names no one population (`mixed`) narrows nothing, and the
|
|
253
264
|
answer says so (`r.population.note`). The school is evidence and order, never a reason
|
|
254
|
-
to drop anyone: candidates whose latest school is the asked one
|
|
255
|
-
danced for it, then the rest. A school row meets the asked school by its id, else by its print
|
|
265
|
+
to drop anyone: candidates whose latest school is the asked one, or one the lineage joins to it,
|
|
266
|
+
come first, then those that ever danced for it, then the rest. A school row meets the asked school by its id, else by its print
|
|
256
267
|
(one `rel.schools` resolves to the asked ids), so a latest print the release
|
|
257
268
|
filed under no id is still `same`. `unique` is one candidate left after the age, never a best
|
|
258
269
|
guess promoted. Where the school singles out one candidate, `preferred` names it with the reason,
|
|
259
|
-
for a caller to weigh, and `arm` stays as counted.
|
|
270
|
+
for a caller to weigh, and `arm` stays as counted. A `lineage` match ranks with `same`: one of
|
|
271
|
+
each singles out neither. The `score` orders candidates and is no
|
|
260
272
|
probability.
|
|
261
273
|
|
|
274
|
+
THE REGIONAL OIREACHTAS (`oireachtas: { region, year? }`). A dancer enters one
|
|
275
|
+
residence-qualifying regional Oireachtas a year. Two regionals in one year are two dancers, the
|
|
276
|
+
overseas entry excepted. The release already applies that rule to each dancer-year it publishes:
|
|
277
|
+
`dancer_years.region` with `region_source = "current"` is the one regional the dancer attended that
|
|
278
|
+
year, the dancer's own overseas entries left out and a year with two regionals given no `current`
|
|
279
|
+
region. Where a year has no regional, an Australian or New Zealand national stands in. The client
|
|
280
|
+
reads those rows and nothing else (`rel.dancers.years` serves them all).
|
|
281
|
+
|
|
282
|
+
- `region` is the release's own spelling, the codes `dancer_years.region` prints (`SRO`, `MAO`,
|
|
283
|
+
`ENG_SOUTH`, ...), case folded. A region the release reads no attended year in narrows and orders
|
|
284
|
+
nothing, and `r.oireachtas.note` says so.
|
|
285
|
+
- `year` is the regional's own calendar year, `asOf`'s year where none is given. The calendar year
|
|
286
|
+
is the only unit (an age is an age on 1 January of the competition's year, as
|
|
287
|
+
`@finbheara/domain/age` reads it), so a regional held in December 2026 is `year: 2026`, read
|
|
288
|
+
against `dancer_years.year` as it is, never shifted by month.
|
|
289
|
+
- THE DENOMINATOR is the candidate's attended (`current`) dancer-years at or before the asked
|
|
290
|
+
year, counted on the candidate (`region.attended`). Later years are not read. A `carried` or
|
|
291
|
+
`carried-back` row is not read: it repeats an attended year, which is read directly. A `school`
|
|
292
|
+
or `cocompetitor` row is not read: it is a vote of other dancers' regions, a prior for a
|
|
293
|
+
dancer-year with no card of its own, not evidence about this one.
|
|
294
|
+
- A `current` year may rest on a registration alone: a no-show still says where the dancer lives.
|
|
295
|
+
So it decides the region, and never alone a contradiction.
|
|
296
|
+
- A DANCED REGIONAL is a row at a `T3` competition that year whose address is on the floor. The
|
|
297
|
+
release publishes no residence flag, and on the event roster every `T3` event is a residence
|
|
298
|
+
qualifier and every residence qualifier is `T3`, so an attended year with a `T3` row was read from
|
|
299
|
+
a regional, and one without from a national standing in for one. ON THE FLOOR is the release's
|
|
300
|
+
own absence rule, read from the row's `place_printed`, `atts` and `withdrawn`: an address with a
|
|
301
|
+
printed absence (`NS`, `DNS`, `DNA`, `No Show`, `Did Not Attend`, `Did Not Compete`, in the place
|
|
302
|
+
cell or as an `atts` token) or a DNT (`withdrawn` `DNT`, or `did-not-start` in older releases) on
|
|
303
|
+
any of its rows danced nothing there. A bare `NP` or `NR` is a dancer placed nowhere, on the
|
|
304
|
+
floor.
|
|
305
|
+
|
|
306
|
+
`evidence.region` is one of:
|
|
307
|
+
|
|
308
|
+
| word | counts | weight |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| `contradicts` | the asked year is attended in another region, and the dancer danced a regional that year: set aside (`why: "region"`) | excluded |
|
|
311
|
+
| `same` | the latest attended year's region is the asked one | 2 where that year is the asked year, 1 where earlier |
|
|
312
|
+
| `moved` | the latest attended year's region is another one (an earlier year, or a national in the asked year) | 0 |
|
|
313
|
+
| `unknown` | no attended year at or before the asked year | 0 |
|
|
314
|
+
| `unasked` | no `oireachtas` asked | 0 |
|
|
315
|
+
|
|
316
|
+
`region` on a candidate is the deciding dancer-year (`{ year, region, regional, attended }`): the
|
|
317
|
+
asked year where it contradicts, else the latest attended one. `moved` weighs nothing: dancers
|
|
318
|
+
move, and no rate at which dancers move is measured, so it is said and never scored. Candidates
|
|
319
|
+
sort by the school first, then `same` before every other region word, then `score`. Where the
|
|
320
|
+
school singles out no one, `preferred` names the only candidate of the school's first rank (every
|
|
321
|
+
candidate where no school is asked) whose region is `same`. The asked entry's own printed location
|
|
322
|
+
is not asked, so an overseas entrant is asked without `oireachtas`. A release that publishes no
|
|
323
|
+
dancer-years (feature `dancers.years`) reads every candidate `unknown`, with the note.
|
|
324
|
+
|
|
262
325
|
A FEIS-LOCAL ID (`FEIS_LOCAL_ID_FLOOR`, 2^34, and up) is valid in its release only: a candidate and a result row
|
|
263
326
|
carrying one say `scope: "per-release"` (`dancer_scope` on a row), so a caller never stores one
|
|
264
327
|
across releases. Every answer carries the release tag.
|
|
@@ -474,7 +537,8 @@ feis's championships), the band is read from the row's own titles (`titles`, by
|
|
|
474
537
|
domain package's title reader, the youngest again, and `age.from` says `title`. Nothing else
|
|
475
538
|
stands in for a band. The dancer resolver is the release's own (`rel.dancers`, above), or the
|
|
476
539
|
`dancerResolver` option. With `dancerResolver: null`, each row's dancer answers `unavailable`
|
|
477
|
-
and the competitions still resolve.
|
|
540
|
+
and the competitions still resolve. A row's `oireachtas`, where the list is a regional's roster,
|
|
541
|
+
is passed to the dancer resolver as asked (`rel.dancers`, above).
|
|
478
542
|
|
|
479
543
|
## The command line
|
|
480
544
|
|
|
@@ -495,7 +559,7 @@ and the competitions still resolve.
|
|
|
495
559
|
idq dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>
|
|
496
560
|
[--order surname-first|given-first] [--school <print|id>]
|
|
497
561
|
[--yob <y> | --band <band> [--year <y>] | --comp <competition>] [--as-of <date>]
|
|
498
|
-
[--population girls|boys] [--rows]
|
|
562
|
+
[--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]
|
|
499
563
|
idq results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <c>] [--school <s>] [--year <y>] [--limit <n>]
|
|
500
564
|
idq majors [--release <spec>] [--event <e>] [--year <y>] [--comp <c>] [--dancer <id>] [--rows]
|
|
501
565
|
idq stream [--release <spec>] --scope majors|transcribed|platform|all [--entries] [--event <e,...>]
|
|
@@ -514,6 +578,7 @@ and the competitions still resolve.
|
|
|
514
578
|
idq syllabus [--release <spec>] --for <series> [year]
|
|
515
579
|
idq resolve comp [<feis> <code>] [--year y] [--title t]
|
|
516
580
|
idq resolve dancer [<name>] [--school s] [--comp <feis>/<code> | --yob y | --band b --year y]
|
|
581
|
+
[--oireachtas <region>[:<year>]]
|
|
517
582
|
idq resolve registrations
|
|
518
583
|
|
|
519
584
|
`stream` prints one JSON line per row (or entry), as it reads them. `dancer` prints the results of the one dancer where the answer is unique (every candidate's with
|
|
@@ -521,7 +586,8 @@ and the competitions still resolve.
|
|
|
521
586
|
|
|
522
587
|
`resolve` speaks JSON lines: one answer per line out, and with no positional question the
|
|
523
588
|
questions are JSON lines on stdin, one per line (`{feis, code, year?, title?}`,
|
|
524
|
-
`{name, school?, age?, population
|
|
589
|
+
`{name, school?, age?, population?, oireachtas?}`,
|
|
590
|
+
`{name, school?, codes, feis, year?, population?, oireachtas?}`). A line that is no JSON object,
|
|
525
591
|
or no question of its kind, answers `{tag, line, error}` and the other lines still answer. The
|
|
526
592
|
exit is non-zero where any line erred. A dancer's age may be given as a comp
|
|
527
593
|
(`{"comp": "<feis>/<code>"}`, or `--comp` on the command line): `rel.registrations.dancer` reads
|
|
@@ -106,6 +106,239 @@ function nameLevel2(q, c) {
|
|
|
106
106
|
return null;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
import { readFileSync } from "fs";
|
|
110
|
+
var SCHEMA = `
|
|
111
|
+
CREATE TABLE feis(
|
|
112
|
+
feis_id TEXT PRIMARY KEY, source TEXT NOT NULL, token TEXT NOT NULL, host_id TEXT NOT NULL,
|
|
113
|
+
name TEXT NOT NULL, date_lo TEXT, date_hi TEXT, date_printed TEXT, year INTEGER,
|
|
114
|
+
location TEXT, instance_id TEXT, brand_id TEXT, regions TEXT NOT NULL,
|
|
115
|
+
competitions INTEGER NOT NULL, rows INTEGER NOT NULL, captures INTEGER NOT NULL
|
|
116
|
+
);
|
|
117
|
+
CREATE TABLE redirect(old_id TEXT PRIMARY KEY, new_id TEXT NOT NULL, kind TEXT NOT NULL);
|
|
118
|
+
`;
|
|
119
|
+
var INDEXES = [
|
|
120
|
+
"CREATE INDEX feis_host ON feis(host_id COLLATE NOCASE)",
|
|
121
|
+
"CREATE INDEX feis_token ON feis(token)",
|
|
122
|
+
"CREATE INDEX feis_source ON feis(source)",
|
|
123
|
+
"CREATE INDEX feis_instance ON feis(instance_id)",
|
|
124
|
+
"CREATE INDEX feis_brand ON feis(brand_id)",
|
|
125
|
+
"CREATE INDEX feis_year ON feis(year)"
|
|
126
|
+
];
|
|
127
|
+
function readJsonl(path) {
|
|
128
|
+
return readFileSync(path, "utf8").split(`
|
|
129
|
+
`).filter((l) => l.trim() !== "").map((l) => JSON.parse(l));
|
|
130
|
+
}
|
|
131
|
+
var feisesIndex = {
|
|
132
|
+
name: "feises",
|
|
133
|
+
version: "2",
|
|
134
|
+
inputs: ["results", "feis-redirects.jsonl"],
|
|
135
|
+
says: "every feis by id, platform, code, series and name, with its counts and regions",
|
|
136
|
+
build(rel, out) {
|
|
137
|
+
out.run(SCHEMA);
|
|
138
|
+
const src = rel.db("results");
|
|
139
|
+
const counts = new Map;
|
|
140
|
+
for (const r of src.query("SELECT event, count(*) AS n, json_group_array(DISTINCT feis_region) AS regions FROM competitions GROUP BY event").all())
|
|
141
|
+
counts.set(r.event, {
|
|
142
|
+
comps: r.n,
|
|
143
|
+
regions: new Set(JSON.parse(r.regions).filter((x) => x !== null))
|
|
144
|
+
});
|
|
145
|
+
const rows = new Map;
|
|
146
|
+
for (const r of src.query("SELECT c.event AS event, sum(r.n) AS n FROM (SELECT competition, count(*) AS n FROM results GROUP BY competition) r JOIN (SELECT DISTINCT competition, event FROM competitions) c ON c.competition = r.competition GROUP BY c.event").all())
|
|
147
|
+
rows.set(r.event, r.n);
|
|
148
|
+
const put = out.prepare("INSERT OR IGNORE INTO feis VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
149
|
+
const captures = new Map;
|
|
150
|
+
const feisRows = src.query("SELECT feis_id, source, host_id, name_printed, date_lo, date_hi, date_printed, year, location_printed, feis_instance_id, feis_brand_id FROM feises ORDER BY file, line").all();
|
|
151
|
+
for (const f of feisRows)
|
|
152
|
+
captures.set(f.feis_id, (captures.get(f.feis_id) ?? 0) + 1);
|
|
153
|
+
for (const f of feisRows) {
|
|
154
|
+
const c = counts.get(f.feis_id);
|
|
155
|
+
const colon = f.feis_id.indexOf(":");
|
|
156
|
+
put.run(f.feis_id, f.source, colon === -1 ? f.source : f.feis_id.slice(0, colon), f.host_id, f.name_printed, f.date_lo, f.date_hi, f.date_printed, f.year, f.location_printed, f.feis_instance_id, f.feis_brand_id, JSON.stringify([...c?.regions ?? []].sort()), c?.comps ?? 0, rows.get(f.feis_id) ?? 0, captures.get(f.feis_id) ?? 0);
|
|
157
|
+
}
|
|
158
|
+
const red = out.prepare("INSERT OR REPLACE INTO redirect VALUES (?, ?, ?)");
|
|
159
|
+
for (const r of readJsonl(rel.path("feis-redirects.jsonl")))
|
|
160
|
+
red.run(r.old_id, r.new_id, r.kind);
|
|
161
|
+
for (const s of INDEXES)
|
|
162
|
+
out.run(s);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var NO_CALENDAR2 = "the release holds a feis-year once its results are captured and carries no calendar of scheduled feiseanna";
|
|
166
|
+
var globToLike = (g) => g.replace(/[\\%_]/g, (c) => `\\${c}`).replace(/\*/g, "%").replace(/\?/g, "_");
|
|
167
|
+
|
|
168
|
+
class FeisFinder2 {
|
|
169
|
+
rel;
|
|
170
|
+
constructor(rel) {
|
|
171
|
+
this.rel = rel;
|
|
172
|
+
}
|
|
173
|
+
async dates() {
|
|
174
|
+
await this.rel.fetch("results");
|
|
175
|
+
await this.rel.require("feises.dates");
|
|
176
|
+
const dates = this.rel.db("results").query("SELECT feis_id, min(date_lo) AS date_lo, max(date_hi) AS date_hi FROM feises GROUP BY feis_id ORDER BY feis_id").all();
|
|
177
|
+
return { tag: this.rel.tag, dates };
|
|
178
|
+
}
|
|
179
|
+
async find(q) {
|
|
180
|
+
const db = await this.rel.indexDb("feises");
|
|
181
|
+
const where = [];
|
|
182
|
+
const args = [];
|
|
183
|
+
let redirected;
|
|
184
|
+
if (q.platform !== undefined) {
|
|
185
|
+
where.push("(source = ? OR token = ?)");
|
|
186
|
+
args.push(q.platform, q.platform);
|
|
187
|
+
}
|
|
188
|
+
if (q.code !== undefined) {
|
|
189
|
+
where.push("host_id = ? COLLATE NOCASE");
|
|
190
|
+
args.push(q.code);
|
|
191
|
+
}
|
|
192
|
+
if (q.id !== undefined) {
|
|
193
|
+
let id = q.id;
|
|
194
|
+
const seen = new Set;
|
|
195
|
+
for (;; ) {
|
|
196
|
+
const r = db.query("SELECT new_id FROM redirect WHERE old_id = ?").get(id);
|
|
197
|
+
if (r === null || seen.has(r.new_id))
|
|
198
|
+
break;
|
|
199
|
+
seen.add(id);
|
|
200
|
+
id = r.new_id;
|
|
201
|
+
}
|
|
202
|
+
if (id !== q.id)
|
|
203
|
+
redirected = { from: q.id, to: id };
|
|
204
|
+
where.push("(feis_id = ? OR instance_id = ? OR brand_id = ?)");
|
|
205
|
+
args.push(id, id, id);
|
|
206
|
+
}
|
|
207
|
+
if (q.name !== undefined) {
|
|
208
|
+
where.push("name LIKE ? ESCAPE '\\'");
|
|
209
|
+
args.push(globToLike(q.name));
|
|
210
|
+
}
|
|
211
|
+
if (q.year !== undefined) {
|
|
212
|
+
where.push("year = ?");
|
|
213
|
+
args.push(q.year);
|
|
214
|
+
}
|
|
215
|
+
const sql = `SELECT * FROM feis${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY date_lo, feis_id`;
|
|
216
|
+
const feises = db.query(sql).all(...args).map((r) => ({
|
|
217
|
+
...r,
|
|
218
|
+
regions: JSON.parse(r.regions)
|
|
219
|
+
}));
|
|
220
|
+
return { tag: this.rel.tag, feises, ...redirected !== undefined ? { redirected } : {} };
|
|
221
|
+
}
|
|
222
|
+
async codeStem(token, stem) {
|
|
223
|
+
const db = await this.rel.indexDb("feises");
|
|
224
|
+
const like = db.query("SELECT host_id, year, brand_id FROM feis WHERE token = ? AND host_id LIKE ? ESCAPE '\\' COLLATE NOCASE").all(token, `${stem.replace(/[\\%_]/g, (c) => `\\${c}`)}__`);
|
|
225
|
+
const fits = like.filter((f) => {
|
|
226
|
+
const d = /(\d{2})$/.exec(f.host_id);
|
|
227
|
+
return d !== null && f.host_id.slice(0, -2).toLowerCase() === stem.toLowerCase() && f.year !== null && f.year % 100 === Number(d[1]) && f.brand_id !== null;
|
|
228
|
+
});
|
|
229
|
+
return [...new Set(fits.map((f) => f.brand_id))].sort();
|
|
230
|
+
}
|
|
231
|
+
async seriesOf(brand) {
|
|
232
|
+
const db = await this.rel.indexDb("feises");
|
|
233
|
+
const rows = db.query("SELECT * FROM feis WHERE brand_id = ? ORDER BY year, date_lo, feis_id").all(brand);
|
|
234
|
+
const byYear = new Map;
|
|
235
|
+
for (const r of rows) {
|
|
236
|
+
const i = byYear.get(r.year);
|
|
237
|
+
if (i === undefined)
|
|
238
|
+
byYear.set(r.year, {
|
|
239
|
+
year: r.year,
|
|
240
|
+
feis_ids: [r.feis_id],
|
|
241
|
+
date_lo: r.date_lo,
|
|
242
|
+
name: r.name
|
|
243
|
+
});
|
|
244
|
+
else
|
|
245
|
+
i.feis_ids.push(r.feis_id);
|
|
246
|
+
}
|
|
247
|
+
const instances = [...byYear.values()];
|
|
248
|
+
const newest = rows[rows.length - 1];
|
|
249
|
+
return {
|
|
250
|
+
series_id: brand,
|
|
251
|
+
name: newest?.name ?? "",
|
|
252
|
+
host: newest?.source ?? "",
|
|
253
|
+
hosts: [...new Set(rows.map((r) => r.source))].sort(),
|
|
254
|
+
codes: [...new Set(rows.map((r) => r.host_id))].sort(),
|
|
255
|
+
years: instances.map((i) => i.year).filter((y) => y !== null),
|
|
256
|
+
instances
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async series(q) {
|
|
260
|
+
const asked = q.trim();
|
|
261
|
+
const answer = async (brands, via, more = {}) => {
|
|
262
|
+
const candidates = [];
|
|
263
|
+
for (const b of brands)
|
|
264
|
+
candidates.push(await this.seriesOf(b));
|
|
265
|
+
const arm = candidates.length === 0 ? "none" : candidates.length === 1 ? "unique" : "ambiguous";
|
|
266
|
+
const u = arm === "unique" ? candidates[0] : null;
|
|
267
|
+
return {
|
|
268
|
+
tag: this.rel.tag,
|
|
269
|
+
asked,
|
|
270
|
+
arm,
|
|
271
|
+
via: candidates.length === 0 ? null : via,
|
|
272
|
+
series_id: u?.series_id ?? null,
|
|
273
|
+
name: u?.name ?? null,
|
|
274
|
+
host: u?.host ?? null,
|
|
275
|
+
years: u?.years ?? [],
|
|
276
|
+
instances: u?.instances ?? [],
|
|
277
|
+
next: null,
|
|
278
|
+
...u !== null ? { next_why: NO_CALENDAR2 } : {},
|
|
279
|
+
candidates,
|
|
280
|
+
...arm === "ambiguous" ? { why: `${candidates.length} series answer "${asked}": ${brands.join(", ")}` } : {},
|
|
281
|
+
...more
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
if (asked === "")
|
|
285
|
+
return answer([], null, { why: "no series was asked" });
|
|
286
|
+
const byId = await this.find({ id: asked });
|
|
287
|
+
if (byId.feises.length > 0) {
|
|
288
|
+
const brands = [...new Set(byId.feises.map((f) => f.brand_id))];
|
|
289
|
+
const filed = brands.filter((b) => b !== null).sort();
|
|
290
|
+
const redirected = byId.redirected !== undefined ? { redirected: byId.redirected } : {};
|
|
291
|
+
if (filed.length === 0)
|
|
292
|
+
return answer([], null, {
|
|
293
|
+
...redirected,
|
|
294
|
+
why: `the release files ${byId.feises.map((f) => f.feis_id).join(", ")} under no series`
|
|
295
|
+
});
|
|
296
|
+
return answer(filed, "id", redirected);
|
|
297
|
+
}
|
|
298
|
+
const code = /^([^:\s]+):(.*?)(\d{2})?$/.exec(asked);
|
|
299
|
+
if (code !== null) {
|
|
300
|
+
const [, token, stem] = code;
|
|
301
|
+
const brands = stem === "" ? [] : await this.codeStem(token, stem);
|
|
302
|
+
if (brands.length > 0)
|
|
303
|
+
return answer(brands, "code-stem");
|
|
304
|
+
}
|
|
305
|
+
const glob = /[*?]/.test(asked) ? asked : `*${asked}*`;
|
|
306
|
+
const byName = await this.find({ name: glob });
|
|
307
|
+
const brands = [
|
|
308
|
+
...new Set(byName.feises.map((f) => f.brand_id).filter((b) => b !== null))
|
|
309
|
+
].sort();
|
|
310
|
+
if (brands.length > 0)
|
|
311
|
+
return answer(brands, "name");
|
|
312
|
+
return answer([], null, {
|
|
313
|
+
why: byName.feises.length > 0 ? `the feises named "${asked}" are filed under no series` : `the release holds no feis or series "${asked}"`
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
var NO_PLACE = new Set(["NP", "NR", "N/A", "NA", "NO RANK", "NORANK", "NO PLACE", "-", "--"]);
|
|
319
|
+
var ABSENT = new Set(["DID NOT COMPETE", "DID NOT ATTEND", "NO SHOW", "DNA", "DNS", "NS"]);
|
|
320
|
+
var ABSENT_WORDS = /not\s*attend|no\s*show/i;
|
|
321
|
+
var DID_NOT_TABULATE = new Set(["DNT", "did-not-start"]);
|
|
322
|
+
function printsAbsence(print) {
|
|
323
|
+
const s = print.trim();
|
|
324
|
+
if (s === "")
|
|
325
|
+
return false;
|
|
326
|
+
const upper = s.toUpperCase();
|
|
327
|
+
if (NO_PLACE.has(upper))
|
|
328
|
+
return false;
|
|
329
|
+
return ABSENT.has(upper) || ABSENT_WORDS.test(s);
|
|
330
|
+
}
|
|
331
|
+
function notOnTheFloor(placePrinted, atts, withdrawn) {
|
|
332
|
+
if (printsAbsence(placePrinted ?? ""))
|
|
333
|
+
return true;
|
|
334
|
+
if (atts !== null && atts !== "") {
|
|
335
|
+
for (const a of atts.split(","))
|
|
336
|
+
if (printsAbsence(a))
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
return withdrawn !== null && DID_NOT_TABULATE.has(withdrawn);
|
|
340
|
+
}
|
|
341
|
+
|
|
109
342
|
var ENTRY_RANK = new Map([
|
|
110
343
|
["final", 0],
|
|
111
344
|
["", 1],
|
|
@@ -375,6 +608,7 @@ class MajorsFinder2 {
|
|
|
375
608
|
}
|
|
376
609
|
}
|
|
377
610
|
|
|
611
|
+
var LINEAGE_FILE = "school-lineage.jsonl";
|
|
378
612
|
function nameOrderOf(s) {
|
|
379
613
|
return s === "surname-first" || s === "given-first" || s === "unknown" ? s : undefined;
|
|
380
614
|
}
|
|
@@ -387,7 +621,7 @@ var PERSON = { table: "person", value: "id", valueType: "INTEGER PRIMARY KEY" };
|
|
|
387
621
|
var ASKED = ["surname_key", "nn4", "blk_surname"];
|
|
388
622
|
var dancersIndex = {
|
|
389
623
|
name: "dancers",
|
|
390
|
-
version: "
|
|
624
|
+
version: "4",
|
|
391
625
|
inputs: ["results"],
|
|
392
626
|
keyParts: () => [...PACKAGE_KEY_PARTS],
|
|
393
627
|
says: "every published dancer print at the names package's keys, its dancers, schools and years",
|
|
@@ -398,6 +632,7 @@ var dancersIndex = {
|
|
|
398
632
|
out.run("CREATE TABLE dancer(dancer_id INTEGER PRIMARY KEY, display TEXT NOT NULL, names_printed TEXT NOT NULL, genders TEXT NOT NULL, appearances INTEGER NOT NULL, kind TEXT, birth_lo INTEGER, birth_hi INTEGER, birth_source TEXT NOT NULL, birth_conflict INTEGER NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER)");
|
|
399
633
|
out.run("CREATE TABLE dancer_school(dancer_id INTEGER NOT NULL, school_id TEXT, print TEXT NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER)");
|
|
400
634
|
out.run("CREATE TABLE dancer_population(dancer_id INTEGER NOT NULL, population TEXT NOT NULL, rows INTEGER NOT NULL, PRIMARY KEY(dancer_id, population)) WITHOUT ROWID");
|
|
635
|
+
out.run("CREATE TABLE dancer_regional(dancer_id INTEGER NOT NULL, year INTEGER NOT NULL, region TEXT NOT NULL, regional INTEGER NOT NULL, PRIMARY KEY(dancer_id, year)) WITHOUT ROWID");
|
|
401
636
|
const src = rel.db("results");
|
|
402
637
|
const groups = src.query(`SELECT r.name_printed AS p, r.name_order_resolved AS o, r.dancer_id AS d,
|
|
403
638
|
r.school_printed AS sp, r.school_id AS s,
|
|
@@ -472,6 +707,25 @@ var dancersIndex = {
|
|
|
472
707
|
if (pop === "girls" || pop === "boys")
|
|
473
708
|
putPop.run(r.d, pop, r.n);
|
|
474
709
|
}
|
|
710
|
+
if (src.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'dancer_years'").get() !== null) {
|
|
711
|
+
const cols = new Set(src.query("PRAGMA table_info(results)").all().map((c) => c.name));
|
|
712
|
+
const col = (c) => cols.has(c) ? `r.${c}` : "NULL";
|
|
713
|
+
const rows = src.query(`SELECT r.dancer_id AS d, c.year AS y, r.address AS a, ${col("place_printed")} AS p,
|
|
714
|
+
${col("atts")} AS t, ${col("withdrawn")} AS w
|
|
715
|
+
FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
|
|
716
|
+
WHERE r.dancer_id IS NOT NULL AND c.tier = 'T3' AND c.year IS NOT NULL`).all();
|
|
717
|
+
const absent = new Set;
|
|
718
|
+
for (const r of rows)
|
|
719
|
+
if (notOnTheFloor(r.p, r.t, r.w))
|
|
720
|
+
absent.add(r.a);
|
|
721
|
+
const t3 = new Set;
|
|
722
|
+
for (const r of rows)
|
|
723
|
+
if (!absent.has(r.a))
|
|
724
|
+
t3.add(`${r.d}\x00${r.y}`);
|
|
725
|
+
const putReg = out.prepare("INSERT INTO dancer_regional VALUES (?, ?, ?, ?)");
|
|
726
|
+
for (const r of src.query("SELECT dancer_id AS d, year AS y, region AS r FROM dancer_years WHERE region_source = 'current' AND region IS NOT NULL").all())
|
|
727
|
+
putReg.run(r.d, r.y, r.r, t3.has(`${r.d}\x00${r.y}`) ? 1 : 0);
|
|
728
|
+
}
|
|
475
729
|
for (const s of schema.indexes)
|
|
476
730
|
if (ASKED.some((c) => s.endsWith(`(${c})`)))
|
|
477
731
|
out.run(s);
|
|
@@ -506,11 +760,17 @@ var AGE_WEIGHT = {
|
|
|
506
760
|
};
|
|
507
761
|
var SCHOOL_RANK = {
|
|
508
762
|
same: 0,
|
|
763
|
+
lineage: 0,
|
|
509
764
|
moved: 1,
|
|
510
765
|
none: 2,
|
|
511
766
|
unresolved: 2,
|
|
512
767
|
unasked: 2
|
|
513
768
|
};
|
|
769
|
+
var PREFERRED_WHY = {
|
|
770
|
+
same: "whose latest school is",
|
|
771
|
+
lineage: "whose latest school the release's lineage joins to",
|
|
772
|
+
moved: "that ever danced for"
|
|
773
|
+
};
|
|
514
774
|
var POPULATION_WEIGHT = {
|
|
515
775
|
same: 1,
|
|
516
776
|
both: 0,
|
|
@@ -518,8 +778,24 @@ var POPULATION_WEIGHT = {
|
|
|
518
778
|
unasked: 0,
|
|
519
779
|
contradicts: -10
|
|
520
780
|
};
|
|
781
|
+
var REGION_WEIGHT = {
|
|
782
|
+
same: 1,
|
|
783
|
+
unknown: 0,
|
|
784
|
+
unasked: 0,
|
|
785
|
+
moved: 0,
|
|
786
|
+
contradicts: -10
|
|
787
|
+
};
|
|
788
|
+
var REGION_SAME_THAT_YEAR = 2;
|
|
789
|
+
var REGION_RANK = {
|
|
790
|
+
same: 0,
|
|
791
|
+
unknown: 1,
|
|
792
|
+
unasked: 1,
|
|
793
|
+
moved: 1,
|
|
794
|
+
contradicts: 2
|
|
795
|
+
};
|
|
521
796
|
var SCHOOL_WEIGHT = {
|
|
522
797
|
same: 2,
|
|
798
|
+
lineage: 2,
|
|
523
799
|
moved: 1,
|
|
524
800
|
none: 0,
|
|
525
801
|
unresolved: 0,
|
|
@@ -535,9 +811,31 @@ function idChunks(ids) {
|
|
|
535
811
|
class DancerResolver2 {
|
|
536
812
|
rel;
|
|
537
813
|
printIds = new Map;
|
|
814
|
+
lineageOf;
|
|
815
|
+
regions;
|
|
538
816
|
constructor(rel) {
|
|
539
817
|
this.rel = rel;
|
|
540
818
|
}
|
|
819
|
+
lineage() {
|
|
820
|
+
this.lineageOf ??= (async () => {
|
|
821
|
+
if ((await this.rel.capability("schools.lineage")).state !== "full")
|
|
822
|
+
return null;
|
|
823
|
+
await this.rel.fetch(LINEAGE_FILE);
|
|
824
|
+
const out = new Map;
|
|
825
|
+
const join = (a, b) => {
|
|
826
|
+
const s = out.get(a) ?? new Set;
|
|
827
|
+
s.add(b);
|
|
828
|
+
out.set(a, s);
|
|
829
|
+
};
|
|
830
|
+
for (const r of readJsonl(this.rel.path(LINEAGE_FILE)))
|
|
831
|
+
if (r.id !== r.other) {
|
|
832
|
+
join(r.id, r.other);
|
|
833
|
+
join(r.other, r.id);
|
|
834
|
+
}
|
|
835
|
+
return out;
|
|
836
|
+
})();
|
|
837
|
+
return this.lineageOf;
|
|
838
|
+
}
|
|
541
839
|
async records(q = {}) {
|
|
542
840
|
await this.rel.fetch("results");
|
|
543
841
|
await this.rel.require("dancers.records");
|
|
@@ -642,12 +940,29 @@ class DancerResolver2 {
|
|
|
642
940
|
const asOf = yearOf(q.asOf);
|
|
643
941
|
const age = q.age === undefined ? undefined : this.readAge(q.age, asOf);
|
|
644
942
|
const population = q.population === undefined ? undefined : readPopulation2(q.population);
|
|
943
|
+
const oireachtas = q.oireachtas === undefined ? undefined : await this.readOireachtas(db, q.oireachtas, asOf);
|
|
645
944
|
let schoolIds = null;
|
|
945
|
+
let lineageIds = [];
|
|
646
946
|
let school;
|
|
647
947
|
if (q.school !== undefined) {
|
|
648
948
|
const s = await this.rel.schools.resolve(q.school);
|
|
649
949
|
schoolIds = s.candidates.map((c) => c.school_id);
|
|
650
|
-
|
|
950
|
+
const lineage = await this.lineage();
|
|
951
|
+
if (lineage === null)
|
|
952
|
+
school = { arm: s.arm, ids: schoolIds };
|
|
953
|
+
else {
|
|
954
|
+
const asked = new Set(schoolIds);
|
|
955
|
+
const reached = new Set(schoolIds);
|
|
956
|
+
const todo = [...schoolIds];
|
|
957
|
+
for (let id = todo.pop();id !== undefined; id = todo.pop())
|
|
958
|
+
for (const o of lineage.get(id) ?? [])
|
|
959
|
+
if (!reached.has(o)) {
|
|
960
|
+
reached.add(o);
|
|
961
|
+
todo.push(o);
|
|
962
|
+
}
|
|
963
|
+
lineageIds = [...reached].filter((id) => !asked.has(id)).sort();
|
|
964
|
+
school = { arm: s.arm, ids: schoolIds, lineage: lineageIds };
|
|
965
|
+
}
|
|
651
966
|
}
|
|
652
967
|
const byDancer = new Map;
|
|
653
968
|
const unlinked = new Map;
|
|
@@ -674,24 +989,37 @@ class DancerResolver2 {
|
|
|
674
989
|
const ageEv = ageEvidence(d, age?.age ?? null);
|
|
675
990
|
const pop = populationOf(db, id);
|
|
676
991
|
const popEv = populationEvidence(pop, population?.read ?? null);
|
|
677
|
-
const schoolEv = schoolIds === null ? "unasked" : schoolIds.length === 0 ? "unresolved" : await this.schoolEvidence(schools, schoolIds, asOf);
|
|
992
|
+
const schoolEv = schoolIds === null ? "unasked" : schoolIds.length === 0 ? "unresolved" : await this.schoolEvidence(schools, schoolIds, lineageIds, asOf);
|
|
993
|
+
const reg = oireachtas === undefined ? null : oireachtas.read === null ? {
|
|
994
|
+
word: "unknown",
|
|
995
|
+
region: { year: null, region: null, regional: false, attended: 0 }
|
|
996
|
+
} : regionEvidence(db, id, oireachtas.read, oireachtas.year);
|
|
997
|
+
const regionEv = reg === null ? "unasked" : reg.word;
|
|
998
|
+
const regionWeight = regionEv === "same" && reg?.region.year === oireachtas?.year ? REGION_SAME_THAT_YEAR : REGION_WEIGHT[regionEv];
|
|
678
999
|
all.push({
|
|
679
1000
|
dancer_id: id,
|
|
680
1001
|
display: d.display,
|
|
681
1002
|
scope: id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable",
|
|
682
1003
|
kind: d.kind,
|
|
683
|
-
score: NAME_WEIGHT[e.level] + AGE_WEIGHT[ageEv] + SCHOOL_WEIGHT[schoolEv] + POPULATION_WEIGHT[popEv],
|
|
684
|
-
evidence: {
|
|
1004
|
+
score: NAME_WEIGHT[e.level] + AGE_WEIGHT[ageEv] + SCHOOL_WEIGHT[schoolEv] + POPULATION_WEIGHT[popEv] + regionWeight,
|
|
1005
|
+
evidence: {
|
|
1006
|
+
nameLevel: e.level,
|
|
1007
|
+
age: ageEv,
|
|
1008
|
+
school: schoolEv,
|
|
1009
|
+
population: popEv,
|
|
1010
|
+
region: regionEv
|
|
1011
|
+
},
|
|
685
1012
|
prints: e.prints.sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
|
|
686
1013
|
rows: d.rows,
|
|
687
1014
|
year_lo: d.year_lo,
|
|
688
1015
|
year_hi: d.year_hi,
|
|
689
1016
|
birth: birthOf(d),
|
|
690
1017
|
population: pop,
|
|
691
|
-
school: latest
|
|
1018
|
+
school: latest,
|
|
1019
|
+
...reg !== null ? { region: reg.region } : {}
|
|
692
1020
|
});
|
|
693
1021
|
}
|
|
694
|
-
all.sort((a, b) => SCHOOL_RANK[a.evidence.school] - SCHOOL_RANK[b.evidence.school] || b.score - a.score || b.rows - a.rows || a.dancer_id - b.dancer_id);
|
|
1022
|
+
all.sort((a, b) => SCHOOL_RANK[a.evidence.school] - SCHOOL_RANK[b.evidence.school] || REGION_RANK[a.evidence.region] - REGION_RANK[b.evidence.region] || b.score - a.score || b.rows - a.rows || a.dancer_id - b.dancer_id);
|
|
695
1023
|
const excluded = [];
|
|
696
1024
|
let kept = all;
|
|
697
1025
|
if (narrow)
|
|
@@ -700,23 +1028,38 @@ class DancerResolver2 {
|
|
|
700
1028
|
excluded.push({ candidate: c, why: "age" });
|
|
701
1029
|
else if (c.evidence.population === "contradicts")
|
|
702
1030
|
excluded.push({ candidate: c, why: "population" });
|
|
1031
|
+
else if (c.evidence.region === "contradicts")
|
|
1032
|
+
excluded.push({ candidate: c, why: "region" });
|
|
703
1033
|
else
|
|
704
1034
|
return true;
|
|
705
1035
|
return false;
|
|
706
1036
|
});
|
|
707
1037
|
let preferred;
|
|
708
1038
|
if (kept.length > 1 && school !== undefined)
|
|
709
|
-
for (const
|
|
710
|
-
const hit = kept.filter((c) => SCHOOL_RANK[c.evidence.school] ===
|
|
1039
|
+
for (const rank of [SCHOOL_RANK.same, SCHOOL_RANK.moved]) {
|
|
1040
|
+
const hit = kept.filter((c) => SCHOOL_RANK[c.evidence.school] === rank);
|
|
711
1041
|
if (hit.length === 0)
|
|
712
1042
|
continue;
|
|
713
|
-
if (hit.length === 1)
|
|
1043
|
+
if (hit.length === 1) {
|
|
1044
|
+
const c = hit[0];
|
|
714
1045
|
preferred = {
|
|
715
|
-
dancer_id:
|
|
716
|
-
why: `the only candidate ${
|
|
1046
|
+
dancer_id: c.dancer_id,
|
|
1047
|
+
why: `the only candidate ${PREFERRED_WHY[c.evidence.school]} ${q.school}`
|
|
717
1048
|
};
|
|
1049
|
+
}
|
|
718
1050
|
break;
|
|
719
1051
|
}
|
|
1052
|
+
if (preferred === undefined && kept.length > 1 && oireachtas?.read != null) {
|
|
1053
|
+
const best = Math.min(...kept.map((c) => SCHOOL_RANK[c.evidence.school]));
|
|
1054
|
+
const hit = kept.filter((c) => SCHOOL_RANK[c.evidence.school] === best && c.evidence.region === "same");
|
|
1055
|
+
if (hit.length === 1) {
|
|
1056
|
+
const c = hit[0];
|
|
1057
|
+
preferred = {
|
|
1058
|
+
dancer_id: c.dancer_id,
|
|
1059
|
+
why: `the only candidate${school !== undefined ? " of the school's first rank" : ""} whose latest attended region is ${oireachtas.read}: ${c.region?.year} of ${c.region?.attended} attended year(s) to ${oireachtas.year}`
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
720
1063
|
return {
|
|
721
1064
|
tag: this.rel.tag,
|
|
722
1065
|
asked: q,
|
|
@@ -727,32 +1070,59 @@ class DancerResolver2 {
|
|
|
727
1070
|
unlinked: [...unlinked].map(([print, rows]) => ({ print, rows })).sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
|
|
728
1071
|
...age !== undefined ? { age: age.answer } : {},
|
|
729
1072
|
...population !== undefined ? { population } : {},
|
|
1073
|
+
...oireachtas !== undefined ? { oireachtas } : {},
|
|
730
1074
|
...school !== undefined ? { school } : {}
|
|
731
1075
|
};
|
|
732
1076
|
}
|
|
733
|
-
async
|
|
1077
|
+
async readOireachtas(db, o, asOf) {
|
|
1078
|
+
const year = o.year ?? asOf ?? new Date().getFullYear();
|
|
1079
|
+
const asked = o.region;
|
|
1080
|
+
if ((await this.rel.capability("dancers.years")).state !== "full")
|
|
1081
|
+
return {
|
|
1082
|
+
asked,
|
|
1083
|
+
read: null,
|
|
1084
|
+
year,
|
|
1085
|
+
note: "this release publishes no dancer-years: the region narrows and orders nothing"
|
|
1086
|
+
};
|
|
1087
|
+
this.regions ??= db.query("SELECT DISTINCT region FROM dancer_regional ORDER BY region").all().map((r) => r.region);
|
|
1088
|
+
const want = asked.trim().toUpperCase();
|
|
1089
|
+
const hit = this.regions.find((r) => r.toUpperCase() === want);
|
|
1090
|
+
if (hit === undefined)
|
|
1091
|
+
return {
|
|
1092
|
+
asked,
|
|
1093
|
+
read: null,
|
|
1094
|
+
year,
|
|
1095
|
+
note: `the release reads no attended year in a region "${asked}" (its spelling is dancer_years.region): it narrows and orders nothing`
|
|
1096
|
+
};
|
|
1097
|
+
return { asked, read: hit, year };
|
|
1098
|
+
}
|
|
1099
|
+
async schoolEvidence(schools, ids, lineage, asOf) {
|
|
734
1100
|
const rows = schools.filter((s) => asOf === null || s.year_lo === null || s.year_lo <= asOf);
|
|
735
|
-
const
|
|
1101
|
+
const idsOf = async (s) => {
|
|
736
1102
|
if (s.school_id !== null)
|
|
737
|
-
return
|
|
1103
|
+
return [s.school_id];
|
|
738
1104
|
let read = this.printIds.get(s.print);
|
|
739
1105
|
if (read === undefined) {
|
|
740
1106
|
read = (await this.rel.schools.resolve(s.print)).candidates.map((c) => c.school_id);
|
|
741
1107
|
this.printIds.set(s.print, read);
|
|
742
1108
|
}
|
|
743
|
-
return read
|
|
1109
|
+
return read;
|
|
744
1110
|
};
|
|
745
1111
|
const top = (s) => asOf !== null && s.year_hi !== null ? Math.min(s.year_hi, asOf) : s.year_hi ?? -1;
|
|
746
1112
|
const latest = Math.max(-1, ...rows.map(top));
|
|
747
1113
|
let earlier = false;
|
|
1114
|
+
let joined = false;
|
|
748
1115
|
for (const s of rows) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
if (
|
|
752
|
-
|
|
753
|
-
|
|
1116
|
+
const own = await idsOf(s);
|
|
1117
|
+
const isLatest = top(s) === latest;
|
|
1118
|
+
if (own.some((id) => ids.includes(id))) {
|
|
1119
|
+
if (isLatest)
|
|
1120
|
+
return "same";
|
|
1121
|
+
earlier = true;
|
|
1122
|
+
} else if (isLatest && own.some((id) => lineage.includes(id)))
|
|
1123
|
+
joined = true;
|
|
754
1124
|
}
|
|
755
|
-
return earlier ? "moved" : "none";
|
|
1125
|
+
return joined ? "lineage" : earlier ? "moved" : "none";
|
|
756
1126
|
}
|
|
757
1127
|
readAge(a, asOf) {
|
|
758
1128
|
const done = (age, note) => {
|
|
@@ -832,6 +1202,22 @@ function populationEvidence(own, asked) {
|
|
|
832
1202
|
return "unknown";
|
|
833
1203
|
return populationRelation(asked, mine) === "disjoint" ? "contradicts" : "same";
|
|
834
1204
|
}
|
|
1205
|
+
function regionEvidence(db, dancerId, region, year) {
|
|
1206
|
+
const rows = db.query("SELECT year, region, regional FROM dancer_regional WHERE dancer_id = ? AND year <= ? ORDER BY year").all(dancerId, year);
|
|
1207
|
+
const attended = rows.length;
|
|
1208
|
+
const at = (r) => ({
|
|
1209
|
+
year: r.year,
|
|
1210
|
+
region: r.region,
|
|
1211
|
+
regional: r.regional === 1,
|
|
1212
|
+
attended
|
|
1213
|
+
});
|
|
1214
|
+
const latest = rows.at(-1);
|
|
1215
|
+
if (latest === undefined)
|
|
1216
|
+
return { word: "unknown", region: { year: null, region: null, regional: false, attended } };
|
|
1217
|
+
if (latest.year === year && latest.regional === 1 && latest.region !== region)
|
|
1218
|
+
return { word: "contradicts", region: at(latest) };
|
|
1219
|
+
return { word: latest.region === region ? "same" : "moved", region: at(latest) };
|
|
1220
|
+
}
|
|
835
1221
|
function ageEvidence(d, asked) {
|
|
836
1222
|
if (asked === null)
|
|
837
1223
|
return "unasked";
|
|
@@ -865,7 +1251,7 @@ var LEVEL = Object.freeze({
|
|
|
865
1251
|
600: "PC",
|
|
866
1252
|
700: "OC"
|
|
867
1253
|
});
|
|
868
|
-
var
|
|
1254
|
+
var SCHEMA2 = `
|
|
869
1255
|
CREATE TABLE comp(
|
|
870
1256
|
competition TEXT PRIMARY KEY, feis_id TEXT NOT NULL, brand_id TEXT, year INTEGER,
|
|
871
1257
|
code TEXT NOT NULL, code_fold TEXT NOT NULL, title TEXT, age TEXT, gender TEXT,
|
|
@@ -874,7 +1260,7 @@ CREATE TABLE comp(
|
|
|
874
1260
|
gender_source TEXT, level_printed TEXT, dance_printed TEXT, dance TEXT
|
|
875
1261
|
);
|
|
876
1262
|
`;
|
|
877
|
-
var
|
|
1263
|
+
var INDEXES2 = [
|
|
878
1264
|
"CREATE INDEX comp_feis_code ON comp(feis_id, code_fold)",
|
|
879
1265
|
"CREATE INDEX comp_brand_year ON comp(brand_id, year)"
|
|
880
1266
|
];
|
|
@@ -893,7 +1279,7 @@ var compsIndex = {
|
|
|
893
1279
|
inputs: ["results"],
|
|
894
1280
|
says: "every competition by its feis and its own code, with its series and year",
|
|
895
1281
|
build(rel, out) {
|
|
896
|
-
out.run(
|
|
1282
|
+
out.run(SCHEMA2);
|
|
897
1283
|
const src = rel.db("results");
|
|
898
1284
|
const brand = new Map;
|
|
899
1285
|
for (const f of src.query("SELECT feis_id, feis_brand_id, year FROM feises ORDER BY file, line").all())
|
|
@@ -909,7 +1295,7 @@ var compsIndex = {
|
|
|
909
1295
|
const f = brand.get(c.event);
|
|
910
1296
|
put.run(c.competition, c.event, f?.brand ?? null, c.year ?? f?.year ?? null, c.comp_no.trim(), fold(c.comp_no), c.title, c.age, c.gender, c.feis_level, c.round, c.source, captures.get(c.competition) ?? 1, c.file, c.line, c.age_source, c.age_printed, c.gender_source, c.level_printed, c.dance_printed, c.dance);
|
|
911
1297
|
}
|
|
912
|
-
for (const s of
|
|
1298
|
+
for (const s of INDEXES2)
|
|
913
1299
|
out.run(s);
|
|
914
1300
|
}
|
|
915
1301
|
};
|
|
@@ -1199,215 +1585,6 @@ class CompResolver2 {
|
|
|
1199
1585
|
}
|
|
1200
1586
|
}
|
|
1201
1587
|
|
|
1202
|
-
import { readFileSync } from "fs";
|
|
1203
|
-
var SCHEMA2 = `
|
|
1204
|
-
CREATE TABLE feis(
|
|
1205
|
-
feis_id TEXT PRIMARY KEY, source TEXT NOT NULL, token TEXT NOT NULL, host_id TEXT NOT NULL,
|
|
1206
|
-
name TEXT NOT NULL, date_lo TEXT, date_hi TEXT, date_printed TEXT, year INTEGER,
|
|
1207
|
-
location TEXT, instance_id TEXT, brand_id TEXT, regions TEXT NOT NULL,
|
|
1208
|
-
competitions INTEGER NOT NULL, rows INTEGER NOT NULL, captures INTEGER NOT NULL
|
|
1209
|
-
);
|
|
1210
|
-
CREATE TABLE redirect(old_id TEXT PRIMARY KEY, new_id TEXT NOT NULL, kind TEXT NOT NULL);
|
|
1211
|
-
`;
|
|
1212
|
-
var INDEXES2 = [
|
|
1213
|
-
"CREATE INDEX feis_host ON feis(host_id COLLATE NOCASE)",
|
|
1214
|
-
"CREATE INDEX feis_token ON feis(token)",
|
|
1215
|
-
"CREATE INDEX feis_source ON feis(source)",
|
|
1216
|
-
"CREATE INDEX feis_instance ON feis(instance_id)",
|
|
1217
|
-
"CREATE INDEX feis_brand ON feis(brand_id)",
|
|
1218
|
-
"CREATE INDEX feis_year ON feis(year)"
|
|
1219
|
-
];
|
|
1220
|
-
function readJsonl(path) {
|
|
1221
|
-
return readFileSync(path, "utf8").split(`
|
|
1222
|
-
`).filter((l) => l.trim() !== "").map((l) => JSON.parse(l));
|
|
1223
|
-
}
|
|
1224
|
-
var feisesIndex = {
|
|
1225
|
-
name: "feises",
|
|
1226
|
-
version: "2",
|
|
1227
|
-
inputs: ["results", "feis-redirects.jsonl"],
|
|
1228
|
-
says: "every feis by id, platform, code, series and name, with its counts and regions",
|
|
1229
|
-
build(rel, out) {
|
|
1230
|
-
out.run(SCHEMA2);
|
|
1231
|
-
const src = rel.db("results");
|
|
1232
|
-
const counts = new Map;
|
|
1233
|
-
for (const r of src.query("SELECT event, count(*) AS n, json_group_array(DISTINCT feis_region) AS regions FROM competitions GROUP BY event").all())
|
|
1234
|
-
counts.set(r.event, {
|
|
1235
|
-
comps: r.n,
|
|
1236
|
-
regions: new Set(JSON.parse(r.regions).filter((x) => x !== null))
|
|
1237
|
-
});
|
|
1238
|
-
const rows = new Map;
|
|
1239
|
-
for (const r of src.query("SELECT c.event AS event, sum(r.n) AS n FROM (SELECT competition, count(*) AS n FROM results GROUP BY competition) r JOIN (SELECT DISTINCT competition, event FROM competitions) c ON c.competition = r.competition GROUP BY c.event").all())
|
|
1240
|
-
rows.set(r.event, r.n);
|
|
1241
|
-
const put = out.prepare("INSERT OR IGNORE INTO feis VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
1242
|
-
const captures = new Map;
|
|
1243
|
-
const feisRows = src.query("SELECT feis_id, source, host_id, name_printed, date_lo, date_hi, date_printed, year, location_printed, feis_instance_id, feis_brand_id FROM feises ORDER BY file, line").all();
|
|
1244
|
-
for (const f of feisRows)
|
|
1245
|
-
captures.set(f.feis_id, (captures.get(f.feis_id) ?? 0) + 1);
|
|
1246
|
-
for (const f of feisRows) {
|
|
1247
|
-
const c = counts.get(f.feis_id);
|
|
1248
|
-
const colon = f.feis_id.indexOf(":");
|
|
1249
|
-
put.run(f.feis_id, f.source, colon === -1 ? f.source : f.feis_id.slice(0, colon), f.host_id, f.name_printed, f.date_lo, f.date_hi, f.date_printed, f.year, f.location_printed, f.feis_instance_id, f.feis_brand_id, JSON.stringify([...c?.regions ?? []].sort()), c?.comps ?? 0, rows.get(f.feis_id) ?? 0, captures.get(f.feis_id) ?? 0);
|
|
1250
|
-
}
|
|
1251
|
-
const red = out.prepare("INSERT OR REPLACE INTO redirect VALUES (?, ?, ?)");
|
|
1252
|
-
for (const r of readJsonl(rel.path("feis-redirects.jsonl")))
|
|
1253
|
-
red.run(r.old_id, r.new_id, r.kind);
|
|
1254
|
-
for (const s of INDEXES2)
|
|
1255
|
-
out.run(s);
|
|
1256
|
-
}
|
|
1257
|
-
};
|
|
1258
|
-
var NO_CALENDAR2 = "the release holds a feis-year once its results are captured and carries no calendar of scheduled feiseanna";
|
|
1259
|
-
var globToLike = (g) => g.replace(/[\\%_]/g, (c) => `\\${c}`).replace(/\*/g, "%").replace(/\?/g, "_");
|
|
1260
|
-
|
|
1261
|
-
class FeisFinder2 {
|
|
1262
|
-
rel;
|
|
1263
|
-
constructor(rel) {
|
|
1264
|
-
this.rel = rel;
|
|
1265
|
-
}
|
|
1266
|
-
async dates() {
|
|
1267
|
-
await this.rel.fetch("results");
|
|
1268
|
-
await this.rel.require("feises.dates");
|
|
1269
|
-
const dates = this.rel.db("results").query("SELECT feis_id, min(date_lo) AS date_lo, max(date_hi) AS date_hi FROM feises GROUP BY feis_id ORDER BY feis_id").all();
|
|
1270
|
-
return { tag: this.rel.tag, dates };
|
|
1271
|
-
}
|
|
1272
|
-
async find(q) {
|
|
1273
|
-
const db = await this.rel.indexDb("feises");
|
|
1274
|
-
const where = [];
|
|
1275
|
-
const args = [];
|
|
1276
|
-
let redirected;
|
|
1277
|
-
if (q.platform !== undefined) {
|
|
1278
|
-
where.push("(source = ? OR token = ?)");
|
|
1279
|
-
args.push(q.platform, q.platform);
|
|
1280
|
-
}
|
|
1281
|
-
if (q.code !== undefined) {
|
|
1282
|
-
where.push("host_id = ? COLLATE NOCASE");
|
|
1283
|
-
args.push(q.code);
|
|
1284
|
-
}
|
|
1285
|
-
if (q.id !== undefined) {
|
|
1286
|
-
let id = q.id;
|
|
1287
|
-
const seen = new Set;
|
|
1288
|
-
for (;; ) {
|
|
1289
|
-
const r = db.query("SELECT new_id FROM redirect WHERE old_id = ?").get(id);
|
|
1290
|
-
if (r === null || seen.has(r.new_id))
|
|
1291
|
-
break;
|
|
1292
|
-
seen.add(id);
|
|
1293
|
-
id = r.new_id;
|
|
1294
|
-
}
|
|
1295
|
-
if (id !== q.id)
|
|
1296
|
-
redirected = { from: q.id, to: id };
|
|
1297
|
-
where.push("(feis_id = ? OR instance_id = ? OR brand_id = ?)");
|
|
1298
|
-
args.push(id, id, id);
|
|
1299
|
-
}
|
|
1300
|
-
if (q.name !== undefined) {
|
|
1301
|
-
where.push("name LIKE ? ESCAPE '\\'");
|
|
1302
|
-
args.push(globToLike(q.name));
|
|
1303
|
-
}
|
|
1304
|
-
if (q.year !== undefined) {
|
|
1305
|
-
where.push("year = ?");
|
|
1306
|
-
args.push(q.year);
|
|
1307
|
-
}
|
|
1308
|
-
const sql = `SELECT * FROM feis${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY date_lo, feis_id`;
|
|
1309
|
-
const feises = db.query(sql).all(...args).map((r) => ({
|
|
1310
|
-
...r,
|
|
1311
|
-
regions: JSON.parse(r.regions)
|
|
1312
|
-
}));
|
|
1313
|
-
return { tag: this.rel.tag, feises, ...redirected !== undefined ? { redirected } : {} };
|
|
1314
|
-
}
|
|
1315
|
-
async codeStem(token, stem) {
|
|
1316
|
-
const db = await this.rel.indexDb("feises");
|
|
1317
|
-
const like = db.query("SELECT host_id, year, brand_id FROM feis WHERE token = ? AND host_id LIKE ? ESCAPE '\\' COLLATE NOCASE").all(token, `${stem.replace(/[\\%_]/g, (c) => `\\${c}`)}__`);
|
|
1318
|
-
const fits = like.filter((f) => {
|
|
1319
|
-
const d = /(\d{2})$/.exec(f.host_id);
|
|
1320
|
-
return d !== null && f.host_id.slice(0, -2).toLowerCase() === stem.toLowerCase() && f.year !== null && f.year % 100 === Number(d[1]) && f.brand_id !== null;
|
|
1321
|
-
});
|
|
1322
|
-
return [...new Set(fits.map((f) => f.brand_id))].sort();
|
|
1323
|
-
}
|
|
1324
|
-
async seriesOf(brand) {
|
|
1325
|
-
const db = await this.rel.indexDb("feises");
|
|
1326
|
-
const rows = db.query("SELECT * FROM feis WHERE brand_id = ? ORDER BY year, date_lo, feis_id").all(brand);
|
|
1327
|
-
const byYear = new Map;
|
|
1328
|
-
for (const r of rows) {
|
|
1329
|
-
const i = byYear.get(r.year);
|
|
1330
|
-
if (i === undefined)
|
|
1331
|
-
byYear.set(r.year, {
|
|
1332
|
-
year: r.year,
|
|
1333
|
-
feis_ids: [r.feis_id],
|
|
1334
|
-
date_lo: r.date_lo,
|
|
1335
|
-
name: r.name
|
|
1336
|
-
});
|
|
1337
|
-
else
|
|
1338
|
-
i.feis_ids.push(r.feis_id);
|
|
1339
|
-
}
|
|
1340
|
-
const instances = [...byYear.values()];
|
|
1341
|
-
const newest = rows[rows.length - 1];
|
|
1342
|
-
return {
|
|
1343
|
-
series_id: brand,
|
|
1344
|
-
name: newest?.name ?? "",
|
|
1345
|
-
host: newest?.source ?? "",
|
|
1346
|
-
hosts: [...new Set(rows.map((r) => r.source))].sort(),
|
|
1347
|
-
codes: [...new Set(rows.map((r) => r.host_id))].sort(),
|
|
1348
|
-
years: instances.map((i) => i.year).filter((y) => y !== null),
|
|
1349
|
-
instances
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
async series(q) {
|
|
1353
|
-
const asked = q.trim();
|
|
1354
|
-
const answer = async (brands, via, more = {}) => {
|
|
1355
|
-
const candidates = [];
|
|
1356
|
-
for (const b of brands)
|
|
1357
|
-
candidates.push(await this.seriesOf(b));
|
|
1358
|
-
const arm = candidates.length === 0 ? "none" : candidates.length === 1 ? "unique" : "ambiguous";
|
|
1359
|
-
const u = arm === "unique" ? candidates[0] : null;
|
|
1360
|
-
return {
|
|
1361
|
-
tag: this.rel.tag,
|
|
1362
|
-
asked,
|
|
1363
|
-
arm,
|
|
1364
|
-
via: candidates.length === 0 ? null : via,
|
|
1365
|
-
series_id: u?.series_id ?? null,
|
|
1366
|
-
name: u?.name ?? null,
|
|
1367
|
-
host: u?.host ?? null,
|
|
1368
|
-
years: u?.years ?? [],
|
|
1369
|
-
instances: u?.instances ?? [],
|
|
1370
|
-
next: null,
|
|
1371
|
-
...u !== null ? { next_why: NO_CALENDAR2 } : {},
|
|
1372
|
-
candidates,
|
|
1373
|
-
...arm === "ambiguous" ? { why: `${candidates.length} series answer "${asked}": ${brands.join(", ")}` } : {},
|
|
1374
|
-
...more
|
|
1375
|
-
};
|
|
1376
|
-
};
|
|
1377
|
-
if (asked === "")
|
|
1378
|
-
return answer([], null, { why: "no series was asked" });
|
|
1379
|
-
const byId = await this.find({ id: asked });
|
|
1380
|
-
if (byId.feises.length > 0) {
|
|
1381
|
-
const brands = [...new Set(byId.feises.map((f) => f.brand_id))];
|
|
1382
|
-
const filed = brands.filter((b) => b !== null).sort();
|
|
1383
|
-
const redirected = byId.redirected !== undefined ? { redirected: byId.redirected } : {};
|
|
1384
|
-
if (filed.length === 0)
|
|
1385
|
-
return answer([], null, {
|
|
1386
|
-
...redirected,
|
|
1387
|
-
why: `the release files ${byId.feises.map((f) => f.feis_id).join(", ")} under no series`
|
|
1388
|
-
});
|
|
1389
|
-
return answer(filed, "id", redirected);
|
|
1390
|
-
}
|
|
1391
|
-
const code = /^([^:\s]+):(.*?)(\d{2})?$/.exec(asked);
|
|
1392
|
-
if (code !== null) {
|
|
1393
|
-
const [, token, stem] = code;
|
|
1394
|
-
const brands = stem === "" ? [] : await this.codeStem(token, stem);
|
|
1395
|
-
if (brands.length > 0)
|
|
1396
|
-
return answer(brands, "code-stem");
|
|
1397
|
-
}
|
|
1398
|
-
const glob = /[*?]/.test(asked) ? asked : `*${asked}*`;
|
|
1399
|
-
const byName = await this.find({ name: glob });
|
|
1400
|
-
const brands = [
|
|
1401
|
-
...new Set(byName.feises.map((f) => f.brand_id).filter((b) => b !== null))
|
|
1402
|
-
].sort();
|
|
1403
|
-
if (brands.length > 0)
|
|
1404
|
-
return answer(brands, "name");
|
|
1405
|
-
return answer([], null, {
|
|
1406
|
-
why: byName.feises.length > 0 ? `the feises named "${asked}" are filed under no series` : `the release holds no feis or series "${asked}"`
|
|
1407
|
-
});
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
1588
|
import { Database } from "bun:sqlite";
|
|
1412
1589
|
import { createHash as createHash2 } from "crypto";
|
|
1413
1590
|
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "fs";
|
|
@@ -2286,7 +2463,8 @@ class RegistrationResolver2 {
|
|
|
2286
2463
|
...row.school !== undefined ? { school: row.school } : {},
|
|
2287
2464
|
...age !== null ? { age: { band: age.band, year: age.year } } : {},
|
|
2288
2465
|
...row.asOf !== undefined ? { asOf: row.asOf } : {},
|
|
2289
|
-
...population !== null && "asked" in population ? { population: population.asked } : {}
|
|
2466
|
+
...population !== null && "asked" in population ? { population: population.asked } : {},
|
|
2467
|
+
...row.oireachtas !== undefined ? { oireachtas: row.oireachtas } : {}
|
|
2290
2468
|
};
|
|
2291
2469
|
const { dancer } = await this.dancer(asked);
|
|
2292
2470
|
out.push({
|
|
@@ -2517,6 +2695,12 @@ var FEATURES2 = [
|
|
|
2517
2695
|
since: 270,
|
|
2518
2696
|
calls: "the `dance` a comp of comps.resolve, comps.syllabus and comps.syllabusFor carries: null, never refused, on a release without it"
|
|
2519
2697
|
},
|
|
2698
|
+
{
|
|
2699
|
+
feature: "schools.lineage",
|
|
2700
|
+
needs: ["school-lineage.jsonl"],
|
|
2701
|
+
since: 184,
|
|
2702
|
+
calls: "the `lineage` school evidence of dancers.resolve and dancers.search: no school is joined by lineage, never refused, on a release without it"
|
|
2703
|
+
},
|
|
2520
2704
|
{
|
|
2521
2705
|
feature: "results.stream",
|
|
2522
2706
|
needs: [
|
|
@@ -3229,4 +3413,4 @@ function releases2(opts = {}) {
|
|
|
3229
3413
|
return new ReleaseManager2(opts);
|
|
3230
3414
|
}
|
|
3231
3415
|
|
|
3232
|
-
export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2,
|
|
3416
|
+
export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, StaffFinder2, SchoolResolver2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, resolveTag2, Release2, openRelease2, openCached2, ReleaseManager2, releases2 };
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
nameOrderOf,
|
|
5
5
|
openRelease2,
|
|
6
6
|
releases2
|
|
7
|
-
} from "./chunks/index-
|
|
7
|
+
} from "./chunks/index-1sj5zr8k.js";
|
|
8
8
|
import"./chunks/index-hne1wadw.js";
|
|
9
9
|
import"./chunks/index-hghdy980.js";
|
|
10
10
|
|
|
@@ -40,9 +40,12 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
|
|
|
40
40
|
dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>
|
|
41
41
|
[--order surname-first|given-first] [--school <print|id>]
|
|
42
42
|
[--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]
|
|
43
|
-
[--population girls|boys] [--rows]
|
|
43
|
+
[--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]
|
|
44
44
|
the dancers a name reaches, each with its evidence
|
|
45
|
-
(name level, age, school, population) and the arm;
|
|
45
|
+
(name level, age, school, population, region) and the arm;
|
|
46
|
+
--oireachtas is the regional the roster is for, its region in
|
|
47
|
+
the release's spelling (SRO, MAO), its year the --as-of year
|
|
48
|
+
where none is given; the results of
|
|
46
49
|
the one dancer where one answers (or --rows for each)
|
|
47
50
|
results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]
|
|
48
51
|
[--school <print|id>] [--year <y>] [--limit <n>]
|
|
@@ -87,12 +90,13 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
|
|
|
87
90
|
resolve comp [<feis> <code>] [--year <y>] [--title <t>]
|
|
88
91
|
resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]
|
|
89
92
|
[--order <o>] [--as-of <date>] [--population girls|boys]
|
|
93
|
+
[--oireachtas <region>[:<year>]]
|
|
90
94
|
resolve registrations
|
|
91
95
|
JSONL out, one answer per line; with no positional the
|
|
92
96
|
questions are JSONL on stdin, one per line (a comp:
|
|
93
97
|
{feis, code, year?, title?}, a dancer: {name, school?, age?,
|
|
94
|
-
population?}, a registration: {name, school?,
|
|
95
|
-
year?, population?})
|
|
98
|
+
population?, oireachtas?}, a registration: {name, school?,
|
|
99
|
+
codes[], feis, year?, population?, oireachtas?})
|
|
96
100
|
|
|
97
101
|
<spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)`;
|
|
98
102
|
var VALUE = new Set([
|
|
@@ -117,6 +121,7 @@ var VALUE = new Set([
|
|
|
117
121
|
"--comp",
|
|
118
122
|
"--as-of",
|
|
119
123
|
"--population",
|
|
124
|
+
"--oireachtas",
|
|
120
125
|
"--dancer",
|
|
121
126
|
"--feis",
|
|
122
127
|
"--limit",
|
|
@@ -169,7 +174,9 @@ function printCached(c, say) {
|
|
|
169
174
|
}
|
|
170
175
|
var span = (lo, hi) => lo === null ? "-" : lo === hi ? `${lo}` : `${lo}-${hi ?? "?"}`;
|
|
171
176
|
function printCandidate(c, say, pad = " ") {
|
|
172
|
-
say(`${pad}${c.dancer_id} ${c.display}${c.scope === "per-release" ? " [per-release id]" : ""} score ${c.score} name ${c.evidence.nameLevel} age ${c.evidence.age} school ${c.evidence.school} population ${c.evidence.population}`);
|
|
177
|
+
say(`${pad}${c.dancer_id} ${c.display}${c.scope === "per-release" ? " [per-release id]" : ""} score ${c.score} name ${c.evidence.nameLevel} age ${c.evidence.age} school ${c.evidence.school} population ${c.evidence.population} region ${c.evidence.region}`);
|
|
178
|
+
if (c.region !== undefined)
|
|
179
|
+
say(`${pad} region ${c.region.region === null ? "-" : `${c.region.region} ${c.region.year}${c.region.regional ? " (regional)" : ""}`} attended ${c.region.attended} year(s)`);
|
|
173
180
|
say(`${pad} ${c.rows} rows ${span(c.year_lo, c.year_hi)} born ${span(c.birth.lo, c.birth.hi)} (${c.birth.source}) majors girls ${c.population.girls} boys ${c.population.boys} school ${c.school === null ? "-" : `${c.school.school_id ?? "(no id)"} "${c.school.print}" ${span(c.school.year_lo, c.school.year_hi)}`}`);
|
|
174
181
|
say(`${pad} prints ${c.prints.slice(0, 5).map((p) => `"${p.print}" (${p.rows}, ${p.level})`).join(", ")}${c.prints.length > 5 ? `, +${c.prints.length - 5} more` : ""}`);
|
|
175
182
|
}
|
|
@@ -178,9 +185,11 @@ function printDancers(a, say) {
|
|
|
178
185
|
if (a.age !== undefined)
|
|
179
186
|
say(` age asked: ${a.age.read ?? "(unread)"}${a.age.birth !== null ? ` born ${a.age.birth.from ?? "?"}-${a.age.birth.to ?? "?"}` : ""}${a.age.note !== undefined ? ` (${a.age.note})` : ""}`);
|
|
180
187
|
if (a.school !== undefined)
|
|
181
|
-
say(` school asked: ${a.school.arm} ${a.school.ids.join(", ") || "-"}`);
|
|
188
|
+
say(` school asked: ${a.school.arm} ${a.school.ids.join(", ") || "-"}${a.school.lineage !== undefined && a.school.lineage.length > 0 ? ` (lineage: ${a.school.lineage.join(", ")})` : ""}`);
|
|
182
189
|
if (a.population !== undefined)
|
|
183
190
|
say(` population asked: ${a.population.read ?? "(unread)"}${a.population.note !== undefined ? ` (${a.population.note})` : ""}`);
|
|
191
|
+
if (a.oireachtas !== undefined)
|
|
192
|
+
say(` oireachtas asked: ${a.oireachtas.read ?? "(unread)"} ${a.oireachtas.year}${a.oireachtas.note !== undefined ? ` (${a.oireachtas.note})` : ""}`);
|
|
184
193
|
for (const c of a.candidates)
|
|
185
194
|
printCandidate(c, say);
|
|
186
195
|
if (a.preferred !== undefined)
|
|
@@ -253,6 +262,14 @@ function jsonLines(text, check) {
|
|
|
253
262
|
return out;
|
|
254
263
|
}
|
|
255
264
|
var checkComp = (q) => typeof q.feis !== "string" || q.feis.trim() === "" ? "a comp question needs feis" : q.code === undefined || q.code === null || String(q.code).trim() === "" ? "a comp question needs code" : null;
|
|
265
|
+
function oireachtasFlag(v) {
|
|
266
|
+
if (v === undefined)
|
|
267
|
+
return {};
|
|
268
|
+
const m = /^(.*?):(\d{4})$/.exec(v.trim());
|
|
269
|
+
return {
|
|
270
|
+
oireachtas: m === null ? { region: v.trim() } : { region: m[1], year: Number(m[2]) }
|
|
271
|
+
};
|
|
272
|
+
}
|
|
256
273
|
var checkDancer = (q) => typeof q.name !== "string" || q.name.trim() === "" ? "a dancer question needs name" : null;
|
|
257
274
|
var checkRegistration = (r) => typeof r.name !== "string" || r.name.trim() === "" ? "a registration row needs name" : typeof r.feis !== "string" || r.feis.trim() === "" ? "a registration row needs feis" : !Array.isArray(r.codes) ? "a registration row needs codes[]" : null;
|
|
258
275
|
async function resolveVerb(rel, what, args, p, say, stdin) {
|
|
@@ -312,7 +329,8 @@ async function resolveVerb(rel, what, args, p, say, stdin) {
|
|
|
312
329
|
...p.flags.has("--school") ? { school: p.flags.get("--school") } : {},
|
|
313
330
|
...age !== undefined ? { age } : {},
|
|
314
331
|
...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {},
|
|
315
|
-
...p.flags.has("--population") ? { population: p.flags.get("--population") } : {}
|
|
332
|
+
...p.flags.has("--population") ? { population: p.flags.get("--population") } : {},
|
|
333
|
+
...oireachtasFlag(p.flags.get("--oireachtas"))
|
|
316
334
|
}
|
|
317
335
|
}
|
|
318
336
|
];
|
|
@@ -619,6 +637,7 @@ async function main(argv, io = {}) {
|
|
|
619
637
|
...age !== undefined ? { age } : {},
|
|
620
638
|
...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {},
|
|
621
639
|
...p.flags.has("--population") ? { population: p.flags.get("--population") } : {},
|
|
640
|
+
...oireachtasFlag(p.flags.get("--oireachtas")),
|
|
622
641
|
...p.flags.has("--limit") ? { limit: Number(p.flags.get("--limit")) } : {}
|
|
623
642
|
};
|
|
624
643
|
const surname = p.flags.get("--surname");
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
unzstdTo2,
|
|
7
7
|
NAME_LEVELS2,
|
|
8
8
|
nameLevel2,
|
|
9
|
+
NO_CALENDAR2,
|
|
10
|
+
FeisFinder2,
|
|
9
11
|
entryRank2,
|
|
10
12
|
FEIS_LOCAL_ID_FLOOR2,
|
|
11
13
|
ResultFinder2,
|
|
@@ -14,8 +16,6 @@ import {
|
|
|
14
16
|
readPopulation2,
|
|
15
17
|
canonicalOf2,
|
|
16
18
|
CompResolver2,
|
|
17
|
-
NO_CALENDAR2,
|
|
18
|
-
FeisFinder2,
|
|
19
19
|
StaffFinder2,
|
|
20
20
|
SchoolResolver2,
|
|
21
21
|
TeamFinder2,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
openCached2,
|
|
46
46
|
ReleaseManager2,
|
|
47
47
|
releases2
|
|
48
|
-
} from "./chunks/index-
|
|
48
|
+
} from "./chunks/index-1sj5zr8k.js";
|
|
49
49
|
import {
|
|
50
50
|
cacheRoot2,
|
|
51
51
|
repoOf2
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import type { OpenOptions } from "./release.js";
|
|
3
3
|
import type { StreamQuery } from "./stream.js";
|
|
4
|
-
export declare const USAGE = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]\n\n config repo <owner/name> set the user-local repository the releases come from\n list [--remote] the cached releases (size, files, indexes, the pin), and\n with --remote the published tags\n fetch [--release <spec>] [--assets results,majors,<file>...]\n download, verify and cache a release (default: results)\n pin <spec> the user-local pin: what a bare open (spec \"pinned\") opens\n unpin remove the pin (a bare open is then \"latest\")\n current the pin, and whether it is cached\n add <dir> --tag <tag> add a release built locally (its MANIFEST.json and files)\n to the cache under its tag\n index [--release <spec>] [<name>...] [--force]\n build the per-tag indexes (default: every one)\n verify [--release <spec>] re-hash every cached file against its receipt\n capabilities [--release <spec>] what the release carries of what the client reads, from\n its own schema: each feature full or missing (its calls\n refuse, naming the first cut that publishes it)\n prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)\n and the pinned one\n rm <tag> [--dry-run] remove one cached release (not the pinned one)\n drop-indexes [<tag>] [--dry-run] remove the index files (of one release, or of every one)\n feis [--release <spec>] [--platform <p>] [--code <c>] [--id <id>] [--name <glob>] [--year <y>]\n the feises a query names\n series [--release <spec>] <name|host code|feis id>\n the series a name, a host code (fweb:ABC) or any feis id\n names: its years, hosts, codes and next instance\n school [--release <spec>] <print|school_id> [--region <code>]\n a school print or id to the published schools\n dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>\n [--order surname-first|given-first] [--school <print|id>]\n [--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]\n [--population girls|boys] [--rows]\n the dancers a name reaches, each with its evidence\n (name level, age, school, population) and the arm; the results of\n the one dancer where one answers (or --rows for each)\n results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]\n [--school <print|id>] [--year <y>] [--limit <n>]\n the published result rows, prints and ids\n majors [--release <spec>] [--event <e>] [--year <y>] [--comp <competition>] [--dancer <id>]\n the majors: every event and its years; an event's\n competitions; a competition's, an event's or a dancer's rows\n stream [--release <spec>] --scope majors|transcribed|platform|all [--entries] [--event <e,...>]\n [--year <y> | --year <from>..<to>] [--tier <t,...>] [--category <c,...>]\n [--level <feis_level,...>] [--dancer <id,...>] [--school <school_id>] [--primary]\n every row a scope holds, one JSON line each, never the\n whole table in memory; --entries one per address (the row\n it stands for, with its recall order)\n records [--release <spec>] [--dancer <id,...>]\n every dancer's record: display, appearances, birth window,\n populations\n records --years [--release <spec>] [--dancer <id,...>] [--year <y>]\n each dancer's region, nationals and place per year\n feis-dates [--release <spec>] every feis's first and last day\n tabulated [--release <spec>] [--min-round <n>]\n the majors addresses the marks sheets tabulate to round n\n or later (default 3)\n teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]\n the team result rows of an event-year; with --rosters its\n roster teams and their dancers; with --joins how each roster\n team meets the results\n events [--release <spec>] [--event <e>] [--year <y>]\n each event-year's dates and place\n teacher [--release <spec>] <name|TD number>\n the staff lists' people: where they taught, the register,\n where they sat\n adjudicator [--release <spec>] <name|TD number>\n the register's and the panels' people: the same answer\n comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]\n a feis's own comp code to the comp it names, through that\n feis's syllabus (or the prior year's)\n syllabus [--release <spec>] <feis> [year]\n one feis-year's comps, in printed order\n syllabus [--release <spec>] --for <series> [year]\n a series' syllabus for a year: that year's where held,\n else the newest, saying which and what it is read from\n resolve comp [<feis> <code>] [--year <y>] [--title <t>]\n resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]\n [--order <o>] [--as-of <date>] [--population girls|boys]\n resolve registrations\n JSONL out, one answer per line; with no positional the\n questions are JSONL on stdin, one per line (a comp:\n {feis, code, year?, title?}, a dancer: {name, school?, age?,\n population?}, a registration: {name, school
|
|
4
|
+
export declare const USAGE = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]\n\n config repo <owner/name> set the user-local repository the releases come from\n list [--remote] the cached releases (size, files, indexes, the pin), and\n with --remote the published tags\n fetch [--release <spec>] [--assets results,majors,<file>...]\n download, verify and cache a release (default: results)\n pin <spec> the user-local pin: what a bare open (spec \"pinned\") opens\n unpin remove the pin (a bare open is then \"latest\")\n current the pin, and whether it is cached\n add <dir> --tag <tag> add a release built locally (its MANIFEST.json and files)\n to the cache under its tag\n index [--release <spec>] [<name>...] [--force]\n build the per-tag indexes (default: every one)\n verify [--release <spec>] re-hash every cached file against its receipt\n capabilities [--release <spec>] what the release carries of what the client reads, from\n its own schema: each feature full or missing (its calls\n refuse, naming the first cut that publishes it)\n prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)\n and the pinned one\n rm <tag> [--dry-run] remove one cached release (not the pinned one)\n drop-indexes [<tag>] [--dry-run] remove the index files (of one release, or of every one)\n feis [--release <spec>] [--platform <p>] [--code <c>] [--id <id>] [--name <glob>] [--year <y>]\n the feises a query names\n series [--release <spec>] <name|host code|feis id>\n the series a name, a host code (fweb:ABC) or any feis id\n names: its years, hosts, codes and next instance\n school [--release <spec>] <print|school_id> [--region <code>]\n a school print or id to the published schools\n dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>\n [--order surname-first|given-first] [--school <print|id>]\n [--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]\n [--population girls|boys] [--oireachtas <region>[:<year>]] [--rows]\n the dancers a name reaches, each with its evidence\n (name level, age, school, population, region) and the arm;\n --oireachtas is the regional the roster is for, its region in\n the release's spelling (SRO, MAO), its year the --as-of year\n where none is given; the results of\n the one dancer where one answers (or --rows for each)\n results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]\n [--school <print|id>] [--year <y>] [--limit <n>]\n the published result rows, prints and ids\n majors [--release <spec>] [--event <e>] [--year <y>] [--comp <competition>] [--dancer <id>]\n the majors: every event and its years; an event's\n competitions; a competition's, an event's or a dancer's rows\n stream [--release <spec>] --scope majors|transcribed|platform|all [--entries] [--event <e,...>]\n [--year <y> | --year <from>..<to>] [--tier <t,...>] [--category <c,...>]\n [--level <feis_level,...>] [--dancer <id,...>] [--school <school_id>] [--primary]\n every row a scope holds, one JSON line each, never the\n whole table in memory; --entries one per address (the row\n it stands for, with its recall order)\n records [--release <spec>] [--dancer <id,...>]\n every dancer's record: display, appearances, birth window,\n populations\n records --years [--release <spec>] [--dancer <id,...>] [--year <y>]\n each dancer's region, nationals and place per year\n feis-dates [--release <spec>] every feis's first and last day\n tabulated [--release <spec>] [--min-round <n>]\n the majors addresses the marks sheets tabulate to round n\n or later (default 3)\n teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]\n the team result rows of an event-year; with --rosters its\n roster teams and their dancers; with --joins how each roster\n team meets the results\n events [--release <spec>] [--event <e>] [--year <y>]\n each event-year's dates and place\n teacher [--release <spec>] <name|TD number>\n the staff lists' people: where they taught, the register,\n where they sat\n adjudicator [--release <spec>] <name|TD number>\n the register's and the panels' people: the same answer\n comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]\n a feis's own comp code to the comp it names, through that\n feis's syllabus (or the prior year's)\n syllabus [--release <spec>] <feis> [year]\n one feis-year's comps, in printed order\n syllabus [--release <spec>] --for <series> [year]\n a series' syllabus for a year: that year's where held,\n else the newest, saying which and what it is read from\n resolve comp [<feis> <code>] [--year <y>] [--title <t>]\n resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]\n [--order <o>] [--as-of <date>] [--population girls|boys]\n [--oireachtas <region>[:<year>]]\n resolve registrations\n JSONL out, one answer per line; with no positional the\n questions are JSONL on stdin, one per line (a comp:\n {feis, code, year?, title?}, a dancer: {name, school?, age?,\n population?, oireachtas?}, a registration: {name, school?,\n codes[], feis, year?, population?, oireachtas?})\n\n <spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)";
|
|
5
5
|
export interface Parsed {
|
|
6
6
|
positional: string[];
|
|
7
7
|
flags: Map<string, string>;
|
package/dist/types/dancers.d.ts
CHANGED
|
@@ -20,11 +20,17 @@ export interface DancerQuery {
|
|
|
20
20
|
school?: string;
|
|
21
21
|
age?: AgeAsked;
|
|
22
22
|
asOf?: string;
|
|
23
|
+
oireachtas?: OireachtasAsked;
|
|
23
24
|
population?: string;
|
|
24
25
|
}
|
|
26
|
+
export interface OireachtasAsked {
|
|
27
|
+
region: string;
|
|
28
|
+
year?: number;
|
|
29
|
+
}
|
|
30
|
+
export type RegionEvidence = "same" | "moved" | "contradicts" | "unknown" | "unasked";
|
|
25
31
|
export type AgeEvidence = "fits" | "overlaps" | "contradicts" | "unknown" | "unasked";
|
|
26
32
|
export type PopulationEvidence = "same" | "contradicts" | "both" | "unknown" | "unasked";
|
|
27
|
-
export type SchoolEvidence = "same" | "moved" | "none" | "unresolved" | "unasked";
|
|
33
|
+
export type SchoolEvidence = "same" | "lineage" | "moved" | "none" | "unresolved" | "unasked";
|
|
28
34
|
export interface DancerSchool {
|
|
29
35
|
school_id: string | null;
|
|
30
36
|
print: string;
|
|
@@ -43,6 +49,7 @@ export interface DancerCandidate {
|
|
|
43
49
|
age: AgeEvidence;
|
|
44
50
|
school: SchoolEvidence;
|
|
45
51
|
population: PopulationEvidence;
|
|
52
|
+
region: RegionEvidence;
|
|
46
53
|
};
|
|
47
54
|
prints: {
|
|
48
55
|
print: string;
|
|
@@ -63,10 +70,17 @@ export interface DancerCandidate {
|
|
|
63
70
|
boys: number;
|
|
64
71
|
};
|
|
65
72
|
school: DancerSchool | null;
|
|
73
|
+
region?: DancerRegion;
|
|
74
|
+
}
|
|
75
|
+
export interface DancerRegion {
|
|
76
|
+
year: number | null;
|
|
77
|
+
region: string | null;
|
|
78
|
+
regional: boolean;
|
|
79
|
+
attended: number;
|
|
66
80
|
}
|
|
67
81
|
export interface ExcludedCandidate {
|
|
68
82
|
candidate: DancerCandidate;
|
|
69
|
-
why: "age" | "population";
|
|
83
|
+
why: "age" | "population" | "region";
|
|
70
84
|
}
|
|
71
85
|
export interface DancerAnswer {
|
|
72
86
|
tag: string;
|
|
@@ -95,9 +109,16 @@ export interface DancerAnswer {
|
|
|
95
109
|
read: "girls" | "boys" | null;
|
|
96
110
|
note?: string;
|
|
97
111
|
};
|
|
112
|
+
oireachtas?: {
|
|
113
|
+
asked: string;
|
|
114
|
+
read: string | null;
|
|
115
|
+
year: number;
|
|
116
|
+
note?: string;
|
|
117
|
+
};
|
|
98
118
|
school?: {
|
|
99
119
|
arm: string;
|
|
100
120
|
ids: string[];
|
|
121
|
+
lineage?: string[];
|
|
101
122
|
};
|
|
102
123
|
refused?: string;
|
|
103
124
|
}
|
|
@@ -163,7 +184,10 @@ export interface DancerYear {
|
|
|
163
184
|
export declare class DancerResolver {
|
|
164
185
|
private readonly rel;
|
|
165
186
|
private readonly printIds;
|
|
187
|
+
private lineageOf;
|
|
188
|
+
private regions;
|
|
166
189
|
constructor(rel: Release);
|
|
190
|
+
private lineage;
|
|
167
191
|
records(q?: {
|
|
168
192
|
ids?: readonly number[];
|
|
169
193
|
}): Promise<{
|
|
@@ -182,6 +206,7 @@ export declare class DancerResolver {
|
|
|
182
206
|
private capped;
|
|
183
207
|
get(dancerId: number): Promise<DancerRecord | null>;
|
|
184
208
|
private answer;
|
|
209
|
+
private readOireachtas;
|
|
185
210
|
private schoolEvidence;
|
|
186
211
|
private readAge;
|
|
187
212
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { type AssetPlan, DATABASES, type DatabaseName, type LandedFile, type Receipt, type ReleaseManifest, planAsset, } from "./assets.js";
|
|
2
2
|
export { sha256File, unzstdTo } from "./bytes.js";
|
|
3
|
-
export { type AgeAsked, type AgeEvidence, type DancerAnswer, type DancerBulkRecord, type DancerCandidate, type DancerQuery, type DancerRecord, DancerResolver, type DancerSchool, type DancerSearchQuery, type DancerYear, type ExcludedCandidate, FEIS_LOCAL_ID_FLOOR, type PopulationEvidence, type SchoolEvidence, readPopulation, } from "./dancers.js";
|
|
3
|
+
export { type AgeAsked, type AgeEvidence, type DancerAnswer, type DancerBulkRecord, type DancerCandidate, type DancerQuery, type DancerRecord, DancerResolver, type DancerRegion, type DancerSchool, type DancerSearchQuery, type DancerYear, type ExcludedCandidate, FEIS_LOCAL_ID_FLOOR, type OireachtasAsked, type PopulationEvidence, type RegionEvidence, type SchoolEvidence, readPopulation, } from "./dancers.js";
|
|
4
4
|
export { NAME_LEVELS, type NameLevel, nameLevel } from "./dancers-names.js";
|
|
5
5
|
export { type Comp, type CompAnswer, type CompDance, type CompKind, type CompQuery, CompResolver, type CompSource, type FeisPlace, type SyllabusAnswer, type SyllabusForAnswer, type SyllabusSource, type TitleCheck, canonicalOf, } from "./comps.js";
|
|
6
6
|
export { type ClientOptions, cacheRoot, repoOf } from "./config.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CompAnswer, CompResolver } from "./comps.js";
|
|
2
|
-
import { type DancerAnswer, type DancerQuery } from "./dancers.js";
|
|
2
|
+
import { type DancerAnswer, type DancerQuery, type OireachtasAsked } from "./dancers.js";
|
|
3
3
|
import type { Release } from "./release.js";
|
|
4
4
|
export interface RegistrationRow {
|
|
5
5
|
name: string;
|
|
@@ -11,6 +11,7 @@ export interface RegistrationRow {
|
|
|
11
11
|
titles?: Readonly<Record<string, string>>;
|
|
12
12
|
asOf?: string;
|
|
13
13
|
population?: string;
|
|
14
|
+
oireachtas?: OireachtasAsked;
|
|
14
15
|
[k: string]: unknown;
|
|
15
16
|
}
|
|
16
17
|
export type DancerQueryLike = DancerQuery;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finbheara/archive-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Open a versioned release of an Irish dance results archive: download, verify, decompress and cache it by tag, keep the cache, and resolve feises and schools against it",
|
|
5
5
|
"keywords": ["irish-dance", "results", "release", "cache", "sqlite"],
|
|
6
6
|
"license": "MIT",
|