@finbheara/archive-client 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -189,7 +189,8 @@ print exactly as the release published it, then the print read by `@finbheara/do
189
189
  reader at its filing key (`sn3`) and at its real words. `ambiguous` and `none` are answers: a
190
190
  best guess is never promoted to `unique`. `region` drops only a candidate the registry places in
191
191
  other regions, and keeps one it places nowhere. A print the reader refuses (a placeholder)
192
- answers `none` with the reason.
192
+ answers `none` with the reason. An id (or a retired id followed) answers with every print its
193
+ rows were published under and their rows, so it agrees with each print lookup of that school.
193
194
 
194
195
  Every answer carries the release tag it came from.
195
196
 
@@ -249,6 +250,10 @@ A FEIS-LOCAL ID (`FEIS_LOCAL_ID_FLOOR`, 2^34, and up) is valid in its release on
249
250
  carrying one say `scope: "per-release"` (`dancer_scope` on a row), so a caller never stores one
250
251
  across releases. Every answer carries the release tag.
251
252
 
253
+ A MAJORS ID (under the floor) is kept across releases on a best-effort basis only: a merge
254
+ retires one, a split mints one, and a renumbered source page can hand one to another dancer. A
255
+ caller that joins ids across releases checks the name and the rows it expects.
256
+
252
257
  ## Results and the majors
253
258
 
254
259
  ```ts
@@ -284,6 +289,8 @@ c.source; // "syllabus" | "prior-year" | "title"
284
289
  c.kind; // "championship" | "grade" | "unrated"
285
290
  c.level; // "OC" (FF, BG, AB, NV, PW, PC, OC), c.feis_level 700
286
291
  c.age; // "U10", c.population "mixed", c.canonical "710OC", c.title, c.competition
292
+ c.age_source; // "printed" | "title" | "code" | "merged" | null, c.age_printed "U10" as printed
293
+ c.second; // true for a second chance or a supplementary comp beside the main one
287
294
  c.from; // { feis_ids: ["fweb:ABC25"], year: 2025 } where an earlier year answered
288
295
  c.check; // "unchecked", or what the caller's `title` says about the earlier comp
289
296
  c.syllabus_source; // "results": what the syllabus the answer read is read from
