@finbheara/archive-client 0.25.0 → 0.27.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,8 @@ 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
+ | `upcoming.feises` | `upcoming.feises`, and the `next` of `feises.series` (null with the reason without it) | 285 |
99
+ | `upcoming.fields`, `upcoming.entrants` | `upcoming.fields`, `upcoming.entrants` (285 and 290 carry the tables empty) | 291 |
98
100
  | `tabs`, `panels` | `tabs.query`, `tabs.stream`; `panels.query` | 184 |
99
101
  | `feis-panels` | `feisPanels.query` | 260 |
100
102
  | `staff.adjudicators` | `staff.adjudicators` | 230 |
@@ -208,8 +210,11 @@ s.next; // the next scheduled instance where the release knows one, else n
208
210
  A series is asked by any id of it (a feis, instance or brand id, a retired one followed), by a
209
211
  host code with or without its year (held codes `<stem><yy>` whose two digits are their own year,
210
212
  never a host id of digits alone, which is a sequence number), or by a name (a glob, or a plain word read as `*word*`). Two series answer `ambiguous` with both.
211
- A release holds a feis-year once its results are captured and carries no calendar, so `next` is
212
- `null` today, with the reason.
213
+ `next` is the series' earliest occurrence of the feis-index dated on or after the cut's day
214
+ (`rel.upcoming.feises`, state `upcoming`), under its brand id or one retired onto it: `{ feis_id,
215
+ date, date_hi, occurrence_id, name_printed, hosts, captured_id, series_basis }`, `feis_id` the
216
+ first listing's that names one. It is `null` with `next_why` where the feis-index lists none, and
217
+ on a release without `upcoming_feises` (feature `upcoming.feises`).
213
218
 
214
219
  ## Schools
215
220
 
@@ -648,6 +653,49 @@ every release the `teams` feature names, and `(await rel.capability("teams.field
648
653
  a release has. The rows are read once per
649
654
  release into the `teams` and `events` indexes.
650
655
 
656
+ ## Upcoming feiseanna and their competitor lists
657
+
658
+ ```ts
659
+ const o = await rel.upcoming.feises({ state: "upcoming", from: "2026-11-01" });
660
+ o.feises; // [{ occurrence_id, occurrence_ids, name_printed, date_lo, date_hi, year, hosts,
661
+ // feis_brand_id, feis_instance_id, series_basis, brand_candidates, captured_id, state }]
662
+ const f = await rel.upcoming.fields({ state: "pending" }); // state, feis, field_id, from, to
663
+ f.fields; // [{ field_id, feis_id, source, host_id, competition_code, count_only, occurrence_id,
664
+ // name_printed, date_lo, date_hi, year, feis_brand_id, comp, state, flags,
665
+ // results_competitions, results_feis_ids, version, versions, captured_date, sha256,
666
+ // limits, entrants, stated_rows, printed_total }]
667
+ const e = await rel.upcoming.entrants({ field_id: "fweb:ABC26/101" }); // version: an older capture
668
+ e.entrants; // [{ name_printed, school_printed, school_id, yob, arm, candidates, flags,
669
+ // competitions, field_ids, captured_id, row_id, ... }]
670
+ ```
671
+
672
+ `upcoming.feises` is every occurrence of the cut's feis-index the release holds no results of:
673
+ `upcoming` (dated on or after the cut's day) or `no-results` (before it), each with every
674
+ platform's or calendar's listing (`hosts`) and its feis series where one was named (`series_basis`
675
+ says how; `none` leaves `feis_brand_id` null, the unjoined ones).
676
+
677
+ A FIELD is one competition a competitor list names, `<feis_id>/<code>`, the same id on every cut. A later capture of the host is a new `version`
678
+ (a capture id; `versions` lists them), never a new field. `state` says how it stands on the cut:
679
+ `pending` (on the list), `results` (the release holds its results, `results_competitions` the
680
+ join), `passed` (its first day is before the cut's day and no result is held) or `withdrawn` (the
681
+ host's newest capture no longer lists it). `comp` is the competition reading of its code (`arm`,
682
+ `level`, `feis_level`, `age`, `population`, `kind`, `title`, `why`), null on a count list. A
683
+ QuickFeis registrant list's field reads it off the list's own by-event label (`source`
684
+ `registration-list`, `title` the label as printed), and `flags` holds the cross-check of the
685
+ list's two documents (`no-event-section`, `event-rows-differ`); `[]` on any other field.
686
+ `limits` names what the capture cannot show (`registered-not-danced`, `paid-in-full-only`, ...).
687
+
688
+ `upcoming.entrants` answers a field's rows in one version, its current one by default. A
689
+ registrant carries the resolver's `arm` and `candidates`, NEVER A `dancer_id`: a list shows who
690
+ registered, not who danced, and a `unique` row's one candidate is read with its `flags`
691
+ (`initial-only`: a FeisWeb print is a surname and an initial; `name-hidden`: QuickFeis printed
692
+ `(Name Hidden)`, so the row has no name and no answer). A QuickFeis registrant list
693
+ (`transcripts/*.registrationlist.jsonl`) is one field per printed code, each registrant a row as
694
+ printed. `count_only` marks a field read from an older registration-by-school page of
695
+ the corpus `entrants/` part: its rows are schools' counts (`level_counts`), with no names, and a
696
+ host with a registrant list publishes no such field. An unknown field or version answers no rows and says
697
+ why. On a release without the tables each call refuses by name.
698
+
651
699
  ## Teachers and adjudicators
652
700
 
653
701
  ```ts
@@ -816,6 +864,9 @@ is passed to the dancer resolver as asked (`rel.dancers`, above).
816
864
  | --associations [--school <school_id>]]
817
865
  idq teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]
818
866
  idq events [--release <spec>] [--event <e>] [--year <y>]
867
+ idq upcoming [--release <spec>] [--state <s,...>] [--feis <id>] [--from <date>] [--to <date>]
868
+ idq upcoming --entrants <field_id> [--version <capture id>]
869
+ idq upcoming --feises [--state upcoming|no-results] [--brand <series>] [--from <date>] [--to <date>]
819
870
  idq teacher [--release <spec>] <name|TD number>
820
871
  idq adjudicator [--release <spec>] <name|TD number>
821
872
  idq comp [--release <spec>] <feis> <code> [--year y] [--title t]
@@ -201,6 +201,7 @@ var feisesIndex = {
201
201
  }
202
202
  };
203
203
  var NO_CALENDAR2 = "the release holds a feis-year once its results are captured and carries no calendar of scheduled feiseanna";
204
+ var NO_UPCOMING2 = "the release's feis-index lists no occurrence of the series dated on or after the cut's day";
204
205
  var globToLike = (g) => g.replace(/[\\%_]/g, (c) => `\\${c}`).replace(/\*/g, "%").replace(/\?/g, "_");
