@finbheara/archive-client 0.1.2 → 0.2.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 +44 -6
- package/dist/chunks/{index-zr8tfjk0.js → index-6e2gb6sk.js} +226 -11
- package/dist/cli.js +33 -9
- package/dist/index.js +11 -1
- package/dist/types/assets.d.ts +1 -0
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/dancers.d.ts +18 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/registrations.d.ts +8 -0
- package/dist/types/release.d.ts +10 -0
- package/dist/types/schema.d.ts +20 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ type declarations (`.d.ts`), so a TypeScript project needs Bun's types (`"types"
|
|
|
20
20
|
`bunx idq <verb>`.
|
|
21
21
|
|
|
22
22
|
The version is 0.x while the API settles: a minor bump may break, so pin a minor
|
|
23
|
-
(`"@finbheara/archive-client": "0.
|
|
23
|
+
(`"@finbheara/archive-client": "0.2.x"`).
|
|
24
24
|
|
|
25
25
|
## Configuration
|
|
26
26
|
|
|
@@ -67,6 +67,25 @@ rel.path("school-registry.jsonl"); // its cach
|
|
|
67
67
|
rel.close();
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
An older release that lacks what a call reads (a column, a table or a side file a newer release
|
|
71
|
+
added) is REFUSED BY NAME, never read in part: the call throws, naming the feature, what the
|
|
72
|
+
release lacks and the first cut that publishes it. What a release carries is read from its own
|
|
73
|
+
schema and assets, never from its tag:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const c = await rel.capabilities();
|
|
77
|
+
c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...], since: 260, calls }]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
| feature | the calls | from cut |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `results.level` | `results.query`, `majors.results` | 215 |
|
|
83
|
+
| `dancers` | `dancers.resolve`, `search`, `get` | 260 |
|
|
84
|
+
| `schools` | `schools.resolve`, a dancer question with a school | 260 |
|
|
85
|
+
| `staff` | `staff.teacher`, `staff.adjudicator` | 260 |
|
|
86
|
+
| `majors.order` | `majors.competitions` | 260 |
|
|
87
|
+
| `feises`, `comps` | `feises.find`, `feises.series`, `comps.*`, `registrations.resolve` | 265 |
|
|
88
|
+
|
|
70
89
|
A spec is `pinned` (the default: the user-local pin, else the latest), `latest`, a cut number
|
|
71
90
|
(`42`, or `42.1` for a fraction) or a tag (`archive-YYYY.MM.DD-N`).
|
|
72
91
|
|
|
@@ -182,10 +201,11 @@ const r = await rel.dancers.resolve({
|
|
|
182
201
|
school: "Ardvarna Academy", // a print or an id, through rel.schools
|
|
183
202
|
age: { band: "U10", year: 2026 }, // or { yob: 2016 }, or { comp: "<competition>" }
|
|
184
203
|
asOf: "2026-10-17", // the school as of then
|
|
204
|
+
population: "girls", // or "boys": only a contradiction excludes
|
|
185
205
|
});
|
|
186
206
|
r.arm; // "unique" | "ambiguous" | "none"
|
|
187
207
|
r.candidates; // [{ dancer_id, display, scope, score, evidence: { nameLevel, age, school }, prints, ... }]
|
|
188
|
-
r.excluded; // the candidates
|
|
208
|
+
r.excluded; // the candidates a contradiction set aside (why: "age" | "population")
|
|
189
209
|
r.preferred; // NON-BINDING: { dancer_id, why } where the school singles one out; never moves arm
|
|
190
210
|
r.unlinked; // rows printing a matched name that the release linked to no dancer
|
|
191
211
|
|
|
@@ -202,8 +222,21 @@ meets the asked age (`Age.fitsIn`, `overlaps`): `fits`, `overlaps`, `contradicts
|
|
|
202
222
|
`school` is how the dancer's schools meet the asked one: `same` (the latest school as of `asOf`),
|
|
203
223
|
`moved` (an earlier one), `none`, `unresolved` (the print reached no school).
|
|
204
224
|
|
|
225
|
+
`population` is how the dancer's published majors population meets the asked one: `same`,
|
|
226
|
+
`contradicts`, `both`, `unknown`, `unasked`. A dancer's population is read only from its rows at
|
|
227
|
+
the majors (tiers T1 to T3) in competitions whose published `gender` reads girls or boys
|
|
228
|
+
(`@finbheara/domain`'s `populationIn`), whatever the competition's category (a solo championship
|
|
229
|
+
often publishes none), counted on the candidate (`population: { girls, boys }`). The results hold
|
|
230
|
+
individual dancers' rows only (a team is published apart), so every such row is a solo row. A feis
|
|
231
|
+
competition is no evidence either way (below open a feis runs its competitions mixed and may merge
|
|
232
|
+
its few boys into the girls' group), and a mixed competition reads `mixed`.
|
|
233
|
+
|
|
205
234
|
NO SILENT GUESS. Only a contradiction sets a candidate aside: a birth window that shares no birth
|
|
206
|
-
year with the asked age
|
|
235
|
+
year with the asked age, or a dancer the release publishes in the other population's gendered
|
|
236
|
+
majors solo competitions and in none of the asked one's (listed in `excluded`, `why: "age"` or
|
|
237
|
+
`"population"`, and a candidate contradicting both says `age`). A dancer published in both, or in
|
|
238
|
+
neither, stays. A population word that names no one population (`mixed`) narrows nothing, and the
|
|
239
|
+
answer says so (`r.population.note`). The school is evidence and order, never a reason
|
|
207
240
|
to drop anyone: candidates whose latest school is the asked one come first, then those that ever
|
|
208
241
|
danced for it, then the rest. A school row meets the asked school by its id, else by its print
|
|
209
242
|
(one `rel.schools` resolves to the asked ids), so a latest print the release
|
|
@@ -301,10 +334,13 @@ const rows = await rel.registrations.resolve([
|
|
|
301
334
|
]);
|
|
302
335
|
rows[0].comps; // one competition answer per code
|
|
303
336
|
rows[0].age; // { band: "U10", year: 2026, code: "610", from: "comp" }: the band the dancer is asked at
|
|
337
|
+
rows[0].population; // { asked: "girls", from: "comp", codes: ["610"] }: who the dancer is asked as
|
|
304
338
|
rows[0].dancer; // the dancer resolver's answer
|
|
305
339
|
```
|
|
306
340
|
|
|
307
|
-
Each code is resolved as above. The dancer is asked at the
|
|
341
|
+
Each code is resolved as above. The dancer is asked at the row's own `population` where it gives
|
|
342
|
+
one, else at the one population its unique comps read (`from: "comp"`, with the codes). Comps
|
|
343
|
+
reading both populations give none, with the reason. The dancer is asked at the YOUNGEST band the row's competitions
|
|
308
344
|
read (the lowest ceiling, then the highest floor): a dancer may dance up into an older band and
|
|
309
345
|
never down, so the youngest bounds the dancer. Every band a row read is listed (`bands`) where
|
|
310
346
|
there are several. Where no comp answers with a band (a syllabus read from results that lacks the
|
|
@@ -323,6 +359,7 @@ and the competitions still resolve.
|
|
|
323
359
|
idq add <dir> --tag <tag>
|
|
324
360
|
idq index [--release <spec>] [<name>...] [--force]
|
|
325
361
|
idq verify [--release <spec>]
|
|
362
|
+
idq capabilities [--release <spec>]
|
|
326
363
|
idq prune [--keep N] [--dry-run]
|
|
327
364
|
idq rm <tag> [--dry-run]
|
|
328
365
|
idq drop-indexes [<tag>] [--dry-run]
|
|
@@ -331,7 +368,8 @@ and the competitions still resolve.
|
|
|
331
368
|
idq school [--release <spec>] <print|school_id> [--region <code>]
|
|
332
369
|
idq dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>
|
|
333
370
|
[--order surname-first|given-first] [--school <print|id>]
|
|
334
|
-
[--yob <y> | --band <band> [--year <y>] | --comp <competition>] [--as-of <date>]
|
|
371
|
+
[--yob <y> | --band <band> [--year <y>] | --comp <competition>] [--as-of <date>]
|
|
372
|
+
[--population girls|boys] [--rows]
|
|
335
373
|
idq results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <c>] [--school <s>] [--year <y>] [--limit <n>]
|
|
336
374
|
idq majors [--release <spec>] [--event <e>] [--year <y>] [--comp <c>] [--dancer <id>] [--rows]
|
|
337
375
|
idq teacher [--release <spec>] <name|TD number>
|
|
@@ -348,7 +386,7 @@ and the competitions still resolve.
|
|
|
348
386
|
|
|
349
387
|
`resolve` speaks JSON lines: one answer per line out, and with no positional question the
|
|
350
388
|
questions are JSON lines on stdin, one per line (`{feis, code, year?, title?}`,
|
|
351
|
-
`{name, school?, age?}`, `{name, school?, codes, feis, year?}`). A line that is no JSON object,
|
|
389
|
+
`{name, school?, age?, population?}`, `{name, school?, codes, feis, year?, population?}`). A line that is no JSON object,
|
|
352
390
|
or no question of its kind, answers `{tag, line, error}` and the other lines still answer. The
|
|
353
391
|
exit is non-zero where any line erred. A dancer's age may be given as a comp
|
|
354
392
|
(`{"comp": "<feis>/<code>"}`, or `--comp` on the command line): `rel.registrations.dancer` reads
|
|
@@ -71,6 +71,7 @@ import { Age } from "@finbheara/domain/age";
|
|
|
71
71
|
import { createIndex, indexSchema, personEncoder } from "@finbheara/domain/noun-index";
|
|
72
72
|
import domainPkg from "@finbheara/domain/package.json";
|
|
73
73
|
import { dancerName } from "@finbheara/domain/person";
|
|
74
|
+
import { populationIn, populationRelation } from "@finbheara/domain/title";
|
|
74
75
|
import {
|
|
75
76
|
foldNameKey,
|
|
76
77
|
surnameBlock
|
|
@@ -160,6 +161,7 @@ class ResultFinder2 {
|
|
|
160
161
|
}
|
|
161
162
|
async query(q) {
|
|
162
163
|
await this.rel.fetch("results");
|
|
164
|
+
await this.rel.require("results.level");
|
|
163
165
|
return queryResults(this.rel, "results", q);
|
|
164
166
|
}
|
|
165
167
|
}
|
|
@@ -188,6 +190,7 @@ class MajorsFinder2 {
|
|
|
188
190
|
}
|
|
189
191
|
async competitions(q) {
|
|
190
192
|
const db = await this.db();
|
|
193
|
+
await this.rel.require("majors.order");
|
|
191
194
|
const args = [q.event];
|
|
192
195
|
let year = "";
|
|
193
196
|
if (q.year !== undefined) {
|
|
@@ -207,6 +210,7 @@ class MajorsFinder2 {
|
|
|
207
210
|
}
|
|
208
211
|
async results(q) {
|
|
209
212
|
await this.db();
|
|
213
|
+
await this.rel.require("results.level");
|
|
210
214
|
return queryResults(this.rel, "majors", q);
|
|
211
215
|
}
|
|
212
216
|
}
|
|
@@ -223,7 +227,7 @@ var PERSON = { table: "person", value: "id", valueType: "INTEGER PRIMARY KEY" };
|
|
|
223
227
|
var ASKED = ["surname_key", "nn4", "blk_surname"];
|
|
224
228
|
var dancersIndex = {
|
|
225
229
|
name: "dancers",
|
|
226
|
-
version: "
|
|
230
|
+
version: "2",
|
|
227
231
|
inputs: ["results"],
|
|
228
232
|
keyParts: () => [...PACKAGE_KEY_PARTS],
|
|
229
233
|
says: "every published dancer print at the names package's keys, its dancers, schools and years",
|
|
@@ -233,6 +237,7 @@ var dancersIndex = {
|
|
|
233
237
|
out.run("CREATE TABLE print_dancer(person INTEGER NOT NULL, dancer_id INTEGER, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER)");
|
|
234
238
|
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)");
|
|
235
239
|
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)");
|
|
240
|
+
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");
|
|
236
241
|
const src = rel.db("results");
|
|
237
242
|
const groups = src.query(`SELECT r.name_printed AS p, r.name_order_resolved AS o, r.dancer_id AS d,
|
|
238
243
|
r.school_printed AS sp, r.school_id AS s,
|
|
@@ -298,6 +303,15 @@ var dancersIndex = {
|
|
|
298
303
|
const t = dt.get(d.dancer_id);
|
|
299
304
|
putD.run(d.dancer_id, d.display, d.names_printed, d.genders, d.appearances, d.scope, d.birth_lo, d.birth_hi, d.birth_source, d.birth_conflict, t?.rows ?? 0, t?.lo ?? null, t?.hi ?? null);
|
|
300
305
|
}
|
|
306
|
+
const putPop = out.prepare("INSERT INTO dancer_population VALUES (?, ?, ?) ON CONFLICT(dancer_id, population) DO UPDATE SET rows = rows + excluded.rows");
|
|
307
|
+
for (const r of src.query(`SELECT r.dancer_id AS d, c.gender AS g, count(*) AS n
|
|
308
|
+
FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
|
|
309
|
+
WHERE r.dancer_id IS NOT NULL AND c.tier IN ('T1', 'T2', 'T3') AND c.gender IS NOT NULL
|
|
310
|
+
GROUP BY 1, 2`).all()) {
|
|
311
|
+
const pop = populationIn(r.g);
|
|
312
|
+
if (pop === "girls" || pop === "boys")
|
|
313
|
+
putPop.run(r.d, pop, r.n);
|
|
314
|
+
}
|
|
301
315
|
for (const s of schema.indexes)
|
|
302
316
|
if (ASKED.some((c) => s.endsWith(`(${c})`)))
|
|
303
317
|
out.run(s);
|
|
@@ -337,6 +351,13 @@ var SCHOOL_RANK = {
|
|
|
337
351
|
unresolved: 2,
|
|
338
352
|
unasked: 2
|
|
339
353
|
};
|
|
354
|
+
var POPULATION_WEIGHT = {
|
|
355
|
+
same: 1,
|
|
356
|
+
both: 0,
|
|
357
|
+
unknown: 0,
|
|
358
|
+
unasked: 0,
|
|
359
|
+
contradicts: -10
|
|
360
|
+
};
|
|
340
361
|
var SCHOOL_WEIGHT = {
|
|
341
362
|
same: 2,
|
|
342
363
|
moved: 1,
|
|
@@ -418,6 +439,7 @@ class DancerResolver2 {
|
|
|
418
439
|
year_lo: d.year_lo,
|
|
419
440
|
year_hi: d.year_hi,
|
|
420
441
|
birth: birthOf(d),
|
|
442
|
+
population: populationOf(db, dancerId),
|
|
421
443
|
prints,
|
|
422
444
|
schools: schoolsOf(db, dancerId)
|
|
423
445
|
};
|
|
@@ -425,6 +447,7 @@ class DancerResolver2 {
|
|
|
425
447
|
async answer(db, q, hits, narrow = true) {
|
|
426
448
|
const asOf = yearOf(q.asOf);
|
|
427
449
|
const age = q.age === undefined ? undefined : this.readAge(q.age, asOf);
|
|
450
|
+
const population = q.population === undefined ? undefined : readPopulation2(q.population);
|
|
428
451
|
let schoolIds = null;
|
|
429
452
|
let school;
|
|
430
453
|
if (q.school !== undefined) {
|
|
@@ -455,19 +478,22 @@ class DancerResolver2 {
|
|
|
455
478
|
const schools = schoolsOf(db, id);
|
|
456
479
|
const latest = latestSchool(schools, asOf);
|
|
457
480
|
const ageEv = ageEvidence(d, age?.age ?? null);
|
|
481
|
+
const pop = populationOf(db, id);
|
|
482
|
+
const popEv = populationEvidence(pop, population?.read ?? null);
|
|
458
483
|
const schoolEv = schoolIds === null ? "unasked" : schoolIds.length === 0 ? "unresolved" : await this.schoolEvidence(schools, schoolIds, asOf);
|
|
459
484
|
all.push({
|
|
460
485
|
dancer_id: id,
|
|
461
486
|
display: d.display,
|
|
462
487
|
scope: id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable",
|
|
463
488
|
kind: d.kind,
|
|
464
|
-
score: NAME_WEIGHT[e.level] + AGE_WEIGHT[ageEv] + SCHOOL_WEIGHT[schoolEv],
|
|
465
|
-
evidence: { nameLevel: e.level, age: ageEv, school: schoolEv },
|
|
489
|
+
score: NAME_WEIGHT[e.level] + AGE_WEIGHT[ageEv] + SCHOOL_WEIGHT[schoolEv] + POPULATION_WEIGHT[popEv],
|
|
490
|
+
evidence: { nameLevel: e.level, age: ageEv, school: schoolEv, population: popEv },
|
|
466
491
|
prints: e.prints.sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
|
|
467
492
|
rows: d.rows,
|
|
468
493
|
year_lo: d.year_lo,
|
|
469
494
|
year_hi: d.year_hi,
|
|
470
495
|
birth: birthOf(d),
|
|
496
|
+
population: pop,
|
|
471
497
|
school: latest
|
|
472
498
|
});
|
|
473
499
|
}
|
|
@@ -476,9 +502,12 @@ class DancerResolver2 {
|
|
|
476
502
|
let kept = all;
|
|
477
503
|
if (narrow)
|
|
478
504
|
kept = all.filter((c) => {
|
|
479
|
-
if (c.evidence.age
|
|
505
|
+
if (c.evidence.age === "contradicts")
|
|
506
|
+
excluded.push({ candidate: c, why: "age" });
|
|
507
|
+
else if (c.evidence.population === "contradicts")
|
|
508
|
+
excluded.push({ candidate: c, why: "population" });
|
|
509
|
+
else
|
|
480
510
|
return true;
|
|
481
|
-
excluded.push({ candidate: c, why: "age" });
|
|
482
511
|
return false;
|
|
483
512
|
});
|
|
484
513
|
let preferred;
|
|
@@ -503,6 +532,7 @@ class DancerResolver2 {
|
|
|
503
532
|
...preferred !== undefined ? { preferred } : {},
|
|
504
533
|
unlinked: [...unlinked].map(([print, rows]) => ({ print, rows })).sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
|
|
505
534
|
...age !== undefined ? { age: age.answer } : {},
|
|
535
|
+
...population !== undefined ? { population } : {},
|
|
506
536
|
...school !== undefined ? { school } : {}
|
|
507
537
|
};
|
|
508
538
|
}
|
|
@@ -580,6 +610,34 @@ function latestSchool(schools, asOf) {
|
|
|
580
610
|
}
|
|
581
611
|
return best;
|
|
582
612
|
}
|
|
613
|
+
function populationOf(db, dancerId) {
|
|
614
|
+
const out = { girls: 0, boys: 0 };
|
|
615
|
+
for (const r of db.query("SELECT population, rows FROM dancer_population WHERE dancer_id = ?").all(dancerId))
|
|
616
|
+
if (r.population === "girls" || r.population === "boys")
|
|
617
|
+
out[r.population] = r.rows;
|
|
618
|
+
return out;
|
|
619
|
+
}
|
|
620
|
+
function readPopulation2(asked) {
|
|
621
|
+
const w = asked.trim().toLowerCase();
|
|
622
|
+
const read = w === "girls" || w === "boys" ? w : populationIn(w);
|
|
623
|
+
if (read === "girls" || read === "boys")
|
|
624
|
+
return { asked, read };
|
|
625
|
+
return {
|
|
626
|
+
asked,
|
|
627
|
+
read: null,
|
|
628
|
+
note: read === "mixed" ? `"${asked}" reads as mixed: it names no one population, so it narrows nothing` : `"${asked}" reads as no population (girls or boys): it narrows nothing`
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
function populationEvidence(own, asked) {
|
|
632
|
+
if (asked === null)
|
|
633
|
+
return "unasked";
|
|
634
|
+
if (own.girls > 0 && own.boys > 0)
|
|
635
|
+
return "both";
|
|
636
|
+
const mine = own.girls > 0 ? "girls" : own.boys > 0 ? "boys" : "";
|
|
637
|
+
if (mine === "")
|
|
638
|
+
return "unknown";
|
|
639
|
+
return populationRelation(asked, mine) === "disjoint" ? "contradicts" : "same";
|
|
640
|
+
}
|
|
583
641
|
function ageEvidence(d, asked) {
|
|
584
642
|
if (asked === null)
|
|
585
643
|
return "unasked";
|
|
@@ -599,7 +657,7 @@ import { Age as Age2 } from "@finbheara/domain/age";
|
|
|
599
657
|
import {
|
|
600
658
|
compTitle,
|
|
601
659
|
matchTitle,
|
|
602
|
-
populationIn,
|
|
660
|
+
populationIn as populationIn2,
|
|
603
661
|
titleRelation
|
|
604
662
|
} from "@finbheara/domain/title";
|
|
605
663
|
var LEVEL = Object.freeze({
|
|
@@ -651,7 +709,7 @@ var compsIndex = {
|
|
|
651
709
|
function compOf(r) {
|
|
652
710
|
const lv = r.feis_level;
|
|
653
711
|
const kind = lv === null ? "unrated" : lv >= 600 ? "championship" : "grade";
|
|
654
|
-
const pop =
|
|
712
|
+
const pop = populationIn2(r.gender ?? "") || populationIn2(r.title ?? "");
|
|
655
713
|
const round = r.round ?? (r.title === null ? null : compTitle(r.title).round?.toString() ?? null);
|
|
656
714
|
return {
|
|
657
715
|
competition: r.competition,
|
|
@@ -1724,13 +1782,31 @@ class RegistrationResolver2 {
|
|
|
1724
1782
|
from: "comp"
|
|
1725
1783
|
} : byTitle !== undefined ? { band: byTitle.age, year: byTitle.year, code: byTitle.code, from: "title" } : null;
|
|
1726
1784
|
const bands = [...new Set((first !== undefined ? read : printed).map((c) => c.age))];
|
|
1785
|
+
let population = null;
|
|
1786
|
+
if (row.population !== undefined)
|
|
1787
|
+
population = { asked: row.population, from: "row" };
|
|
1788
|
+
else {
|
|
1789
|
+
const gendered = comps.flatMap((c, i) => c.arm === "unique" && (c.population === "girls" || c.population === "boys") ? [{ pop: c.population, code: row.codes[i] }] : []);
|
|
1790
|
+
const pops = [...new Set(gendered.map((g) => g.pop))];
|
|
1791
|
+
if (pops.length === 1)
|
|
1792
|
+
population = {
|
|
1793
|
+
asked: pops[0],
|
|
1794
|
+
from: "comp",
|
|
1795
|
+
codes: gendered.map((g) => g.code)
|
|
1796
|
+
};
|
|
1797
|
+
else if (pops.length > 1)
|
|
1798
|
+
population = {
|
|
1799
|
+
why: `the row's comps read both populations (${gendered.map((g) => `${g.code}: ${g.pop}`).join(", ")}): none asked`
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1727
1802
|
const order = nameOrderOf(row.order);
|
|
1728
1803
|
const asked = {
|
|
1729
1804
|
name: row.name,
|
|
1730
1805
|
...order !== undefined ? { order } : {},
|
|
1731
1806
|
...row.school !== undefined ? { school: row.school } : {},
|
|
1732
1807
|
...age !== null ? { age: { band: age.band, year: age.year } } : {},
|
|
1733
|
-
...row.asOf !== undefined ? { asOf: row.asOf } : {}
|
|
1808
|
+
...row.asOf !== undefined ? { asOf: row.asOf } : {},
|
|
1809
|
+
...population !== null && "asked" in population ? { population: population.asked } : {}
|
|
1734
1810
|
};
|
|
1735
1811
|
const { dancer } = await this.dancer(asked);
|
|
1736
1812
|
out.push({
|
|
@@ -1739,6 +1815,7 @@ class RegistrationResolver2 {
|
|
|
1739
1815
|
comps,
|
|
1740
1816
|
age,
|
|
1741
1817
|
...bands.length > 1 ? { bands } : {},
|
|
1818
|
+
population,
|
|
1742
1819
|
dancer,
|
|
1743
1820
|
asked
|
|
1744
1821
|
});
|
|
@@ -1903,6 +1980,88 @@ function defaultRemote() {
|
|
|
1903
1980
|
return installed ?? fallbackRemote2(ghRemote2, httpsRemote2());
|
|
1904
1981
|
}
|
|
1905
1982
|
|
|
1983
|
+
function columnsOf2(db, table) {
|
|
1984
|
+
return new Set(db.query("SELECT name FROM pragma_table_info(?)").all(table).map((r) => r.name));
|
|
1985
|
+
}
|
|
1986
|
+
var FEATURES2 = [
|
|
1987
|
+
{
|
|
1988
|
+
feature: "results.level",
|
|
1989
|
+
needs: ["results:competitions.feis_level"],
|
|
1990
|
+
since: 215,
|
|
1991
|
+
calls: "results.query, majors.results"
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
feature: "majors.order",
|
|
1995
|
+
needs: ["majors:competitions.seq"],
|
|
1996
|
+
since: 260,
|
|
1997
|
+
calls: "majors.competitions"
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
feature: "dancers",
|
|
2001
|
+
needs: ["results:results.name_order_resolved"],
|
|
2002
|
+
since: 260,
|
|
2003
|
+
calls: "dancers.resolve, dancers.search, dancers.get (the dancers index)"
|
|
2004
|
+
},
|
|
2005
|
+
{
|
|
2006
|
+
feature: "schools",
|
|
2007
|
+
needs: ["school-registry.jsonl", "school-redirects.jsonl"],
|
|
2008
|
+
since: 260,
|
|
2009
|
+
calls: "schools.resolve, a dancer question with a school (the schools index)"
|
|
2010
|
+
},
|
|
2011
|
+
{
|
|
2012
|
+
feature: "feises",
|
|
2013
|
+
needs: [
|
|
2014
|
+
"results:feises.date_lo",
|
|
2015
|
+
"results:feises.date_hi",
|
|
2016
|
+
"results:feises.feis_instance_id",
|
|
2017
|
+
"results:feises.feis_brand_id",
|
|
2018
|
+
"results:competitions.feis_region",
|
|
2019
|
+
"feis-redirects.jsonl"
|
|
2020
|
+
],
|
|
2021
|
+
since: 265,
|
|
2022
|
+
calls: "feises.find, feises.series, and comps and registrations through them (the feises index)"
|
|
2023
|
+
},
|
|
2024
|
+
{
|
|
2025
|
+
feature: "comps",
|
|
2026
|
+
needs: [
|
|
2027
|
+
"results:competitions.feis_level",
|
|
2028
|
+
"results:competitions.round",
|
|
2029
|
+
"results:feises.feis_brand_id"
|
|
2030
|
+
],
|
|
2031
|
+
since: 265,
|
|
2032
|
+
calls: "comps.resolve, comps.syllabus, comps.syllabusFor, registrations.resolve (the comps index)"
|
|
2033
|
+
},
|
|
2034
|
+
{
|
|
2035
|
+
feature: "staff",
|
|
2036
|
+
needs: [
|
|
2037
|
+
"results:adjudicators",
|
|
2038
|
+
"results:feis_panels",
|
|
2039
|
+
"school-staff.jsonl",
|
|
2040
|
+
"school-teachers.jsonl",
|
|
2041
|
+
"school-registry.jsonl"
|
|
2042
|
+
],
|
|
2043
|
+
since: 260,
|
|
2044
|
+
calls: "staff.teacher, staff.adjudicator (the staff index)"
|
|
2045
|
+
}
|
|
2046
|
+
];
|
|
2047
|
+
async function hasNeed(probe, n) {
|
|
2048
|
+
const m = /^(results|majors):([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
|
|
2049
|
+
if (m === null)
|
|
2050
|
+
return probe.publishes(n);
|
|
2051
|
+
const cols = columnsOf2(await probe.db(m[1]), m[2]);
|
|
2052
|
+
return m[3] === undefined ? cols.size > 0 : cols.has(m[3]);
|
|
2053
|
+
}
|
|
2054
|
+
async function capabilityOf2(probe, f) {
|
|
2055
|
+
const missing = [];
|
|
2056
|
+
for (const n of f.needs)
|
|
2057
|
+
if (!await hasNeed(probe, n))
|
|
2058
|
+
missing.push(n);
|
|
2059
|
+
return { ...f, state: missing.length === 0 ? "full" : "missing", missing };
|
|
2060
|
+
}
|
|
2061
|
+
function needsRelease2(tag, c) {
|
|
2062
|
+
return new Error(`archive-client: ${tag} is too old for ${c.feature} (${c.calls}): it lacks ${c.missing.join(", ")}. Open a release of cut ${c.since} or later`);
|
|
2063
|
+
}
|
|
2064
|
+
|
|
1906
2065
|
function tagDir(root, tag) {
|
|
1907
2066
|
if (parseArchiveTag2(tag) === null)
|
|
1908
2067
|
throw new Error(`archive-client: "${tag}" is not a release tag (archive-YYYY.MM.DD[-N[.k]])`);
|
|
@@ -1999,9 +2158,62 @@ class Release2 {
|
|
|
1999
2158
|
out.push(await this.fetchOne(a));
|
|
2000
2159
|
return out;
|
|
2001
2160
|
}
|
|
2161
|
+
async publishes(asset) {
|
|
2162
|
+
const plan = this.plan(asset);
|
|
2163
|
+
const r = this.receipt();
|
|
2164
|
+
if (r.files[plan.local] !== undefined)
|
|
2165
|
+
return true;
|
|
2166
|
+
if (r.source.startsWith("local:"))
|
|
2167
|
+
return this.has(asset);
|
|
2168
|
+
if (r.published !== undefined)
|
|
2169
|
+
return r.published.some((n) => plan.published.includes(n));
|
|
2170
|
+
let listed;
|
|
2171
|
+
try {
|
|
2172
|
+
listed = await this.listed();
|
|
2173
|
+
} catch (e) {
|
|
2174
|
+
throw new Error(`archive-client: cannot tell whether ${this.tag} publishes ${asset}: its cache records no asset listing and the host did not answer (${String(e)}). Open it once with the network`);
|
|
2175
|
+
}
|
|
2176
|
+
return listed.some((a) => plan.published.includes(a.name));
|
|
2177
|
+
}
|
|
2178
|
+
probe = {
|
|
2179
|
+
db: async (name) => {
|
|
2180
|
+
await this.fetch(name);
|
|
2181
|
+
return this.db(name);
|
|
2182
|
+
},
|
|
2183
|
+
publishes: (asset) => this.publishes(asset)
|
|
2184
|
+
};
|
|
2185
|
+
checked = new Map;
|
|
2186
|
+
async capability(feature) {
|
|
2187
|
+
let c = this.checked.get(feature);
|
|
2188
|
+
if (c === undefined) {
|
|
2189
|
+
const f = FEATURES2.find((x) => x.feature === feature);
|
|
2190
|
+
if (f === undefined)
|
|
2191
|
+
throw new Error(`archive-client: no feature "${feature}"`);
|
|
2192
|
+
c = await capabilityOf2(this.probe, f);
|
|
2193
|
+
this.checked.set(feature, c);
|
|
2194
|
+
}
|
|
2195
|
+
return c;
|
|
2196
|
+
}
|
|
2197
|
+
async capabilities() {
|
|
2198
|
+
const features = [];
|
|
2199
|
+
for (const f of FEATURES2)
|
|
2200
|
+
features.push(await this.capability(f.feature));
|
|
2201
|
+
return { tag: this.tag, features };
|
|
2202
|
+
}
|
|
2203
|
+
async require(feature) {
|
|
2204
|
+
const c = await this.capability(feature);
|
|
2205
|
+
if (c.state === "missing")
|
|
2206
|
+
throw needsRelease2(this.tag, c);
|
|
2207
|
+
}
|
|
2002
2208
|
async listed() {
|
|
2003
|
-
if (this.remoteAssets === null)
|
|
2209
|
+
if (this.remoteAssets === null) {
|
|
2004
2210
|
this.remoteAssets = await (this.opts.remote ?? defaultRemote()).assets(requireRepo2(this.opts), this.tag);
|
|
2211
|
+
if (existsSync4(this.dir)) {
|
|
2212
|
+
const r = this.receipt();
|
|
2213
|
+
r.published = this.remoteAssets.map((a) => a.name).sort();
|
|
2214
|
+
writeJsonAtomic2(receiptPath(this.dir), r);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2005
2217
|
return this.remoteAssets;
|
|
2006
2218
|
}
|
|
2007
2219
|
async fetchOne(asset) {
|
|
@@ -2062,6 +2274,8 @@ class Release2 {
|
|
|
2062
2274
|
const b = this.builders().find((x) => x.name === name);
|
|
2063
2275
|
if (b === undefined)
|
|
2064
2276
|
throw new Error(`archive-client: no index "${name}" (known: ${this.builders().map((x) => x.name).join(", ")})`);
|
|
2277
|
+
if (FEATURES2.some((f) => f.feature === b.name))
|
|
2278
|
+
await this.require(b.name);
|
|
2065
2279
|
await this.fetch(...b.inputs);
|
|
2066
2280
|
return buildIndex2(this, b, { force: o.force ?? false, log: this.opts.log });
|
|
2067
2281
|
}
|
|
@@ -2142,7 +2356,8 @@ async function openRelease2(spec = "pinned", opts = {}) {
|
|
|
2142
2356
|
writeJsonAtomic2(receiptPath(dir), {
|
|
2143
2357
|
tag,
|
|
2144
2358
|
source: repo,
|
|
2145
|
-
files: { "MANIFEST.json": landed }
|
|
2359
|
+
files: { "MANIFEST.json": landed },
|
|
2360
|
+
published: listed.map((a) => a.name).sort()
|
|
2146
2361
|
});
|
|
2147
2362
|
dropIfEmpty(part);
|
|
2148
2363
|
}
|
|
@@ -2354,4 +2569,4 @@ function releases2(opts = {}) {
|
|
|
2354
2569
|
return new ReleaseManager2(opts);
|
|
2355
2570
|
}
|
|
2356
2571
|
|
|
2357
|
-
export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, nameOrderOf, DancerResolver2, canonicalOf2, CompResolver2, NO_CALENDAR2, FeisFinder2, StaffFinder2, SchoolResolver2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, resolveTag2, Release2, openRelease2, openCached2, ReleaseManager2, releases2 };
|
|
2572
|
+
export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, NO_CALENDAR2, FeisFinder2, StaffFinder2, SchoolResolver2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, 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-6e2gb6sk.js";
|
|
8
8
|
import"./chunks/index-hne1wadw.js";
|
|
9
9
|
import"./chunks/index-hghdy980.js";
|
|
10
10
|
|
|
@@ -23,6 +23,9 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
|
|
|
23
23
|
index [--release <spec>] [<name>...] [--force]
|
|
24
24
|
build the per-tag indexes (default: every one)
|
|
25
25
|
verify [--release <spec>] re-hash every cached file against its receipt
|
|
26
|
+
capabilities [--release <spec>] what the release carries of what the client reads, from
|
|
27
|
+
its own schema: each feature full or missing (its calls
|
|
28
|
+
refuse, naming the first cut that publishes it)
|
|
26
29
|
prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)
|
|
27
30
|
and the pinned one
|
|
28
31
|
rm <tag> [--dry-run] remove one cached release (not the pinned one)
|
|
@@ -37,8 +40,9 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
|
|
|
37
40
|
dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>
|
|
38
41
|
[--order surname-first|given-first] [--school <print|id>]
|
|
39
42
|
[--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]
|
|
40
|
-
[--rows]
|
|
41
|
-
|
|
43
|
+
[--population girls|boys] [--rows]
|
|
44
|
+
the dancers a name reaches, each with its evidence
|
|
45
|
+
(name level, age, school, population) and the arm; the results of
|
|
42
46
|
the one dancer where one answers (or --rows for each)
|
|
43
47
|
results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]
|
|
44
48
|
[--school <print|id>] [--year <y>] [--limit <n>]
|
|
@@ -61,12 +65,13 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
|
|
|
61
65
|
else the newest, saying which and what it is read from
|
|
62
66
|
resolve comp [<feis> <code>] [--year <y>] [--title <t>]
|
|
63
67
|
resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]
|
|
64
|
-
[--order <o>] [--as-of <date>]
|
|
68
|
+
[--order <o>] [--as-of <date>] [--population girls|boys]
|
|
65
69
|
resolve registrations
|
|
66
70
|
JSONL out, one answer per line; with no positional the
|
|
67
71
|
questions are JSONL on stdin, one per line (a comp:
|
|
68
|
-
{feis, code, year?, title?}, a dancer: {name, school?, age
|
|
69
|
-
a registration: {name, school?, codes[], feis,
|
|
72
|
+
{feis, code, year?, title?}, a dancer: {name, school?, age?,
|
|
73
|
+
population?}, a registration: {name, school?, codes[], feis,
|
|
74
|
+
year?, population?})
|
|
70
75
|
|
|
71
76
|
<spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)`;
|
|
72
77
|
var VALUE = new Set([
|
|
@@ -90,6 +95,7 @@ var VALUE = new Set([
|
|
|
90
95
|
"--band",
|
|
91
96
|
"--comp",
|
|
92
97
|
"--as-of",
|
|
98
|
+
"--population",
|
|
93
99
|
"--dancer",
|
|
94
100
|
"--feis",
|
|
95
101
|
"--limit",
|
|
@@ -137,8 +143,8 @@ function printCached(c, say) {
|
|
|
137
143
|
}
|
|
138
144
|
var span = (lo, hi) => lo === null ? "-" : lo === hi ? `${lo}` : `${lo}-${hi ?? "?"}`;
|
|
139
145
|
function printCandidate(c, say, pad = " ") {
|
|
140
|
-
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}`);
|
|
141
|
-
say(`${pad} ${c.rows} rows ${span(c.year_lo, c.year_hi)} born ${span(c.birth.lo, c.birth.hi)} (${c.birth.source}) school ${c.school === null ? "-" : `${c.school.school_id ?? "(no id)"} "${c.school.print}" ${span(c.school.year_lo, c.school.year_hi)}`}`);
|
|
146
|
+
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}`);
|
|
147
|
+
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)}`}`);
|
|
142
148
|
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` : ""}`);
|
|
143
149
|
}
|
|
144
150
|
function printDancers(a, say) {
|
|
@@ -147,6 +153,8 @@ function printDancers(a, say) {
|
|
|
147
153
|
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})` : ""}`);
|
|
148
154
|
if (a.school !== undefined)
|
|
149
155
|
say(` school asked: ${a.school.arm} ${a.school.ids.join(", ") || "-"}`);
|
|
156
|
+
if (a.population !== undefined)
|
|
157
|
+
say(` population asked: ${a.population.read ?? "(unread)"}${a.population.note !== undefined ? ` (${a.population.note})` : ""}`);
|
|
150
158
|
for (const c of a.candidates)
|
|
151
159
|
printCandidate(c, say);
|
|
152
160
|
if (a.preferred !== undefined)
|
|
@@ -277,7 +285,8 @@ async function resolveVerb(rel, what, args, p, say, stdin) {
|
|
|
277
285
|
...nameOrderOf(p.flags.get("--order")) !== undefined ? { order: nameOrderOf(p.flags.get("--order")) } : {},
|
|
278
286
|
...p.flags.has("--school") ? { school: p.flags.get("--school") } : {},
|
|
279
287
|
...age !== undefined ? { age } : {},
|
|
280
|
-
...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {}
|
|
288
|
+
...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {},
|
|
289
|
+
...p.flags.has("--population") ? { population: p.flags.get("--population") } : {}
|
|
281
290
|
}
|
|
282
291
|
}
|
|
283
292
|
];
|
|
@@ -404,6 +413,20 @@ async function main(argv, io = {}) {
|
|
|
404
413
|
});
|
|
405
414
|
return 0;
|
|
406
415
|
}
|
|
416
|
+
case "capabilities": {
|
|
417
|
+
const rel = await openRelease2(spec, { ...opts, assets: [] });
|
|
418
|
+
try {
|
|
419
|
+
const c = await rel.capabilities();
|
|
420
|
+
out(c, () => {
|
|
421
|
+
say(`${c.tag}: what it carries of what the client reads`);
|
|
422
|
+
for (const f of c.features)
|
|
423
|
+
say(` ${f.state.padEnd(8)} ${f.feature}${f.state === "full" ? "" : ` lacks ${f.missing.join(", ")}: a release of cut ${f.since} or later answers ${f.calls}`}`);
|
|
424
|
+
});
|
|
425
|
+
return 0;
|
|
426
|
+
} finally {
|
|
427
|
+
rel.close();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
407
430
|
case "verify": {
|
|
408
431
|
const tag = (await m.fetch(spec, { assets: [] })).tag;
|
|
409
432
|
const bad = await m.verify(tag);
|
|
@@ -522,6 +545,7 @@ async function main(argv, io = {}) {
|
|
|
522
545
|
...p.flags.has("--school") ? { school: p.flags.get("--school") } : {},
|
|
523
546
|
...age !== undefined ? { age } : {},
|
|
524
547
|
...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {},
|
|
548
|
+
...p.flags.has("--population") ? { population: p.flags.get("--population") } : {},
|
|
525
549
|
...p.flags.has("--limit") ? { limit: Number(p.flags.get("--limit")) } : {}
|
|
526
550
|
};
|
|
527
551
|
const surname = p.flags.get("--surname");
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ResultFinder2,
|
|
11
11
|
MajorsFinder2,
|
|
12
12
|
DancerResolver2,
|
|
13
|
+
readPopulation2,
|
|
13
14
|
canonicalOf2,
|
|
14
15
|
CompResolver2,
|
|
15
16
|
NO_CALENDAR2,
|
|
@@ -30,13 +31,17 @@ import {
|
|
|
30
31
|
httpsRemote2,
|
|
31
32
|
fallbackRemote2,
|
|
32
33
|
setRemote2,
|
|
34
|
+
columnsOf2,
|
|
35
|
+
FEATURES2,
|
|
36
|
+
capabilityOf2,
|
|
37
|
+
needsRelease2,
|
|
33
38
|
resolveTag2,
|
|
34
39
|
Release2,
|
|
35
40
|
openRelease2,
|
|
36
41
|
openCached2,
|
|
37
42
|
ReleaseManager2,
|
|
38
43
|
releases2
|
|
39
|
-
} from "./chunks/index-
|
|
44
|
+
} from "./chunks/index-6e2gb6sk.js";
|
|
40
45
|
import {
|
|
41
46
|
cacheRoot2,
|
|
42
47
|
repoOf2
|
|
@@ -55,6 +60,7 @@ export {
|
|
|
55
60
|
CompResolver2 as CompResolver,
|
|
56
61
|
DATABASES2 as DATABASES,
|
|
57
62
|
DancerResolver2 as DancerResolver,
|
|
63
|
+
FEATURES2 as FEATURES,
|
|
58
64
|
FEIS_LOCAL_ID_FLOOR2 as FEIS_LOCAL_ID_FLOOR,
|
|
59
65
|
FeisFinder2 as FeisFinder,
|
|
60
66
|
MajorsFinder2 as MajorsFinder,
|
|
@@ -69,6 +75,8 @@ export {
|
|
|
69
75
|
buildIndex2 as buildIndex,
|
|
70
76
|
cacheRoot2 as cacheRoot,
|
|
71
77
|
canonicalOf2 as canonicalOf,
|
|
78
|
+
capabilityOf2 as capabilityOf,
|
|
79
|
+
columnsOf2 as columnsOf,
|
|
72
80
|
compareTagsNewestFirst2 as compareTagsNewestFirst,
|
|
73
81
|
cutOf2 as cutOf,
|
|
74
82
|
dancerResolverOf2 as dancerResolverOf,
|
|
@@ -81,6 +89,7 @@ export {
|
|
|
81
89
|
isCurrent2 as isCurrent,
|
|
82
90
|
listIndexes2 as listIndexes,
|
|
83
91
|
nameLevel2 as nameLevel,
|
|
92
|
+
needsRelease2 as needsRelease,
|
|
84
93
|
openCached2 as openCached,
|
|
85
94
|
openRelease2 as openRelease,
|
|
86
95
|
parseArchiveTag2 as parseArchiveTag,
|
|
@@ -88,6 +97,7 @@ export {
|
|
|
88
97
|
pickTag2 as pickTag,
|
|
89
98
|
planAsset2 as planAsset,
|
|
90
99
|
readPin2 as readPin,
|
|
100
|
+
readPopulation2 as readPopulation,
|
|
91
101
|
releases2 as releases,
|
|
92
102
|
renderArchiveTag2 as renderArchiveTag,
|
|
93
103
|
repoOf2 as repoOf,
|
package/dist/types/assets.d.ts
CHANGED
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import type { OpenOptions } from "./release.js";
|
|
3
|
-
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 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 [--rows]
|
|
3
|
+
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 teacher [--release <spec>] <name|TD number>\n the staff lists' people: where they taught, the register,\n where they sat\n adjudicator [--release <spec>] <name|TD number>\n the register's and the panels' people: the same answer\n comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]\n a feis's own comp code to the comp it names, through that\n feis's syllabus (or the prior year's)\n syllabus [--release <spec>] <feis> [year]\n one feis-year's comps, in printed order\n syllabus [--release <spec>] --for <series> [year]\n a series' syllabus for a year: that year's where held,\n else the newest, saying which and what it is read from\n resolve comp [<feis> <code>] [--year <y>] [--title <t>]\n resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]\n [--order <o>] [--as-of <date>] [--population girls|boys]\n resolve registrations\n JSONL out, one answer per line; with no positional the\n questions are JSONL on stdin, one per line (a comp:\n {feis, code, year?, title?}, a dancer: {name, school?, age?,\n population?}, a registration: {name, school?, codes[], feis,\n year?, population?})\n\n <spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)";
|
|
4
4
|
export interface Parsed {
|
|
5
5
|
positional: string[];
|
|
6
6
|
flags: Map<string, string>;
|
package/dist/types/dancers.d.ts
CHANGED
|
@@ -20,8 +20,10 @@ export interface DancerQuery {
|
|
|
20
20
|
school?: string;
|
|
21
21
|
age?: AgeAsked;
|
|
22
22
|
asOf?: string;
|
|
23
|
+
population?: string;
|
|
23
24
|
}
|
|
24
25
|
export type AgeEvidence = "fits" | "overlaps" | "contradicts" | "unknown" | "unasked";
|
|
26
|
+
export type PopulationEvidence = "same" | "contradicts" | "both" | "unknown" | "unasked";
|
|
25
27
|
export type SchoolEvidence = "same" | "moved" | "none" | "unresolved" | "unasked";
|
|
26
28
|
export interface DancerSchool {
|
|
27
29
|
school_id: string | null;
|
|
@@ -40,6 +42,7 @@ export interface DancerCandidate {
|
|
|
40
42
|
nameLevel: NameLevel;
|
|
41
43
|
age: AgeEvidence;
|
|
42
44
|
school: SchoolEvidence;
|
|
45
|
+
population: PopulationEvidence;
|
|
43
46
|
};
|
|
44
47
|
prints: {
|
|
45
48
|
print: string;
|
|
@@ -55,11 +58,15 @@ export interface DancerCandidate {
|
|
|
55
58
|
source: string;
|
|
56
59
|
conflict: boolean;
|
|
57
60
|
};
|
|
61
|
+
population: {
|
|
62
|
+
girls: number;
|
|
63
|
+
boys: number;
|
|
64
|
+
};
|
|
58
65
|
school: DancerSchool | null;
|
|
59
66
|
}
|
|
60
67
|
export interface ExcludedCandidate {
|
|
61
68
|
candidate: DancerCandidate;
|
|
62
|
-
why: "age";
|
|
69
|
+
why: "age" | "population";
|
|
63
70
|
}
|
|
64
71
|
export interface DancerAnswer {
|
|
65
72
|
tag: string;
|
|
@@ -83,6 +90,11 @@ export interface DancerAnswer {
|
|
|
83
90
|
} | null;
|
|
84
91
|
note?: string;
|
|
85
92
|
};
|
|
93
|
+
population?: {
|
|
94
|
+
asked: string;
|
|
95
|
+
read: "girls" | "boys" | null;
|
|
96
|
+
note?: string;
|
|
97
|
+
};
|
|
86
98
|
school?: {
|
|
87
99
|
arm: string;
|
|
88
100
|
ids: string[];
|
|
@@ -113,6 +125,10 @@ export interface DancerRecord {
|
|
|
113
125
|
source: string;
|
|
114
126
|
conflict: boolean;
|
|
115
127
|
};
|
|
128
|
+
population: {
|
|
129
|
+
girls: number;
|
|
130
|
+
boys: number;
|
|
131
|
+
};
|
|
116
132
|
prints: {
|
|
117
133
|
print: string;
|
|
118
134
|
rows: number;
|
|
@@ -136,3 +152,4 @@ export declare class DancerResolver {
|
|
|
136
152
|
private schoolEvidence;
|
|
137
153
|
private readAge;
|
|
138
154
|
}
|
|
155
|
+
export declare function readPopulation(asked: string): NonNullable<DancerAnswer["population"]>;
|
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 DancerCandidate, type DancerQuery, type DancerRecord, DancerResolver, type DancerSchool, type DancerSearchQuery, type ExcludedCandidate, FEIS_LOCAL_ID_FLOOR, type SchoolEvidence, } from "./dancers.js";
|
|
3
|
+
export { type AgeAsked, type AgeEvidence, type DancerAnswer, type DancerCandidate, type DancerQuery, type DancerRecord, DancerResolver, type DancerSchool, type DancerSearchQuery, type ExcludedCandidate, FEIS_LOCAL_ID_FLOOR, type PopulationEvidence, 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 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";
|
|
@@ -14,4 +14,5 @@ export { type OpenOptions, Release, openCached, openRelease, resolveTag, } from
|
|
|
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
16
|
export { type SchoolAnswer, type SchoolCandidate, type SchoolQuery, SchoolResolver, type SchoolRung, } from "./schools.js";
|
|
17
|
+
export { type Capability, FEATURES, type Feature, type SchemaProbe, capabilityOf, columnsOf, needsRelease, } from "./schema.js";
|
|
17
18
|
export { ARCHIVE_TAG, type ArchiveTag, type ReleaseSpec, compareTagsNewestFirst, cutOf, parseArchiveTag, parseReleaseSpec, pickTag, renderArchiveTag, } from "./tag.js";
|
|
@@ -10,6 +10,7 @@ export interface RegistrationRow {
|
|
|
10
10
|
year?: number;
|
|
11
11
|
titles?: Readonly<Record<string, string>>;
|
|
12
12
|
asOf?: string;
|
|
13
|
+
population?: string;
|
|
13
14
|
[k: string]: unknown;
|
|
14
15
|
}
|
|
15
16
|
export type DancerQueryLike = DancerQuery;
|
|
@@ -36,6 +37,13 @@ export interface ResolvedRegistration {
|
|
|
36
37
|
from: "comp" | "title";
|
|
37
38
|
} | null;
|
|
38
39
|
bands?: string[];
|
|
40
|
+
population: {
|
|
41
|
+
asked: string;
|
|
42
|
+
from: "row" | "comp";
|
|
43
|
+
codes?: string[];
|
|
44
|
+
} | {
|
|
45
|
+
why: string;
|
|
46
|
+
} | null;
|
|
39
47
|
dancer: DancerAnswer | DancerAnswerLike | DancerUnavailable;
|
|
40
48
|
asked?: DancerQueryLike;
|
|
41
49
|
}
|
package/dist/types/release.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { StaffFinder } from "./people.js";
|
|
|
9
9
|
import { type DancerResolverLike, RegistrationResolver } from "./registrations.js";
|
|
10
10
|
import { type Remote } from "./remote.js";
|
|
11
11
|
import { MajorsFinder, ResultFinder } from "./results.js";
|
|
12
|
+
import { type Capability } from "./schema.js";
|
|
12
13
|
import { type SchoolReaderChoice, SchoolResolver } from "./schools.js";
|
|
13
14
|
import { type ReleaseSpec } from "./tag.js";
|
|
14
15
|
export interface OpenOptions extends ClientOptions {
|
|
@@ -49,6 +50,15 @@ export declare class Release {
|
|
|
49
50
|
has(asset: string): boolean;
|
|
50
51
|
path(asset: string): string;
|
|
51
52
|
fetch(...assets: string[]): Promise<LandedFile[]>;
|
|
53
|
+
publishes(asset: string): Promise<boolean>;
|
|
54
|
+
private readonly probe;
|
|
55
|
+
private readonly checked;
|
|
56
|
+
capability(feature: string): Promise<Capability>;
|
|
57
|
+
capabilities(): Promise<{
|
|
58
|
+
tag: string;
|
|
59
|
+
features: Capability[];
|
|
60
|
+
}>;
|
|
61
|
+
require(feature: string): Promise<void>;
|
|
52
62
|
private listed;
|
|
53
63
|
private fetchOne;
|
|
54
64
|
private record;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
export declare function columnsOf(db: Database, table: string): Set<string>;
|
|
3
|
+
export interface Feature {
|
|
4
|
+
feature: string;
|
|
5
|
+
needs: readonly string[];
|
|
6
|
+
since: number;
|
|
7
|
+
calls: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const FEATURES: readonly Feature[];
|
|
10
|
+
export interface Capability extends Feature {
|
|
11
|
+
state: "full" | "missing";
|
|
12
|
+
missing: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface SchemaProbe {
|
|
15
|
+
db(name: string): Promise<Database>;
|
|
16
|
+
publishes(asset: string): Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
export declare function hasNeed(probe: SchemaProbe, n: string): Promise<boolean>;
|
|
19
|
+
export declare function capabilityOf(probe: SchemaProbe, f: Feature): Promise<Capability>;
|
|
20
|
+
export declare function needsRelease(tag: string, c: Capability): Error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finbheara/archive-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|