@@ -326,6 +333,21 @@ QuickFeis numbers its own and-over championships (`15 & over` prelim `616PC`, `1
326
333
  `719OC`). An and-over code is then the same string as an `Under N + 1` one, and `age` (`O18`)
327
334
  tells them apart. An adult band, a range and a grade render none.
328
335
 
336
+ `age` is the release's reading, and `age_source` says where it was read. A `merged` comp (a
337
+ platform's comps merged into one, `710OCM` with no `710OC` beside it) prints one member's band,
338
+ so its `age` is the union of its members' (`U12` for `710OCM - U10 Open Champion` where the next
339
+ open the feis prints is `713OCM`), and `age_printed` keeps the platform's own field (`U10`). A
340
+ joined code at the bottom of its series stays open below (`710OC/711OC` with no open under 10
341
+ beside it is `U11/11yr`, every age under 11), as a younger dancer dances up into it.
342
+
343
+ `population` is the sex the release states, else a population word in the title, else the
344
+ code's sex suffix read with every code its feis printed (`codePopulation` from
345
+ `@finbheara/domain/title`: `720OCB` beside `720OCG` is boys and girls, `512M` beside `512` is
346
+ boys, a lone `713OCM` says nothing), else the release's `mixed` for a grade or a preliminary
347
+ championship. `second` is read from the words of the title and the platform's level and dance
348
+ fields (`secondIn`: `2nd Chance`, `2nd Chc`, `Second Chance`, `Supplementary Prelim`, `2nd Open
349
+ Champ`), and never from a code's digit, which marks a round's half at some hosts (`610PC2H`).
350
+
329
351
  ## Registrations
330
352
 
331
353
  ```ts
@@ -655,9 +655,11 @@ function ageEvidence(d, asked) {
655
655
 
656
656
  import { Age as Age2 } from "@finbheara/domain/age";
657
657
  import {
658
+ codePopulation,
658
659
  compTitle,
659
660
  matchTitle,
660
661
  populationIn as populationIn2,
662
+ secondIn,
661
663
  titleRelation
662
664
  } from "@finbheara/domain/title";
663
665
  var LEVEL = Object.freeze({
@@ -674,17 +676,25 @@ CREATE TABLE comp(
674
676
  competition TEXT PRIMARY KEY, feis_id TEXT NOT NULL, brand_id TEXT, year INTEGER,
675
677
  code TEXT NOT NULL, code_fold TEXT NOT NULL, title TEXT, age TEXT, gender TEXT,
676
678
  feis_level INTEGER, round TEXT, platform TEXT NOT NULL, captures INTEGER NOT NULL,
677
- file TEXT NOT NULL, line INTEGER NOT NULL
679
+ file TEXT NOT NULL, line INTEGER NOT NULL, age_source TEXT, age_printed TEXT,
680
+ gender_source TEXT, level_printed TEXT, dance_printed TEXT
678
681
  );
679
682
  `;
680
683
  var INDEXES = [
681
684
  "CREATE INDEX comp_feis_code ON comp(feis_id, code_fold)",
682
685
  "CREATE INDEX comp_brand_year ON comp(brand_id, year)"
683
686
  ];
687
+ var OPTIONAL = [
688
+ "age_source",
689
+ "age_printed",
690
+ "gender_source",
691
+ "level_printed",
692
+ "dance_printed"
693
+ ];
684
694
  var fold = (code) => code.trim().toLowerCase();
685
695
  var compsIndex = {
686
696
  name: "comps",
687
- version: "1",
697
+ version: "2",
688
698
  inputs: ["results"],
689
699
  says: "every competition by its feis and its own code, with its series and year",
690
700
  build(rel, out) {
@@ -697,19 +707,23 @@ var compsIndex = {
697
707
  const captures = new Map;
698
708
  for (const r of src.query("SELECT competition, count(*) AS n FROM competitions GROUP BY competition").all())
699
709
  captures.set(r.competition, r.n);
700
- const put = out.prepare("INSERT OR IGNORE INTO comp VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
701
- for (const c of src.query("SELECT competition, event, year, comp_no, title, age, gender, feis_level, round, source, file, line FROM competitions WHERE comp_no IS NOT NULL AND trim(comp_no) != '' ORDER BY file, line").iterate()) {
710
+ const put = out.prepare("INSERT OR IGNORE INTO comp VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
711
+ const has = new Set(src.query("PRAGMA table_info(competitions)").all().map((r) => r.name));
712
+ const opt = (col) => has.has(col) ? col : `NULL AS ${col}`;
713
+ for (const c of src.query(`SELECT competition, event, year, comp_no, title, age, gender, feis_level, round, source, file, line, ${OPTIONAL.map(opt).join(", ")} FROM competitions WHERE comp_no IS NOT NULL AND trim(comp_no) != '' ORDER BY file, line`).iterate()) {
702
714
  const f = brand.get(c.event);
703
- 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);
715
+ 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);
704
716
  }
705
717
  for (const s of INDEXES)
706
718
  out.run(s);
707
719
  }
708
720
  };
709
- function compOf(r) {
721
+ function compOf(r, codes = []) {
710
722
  const lv = r.feis_level;
711
723
  const kind = lv === null ? "unrated" : lv >= 600 ? "championship" : "grade";
712
- const pop = populationIn2(r.gender ?? "") || populationIn2(r.title ?? "");
724
+ const stated = r.gender_source === "level" ? "" : populationIn2(r.gender ?? "");
725
+ const pop = stated || populationIn2(r.title ?? "") || (r.platform === "ifeis" && codes.length > 0 ? codePopulation(r.code, codes) : "") || populationIn2(r.gender ?? "");
726
+ const second = [r.title, r.level_printed, r.dance_printed].some((t) => t != null && secondIn(t));
713
727
  const round = r.round ?? (r.title === null ? null : compTitle(r.title).round?.toString() ?? null);
714
728
  return {
715
729
  competition: r.competition,
@@ -721,7 +735,10 @@ function compOf(r) {
721
735
  feis_level: lv,
722
736
  level: lv === null ? null : LEVEL[lv] ?? null,
723
737
  age: r.age,
738
+ age_source: r.age_source ?? null,
739
+ age_printed: r.age_printed ?? null,
724
740
  population: pop === "" ? null : pop,
741
+ second,
725
742
  round,
726
743
  canonical: canonicalOf2(lv, r.age),
727
744
  platform: r.platform,
@@ -828,7 +845,12 @@ class CompResolver2 {
828
845
  const marks = feisIds.map(() => "?").join(", ");
829
846
  const sql = `SELECT * FROM comp WHERE feis_id IN (${marks})${code !== undefined ? " AND code_fold = ?" : ""} ORDER BY file, line`;
830
847
  const args = code !== undefined ? [...feisIds, fold(code)] : [...feisIds];
831
- return db.query(sql).all(...args).map(compOf);
848
+ const rows = db.query(sql).all(...args);
849
+ const all = code === undefined ? rows : db.query(`SELECT feis_id, code FROM comp WHERE feis_id IN (${marks})`).all(...feisIds);
850
+ const codes = new Map;
851
+ for (const r of all)
852
+ codes.set(r.feis_id, [...codes.get(r.feis_id) ?? [], r.code]);
853
+ return rows.map((r) => compOf(r, codes.get(r.feis_id) ?? []));
832
854
  }
833
855
  async idsOf(brand, year) {
834
856
  return (await this.ofSeries("", brand, year, "series")).feis_ids;
@@ -1437,7 +1459,7 @@ var encoderOf = (r) => schoolEncoder({ reader: r.reader });
1437
1459
  var TABLE = "school_name";
1438
1460
  var schoolsIndex = {
1439
1461
  name: "schools",
1440
- version: "1",
1462
+ version: "2",
1441
1463
  inputs: ["results", "school-registry.jsonl", "school-redirects.jsonl"],
1442
1464
  says: "every published school print and its ids, read at the school reader's slots",
1443
1465
  keyParts: (rel) => [`reader=${rel.schoolReader().id}`],
@@ -1453,6 +1475,7 @@ var schoolsIndex = {
1453
1475
  const putPair = out.prepare("INSERT INTO print_id VALUES (?, ?, ?)");
1454
1476
  for (const r of pairs)
1455
1477
  putPair.run(r.p, r.s, r.n);
1478
+ out.run("CREATE INDEX print_id_school ON print_id(school_id)");
1456
1479
  const names = createIndex3(out, encoder, {
1457
1480
  table: TABLE
1458
1481
  });
@@ -1512,9 +1535,13 @@ class SchoolResolver2 {
1512
1535
  ...extra
1513
1536
  };
1514
1537
  };
1538
+ const byId = (id, by) => {
1539
+ const rs = db.query("SELECT print, rows FROM print_id WHERE school_id = ? ORDER BY print").all(id);
1540
+ return candidate(id, by, rs.reduce((n, r) => n + r.rows, 0), rs.map((r) => r.print));
1541
+ };
1515
1542
  const a = asked.trim();
1516
1543
  if (reg.get(a) !== null)
1517
- return answer([candidate(a, "id", 0, [])]);
1544
+ return answer([byId(a, "id")]);
1518
1545
  let live = a;
1519
1546
  const seen = new Set;
1520
1547
  for (;; ) {
@@ -1525,7 +1552,7 @@ class SchoolResolver2 {
1525
1552
  live = r.new_id;
1526
1553
  }
1527
1554
  if (live !== a)
1528
- return answer([candidate(live, "redirect", 0, [])], { redirected: { from: a, to: live } });
1555
+ return answer([byId(live, "redirect")], { redirected: { from: a, to: live } });
1529
1556
  const byPrints = (prints, by) => {
1530
1557
  const ids = new Map;
1531
1558
  const q2 = db.query("SELECT school_id, rows FROM print_id WHERE print = ?");
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  nameOrderOf,
5
5
  openRelease2,
6
6
  releases2
7
- } from "./chunks/index-6e2gb6sk.js";
7
+ } from "./chunks/index-3va2jzse.js";
8
8
  import"./chunks/index-hne1wadw.js";
9
9
  import"./chunks/index-hghdy980.js";
10
10
 
@@ -690,7 +690,7 @@ async function main(argv, io = {}) {
690
690
  out(a, () => {
691
691
  say(`${a.feis.asked} ${a.year ?? "?"} ${a.held ? `held: ${a.feis.feis_ids.join(", ")}` : "not held"} series ${a.feis.brand_id ?? "-"} (years ${a.years.join(", ") || "-"}) in ${a.tag}`);
692
692
  for (const c of a.entries)
693
- say(` ${c.code} ${c.kind} ${c.level ?? "-"} ${c.age ?? "-"} ${c.population ?? "-"} "${c.title ?? ""}"`);
693
+ say(` ${c.code} ${c.kind} ${c.level ?? "-"} ${c.age ?? "-"} ${c.population ?? "-"}${c.second ? " second" : ""} "${c.title ?? ""}"`);
694
694
  say(`${a.entries.length} comp(s)`);
695
695
  });
696
696
  rel.close();
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ import {
41
41
  openCached2,
42
42
  ReleaseManager2,
43
43
  releases2
44
- } from "./chunks/index-6e2gb6sk.js";
44
+ } from "./chunks/index-3va2jzse.js";
45
45
  import {
46
46
  cacheRoot2,
47
47
  repoOf2
@@ -15,7 +15,10 @@ export interface Comp {
15
15
  feis_level: number | null;
16
16
  level: string | null;
17
17
  age: string | null;
18
+ age_source: string | null;
19
+ age_printed: string | null;
18
20
  population: Population | null;
21
+ second: boolean;
19
22
  round: string | null;
20
23
  canonical: string | null;
21
24
  platform: string;
@@ -107,8 +110,13 @@ interface CompRow {
107
110
  round: string | null;
108
111
  platform: string;
109
112
  captures: number;
113
+ age_source?: string | null;
114
+ age_printed?: string | null;
115
+ gender_source?: string | null;
116
+ level_printed?: string | null;
117
+ dance_printed?: string | null;
110
118
  }
111
- export declare function compOf(r: CompRow): Comp;
119
+ export declare function compOf(r: CompRow, codes?: readonly string[]): Comp;
112
120
  export declare function canonicalOf(level: number | null, age: string | null): string | null;
113
121
  export declare class CompResolver {
114
122
  private readonly rel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Open a versioned release of an Irish dance results archive: download, verify, decompress and cache it by tag, keep the cache, and resolve feises and schools against it",
5
5
  "keywords": ["irish-dance", "results", "release", "cache", "sqlite"],
6
6
  "license": "MIT",
@@ -44,7 +44,7 @@
44
44
  "prepublishOnly": "bun scripts/build.ts && bun ../../tools/checks/archive-client-tarball.ts"
45
45
  },
46
46
  "dependencies": {
47
- "@finbheara/domain": "0.48.0",
47
+ "@finbheara/domain": "0.49.0",
48
48
  "@finbheara/names": "0.22.0"
49
49
  },
50
50
  "devDependencies": {