205
206
 
206
207
  class FeisFinder2 {
@@ -296,6 +297,29 @@ class FeisFinder2 {
296
297
  instances
297
298
  };
298
299
  }
300
+ async nextOf(brand) {
301
+ if ((await this.rel.capability("upcoming.feises")).state === "missing")
302
+ return { next: null, next_why: NO_CALENDAR2 };
303
+ const db = await this.rel.indexDb("feises");
304
+ const retired = db.query("SELECT old_id FROM redirect WHERE new_id = ? AND kind = 'brand' ORDER BY old_id").all(brand).map((r) => r.old_id);
305
+ let best = null;
306
+ for (const id of [brand, ...retired])
307
+ for (const o of (await this.rel.upcoming.feises({ state: "upcoming", brand: id })).feises) {
308
+ if (best !== null && (best.date < o.date_lo || best.date === o.date_lo && best.occurrence_id <= o.occurrence_id))
309
+ continue;
310
+ best = {
311
+ feis_id: o.hosts.find((h) => h.feis_id !== null)?.feis_id ?? null,
312
+ date: o.date_lo,
313
+ date_hi: o.date_hi,
314
+ occurrence_id: o.occurrence_id,
315
+ name_printed: o.name_printed,
316
+ hosts: o.hosts,
317
+ captured_id: o.captured_id,
318
+ series_basis: o.series_basis
319
+ };
320
+ }
321
+ return best === null ? { next: null, next_why: NO_UPCOMING2 } : { next: best };
322
+ }
299
323
  async series(q) {
300
324
  const asked = q.trim();
301
325
  const answer = async (brands, via, more = {}) => {
@@ -314,8 +338,7 @@ class FeisFinder2 {
314
338
  host: u?.host ?? null,
315
339
  years: u?.years ?? [],
316
340
  instances: u?.instances ?? [],
317
- next: null,
318
- ...u !== null ? { next_why: NO_CALENDAR2 } : {},
341
+ ...u !== null ? await this.nextOf(u.series_id) : { next: null },
319
342
  candidates,
320
343
  ...arm === "ambiguous" ? { why: `${candidates.length} series answer "${asked}": ${brands.join(", ")}` } : {},
321
344
  ...more
@@ -3716,6 +3739,94 @@ var FEATURES2 = [
3716
3739
  since: 260,
3717
3740
  calls: "events.years (the events index)"
3718
3741
  },
3742
+ {
3743
+ feature: "upcoming.feises",
3744
+ needs: [
3745
+ "occurrence_id",
3746
+ "occurrence_ids",
3747
+ "name_printed",
3748
+ "date_lo",
3749
+ "date_hi",
3750
+ "year",
3751
+ "hosts",
3752
+ "feis_brand_id",
3753
+ "feis_instance_id",
3754
+ "series_basis",
3755
+ "brand_candidates",
3756
+ "captured_id",
3757
+ "results_state"
3758
+ ].map((c) => `results:upcoming_feises.${c}`),
3759
+ since: 285,
3760
+ calls: "upcoming.feises, the `next` of feises.series"
3761
+ },
3762
+ {
3763
+ feature: "upcoming.fields",
3764
+ needs: [
3765
+ "field_id",
3766
+ "feis_id",
3767
+ "source",
3768
+ "host_id",
3769
+ "competition_code",
3770
+ "count_only",
3771
+ "occurrence_id",
3772
+ "name_printed",
3773
+ "date_lo",
3774
+ "date_hi",
3775
+ "year",
3776
+ "feis_brand_id",
3777
+ "comp_arm",
3778
+ "comp_source",
3779
+ "comp_kind",
3780
+ "level",
3781
+ "feis_level",
3782
+ "age",
3783
+ "population",
3784
+ "comp_title",
3785
+ "comp_why",
3786
+ "state",
3787
+ "results_competitions",
3788
+ "results_feis_ids",
3789
+ "version",
3790
+ "versions",
3791
+ "captured_date",
3792
+ "sha256",
3793
+ "limits",
3794
+ "entrants",
3795
+ "stated_rows",
3796
+ "printed_total"
3797
+ ].map((c) => `results:upcoming_fields.${c}`),
3798
+ since: 291,
3799
+ calls: "upcoming.fields (285 and 290 carry the table empty)"
3800
+ },
3801
+ {
3802
+ feature: "upcoming.entrants",
3803
+ needs: [
3804
+ "feis_id",
3805
+ "captured_id",
3806
+ "row_id",
3807
+ "source",
3808
+ "host_id",
3809
+ "row_kind",
3810
+ "count_only",
3811
+ "n",
3812
+ "name_printed",
3813
+ "school_printed",
3814
+ "school_id",
3815
+ "school_rung",
3816
+ "school_candidates",
3817
+ "competitions",
3818
+ "field_ids",
3819
+ "level_counts",
3820
+ "yob",
3821
+ "asked",
3822
+ "arm",
3823
+ "candidate_ids",
3824
+ "candidates",
3825
+ "flags"
3826
+ ].map((c) => `results:upcoming_entrants.${c}`),
3827
+ since: 291,
3828
+ calls: "upcoming.entrants (285 and 290 carry the table empty)"
3829
+ },
3719
3830
  {
3720
3831
  feature: "staff",
3721
3832
  needs: [
@@ -3815,14 +3926,14 @@ var FEATURES2 = [
3815
3926
  }
3816
3927
  ];
3817
3928
  async function hasNeed(probe, n) {
3818
- const t = /^table:([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
3929
+ const t = /^table:([A-Za-z_]\w*)(?:\.([A-Za-z_]\w*))?$/.exec(n);
3819
3930
  if (t !== null) {
3820
3931
  if (probe.tableColumns === undefined)
3821
3932
  return false;
3822
3933
  const cols = await probe.tableColumns(t[1]);
3823
3934
  return t[2] === undefined ? cols.size > 0 : cols.has(t[2]);
3824
3935
  }
3825
- const m = /^(results|majors):([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
3936
+ const m = /^(results|majors):([A-Za-z_]\w*)(?:\.([A-Za-z_]\w*))?$/.exec(n);
3826
3937
  if (m === null)
3827
3938
  return probe.publishes(n);
3828
3939
  const cols = columnsOf2(await probe.db(m[1]), m[2]);
@@ -3839,6 +3950,187 @@ function needsRelease2(tag, c) {
3839
3950
  return new Error(`archive-client: ${tag} is too old for ${c.feature} (${c.calls}): it lacks ${c.missing.join(", ")}. Open a release of cut ${c.since} or later`);
3840
3951
  }
3841
3952
 
3953
+ var FIELD_STATES2 = ["pending", "results", "passed", "withdrawn"];
3954
+ var json = (s, empty) => typeof s === "string" && s !== "" ? JSON.parse(s) : empty;
3955
+ function fieldOf(r) {
3956
+ const countOnly = r.count_only === 1;
3957
+ return {
3958
+ field_id: r.field_id,
3959
+ feis_id: r.feis_id,
3960
+ source: r.source,
3961
+ host_id: r.host_id,
3962
+ competition_code: r.competition_code ?? null,
3963
+ count_only: countOnly,
3964
+ occurrence_id: r.occurrence_id ?? null,
3965
+ name_printed: r.name_printed,
3966
+ date_lo: r.date_lo ?? null,
3967
+ date_hi: r.date_hi ?? null,
3968
+ year: r.year,
3969
+ feis_brand_id: r.feis_brand_id ?? null,
3970
+ comp: r.comp_arm === null ? null : {
3971
+ arm: r.comp_arm,
3972
+ source: r.comp_source ?? null,
3973
+ kind: r.comp_kind ?? null,
3974
+ level: r.level ?? null,
3975
+ feis_level: r.feis_level ?? null,
3976
+ age: r.age ?? null,
3977
+ population: r.population ?? null,
3978
+ title: r.comp_title ?? null,
3979
+ why: r.comp_why ?? null
3980
+ },
3981
+ state: r.state,
3982
+ flags: json(r.flags, []),
3983
+ results_competitions: json(r.results_competitions, []),
3984
+ results_feis_ids: json(r.results_feis_ids, []),
3985
+ version: r.version,
3986
+ versions: json(r.versions, []),
3987
+ captured_date: r.captured_date,
3988
+ sha256: r.sha256,
3989
+ limits: json(r.limits, []),
3990
+ entrants: r.entrants,
3991
+ stated_rows: r.stated_rows ?? null,
3992
+ printed_total: json(r.printed_total, null)
3993
+ };
3994
+ }
3995
+ function entrantOf(r) {
3996
+ return {
3997
+ feis_id: r.feis_id,
3998
+ captured_id: r.captured_id,
3999
+ row_id: r.row_id,
4000
+ source: r.source,
4001
+ host_id: r.host_id,
4002
+ row_kind: r.row_kind,
4003
+ count_only: r.count_only === 1,
4004
+ n: r.n,
4005
+ name_printed: r.name_printed ?? null,
4006
+ school_printed: r.school_printed ?? null,
4007
+ school_id: r.school_id ?? null,
4008
+ school_rung: r.school_rung,
4009
+ school_candidates: json(r.school_candidates, []),
4010
+ competitions: json(r.competitions, []),
4011
+ field_ids: json(r.field_ids, []),
4012
+ level_counts: json(r.level_counts, null),
4013
+ yob: r.yob ?? null,
4014
+ asked: json(r.asked, null),
4015
+ arm: r.arm ?? null,
4016
+ candidates: json(r.candidates, []),
4017
+ flags: json(r.flags, [])
4018
+ };
4019
+ }
4020
+ function feisOf(r) {
4021
+ return {
4022
+ occurrence_id: r.occurrence_id,
4023
+ occurrence_ids: json(r.occurrence_ids, []),
4024
+ name_printed: r.name_printed,
4025
+ date_lo: r.date_lo,
4026
+ date_hi: r.date_hi ?? null,
4027
+ year: r.year,
4028
+ hosts: json(r.hosts, []),
4029
+ feis_brand_id: r.feis_brand_id ?? null,
4030
+ feis_instance_id: r.feis_instance_id ?? null,
4031
+ series_basis: r.series_basis,
4032
+ brand_candidates: json(r.brand_candidates, []),
4033
+ captured_id: r.captured_id,
4034
+ state: r.results_state
4035
+ };
4036
+ }
4037
+
4038
+ class UpcomingFinder2 {
4039
+ rel;
4040
+ constructor(rel) {
4041
+ this.rel = rel;
4042
+ }
4043
+ async db(feature) {
4044
+ await this.rel.fetch("results");
4045
+ await this.rel.require(feature);
4046
+ return this.rel.db("results");
4047
+ }
4048
+ async feises(q = {}) {
4049
+ const db = await this.db("upcoming.feises");
4050
+ const where = [];
4051
+ const args = [];
4052
+ if (q.state !== undefined) {
4053
+ where.push("results_state = ?");
4054
+ args.push(q.state);
4055
+ }
4056
+ if (q.brand !== undefined) {
4057
+ where.push("feis_brand_id = ?");
4058
+ args.push(q.brand);
4059
+ }
4060
+ if (q.from !== undefined) {
4061
+ where.push("date_lo >= ?");
4062
+ args.push(q.from);
4063
+ }
4064
+ if (q.to !== undefined) {
4065
+ where.push("date_lo <= ?");
4066
+ args.push(q.to);
4067
+ }
4068
+ const sql = `SELECT * FROM upcoming_feises${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY date_lo, occurrence_id`;
4069
+ const rows = db.query(sql).all(...args);
4070
+ return { tag: this.rel.tag, feises: rows.map(feisOf) };
4071
+ }
4072
+ async fields(q = {}) {
4073
+ const db = await this.db("upcoming.fields");
4074
+ const where = [];
4075
+ const args = [];
4076
+ if (q.state !== undefined) {
4077
+ const states = typeof q.state === "string" ? [q.state] : [...q.state];
4078
+ if (states.length === 0)
4079
+ return { tag: this.rel.tag, fields: [] };
4080
+ where.push(`state IN (${states.map(() => "?").join(", ")})`);
4081
+ args.push(...states);
4082
+ }
4083
+ if (q.feis !== undefined) {
4084
+ where.push("(feis_id = ? OR feis_brand_id = ? OR occurrence_id = ?)");
4085
+ args.push(q.feis, q.feis, q.feis);
4086
+ }
4087
+ if (q.field_id !== undefined) {
4088
+ where.push("field_id = ?");
4089
+ args.push(q.field_id);
4090
+ }
4091
+ if (q.from !== undefined) {
4092
+ where.push("date_lo >= ?");
4093
+ args.push(q.from);
4094
+ }
4095
+ if (q.to !== undefined) {
4096
+ where.push("date_lo <= ?");
4097
+ args.push(q.to);
4098
+ }
4099
+ const sql = `SELECT * FROM upcoming_fields${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY date_lo, feis_id, field_id`;
4100
+ const rows = db.query(sql).all(...args);
4101
+ return { tag: this.rel.tag, fields: rows.map(fieldOf) };
4102
+ }
4103
+ async entrants(q) {
4104
+ const [field] = (await this.fields({ field_id: q.field_id })).fields;
4105
+ const db = await this.db("upcoming.entrants");
4106
+ if (field === undefined)
4107
+ return {
4108
+ tag: this.rel.tag,
4109
+ field: null,
4110
+ version: null,
4111
+ entrants: [],
4112
+ why: `the release holds no field "${q.field_id}"`
4113
+ };
4114
+ const version = q.version ?? field.version;
4115
+ if (!field.versions.includes(version))
4116
+ return {
4117
+ tag: this.rel.tag,
4118
+ field,
4119
+ version,
4120
+ entrants: [],
4121
+ why: `${field.field_id} has no version ${version} (versions ${field.versions.join(", ")})`
4122
+ };
4123
+ const rows = db.query("SELECT * FROM upcoming_entrants WHERE feis_id = ? AND captured_id = ? ORDER BY row_id").all(field.feis_id, version).map(entrantOf).filter((e) => e.field_ids.includes(field.field_id));
4124
+ return {
4125
+ tag: this.rel.tag,
4126
+ field,
4127
+ version,
4128
+ entrants: rows,
4129
+ ...rows.length === 0 ? { why: `${field.field_id} lists no row in version ${version}` } : {}
4130
+ };
4131
+ }
4132
+ }
4133
+
3842
4134
  function tagDir(root, tag) {
3843
4135
  if (parseArchiveTag2(tag) === null)
3844
4136
  throw new Error(`archive-client: "${tag}" is not a release tag (archive-YYYY.MM.DD[-N[.k]])`);
@@ -3895,6 +4187,7 @@ class Release2 {
3895
4187
  registrations;
3896
4188
  teams;
3897
4189
  events;
4190
+ upcoming;
3898
4191
  tabs;
3899
4192
  panels;
3900
4193
  feisPanels;
@@ -3920,6 +4213,7 @@ class Release2 {
3920
4213
  this.registrations = new RegistrationResolver2(this, this.comps, opts.dancerResolver);
3921
4214
  this.teams = new TeamFinder2(this);
3922
4215
  this.events = new EventFinder2(this);
4216
+ this.upcoming = new UpcomingFinder2(this);
3923
4217
  this.tabs = new TabFinder2(this);
3924
4218
  this.panels = new PanelFinder2(this);
3925
4219
  this.feisPanels = new FeisPanelFinder2(this);
@@ -4643,4 +4937,4 @@ function staleWarning2(answered, latest, floor, tags = [], command = "idq") {
4643
4937
  return `answered from ${answered}. ${floorTag ?? `cut ${floor.cut}`} is required${why}: run \`${command} use ${floor.cut}\` to get it`;
4644
4938
  }
4645
4939
 
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 };
4940
+ export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, 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, 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, FIELD_STATES2, 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-2ms980hc.js";
24
+ } from "./chunks/index-8c1pk7gw.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]";
@@ -360,6 +360,32 @@ var VERBS = [
360
360
  " each event-year's dates and place"
361
361
  ]
362
362
  },
363
+ {
364
+ verb: "upcoming",
365
+ value: [
366
+ "--release",
367
+ "--state",
368
+ "--feis",
369
+ "--from",
370
+ "--to",
371
+ "--entrants",
372
+ "--version",
373
+ "--brand"
374
+ ],
375
+ bool: ["--feises"],
376
+ usage: [
377
+ " upcoming [--release <spec>] [--state pending,results,passed,withdrawn] [--feis <id>]",
378
+ " [--from <date>] [--to <date>]",
379
+ " the competitor lists' fields, each with its state, version",
380
+ " and competition reading; --feis a feis, series or",
381
+ " occurrence id",
382
+ " upcoming --entrants <field_id> [--version <capture id>]",
383
+ " a field's rows (default its current version): each",
384
+ " registrant's arm and candidates",
385
+ " upcoming --feises [--state upcoming|no-results] [--brand <series>] [--from <date>] [--to <date>]",
386
+ " the feis-index's occurrences the release holds no results of"
387
+ ]
388
+ },
363
389
  {
364
390
  verb: "teacher",
365
391
  value: ["--release"],
@@ -766,6 +792,7 @@ var ANSWERING = new Set([
766
792
  "schools",
767
793
  "teams",
768
794
  "events",
795
+ "upcoming",
769
796
  "teacher",
770
797
  "adjudicator",
771
798
  "comp",
@@ -1032,7 +1059,7 @@ async function main(argv, io = {}) {
1032
1059
  say(` ${i.year ?? "?"} ${i.date_lo ?? "?"} ${i.feis_ids.join(", ")}`);
1033
1060
  }
1034
1061
  if (a.arm === "unique")
1035
- say(` next: ${a.next === null ? `unknown (${a.next_why ?? ""})` : `${a.next.feis_id ?? "?"} ${a.next.date}`}`);
1062
+ say(` next: ${a.next === null ? `unknown (${a.next_why ?? ""})` : `${a.next.feis_id ?? "?"} ${a.next.date} ${a.next.occurrence_id} (${a.next.hosts.map((h) => h.source).join(",")}; ${a.next.series_basis})`}`);
1036
1063
  });
1037
1064
  rel.close();
1038
1065
  return a.arm === "none" ? 1 : 0;
@@ -1433,6 +1460,60 @@ async function main(argv, io = {}) {
1433
1460
  rel.close();
1434
1461
  }
1435
1462
  }
1463
+ case "upcoming": {
1464
+ const rel = await open(spec, { ...opts, assets: [] });
1465
+ try {
1466
+ const from = p.flags.get("--from");
1467
+ const to = p.flags.get("--to");
1468
+ const range = {
1469
+ ...from !== undefined ? { from } : {},
1470
+ ...to !== undefined ? { to } : {}
1471
+ };
1472
+ const state = p.flags.get("--state");
1473
+ const fieldId = p.flags.get("--entrants");
1474
+ if (fieldId !== undefined) {
1475
+ const version = p.flags.get("--version");
1476
+ const a = await rel.upcoming.entrants({
1477
+ field_id: fieldId,
1478
+ ...version !== undefined ? { version } : {}
1479
+ });
1480
+ out(a, () => {
1481
+ for (const e of a.entrants)
1482
+ say(` ${e.arm ?? "-"} ${e.candidates.map((c) => c.dancer_id).join(",") || "-"} "${e.name_printed ?? ""}" ${e.school_id ?? "(no id)"} "${e.school_printed ?? ""}"${e.flags.length > 0 ? ` [${e.flags.join(",")}]` : ""}`);
1483
+ say(`${a.entrants.length} row(s) of ${a.field?.field_id ?? fieldId} version ${a.version ?? "-"}${a.why !== undefined ? ` (${a.why})` : ""} in ${a.tag}`);
1484
+ });
1485
+ return a.entrants.length === 0 ? 1 : 0;
1486
+ }
1487
+ if (p.bools.has("--feises")) {
1488
+ const brand = p.flags.get("--brand");
1489
+ const a = await rel.upcoming.feises({
1490
+ ...state !== undefined ? { state } : {},
1491
+ ...brand !== undefined ? { brand } : {},
1492
+ ...range
1493
+ });
1494
+ out(a, () => {
1495
+ for (const f of a.feises)
1496
+ say(` ${f.date_lo} ${f.occurrence_id} "${f.name_printed}" ${f.feis_brand_id ?? "(no series)"} (${f.series_basis}) ${f.hosts.map((h) => h.feis_id ?? `${h.source}:${h.host_id}`).join(", ")} [${f.state}]`);
1497
+ say(`${a.feises.length} feis occurrence(s) in ${a.tag}`);
1498
+ });
1499
+ return a.feises.length === 0 ? 1 : 0;
1500
+ }
1501
+ const feis = p.flags.get("--feis");
1502
+ const a = await rel.upcoming.fields({
1503
+ ...state !== undefined ? { state: state.split(",") } : {},
1504
+ ...feis !== undefined ? { feis } : {},
1505
+ ...range
1506
+ });
1507
+ out(a, () => {
1508
+ for (const f of a.fields)
1509
+ say(` ${f.date_lo ?? "?"} ${f.field_id} ${f.state} ${f.entrants} row(s) v${f.version} ${f.feis_brand_id ?? "(no series)"} ${f.comp === null ? "" : f.comp.arm === "none" ? "comp unread" : `${f.comp.level ?? "?"} ${f.comp.age ?? "?"} ${f.comp.population ?? ""}`}`);
1510
+ say(`${a.fields.length} field(s) in ${a.tag}`);
1511
+ });
1512
+ return a.fields.length === 0 ? 1 : 0;
1513
+ } finally {
1514
+ rel.close();
1515
+ }
1516
+ }
1436
1517
  case "teacher":
1437
1518
  case "adjudicator": {
1438
1519
  if (rest.length === 0) {
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  NAME_LEVELS2,
8
8
  nameLevel2,
9
9
  NO_CALENDAR2,
10
+ NO_UPCOMING2,
10
11
  FeisFinder2,
11
12
  ADVANCEMENT_DANCES2,
12
13
  entryRank2,
@@ -49,6 +50,8 @@ import {
49
50
  FEATURES2,
50
51
  capabilityOf2,
51
52
  needsRelease2,
53
+ FIELD_STATES2,
54
+ UpcomingFinder2,
52
55
  resolveTag2,
53
56
  Release2,
54
57
  openRelease2,
@@ -71,7 +74,7 @@ import {
71
74
  readLatestCheck2,
72
75
  latestCheck2,
73
76
  staleWarning2
74
- } from "./chunks/index-2ms980hc.js";
77
+ } from "./chunks/index-8c1pk7gw.js";
75
78
  import {
76
79
  cacheRoot2,
77
80
  repoOf2
@@ -95,6 +98,7 @@ export {
95
98
  EventFinder2 as EventFinder,
96
99
  FEATURES2 as FEATURES,
97
100
  FEIS_LOCAL_ID_FLOOR2 as FEIS_LOCAL_ID_FLOOR,
101
+ FIELD_STATES2 as FIELD_STATES,
98
102
  FeisFinder2 as FeisFinder,
99
103
  FeisPanelFinder2 as FeisPanelFinder,
100
104
  LATEST_CHECK_BUDGET_MS2 as LATEST_CHECK_BUDGET_MS,
@@ -104,6 +108,7 @@ export {
104
108
  MajorsFinder2 as MajorsFinder,
105
109
  NAME_LEVELS2 as NAME_LEVELS,
106
110
  NO_CALENDAR2 as NO_CALENDAR,
111
+ NO_UPCOMING2 as NO_UPCOMING,
107
112
  PanelFinder2 as PanelFinder,
108
113
  RegistrationResolver2 as RegistrationResolver,
109
114
  Release2 as Release,
@@ -113,6 +118,7 @@ export {
113
118
  StaffFinder2 as StaffFinder,
114
119
  TabFinder2 as TabFinder,
115
120
  TeamFinder2 as TeamFinder,
121
+ UpcomingFinder2 as UpcomingFinder,
116
122
  buildIndex2 as buildIndex,
117
123
  buildTag2 as buildTag,
118
124
  cacheRoot2 as cacheRoot,
@@ -1,5 +1,6 @@
1
1
  import type { IndexBuilder } from "./indexes.js";
2
2
  import type { Release } from "./release.js";
3
+ import type { UpcomingHost } from "./upcoming.js";
3
4
  export interface Feis {
4
5
  feis_id: string;
5
6
  source: string;
@@ -53,6 +54,12 @@ export interface Series {
53
54
  export interface SeriesNext {
54
55
  feis_id: string | null;
55
56
  date: string;
57
+ date_hi: string | null;
58
+ occurrence_id: string;
59
+ name_printed: string;
60
+ hosts: UpcomingHost[];
61
+ captured_id: string;
62
+ series_basis: string;
56
63
  }
57
64
  export interface SeriesAnswer {
58
65
  tag: string;
@@ -74,6 +81,7 @@ export interface SeriesAnswer {
74
81
  why?: string;
75
82
  }
76
83
  export declare const NO_CALENDAR = "the release holds a feis-year once its results are captured and carries no calendar of scheduled feiseanna";
84
+ export declare const NO_UPCOMING = "the release's feis-index lists no occurrence of the series dated on or after the cut's day";
77
85
  export declare class FeisFinder {
78
86
  private readonly rel;
79
87
  constructor(rel: Release);
@@ -88,5 +96,6 @@ export declare class FeisFinder {
88
96
  find(q: FeisQuery): Promise<FeisAnswer>;
89
97
  codeStem(token: string, stem: string): Promise<string[]>;
90
98
  seriesOf(brand: string): Promise<Series>;
99
+ nextOf(brand: string): Promise<Pick<SeriesAnswer, "next" | "next_why">>;
91
100
  series(q: string): Promise<SeriesAnswer>;
92
101
  }
@@ -5,7 +5,7 @@ export { NAME_LEVELS, type NameLevel, nameLevel } from "./dancers-names.js";
5
5
  export { ADVANCEMENT_DANCES, type DancerGrades, type DancerLevelAnswer, type DancerLevelQuery, type LevelRange, } from "./dancer-level.js";
6
6
  export { type Comp, type CompAnswer, type CompDance, type CompKind, type CompQuery, CompResolver, type CompSource, type FeisPlace, type SyllabusAnswer, type SyllabusForAnswer, type SyllabusSource, type TitleCheck, canonicalOf, syllabusSourceOf, } from "./comps.js";
7
7
  export { type ClientOptions, cacheRoot, repoOf } from "./config.js";
8
- export { type Feis, type FeisAnswer, FeisFinder, type FeisQuery, NO_CALENDAR, type Series, type SeriesAnswer, type SeriesInstance, type SeriesNext, } from "./feises.js";
8
+ export { type Feis, type FeisAnswer, FeisFinder, type FeisQuery, NO_CALENDAR, NO_UPCOMING, type Series, type SeriesAnswer, type SeriesInstance, type SeriesNext, } from "./feises.js";
9
9
  export { type IndexBuilder, type IndexFile, buildIndex, defaultBuilders, ensureIndex, indexKey, indexPath, isCurrent, listIndexes, } from "./indexes.js";
10
10
  export { type CutVersion, LOCAL_DIR, type LocalBuild, type PickedBuild, buildTag, incomplete, localRoot, newerThan, openBuild, pickBuild, releaseVersion, sweepBuilds, } from "./local.js";
11
11
  export { type CachedRelease, DEFAULT_KEEP, type ReleaseList, ReleaseManager, type RemovalPlan, type UseResult, releases, } from "./manager.js";
@@ -24,4 +24,5 @@ export { type Capability, FEATURES, type Feature, type SchemaProbe, capabilityOf
24
24
  export { type EntryRow, type StreamQuery, type StreamRow, type StreamScope, entryRank, } from "./stream.js";
25
25
  export { type FeisPanelSeat, FeisPanelFinder, PanelFinder, type PanelSeat, type RegisterRow, type TabAnswer, TabFinder, type TabQuery, type TabRow, type TabStreamQuery, } from "./tabs.js";
26
26
  export { EventFinder, type EventYear, type EventYearQuery, type ResultRoster, type RosterMember, type RosterTeam, TeamFinder, type TeamResult, } from "./teams.js";
27
+ export { type EntrantCandidate, FIELD_STATES, type FieldComp, type FieldState, 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";
27
28
  export { ARCHIVE_TAG, type ArchiveTag, type ReleaseSpec, compareTagsNewestFirst, cutOf, parseArchiveTag, parseReleaseSpec, pickTag, renderArchiveTag, } from "./tag.js";
@@ -14,6 +14,7 @@ import { type SchoolReaderChoice, SchoolResolver } from "./schools.js";
14
14
  import { FeisPanelFinder, PanelFinder, TabFinder } from "./tabs.js";
15
15
  import { type ReleaseSpec } from "./tag.js";
16
16
  import { EventFinder, TeamFinder } from "./teams.js";
17
+ import { UpcomingFinder } from "./upcoming.js";
17
18
  export interface OpenOptions extends ClientOptions {
18
19
  assets?: readonly string[];
19
20
  remote?: Remote;
@@ -45,6 +46,7 @@ export declare class Release {
45
46
  readonly registrations: RegistrationResolver;
46
47
  readonly teams: TeamFinder;
47
48
  readonly events: EventFinder;
49
+ readonly upcoming: UpcomingFinder;
48
50
  readonly tabs: TabFinder;
49
51
  readonly panels: PanelFinder;
50
52
  readonly feisPanels: FeisPanelFinder;
@@ -0,0 +1,139 @@
1
+ import type { Release } from "./release.js";
2
+ export type FieldState = "pending" | "results" | "passed" | "withdrawn";
3
+ export declare const FIELD_STATES: readonly FieldState[];
4
+ export type UpcomingFeisState = "upcoming" | "no-results";
5
+ export interface UpcomingHost {
6
+ source: string;
7
+ host_id: string;
8
+ feis_id: string | null;
9
+ series: string | null;
10
+ name_printed: string | null;
11
+ date_lo: string | null;
12
+ date_hi: string | null;
13
+ url: string | null;
14
+ }
15
+ export interface UpcomingFeis {
16
+ occurrence_id: string;
17
+ occurrence_ids: string[];
18
+ name_printed: string;
19
+ date_lo: string;
20
+ date_hi: string | null;
21
+ year: number;
22
+ hosts: UpcomingHost[];
23
+ feis_brand_id: string | null;
24
+ feis_instance_id: string | null;
25
+ series_basis: string;
26
+ brand_candidates: string[];
27
+ captured_id: string;
28
+ state: UpcomingFeisState;
29
+ }
30
+ export interface FieldComp {
31
+ arm: "unique" | "ambiguous" | "none";
32
+ source: string | null;
33
+ kind: string | null;
34
+ level: string | null;
35
+ feis_level: number | null;
36
+ age: string | null;
37
+ population: string | null;
38
+ title: string | null;
39
+ why: string | null;
40
+ }
41
+ export interface UpcomingField {
42
+ field_id: string;
43
+ feis_id: string;
44
+ source: string;
45
+ host_id: string;
46
+ competition_code: string | null;
47
+ count_only: boolean;
48
+ occurrence_id: string | null;
49
+ name_printed: string;
50
+ date_lo: string | null;
51
+ date_hi: string | null;
52
+ year: number;
53
+ feis_brand_id: string | null;
54
+ comp: FieldComp | null;
55
+ state: FieldState;
56
+ flags: string[];
57
+ results_competitions: string[];
58
+ results_feis_ids: string[];
59
+ version: string;
60
+ versions: string[];
61
+ captured_date: string;
62
+ sha256: string;
63
+ limits: string[];
64
+ entrants: number;
65
+ stated_rows: number | null;
66
+ printed_total: unknown;
67
+ }
68
+ export interface EntrantCandidate {
69
+ dancer_id: number;
70
+ scope: string;
71
+ kind: string | null;
72
+ score: number;
73
+ name: string | null;
74
+ age: string | null;
75
+ school: string | null;
76
+ }
77
+ export interface UpcomingEntrant {
78
+ feis_id: string;
79
+ captured_id: string;
80
+ row_id: string;
81
+ source: string;
82
+ host_id: string;
83
+ row_kind: string;
84
+ count_only: boolean;
85
+ n: number;
86
+ name_printed: string | null;
87
+ school_printed: string | null;
88
+ school_id: string | null;
89
+ school_rung: string;
90
+ school_candidates: string[];
91
+ competitions: string[];
92
+ field_ids: string[];
93
+ level_counts: Record<string, number> | null;
94
+ yob: number | null;
95
+ asked: Record<string, unknown> | null;
96
+ arm: "unique" | "ambiguous" | "none" | null;
97
+ candidates: EntrantCandidate[];
98
+ flags: string[];
99
+ }
100
+ export interface UpcomingFeisQuery {
101
+ state?: UpcomingFeisState;
102
+ brand?: string;
103
+ from?: string;
104
+ to?: string;
105
+ }
106
+ export interface UpcomingFieldQuery {
107
+ state?: FieldState | readonly FieldState[];
108
+ feis?: string;
109
+ field_id?: string;
110
+ from?: string;
111
+ to?: string;
112
+ }
113
+ export interface UpcomingEntrantQuery {
114
+ field_id: string;
115
+ version?: string;
116
+ }
117
+ export interface UpcomingFeisAnswer {
118
+ tag: string;
119
+ feises: UpcomingFeis[];
120
+ }
121
+ export interface UpcomingFieldAnswer {
122
+ tag: string;
123
+ fields: UpcomingField[];
124
+ }
125
+ export interface UpcomingEntrantAnswer {
126
+ tag: string;
127
+ field: UpcomingField | null;
128
+ version: string | null;
129
+ entrants: UpcomingEntrant[];
130
+ why?: string;
131
+ }
132
+ export declare class UpcomingFinder {
133
+ private readonly rel;
134
+ constructor(rel: Release);
135
+ private db;
136
+ feises(q?: UpcomingFeisQuery): Promise<UpcomingFeisAnswer>;
137
+ fields(q?: UpcomingFieldQuery): Promise<UpcomingFieldAnswer>;
138
+ entrants(q: UpcomingEntrantQuery): Promise<UpcomingEntrantAnswer>;
139
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.25.0",
3
+ "version": "0.27.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",