@cyc-seattle/platform 1.38.0 → 1.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -32,7 +32,10 @@ export function scheduleActivities(ctx) {
|
|
|
32
32
|
const span = start !== null && end !== null ? end - start : 0;
|
|
33
33
|
const closedFor = closed.get(activity.date);
|
|
34
34
|
if (closedFor !== undefined && activity.kind !== "travel") {
|
|
35
|
-
ctx.warn("closed_date", `${offering.name}
|
|
35
|
+
ctx.warn("closed_date", `${offering.name}'s ${activity.kind} falls on ${closedFor}, a closed day`, activity.key, {
|
|
36
|
+
version_id: activity.version_id,
|
|
37
|
+
date: activity.date,
|
|
38
|
+
});
|
|
36
39
|
}
|
|
37
40
|
const usesBoats = activity.kind !== "travel" && activity.uses_boats;
|
|
38
41
|
result.push({
|
|
@@ -10,6 +10,22 @@ function sum(values) {
|
|
|
10
10
|
total += v;
|
|
11
11
|
return total;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* `n` of something, in words: "9 Sailqubes", "1 Opti". Only a one-word name is made plural, and not one that already
|
|
15
|
+
* ends in "s", such as RIBs; any other name is used as given, since adding an "s" to "Laser Radial" reads wrong.
|
|
16
|
+
*/
|
|
17
|
+
function countOf(n, name) {
|
|
18
|
+
const plural = n !== 1 && /^\S+$/.test(name) && !/s$/i.test(name);
|
|
19
|
+
return `${n} ${plural ? `${name}s` : name}`;
|
|
20
|
+
}
|
|
21
|
+
/** Names as a sentence lists them: `a`, `a and b`, or `a, b, and c`. */
|
|
22
|
+
function listOf(names) {
|
|
23
|
+
return names.length <= 2 ? names.join(" and ") : `${names.slice(0, -1).join(", ")}, and ${names.at(-1)}`;
|
|
24
|
+
}
|
|
25
|
+
/** The offerings a block holds, by name, so a warning about it says whose it is. */
|
|
26
|
+
function offeringsOf(block) {
|
|
27
|
+
return listOf([...new Set(block.members.map((a) => a.offering.name))]);
|
|
28
|
+
}
|
|
13
29
|
function groupBy(rows, key) {
|
|
14
30
|
const groups = new Map();
|
|
15
31
|
for (const row of rows)
|
|
@@ -71,7 +87,7 @@ export class Staffing {
|
|
|
71
87
|
continue;
|
|
72
88
|
}
|
|
73
89
|
if (row.date !== a.date) {
|
|
74
|
-
this.ctx.warn("block_date_mismatch", `${a.offering.name}'s
|
|
90
|
+
this.ctx.warn("block_date_mismatch", `${a.offering.name}'s ${a.kind} is in ${row.name}, which is on ${row.date}`, a.stored.key, { version_id: a.stored.version_id, date: a.date });
|
|
75
91
|
}
|
|
76
92
|
members.set(id, [...(members.get(id) ?? []), a]);
|
|
77
93
|
}
|
|
@@ -114,7 +130,7 @@ export class Staffing {
|
|
|
114
130
|
const block = a.stored.block_id == null ? undefined : blocks.get(a.stored.block_id);
|
|
115
131
|
if (block !== undefined && sum(block.staff.map((s) => s.count)) > 0)
|
|
116
132
|
continue;
|
|
117
|
-
this.ctx.warn("unallocated_activity", `${a.offering.name}'s ${a.kind}
|
|
133
|
+
this.ctx.warn("unallocated_activity", `${a.offering.name}'s ${a.kind} has no staff`, a.stored.key, {
|
|
118
134
|
version_id: a.stored.version_id,
|
|
119
135
|
date: a.date,
|
|
120
136
|
});
|
|
@@ -146,7 +162,7 @@ export class Staffing {
|
|
|
146
162
|
if (sailors <= 0)
|
|
147
163
|
return;
|
|
148
164
|
const scope = { version_id: block.row.version_id, date: block.row.date };
|
|
149
|
-
const where =
|
|
165
|
+
const where = offeringsOf(block);
|
|
150
166
|
let load = 0;
|
|
151
167
|
let counted = 0;
|
|
152
168
|
const required = [];
|
|
@@ -172,7 +188,7 @@ export class Staffing {
|
|
|
172
188
|
return required.some((r) => this.qualifiedFor(held, level, r)) ? s.count : 0;
|
|
173
189
|
}));
|
|
174
190
|
if (load > qualified + 1e-9) {
|
|
175
|
-
ctx.warn("over_capacity", `${where}: ${counted} sailors, but
|
|
191
|
+
ctx.warn("over_capacity", `${where}: ${counted} sailors, but the staff can take ${Math.floor((qualified * counted) / load + 1e-9)}`, block.row.key, scope);
|
|
176
192
|
}
|
|
177
193
|
}
|
|
178
194
|
if (!block.members.some((a) => a.uses_boats))
|
|
@@ -188,7 +204,7 @@ export class Staffing {
|
|
|
188
204
|
seats += (b.count + (b.sailor_owned ?? 0)) * perBoat;
|
|
189
205
|
}
|
|
190
206
|
if (sailors > seats) {
|
|
191
|
-
ctx.warn("over_capacity", `${where}: ${sailors} sailors, but
|
|
207
|
+
ctx.warn("over_capacity", `${where}: ${sailors} sailors, but the boats can take ${seats}`, block.row.key, scope);
|
|
192
208
|
}
|
|
193
209
|
}
|
|
194
210
|
/**
|
|
@@ -201,6 +217,7 @@ export class Staffing {
|
|
|
201
217
|
const classes = new Map(ctx.data.boat_classes.map((c) => [c.id, c]));
|
|
202
218
|
const fleet = new Map(ctx.data.plan_boat_rates.map((r) => [r.boat_class_id, r.quantity]));
|
|
203
219
|
const resources = new Map(ctx.data.plan_resources.map((r) => [r.key, r]));
|
|
220
|
+
const organization = ctx.data.organization.name;
|
|
204
221
|
for (const [date, list] of dates) {
|
|
205
222
|
const check = (code, what, have, used) => {
|
|
206
223
|
if (have == null)
|
|
@@ -209,9 +226,12 @@ export class Staffing {
|
|
|
209
226
|
const peak = mostAtOnce(out);
|
|
210
227
|
if (peak.total <= have)
|
|
211
228
|
return;
|
|
212
|
-
const
|
|
229
|
+
const whose = peak.at.length === 1
|
|
230
|
+
? `, for ${offeringsOf(peak.at[0].block)}`
|
|
231
|
+
: `: ${listOf(peak.at.map(({ block, n }) => `${n} for ${offeringsOf(block)}`))}`;
|
|
232
|
+
const message = `${countOf(peak.total, what)} out, but ${organization} has ${have}${whose}`;
|
|
213
233
|
for (const version of new Set(peak.at.map(({ block }) => block.row.version_id))) {
|
|
214
|
-
ctx.warn(code,
|
|
234
|
+
ctx.warn(code, message, what, {
|
|
215
235
|
version_id: version,
|
|
216
236
|
date: date,
|
|
217
237
|
});
|
|
@@ -258,7 +278,7 @@ export class Staffing {
|
|
|
258
278
|
const total = result.get(key) ?? { hours: 0, dates: 0 };
|
|
259
279
|
result.set(key, { hours: total.hours + hours, dates: total.dates + 1 });
|
|
260
280
|
for (const version of new Set(list.map((b) => b.row.version_id))) {
|
|
261
|
-
this.ctx.warn("position_double_booked", `${names.get(key) ?? key} coaches ${clash[0].row.name} and ${clash[1].row.name} at the same time
|
|
281
|
+
this.ctx.warn("position_double_booked", `${names.get(key) ?? key} coaches ${clash[0].row.name} and ${clash[1].row.name} at the same time` +
|
|
262
282
|
(hours > 0 ? `, so another coach is needed for ${round2(hours)} hours` : ""), key, { version_id: version, date });
|
|
263
283
|
}
|
|
264
284
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyc-seattle/platform",
|
|
3
|
-
"version": "1.38.
|
|
3
|
+
"version": "1.38.1",
|
|
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",
|