@finbheara/archive-client 0.33.0 → 0.35.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
@@ -100,6 +100,9 @@ c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...
100
100
  | `upcoming.feises` | `upcoming.feises`, and the `next` of `feises.series` (null with the reason without it) | 285 |
101
101
  | `upcoming.fields`, `upcoming.entrants` | `upcoming.fields`, `upcoming.entrants` (285 and 290 carry the tables empty) | 291 |
102
102
  | `upcoming.fields.instance` | the `feis_instance_id` `upcoming.fields` carries; absent, never refused, without it | 320 |
103
+ | `calendar` | `calendar.get`, `calendar.list` | 320 |
104
+ | `calendar.expected` | each calendar stage's `expected`; absent, never refused, without it | 320 |
105
+ | `calendar.next` | `calendar.next` | 320 |
103
106
  | `tabs`, `panels` | `tabs.query`, `tabs.stream`; `panels.query` | 184 |
104
107
  | `feis-panels` | `feisPanels.query` | 260 |
105
108
  | `staff.adjudicators` | `staff.adjudicators` | 230 |
@@ -752,6 +755,53 @@ the corpus `entrants/` part: its rows are schools' counts (`level_counts`), with
752
755
  host with a registrant list publishes no such field. An unknown field or version answers no rows and says
753
756
  why. On a release without the tables each call refuses by name.
754
757
 
