@cyc-seattle/platform 1.53.0 → 1.55.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
@@ -4,6 +4,9 @@ Pulumi stacks for CYC Community Sailing Center's shared Google Cloud platform: t
4
4
  app owns. The apps that run on it live in their own repos, such as
5
5
  [isthmia](https://github.com/cyc-seattle/isthmia).
6
6
 
7
+ The [docs site](https://cyc-seattle.github.io/platform/) publishes [`docs/`](docs) from `main`: how the
8
+ platform is built, its data model, and the planning engine.
9
+
7
10
  ## Stacks
8
11
 
9
12
  | Stack | Holds | Applied by |
@@ -101,20 +104,19 @@ deployer needs first.
101
104
  authenticates with Workload Identity Federation and stores no credentials. Each job runs in a
102
105
  GitHub environment that holds its identity and state bucket:
103
106
 
104
- | Event | Jobs, in order |
105
- | -------------------------- | ----------------------------------------------------------------------- |
106
- | Pull request | `ci` |
107
- | Merge queue | `ci`, then `dev`: `mise run deploy` as `deploy-dev`; `dev-passed` |
108
- | Push to `main` | `prod-preview`, then `prod`, which waits for the `maintainers` approval |
109
- | Run by hand, from a branch | `ci`, then `dev` |
110
- | Run by hand, from `main` | `prod-preview`, then `prod` |
107
+ | Event | Jobs, in order |
108
+ | ------------------------ | ----------------------------------------------------------------------- |
109
+ | Pull request | `ci` |
110
+ | Merge queue | `ci`, then `dev`: `mise run deploy` as `deploy-dev`; `dev-passed` |
111
+ | Push to `main` | `prod-preview`, then `prod`, which waits for the `maintainers` approval |
112
+ | Run by hand, from `main` | `prod-preview`, then `prod` |
111
113
 
112
114
  Dev is shared, so only the merge queue deploys to it (#69, #80). PRs queued together deploy as
113
115
  one batch: each queued PR's run skips dev when a newer PR is queued behind it, and the newest
114
116
  deploys them all. The queue requires `dev-passed`, so a batch merges only once its dev deploy passes, and `main` gets the exact commit the queue tested, in one push. If the
115
117
  newest PR fails, the queue drops it and the rest of the batch deploys without it. A queued PR of only docs or CI skips `dev`. The queue
116
- doesn't run the integration tests (#161); a run by hand does. To try a
117
- branch on dev and test it before queueing it, run `gh workflow run pull-request.yml --ref <branch>`.
118
+ doesn't run the integration tests (#161); a run by hand does. Dev's environment takes only `main`
119
+ (#280), so a branch can't deploy to it.
118
120
 
119
121
  `prod-preview` runs `mise run preview` as `deploy-runner` and writes the diff to the job summary.
120
122
  `prod` runs `mise run deploy` on the same commit once approved. It runs only when the preview shows
@@ -164,9 +166,17 @@ login only when it isn't already set, so Actions' credentials win.
164
166
  mise run ci
165
167
  ```
166
168
 
167
- Runs the type check, Prettier, ESLint, and the unit tests. Tests mock Pulumi with
169
+ Runs the type check, Prettier, ESLint, the unit tests, and the docs site's build. Tests mock Pulumi with
168
170
  `pulumi.runtime.setMocks` and never call a live API.
169
171
 
172
+ ```sh
173
+ mise run docs-dev
174
+ ```
175
+
176
+ Serves the docs site, from `docs-site/`, as you edit `docs/`. A page in `docs/` takes its title from
177
+ its first heading, and a link to another file in the repo goes to GitHub. The build fails on a broken
178
+ link. `.github/workflows/docs.yml` publishes it to GitHub Pages on every push to `main`.
179
+
170
180
  ```sh
171
181
  mise run integration
172
182
  ```
@@ -8,7 +8,7 @@ export function scheduleActivities(ctx) {
8
8
  const closed = new Map();
9
9
  for (const c of ctx.data.plan_closed_dates ?? []) {
10
10
  // The closed days are the Operations Plan's, not an area's.
11
- if (ctx.planOfVersion.get(c.version_id)?.kind === "program_area") {
11
+ if (ctx.planOf.get(c.plan_id)?.kind === "program_area") {
12
12
  ctx.warn("closed_date_in_area_plan", `${c.name} is a closed day in a Program Plan. Only the Operations Plan's closed days count`, c.key);
13
13
  continue;
14
14
  }
@@ -31,7 +31,7 @@ export function scheduleActivities(ctx) {
31
31
  const closedFor = closed.get(activity.date);
32
32
  if (closedFor !== undefined && activity.kind !== "travel") {
33
33
  ctx.warn("closed_date", `${offering.name}'s ${activity.kind} is on ${closedFor}, when ${ctx.data.organization.name} is closed`, activity.key, {
34
- version_id: activity.version_id,
34
+ plan_id: activity.plan_id,
35
35
  date: activity.date,
36
36
  });
37
37
  }
@@ -94,7 +94,7 @@ function areaOf(ctx, owner) {
94
94
  * order and then no term. An area plan's bonus counts only that area's products, and warns about any other.
95
95
  */
96
96
  function basesOf(ctx, owner, terms) {
97
- const plan = ctx.planOfVersion.get(owner.version_id);
97
+ const plan = ctx.planOf.get(owner.plan_id);
98
98
  const products = new Set();
99
99
  for (const row of ctx.data.plan_bonus_owner_products) {
100
100
  if (row.bonus_owner_id !== owner.id)
@@ -1,7 +1,7 @@
1
1
  import type { Account, AccountFunction, CostCenterType, CostType, DateString, EngineWarning, Id, PlanData, PlanOffering, Plan, PlanRates, Product, ProgramArea } from "./types.ts";
2
- /** Where a warning applies: a plan copy, and a date. */
2
+ /** Where a warning applies: a plan, and a date. */
3
3
  export interface WarningScope {
4
- version_id?: Id;
4
+ plan_id?: Id;
5
5
  date?: DateString;
6
6
  }
7
7
  /** The step that recorded an entry. Only `direct` entries count toward MTDC. */
@@ -31,8 +31,8 @@ export declare class Context {
31
31
  readonly accounts: Map<Id, Account>;
32
32
  readonly products: Map<Id, Product>;
33
33
  readonly areas: Map<Id, ProgramArea>;
34
- /** Each version's plan. */
35
- readonly planOfVersion: Map<Id, Plan>;
34
+ /** Each plan, by its id. */
35
+ readonly planOf: Map<Id, Plan>;
36
36
  readonly offeringsById: Map<Id, PlanOffering>;
37
37
  readonly offeringsByKey: Map<Id, PlanOffering>;
38
38
  /** Expected enrollment per offering id, which the costs and revenue use. */
@@ -24,8 +24,8 @@ export class Context {
24
24
  accounts;
25
25
  products;
26
26
  areas;
27
- /** Each version's plan. */
28
- planOfVersion;
27
+ /** Each plan, by its id. */
28
+ planOf;
29
29
  offeringsById;
30
30
  offeringsByKey;
31
31
  /** Expected enrollment per offering id, which the costs and revenue use. */
@@ -42,11 +42,7 @@ export class Context {
42
42
  this.accounts = new Map(data.accounts.map((a) => [a.id, a]));
43
43
  this.products = new Map(data.products.map((p) => [p.id, p]));
44
44
  this.areas = new Map(data.program_areas.map((a) => [a.id, a]));
45
- const plans = new Map(data.plans.map((p) => [p.id, p]));
46
- this.planOfVersion = new Map(data.plan_versions.flatMap((v) => {
47
- const plan = plans.get(v.plan_id);
48
- return plan === undefined ? [] : [[v.id, plan]];
49
- }));
45
+ this.planOf = new Map(data.plans.map((p) => [p.id, p]));
50
46
  this.offeringsById = new Map(data.plan_offerings.map((o) => [o.id, o]));
51
47
  this.offeringsByKey = new Map(data.plan_offerings.map((o) => [o.key, o]));
52
48
  this.rates = data.plan_rates[0];
@@ -65,7 +61,7 @@ export class Context {
65
61
  return account;
66
62
  }
67
63
  warn(code, message, key, scope = {}) {
68
- const same = (w) => w.code === code && w.message === message && w.version_id === scope.version_id;
64
+ const same = (w) => w.code === code && w.message === message && w.plan_id === scope.plan_id;
69
65
  if (this.warnings.some(same))
70
66
  return;
71
67
  const category = ALLOCATION_WARNINGS.has(code) ? "allocation" : "data";
@@ -74,7 +70,7 @@ export class Context {
74
70
  message,
75
71
  category,
76
72
  ...(key !== undefined && { key }),
77
- ...(scope.version_id !== undefined && { version_id: scope.version_id }),
73
+ ...(scope.plan_id !== undefined && { plan_id: scope.plan_id }),
78
74
  ...(scope.date !== undefined && { date: scope.date }),
79
75
  });
80
76
  }
@@ -127,7 +127,7 @@ function checkAreas(ctx) {
127
127
  else if (product.kind !== "program" && product.program_area_id != null) {
128
128
  ctx.warn("area_on_non_program", `${product.name} isn't a program but has a program area`, product.id);
129
129
  }
130
- const plan = ctx.planOfVersion.get(offering.version_id);
130
+ const plan = ctx.planOf.get(offering.plan_id);
131
131
  if (plan === undefined)
132
132
  continue;
133
133
  const wrong = plan.kind === "operations"
@@ -62,9 +62,9 @@ function checkCostCenter(ctx, type, key, line) {
62
62
  * organization already has its own warning, `capital_off_org`.
63
63
  */
64
64
  function warnCostShift(ctx, line) {
65
- if (ctx.planOfVersion.get(line.version_id)?.kind !== "program_area")
65
+ if (ctx.planOf.get(line.plan_id)?.kind !== "program_area")
66
66
  return;
67
- const warn = (message) => ctx.warn("cost_off_programs", message, line.key, { version_id: line.version_id });
67
+ const warn = (message) => ctx.warn("cost_off_programs", message, line.key, { plan_id: line.plan_id });
68
68
  const org = ctx.data.organization.name;
69
69
  const capital = ctx.accountType(line.account_id) === "asset";
70
70
  if (capital && line.cost_center_type === "org") {
@@ -34,12 +34,17 @@ export function bookSalaries(ctx, coaching, booker) {
34
34
  }
35
35
  return [
36
36
  b.product_id != null
37
- ? { cost_center_type: "product", cost_center_key: b.product_id, hours: b.hours, line: b.kind }
37
+ ? {
38
+ cost_center_type: "product",
39
+ cost_center_key: b.product_id,
40
+ hours: b.hours,
41
+ line: "management",
42
+ }
38
43
  : {
39
44
  cost_center_type: "program_area",
40
45
  cost_center_key: b.program_area_id,
41
46
  hours: b.hours,
42
- line: b.kind,
47
+ line: "management",
43
48
  },
44
49
  ];
45
50
  }),
@@ -18,7 +18,7 @@ export interface DoubleBooked {
18
18
  /** Dates it's on two blocks at once. */
19
19
  dates: number;
20
20
  }
21
- /** Whole kids expected: `target_enrollment` if set, otherwise `capacity × fill_rate` rounded. */
21
+ /** Whole kids expected: `target_enrollment`, or none when it's empty. */
22
22
  export declare function enrollmentOf(offering: PlanOffering): number;
23
23
  /** The most sailors an offering takes: its `capacity`, or else the sailors expected. */
24
24
  export declare function maxEnrollmentOf(offering: PlanOffering): number;
@@ -26,7 +26,7 @@ export declare class Staffing {
26
26
  private readonly ctx;
27
27
  private readonly warned;
28
28
  constructor(ctx: Context);
29
- /** Warns once for each row and plan copy, about the first date it's seen on. */
29
+ /** Warns once for each row and plan, about the first date it's seen on. */
30
30
  private warnOnce;
31
31
  /**
32
32
  * Gathers each block's activities, times and allocations. A block's paid time runs from its first
@@ -1,8 +1,6 @@
1
- /** Whole kids expected: `target_enrollment` if set, otherwise `capacity × fill_rate` rounded. */
1
+ /** Whole kids expected: `target_enrollment`, or none when it's empty. */
2
2
  export function enrollmentOf(offering) {
3
- if (offering.target_enrollment != null)
4
- return Math.round(offering.target_enrollment);
5
- return Math.round((offering.capacity ?? 0) * (offering.fill_rate ?? 0));
3
+ return Math.round(offering.target_enrollment ?? 0);
6
4
  }
7
5
  /** The most sailors an offering takes: its `capacity`, or else the sailors expected. */
8
6
  export function maxEnrollmentOf(offering) {
@@ -51,9 +49,9 @@ export class Staffing {
51
49
  constructor(ctx) {
52
50
  this.ctx = ctx;
53
51
  }
54
- /** Warns once for each row and plan copy, about the first date it's seen on. */
52
+ /** Warns once for each row and plan, about the first date it's seen on. */
55
53
  warnOnce(code, message, key, scope = {}) {
56
- const id = `${code}|${key}|${scope.version_id ?? ""}`;
54
+ const id = `${code}|${key}|${scope.plan_id ?? ""}`;
57
55
  if (this.warned.has(id))
58
56
  return;
59
57
  this.warned.add(id);
@@ -80,7 +78,7 @@ export class Staffing {
80
78
  continue;
81
79
  }
82
80
  if (row.date !== a.date) {
83
- this.ctx.warn("block_date_mismatch", `${a.offering.name}'s ${a.kind} is in ${row.name}, which is on ${dayOf(row.date)}`, a.stored.key, { version_id: a.stored.version_id, date: a.date });
81
+ this.ctx.warn("block_date_mismatch", `${a.offering.name}'s ${a.kind} is in ${row.name}, which is on ${dayOf(row.date)}`, a.stored.key, { plan_id: a.stored.plan_id, date: a.date });
84
82
  }
85
83
  members.set(id, [...(members.get(id) ?? []), a]);
86
84
  }
@@ -125,7 +123,7 @@ export class Staffing {
125
123
  if (block !== undefined && sum(block.staff.map((s) => s.count)) > 0)
126
124
  continue;
127
125
  this.ctx.warn("unallocated_activity", `${a.offering.name}'s ${a.kind} has no staff`, a.stored.key, {
128
- version_id: a.stored.version_id,
126
+ plan_id: a.stored.plan_id,
129
127
  date: a.date,
130
128
  });
131
129
  }
@@ -149,7 +147,7 @@ export class Staffing {
149
147
  const most = (o) => ctx.maxEnrollment.get(o.id) ?? 0;
150
148
  const sailors = sum(offerings.map(most));
151
149
  const warns = sailors > 0;
152
- const scope = { version_id: block.row.version_id, date: block.row.date };
150
+ const scope = { plan_id: block.row.plan_id, date: block.row.date };
153
151
  // The block's offerings, with the verbs that agree with them: "Guppies Week 2 is for", "has".
154
152
  const several = new Set(block.members.map((a) => a.offering.id)).size > 1;
155
153
  const is = `${offeringsOf(block)} ${several ? "are" : "is"} for up to`;
@@ -161,7 +159,7 @@ export class Staffing {
161
159
  const settings = programs.get(o.product_id);
162
160
  if (settings?.staff_level_key == null || settings.sailors_per_coach == null || settings.sailors_per_coach <= 0) {
163
161
  if (warns) {
164
- this.warnOnce("no_program_settings", `${o.name}'s program has no staff level or sailors per coach. Add both to check that it has enough coaches`, o.product_id, { version_id: o.version_id, date: block.row.date });
162
+ this.warnOnce("no_program_settings", `${o.name}'s program has no staff level or sailors per coach. Add both to check that it has enough coaches`, o.product_id, { plan_id: o.plan_id, date: block.row.date });
165
163
  }
166
164
  continue;
167
165
  }
@@ -267,7 +265,7 @@ export class Staffing {
267
265
  const rate = rates.get(id)?.charter_per_day_cents;
268
266
  if (rate == null) {
269
267
  for (const { block } of uses) {
270
- this.warnOnce("no_charter_rate", `${block.row.name} charters ${pluralOf(name)}, but the Operations Plan has no charter rate for them, so they cost nothing`, id, { version_id: block.row.version_id, date: block.row.date });
268
+ this.warnOnce("no_charter_rate", `${block.row.name} charters ${pluralOf(name)}, but the Operations Plan has no charter rate for them, so they cost nothing`, id, { plan_id: block.row.plan_id, date: block.row.date });
271
269
  }
272
270
  continue;
273
271
  }
@@ -305,9 +303,9 @@ export class Staffing {
305
303
  const message = peak.at.length === 1
306
304
  ? `${count} out at once for ${offeringsOf(peak.at[0].block)}, but ${organization} has ${only}`
307
305
  : `${count} out at once, but ${organization} has ${only}: ${listOf(peak.at.map(({ block, n }) => `${n} for ${offeringsOf(block)}`))}`;
308
- for (const version of new Set(peak.at.map(({ block }) => block.row.version_id))) {
306
+ for (const plan of new Set(peak.at.map(({ block }) => block.row.plan_id))) {
309
307
  ctx.warn(code, message, what, {
310
- version_id: version,
308
+ plan_id: plan,
311
309
  date: date,
312
310
  });
313
311
  }
@@ -343,9 +341,9 @@ export class Staffing {
343
341
  const hours = extraCoachMinutes(list) / 60;
344
342
  const total = result.get(key) ?? { hours: 0, dates: 0 };
345
343
  result.set(key, { hours: total.hours + hours, dates: total.dates + 1 });
346
- for (const version of new Set(list.map((b) => b.row.version_id))) {
344
+ for (const plan of new Set(list.map((b) => b.row.plan_id))) {
347
345
  this.ctx.warn("position_double_booked", `${names.get(key) ?? key} coaches ${clash[0].row.name} and ${clash[1].row.name} at the same time` +
348
- (hours > 0 ? `, so another coach is needed for ${round2(hours)} hours` : ""), key, { version_id: version, date });
346
+ (hours > 0 ? `, so another coach is needed for ${round2(hours)} hours` : ""), key, { plan_id: plan, date });
349
347
  }
350
348
  }
351
349
  return result;
@@ -370,7 +368,7 @@ export class Staffing {
370
368
  const given = new Map();
371
369
  const tripCoaches = new Map();
372
370
  for (const block of byDate(blocks)) {
373
- const scope = { version_id: block.row.version_id, date: block.row.date };
371
+ const scope = { plan_id: block.row.plan_id, date: block.row.date };
374
372
  const paidHours = block.paid_minutes / 60;
375
373
  const waterHours = block.on_water_minutes / 60;
376
374
  const expected = this.coachesExpected(block, programs);
@@ -479,7 +477,7 @@ export class Staffing {
479
477
  .filter(travel)
480
478
  .map((a) => a.date)
481
479
  .sort()[0];
482
- this.warnOnce("trip_without_staff", `${trip.name} has no staff on its travel days, so the plan has no airfare or lodging for its coaches`, trip.key, { version_id: trip.version_id, ...(first !== undefined && { date: first }) });
480
+ this.warnOnce("trip_without_staff", `${trip.name} has no staff on its travel days, so the plan has no airfare or lodging for its coaches`, trip.key, { plan_id: trip.plan_id, ...(first !== undefined && { date: first }) });
483
481
  }
484
482
  const lodging = ctx.rates?.lodging_per_night_cents ?? null;
485
483
  const mileage = ctx.rates?.mileage_rate_cents ?? null;
@@ -4,11 +4,11 @@ export type Id = string;
4
4
  export type DateString = string;
5
5
  /** A `time`, as `HH:MM` or `HH:MM:SS`. */
6
6
  export type TimeString = string;
7
- /** The columns every version-scoped row carries. */
8
- export interface Versioned {
7
+ /** The columns every row of a plan carries. */
8
+ export interface PlanRow {
9
9
  id: Id;
10
- version_id: Id;
11
- /** Stable across versions; references from other plans use it. */
10
+ plan_id: Id;
11
+ /** Stays the same when a Financial Plan is copied; references from other plans use it. */
12
12
  key: Id;
13
13
  }
14
14
  export type CostCenterType = "offering" | "product" | "program_area" | "pool" | "org";
@@ -94,13 +94,8 @@ export interface Plan {
94
94
  kind: "program_area" | "operations";
95
95
  program_area_id: Id | null;
96
96
  }
97
- /** A plan version. The engine reads only which plan it belongs to. */
98
- export interface PlanVersion {
99
- id: Id;
100
- plan_id: Id;
101
- }
102
97
  export type ActivityKind = "practice" | "regatta" | "clinic" | "travel";
103
- export interface PlanOffering extends Versioned {
98
+ export interface PlanOffering extends PlanRow {
104
99
  product_id: Id;
105
100
  name: string;
106
101
  /** A `plan_terms` key in the Operations Plan, or none. */
@@ -109,23 +104,22 @@ export interface PlanOffering extends Versioned {
109
104
  ends_on: DateString;
110
105
  capacity: number | null;
111
106
  target_enrollment: number | null;
112
- fill_rate: number | null;
113
107
  /** Per kid. */
114
108
  price_cents: number | null;
115
109
  cogs_rate: number | null;
116
110
  note: string | null;
117
111
  }
118
112
  /** A program's settings in a Program Plan: who coaches it, and how many sailors each coach takes. */
119
- export interface PlanProgram extends Versioned {
113
+ export interface PlanProgram extends PlanRow {
120
114
  /** The program's product. */
121
115
  product_id: Id;
122
116
  /** A `plan_staff_levels` key. */
123
117
  staff_level_key: Id | null;
124
118
  sailors_per_coach: number | null;
125
- /** The planner's own words, which only their AI reads. */
126
- rules: string | null;
119
+ /** The planner's short notes, which only their AI reads. */
120
+ notes: string[] | null;
127
121
  }
128
- export interface PlanTrip extends Versioned {
122
+ export interface PlanTrip extends PlanRow {
129
123
  offering_id: Id;
130
124
  name: string;
131
125
  nights: number | null;
@@ -134,7 +128,7 @@ export interface PlanTrip extends Versioned {
134
128
  miles: number | null;
135
129
  }
136
130
  /** One dated activity, or a travel day, as the planner or a scheduling tool entered it. */
137
- export interface PlanActivity extends Versioned {
131
+ export interface PlanActivity extends PlanRow {
138
132
  offering_id: Id;
139
133
  /** The trip a travel day belongs to. */
140
134
  trip_id: Id | null;
@@ -150,12 +144,12 @@ export interface PlanActivity extends Versioned {
150
144
  block_id: Id | null;
151
145
  }
152
146
  /** Activities on one date that share their staff and boats. */
153
- export interface PlanActivityBlock extends Versioned {
147
+ export interface PlanActivityBlock extends PlanRow {
154
148
  date: DateString;
155
149
  name: string;
156
150
  }
157
151
  /** Staff on a block: a number at one staff level, or a salaried position. */
158
- export interface PlanBlockStaff extends Versioned {
152
+ export interface PlanBlockStaff extends PlanRow {
159
153
  block_id: Id;
160
154
  /** A `plan_staff_levels` key, for hourly or volunteer staff. */
161
155
  staff_level_key: Id | null;
@@ -166,14 +160,14 @@ export interface PlanBlockStaff extends Versioned {
166
160
  pay: "salary" | "hourly" | null;
167
161
  }
168
162
  /** Boats of one class on a block: the organization's, the ones it charters, and the ones sailors bring. */
169
- export interface PlanBlockBoats extends Versioned {
163
+ export interface PlanBlockBoats extends PlanRow {
170
164
  block_id: Id;
171
165
  boat_class_id: Id;
172
166
  count: number;
173
167
  sailor_owned: number | null;
174
168
  chartered?: number | null;
175
169
  }
176
- export interface PlanLaborLine extends Versioned {
170
+ export interface PlanLaborLine extends PlanRow {
177
171
  name: string;
178
172
  cost_center_type: CostCenterType;
179
173
  cost_center_key: Id | null;
@@ -184,7 +178,7 @@ export interface PlanLaborLine extends Versioned {
184
178
  function: AccountFunction | null;
185
179
  note: string | null;
186
180
  }
187
- export interface PlanBudgetLine extends Versioned {
181
+ export interface PlanBudgetLine extends PlanRow {
188
182
  name: string;
189
183
  account_id: Id;
190
184
  function: AccountFunction | null;
@@ -200,7 +194,7 @@ export interface PlanBudgetLine extends Versioned {
200
194
  * Something lasting the Operations Plan means to buy, ranked by `sort` with the most needed first.
201
195
  * Its account is an asset account.
202
196
  */
203
- export interface PlanCapitalItem extends Versioned {
197
+ export interface PlanCapitalItem extends PlanRow {
204
198
  name: string;
205
199
  amount_cents: number;
206
200
  account_id: Id;
@@ -208,19 +202,19 @@ export interface PlanCapitalItem extends Versioned {
208
202
  note?: string | null;
209
203
  link?: string | null;
210
204
  }
211
- /** The year's rates. An Operations plan version has at most one row. */
212
- export interface PlanRates extends Versioned {
205
+ /** The year's rates. An Operations Plan has at most one row. */
206
+ export interface PlanRates extends PlanRow {
213
207
  /** Employer payroll costs, as a share of wages. */
214
208
  payroll_tax_rate: number | null;
215
209
  lodging_per_night_cents: number | null;
216
210
  mileage_rate_cents: number | null;
217
211
  }
218
- export interface PlanStaffLevel extends Versioned {
212
+ export interface PlanStaffLevel extends PlanRow {
219
213
  name: string;
220
214
  pay_type: "hourly" | "volunteer";
221
215
  hourly_rate_cents: number | null;
222
216
  }
223
- export interface PlanSalariedPosition extends Versioned {
217
+ export interface PlanSalariedPosition extends PlanRow {
224
218
  name: string;
225
219
  annual_salary_cents: number;
226
220
  paid_hours_per_year: number | null;
@@ -231,15 +225,14 @@ export interface PlanSalariedPosition extends Versioned {
231
225
  hourly_coaching_rate_cents?: number | null;
232
226
  }
233
227
  /** Hours charged to a product, or to a program area and shared among its offerings: one of the two. */
234
- export interface PlanSalariedBooking extends Versioned {
228
+ export interface PlanSalariedBooking extends PlanRow {
235
229
  position_id: Id;
236
230
  product_id: Id | null;
237
231
  program_area_id: Id | null;
238
- kind: "management";
239
232
  hours: number;
240
233
  }
241
234
  /** A boat class's rates for the year. */
242
- export interface PlanBoatRate extends Versioned {
235
+ export interface PlanBoatRate extends PlanRow {
243
236
  boat_class_id: Id;
244
237
  /** Upkeep for each hour one of the organization's boats of the class is on the water. */
245
238
  maintenance_per_boat_hour_cents: number;
@@ -249,10 +242,10 @@ export interface PlanBoatRate extends Versioned {
249
242
  charter_per_day_cents?: number | null;
250
243
  }
251
244
  /** A group of shared costs, such as G&A, divided among all of the plan's offerings by MTDC. */
252
- export interface PlanPool extends Versioned {
245
+ export interface PlanPool extends PlanRow {
253
246
  name: string;
254
247
  }
255
- export interface PlanBonusOwner extends Versioned {
248
+ export interface PlanBonusOwner extends PlanRow {
256
249
  role_name: string;
257
250
  bonus_rate: number;
258
251
  account_id: Id;
@@ -261,12 +254,12 @@ export interface PlanBonusOwner extends Versioned {
261
254
  /** A `plan_salaried_positions` key: the position the bonus pays. */
262
255
  position_key?: Id | null;
263
256
  }
264
- export interface PlanBonusOwnerProduct extends Versioned {
257
+ export interface PlanBonusOwnerProduct extends PlanRow {
265
258
  bonus_owner_id: Id;
266
259
  product_id: Id;
267
260
  }
268
261
  /** A charge set as a share of each offering's revenue, such as scholarships or card fees. */
269
- export interface PlanRevenueCharge extends Versioned {
262
+ export interface PlanRevenueCharge extends PlanRow {
270
263
  name: string;
271
264
  /** A share, such as 0.02 for 2%. */
272
265
  rate: number;
@@ -279,17 +272,17 @@ export interface PlanRevenueCharge extends Versioned {
279
272
  note: string | null;
280
273
  }
281
274
  /** A day the organization is closed, such as Labor Day. */
282
- export interface PlanClosedDate extends Versioned {
275
+ export interface PlanClosedDate extends PlanRow {
283
276
  date: DateString;
284
277
  name: string;
285
278
  }
286
279
  /** A planning period in the Operations Plan, such as Summer 2027, which an offering can be in. */
287
- export interface PlanTerm extends Versioned {
280
+ export interface PlanTerm extends PlanRow {
288
281
  name: string;
289
282
  starts_on: DateString;
290
283
  ends_on: DateString;
291
284
  }
292
- /** The catalog plus every row of the plan versions being calculated. */
285
+ /** The catalog plus every row of the plans being calculated. */
293
286
  export interface PlanData {
294
287
  /** The plan year. Fiscal year N ends in calendar year N. */
295
288
  fiscal_year: number;
@@ -304,9 +297,8 @@ export interface PlanData {
304
297
  /** The asset list, with its boats, which the engine counts by class on each day. */
305
298
  assets: Asset[];
306
299
  boats: Boat[];
307
- /** The plans and versions being calculated, so the engine can tell which plan each offering is in. */
300
+ /** The plans being calculated, so the engine can tell which plan each row is in. */
308
301
  plans: Plan[];
309
- plan_versions: PlanVersion[];
310
302
  plan_programs: PlanProgram[];
311
303
  plan_offerings: PlanOffering[];
312
304
  plan_trips: PlanTrip[];
@@ -406,8 +398,8 @@ export interface EngineWarning {
406
398
  category: WarningCategory;
407
399
  /** The key of the row it's about, when there is one. */
408
400
  key?: string;
409
- /** The plan copy it's about, when there is one. */
410
- version_id?: Id;
401
+ /** The plan it's about, when there is one. */
402
+ plan_id?: Id;
411
403
  /** The date it's about, when there is one. */
412
404
  date?: DateString;
413
405
  }
@@ -21,6 +21,7 @@ export interface Schema {
21
21
  clubspot_sessions: ClubspotSessions[];
22
22
  contact_points: ContactPoints[];
23
23
  contacts: Contacts[];
24
+ financial_plans: FinancialPlans[];
24
25
  google_group_memberships: GoogleGroupMemberships[];
25
26
  google_groups: GoogleGroups[];
26
27
  group_members: GroupMembers[];
@@ -61,7 +62,6 @@ export interface Schema {
61
62
  plan_staff_levels: PlanStaffLevels[];
62
63
  plan_terms: PlanTerms[];
63
64
  plan_trips: PlanTrips[];
64
- plan_versions: PlanVersions[];
65
65
  plans: Plans[];
66
66
  products: Products[];
67
67
  program_area_role_assignments: ProgramAreaRoleAssignments[];
@@ -77,8 +77,6 @@ export interface Schema {
77
77
  sync_runs: SyncRuns[];
78
78
  sync_tasks: SyncTasks[];
79
79
  venues: Venues[];
80
- version_set_members: VersionSetMembers[];
81
- version_sets: VersionSets[];
82
80
  website_categories: WebsiteCategories[];
83
81
  website_categories_faq_topics: WebsiteCategoriesFaqTopics[];
84
82
  website_categories_files: WebsiteCategoriesFiles[];
@@ -147,6 +145,7 @@ export interface Assets {
147
145
  acquired_on: string | null;
148
146
  /** The day CSC sold, scrapped, or gave it away. Planning stops counting it on this day. Empty while CSC still has it. */
149
147
  disposed_on: string | null;
148
+ /** Up to 280 characters. Bold, italic, code and links work. */
150
149
  note: string | null;
151
150
  }
152
151
  /** Something a sync noticed but didn't fix. A dismissed finding stays dismissed on later runs. */
@@ -396,6 +395,36 @@ export interface Contacts {
396
395
  contact_order: number | null;
397
396
  relationship_detail: string | null;
398
397
  }
398
+ /** The organization's plan for a year: an Operations Plan and a Program Plan for any of the program areas. Change it while it's a draft. Freezing it keeps it, and its plans, from changing. Pick a parent when you add one, and its plans are copied in. */
399
+ export interface FinancialPlans {
400
+ id: string;
401
+ /** The organization the Financial Plan is for. */
402
+ organization_id: string | Organizations;
403
+ /** The year the Financial Plan is for. */
404
+ fiscal_year: number;
405
+ /** Such as 2027 budget as adopted. You can change it after it's frozen. */
406
+ name: string;
407
+ /** What it's for, such as "Reviewed with the board". Up to 280 characters. Bold, italic, code and links work. You can change it after it's frozen. */
408
+ note: string | null;
409
+ /** Set it to frozen to freeze the Financial Plan and its plans. A frozen one never changes, apart from its name, its note, the date it's active from, and whether it's archived. */
410
+ status: "draft" | "frozen";
411
+ /** The Financial Plan this one started as a copy of. Pick it when you add this one, and its plans are copied in. */
412
+ parent_id: string | FinancialPlans | null;
413
+ /** Set when the Financial Plan is frozen. */
414
+ frozen_by: string | null;
415
+ /** Set when the Financial Plan is frozen. */
416
+ frozen_at: string | null;
417
+ /** The version of the planning engine that calculated its numbers when it was frozen. */
418
+ platform_version: string | null;
419
+ /** The day the Financial Plan is in force from. Leave it empty for one that isn't active. On any day, the plan in force is the one for the year with the latest date on or before that day. A draft can be active. No two Financial Plans for a year can share a date. */
420
+ effective_from: string | null;
421
+ /** Set when the Financial Plan is archived, which hides it from Planning's list. Clear it to bring the plan back. The plan in force can't be archived. */
422
+ archived_at: string | null;
423
+ /** The organization's P&L. A draft's is worked out again a moment after each change to it. A frozen one's is worked out once, when it's frozen. One with none means working it out failed, and the logs say why. */
424
+ results: (string | PlanResults)[];
425
+ /** The plans it holds: one Operations Plan, and at most one Program Plan for each program area. Add or remove one only while it's a draft. */
426
+ plans: (string | Plans)[];
427
+ }
399
428
  /** One row for each person the sync has seen in a program's Google Group. Set Removed at to stop the sync adding them back. Delete the row to let the sync add them again. */
400
429
  export interface GoogleGroupMemberships {
401
430
  id: string;
@@ -562,9 +591,9 @@ export interface People {
562
591
  /** One dated activity, such as a practice, a regatta day, or a travel day. */
563
592
  export interface PlanActivities {
564
593
  id: string;
565
- /** The version this row belongs to. */
566
- version_id: string | PlanVersions;
567
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
594
+ /** The plan this row belongs to. */
595
+ plan_id: string | Plans;
596
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
568
597
  key: string;
569
598
  offering_id: string | PlanOfferings;
570
599
  /** The trip a travel day belongs to. */
@@ -583,9 +612,9 @@ export interface PlanActivities {
583
612
  /** Activities on one date that share their staff, RIBs and boats, such as Guppies and Minnows on the morning of July 13. */
584
613
  export interface PlanActivityBlocks {
585
614
  id: string;
586
- /** The version this row belongs to. */
587
- version_id: string | PlanVersions;
588
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
615
+ /** The plan this row belongs to. */
616
+ plan_id: string | Plans;
617
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
589
618
  key: string;
590
619
  date: string;
591
620
  /** Such as Guppies and Minnows, morning. */
@@ -594,9 +623,9 @@ export interface PlanActivityBlocks {
594
623
  /** Boats of one class a block sails: the organization's, and the ones sailors bring. */
595
624
  export interface PlanBlockBoats {
596
625
  id: string;
597
- /** The version this row belongs to. */
598
- version_id: string | PlanVersions;
599
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
626
+ /** The plan this row belongs to. */
627
+ plan_id: string | Plans;
628
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
600
629
  key: string;
601
630
  block_id: string | PlanActivityBlocks;
602
631
  boat_class_id: string | BoatClasses;
@@ -610,9 +639,9 @@ export interface PlanBlockBoats {
610
639
  /** Who coaches a block: a number of staff at one staff level, or a salaried position. */
611
640
  export interface PlanBlockStaff {
612
641
  id: string;
613
- /** The version this row belongs to. */
614
- version_id: string | PlanVersions;
615
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
642
+ /** The plan this row belongs to. */
643
+ plan_id: string | Plans;
644
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
616
645
  key: string;
617
646
  block_id: string | PlanActivityBlocks;
618
647
  /** For staff at a staff level: copy the Key from a staff level in the Operations Plan. Leave it empty for a salaried position. */
@@ -627,9 +656,9 @@ export interface PlanBlockStaff {
627
656
  /** What an hour of sailing costs in maintenance, for each boat class. */
628
657
  export interface PlanBoatRates {
629
658
  id: string;
630
- /** The version this row belongs to. */
631
- version_id: string | PlanVersions;
632
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
659
+ /** The plan this row belongs to. */
660
+ plan_id: string | Plans;
661
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
633
662
  key: string;
634
663
  boat_class_id: string | BoatClasses;
635
664
  /** Upkeep for each hour one of CSC's boats of the class is on the water. In cents: 12500 is $125.00. */
@@ -642,9 +671,9 @@ export interface PlanBoatRates {
642
671
  /** The products a bonus owner owns. */
643
672
  export interface PlanBonusOwnerProducts {
644
673
  id: string;
645
- /** The version this row belongs to. */
646
- version_id: string | PlanVersions;
647
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
674
+ /** The plan this row belongs to. */
675
+ plan_id: string | Plans;
676
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
648
677
  key: string;
649
678
  bonus_owner_id: string | PlanBonusOwners;
650
679
  product_id: string | Products;
@@ -652,9 +681,9 @@ export interface PlanBonusOwnerProducts {
652
681
  /** A role, such as a program area's director, that earns a bonus on the products it owns. */
653
682
  export interface PlanBonusOwners {
654
683
  id: string;
655
- /** The version this row belongs to. */
656
- version_id: string | PlanVersions;
657
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
684
+ /** The plan this row belongs to. */
685
+ plan_id: string | Plans;
686
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
658
687
  key: string;
659
688
  role_name: string;
660
689
  /** A share of its products' net. 0.05 means 5%. */
@@ -668,9 +697,9 @@ export interface PlanBonusOwners {
668
697
  /** A cost or revenue entered as an amount. */
669
698
  export interface PlanBudgetLines {
670
699
  id: string;
671
- /** The version this row belongs to. */
672
- version_id: string | PlanVersions;
673
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
700
+ /** The plan this row belongs to. */
701
+ plan_id: string | Plans;
702
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
674
703
  key: string;
675
704
  name: string;
676
705
  account_id: string | Accounts;
@@ -683,14 +712,15 @@ export interface PlanBudgetLines {
683
712
  fiscal_year: number;
684
713
  /** Counts toward modified total direct costs (MTDC). Clear it for a cost 2 CFR 200.1 leaves out, such as equipment. */
685
714
  in_mtdc: boolean;
715
+ /** Up to 280 characters. Bold, italic, code and links work. */
686
716
  note: string | null;
687
717
  }
688
718
  /** Something lasting CSC means to buy, ranked with the most needed first. */
689
719
  export interface PlanCapitalItems {
690
720
  id: string;
691
- /** The version this row belongs to. Capital goes in the Operations Plan's. */
692
- version_id: string | PlanVersions;
693
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
721
+ /** The plan this row belongs to. */
722
+ plan_id: string | Plans;
723
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
694
724
  key: string;
695
725
  /** What to buy, such as a coach boat. */
696
726
  name: string;
@@ -700,7 +730,7 @@ export interface PlanCapitalItems {
700
730
  account_id: string | Accounts;
701
731
  /** 1 is the most needed. Drag rows in the table to reorder them. */
702
732
  sort: number | null;
703
- /** Why CSC needs it. */
733
+ /** Up to 280 characters. Bold, italic, code and links work. */
704
734
  note: string | null;
705
735
  /** A quote or listing for it, as a web address. */
706
736
  link: string | null;
@@ -708,9 +738,9 @@ export interface PlanCapitalItems {
708
738
  /** A day the organization is closed, such as Labor Day. The P&L warns about an activity on one. */
709
739
  export interface PlanClosedDates {
710
740
  id: string;
711
- /** The version this row belongs to. */
712
- version_id: string | PlanVersions;
713
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
741
+ /** The plan this row belongs to. */
742
+ plan_id: string | Plans;
743
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
714
744
  key: string;
715
745
  date: string;
716
746
  /** Why it's closed, such as Labor Day. */
@@ -719,9 +749,9 @@ export interface PlanClosedDates {
719
749
  /** Labor not tied to an activity, such as training, priced at a staff level. */
720
750
  export interface PlanLaborLines {
721
751
  id: string;
722
- /** The version this row belongs to. */
723
- version_id: string | PlanVersions;
724
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
752
+ /** The plan this row belongs to. */
753
+ plan_id: string | Plans;
754
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
725
755
  key: string;
726
756
  name: string;
727
757
  cost_center_type: "offering" | "product" | "program_area" | "pool" | "org";
@@ -732,14 +762,15 @@ export interface PlanLaborLines {
732
762
  hours: string;
733
763
  account_id: string | Accounts;
734
764
  function: "program" | "management" | "fundraising" | null;
765
+ /** Up to 280 characters. Bold, italic, code and links work. */
735
766
  note: string | null;
736
767
  }
737
768
  /** A product sold for a period, such as a season or a week. */
738
769
  export interface PlanOfferings {
739
770
  id: string;
740
- /** The version this row belongs to. */
741
- version_id: string | PlanVersions;
742
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
771
+ /** The plan this row belongs to. */
772
+ plan_id: string | Plans;
773
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
743
774
  key: string;
744
775
  product_id: string | Products;
745
776
  name: string;
@@ -750,29 +781,28 @@ export interface PlanOfferings {
750
781
  capacity: number | null;
751
782
  /** Whole kids. Or set Fill rate instead. */
752
783
  target_enrollment: number | null;
753
- /** A share of capacity, used when Target enrollment is empty. 0.8 means 80%. */
754
- fill_rate: string | null;
755
784
  /** Per kid. In cents: 12500 is $125.00. */
756
785
  price_cents: number | null;
757
786
  /** Cost of goods sold, as a share of revenue. 0.1 means 10%. */
758
787
  cogs_rate: string | null;
788
+ /** Up to 280 characters. Bold, italic, code and links work. */
759
789
  note: string | null;
760
790
  }
761
791
  /** Shared costs no single offering owns, such as G&A, divided among the year's offerings by MTDC. */
762
792
  export interface PlanPools {
763
793
  id: string;
764
- /** The version this row belongs to. */
765
- version_id: string | PlanVersions;
766
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
794
+ /** The plan this row belongs to. */
795
+ plan_id: string | Plans;
796
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
767
797
  key: string;
768
798
  name: string;
769
799
  }
770
- /** A program's settings in a Program Plan: the staff level that coaches it, how many sailors each coach can take, and its rules. */
800
+ /** A program's settings in a Program Plan: the staff level that coaches it, how many sailors each coach can take, and its notes. */
771
801
  export interface PlanPrograms {
772
802
  id: string;
773
- /** The version this row belongs to. */
774
- version_id: string | PlanVersions;
775
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
803
+ /** The plan this row belongs to. */
804
+ plan_id: string | Plans;
805
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
776
806
  key: string;
777
807
  /** The program. */
778
808
  product_id: string | Products;
@@ -780,15 +810,15 @@ export interface PlanPrograms {
780
810
  staff_level_key: string | null;
781
811
  /** How many sailors one coach at its staff level can safely take. */
782
812
  sailors_per_coach: string | null;
783
- /** What you want for this program that its numbers don't capture, for your AI to follow. For example: if there are more than 12 Guppies, add a Junior Instructor. */
784
- rules: string | null;
813
+ /** The program's rules, for your AI to follow in each of its offerings. Short notes, one a line, each up to 280 characters. Bold, italic, code and links work. */
814
+ notes: string[] | null;
785
815
  }
786
- /** The year's rates. One row at most in each copy. */
816
+ /** The year's rates. One row at most in each plan. */
787
817
  export interface PlanRates {
788
818
  id: string;
789
- /** The version this row belongs to. */
790
- version_id: string | PlanVersions;
791
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
819
+ /** The plan this row belongs to. */
820
+ plan_id: string | Plans;
821
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
792
822
  key: string;
793
823
  /** Employer payroll costs, as a share of wages. 0.1 means 10%. */
794
824
  payroll_tax_rate: string | null;
@@ -797,7 +827,7 @@ export interface PlanRates {
797
827
  /** In cents: 12500 is $125.00. */
798
828
  mileage_rate_cents: number | null;
799
829
  }
800
- /** Each bonus in a Financial Plan, for each term, and for the offerings in no term on the year. A draft's are worked out again a moment after each change to it, and a saved one's when it's saved, and then never change. */
830
+ /** Each bonus in a Financial Plan, for each term, and for the offerings in no term on the year. A draft's are worked out again a moment after each change to it, and a frozen one's when it's frozen, and then never change. */
801
831
  export interface PlanResultBonuses {
802
832
  id: string;
803
833
  /** The bonus's role, such as Director. */
@@ -816,15 +846,15 @@ export interface PlanResultBonuses {
816
846
  bonus_owner_key: string;
817
847
  program_area_key: string | null;
818
848
  /** The Financial Plan these results are for. */
819
- version_set_id: string | VersionSets;
820
- /** The plan in the Financial Plan that pays the bonus. */
821
- version_set_member_id: string | VersionSetMembers | null;
849
+ financial_plan_id: string | FinancialPlans;
850
+ /** The plan it's charged in, or about. */
851
+ plan_id: string | Plans | null;
822
852
  }
823
- /** The capital list in rank order, in a Financial Plan, with what the year's money covers. A draft's are worked out again a moment after each change to it, and a saved one's when it's saved, and then never change. */
853
+ /** The capital list in rank order, in a Financial Plan, with what the year's money covers. A draft's are worked out again a moment after each change to it, and a frozen one's when it's frozen, and then never change. */
824
854
  export interface PlanResultCapital {
825
855
  id: string;
826
856
  /** The Financial Plan these results are for. */
827
- version_set_id: string | VersionSets;
857
+ financial_plan_id: string | FinancialPlans;
828
858
  /** 1 is the most needed. */
829
859
  rank: number | null;
830
860
  item_key: string;
@@ -836,7 +866,7 @@ export interface PlanResultCapital {
836
866
  /** Whether what's left after operations and fundraising covers it, with everything ranked above it. */
837
867
  funded: boolean | null;
838
868
  }
839
- /** Each cost and revenue line in a Financial Plan's P&L. A draft's are worked out again a moment after each change to it, and a saved one's when it's saved, and then never change. */
869
+ /** Each cost and revenue line in a Financial Plan's P&L. A draft's are worked out again a moment after each change to it, and a frozen one's when it's frozen, and then never change. */
840
870
  export interface PlanResultLines {
841
871
  id: string;
842
872
  cost_center_type: "offering" | "product" | "program_area" | "pool" | "org";
@@ -850,13 +880,13 @@ export interface PlanResultLines {
850
880
  line: string;
851
881
  explanation: string | null;
852
882
  /** The Financial Plan these results are for. */
853
- version_set_id: string | VersionSets;
854
- /** The plan in the Financial Plan that holds what it's charged to. */
855
- version_set_member_id: string | VersionSetMembers | null;
883
+ financial_plan_id: string | FinancialPlans;
884
+ /** The plan it's charged in, or about. */
885
+ plan_id: string | Plans | null;
856
886
  /** What the line is, in plain words, such as G&A share: Payroll tax. */
857
887
  line_name: string | null;
858
888
  }
859
- /** A Financial Plan's P&L for each offering, product, and program area. A draft's are worked out again a moment after each change to it, and a saved one's when it's saved, and then never change. */
889
+ /** A Financial Plan's P&L for each offering, product, and program area. A draft's are worked out again a moment after each change to it, and a frozen one's when it's frozen, and then never change. */
860
890
  export interface PlanResultPnl {
861
891
  id: string;
862
892
  level: "offering" | "product" | "program_area" | "program_area_term";
@@ -874,9 +904,9 @@ export interface PlanResultPnl {
874
904
  days: number;
875
905
  sailor_days: number;
876
906
  /** The Financial Plan these results are for. */
877
- version_set_id: string | VersionSets;
878
- /** The plan in the Financial Plan that holds what it's charged to. */
879
- version_set_member_id: string | VersionSetMembers | null;
907
+ financial_plan_id: string | FinancialPlans;
908
+ /** The plan it's charged in, or about. */
909
+ plan_id: string | Plans | null;
880
910
  term_key: string | null;
881
911
  /** For a program area by term, the term. Empty for its offerings in no term, such as Adult Learn to Sail. */
882
912
  term_name: string | null;
@@ -887,7 +917,7 @@ export interface PlanResultPnl {
887
917
  /** The fewest sailors, up to its capacity, at which an offering pays for itself, with its share of shared costs as planned. Empty when it doesn't even when full, and for a product or program area. */
888
918
  breakeven_enrollment: number | null;
889
919
  }
890
- /** Each salaried position's pay for the year, in a Financial Plan. A draft's are worked out again a moment after each change to it, and a saved one's when it's saved, and then never change. */
920
+ /** Each salaried position's pay for the year, in a Financial Plan. A draft's are worked out again a moment after each change to it, and a frozen one's when it's frozen, and then never change. */
891
921
  export interface PlanResultPositions {
892
922
  id: string;
893
923
  position_key: string;
@@ -910,13 +940,13 @@ export interface PlanResultPositions {
910
940
  /** The year's payroll tax on the total pay. In cents: 12500 is $125.00. */
911
941
  payroll_tax_cents: number;
912
942
  /** The Financial Plan these results are for. */
913
- version_set_id: string | VersionSets;
943
+ financial_plan_id: string | FinancialPlans;
914
944
  /** Hours another coach would cover while this position coaches two or more groups at once: for three groups at once, two coaches. */
915
945
  double_booked_hours: string | null;
916
946
  /** Dates this position coaches two or more groups at once. */
917
947
  double_booked_dates: number | null;
918
948
  }
919
- /** What a Financial Plan's P&L warns about. A draft's are worked out again a moment after each change to it, and a saved one's when it's saved, and then never change. */
949
+ /** What a Financial Plan's P&L warns about. A draft's are worked out again a moment after each change to it, and a frozen one's when it's frozen, and then never change. */
920
950
  export interface PlanResultWarnings {
921
951
  id: string;
922
952
  /** The kind of warning. */
@@ -924,15 +954,15 @@ export interface PlanResultWarnings {
924
954
  message: string;
925
955
  subject_key: string | null;
926
956
  /** The Financial Plan these results are for. */
927
- version_set_id: string | VersionSets;
928
- /** The plan in the Financial Plan it's about. */
929
- version_set_member_id: string | VersionSetMembers | null;
957
+ financial_plan_id: string | FinancialPlans;
958
+ /** The plan it's charged in, or about. */
959
+ plan_id: string | Plans | null;
930
960
  /** The date it's about. */
931
961
  date: string | null;
932
962
  /** Allocation: the blocks break a limit, or run on a closed day. Data: a row is missing or wrong. */
933
963
  category: "allocation" | "data" | null;
934
964
  }
935
- /** A Financial Plan's totals for the organization. A draft's are worked out again a moment after each change to it, and a saved one's when it's saved, and then never change. */
965
+ /** A Financial Plan's totals for the organization. A draft's are worked out again a moment after each change to it, and a frozen one's when it's frozen, and then never change. */
936
966
  export interface PlanResults {
937
967
  id: string;
938
968
  calculated_at: string;
@@ -953,14 +983,14 @@ export interface PlanResults {
953
983
  /** Operating net, plus fundraising net, less capital. In cents: 12500 is $125.00. */
954
984
  total_cents: number;
955
985
  /** The Financial Plan these results are for. */
956
- version_set_id: string | VersionSets;
986
+ financial_plan_id: string | FinancialPlans;
957
987
  }
958
988
  /** A charge set as a share of each offering's revenue, such as scholarships or card fees. Operations Plan only. */
959
989
  export interface PlanRevenueCharges {
960
990
  id: string;
961
- /** The version this row belongs to. */
962
- version_id: string | PlanVersions;
963
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
991
+ /** The plan this row belongs to. */
992
+ plan_id: string | Plans;
993
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
964
994
  key: string;
965
995
  name: string;
966
996
  /** The share of each offering's revenue: 0.02 means 2%. */
@@ -971,29 +1001,29 @@ export interface PlanRevenueCharges {
971
1001
  account_id: string | Accounts;
972
1002
  /** Counts toward MTDC. Clear it for a charge 2 CFR 200.1 leaves out, such as scholarships. */
973
1003
  in_mtdc: boolean;
1004
+ /** Up to 280 characters. Bold, italic, code and links work. */
974
1005
  note: string | null;
975
1006
  }
976
1007
  /** Hours of a salaried position booked to a product. */
977
1008
  export interface PlanSalariedBookings {
978
1009
  id: string;
979
- /** The version this row belongs to. */
980
- version_id: string | PlanVersions;
981
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
1010
+ /** The plan this row belongs to. */
1011
+ plan_id: string | Plans;
1012
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
982
1013
  key: string;
983
1014
  position_id: string | PlanSalariedPositions;
984
1015
  /** The product the hours are for. Set this or the program area, not both. */
985
1016
  product_id: string | Products | null;
986
1017
  /** The program area the hours are for, shared among its offerings. Set this or the product, not both. */
987
1018
  program_area_id: string | ProgramAreas | null;
988
- kind: "management";
989
1019
  hours: string;
990
1020
  }
991
1021
  /** A salaried position, and where the part of its salary not charged to programs goes. */
992
1022
  export interface PlanSalariedPositions {
993
1023
  id: string;
994
- /** The version this row belongs to. */
995
- version_id: string | PlanVersions;
996
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
1024
+ /** The plan this row belongs to. */
1025
+ plan_id: string | Plans;
1026
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
997
1027
  key: string;
998
1028
  name: string;
999
1029
  /** In cents: 12500 is $125.00. */
@@ -1009,9 +1039,9 @@ export interface PlanSalariedPositions {
1009
1039
  /** A level hourly staff are paid at, such as head coach. */
1010
1040
  export interface PlanStaffLevels {
1011
1041
  id: string;
1012
- /** The version this row belongs to. */
1013
- version_id: string | PlanVersions;
1014
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
1042
+ /** The plan this row belongs to. */
1043
+ plan_id: string | Plans;
1044
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
1015
1045
  key: string;
1016
1046
  name: string;
1017
1047
  pay_type: "hourly" | "volunteer";
@@ -1021,9 +1051,9 @@ export interface PlanStaffLevels {
1021
1051
  /** A planning period, such as Summer 2027, with its start and end dates. An offering in a Program Plan can be in one of its Financial Plan's terms. */
1022
1052
  export interface PlanTerms {
1023
1053
  id: string;
1024
- /** The version this row belongs to. */
1025
- version_id: string | PlanVersions;
1026
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
1054
+ /** The plan this row belongs to. */
1055
+ plan_id: string | Plans;
1056
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
1027
1057
  key: string;
1028
1058
  /** What it's called, such as Summer 2027. */
1029
1059
  name: string;
@@ -1035,9 +1065,9 @@ export interface PlanTerms {
1035
1065
  /** A trip an offering travels for, such as an away regatta. */
1036
1066
  export interface PlanTrips {
1037
1067
  id: string;
1038
- /** The version this row belongs to. */
1039
- version_id: string | PlanVersions;
1040
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
1068
+ /** The plan this row belongs to. */
1069
+ plan_id: string | Plans;
1070
+ /** Stays the same when a Financial Plan is copied. Copy it where another row asks for this one's Key. */
1041
1071
  key: string;
1042
1072
  offering_id: string | PlanOfferings;
1043
1073
  name: string;
@@ -1046,38 +1076,16 @@ export interface PlanTrips {
1046
1076
  airfare_cents: number | null;
1047
1077
  miles: number | null;
1048
1078
  }
1049
- /** One plan's copy in one Financial Plan, with every row it holds. It's saved with its Financial Plan, and then never changes. */
1050
- export interface PlanVersions {
1051
- id: string;
1052
- plan_id: string | Plans;
1053
- /** The copy this one was made from, in the Financial Plan this one's started from. */
1054
- parent_version_id: string | PlanVersions | null;
1055
- /** Saved with its Financial Plan. A saved copy never changes, even for an admin. */
1056
- status: "draft" | "saved";
1057
- /** What changed in this version. */
1058
- message: string | null;
1059
- /** Set when its Financial Plan is saved. */
1060
- saved_by: string | null;
1061
- /** Set when its Financial Plan is saved. */
1062
- saved_at: string | null;
1063
- /** The version of the planning engine that calculated its numbers. Required to save. */
1064
- platform_version: string | null;
1065
- /** What you want that the plan's numbers don't capture, for your AI to fill in. For example: every Monday, Wednesday and Friday, except on SPS school holidays, from 4pm until dusk. */
1066
- rules: string | null;
1067
- }
1068
- /** One part of a Financial Plan: a program area's Program Plan, or the Operations Plan. */
1079
+ /** One part of a Financial Plan, with every row it holds: a program area's Program Plan, or the Operations Plan. */
1069
1080
  export interface Plans {
1070
1081
  id: string;
1082
+ /** What you want that the plan's numbers don't capture, for your AI to fill in. Short notes, one a line, each up to 280 characters. Bold, italic, code and links work. */
1083
+ notes: string[] | null;
1084
+ /** The Financial Plan it's part of. It can't move to another. */
1085
+ financial_plan_id: string | FinancialPlans;
1071
1086
  kind: "program_area" | "operations";
1072
1087
  /** The program area the plan is for. Empty for the Operations Plan. */
1073
1088
  program_area_id: string | ProgramAreas | null;
1074
- /** The organization the plan is for. Can't change once the plan has a saved copy. */
1075
- organization_id: string | Organizations;
1076
- /** The plan year. Fiscal year N ends in calendar year N. Can't change once the plan has a saved copy. */
1077
- fiscal_year: number;
1078
- name: string;
1079
- /** The plan's copy in each Financial Plan. */
1080
- versions: (string | PlanVersions)[];
1081
1089
  }
1082
1090
  /** A lasting thing the club sells. A program also has a row in Programs. */
1083
1091
  export interface Products {
@@ -1226,45 +1234,6 @@ export interface Venues {
1226
1234
  /** Other names it goes by, such as on a regatta's notice of race. */
1227
1235
  aliases: string[] | null;
1228
1236
  }
1229
- /** One plan in a Financial Plan: the copy of the plan it holds. Add or remove one only while the Financial Plan is a draft. */
1230
- export interface VersionSetMembers {
1231
- id: string;
1232
- version_set_id: string | VersionSets;
1233
- /** Filled in from the plan copy. */
1234
- plan_id: string | Plans | null;
1235
- /** The plan's copy in this Financial Plan. It's in no other Financial Plan. */
1236
- version_id: string | PlanVersions;
1237
- }
1238
- /** The organization's plan for a year: one Operations Plan, and a Program Plan for any of the program areas, with the P&L they make together. Change its plans while it's a draft. Its P&L is worked out again a moment after each change. Saving it saves them all, and then it never changes, apart from its name and note. Pick a parent when you add one, and its plans are copied in. */
1239
- export interface VersionSets {
1240
- id: string;
1241
- /** The organization the Financial Plan is for. */
1242
- organization_id: string | Organizations;
1243
- /** The year the Financial Plan is for. */
1244
- fiscal_year: number;
1245
- /** Such as 2027 budget as adopted. You can change it after it's saved. */
1246
- name: string;
1247
- /** What it was for, such as "Reviewed with the board". You can change it after it's saved. */
1248
- message: string | null;
1249
- /** Set it to saved to save the Financial Plan and all its plans. A saved Financial Plan never changes, apart from its name, its note, the date it's active from, and whether it's archived. */
1250
- status: "draft" | "saved";
1251
- /** The Financial Plan this one started as a copy of. Pick it when you add this one, and its plans are copied in. */
1252
- parent_id: string | VersionSets | null;
1253
- /** Set when the Financial Plan is saved. */
1254
- saved_by: string | null;
1255
- /** Set when the Financial Plan is saved. */
1256
- saved_at: string | null;
1257
- /** The version of the planning engine that calculated its numbers. */
1258
- platform_version: string | null;
1259
- /** The day the Financial Plan is in force from. Leave it empty for one that isn't active. On any day, the plan in force is the one for the year with the latest date on or before that day. A draft can be active. No two Financial Plans for a year can share a date. */
1260
- effective_from: string | null;
1261
- /** Set when the Financial Plan is archived, which hides it from Planning's list. Clear it to bring the plan back. The plan in force can't be archived. */
1262
- archived_at: string | null;
1263
- /** Each plan in the Financial Plan. Add or remove one only while it's a draft. */
1264
- members: (string | VersionSetMembers)[];
1265
- /** The organization's P&L. A draft's is worked out again a moment after each change to it. A saved one's is what it showed when it was saved, worked out a moment after. One with none means working it out failed, and the logs say why. An admin can fill in a saved one's. */
1266
- results: (string | PlanResults)[];
1267
- }
1268
1237
  /** Program categories, such as Summer Sailing Camps, each with a page at /program-category/ and its slug. */
1269
1238
  export interface WebsiteCategories {
1270
1239
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyc-seattle/platform",
3
- "version": "1.53.0",
3
+ "version": "1.55.0",
4
4
  "description": "Pulumi components for CYC's shared Google Cloud platform: WebService, the platform outputs reader, and the stacks that build the platform",
5
5
  "author": "CYC Community Sailing Center",
6
6
  "license": "Apache-2.0",
@@ -81,6 +81,7 @@
81
81
  "@types/js-yaml": "4.0.9",
82
82
  "@types/luxon": "^3.7.6",
83
83
  "@types/node": "^22.19.1",
84
+ "commander": "13.1.0",
84
85
  "esbuild": "0.28.2",
85
86
  "eslint": "^9.39.5",
86
87
  "google-auth-library": "^11.3.0",