@finbheara/archive-client 0.11.0 → 0.12.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 +9 -8
- package/dist/chunks/{index-7hw19tw1.js → index-xtv455ft.js} +324 -300
- package/dist/cli.js +1 -1
- package/dist/index.js +5 -5
- package/dist/types/stream.d.ts +1 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,7 +87,6 @@ c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...
|
|
|
87
87
|
| `feises`, `comps` | `feises.find`, `feises.series`, `comps.*`, `registrations.resolve` | 265 |
|
|
88
88
|
| `comps.dance` | the `dance` a comp of `comps.*` carries: null, never refused, without it | 270 |
|
|
89
89
|
| `results.stream`, `majors.stream` | `results.stream`, `results.entries` | 255 |
|
|
90
|
-
| `results.printed_age` | the `printed_age` a row of `results.stream` and `results.entries` carries: null, never refused, without it | 271 |
|
|
91
90
|
| `feises.dates` | `feises.dates` | 245 |
|
|
92
91
|
| `dancers.records` | `dancers.records` | 215 |
|
|
93
92
|
| `dancers.years` | `dancers.years` | 245 |
|
|
@@ -282,7 +281,13 @@ words):
|
|
|
282
281
|
followed through the redirects, a print read by `rel.schools`), and none of them is the asked
|
|
283
282
|
school or a school its line had joined by that year (lineage edges dated in or before the asked
|
|
284
283
|
year). A dancer with no school row, or a print no school reads, is unknown and never refuted. A
|
|
285
|
-
dancer that ever danced for the asked school's line is never refuted by the school.
|
|
284
|
+
dancer that ever danced for the asked school's line is never refuted by the school. For an
|
|
285
|
+
adult-category question (the asked band is the adult band, read by `@finbheara/domain`:
|
|
286
|
+
`Adult`, `Advanced Adult`, `Adult Beginner`, `Open Adult`, `Adult Masters`), a row the
|
|
287
|
+
candidate danced under 18 (at every birth year of its window, in the row's latest year)
|
|
288
|
+
refutes nothing where the candidate is 18 or over in the asked year, however long ago the row
|
|
289
|
+
is: many adult dancers danced as children at another school. An and-over band (`O18`,
|
|
290
|
+
`18 & Over`, `18+`), `Senior`, a year of birth and a youth question are unchanged.
|
|
286
291
|
- `age`: the birth window misses the asked band even with one band's dance-up (`DANCE_UP_YEARS`
|
|
287
292
|
of `@finbheara/domain/age`: a dancer may enter the band one year older than the dancer's own). A
|
|
288
293
|
candidate the age set aside that the dance-up holds stays open (`r.initials.heldByDanceUp`).
|
|
@@ -389,15 +394,11 @@ competition beside its own fields: `competition`, `event`, `year`, `tier`, `comp
|
|
|
389
394
|
`date_printed`, `source`, `round`, and at a feis its `feis_id`, `feis_date_lo` and
|
|
390
395
|
`feis_date_hi`. Its own: `address`, `section`, `card`, `place`, `place_printed`, `tie`,
|
|
391
396
|
`relative_place`, `atts`, `withdrawn`, `qualifier`, `name_printed`, `name`, `school_printed`,
|
|
392
|
-
`school_loc`, `school_id`, `dancer_id`, `dancer_scope`, `yob`, `yob_printed`, `
|
|
393
|
-
`
|
|
397
|
+
`school_loc`, `school_id`, `dancer_id`, `dancer_scope`, `yob`, `yob_printed`, `points_printed`,
|
|
398
|
+
`platform_dancer_id`, `primary_source`, and its key in the release, `file` and `line` (one
|
|
394
399
|
`(file, line)` is one row, where an `address` may carry one row per section). Rows come in the
|
|
395
400
|
release's own order, a competition's rows together.
|
|
396
401
|
|
|
397
|
-
`printed_age` is the dancer's own age as the row printed it (`9`), verbatim, null where the row
|
|
398
|
-
printed none. It is a print, never a reading of a birth year. A release older than the column
|
|
399
|
-
answers null for every row, and `rel.capability("results.printed_age")` says which.
|
|
400
|
-
|
|
401
402
|
The scopes: `majors` reads the majors (tiers T1 to T3), `transcribed` the transcribed rows of
|
|
402
403
|
every tier, `platform` every row a results platform published, `all` every row.
|
|
403
404
|
|
|
@@ -71,7 +71,11 @@ import { Age, DANCE_UP_YEARS, birthYearsMeet } 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 {
|
|
74
|
+
import {
|
|
75
|
+
compTitle,
|
|
76
|
+
populationIn,
|
|
77
|
+
populationRelation
|
|
78
|
+
} from "@finbheara/domain/title";
|
|
75
79
|
import {
|
|
76
80
|
foldNameKey,
|
|
77
81
|
surnameBlock
|
|
@@ -339,286 +343,23 @@ function notOnTheFloor(placePrinted, atts, withdrawn) {
|
|
|
339
343
|
return withdrawn !== null && DID_NOT_TABULATE.has(withdrawn);
|
|
340
344
|
}
|
|
341
345
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
feature: "dancers",
|
|
360
|
-
needs: ["results:results.name_order_resolved"],
|
|
361
|
-
since: 260,
|
|
362
|
-
calls: "dancers.resolve, dancers.search, dancers.get (the dancers index)"
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
feature: "schools",
|
|
366
|
-
needs: ["school-registry.jsonl", "school-redirects.jsonl"],
|
|
367
|
-
since: 260,
|
|
368
|
-
calls: "schools.resolve, a dancer question with a school (the schools index)"
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
feature: "feises",
|
|
372
|
-
needs: [
|
|
373
|
-
"results:feises.date_lo",
|
|
374
|
-
"results:feises.date_hi",
|
|
375
|
-
"results:feises.feis_instance_id",
|
|
376
|
-
"results:feises.feis_brand_id",
|
|
377
|
-
"results:competitions.feis_region",
|
|
378
|
-
"feis-redirects.jsonl"
|
|
379
|
-
],
|
|
380
|
-
since: 265,
|
|
381
|
-
calls: "feises.find, feises.series, and comps and registrations through them (the feises index)"
|
|
382
|
-
},
|
|
383
|
-
{
|
|
384
|
-
feature: "comps",
|
|
385
|
-
needs: [
|
|
386
|
-
"results:competitions.feis_level",
|
|
387
|
-
"results:competitions.round",
|
|
388
|
-
"results:feises.feis_brand_id"
|
|
389
|
-
],
|
|
390
|
-
since: 265,
|
|
391
|
-
calls: "comps.resolve, comps.syllabus, comps.syllabusFor, registrations.resolve (the comps index)"
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
feature: "comps.dance",
|
|
395
|
-
needs: ["results:competitions.dance"],
|
|
396
|
-
since: 270,
|
|
397
|
-
calls: "the `dance` a comp of comps.resolve, comps.syllabus and comps.syllabusFor carries: null, never refused, on a release without it"
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
feature: "schools.lineage",
|
|
401
|
-
needs: ["school-lineage.jsonl"],
|
|
402
|
-
since: 184,
|
|
403
|
-
calls: "the `lineage` school evidence of dancers.resolve and dancers.search: no school is joined by lineage, never refused, on a release without it"
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
feature: "results.stream",
|
|
407
|
-
needs: [
|
|
408
|
-
"results:competitions.round",
|
|
409
|
-
"results:competitions.feis_level",
|
|
410
|
-
"results:competitions.level_printed",
|
|
411
|
-
"results:competitions.age_printed",
|
|
412
|
-
"results:results.yob_printed",
|
|
413
|
-
"results:results.primary_source",
|
|
414
|
-
"results:results.platform_dancer_id",
|
|
415
|
-
"results:results.relative_place",
|
|
416
|
-
"results:feises.date_lo",
|
|
417
|
-
"results:feises.date_hi"
|
|
418
|
-
],
|
|
419
|
-
since: 255,
|
|
420
|
-
calls: "results.stream, results.entries (scopes transcribed, platform, all)"
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
feature: "majors.stream",
|
|
424
|
-
needs: [
|
|
425
|
-
"majors:competitions.round",
|
|
426
|
-
"majors:competitions.feis_level",
|
|
427
|
-
"majors:results.yob_printed",
|
|
428
|
-
"majors:results.primary_source",
|
|
429
|
-
"majors:results.relative_place",
|
|
430
|
-
"majors:feises.date_lo"
|
|
431
|
-
],
|
|
432
|
-
since: 255,
|
|
433
|
-
calls: "results.stream, results.entries (scope majors)"
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
feature: "results.printed_age",
|
|
437
|
-
needs: ["results:results.printed_age", "majors:results.printed_age"],
|
|
438
|
-
since: 271,
|
|
439
|
-
calls: "the `printed_age` a row of results.stream and results.entries carries: null, never refused, on a release without it"
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
feature: "majors.tabs",
|
|
443
|
-
needs: ["majors:tabs.address", "majors:tabs.true_round"],
|
|
444
|
-
since: 215,
|
|
445
|
-
calls: "majors.tabulated"
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
feature: "feises.dates",
|
|
449
|
-
needs: ["results:feises.feis_id", "results:feises.date_lo", "results:feises.date_hi"],
|
|
450
|
-
since: 245,
|
|
451
|
-
calls: "feises.dates"
|
|
452
|
-
},
|
|
453
|
-
{
|
|
454
|
-
feature: "dancers.records",
|
|
455
|
-
needs: [
|
|
456
|
-
"results:dancers.display",
|
|
457
|
-
"results:dancers.appearances",
|
|
458
|
-
"results:dancers.birth_lo",
|
|
459
|
-
"results:dancers.birth_hi",
|
|
460
|
-
"results:dancers.birth_source",
|
|
461
|
-
"results:dancers.birth_conflict",
|
|
462
|
-
"results:dancers.genders"
|
|
463
|
-
],
|
|
464
|
-
since: 215,
|
|
465
|
-
calls: "dancers.records"
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
feature: "dancers.years",
|
|
469
|
-
needs: [
|
|
470
|
-
"results:dancer_years.region",
|
|
471
|
-
"results:dancer_years.region_source",
|
|
472
|
-
"results:dancer_years.nationals",
|
|
473
|
-
"results:dancer_years.nationals_source",
|
|
474
|
-
"results:dancer_years.geohash"
|
|
475
|
-
],
|
|
476
|
-
since: 245,
|
|
477
|
-
calls: "dancers.years"
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
feature: "teams",
|
|
481
|
-
needs: [
|
|
482
|
-
...[
|
|
483
|
-
"slug",
|
|
484
|
-
"address",
|
|
485
|
-
"event",
|
|
486
|
-
"year",
|
|
487
|
-
"card",
|
|
488
|
-
"comp_no",
|
|
489
|
-
"title",
|
|
490
|
-
"category",
|
|
491
|
-
"hands",
|
|
492
|
-
"letter",
|
|
493
|
-
"label",
|
|
494
|
-
"name_printed",
|
|
495
|
-
"school_printed",
|
|
496
|
-
"school_loc",
|
|
497
|
-
"school_id",
|
|
498
|
-
"place",
|
|
499
|
-
"place_printed",
|
|
500
|
-
"tie",
|
|
501
|
-
"competitors",
|
|
502
|
-
"atts"
|
|
503
|
-
].map((c) => `table:team_results.${c}`),
|
|
504
|
-
...[
|
|
505
|
-
"team",
|
|
506
|
-
"kind",
|
|
507
|
-
"event_year",
|
|
508
|
-
"year",
|
|
509
|
-
"comp_no",
|
|
510
|
-
"title",
|
|
511
|
-
"team_index",
|
|
512
|
-
"seq",
|
|
513
|
-
"school_id",
|
|
514
|
-
"school_print_materialized"
|
|
515
|
-
].map((c) => `table:roster_teams.${c}`),
|
|
516
|
-
...["team", "seq", "name_printed", "name", "yob", "dancer_id"].map((c) => `table:roster_members.${c}`),
|
|
517
|
-
...[
|
|
518
|
-
"event_year",
|
|
519
|
-
"comp_no",
|
|
520
|
-
"team",
|
|
521
|
-
"result_address",
|
|
522
|
-
"school_id",
|
|
523
|
-
"join_kind",
|
|
524
|
-
"via_split",
|
|
525
|
-
"roster_teams",
|
|
526
|
-
"result_teams",
|
|
527
|
-
"roster_index",
|
|
528
|
-
"result_letter"
|
|
529
|
-
].map((c) => `table:team_result_rosters.${c}`),
|
|
530
|
-
"table:event_years.event_year",
|
|
531
|
-
"table:event_years.event",
|
|
532
|
-
"table:event_years.year"
|
|
533
|
-
],
|
|
534
|
-
since: 260,
|
|
535
|
-
calls: "teams.results, teams.rosters, teams.resultRosters (the teams index)"
|
|
536
|
-
},
|
|
537
|
-
{
|
|
538
|
-
feature: "teams.fields",
|
|
539
|
-
needs: [
|
|
540
|
-
"table:team_results.age",
|
|
541
|
-
"table:team_results.gender",
|
|
542
|
-
"table:team_results.printed_status",
|
|
543
|
-
"table:roster_teams.dance",
|
|
544
|
-
"table:roster_teams.school_print_as_of",
|
|
545
|
-
"table:roster_members.gender"
|
|
546
|
-
],
|
|
547
|
-
since: 266,
|
|
548
|
-
calls: "the fields teams.results (age, gender, printed_status) and teams.rosters (dance, school_printed_as_of, a member's gender) carry where present: absent, never refused, on a release without them"
|
|
549
|
-
},
|
|
550
|
-
{
|
|
551
|
-
feature: "events",
|
|
552
|
-
needs: [
|
|
553
|
-
"event_year",
|
|
554
|
-
"event",
|
|
555
|
-
"year",
|
|
556
|
-
"date_lo",
|
|
557
|
-
"date_hi",
|
|
558
|
-
"resolution",
|
|
559
|
-
"source",
|
|
560
|
-
"date_printed",
|
|
561
|
-
"place",
|
|
562
|
-
"place_printed"
|
|
563
|
-
].map((c) => `table:event_years.${c}`),
|
|
564
|
-
since: 260,
|
|
565
|
-
calls: "events.years (the events index)"
|
|
566
|
-
},
|
|
567
|
-
{
|
|
568
|
-
feature: "staff",
|
|
569
|
-
needs: [
|
|
570
|
-
"results:adjudicators",
|
|
571
|
-
"results:feis_panels",
|
|
572
|
-
"school-staff.jsonl",
|
|
573
|
-
"school-teachers.jsonl",
|
|
574
|
-
"school-registry.jsonl"
|
|
575
|
-
],
|
|
576
|
-
since: 260,
|
|
577
|
-
calls: "staff.teacher, staff.adjudicator (the staff index)"
|
|
578
|
-
}
|
|
579
|
-
];
|
|
580
|
-
async function hasNeed(probe, n) {
|
|
581
|
-
const t = /^table:([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
|
|
582
|
-
if (t !== null) {
|
|
583
|
-
if (probe.tableColumns === undefined)
|
|
584
|
-
return false;
|
|
585
|
-
const cols = await probe.tableColumns(t[1]);
|
|
586
|
-
return t[2] === undefined ? cols.size > 0 : cols.has(t[2]);
|
|
587
|
-
}
|
|
588
|
-
const m = /^(results|majors):([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
|
|
589
|
-
if (m === null)
|
|
590
|
-
return probe.publishes(n);
|
|
591
|
-
const cols = columnsOf2(await probe.db(m[1]), m[2]);
|
|
592
|
-
return m[3] === undefined ? cols.size > 0 : cols.has(m[3]);
|
|
593
|
-
}
|
|
594
|
-
async function capabilityOf2(probe, f) {
|
|
595
|
-
const missing = [];
|
|
596
|
-
for (const n of f.needs)
|
|
597
|
-
if (!await hasNeed(probe, n))
|
|
598
|
-
missing.push(n);
|
|
599
|
-
return { ...f, state: missing.length === 0 ? "full" : "missing", missing };
|
|
600
|
-
}
|
|
601
|
-
function needsRelease2(tag, c) {
|
|
602
|
-
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`);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
var ENTRY_RANK = new Map([
|
|
606
|
-
["final", 0],
|
|
607
|
-
["", 1],
|
|
608
|
-
["recall", 2],
|
|
609
|
-
["standings", 3],
|
|
610
|
-
["unplaced", 4],
|
|
611
|
-
["qual", 5],
|
|
612
|
-
["qual-a", 5],
|
|
613
|
-
["qual-b", 5]
|
|
614
|
-
]);
|
|
615
|
-
function entryRank2(section) {
|
|
616
|
-
const known = ENTRY_RANK.get(section);
|
|
617
|
-
if (known !== undefined)
|
|
618
|
-
return known;
|
|
619
|
-
if (/^solo(?:-r\d+)?$/.test(section))
|
|
620
|
-
return 6;
|
|
621
|
-
return 9;
|
|
346
|
+
var ENTRY_RANK = new Map([
|
|
347
|
+
["final", 0],
|
|
348
|
+
["", 1],
|
|
349
|
+
["recall", 2],
|
|
350
|
+
["standings", 3],
|
|
351
|
+
["unplaced", 4],
|
|
352
|
+
["qual", 5],
|
|
353
|
+
["qual-a", 5],
|
|
354
|
+
["qual-b", 5]
|
|
355
|
+
]);
|
|
356
|
+
function entryRank2(section) {
|
|
357
|
+
const known = ENTRY_RANK.get(section);
|
|
358
|
+
if (known !== undefined)
|
|
359
|
+
return known;
|
|
360
|
+
if (/^solo(?:-r\d+)?$/.test(section))
|
|
361
|
+
return 6;
|
|
362
|
+
return 9;
|
|
622
363
|
}
|
|
623
364
|
var COLUMNS = `r.address AS address, r.competition AS competition, c.event AS event,
|
|
624
365
|
c.year AS year, c.tier AS tier, c.comp_no AS comp_no, c.title AS title, c.category AS category,
|
|
@@ -632,13 +373,9 @@ var COLUMNS = `r.address AS address, r.competition AS competition, c.event AS ev
|
|
|
632
373
|
r.yob_printed AS yob_printed, r.points_printed AS points_printed,
|
|
633
374
|
r.platform_dancer_id AS platform_dancer_id, r.primary_source AS primary_source,
|
|
634
375
|
r.file AS file, r.line AS line`;
|
|
635
|
-
var printedAgeColumn = (carried) => carried ? "r.printed_age AS printed_age" : "NULL AS printed_age";
|
|
636
|
-
var streamColumns = (db) => ({
|
|
637
|
-
printedAge: columnsOf2(db, "results").has("printed_age")
|
|
638
|
-
});
|
|
639
376
|
var TRANSCRIBED = "('transcript', 'manifested')";
|
|
640
377
|
var scopeDb = (s) => s === "majors" ? "majors" : "results";
|
|
641
|
-
function streamSql(q
|
|
378
|
+
function streamSql(q) {
|
|
642
379
|
const where = [];
|
|
643
380
|
const args = [];
|
|
644
381
|
const list = (col, v, nocase = false) => {
|
|
@@ -679,7 +416,7 @@ function streamSql(q, carried = { printedAge: false }) {
|
|
|
679
416
|
if (q.primary === true)
|
|
680
417
|
where.push("(r.primary_source IS NULL OR r.primary_source = '')");
|
|
681
418
|
return {
|
|
682
|
-
sql: `SELECT ${COLUMNS}
|
|
419
|
+
sql: `SELECT ${COLUMNS} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line${where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`} ORDER BY r.file, r.line`,
|
|
683
420
|
args
|
|
684
421
|
};
|
|
685
422
|
}
|
|
@@ -702,13 +439,12 @@ function rowOf(r, dates) {
|
|
|
702
439
|
withdrawn: text(withdrawn),
|
|
703
440
|
qualifier: text(rest.qualifier),
|
|
704
441
|
primary_source: text(rest.primary_source),
|
|
705
|
-
printed_age: text(rest.printed_age),
|
|
706
442
|
dancer_scope: r.dancer_id === null ? null : r.dancer_id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable"
|
|
707
443
|
};
|
|
708
444
|
}
|
|
709
445
|
async function* streamRows(db, q) {
|
|
710
446
|
const dates = feisDates(db);
|
|
711
|
-
const { sql, args } = streamSql(q
|
|
447
|
+
const { sql, args } = streamSql(q);
|
|
712
448
|
const stmt = db.prepare(sql);
|
|
713
449
|
try {
|
|
714
450
|
for (const r of stmt.iterate(...args))
|
|
@@ -719,7 +455,7 @@ async function* streamRows(db, q) {
|
|
|
719
455
|
}
|
|
720
456
|
async function* entryRows(db, q) {
|
|
721
457
|
const dates = feisDates(db);
|
|
722
|
-
const { sql, args } = streamSql(q
|
|
458
|
+
const { sql, args } = streamSql(q);
|
|
723
459
|
const stmt = db.prepare(sql);
|
|
724
460
|
let file = null;
|
|
725
461
|
let held = new Map;
|
|
@@ -1323,8 +1059,11 @@ class DancerResolver2 {
|
|
|
1323
1059
|
const held = [];
|
|
1324
1060
|
const refuted = [];
|
|
1325
1061
|
const bySchool = new Set;
|
|
1062
|
+
const adultBy = adultBornBy(year);
|
|
1063
|
+
const adultAsked = adultBy !== null && adultCategory(q.age, age?.age ?? null);
|
|
1326
1064
|
const schoolFact = async (c) => {
|
|
1327
|
-
const
|
|
1065
|
+
const returning = adultAsked && c.birth.hi !== null && c.birth.hi <= adultBy ? c.birth.lo : null;
|
|
1066
|
+
const fact = await this.schoolRefutes(schoolsOf(db, c.dancer_id), line, year, q.school, returning);
|
|
1328
1067
|
if (fact === null)
|
|
1329
1068
|
return false;
|
|
1330
1069
|
refuted.push({ dancer_id: c.dancer_id, why: "school", fact });
|
|
@@ -1463,20 +1202,30 @@ class DancerResolver2 {
|
|
|
1463
1202
|
}
|
|
1464
1203
|
return joined ? "lineage" : earlier ? "moved" : "none";
|
|
1465
1204
|
}
|
|
1466
|
-
async schoolRefutes(schools, line, year, asked) {
|
|
1205
|
+
async schoolRefutes(schools, line, year, asked, returning = null) {
|
|
1467
1206
|
const rows = schools.filter((s) => s.year_lo === null || s.year_lo <= year);
|
|
1468
1207
|
if (rows.length === 0)
|
|
1469
1208
|
return null;
|
|
1470
1209
|
const seen = new Set;
|
|
1210
|
+
const kept = [];
|
|
1211
|
+
let youth = 0;
|
|
1471
1212
|
for (const s of rows) {
|
|
1472
1213
|
const own = await this.schoolIdsOf(s);
|
|
1473
1214
|
if (own.length === 0 || own.some((id) => line.has(id)))
|
|
1474
1215
|
return null;
|
|
1216
|
+
if (returning !== null && youthRow(s, returning)) {
|
|
1217
|
+
youth++;
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
kept.push(s);
|
|
1475
1221
|
for (const id of own)
|
|
1476
1222
|
seen.add(id);
|
|
1477
1223
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1224
|
+
if (kept.length === 0)
|
|
1225
|
+
return null;
|
|
1226
|
+
const last = latestSchool(kept, year);
|
|
1227
|
+
const set = youth > 0 ? ` (${youth} youth row(s) set aside for an adult-category question)` : "";
|
|
1228
|
+
return `every school row to ${year} reads ${[...seen].sort().join(", ")} (the latest ${last?.school_id ?? last?.print} in ${last?.year_hi}), none of them ${asked ?? "the asked school"} or a school its line joined by ${year}${set}`;
|
|
1480
1229
|
}
|
|
1481
1230
|
async schoolIdsOf(s) {
|
|
1482
1231
|
const key = s.school_id ?? s.print;
|
|
@@ -1515,6 +1264,24 @@ class DancerResolver2 {
|
|
|
1515
1264
|
return done(Age.from(c.age, { year: c.year }));
|
|
1516
1265
|
}
|
|
1517
1266
|
}
|
|
1267
|
+
function adultBornBy(year) {
|
|
1268
|
+
const a = Age.from("adult", { year });
|
|
1269
|
+
return a.isAge() ? a.birthYears().to : null;
|
|
1270
|
+
}
|
|
1271
|
+
function adultCategory(asked, read) {
|
|
1272
|
+
if (read?.isAge() && read.isAdult())
|
|
1273
|
+
return true;
|
|
1274
|
+
if (asked === undefined || !("band" in asked))
|
|
1275
|
+
return false;
|
|
1276
|
+
const t = compTitle(asked.band).age;
|
|
1277
|
+
return t?.isAge() === true && t.isAdult();
|
|
1278
|
+
}
|
|
1279
|
+
function youthRow(s, bornLo) {
|
|
1280
|
+
if (bornLo === null || s.year_hi === null)
|
|
1281
|
+
return false;
|
|
1282
|
+
const by = adultBornBy(s.year_hi);
|
|
1283
|
+
return by !== null && bornLo > by;
|
|
1284
|
+
}
|
|
1518
1285
|
function birthOf(d) {
|
|
1519
1286
|
return {
|
|
1520
1287
|
lo: d.birth_lo,
|
|
@@ -1620,7 +1387,7 @@ function contradictionFact(x, age, slack) {
|
|
|
1620
1387
|
import { Age as Age2 } from "@finbheara/domain/age";
|
|
1621
1388
|
import {
|
|
1622
1389
|
codePopulation,
|
|
1623
|
-
compTitle,
|
|
1390
|
+
compTitle as compTitle2,
|
|
1624
1391
|
matchTitle,
|
|
1625
1392
|
populationIn as populationIn2,
|
|
1626
1393
|
secondIn,
|
|
@@ -1689,7 +1456,7 @@ function compOf(r, codes = []) {
|
|
|
1689
1456
|
const stated = r.gender_source === "level" ? "" : populationIn2(r.gender ?? "");
|
|
1690
1457
|
const pop = stated || populationIn2(r.title ?? "") || (r.platform === "ifeis" && codes.length > 0 ? codePopulation(r.code, codes) : "") || populationIn2(r.gender ?? "");
|
|
1691
1458
|
const second = [r.title, r.level_printed, r.dance_printed].some((t) => t != null && secondIn(t));
|
|
1692
|
-
const round = r.round ?? (r.title === null ? null :
|
|
1459
|
+
const round = r.round ?? (r.title === null ? null : compTitle2(r.title).round?.toString() ?? null);
|
|
1693
1460
|
return {
|
|
1694
1461
|
competition: r.competition,
|
|
1695
1462
|
feis_id: r.feis_id,
|
|
@@ -1950,7 +1717,7 @@ class CompResolver2 {
|
|
|
1950
1717
|
if (q.title === undefined || q.title.trim() === "")
|
|
1951
1718
|
return answer("unique", "prior-year", hits, { from, check: "unchecked" });
|
|
1952
1719
|
const m = matchTitle(q.title, [entryOf(hit)]);
|
|
1953
|
-
const r = titleRelation(m.title,
|
|
1720
|
+
const r = titleRelation(m.title, compTitle2(hit.title ?? "", {}));
|
|
1954
1721
|
const check = {
|
|
1955
1722
|
title: q.title,
|
|
1956
1723
|
agrees: m.isSingular(),
|
|
@@ -2732,7 +2499,7 @@ import {
|
|
|
2732
2499
|
import { join as join3 } from "path";
|
|
2733
2500
|
|
|
2734
2501
|
import { Age as Age3 } from "@finbheara/domain/age";
|
|
2735
|
-
import { compTitle as
|
|
2502
|
+
import { compTitle as compTitle3 } from "@finbheara/domain/title";
|
|
2736
2503
|
function youngest2(answers) {
|
|
2737
2504
|
const key = (a) => {
|
|
2738
2505
|
const iv = Age3.from(a.age, { year: null }).interval();
|
|
@@ -2812,7 +2579,7 @@ class RegistrationResolver2 {
|
|
|
2812
2579
|
const printed = first !== undefined ? [] : comps.flatMap((c, i) => {
|
|
2813
2580
|
const code = row.codes[i];
|
|
2814
2581
|
const t = row.titles?.[code];
|
|
2815
|
-
const a = t === undefined ? null :
|
|
2582
|
+
const a = t === undefined ? null : compTitle3(t).age;
|
|
2816
2583
|
return a?.isAge() === true && a.kind !== "yob" ? [{ age: a.ag1, year: c.feis.year, code }] : [];
|
|
2817
2584
|
});
|
|
2818
2585
|
const byTitle = youngest2(printed);
|
|
@@ -3022,6 +2789,263 @@ function defaultRemote() {
|
|
|
3022
2789
|
return installed ?? fallbackRemote2(ghRemote2, httpsRemote2());
|
|
3023
2790
|
}
|
|
3024
2791
|
|
|
2792
|
+
function columnsOf2(db, table) {
|
|
2793
|
+
return new Set(db.query("SELECT name FROM pragma_table_info(?)").all(table).map((r) => r.name));
|
|
2794
|
+
}
|
|
2795
|
+
var FEATURES2 = [
|
|
2796
|
+
{
|
|
2797
|
+
feature: "results.level",
|
|
2798
|
+
needs: ["results:competitions.feis_level"],
|
|
2799
|
+
since: 215,
|
|
2800
|
+
calls: "results.query, majors.results"
|
|
2801
|
+
},
|
|
2802
|
+
{
|
|
2803
|
+
feature: "majors.order",
|
|
2804
|
+
needs: ["majors:competitions.seq"],
|
|
2805
|
+
since: 260,
|
|
2806
|
+
calls: "majors.competitions"
|
|
2807
|
+
},
|
|
2808
|
+
{
|
|
2809
|
+
feature: "dancers",
|
|
2810
|
+
needs: ["results:results.name_order_resolved"],
|
|
2811
|
+
since: 260,
|
|
2812
|
+
calls: "dancers.resolve, dancers.search, dancers.get (the dancers index)"
|
|
2813
|
+
},
|
|
2814
|
+
{
|
|
2815
|
+
feature: "schools",
|
|
2816
|
+
needs: ["school-registry.jsonl", "school-redirects.jsonl"],
|
|
2817
|
+
since: 260,
|
|
2818
|
+
calls: "schools.resolve, a dancer question with a school (the schools index)"
|
|
2819
|
+
},
|
|
2820
|
+
{
|
|
2821
|
+
feature: "feises",
|
|
2822
|
+
needs: [
|
|
2823
|
+
"results:feises.date_lo",
|
|
2824
|
+
"results:feises.date_hi",
|
|
2825
|
+
"results:feises.feis_instance_id",
|
|
2826
|
+
"results:feises.feis_brand_id",
|
|
2827
|
+
"results:competitions.feis_region",
|
|
2828
|
+
"feis-redirects.jsonl"
|
|
2829
|
+
],
|
|
2830
|
+
since: 265,
|
|
2831
|
+
calls: "feises.find, feises.series, and comps and registrations through them (the feises index)"
|
|
2832
|
+
},
|
|
2833
|
+
{
|
|
2834
|
+
feature: "comps",
|
|
2835
|
+
needs: [
|
|
2836
|
+
"results:competitions.feis_level",
|
|
2837
|
+
"results:competitions.round",
|
|
2838
|
+
"results:feises.feis_brand_id"
|
|
2839
|
+
],
|
|
2840
|
+
since: 265,
|
|
2841
|
+
calls: "comps.resolve, comps.syllabus, comps.syllabusFor, registrations.resolve (the comps index)"
|
|
2842
|
+
},
|
|
2843
|
+
{
|
|
2844
|
+
feature: "comps.dance",
|
|
2845
|
+
needs: ["results:competitions.dance"],
|
|
2846
|
+
since: 270,
|
|
2847
|
+
calls: "the `dance` a comp of comps.resolve, comps.syllabus and comps.syllabusFor carries: null, never refused, on a release without it"
|
|
2848
|
+
},
|
|
2849
|
+
{
|
|
2850
|
+
feature: "schools.lineage",
|
|
2851
|
+
needs: ["school-lineage.jsonl"],
|
|
2852
|
+
since: 184,
|
|
2853
|
+
calls: "the `lineage` school evidence of dancers.resolve and dancers.search: no school is joined by lineage, never refused, on a release without it"
|
|
2854
|
+
},
|
|
2855
|
+
{
|
|
2856
|
+
feature: "results.stream",
|
|
2857
|
+
needs: [
|
|
2858
|
+
"results:competitions.round",
|
|
2859
|
+
"results:competitions.feis_level",
|
|
2860
|
+
"results:competitions.level_printed",
|
|
2861
|
+
"results:competitions.age_printed",
|
|
2862
|
+
"results:results.yob_printed",
|
|
2863
|
+
"results:results.primary_source",
|
|
2864
|
+
"results:results.platform_dancer_id",
|
|
2865
|
+
"results:results.relative_place",
|
|
2866
|
+
"results:feises.date_lo",
|
|
2867
|
+
"results:feises.date_hi"
|
|
2868
|
+
],
|
|
2869
|
+
since: 255,
|
|
2870
|
+
calls: "results.stream, results.entries (scopes transcribed, platform, all)"
|
|
2871
|
+
},
|
|
2872
|
+
{
|
|
2873
|
+
feature: "majors.stream",
|
|
2874
|
+
needs: [
|
|
2875
|
+
"majors:competitions.round",
|
|
2876
|
+
"majors:competitions.feis_level",
|
|
2877
|
+
"majors:results.yob_printed",
|
|
2878
|
+
"majors:results.primary_source",
|
|
2879
|
+
"majors:results.relative_place",
|
|
2880
|
+
"majors:feises.date_lo"
|
|
2881
|
+
],
|
|
2882
|
+
since: 255,
|
|
2883
|
+
calls: "results.stream, results.entries (scope majors)"
|
|
2884
|
+
},
|
|
2885
|
+
{
|
|
2886
|
+
feature: "majors.tabs",
|
|
2887
|
+
needs: ["majors:tabs.address", "majors:tabs.true_round"],
|
|
2888
|
+
since: 215,
|
|
2889
|
+
calls: "majors.tabulated"
|
|
2890
|
+
},
|
|
2891
|
+
{
|
|
2892
|
+
feature: "feises.dates",
|
|
2893
|
+
needs: ["results:feises.feis_id", "results:feises.date_lo", "results:feises.date_hi"],
|
|
2894
|
+
since: 245,
|
|
2895
|
+
calls: "feises.dates"
|
|
2896
|
+
},
|
|
2897
|
+
{
|
|
2898
|
+
feature: "dancers.records",
|
|
2899
|
+
needs: [
|
|
2900
|
+
"results:dancers.display",
|
|
2901
|
+
"results:dancers.appearances",
|
|
2902
|
+
"results:dancers.birth_lo",
|
|
2903
|
+
"results:dancers.birth_hi",
|
|
2904
|
+
"results:dancers.birth_source",
|
|
2905
|
+
"results:dancers.birth_conflict",
|
|
2906
|
+
"results:dancers.genders"
|
|
2907
|
+
],
|
|
2908
|
+
since: 215,
|
|
2909
|
+
calls: "dancers.records"
|
|
2910
|
+
},
|
|
2911
|
+
{
|
|
2912
|
+
feature: "dancers.years",
|
|
2913
|
+
needs: [
|
|
2914
|
+
"results:dancer_years.region",
|
|
2915
|
+
"results:dancer_years.region_source",
|
|
2916
|
+
"results:dancer_years.nationals",
|
|
2917
|
+
"results:dancer_years.nationals_source",
|
|
2918
|
+
"results:dancer_years.geohash"
|
|
2919
|
+
],
|
|
2920
|
+
since: 245,
|
|
2921
|
+
calls: "dancers.years"
|
|
2922
|
+
},
|
|
2923
|
+
{
|
|
2924
|
+
feature: "teams",
|
|
2925
|
+
needs: [
|
|
2926
|
+
...[
|
|
2927
|
+
"slug",
|
|
2928
|
+
"address",
|
|
2929
|
+
"event",
|
|
2930
|
+
"year",
|
|
2931
|
+
"card",
|
|
2932
|
+
"comp_no",
|
|
2933
|
+
"title",
|
|
2934
|
+
"category",
|
|
2935
|
+
"hands",
|
|
2936
|
+
"letter",
|
|
2937
|
+
"label",
|
|
2938
|
+
"name_printed",
|
|
2939
|
+
"school_printed",
|
|
2940
|
+
"school_loc",
|
|
2941
|
+
"school_id",
|
|
2942
|
+
"place",
|
|
2943
|
+
"place_printed",
|
|
2944
|
+
"tie",
|
|
2945
|
+
"competitors",
|
|
2946
|
+
"atts"
|
|
2947
|
+
].map((c) => `table:team_results.${c}`),
|
|
2948
|
+
...[
|
|
2949
|
+
"team",
|
|
2950
|
+
"kind",
|
|
2951
|
+
"event_year",
|
|
2952
|
+
"year",
|
|
2953
|
+
"comp_no",
|
|
2954
|
+
"title",
|
|
2955
|
+
"team_index",
|
|
2956
|
+
"seq",
|
|
2957
|
+
"school_id",
|
|
2958
|
+
"school_print_materialized"
|
|
2959
|
+
].map((c) => `table:roster_teams.${c}`),
|
|
2960
|
+
...["team", "seq", "name_printed", "name", "yob", "dancer_id"].map((c) => `table:roster_members.${c}`),
|
|
2961
|
+
...[
|
|
2962
|
+
"event_year",
|
|
2963
|
+
"comp_no",
|
|
2964
|
+
"team",
|
|
2965
|
+
"result_address",
|
|
2966
|
+
"school_id",
|
|
2967
|
+
"join_kind",
|
|
2968
|
+
"via_split",
|
|
2969
|
+
"roster_teams",
|
|
2970
|
+
"result_teams",
|
|
2971
|
+
"roster_index",
|
|
2972
|
+
"result_letter"
|
|
2973
|
+
].map((c) => `table:team_result_rosters.${c}`),
|
|
2974
|
+
"table:event_years.event_year",
|
|
2975
|
+
"table:event_years.event",
|
|
2976
|
+
"table:event_years.year"
|
|
2977
|
+
],
|
|
2978
|
+
since: 260,
|
|
2979
|
+
calls: "teams.results, teams.rosters, teams.resultRosters (the teams index)"
|
|
2980
|
+
},
|
|
2981
|
+
{
|
|
2982
|
+
feature: "teams.fields",
|
|
2983
|
+
needs: [
|
|
2984
|
+
"table:team_results.age",
|
|
2985
|
+
"table:team_results.gender",
|
|
2986
|
+
"table:team_results.printed_status",
|
|
2987
|
+
"table:roster_teams.dance",
|
|
2988
|
+
"table:roster_teams.school_print_as_of",
|
|
2989
|
+
"table:roster_members.gender"
|
|
2990
|
+
],
|
|
2991
|
+
since: 266,
|
|
2992
|
+
calls: "the fields teams.results (age, gender, printed_status) and teams.rosters (dance, school_printed_as_of, a member's gender) carry where present: absent, never refused, on a release without them"
|
|
2993
|
+
},
|
|
2994
|
+
{
|
|
2995
|
+
feature: "events",
|
|
2996
|
+
needs: [
|
|
2997
|
+
"event_year",
|
|
2998
|
+
"event",
|
|
2999
|
+
"year",
|
|
3000
|
+
"date_lo",
|
|
3001
|
+
"date_hi",
|
|
3002
|
+
"resolution",
|
|
3003
|
+
"source",
|
|
3004
|
+
"date_printed",
|
|
3005
|
+
"place",
|
|
3006
|
+
"place_printed"
|
|
3007
|
+
].map((c) => `table:event_years.${c}`),
|
|
3008
|
+
since: 260,
|
|
3009
|
+
calls: "events.years (the events index)"
|
|
3010
|
+
},
|
|
3011
|
+
{
|
|
3012
|
+
feature: "staff",
|
|
3013
|
+
needs: [
|
|
3014
|
+
"results:adjudicators",
|
|
3015
|
+
"results:feis_panels",
|
|
3016
|
+
"school-staff.jsonl",
|
|
3017
|
+
"school-teachers.jsonl",
|
|
3018
|
+
"school-registry.jsonl"
|
|
3019
|
+
],
|
|
3020
|
+
since: 260,
|
|
3021
|
+
calls: "staff.teacher, staff.adjudicator (the staff index)"
|
|
3022
|
+
}
|
|
3023
|
+
];
|
|
3024
|
+
async function hasNeed(probe, n) {
|
|
3025
|
+
const t = /^table:([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
|
|
3026
|
+
if (t !== null) {
|
|
3027
|
+
if (probe.tableColumns === undefined)
|
|
3028
|
+
return false;
|
|
3029
|
+
const cols = await probe.tableColumns(t[1]);
|
|
3030
|
+
return t[2] === undefined ? cols.size > 0 : cols.has(t[2]);
|
|
3031
|
+
}
|
|
3032
|
+
const m = /^(results|majors):([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
|
|
3033
|
+
if (m === null)
|
|
3034
|
+
return probe.publishes(n);
|
|
3035
|
+
const cols = columnsOf2(await probe.db(m[1]), m[2]);
|
|
3036
|
+
return m[3] === undefined ? cols.size > 0 : cols.has(m[3]);
|
|
3037
|
+
}
|
|
3038
|
+
async function capabilityOf2(probe, f) {
|
|
3039
|
+
const missing = [];
|
|
3040
|
+
for (const n of f.needs)
|
|
3041
|
+
if (!await hasNeed(probe, n))
|
|
3042
|
+
missing.push(n);
|
|
3043
|
+
return { ...f, state: missing.length === 0 ? "full" : "missing", missing };
|
|
3044
|
+
}
|
|
3045
|
+
function needsRelease2(tag, c) {
|
|
3046
|
+
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`);
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3025
3049
|
function tagDir(root, tag) {
|
|
3026
3050
|
if (parseArchiveTag2(tag) === null)
|
|
3027
3051
|
throw new Error(`archive-client: "${tag}" is not a release tag (archive-YYYY.MM.DD[-N[.k]])`);
|
|
@@ -3540,4 +3564,4 @@ function releases2(opts = {}) {
|
|
|
3540
3564
|
return new ReleaseManager2(opts);
|
|
3541
3565
|
}
|
|
3542
3566
|
|
|
3543
|
-
export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2,
|
|
3567
|
+
export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, initialsOnly2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, StaffFinder2, SchoolResolver2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, resolveTag2, Release2, openRelease2, openCached2, ReleaseManager2, releases2 };
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -8,10 +8,6 @@ import {
|
|
|
8
8
|
nameLevel2,
|
|
9
9
|
NO_CALENDAR2,
|
|
10
10
|
FeisFinder2,
|
|
11
|
-
columnsOf2,
|
|
12
|
-
FEATURES2,
|
|
13
|
-
capabilityOf2,
|
|
14
|
-
needsRelease2,
|
|
15
11
|
entryRank2,
|
|
16
12
|
FEIS_LOCAL_ID_FLOOR2,
|
|
17
13
|
ResultFinder2,
|
|
@@ -40,13 +36,17 @@ import {
|
|
|
40
36
|
httpsRemote2,
|
|
41
37
|
fallbackRemote2,
|
|
42
38
|
setRemote2,
|
|
39
|
+
columnsOf2,
|
|
40
|
+
FEATURES2,
|
|
41
|
+
capabilityOf2,
|
|
42
|
+
needsRelease2,
|
|
43
43
|
resolveTag2,
|
|
44
44
|
Release2,
|
|
45
45
|
openRelease2,
|
|
46
46
|
openCached2,
|
|
47
47
|
ReleaseManager2,
|
|
48
48
|
releases2
|
|
49
|
-
} from "./chunks/index-
|
|
49
|
+
} from "./chunks/index-xtv455ft.js";
|
|
50
50
|
import {
|
|
51
51
|
cacheRoot2,
|
|
52
52
|
repoOf2
|
package/dist/types/stream.d.ts
CHANGED
|
@@ -50,7 +50,6 @@ export interface StreamRow {
|
|
|
50
50
|
dancer_scope: "per-release" | "stable" | null;
|
|
51
51
|
yob: number | null;
|
|
52
52
|
yob_printed: string | null;
|
|
53
|
-
printed_age: string | null;
|
|
54
53
|
points_printed: string | null;
|
|
55
54
|
platform_dancer_id: string | null;
|
|
56
55
|
primary_source: string | null;
|
|
@@ -61,12 +60,8 @@ export interface EntryRow extends StreamRow {
|
|
|
61
60
|
recall_place: number | null;
|
|
62
61
|
}
|
|
63
62
|
export declare function entryRank(section: string): number;
|
|
64
|
-
export interface StreamColumns {
|
|
65
|
-
printedAge: boolean;
|
|
66
|
-
}
|
|
67
|
-
export declare const streamColumns: (db: Database) => StreamColumns;
|
|
68
63
|
export declare const scopeDb: (s: StreamScope) => "majors" | "results";
|
|
69
|
-
export declare function streamSql(q: StreamQuery
|
|
64
|
+
export declare function streamSql(q: StreamQuery): {
|
|
70
65
|
sql: string;
|
|
71
66
|
args: (string | number)[];
|
|
72
67
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finbheara/archive-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|