@finbheara/archive-client 0.24.0 → 0.25.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 CHANGED
@@ -95,6 +95,13 @@ c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...
95
95
  | `teams` | `teams.results`, `teams.rosters`, `teams.resultRosters` | 260 |
96
96
  | `teams.fields` | the fields `teams.results` (`age`, `gender`, `printed_status`) and `teams.rosters` (`dance`, `school_printed_as_of`, a member's `gender`) carry; absent, never refused, without it | 266 |
97
97
  | `events` | `events.years` | 260 |
98
+ | `tabs`, `panels` | `tabs.query`, `tabs.stream`; `panels.query` | 184 |
99
+ | `feis-panels` | `feisPanels.query` | 260 |
100
+ | `staff.adjudicators` | `staff.adjudicators` | 230 |
101
+ | `schools.registry` | `schools.list` | 260 |
102
+ | `schools.prints`, `schools.redirects` | `schools.prints`; `schools.redirects` | 184 |
103
+ | `schools.lineage` | `schools.links`, `schools.line`, `schools.lineage` | 184 |
104
+ | `schools.associations` | `schools.associations` | 215 |
98
105
 
99
106
  A spec is `pinned` (the default: the user-local pin, else the latest), `latest`, a cut number
100
107
  (`42`, or `42.1` for a fraction) or a tag (`archive-YYYY.MM.DD-N`).
@@ -227,6 +234,29 @@ rows were published under and their rows, so it agrees with each print lookup of
227
234
 
228
235
  Every answer carries the release tag it came from.
229
236
 
237
+ ### The schools in bulk
238
+
239
+ ```ts
240
+ await rel.schools.list(); // { schools: [{ school_id, display, regions, places }] }: the registry
241
+ await rel.schools.prints({ event: "<event>", year: 2024 });
242
+ // { prints: [{ event, year, print, school_id, rows }] }
243
+ await rel.schools.redirects(); // { redirects: [{ old_id, new_id, kind, since }] }
244
+ await rel.schools.lineage(); // { edges: [{ id, direction, other, shape, year, provisional,
245
+ // decision, confidence }] }
246
+ await rel.schools.associations({ school_id: "<id>" });
247
+ // { associations: [{ at, seq, date, person, person_printed,
248
+ // person_type, credentials, caption, school_printed, school_id,
249
+ // association_type, src_type, src, candidate_school_id, candidate_rung, ... }] }
250
+ ```
251
+
252
+ `prints` is every (print, school id) pair the release's result rows attach in an event-year (the
253
+ print trimmed), with the rows that carry it: an event's (case-blind), a year's, both or every one.
254
+ A row attached to no school is in no pair, and a team result's school is `rel.teams.results`'.
255
+ `lineage` is each edge end as the release publishes it (an edge may carry both ends): `links` and
256
+ `line` are the graph over them. `associations` is the people a directory lists beside a school, as
257
+ evidence of an association, never an identity: `school_id` is the school the print attached to,
258
+ `candidate_school_id` one it reached without attaching.
259
+
230
260
  ### How two schools are linked
231
261
 
232
262
  ```ts
@@ -550,6 +580,33 @@ await rel.feises.dates(); // every feis id with its first a
550
580
  await rel.majors.tabulated({ min_true_round: 3 }); // the majors addresses marked to round 3 or later
551
581
  ```
552
582
 