758
+ ## The feis calendar
759
+
760
+ ```ts
761
+ const g = await rel.calendar.get("plf.2026"); // an instance, an event-year (`<event><year>`), a retired
762
+ // id, a capture (`fweb:PLF26`) or an occurrence id
763
+ g.via; // "instance" | "formerly" | "feis_id" | "occurrence_id" | null
764
+ g.entries; // [{ feis_instance_id, feis_brand_id, tier, name_printed, date_lo, date_hi, date_source,
765
+ // occurrence_ids, feis_ids, stages: { announced, syllabus, entrants, run, results },
766
+ // terminal, carried, formerly }]
767
+ g.entries[0].stages.results.expected;
768
+ // { date, basis, n, platform } or null: "announced" | "brand" | "platform" |
769
+ // "pooled" | "roster-window" | "unmeasured"
770
+ const l = await rel.calendar.list({ stage: "entrants", state: "observed", from: "2026-11-01" });
771
+ // brand, tier, from, to, stage, state, terminal ("none": still ahead)
772
+ const n = await rel.calendar.next({ brand: "plf" });
773
+ // [{ feis_brand_id, year, run_expected, run_expected_basis, run_expected_n,
774
+ // from_instance_id, from_date }]: a series' next edition no listing names yet
775
+ ```
776
+
777
+ ONE ROW PER FEIS INSTANCE, on the id its results carry: a feis-series instance (`<brand>.<year>`),
778
+ announced, held or both, or a rated majors event-year. Each stage (`announced`, `syllabus`,
779
+ `entrants`, `run`, `results`) is `{ state, date, cite }`: `observed` (a capture showed it, `date`
780
+ the day it was first observed, null where no capture dates it, `cite` the capture), `pending` (the
781
+ release holds captures of that kind for the instance's platform, so a later one may show it) or
782
+ `not-captured` (it holds none). A day first observed never moves later from one release to the
783
+ next: a stage an earlier release observed keeps its day, named in `carried` (`["row"]` for a row
784
+ the cut no longer names, kept whole), and a retired id is listed in `formerly`. `terminal` is
785
+ `results`, `withdrawn` (every field withdrawn), `passed-no-results` (its first day is before the
786
+ cut's day), or null for a feis still ahead. Stage dates follow capture timing, not the feis's own
787
+ order: only results after the run is a belief. `get` tries the instance id, then `formerly`, then
788
+ a capture, then an occurrence id, and says which met (`via`). On a release without the table each
789
+ call refuses by name.
790
+
791
+ EXPECTED DAYS (`calendar.expected`): each stage carries `expected`, `{ date, basis, n, platform }`
792
+ or null. `announced` is the listing's own first day; `brand` the series' edition the year before
793
+ plus 364 days, `n` its held years; `platform` a platform's capture lag (the P90 of first results
794
+ capture less last day) or list lead (the median of first day less first list capture), measured
795
+ over the feiseanna run in the 180 days before the cut, `n` at least 10; `pooled` every platform's
796
+ results lags together, for a feis whose hosts have no capture form (`platform` `all`) and, NAMED, in
797
+ place of a platform measured on fewer than 10 (`platform` the host's); `roster-window` a majors event-year
798
+ no capture dates; `unmeasured` no day. A stage still ahead always carries one; an observed stage
799
+ carries the day its estimator gives it where one applies, so the misses can be read. The lags are
800
+ capture's own scrape cadence, re-measured every release. `next` lists each series with two or more
801
+ held years, the last the release's year or the one before, whose next edition is not on the
802
+ calendar: its year and expected first day. It is never an instance: an id is minted only once a
803
+ listing names the edition.
804
+
755
805
  ## Teachers and adjudicators
756
806
 
757
807
  ```ts
@@ -927,6 +977,10 @@ is passed to the dancer resolver as asked (`rel.dancers`, above).
927
977
  idq upcoming [--release <spec>] [--state <s,...>] [--feis <id>] [--from <date>] [--to <date>]
928
978
  idq upcoming --entrants <field_id> [--version <capture id>]
929
979
  idq upcoming --feises [--state upcoming|no-results] [--brand <series>] [--from <date>] [--to <date>]
980
+ idq calendar [--release <spec>] <instance|feis id|occurrence id>
981
+ idq calendar [--brand <series>] [--tier <t>] [--from <date>] [--to <date>] [--stage <s>] [--state <s>]
982
+ [--terminal results|withdrawn|passed-no-results|none]
983
+ idq calendar --next [--brand <series>]
930
984
  idq teacher [--release <spec>] <name|TD number>
931
985
  idq adjudicator [--release <spec>] <name|TD number>
932
986
  idq comp [--release <spec>] <feis> <code> [--year y] [--title t]
@@ -688,6 +688,60 @@ var FEATURES2 = [
688
688
  since: 320,
689
689
  calls: "upcoming.fields' `feis_instance_id` (absent, never null, without it)"
690
690
  },
691
+ {
692
+ feature: "calendar",
693
+ needs: [
694
+ "feis_instance_id",
695
+ "feis_brand_id",
696
+ "tier",
697
+ "name_printed",
698
+ "date_lo",
699
+ "date_hi",
700
+ "date_source",
701
+ "occurrence_ids",
702
+ "feis_ids",
703
+ "announced_state",
704
+ "announced_date",
705
+ "announced_cite",
706
+ "syllabus_state",
707
+ "syllabus_date",
708
+ "syllabus_cite",
709
+ "entrants_state",
710
+ "entrants_date",
711
+ "entrants_cite",
712
+ "run_state",
713
+ "run_date",
714
+ "run_cite",
715
+ "results_state",
716
+ "results_date",
717
+ "results_cite",
718
+ "terminal",
719
+ "carried",
720
+ "formerly"
721
+ ].map((c) => `results:feis_calendar.${c}`),
722
+ since: 320,
723
+ calls: "calendar.get, calendar.list"
724
+ },
725
+ {
726
+ feature: "calendar.expected",
727
+ needs: ["announced", "syllabus", "entrants", "run", "results"].flatMap((st) => ["", "_basis", "_n", "_platform"].map((x) => `results:feis_calendar.${st}_expected${x}`)),
728
+ since: 320,
729
+ calls: "each calendar stage's `expected` (absent, never refused, without it)"
730
+ },
731
+ {
732
+ feature: "calendar.next",
733
+ needs: [
734
+ "feis_brand_id",
735
+ "year",
736
+ "run_expected",
737
+ "run_expected_basis",
738
+ "run_expected_n",
739
+ "from_instance_id",
740
+ "from_date"
741
+ ].map((c) => `results:feis_series_next.${c}`),
742
+ since: 320,
743
+ calls: "calendar.next"
744
+ },
691
745
  {
692
746
  feature: "upcoming.entrants",
693
747
  needs: [
@@ -3825,6 +3879,155 @@ import {
3825
3879
  } from "fs";
3826
3880
  import { join as join3 } from "path";
3827
3881
 
3882
+ var CALENDAR_STAGES2 = [
3883
+ "announced",
3884
+ "syllabus",
3885
+ "entrants",
3886
+ "run",
3887
+ "results"
3888
+ ];
3889
+ var CALENDAR_STAGE_STATES2 = [
3890
+ "observed",
3891
+ "pending",
3892
+ "not-captured"
3893
+ ];
3894
+ var CALENDAR_TERMINALS2 = [
3895
+ "results",
3896
+ "withdrawn",
3897
+ "passed-no-results"
3898
+ ];
3899
+ var json = (s, empty) => typeof s === "string" && s !== "" ? JSON.parse(s) : empty;
3900
+ function expectedOf(r, s) {
3901
+ const basis = r[`${s}_expected_basis`];
3902
+ if (basis === null || basis === undefined)
3903
+ return null;
3904
+ const n = r[`${s}_expected_n`];
3905
+ return {
3906
+ date: r[`${s}_expected`] ?? null,
3907
+ basis,
3908
+ n: n === null || n === undefined ? null : Number(n),
3909
+ platform: r[`${s}_expected_platform`] ?? null
3910
+ };
3911
+ }
3912
+ function entryOf2(r, expected) {
3913
+ const stages = Object.fromEntries(CALENDAR_STAGES2.map((s) => [
3914
+ s,
3915
+ {
3916
+ state: r[`${s}_state`],
3917
+ date: r[`${s}_date`] ?? null,
3918
+ cite: r[`${s}_cite`] ?? null,
3919
+ ...expected ? { expected: expectedOf(r, s) } : {}
3920
+ }
3921
+ ]));
3922
+ return {
3923
+ feis_instance_id: r.feis_instance_id,
3924
+ feis_brand_id: r.feis_brand_id ?? null,
3925
+ tier: r.tier,
3926
+ name_printed: r.name_printed ?? null,
3927
+ date_lo: r.date_lo ?? null,
3928
+ date_hi: r.date_hi ?? null,
3929
+ date_source: r.date_source ?? null,
3930
+ occurrence_ids: json(r.occurrence_ids, []),
3931
+ feis_ids: json(r.feis_ids, []),
3932
+ stages,
3933
+ terminal: r.terminal ?? null,
3934
+ carried: json(r.carried, []),
3935
+ formerly: json(r.formerly, [])
3936
+ };
3937
+ }
3938
+ var holds = (col) => `EXISTS (SELECT 1 FROM json_each(${col}) WHERE value = ?)`;
3939
+
3940
+ class CalendarFinder2 {
3941
+ rel;
3942
+ constructor(rel) {
3943
+ this.rel = rel;
3944
+ }
3945
+ async db() {
3946
+ await this.rel.fetch("results");
3947
+ await this.rel.require("calendar");
3948
+ return this.rel.db("results");
3949
+ }
3950
+ async expected() {
3951
+ return (await this.rel.capability("calendar.expected")).state === "full";
3952
+ }
3953
+ async next(q = {}) {
3954
+ await this.rel.fetch("results");
3955
+ await this.rel.require("calendar.next");
3956
+ const db = this.rel.db("results");
3957
+ const rows = q.brand === undefined ? db.query("SELECT * FROM feis_series_next ORDER BY run_expected, feis_brand_id").all() : db.query("SELECT * FROM feis_series_next WHERE feis_brand_id = ? ORDER BY run_expected").all(q.brand);
3958
+ return {
3959
+ tag: this.rel.tag,
3960
+ next: rows.map((r) => ({
3961
+ feis_brand_id: r.feis_brand_id,
3962
+ year: Number(r.year),
3963
+ run_expected: r.run_expected,
3964
+ run_expected_basis: "brand",
3965
+ run_expected_n: Number(r.run_expected_n),
3966
+ from_instance_id: r.from_instance_id,
3967
+ from_date: r.from_date
3968
+ }))
3969
+ };
3970
+ }
3971
+ async get(id) {
3972
+ const db = await this.db();
3973
+ const ways = [
3974
+ ["instance", "feis_instance_id = ?"],
3975
+ ["formerly", holds("formerly")],
3976
+ ["feis_id", holds("feis_ids")],
3977
+ ["occurrence_id", holds("occurrence_ids")]
3978
+ ];
3979
+ for (const [via, where] of ways) {
3980
+ const rows = db.query(`SELECT * FROM feis_calendar WHERE ${where} ORDER BY feis_instance_id`).all(id);
3981
+ if (rows.length > 0) {
3982
+ const e = await this.expected();
3983
+ return { tag: this.rel.tag, asked: id, via, entries: rows.map((r) => entryOf2(r, e)) };
3984
+ }
3985
+ }
3986
+ return {
3987
+ tag: this.rel.tag,
3988
+ asked: id,
3989
+ via: null,
3990
+ entries: [],
3991
+ why: `the calendar names no instance, capture or occurrence "${id}"`
3992
+ };
3993
+ }
3994
+ async list(q = {}) {
3995
+ if (q.stage !== undefined && !CALENDAR_STAGES2.includes(q.stage))
3996
+ throw new Error(`calendar.list: no stage "${q.stage}" (the stages are ${CALENDAR_STAGES2.join(", ")})`);
3997
+ if (q.state !== undefined && !CALENDAR_STAGE_STATES2.includes(q.state))
3998
+ throw new Error(`calendar.list: no state "${q.state}" (the states are ${CALENDAR_STAGE_STATES2.join(", ")})`);
3999
+ if (q.terminal !== undefined && q.terminal !== "none" && !CALENDAR_TERMINALS2.includes(q.terminal))
4000
+ throw new Error(`calendar.list: no terminal "${q.terminal}" (the terminals are ${CALENDAR_TERMINALS2.join(", ")}, none)`);
4001
+ const db = await this.db();
4002
+ const where = [];
4003
+ const args = [];
4004
+ const add = (sql, ...v) => {
4005
+ where.push(sql);
4006
+ args.push(...v);
4007
+ };
4008
+ if (q.brand !== undefined)
4009
+ add("feis_brand_id = ?", q.brand);
4010
+ if (q.tier !== undefined)
4011
+ add("tier = ?", q.tier);
4012
+ if (q.from !== undefined)
4013
+ add("date_lo >= ?", q.from);
4014
+ if (q.to !== undefined)
4015
+ add("date_lo <= ?", q.to);
4016
+ if (q.stage !== undefined)
4017
+ add(`${q.stage}_state = ?`, q.state ?? "observed");
4018
+ else if (q.state !== undefined)
4019
+ add(`(${CALENDAR_STAGES2.map((s) => `${s}_state = ?`).join(" OR ")})`, ...CALENDAR_STAGES2.map(() => q.state));
4020
+ if (q.terminal === "none")
4021
+ add("terminal IS NULL");
4022
+ else if (q.terminal !== undefined)
4023
+ add("terminal = ?", q.terminal);
4024
+ const sql = `SELECT * FROM feis_calendar${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY date_lo IS NULL, date_lo, feis_instance_id`;
4025
+ const rows = db.query(sql).all(...args);
4026
+ const e = await this.expected();
4027
+ return { tag: this.rel.tag, entries: rows.map((r) => entryOf2(r, e)) };
4028
+ }
4029
+ }
4030
+
3828
4031
  import { Age as Age3 } from "@finbheara/domain/age";
3829
4032
  import { compTitle as compTitle3 } from "@finbheara/domain/title";
3830
4033
  function youngest2(answers) {
@@ -4125,7 +4328,7 @@ var FIELD_STATES2 = [
4125
4328
  "withdrawn"
4126
4329
  ];
4127
4330
  var DEPARTURE_REASONS2 = FIELD_STATES2.filter((s) => s !== "pending");
4128
- var json = (s, empty) => typeof s === "string" && s !== "" ? JSON.parse(s) : empty;
4331
+ var json2 = (s, empty) => typeof s === "string" && s !== "" ? JSON.parse(s) : empty;
4129
4332
  function fieldOf(r) {
4130
4333
  const countOnly = r.count_only === 1;
4131
4334
  return {
@@ -4154,19 +4357,19 @@ function fieldOf(r) {
4154
4357
  why: r.comp_why ?? null
4155
4358
  },
4156
4359
  state: r.state,
4157
- flags: json(r.flags, []),
4158
- results_competitions: json(r.results_competitions, []),
4159
- results_feis_ids: json(r.results_feis_ids, []),
4160
- held_out: json(r.held_out, []),
4360
+ flags: json2(r.flags, []),
4361
+ results_competitions: json2(r.results_competitions, []),
4362
+ results_feis_ids: json2(r.results_feis_ids, []),
4363
+ held_out: json2(r.held_out, []),
4161
4364
  merged_into: r.merged_into ?? null,
4162
4365
  version: r.version,
4163
- versions: json(r.versions, []),
4366
+ versions: json2(r.versions, []),
4164
4367
  captured_date: r.captured_date,
4165
4368
  sha256: r.sha256,
4166
- limits: json(r.limits, []),
4369
+ limits: json2(r.limits, []),
4167
4370
  entrants: r.entrants,
4168
4371
  stated_rows: r.stated_rows ?? null,
4169
- printed_total: json(r.printed_total, null)
4372
+ printed_total: json2(r.printed_total, null)
4170
4373
  };
4171
4374
  }
4172
4375
  function entrantOf(r) {
@@ -4183,30 +4386,30 @@ function entrantOf(r) {
4183
4386
  school_printed: r.school_printed ?? null,
4184
4387
  school_id: r.school_id ?? null,
4185
4388
  school_rung: r.school_rung,
4186
- school_candidates: json(r.school_candidates, []),
4187
- competitions: json(r.competitions, []),
4188
- field_ids: json(r.field_ids, []),
4189
- level_counts: json(r.level_counts, null),
4389
+ school_candidates: json2(r.school_candidates, []),
4390
+ competitions: json2(r.competitions, []),
4391
+ field_ids: json2(r.field_ids, []),
4392
+ level_counts: json2(r.level_counts, null),
4190
4393
  yob: r.yob ?? null,
4191
- asked: json(r.asked, null),
4394
+ asked: json2(r.asked, null),
4192
4395
  arm: r.arm ?? null,
4193
- candidates: json(r.candidates, []),
4194
- flags: json(r.flags, [])
4396
+ candidates: json2(r.candidates, []),
4397
+ flags: json2(r.flags, [])
4195
4398
  };
4196
4399
  }
4197
4400
  function feisOf(r) {
4198
4401
  return {
4199
4402
  occurrence_id: r.occurrence_id,
4200
- occurrence_ids: json(r.occurrence_ids, []),
4403
+ occurrence_ids: json2(r.occurrence_ids, []),
4201
4404
  name_printed: r.name_printed,
4202
4405
  date_lo: r.date_lo,
4203
4406
  date_hi: r.date_hi ?? null,
4204
4407
  year: r.year,
4205
- hosts: json(r.hosts, []),
4408
+ hosts: json2(r.hosts, []),
4206
4409
  feis_brand_id: r.feis_brand_id ?? null,
4207
4410
  feis_instance_id: r.feis_instance_id ?? null,
4208
4411
  series_basis: r.series_basis,
4209
- brand_candidates: json(r.brand_candidates, []),
4412
+ brand_candidates: json2(r.brand_candidates, []),
4210
4413
  captured_id: r.captured_id,
4211
4414
  state: r.results_state
4212
4415
  };
@@ -4396,6 +4599,7 @@ class Release2 {
4396
4599
  teams;
4397
4600
  events;
4398
4601
  upcoming;
4602
+ calendar;
4399
4603
  tabs;
4400
4604
  panels;
4401
4605
  feisPanels;
@@ -4422,6 +4626,7 @@ class Release2 {
4422
4626
  this.teams = new TeamFinder2(this);
4423
4627
  this.events = new EventFinder2(this);
4424
4628
  this.upcoming = new UpcomingFinder2(this);
4629
+ this.calendar = new CalendarFinder2(this);
4425
4630
  this.tabs = new TabFinder2(this);
4426
4631
  this.panels = new PanelFinder2(this);
4427
4632
  this.feisPanels = new FeisPanelFinder2(this);
@@ -5145,4 +5350,4 @@ function staleWarning2(answered, latest, floor, tags = [], command = "idq") {
5145
5350
  return `answered from ${answered}. ${floorTag ?? `cut ${floor.cut}`} is required${why}: run \`${command} use ${floor.cut}\` to get it`;
5146
5351
  }
5147
5352
 
5148
- export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, TabFinder2, PanelFinder2, FeisPanelFinder2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, rosterOf2, NO_CALENDAR2, NO_UPCOMING2, 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, FIELD_STATES2, DEPARTURE_REASONS2, UpcomingFinder2, 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 };
5353
+ export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, TabFinder2, PanelFinder2, FeisPanelFinder2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, rosterOf2, NO_CALENDAR2, NO_UPCOMING2, 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, CALENDAR_STAGES2, CALENDAR_STAGE_STATES2, CALENDAR_TERMINALS2, CalendarFinder2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, defaultRemote, FIELD_STATES2, DEPARTURE_REASONS2, UpcomingFinder2, 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-g8ab43kz.js";
24
+ } from "./chunks/index-gt7yxxp8.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]";
@@ -392,6 +392,25 @@ var VERBS = [
392
392
  " held-out competitions)"
393
393
  ]
394
394
  },
395
+ {
396
+ verb: "calendar",
397
+ value: ["--release", "--brand", "--tier", "--from", "--to", "--stage", "--state", "--terminal"],
398
+ bool: ["--next"],
399
+ usage: [
400
+ " calendar [--release <spec>] <instance|feis id|occurrence id>",
401
+ " one feis instance's stages: announced, syllabus, entrants,",
402
+ " run and results, each observed, pending or not-captured,",
403
+ " the day first observed and the capture that observed it",
404
+ " calendar [--brand <series>] [--tier T1..T4] [--from <date>] [--to <date>]",
405
+ " [--stage announced|syllabus|entrants|run|results] [--state observed|pending|not-captured]",
406
+ " [--terminal results|withdrawn|passed-no-results|none]",
407
+ " the instances a query names, by first day; each stage's",
408
+ " expected day with its basis and n where the release has them",
409
+ " calendar --next [--brand <series>]",
410
+ " each series' next edition no listing names yet: its year",
411
+ " and expected first day from the year before (basis brand)"
412
+ ]
413
+ },
395
414
  {
396
415
  verb: "teacher",
397
416
  value: ["--release"],
@@ -508,6 +527,7 @@ function parseArgs(argv) {
508
527
  return { positional, flags, bools };
509
528
  }
510
529
  var mb = (n) => `${(n / 1e6).toFixed(1)} MB`;
530
+ var expectedText = (e) => e === null || e === undefined ? "" : ` expected ${e.date ?? "-"} (${e.basis}${e.platform !== null ? ` ${e.platform}` : ""}${e.n !== null ? `, n ${e.n}` : ""})`;
511
531
  function printPlan(p, say) {
512
532
  for (const k of p.keep)
513
533
  say(`keep ${k.tag} (${k.why})`);
@@ -799,6 +819,7 @@ var ANSWERING = new Set([
799
819
  "teams",
800
820
  "events",
801
821
  "upcoming",
822
+ "calendar",
802
823
  "teacher",
803
824
  "adjudicator",
804
825
  "comp",
@@ -1533,6 +1554,52 @@ async function main(argv, io = {}) {
1533
1554
  rel.close();
1534
1555
  }
1535
1556
  }
1557
+ case "calendar": {
1558
+ const rel = await open(spec, { ...opts, assets: [] });
1559
+ try {
1560
+ const line = (e) => ` ${e.date_lo ?? "?"} ${e.feis_instance_id} ${e.tier} ${["announced", "syllabus", "entrants", "run", "results"].map((s) => `${s}:${e.stages[s].state === "observed" ? e.stages[s].date ?? "seen" : e.stages[s].state}`).join(" ")} [${e.terminal ?? "ahead"}]${e.name_printed !== null ? ` "${e.name_printed}"` : ""}`;
1561
+ if (p.bools.has("--next")) {
1562
+ const brand = p.flags.get("--brand");
1563
+ const a = await rel.calendar.next(brand === undefined ? {} : { brand });
1564
+ out(a, () => {
1565
+ for (const n of a.next)
1566
+ say(` ${n.run_expected} ${n.feis_brand_id} ${n.year} brand n ${n.run_expected_n} from ${n.from_instance_id} (${n.from_date})`);
1567
+ say(`${a.next.length} next edition(s) in ${a.tag}`);
1568
+ });
1569
+ return a.next.length === 0 ? 1 : 0;
1570
+ }
1571
+ if (rest.length > 0) {
1572
+ const a = await rel.calendar.get(rest.join(" "));
1573
+ out(a, () => {
1574
+ for (const e of a.entries) {
1575
+ say(line(e));
1576
+ for (const s of ["announced", "syllabus", "entrants", "run", "results"])
1577
+ say(` ${s} ${e.stages[s].state} ${e.stages[s].date ?? "-"} ${e.stages[s].cite ?? "-"}${e.carried.includes(s) ? " (carried)" : ""}${expectedText(e.stages[s].expected)}`);
1578
+ }
1579
+ say(`${a.entries.length} instance(s) for "${a.asked}"${a.via !== null ? ` by ${a.via}` : ""}${a.why !== undefined ? ` (${a.why})` : ""} in ${a.tag}`);
1580
+ });
1581
+ return a.entries.length === 0 ? 1 : 0;
1582
+ }
1583
+ const v = (f) => p.flags.get(f);
1584
+ const a = await rel.calendar.list({
1585
+ ...v("--brand") !== undefined ? { brand: v("--brand") } : {},
1586
+ ...v("--tier") !== undefined ? { tier: v("--tier") } : {},
1587
+ ...v("--from") !== undefined ? { from: v("--from") } : {},
1588
+ ...v("--to") !== undefined ? { to: v("--to") } : {},
1589
+ ...v("--stage") !== undefined ? { stage: v("--stage") } : {},
1590
+ ...v("--state") !== undefined ? { state: v("--state") } : {},
1591
+ ...v("--terminal") !== undefined ? { terminal: v("--terminal") } : {}
1592
+ });
1593
+ out(a, () => {
1594
+ for (const e of a.entries)
1595
+ say(line(e));
1596
+ say(`${a.entries.length} instance(s) in ${a.tag}`);
1597
+ });
1598
+ return a.entries.length === 0 ? 1 : 0;
1599
+ } finally {
1600
+ rel.close();
1601
+ }
1602
+ }
1536
1603
  case "teacher":
1537
1604
  case "adjudicator": {
1538
1605
  if (rest.length === 0) {
package/dist/index.js CHANGED
@@ -44,6 +44,10 @@ import {
44
44
  ensureIndex2,
45
45
  listIndexes2,
46
46
  readPin2,
47
+ CALENDAR_STAGES2,
48
+ CALENDAR_STAGE_STATES2,
49
+ CALENDAR_TERMINALS2,
50
+ CalendarFinder2,
47
51
  youngest2,
48
52
  dancerResolverOf2,
49
53
  RegistrationResolver2,
@@ -76,7 +80,7 @@ import {
76
80
  readLatestCheck2,
77
81
  latestCheck2,
78
82
  staleWarning2
79
- } from "./chunks/index-g8ab43kz.js";
83
+ } from "./chunks/index-gt7yxxp8.js";
80
84
  import {
81
85
  cacheRoot2,
82
86
  repoOf2
@@ -93,6 +97,10 @@ import {
93
97
  export {
94
98
  ADVANCEMENT_DANCES2 as ADVANCEMENT_DANCES,
95
99
  ARCHIVE_TAG2 as ARCHIVE_TAG,
100
+ CALENDAR_STAGES2 as CALENDAR_STAGES,
101
+ CALENDAR_STAGE_STATES2 as CALENDAR_STAGE_STATES,
102
+ CALENDAR_TERMINALS2 as CALENDAR_TERMINALS,
103
+ CalendarFinder2 as CalendarFinder,
96
104
  CompResolver2 as CompResolver,
97
105
  DATABASES2 as DATABASES,
98
106
  DEFAULT_KEEP2 as DEFAULT_KEEP,
@@ -0,0 +1,79 @@
1
+ import type { Release } from "./release.js";
2
+ export type CalendarStage = "announced" | "syllabus" | "entrants" | "run" | "results";
3
+ export declare const CALENDAR_STAGES: readonly CalendarStage[];
4
+ export type CalendarStageState = "observed" | "pending" | "not-captured";
5
+ export declare const CALENDAR_STAGE_STATES: readonly CalendarStageState[];
6
+ export type CalendarTerminal = "results" | "withdrawn" | "passed-no-results";
7
+ export declare const CALENDAR_TERMINALS: readonly CalendarTerminal[];
8
+ export type CalendarExpectedBasis = "announced" | "brand" | "platform" | "pooled" | "roster-window" | "unmeasured";
9
+ export interface CalendarExpected {
10
+ date: string | null;
11
+ basis: CalendarExpectedBasis;
12
+ n: number | null;
13
+ platform: string | null;
14
+ }
15
+ export interface CalendarStageValue {
16
+ state: CalendarStageState;
17
+ date: string | null;
18
+ cite: string | null;
19
+ expected?: CalendarExpected | null;
20
+ }
21
+ export interface CalendarEntry {
22
+ feis_instance_id: string;
23
+ feis_brand_id: string | null;
24
+ tier: string;
25
+ name_printed: string | null;
26
+ date_lo: string | null;
27
+ date_hi: string | null;
28
+ date_source: "run" | "announced" | null;
29
+ occurrence_ids: string[];
30
+ feis_ids: string[];
31
+ stages: Record<CalendarStage, CalendarStageValue>;
32
+ terminal: CalendarTerminal | null;
33
+ carried: string[];
34
+ formerly: string[];
35
+ }
36
+ export interface CalendarQuery {
37
+ brand?: string;
38
+ tier?: string;
39
+ from?: string;
40
+ to?: string;
41
+ stage?: CalendarStage;
42
+ state?: CalendarStageState;
43
+ terminal?: CalendarTerminal | "none";
44
+ }
45
+ export interface CalendarAnswer {
46
+ tag: string;
47
+ entries: CalendarEntry[];
48
+ }
49
+ export interface CalendarNext {
50
+ feis_brand_id: string;
51
+ year: number;
52
+ run_expected: string;
53
+ run_expected_basis: "brand";
54
+ run_expected_n: number;
55
+ from_instance_id: string;
56
+ from_date: string;
57
+ }
58
+ export interface CalendarNextAnswer {
59
+ tag: string;
60
+ next: CalendarNext[];
61
+ }
62
+ export interface CalendarGetAnswer {
63
+ tag: string;
64
+ asked: string;
65
+ via: "instance" | "formerly" | "feis_id" | "occurrence_id" | null;
66
+ entries: CalendarEntry[];
67
+ why?: string;
68
+ }
69
+ export declare class CalendarFinder {
70
+ private readonly rel;
71
+ constructor(rel: Release);
72
+ private db;
73
+ private expected;
74
+ next(q?: {
75
+ brand?: string;
76
+ }): Promise<CalendarNextAnswer>;
77
+ get(id: string): Promise<CalendarGetAnswer>;
78
+ list(q?: CalendarQuery): Promise<CalendarAnswer>;
79
+ }
@@ -26,4 +26,5 @@ export { type EntryRow, type StreamCompetition, type StreamQuery, type StreamRow
26
26
  export { type FeisPanelSeat, FeisPanelFinder, PanelFinder, type PanelSeat, type RegisterRow, type TabAnswer, TabFinder, type TabQuery, type TabRow, type TabStreamQuery, } from "./tabs.js";
27
27
  export { EventFinder, type EventYear, type EventYearQuery, type ResultRoster, type RosterMember, type RosterTeam, TeamFinder, type TeamResult, } from "./teams.js";
28
28
  export { type EntrantCandidate, FIELD_STATES, type FieldComp, DEPARTURE_REASONS, type DepartureReason, type FieldState, type HeldOut, type UpcomingDeparture, type UpcomingDepartureAnswer, type UpcomingDepartureQuery, type UpcomingEntrant, type UpcomingEntrantAnswer, type UpcomingEntrantQuery, type UpcomingFeis, type UpcomingFeisAnswer, type UpcomingFeisQuery, type UpcomingFeisState, type UpcomingField, type UpcomingFieldAnswer, type UpcomingFieldQuery, UpcomingFinder, type UpcomingHost, } from "./upcoming.js";
29
+ export { CALENDAR_STAGES, CALENDAR_STAGE_STATES, CALENDAR_TERMINALS, type CalendarAnswer, type CalendarEntry, type CalendarExpected, type CalendarExpectedBasis, type CalendarNext, type CalendarNextAnswer, CalendarFinder, type CalendarGetAnswer, type CalendarQuery, type CalendarStage, type CalendarStageState, type CalendarStageValue, type CalendarTerminal, } from "./calendar.js";
29
30
  export { ARCHIVE_TAG, type ArchiveTag, type ReleaseSpec, compareTagsNewestFirst, cutOf, parseArchiveTag, parseReleaseSpec, pickTag, renderArchiveTag, } from "./tag.js";
@@ -1,5 +1,6 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import { type AssetPlan, type LandedFile, type Receipt, type ReleaseManifest } from "./assets.js";
3
+ import { CalendarFinder } from "./calendar.js";
3
4
  import { CompResolver } from "./comps.js";
4
5
  import { type ClientOptions } from "./config.js";
5
6
  import { DancerResolver } from "./dancers.js";
@@ -47,6 +48,7 @@ export declare class Release {
47
48
  readonly teams: TeamFinder;
48
49
  readonly events: EventFinder;
49
50
  readonly upcoming: UpcomingFinder;
51
+ readonly calendar: CalendarFinder;
50
52
  readonly tabs: TabFinder;
51
53
  readonly panels: PanelFinder;
52
54
  readonly feisPanels: FeisPanelFinder;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.33.0",
3
+ "version": "0.35.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",