583
+ ## Marks sheets and panels
584
+
585
+ ```ts
586
+ const s = await rel.tabs.query({ competition: "<competition>" }); // or { address }, and a { round }
587
+ s.rows; // [{ address, competition, card, line, event, year, tier, round, true_round, seat_index,
588
+ // section, panel_size, column_order, drop_rule, the judge (name, print, TD number,
589
+ // id, unmatched reason), mark, mark_printed, place, place_printed, tie, points,
590
+ // points_printed }]
591
+ for await (const t of rel.tabs.stream({ tier: ["T1", "T2", "T3"], year: [2015, 2025] })) ...
592
+ await rel.panels.query({ competition: "<competition>" }); // { seats: [{ competition, panel, round,
593
+ // seat_index, address, the judge (print, TD number, id, unmatched reason) }] }
594
+ await rel.feisPanels.query({ address: "fweb:<code>.<comp>" }); // { seats: [{ address, platform, round,
595
+ // seat_index, panel_size, panel_kind, the judge (print, state printed, TD number, id,
596
+ // unmatched reason, basis) }] }
597
+ ```
598
+
599
+ A marks sheet is a row per card, round and seat, with the seat's mark, place and points as read
600
+ beside their prints, from `results.sqlite` (every tabulated sheet, the T4 events' too). The stream
601
+ takes an event (or several, case-blind), a year or an inclusive range, tiers, competitions, or
602
+ nothing (every cell), and yields one competition's rows together. A sheet's `event`, `year` and
603
+ `tier` are its competition's: a solo competition's from the results, a team competition's from the
604
+ team results (with the tier the release gives that event-year's solo competitions). A sheet the
605
+ release places in neither carries nulls, and a filter on them never reaches it. `panels` and
606
+ `feisPanels` with nothing named answer every seat. `round` is `rx` where one panel sat every round.
607
+ A seat carries its judge's TD number where the release linked one, else a judge id, else the
608
+ reason it is linked to neither (`TabRow`, `PanelSeat` and `FeisPanelSeat` name the fields).
609
+
553
610
  ## Teams and event-years
554
611
 
555
612
  ```ts
@@ -596,6 +653,9 @@ release into the `teams` and `events` indexes.
596
653
  ```ts
597
654
  await rel.staff.teacher("<name>"); // or a TD number
598
655
  await rel.staff.adjudicator("<name>");
656
+ await rel.staff.adjudicators({ year: 2025 }); // the register's rows (or a TD number's, or every one):
657
+ // [{ the TD number, year, cat, name_printed, country, country_printed, country_code, region,
658
+ // region_printed, region_code, geohash, src }] (`RegisterRow`)
599
659
  ```
600
660
 
601
661
  One person per TD number (else per judge id, else per name): the schools taught at (role,
@@ -747,6 +807,13 @@ is passed to the dancer resolver as asked (`rel.dancers`, above).
747
807
  idq records --years [--release <spec>] [--dancer <id,...>] [--year <y>]
748
808
  idq feis-dates [--release <spec>]
749
809
  idq tabulated [--release <spec>] [--min-round <n>]
810
+ idq tabs [--release <spec>] --comp <competition> | --address <address> [--round <r>]
811
+ idq tabs --stream [--release <spec>] [--comp <c,...>] [--event <e,...>] [--year <y> | --year <from>..<to>] [--tier <t,...>]
812
+ idq panels [--release <spec>] [--comp <competition>]
813
+ idq feis-panels [--release <spec>] [--address <feis competition>] [--platform <p>]
814
+ idq register [--release <spec>] [--year <y>] [--td <TD number>]
815
+ idq schools [--release <spec>] [--prints [--event <e>] [--year <y>] | --redirects | --lineage
816
+ | --associations [--school <school_id>]]
750
817
  idq teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]
751
818
  idq events [--release <spec>] [--event <e>] [--year <y>]
752
819
  idq teacher [--release <spec>] <name|TD number>
@@ -759,7 +826,7 @@ is passed to the dancer resolver as asked (`rel.dancers`, above).
759
826
  [--oireachtas <region>[:<year>]]
760
827
  idq resolve registrations
761
828
 
762
- `stream` prints one JSON line per row (or entry), as it reads them. `dancer` prints the results of the one dancer where the answer is unique (every candidate's with
829
+ `stream` (and `tabs --stream`) prints one JSON line per row (or entry, or cell), as it reads them. `dancer` prints the results of the one dancer where the answer is unique (every candidate's with
763
830
  `--rows`). `--json` prints the call's answer as it is. `--cache` and `--repo` override the defaults.
764
831
 
765
832
  `update` moves to the newest published release, as a package manager's `update` moves to the newest
@@ -774,6 +774,96 @@ import {
774
774
  DEFAULT_SCHOOL_NAME_READER,
775
775
  schoolEncoder
776
776
  } from "@finbheara/domain/school";
777
+
778
+ var str = (v) => typeof v === "string" && v !== "" ? v : null;
779
+ var num = (v) => typeof v === "number" ? v : null;
780
+ var uniq = (xs) => [...new Set(xs)];
781
+ async function registrySchools(rel) {
782
+ await rel.require("schools.registry");
783
+ await rel.fetch("school-registry.jsonl");
784
+ const schools = readJsonl(rel.path("school-registry.jsonl")).map((r) => ({
785
+ school_id: r.school_id,
786
+ display: r.display,
787
+ regions: uniq((r.regions ?? []).map((x) => x.region)),
788
+ places: uniq((r.locations ?? []).map((x) => x.place))
789
+ })).sort((a, b) => a.school_id < b.school_id ? -1 : a.school_id > b.school_id ? 1 : 0);
790
+ return { tag: rel.tag, schools };
791
+ }
792
+ async function schoolPrints(rel, q) {
793
+ await rel.fetch("results");
794
+ await rel.require("schools.prints");
795
+ const where = ["r.school_id IS NOT NULL"];
796
+ const args = [];
797
+ if (q.event !== undefined) {
798
+ where.push("c.event = ? COLLATE NOCASE");
799
+ args.push(q.event);
800
+ }
801
+ if (q.year !== undefined) {
802
+ where.push("c.year = ?");
803
+ args.push(q.year);
804
+ }
805
+ const prints = rel.db("results").query(`SELECT c.event AS event, c.year AS year, trim(r.school_printed) AS print,
806
+ r.school_id AS school_id, count(*) AS rows
807
+ FROM competitions c JOIN results r ON r.file = c.file AND r.competition_line = c.line
808
+ WHERE ${where.join(" AND ")} GROUP BY 1, 2, 3, 4 ORDER BY 2, 1, 3, 4`).all(...args);
809
+ return { tag: rel.tag, asked: q, prints };
810
+ }
811
+ async function schoolRedirects(rel) {
812
+ await rel.require("schools.redirects");
813
+ await rel.fetch("school-redirects.jsonl");
814
+ const redirects = readJsonl(rel.path("school-redirects.jsonl")).map((r) => ({
815
+ old_id: String(r.old_id),
816
+ new_id: String(r.new_id),
817
+ kind: String(r.kind),
818
+ since: num(r.since)
819
+ }));
820
+ return { tag: rel.tag, redirects };
821
+ }
822
+ async function schoolLineage(rel) {
823
+ await rel.require("schools.lineage");
824
+ await rel.fetch("school-lineage.jsonl");
825
+ const edges = readJsonl(rel.path("school-lineage.jsonl")).map((r) => ({
826
+ id: String(r.id),
827
+ direction: String(r.direction),
828
+ other: String(r.other),
829
+ shape: String(r.shape),
830
+ year: num(r.year),
831
+ provisional: r.provisional === true,
832
+ decision: str(r.decision),
833
+ confidence: num(r.confidence)
834
+ }));
835
+ return { tag: rel.tag, edges };
836
+ }
837
+ async function observedAssociations(rel, q) {
838
+ await rel.require("schools.associations");
839
+ await rel.fetch("observed-associations.jsonl");
840
+ const associations = [];
841
+ for (const r of readJsonl(rel.path("observed-associations.jsonl"))) {
842
+ const school_id = str(r.schoolId);
843
+ if (q.school_id !== undefined && school_id !== q.school_id)
844
+ continue;
845
+ associations.push({
846
+ at: String(r.at),
847
+ seq: num(r.seq) ?? 0,
848
+ date: str(r.date),
849
+ person: str(r.person),
850
+ person_printed: typeof r.personPrinted === "string" ? r.personPrinted : "",
851
+ reading: str(r.reading),
852
+ person_type: str(r.personType),
853
+ credentials: str(r.credentials),
854
+ caption: str(r.caption),
855
+ school_printed: str(r.school),
856
+ school_id,
857
+ association_type: str(r.associationType),
858
+ src_type: str(r.srcType),
859
+ src: str(r.src),
860
+ candidate_school_id: str(r.candidateSchoolId),
861
+ candidate_rung: str(r.candidateRung)
862
+ });
863
+ }
864
+ return { tag: rel.tag, asked: q, associations };
865
+ }
866
+
777
867
  function readsSpanishNames2(candidates) {
778
868
  return candidates.some((c) => c.spanishNames !== undefined && (c.spanishNames.file || c.prints.some((p) => c.spanishNames?.prints.includes(p.trim()))));
779
869
  }
@@ -846,6 +936,21 @@ class SchoolResolver2 {
846
936
  this.graph ??= releaseSchoolGraph2(this.rel);
847
937
  return { tag: this.rel.tag, ...(await this.graph).line(a.trim(), q) };
848
938
  }
939
+ list() {
940
+ return registrySchools(this.rel);
941
+ }
942
+ prints(q = {}) {
943
+ return schoolPrints(this.rel, q);
944
+ }
945
+ redirects() {
946
+ return schoolRedirects(this.rel);
947
+ }
948
+ lineage() {
949
+ return schoolLineage(this.rel);
950
+ }
951
+ associations(q = {}) {
952
+ return observedAssociations(this.rel, q);
953
+ }
849
954
  async resolve(asked, q = {}) {
850
955
  const db = await this.rel.indexDb("schools");
851
956
  const reg = db.query("SELECT display, regions, places, spanish FROM registry WHERE school_id = ?");
@@ -2246,6 +2351,218 @@ import {
2246
2351
  roleEncoder
2247
2352
  } from "@finbheara/domain/noun-index";
2248
2353
  import { roleName } from "@finbheara/domain/person";
2354
+
2355
+ var TAB_COLUMNS = `address, slug AS competition, card, line, round, true_round, seat_index,
2356
+ section, panel_size, column_order, drop_rule, judge_name, judge_name_printed, judge_clrg,
2357
+ judge_id, judge_unmatched, mark, mark_printed, place, place_printed, tie, points, points_printed`;
2358
+ var TAB_NEEDS = [
2359
+ "address",
2360
+ "slug",
2361
+ "card",
2362
+ "line",
2363
+ "round",
2364
+ "true_round",
2365
+ "seat_index",
2366
+ "section",
2367
+ "panel_size",
2368
+ "column_order",
2369
+ "drop_rule",
2370
+ "judge_name",
2371
+ "judge_name_printed",
2372
+ "judge_clrg",
2373
+ "judge_id",
2374
+ "judge_unmatched",
2375
+ "mark",
2376
+ "mark_printed",
2377
+ "place",
2378
+ "place_printed",
2379
+ "tie",
2380
+ "points",
2381
+ "points_printed"
2382
+ ].map((c) => `results:tabs.${c}`);
2383
+ var NO_SHEET = { event: null, year: null, tier: null };
2384
+ var cardRange = (competition) => [`${competition}#`, `${competition}$`];
2385
+
2386
+ class TabFinder2 {
2387
+ rel;
2388
+ constructor(rel) {
2389
+ this.rel = rel;
2390
+ }
2391
+ async db() {
2392
+ await this.rel.fetch("results");
2393
+ await this.rel.require("tabs");
2394
+ return this.rel.db("results");
2395
+ }
2396
+ async sheets(db, only) {
2397
+ const out = new Map;
2398
+ const tiers = new Map;
2399
+ const rows = [];
2400
+ if (only === null)
2401
+ rows.push(...db.query("SELECT competition, event, year, tier FROM competitions WHERE competition IN (SELECT DISTINCT slug FROM tabs)").all());
2402
+ else
2403
+ for (let i = 0;i < only.length; i += 500) {
2404
+ const part = only.slice(i, i + 500);
2405
+ rows.push(...db.query(`SELECT competition, event, year, tier FROM competitions WHERE competition IN (${part.map(() => "?").join(", ")})`).all(...part));
2406
+ }
2407
+ for (const r of rows) {
2408
+ out.set(r.competition, { event: r.event, year: r.year, tier: r.tier });
2409
+ tiers.set(`${r.event.toLowerCase()}\x00${r.year}`, r.tier);
2410
+ }
2411
+ const missing = only === null ? null : only.filter((c) => !out.has(c));
2412
+ if (missing !== null && missing.length === 0)
2413
+ return out;
2414
+ if ((await this.rel.capability("teams")).state !== "full")
2415
+ return out;
2416
+ const teams = await this.rel.indexDb("teams");
2417
+ const team = teams.query("SELECT competition, event, year FROM result WHERE competition IS NOT NULL GROUP BY competition");
2418
+ const want = missing === null ? null : new Set(missing);
2419
+ for (const t of team.all()) {
2420
+ if (out.has(t.competition) || want !== null && !want.has(t.competition))
2421
+ continue;
2422
+ out.set(t.competition, {
2423
+ event: t.event,
2424
+ year: t.year,
2425
+ tier: tiers.get(`${t.event.toLowerCase()}\x00${t.year}`) ?? null
2426
+ });
2427
+ }
2428
+ if ([...out.values()].some((s) => s.tier === null && s.event !== null)) {
2429
+ for (const r of db.query("SELECT event, year, min(tier) AS tier FROM competitions GROUP BY event, year").all())
2430
+ tiers.set(`${r.event.toLowerCase()}\x00${r.year}`, r.tier);
2431
+ for (const s of out.values())
2432
+ if (s.tier === null && s.event !== null)
2433
+ s.tier = tiers.get(`${s.event.toLowerCase()}\x00${s.year}`) ?? null;
2434
+ }
2435
+ return out;
2436
+ }
2437
+ async query(q) {
2438
+ const db = await this.db();
2439
+ const where = [];
2440
+ const args = [];
2441
+ if (q.address !== undefined) {
2442
+ where.push("address = ?");
2443
+ args.push(q.address);
2444
+ } else if (q.competition !== undefined) {
2445
+ const [lo, hi] = cardRange(q.competition);
2446
+ where.push("address >= ? AND address < ? AND slug = ?");
2447
+ args.push(lo, hi, q.competition);
2448
+ } else
2449
+ throw new Error("archive-client: a tabs query names a competition or an address");
2450
+ if (q.round !== undefined) {
2451
+ where.push("round = ?");
2452
+ args.push(q.round);
2453
+ }
2454
+ const raw = db.query(`SELECT ${TAB_COLUMNS} FROM tabs WHERE ${where.join(" AND ")} ORDER BY address, round, seat_index, line`).all(...args);
2455
+ const sheets = raw.length === 0 ? new Map : await this.sheets(db, [...new Set(raw.map((r) => r.competition))]);
2456
+ return {
2457
+ tag: this.rel.tag,
2458
+ asked: q,
2459
+ rows: raw.map((r) => ({ ...r, ...sheets.get(r.competition) ?? NO_SHEET }))
2460
+ };
2461
+ }
2462
+ async* stream(q = {}) {
2463
+ const db = await this.db();
2464
+ const sheets = await this.sheets(db, q.competition ?? null);
2465
+ const events = q.event === undefined ? null : new Set((typeof q.event === "string" ? [q.event] : q.event).map((e) => e.toLowerCase()));
2466
+ const tiers = q.tier === undefined ? null : new Set(q.tier);
2467
+ const holds = (s) => {
2468
+ if (events !== null && (s.event === null || !events.has(s.event.toLowerCase())))
2469
+ return false;
2470
+ if (tiers !== null && (s.tier === null || !tiers.has(s.tier)))
2471
+ return false;
2472
+ if (typeof q.year === "number" && s.year !== q.year)
2473
+ return false;
2474
+ if (Array.isArray(q.year) && (s.year === null || s.year < q.year[0] || s.year > q.year[1]))
2475
+ return false;
2476
+ return true;
2477
+ };
2478
+ const filtered = events !== null || tiers !== null || q.year !== undefined;
2479
+ const run = function* (stmt, args) {
2480
+ for (const r of stmt.iterate(...args))
2481
+ yield { ...r, ...sheets.get(r.competition) ?? NO_SHEET };
2482
+ };
2483
+ if (!filtered && q.competition === undefined) {
2484
+ const all = db.prepare(`SELECT ${TAB_COLUMNS} FROM tabs ORDER BY address, round, seat_index, line`);
2485
+ try {
2486
+ yield* run(all, []);
2487
+ } finally {
2488
+ all.finalize();
2489
+ }
2490
+ return;
2491
+ }
2492
+ const asked = [...new Set(q.competition ?? sheets.keys())].filter((c) => holds(sheets.get(c) ?? NO_SHEET));
2493
+ const one = db.prepare(`SELECT ${TAB_COLUMNS} FROM tabs WHERE address >= ? AND address < ? AND slug = ? ORDER BY address, round, seat_index, line`);
2494
+ try {
2495
+ for (const c of asked.sort((a, b) => a < b ? -1 : a > b ? 1 : 0)) {
2496
+ const [lo, hi] = cardRange(c);
2497
+ yield* run(one, [lo, hi, c]);
2498
+ }
2499
+ } finally {
2500
+ one.finalize();
2501
+ }
2502
+ }
2503
+ }
2504
+
2505
+ class PanelFinder2 {
2506
+ rel;
2507
+ constructor(rel) {
2508
+ this.rel = rel;
2509
+ }
2510
+ async query(q = {}) {
2511
+ await this.rel.fetch("results");
2512
+ await this.rel.require("panels");
2513
+ const where = q.competition === undefined ? "" : " WHERE slug = ?";
2514
+ const seats = this.rel.db("results").query(`SELECT slug AS competition, panel, round, seat_index, address, judge_name_printed,
2515
+ judge_clrg, judge_id, judge_unmatched
2516
+ FROM panels${where} ORDER BY slug, panel, round, seat_index`).all(...q.competition === undefined ? [] : [q.competition]);
2517
+ return { tag: this.rel.tag, asked: q, seats };
2518
+ }
2519
+ }
2520
+
2521
+ class FeisPanelFinder2 {
2522
+ rel;
2523
+ constructor(rel) {
2524
+ this.rel = rel;
2525
+ }
2526
+ async query(q = {}) {
2527
+ await this.rel.fetch("results");
2528
+ await this.rel.require("feis-panels");
2529
+ const where = [];
2530
+ const args = [];
2531
+ if (q.address !== undefined) {
2532
+ where.push("address = ?");
2533
+ args.push(q.address);
2534
+ }
2535
+ if (q.platform !== undefined) {
2536
+ where.push("platform = ?");
2537
+ args.push(q.platform);
2538
+ }
2539
+ const seats = this.rel.db("results").query(`SELECT address, platform, round, seat_index, panel_size, panel_kind, judge_name_printed,
2540
+ judge_state_printed, judge_clrg, judge_id, judge_unmatched, judge_basis
2541
+ FROM feis_panels${where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`}
2542
+ ORDER BY address, round, seat_index`).all(...args);
2543
+ return { tag: this.rel.tag, asked: q, seats };
2544
+ }
2545
+ }
2546
+ async function registerRows(rel, q) {
2547
+ await rel.fetch("results");
2548
+ await rel.require("staff.adjudicators");
2549
+ const where = [];
2550
+ const args = [];
2551
+ if (q.judge_clrg !== undefined) {
2552
+ where.push("judge_clrg = ?");
2553
+ args.push(q.judge_clrg);
2554
+ }
2555
+ if (q.year !== undefined) {
2556
+ where.push("year = ?");
2557
+ args.push(q.year);
2558
+ }
2559
+ const rows = rel.db("results").query(`SELECT judge_clrg, year, cat, name_printed, country, country_printed, country_code, region,
2560
+ region_printed, region_code, geohash, src
2561
+ FROM adjudicators${where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`}
2562
+ ORDER BY judge_clrg, year`).all(...args);
2563
+ return { tag: rel.tag, asked: q, rows };
2564
+ }
2565
+
2249
2566
  var ENCODER2 = roleEncoder();
2250
2567
  var WHO = { table: "who", value: "entry", valueType: "INTEGER PRIMARY KEY" };
2251
2568
  var ASKED2 = ["surname_key", "nn4", "id"];
@@ -2363,6 +2680,9 @@ class StaffFinder2 {
2363
2680
  adjudicator(asked) {
2364
2681
  return this.find(asked, "adjudicator");
2365
2682
  }
2683
+ adjudicators(q = {}) {
2684
+ return registerRows(this.rel, q);
2685
+ }
2366
2686
  async find(asked, role) {
2367
2687
  const db = await this.rel.indexDb("staff");
2368
2688
  const a = asked.trim();
@@ -2507,8 +2827,8 @@ async function parquetColumns(path) {
2507
2827
  return new Set(parquetSchema(meta).children.map((c) => c.element.name));
2508
2828
  }
2509
2829
 
2510
- var str = (v) => typeof v === "string" && v !== "" ? v : null;
2511
- var num = (v) => typeof v === "number" ? v : null;
2830
+ var str2 = (v) => typeof v === "string" && v !== "" ? v : null;
2831
+ var num2 = (v) => typeof v === "number" ? v : null;
2512
2832
  var strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
2513
2833
  var scope = (id) => id === null ? null : id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable";
2514
2834
  var TEAM_TABLES = [
@@ -2563,9 +2883,9 @@ var teamsIndex = {
2563
2883
  const ey = new Map;
2564
2884
  const putEy = out.prepare("INSERT OR IGNORE INTO event_year VALUES (?, ?, ?)");
2565
2885
  for (const r of t.event_years) {
2566
- const k = str(r.event_year);
2567
- const event = str(r.event);
2568
- const year = num(r.year);
2886
+ const k = str2(r.event_year);
2887
+ const event = str2(r.event);
2888
+ const year = num2(r.year);
2569
2889
  if (k === null || event === null || year === null)
2570
2890
  continue;
2571
2891
  ey.set(k, { event, year });
@@ -2574,19 +2894,19 @@ var teamsIndex = {
2574
2894
  const putR = out.prepare("INSERT OR IGNORE INTO result VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
2575
2895
  let ord = 0;
2576
2896
  for (const r of t.team_results)
2577
- putR.run(str(r.address), str(r.slug), str(r.event), num(r.year), str(r.card), str(r.comp_no), str(r.title), str(r.category), num(r.hands), str(r.letter), str(r.label), str(r.name_printed), str(r.school_printed), str(r.school_loc), str(r.school_id), num(r.place), str(r.place_printed), num(r.tie), num(r.competitors), JSON.stringify(strs(r.atts)), str(r.age), str(r.gender), JSON.stringify(strs(r.printed_status)), ord++);
2897
+ putR.run(str2(r.address), str2(r.slug), str2(r.event), num2(r.year), str2(r.card), str2(r.comp_no), str2(r.title), str2(r.category), num2(r.hands), str2(r.letter), str2(r.label), str2(r.name_printed), str2(r.school_printed), str2(r.school_loc), str2(r.school_id), num2(r.place), str2(r.place_printed), num2(r.tie), num2(r.competitors), JSON.stringify(strs(r.atts)), str2(r.age), str2(r.gender), JSON.stringify(strs(r.printed_status)), ord++);
2578
2898
  const putT = out.prepare("INSERT OR IGNORE INTO roster_team VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
2579
2899
  for (const r of t.roster_teams) {
2580
- const e = ey.get(str(r.event_year) ?? "");
2581
- putT.run(str(r.team), str(r.kind), e?.event ?? null, num(r.year) ?? e?.year ?? null, str(r.comp_no), str(r.title), str(r.team_index), num(r.seq), str(r.school_id), str(r.school_print_materialized), str(r.dance), str(r.school_print_as_of), ord++);
2900
+ const e = ey.get(str2(r.event_year) ?? "");
2901
+ putT.run(str2(r.team), str2(r.kind), e?.event ?? null, num2(r.year) ?? e?.year ?? null, str2(r.comp_no), str2(r.title), str2(r.team_index), num2(r.seq), str2(r.school_id), str2(r.school_print_materialized), str2(r.dance), str2(r.school_print_as_of), ord++);
2582
2902
  }
2583
2903
  const putM = out.prepare("INSERT INTO roster_member VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
2584
2904
  for (const r of t.roster_members)
2585
- putM.run(str(r.team), num(r.seq), typeof r.name_printed === "string" ? r.name_printed : "", str(r.name), num(r.yob), num(r.dancer_id), str(r.gender), ord++);
2905
+ putM.run(str2(r.team), num2(r.seq), typeof r.name_printed === "string" ? r.name_printed : "", str2(r.name), num2(r.yob), num2(r.dancer_id), str2(r.gender), ord++);
2586
2906
  const putJ = out.prepare("INSERT INTO result_roster VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
2587
2907
  for (const r of t.team_result_rosters) {
2588
- const e = ey.get(str(r.event_year) ?? "");
2589
- putJ.run(str(r.team), e?.event ?? null, e?.year ?? null, str(r.comp_no), str(r.school_id), str(r.result_address), str(r.join_kind), num(r.via_split) === 1 ? 1 : 0, num(r.roster_teams), num(r.result_teams), str(r.roster_index), str(r.result_letter), ord++);
2908
+ const e = ey.get(str2(r.event_year) ?? "");
2909
+ putJ.run(str2(r.team), e?.event ?? null, e?.year ?? null, str2(r.comp_no), str2(r.school_id), str2(r.result_address), str2(r.join_kind), num2(r.via_split) === 1 ? 1 : 0, num2(r.roster_teams), num2(r.result_teams), str2(r.roster_index), str2(r.result_letter), ord++);
2590
2910
  }
2591
2911
  for (const s of TEAMS_INDEXES)
2592
2912
  out.run(s);
@@ -2606,11 +2926,11 @@ var eventsIndex = {
2606
2926
  out.run(EVENTS_SCHEMA);
2607
2927
  const put = out.prepare("INSERT OR IGNORE INTO event_year VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
2608
2928
  for (const r of rows) {
2609
- const event = str(r.event);
2610
- const year = num(r.year);
2929
+ const event = str2(r.event);
2930
+ const year = num2(r.year);
2611
2931
  if (event === null || year === null)
2612
2932
  continue;
2613
- put.run(event, year, str(r.date_lo), str(r.date_hi), str(r.resolution), str(r.source), JSON.stringify(strs(r.date_printed)), str(r.place), JSON.stringify(strs(r.place_printed)));
2933
+ put.run(event, year, str2(r.date_lo), str2(r.date_hi), str2(r.resolution), str2(r.source), JSON.stringify(strs(r.date_printed)), str2(r.place), JSON.stringify(strs(r.place_printed)));
2614
2934
  }
2615
2935
  out.run("CREATE INDEX event_year_event ON event_year(event COLLATE NOCASE)");
2616
2936
  }
@@ -3238,7 +3558,7 @@ var FEATURES2 = [
3238
3558
  feature: "schools.lineage",
3239
3559
  needs: ["school-lineage.jsonl"],
3240
3560
  since: 184,
3241
- calls: "schools.links and schools.line (refused on a release without it), and the `lineage` school evidence of dancers.resolve and dancers.search: no school is joined by lineage, never refused, on a release without it"
3561
+ calls: "schools.links, schools.line and schools.lineage (refused on a release without it), and the `lineage` school evidence of dancers.resolve and dancers.search: no school is joined by lineage, never refused, on a release without it"
3242
3562
  },
3243
3563
  {
3244
3564
  feature: "results.stream",
@@ -3407,6 +3727,91 @@ var FEATURES2 = [
3407
3727
  ],
3408
3728
  since: 260,
3409
3729
  calls: "staff.teacher, staff.adjudicator (the staff index)"
3730
+ },
3731
+ { feature: "tabs", needs: TAB_NEEDS, since: 184, calls: "tabs.query, tabs.stream" },
3732
+ {
3733
+ feature: "panels",
3734
+ needs: [
3735
+ "slug",
3736
+ "panel",
3737
+ "round",
3738
+ "seat_index",
3739
+ "address",
3740
+ "judge_name_printed",
3741
+ "judge_clrg",
3742
+ "judge_id",
3743
+ "judge_unmatched"
3744
+ ].map((c) => `results:panels.${c}`),
3745
+ since: 184,
3746
+ calls: "panels.query"
3747
+ },
3748
+ {
3749
+ feature: "feis-panels",
3750
+ needs: [
3751
+ "address",
3752
+ "platform",
3753
+ "round",
3754
+ "seat_index",
3755
+ "panel_size",
3756
+ "panel_kind",
3757
+ "judge_name_printed",
3758
+ "judge_state_printed",
3759
+ "judge_clrg",
3760
+ "judge_id",
3761
+ "judge_unmatched",
3762
+ "judge_basis"
3763
+ ].map((c) => `results:feis_panels.${c}`),
3764
+ since: 260,
3765
+ calls: "feisPanels.query"
3766
+ },
3767
+ {
3768
+ feature: "staff.adjudicators",
3769
+ needs: [
3770
+ "judge_clrg",
3771
+ "year",
3772
+ "cat",
3773
+ "name_printed",
3774
+ "country",
3775
+ "country_printed",
3776
+ "country_code",
3777
+ "region",
3778
+ "region_printed",
3779
+ "region_code",
3780
+ "geohash",
3781
+ "src"
3782
+ ].map((c) => `results:adjudicators.${c}`),
3783
+ since: 230,
3784
+ calls: "staff.adjudicators"
3785
+ },
3786
+ {
3787
+ feature: "schools.registry",
3788
+ needs: ["school-registry.jsonl"],
3789
+ since: 260,
3790
+ calls: "schools.list"
3791
+ },
3792
+ {
3793
+ feature: "schools.prints",
3794
+ needs: [
3795
+ "results:results.school_printed",
3796
+ "results:results.school_id",
3797
+ "results:results.competition_line",
3798
+ "results:competitions.event",
3799
+ "results:competitions.year"
3800
+ ],
3801
+ since: 184,
3802
+ calls: "schools.prints"
3803
+ },
3804
+ {
3805
+ feature: "schools.redirects",
3806
+ needs: ["school-redirects.jsonl"],
3807
+ since: 184,
3808
+ calls: "schools.redirects"
3809
+ },
3810
+ {
3811
+ feature: "schools.associations",
3812
+ needs: ["observed-associations.jsonl"],
3813
+ since: 215,
3814
+ calls: "schools.associations"
3410
3815
  }
3411
3816
  ];
3412
3817
  async function hasNeed(probe, n) {
@@ -3490,6 +3895,9 @@ class Release2 {
3490
3895
  registrations;
3491
3896
  teams;
3492
3897
  events;
3898
+ tabs;
3899
+ panels;
3900
+ feisPanels;
3493
3901
  opts;
3494
3902
  handles = new Map;
3495
3903
  remoteAssets = null;
@@ -3512,6 +3920,9 @@ class Release2 {
3512
3920
  this.registrations = new RegistrationResolver2(this, this.comps, opts.dancerResolver);
3513
3921
  this.teams = new TeamFinder2(this);
3514
3922
  this.events = new EventFinder2(this);
3923
+ this.tabs = new TabFinder2(this);
3924
+ this.panels = new PanelFinder2(this);
3925
+ this.feisPanels = new FeisPanelFinder2(this);
3515
3926
  }
3516
3927
  receipt() {
3517
3928
  return readReceipt(this.dir) ?? { tag: this.tag, source: "unknown", files: {} };
@@ -4232,4 +4643,4 @@ function staleWarning2(answered, latest, floor, tags = [], command = "idq") {
4232
4643
  return `answered from ${answered}. ${floorTag ?? `cut ${floor.cut}`} is required${why}: run \`${command} use ${floor.cut}\` to get it`;
4233
4644
  }
4234
4645
 
4235
- export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, ADVANCEMENT_DANCES2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, LINEAGE_FILE2, linksOfLineage2, releaseSchoolGraph2, readsSpanishNames2, SchoolResolver2, initialsOnly2, nameOrderOf, DancerResolver2, readPopulation2, syllabusSourceOf2, canonicalOf2, CompResolver2, StaffFinder2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, defaultRemote, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, resolveTag2, Release2, openRelease2, openCached2, DEFAULT_KEEP2, ReleaseManager2, releases2, LOCAL_DIR2, localRoot2, newerThan2, incomplete2, releaseVersion2, pickBuild2, buildTag2, lacks, openBuild2, sweepBuilds2, LATEST_CHECK_MS2, LATEST_CHECK_BUDGET_MS2, latestCheckPath2, readLatestCheck2, latestCheck2, within, staleWarning2 };
4646
+ export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, ADVANCEMENT_DANCES2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, LINEAGE_FILE2, linksOfLineage2, releaseSchoolGraph2, readsSpanishNames2, SchoolResolver2, initialsOnly2, nameOrderOf, DancerResolver2, readPopulation2, syllabusSourceOf2, canonicalOf2, CompResolver2, TabFinder2, PanelFinder2, FeisPanelFinder2, StaffFinder2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, defaultRemote, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, resolveTag2, Release2, openRelease2, openCached2, DEFAULT_KEEP2, ReleaseManager2, releases2, LOCAL_DIR2, localRoot2, newerThan2, incomplete2, releaseVersion2, pickBuild2, buildTag2, lacks, openBuild2, sweepBuilds2, LATEST_CHECK_MS2, LATEST_CHECK_BUDGET_MS2, latestCheckPath2, readLatestCheck2, latestCheck2, within, staleWarning2 };
package/dist/cli.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  latestCheck2,
22
22
  within,
23
23
  staleWarning2
24
- } from "./chunks/index-vsk6837c.js";
24
+ } from "./chunks/index-2ms980hc.js";
25
25
  import"./chunks/index-hghdy980.js";
26
26
 
27
27
  var USAGE_HEADER = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json] [--live]";
@@ -285,6 +285,61 @@ var VERBS = [
285
285
  " or later (default 3)"
286
286
  ]
287
287
  },
288
+ {
289
+ verb: "tabs",
290
+ value: ["--release", "--comp", "--address", "--round", "--event", "--year", "--tier"],
291
+ bool: ["--stream"],
292
+ usage: [
293
+ " tabs [--release <spec>] --comp <competition> | --address <address> [--round <r>]",
294
+ " a marks sheet (or one card of it): every seat's mark,",
295
+ " place and points, with the judge",
296
+ " tabs --stream [--release <spec>] [--comp <c,...>] [--event <e,...>]",
297
+ " [--year <y> | --year <from>..<to>] [--tier <t,...>]",
298
+ " every cell of every sheet named, one JSON line each"
299
+ ]
300
+ },
301
+ {
302
+ verb: "panels",
303
+ value: ["--release", "--comp"],
304
+ bool: [],
305
+ usage: [
306
+ " panels [--release <spec>] [--comp <competition>]",
307
+ " who sat a major's competition (every one's, with no --comp)"
308
+ ]
309
+ },
310
+ {
311
+ verb: "feis-panels",
312
+ value: ["--release", "--address", "--platform"],
313
+ bool: [],
314
+ usage: [
315
+ " feis-panels [--release <spec>] [--address <feis competition>] [--platform <p>]",
316
+ " who sat a feis's competition (fweb:ABC24.105)"
317
+ ]
318
+ },
319
+ {
320
+ verb: "register",
321
+ value: ["--release", "--year", "--td"],
322
+ bool: [],
323
+ usage: [
324
+ " register [--release <spec>] [--year <y>] [--td <TD number>]",
325
+ " the adjudicator register's rows: category, country,",
326
+ " region, geohash, per year"
327
+ ]
328
+ },
329
+ {
330
+ verb: "schools",
331
+ value: ["--release", "--event", "--year", "--school"],
332
+ bool: ["--prints", "--redirects", "--lineage", "--associations"],
333
+ usage: [
334
+ " schools [--release <spec>] every school the registry lists: display, regions, places",
335
+ " schools --prints [--release <spec>] [--event <e>] [--year <y>]",
336
+ " the (print, school_id) pairs the rows attach, per event-year",
337
+ " schools --redirects | --lineage [--release <spec>]",
338
+ " the retired ids; the lineage edges",
339
+ " schools --associations [--release <spec>] [--school <school_id>]",
340
+ " the people a directory lists beside a school"
341
+ ]
342
+ },
288
343
  {
289
344
  verb: "teams",
290
345
  value: ["--release", "--event", "--year"],
@@ -662,6 +717,27 @@ function streamQueryOf(p) {
662
717
  q.primary = true;
663
718
  return q;
664
719
  }
720
+ function tabStreamQueryOf(p) {
721
+ const q = {};
722
+ const comp = p.flags.get("--comp");
723
+ if (comp !== undefined)
724
+ q.competition = listOf(comp);
725
+ const event = p.flags.get("--event");
726
+ if (event !== undefined)
727
+ q.event = listOf(event);
728
+ const tier = p.flags.get("--tier");
729
+ if (tier !== undefined)
730
+ q.tier = listOf(tier);
731
+ const year = p.flags.get("--year");
732
+ if (year !== undefined) {
733
+ const m = /^(\d{4})\.\.(\d{4})$/.exec(year);
734
+ q.year = m === null ? Number(year) : [Number(m[1]), Number(m[2])];
735
+ if (typeof q.year === "number" && !Number.isInteger(q.year))
736
+ throw new Error(`--year takes a year or <from>..<to>, not "${year}"`);
737
+ }
738
+ return q;
739
+ }
740
+ var tabLine = (t) => ` ${t.address} ${t.round} seat ${t.seat_index}/${t.panel_size} ${t.judge_clrg ?? t.judge_id ?? "-"} ${t.judge_name_printed ?? "-"} mark ${t.mark_printed ?? "-"} place ${t.place_printed ?? "-"}${t.tie === 1 ? "=" : ""} points ${t.points_printed ?? "-"}`;
665
741
  function eventYearOf(p) {
666
742
  const event = p.flags.get("--event");
667
743
  const year = p.flags.get("--year");
@@ -683,6 +759,11 @@ var ANSWERING = new Set([
683
759
  "records",
684
760
  "feis-dates",
685
761
  "tabulated",
762
+ "tabs",
763
+ "panels",
764
+ "feis-panels",
765
+ "register",
766
+ "schools",
686
767
  "teams",
687
768
  "events",
688
769
  "teacher",
@@ -1163,6 +1244,145 @@ async function main(argv, io = {}) {
1163
1244
  rel.close();
1164
1245
  }
1165
1246
  }
1247
+ case "tabs": {
1248
+ const stream = p.bools.has("--stream");
1249
+ const tq = stream ? tabStreamQueryOf(p) : null;
1250
+ const comp = p.flags.get("--comp");
1251
+ const address = p.flags.get("--address");
1252
+ if (!stream && comp === undefined && address === undefined) {
1253
+ log("tabs --comp <competition> | --address <address> [--round <r>], or tabs --stream");
1254
+ return 2;
1255
+ }
1256
+ const rel = await open(spec, { ...opts, assets: [] });
1257
+ try {
1258
+ if (tq !== null) {
1259
+ let n = 0;
1260
+ for await (const t of rel.tabs.stream(tq)) {
1261
+ say(JSON.stringify(t));
1262
+ n++;
1263
+ }
1264
+ log(`${n} cell(s) in ${rel.tag}`);
1265
+ return n === 0 ? 1 : 0;
1266
+ }
1267
+ const round = p.flags.get("--round");
1268
+ const a = await rel.tabs.query({
1269
+ ...address !== undefined ? { address } : { competition: comp },
1270
+ ...round !== undefined ? { round } : {}
1271
+ });
1272
+ out(a, () => {
1273
+ for (const t of a.rows)
1274
+ say(tabLine(t));
1275
+ say(`${a.rows.length} cell(s) in ${a.tag}`);
1276
+ });
1277
+ return a.rows.length === 0 ? 1 : 0;
1278
+ } finally {
1279
+ rel.close();
1280
+ }
1281
+ }
1282
+ case "panels": {
1283
+ const rel = await open(spec, { ...opts, assets: [] });
1284
+ try {
1285
+ const comp = p.flags.get("--comp");
1286
+ const a = await rel.panels.query(comp !== undefined ? { competition: comp } : {});
1287
+ out(a, () => {
1288
+ for (const s of a.seats)
1289
+ say(` ${s.competition} ${s.panel} ${s.round} seat ${s.seat_index} ${s.judge_clrg ?? s.judge_id ?? "-"} ${s.judge_name_printed ?? "-"}${s.judge_unmatched === null ? "" : ` (${s.judge_unmatched})`}`);
1290
+ say(`${a.seats.length} seat(s) in ${a.tag}`);
1291
+ });
1292
+ return a.seats.length === 0 ? 1 : 0;
1293
+ } finally {
1294
+ rel.close();
1295
+ }
1296
+ }
1297
+ case "feis-panels": {
1298
+ const rel = await open(spec, { ...opts, assets: [] });
1299
+ try {
1300
+ const address = p.flags.get("--address");
1301
+ const platform = p.flags.get("--platform");
1302
+ const a = await rel.feisPanels.query({
1303
+ ...address !== undefined ? { address } : {},
1304
+ ...platform !== undefined ? { platform } : {}
1305
+ });
1306
+ out(a, () => {
1307
+ for (const s of a.seats)
1308
+ say(` ${s.address} ${s.round} seat ${s.seat_index}/${s.panel_size} ${s.judge_clrg ?? s.judge_id ?? "-"} ${s.judge_name_printed}${s.judge_basis === null ? "" : ` (${s.judge_basis})`}`);
1309
+ say(`${a.seats.length} seat(s) in ${a.tag}`);
1310
+ });
1311
+ return a.seats.length === 0 ? 1 : 0;
1312
+ } finally {
1313
+ rel.close();
1314
+ }
1315
+ }
1316
+ case "register": {
1317
+ const rel = await open(spec, { ...opts, assets: [] });
1318
+ try {
1319
+ const year = p.flags.get("--year");
1320
+ const td = p.flags.get("--td");
1321
+ const a = await rel.staff.adjudicators({
1322
+ ...year !== undefined ? { year: Number(year) } : {},
1323
+ ...td !== undefined ? { judge_clrg: Number(td) } : {}
1324
+ });
1325
+ out(a, () => {
1326
+ for (const r of a.rows)
1327
+ say(` ${r.judge_clrg} ${r.year} ${r.cat} ${r.name_printed} ${r.country_code ?? r.country} ${r.region_code ?? r.region} ${r.geohash ?? "-"}`);
1328
+ say(`${a.rows.length} register row(s) in ${a.tag}`);
1329
+ });
1330
+ return a.rows.length === 0 ? 1 : 0;
1331
+ } finally {
1332
+ rel.close();
1333
+ }
1334
+ }
1335
+ case "schools": {
1336
+ const rel = await open(spec, { ...opts, assets: [] });
1337
+ try {
1338
+ if (p.bools.has("--prints")) {
1339
+ const a = await rel.schools.prints(eventYearOf(p));
1340
+ out(a, () => {
1341
+ for (const x of a.prints)
1342
+ say(` ${x.year ?? "?"} ${x.event} ${x.school_id} "${x.print}" ${x.rows} rows`);
1343
+ say(`${a.prints.length} pair(s) in ${a.tag}`);
1344
+ });
1345
+ return a.prints.length === 0 ? 1 : 0;
1346
+ }
1347
+ if (p.bools.has("--redirects")) {
1348
+ const a = await rel.schools.redirects();
1349
+ out(a, () => {
1350
+ for (const r of a.redirects)
1351
+ say(` ${r.old_id} -> ${r.new_id} ${r.kind}${r.since === null ? "" : ` since ${r.since}`}`);
1352
+ say(`${a.redirects.length} redirect(s) in ${a.tag}`);
1353
+ });
1354
+ return a.redirects.length === 0 ? 1 : 0;
1355
+ }
1356
+ if (p.bools.has("--lineage")) {
1357
+ const a = await rel.schools.lineage();
1358
+ out(a, () => {
1359
+ for (const e of a.edges)
1360
+ say(` ${e.id} ${e.direction} ${e.other} ${e.shape} ${e.year ?? "?"}${e.provisional ? " provisional" : ""}`);
1361
+ say(`${a.edges.length} edge end(s) in ${a.tag}`);
1362
+ });
1363
+ return a.edges.length === 0 ? 1 : 0;
1364
+ }
1365
+ if (p.bools.has("--associations")) {
1366
+ const school = p.flags.get("--school");
1367
+ const a = await rel.schools.associations(school !== undefined ? { school_id: school } : {});
1368
+ out(a, () => {
1369
+ for (const x of a.associations)
1370
+ say(` ${x.school_id ?? "(no id)"} "${x.school_printed ?? ""}" ${x.person_printed} (${x.person_type ?? "-"}${x.credentials === null ? "" : `, ${x.credentials}`}) ${x.date ?? "-"} ${x.src_type ?? ""}`);
1371
+ say(`${a.associations.length} association(s) in ${a.tag}`);
1372
+ });
1373
+ return a.associations.length === 0 ? 1 : 0;
1374
+ }
1375
+ const a = await rel.schools.list();
1376
+ out(a, () => {
1377
+ for (const s of a.schools)
1378
+ say(` ${s.school_id} ${s.display} ${s.regions.join(",") || "-"} ${s.places.join(",") || "-"}`);
1379
+ say(`${a.schools.length} school(s) in ${a.tag}`);
1380
+ });
1381
+ return a.schools.length === 0 ? 1 : 0;
1382
+ } finally {
1383
+ rel.close();
1384
+ }
1385
+ }
1166
1386
  case "teams": {
1167
1387
  const rel = await open(spec, { ...opts, assets: [] });
1168
1388
  try {
@@ -1320,5 +1540,6 @@ export {
1320
1540
  parseArgs,
1321
1541
  renderUsage,
1322
1542
  streamQueryOf,
1543
+ tabStreamQueryOf,
1323
1544
  valueFlags
1324
1545
  };
package/dist/index.js CHANGED
@@ -24,6 +24,9 @@ import {
24
24
  syllabusSourceOf2,
25
25
  canonicalOf2,
26
26
  CompResolver2,
27
+ TabFinder2,
28
+ PanelFinder2,
29
+ FeisPanelFinder2,
27
30
  StaffFinder2,
28
31
  TeamFinder2,
29
32
  EventFinder2,
@@ -68,7 +71,7 @@ import {
68
71
  readLatestCheck2,
69
72
  latestCheck2,
70
73
  staleWarning2
71
- } from "./chunks/index-vsk6837c.js";
74
+ } from "./chunks/index-2ms980hc.js";
72
75
  import {
73
76
  cacheRoot2,
74
77
  repoOf2
@@ -93,6 +96,7 @@ export {
93
96
  FEATURES2 as FEATURES,
94
97
  FEIS_LOCAL_ID_FLOOR2 as FEIS_LOCAL_ID_FLOOR,
95
98
  FeisFinder2 as FeisFinder,
99
+ FeisPanelFinder2 as FeisPanelFinder,
96
100
  LATEST_CHECK_BUDGET_MS2 as LATEST_CHECK_BUDGET_MS,
97
101
  LATEST_CHECK_MS2 as LATEST_CHECK_MS,
98
102
  LINEAGE_FILE2 as LINEAGE_FILE,
@@ -100,12 +104,14 @@ export {
100
104
  MajorsFinder2 as MajorsFinder,
101
105
  NAME_LEVELS2 as NAME_LEVELS,
102
106
  NO_CALENDAR2 as NO_CALENDAR,
107
+ PanelFinder2 as PanelFinder,
103
108
  RegistrationResolver2 as RegistrationResolver,
104
109
  Release2 as Release,
105
110
  ReleaseManager2 as ReleaseManager,
106
111
  ResultFinder2 as ResultFinder,
107
112
  SchoolResolver2 as SchoolResolver,
108
113
  StaffFinder2 as StaffFinder,
114
+ TabFinder2 as TabFinder,
109
115
  TeamFinder2 as TeamFinder,
110
116
  buildIndex2 as buildIndex,
111
117
  buildTag2 as buildTag,
@@ -3,6 +3,7 @@ import { type FreshnessFloor } from "./freshness.js";
3
3
  import { type LocalBuild } from "./local.js";
4
4
  import type { OpenOptions } from "./release.js";
5
5
  import type { StreamQuery } from "./stream.js";
6
+ import type { TabStreamQuery } from "./tabs.js";
6
7
  export { GLOBAL_FLAGS, USAGE_FOOTER, USAGE_HEADER, VERBS, type VerbSpec, renderUsage, valueFlags, } from "./cli-spec.js";
7
8
  export declare const USAGE: string;
8
9
  export interface Parsed {
@@ -20,6 +21,7 @@ type Line<T> = {
20
21
  };
21
22
  export declare function jsonLines<T>(text: string, check: (v: T) => string | null): Line<T>[];
22
23
  export declare function streamQueryOf(p: Parsed): StreamQuery;
24
+ export declare function tabStreamQueryOf(p: Parsed): TabStreamQuery;
23
25
  export interface LocalIO {
24
26
  builds: () => readonly LocalBuild[];
25
27
  }
@@ -16,10 +16,12 @@ export { type Remote, type RemoteAsset, fallbackRemote, ghRemote, httpsRemote, s
16
16
  export { type OpenOptions, Release, openCached, openRelease, resolveTag, } from "./release.js";
17
17
  export { type MajorCompetition, type MajorEvent, MajorsFinder, type ResultAnswer, ResultFinder, type ResultQuery, type ResultRow, } from "./results.js";
18
18
  export { type DancerAnswerLike, type DancerQueryLike, type DancerResolverLike, type DancerUnavailable, type RegistrationRow, type ResolvedDancer, RegistrationResolver, type ResolvedRegistration, dancerResolverOf, youngest, } from "./registrations.js";
19
+ export type { LineageEdge, ObservedAssociation, RegistrySchool, SchoolPrint, SchoolRedirect, } from "./school-bulk.js";
19
20
  export { type SchoolAnswer, type SchoolCandidate, type SchoolQuery, SchoolResolver, type SchoolRung, readsSpanishNames, type SpanishNames, } from "./schools.js";
20
21
  export { LINEAGE_FILE, type SchoolLineAnswer, type SchoolLinksAnswer, linksOfLineage, releaseSchoolGraph, } from "./school-links.js";
21
22
  export type { LineCandidate, LinkPath, LinkPolicy, LinkQuery, LinkStep, PathFlags, SchoolLink, StepFlags, StepPolicy, } from "@finbheara/domain/school-graph";
22
23
  export { type Capability, FEATURES, type Feature, type SchemaProbe, capabilityOf, columnsOf, needsRelease, } from "./schema.js";
23
24
  export { type EntryRow, type StreamQuery, type StreamRow, type StreamScope, entryRank, } from "./stream.js";
25
+ export { type FeisPanelSeat, FeisPanelFinder, PanelFinder, type PanelSeat, type RegisterRow, type TabAnswer, TabFinder, type TabQuery, type TabRow, type TabStreamQuery, } from "./tabs.js";
24
26
  export { EventFinder, type EventYear, type EventYearQuery, type ResultRoster, type RosterMember, type RosterTeam, TeamFinder, type TeamResult, } from "./teams.js";
25
27
  export { ARCHIVE_TAG, type ArchiveTag, type ReleaseSpec, compareTagsNewestFirst, cutOf, parseArchiveTag, parseReleaseSpec, pickTag, renderArchiveTag, } from "./tag.js";
@@ -2,6 +2,7 @@ import { type StaffRole } from "@finbheara/domain/noun-index";
2
2
  import { type NameLevel } from "./dancers-names.js";
3
3
  import type { IndexBuilder } from "./indexes.js";
4
4
  import type { Release } from "./release.js";
5
+ import { type RegisterRow } from "./tabs.js";
5
6
  export interface Taught {
6
7
  school_id: string;
7
8
  school: string | null;
@@ -59,6 +60,17 @@ export declare class StaffFinder {
59
60
  constructor(rel: Release);
60
61
  teacher(asked: string): Promise<StaffAnswer>;
61
62
  adjudicator(asked: string): Promise<StaffAnswer>;
63
+ adjudicators(q?: {
64
+ year?: number;
65
+ judge_clrg?: number;
66
+ }): Promise<{
67
+ tag: string;
68
+ asked: {
69
+ year?: number;
70
+ judge_clrg?: number;
71
+ };
72
+ rows: RegisterRow[];
73
+ }>;
62
74
  private find;
63
75
  private person;
64
76
  private taughtWhere;
@@ -11,6 +11,7 @@ import { type Remote } from "./remote.js";
11
11
  import { MajorsFinder, ResultFinder } from "./results.js";
12
12
  import { type Capability } from "./schema.js";
13
13
  import { type SchoolReaderChoice, SchoolResolver } from "./schools.js";
14
+ import { FeisPanelFinder, PanelFinder, TabFinder } from "./tabs.js";
14
15
  import { type ReleaseSpec } from "./tag.js";
15
16
  import { EventFinder, TeamFinder } from "./teams.js";
16
17
  export interface OpenOptions extends ClientOptions {
@@ -44,6 +45,9 @@ export declare class Release {
44
45
  readonly registrations: RegistrationResolver;
45
46
  readonly teams: TeamFinder;
46
47
  readonly events: EventFinder;
48
+ readonly tabs: TabFinder;
49
+ readonly panels: PanelFinder;
50
+ readonly feisPanels: FeisPanelFinder;
47
51
  private readonly opts;
48
52
  private readonly handles;
49
53
  private remoteAssets;
@@ -0,0 +1,80 @@
1
+ import type { Release } from "./release.js";
2
+ export interface RegistrySchool {
3
+ school_id: string;
4
+ display: string;
5
+ regions: string[];
6
+ places: string[];
7
+ }
8
+ export interface SchoolPrint {
9
+ event: string;
10
+ year: number | null;
11
+ print: string;
12
+ school_id: string;
13
+ rows: number;
14
+ }
15
+ export interface SchoolRedirect {
16
+ old_id: string;
17
+ new_id: string;
18
+ kind: string;
19
+ since: number | null;
20
+ }
21
+ export interface LineageEdge {
22
+ id: string;
23
+ direction: string;
24
+ other: string;
25
+ shape: string;
26
+ year: number | null;
27
+ provisional: boolean;
28
+ decision: string | null;
29
+ confidence: number | null;
30
+ }
31
+ export interface ObservedAssociation {
32
+ at: string;
33
+ seq: number;
34
+ date: string | null;
35
+ person: string | null;
36
+ person_printed: string;
37
+ reading: string | null;
38
+ person_type: string | null;
39
+ credentials: string | null;
40
+ caption: string | null;
41
+ school_printed: string | null;
42
+ school_id: string | null;
43
+ association_type: string | null;
44
+ src_type: string | null;
45
+ src: string | null;
46
+ candidate_school_id: string | null;
47
+ candidate_rung: string | null;
48
+ }
49
+ export declare function registrySchools(rel: Release): Promise<{
50
+ tag: string;
51
+ schools: RegistrySchool[];
52
+ }>;
53
+ export declare function schoolPrints(rel: Release, q: {
54
+ event?: string;
55
+ year?: number;
56
+ }): Promise<{
57
+ tag: string;
58
+ asked: {
59
+ event?: string;
60
+ year?: number;
61
+ };
62
+ prints: SchoolPrint[];
63
+ }>;
64
+ export declare function schoolRedirects(rel: Release): Promise<{
65
+ tag: string;
66
+ redirects: SchoolRedirect[];
67
+ }>;
68
+ export declare function schoolLineage(rel: Release): Promise<{
69
+ tag: string;
70
+ edges: LineageEdge[];
71
+ }>;
72
+ export declare function observedAssociations(rel: Release, q: {
73
+ school_id?: string;
74
+ }): Promise<{
75
+ tag: string;
76
+ asked: {
77
+ school_id?: string;
78
+ };
79
+ associations: ObservedAssociation[];
80
+ }>;
@@ -1,6 +1,7 @@
1
1
  import { type SchoolNameParser } from "@finbheara/domain/school";
2
2
  import type { IndexBuilder } from "./indexes.js";
3
3
  import type { Release } from "./release.js";
4
+ import { type LineageEdge, type ObservedAssociation, type RegistrySchool, type SchoolPrint, type SchoolRedirect } from "./school-bulk.js";
4
5
  import { type LinkQuery, type SchoolLineAnswer, type SchoolLinksAnswer } from "./school-links.js";
5
6
  export type SchoolRung = "id" | "redirect" | "print" | "sn3" | "realWords";
6
7
  export interface SchoolCandidate {
@@ -57,5 +58,37 @@ export declare class SchoolResolver {
57
58
  constructor(rel: Release);
58
59
  links(a: string, b: string, q?: LinkQuery): Promise<SchoolLinksAnswer>;
59
60
  line(a: string, q?: LinkQuery): Promise<SchoolLineAnswer>;
61
+ list(): Promise<{
62
+ tag: string;
63
+ schools: RegistrySchool[];
64
+ }>;
65
+ prints(q?: {
66
+ event?: string;
67
+ year?: number;
68
+ }): Promise<{
69
+ tag: string;
70
+ asked: {
71
+ event?: string;
72
+ year?: number;
73
+ };
74
+ prints: SchoolPrint[];
75
+ }>;
76
+ redirects(): Promise<{
77
+ tag: string;
78
+ redirects: SchoolRedirect[];
79
+ }>;
80
+ lineage(): Promise<{
81
+ tag: string;
82
+ edges: LineageEdge[];
83
+ }>;
84
+ associations(q?: {
85
+ school_id?: string;
86
+ }): Promise<{
87
+ tag: string;
88
+ asked: {
89
+ school_id?: string;
90
+ };
91
+ associations: ObservedAssociation[];
92
+ }>;
60
93
  resolve(asked: string, q?: SchoolQuery): Promise<SchoolAnswer>;
61
94
  }
@@ -0,0 +1,132 @@
1
+ import type { Release } from "./release.js";
2
+ export interface TabRow {
3
+ address: string;
4
+ competition: string;
5
+ card: string;
6
+ line: number;
7
+ event: string | null;
8
+ year: number | null;
9
+ tier: string | null;
10
+ round: string;
11
+ true_round: number;
12
+ seat_index: number;
13
+ section: string;
14
+ panel_size: number;
15
+ column_order: string;
16
+ drop_rule: string;
17
+ judge_name: string | null;
18
+ judge_name_printed: string | null;
19
+ judge_clrg: number | null;
20
+ judge_id: number | null;
21
+ judge_unmatched: string | null;
22
+ mark: number | null;
23
+ mark_printed: string | null;
24
+ place: number | null;
25
+ place_printed: string | null;
26
+ tie: number;
27
+ points: number | null;
28
+ points_printed: string | null;
29
+ }
30
+ export interface TabQuery {
31
+ competition?: string;
32
+ address?: string;
33
+ round?: string;
34
+ }
35
+ export interface TabStreamQuery {
36
+ event?: string | string[];
37
+ year?: number | [number, number];
38
+ tier?: string[];
39
+ competition?: string[];
40
+ }
41
+ export interface TabAnswer {
42
+ tag: string;
43
+ asked: TabQuery;
44
+ rows: TabRow[];
45
+ }
46
+ export interface PanelSeat {
47
+ competition: string;
48
+ panel: string;
49
+ round: string;
50
+ seat_index: number;
51
+ address: string | null;
52
+ judge_name_printed: string | null;
53
+ judge_clrg: number | null;
54
+ judge_id: number | null;
55
+ judge_unmatched: string | null;
56
+ }
57
+ export interface FeisPanelSeat {
58
+ address: string;
59
+ platform: string;
60
+ round: string;
61
+ seat_index: number;
62
+ panel_size: number;
63
+ panel_kind: string;
64
+ judge_name_printed: string;
65
+ judge_state_printed: string | null;
66
+ judge_clrg: number | null;
67
+ judge_id: number | null;
68
+ judge_unmatched: string | null;
69
+ judge_basis: string | null;
70
+ }
71
+ export interface RegisterRow {
72
+ judge_clrg: number;
73
+ year: number;
74
+ cat: string;
75
+ name_printed: string;
76
+ country: string;
77
+ country_printed: string;
78
+ country_code: string | null;
79
+ region: string;
80
+ region_printed: string;
81
+ region_code: string | null;
82
+ geohash: string | null;
83
+ src: string;
84
+ }
85
+ export declare const TAB_NEEDS: readonly string[];
86
+ export declare class TabFinder {
87
+ private readonly rel;
88
+ constructor(rel: Release);
89
+ private db;
90
+ private sheets;
91
+ query(q: TabQuery): Promise<TabAnswer>;
92
+ stream(q?: TabStreamQuery): AsyncGenerator<TabRow>;
93
+ }
94
+ export declare class PanelFinder {
95
+ private readonly rel;
96
+ constructor(rel: Release);
97
+ query(q?: {
98
+ competition?: string;
99
+ }): Promise<{
100
+ tag: string;
101
+ asked: {
102
+ competition?: string;
103
+ };
104
+ seats: PanelSeat[];
105
+ }>;
106
+ }
107
+ export declare class FeisPanelFinder {
108
+ private readonly rel;
109
+ constructor(rel: Release);
110
+ query(q?: {
111
+ address?: string;
112
+ platform?: string;
113
+ }): Promise<{
114
+ tag: string;
115
+ asked: {
116
+ address?: string;
117
+ platform?: string;
118
+ };
119
+ seats: FeisPanelSeat[];
120
+ }>;
121
+ }
122
+ export declare function registerRows(rel: Release, q: {
123
+ year?: number;
124
+ judge_clrg?: number;
125
+ }): Promise<{
126
+ tag: string;
127
+ asked: {
128
+ year?: number;
129
+ judge_clrg?: number;
130
+ };
131
+ rows: RegisterRow[];
132
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.24.0",
3
+ "version": "0.25.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",