placementt-core 1.400.985 → 1.400.987

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.
@@ -0,0 +1,443 @@
1
+ "use strict";
2
+ /* eslint-disable max-len */
3
+ /**
4
+ * surveys/registry.ts — what makes each survey kind different.
5
+ *
6
+ * The engine (functions/src/surveys.ts) and the web hooks are kind-agnostic: they
7
+ * read behaviour from here. Everything a kind needs that isn't the data shape lives
8
+ * in one SurveyKindDef — auth mode, who can answer, thread position, message types,
9
+ * URL builders and email copy.
10
+ *
11
+ * Copy rule: the student/parent invite and reminder wording of a MIGRATED kind must
12
+ * not change. Every string below marked "(migrated)" is lifted character-for-character
13
+ * from the notifications file it replaces:
14
+ * aspirations ← notifications/aspirations.ts
15
+ * leaver* ← notifications/leavers.ts (incl. CHECKPOINT_COPY)
16
+ * alumniCheckIn ← notifications/alumni.ts
17
+ * skills ← notifications/skillsAssessments.ts
18
+ * Strings marked "(new)" have no pre-migration equivalent — parent tracks on kinds
19
+ * that never had one, and the staff heads-up email.
20
+ *
21
+ * Core stays UI-free: form components and UniversalAnalyticsPage schemas are
22
+ * registered web-side, keyed by the same SurveyKind.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.surveyEmailCopy = exports.legacySupersedingMessageTypes = exports.supersedingMessageTypes = exports.supersedingSurveyKinds = exports.surveyThreadKinds = exports.isSurveyKind = exports.surveyKindDef = exports.SURVEY_KINDS = exports.SURVEY_THREADS = exports.SURVEY_HEADS_UP_COPY = exports.absoluteSurveyUrl = exports.SURVEY_SITE_BASE_URL = void 0;
26
+ const types_1 = require("./types");
27
+ /** Where survey links point. The notifications files each hardcoded this. */
28
+ exports.SURVEY_SITE_BASE_URL = "https://careerthread.co.uk";
29
+ /** Prefix a survey path with the site origin. */
30
+ const absoluteSurveyUrl = (path) => `${exports.SURVEY_SITE_BASE_URL}${path}`;
31
+ exports.absoluteSurveyUrl = absoluteSurveyUrl;
32
+ // ─── Shared copy ──────────────────────────────────────────────────────────────
33
+ const studentFullName = (ctx) => `${ctx.studentForename ?? ""} ${ctx.studentSurname ?? ""}`.trim();
34
+ /**
35
+ * (new) The parent track's copy, shared by every kind that doesn't override it —
36
+ * one template family across kinds, parameterised by the kind's label (D7). Skills
37
+ * overrides it, since its parent wording predates the engine and must not change.
38
+ */
39
+ const GENERIC_PARENT_COPY = {
40
+ invite: {
41
+ subject: (ctx) => `${ctx.instituteName} — ${ctx.surveyLabel ?? "a short survey"} for ${ctx.studentForename}`,
42
+ preheader: () => "It takes a few minutes and helps your child's school plan their next steps.",
43
+ title: () => "Your view, in a few minutes",
44
+ body: (ctx) => `${ctx.instituteName} is asking parents and carers to complete a short form about ${ctx.studentForename}.\n\nThere are no right or wrong answers — your view sits alongside ${ctx.studentForename}'s own, and helps the school support them. Tap the button below to start; no login needed.`,
45
+ button: "Complete the form",
46
+ },
47
+ reminder: {
48
+ subject: (ctx) => `Reminder — ${ctx.surveyLabel ?? "a short survey"} for ${ctx.studentForename}`,
49
+ preheader: () => "The form closes soon — it only takes a few minutes.",
50
+ title: () => "Just a reminder",
51
+ body: (ctx) => `We noticed the short form ${ctx.instituteName} sent about ${ctx.studentForename} hasn't been completed yet.\n\nIt takes a few minutes and there's no login needed — just tap the button below.`,
52
+ button: "Complete the form",
53
+ },
54
+ };
55
+ /**
56
+ * (new) Staff heads-up, sent at T-7 and T-1 before any scheduled send. One template
57
+ * for every kind. The two buttons are "view the cycle" and "turn auto-send off".
58
+ */
59
+ exports.SURVEY_HEADS_UP_COPY = {
60
+ subject: (ctx) => ctx.daysUntilSend === 1 ?
61
+ `Tomorrow: ${ctx.surveyLabel} sends to ${ctx.recipientCount} ${ctx.recipientCount === 1 ? "person" : "people"}` :
62
+ `In ${ctx.daysUntilSend} days: ${ctx.surveyLabel} is scheduled to send`,
63
+ preheader: (ctx) => `Nothing to do unless you want to change it — sending ${ctx.sendDateLabel}.`,
64
+ title: () => "A survey is about to go out",
65
+ body: (ctx) => `${ctx.surveyLabel} is scheduled to send to ${ctx.recipientCount} ${ctx.recipientCount === 1 ? "person" : "people"} on ${ctx.sendDateLabel}.\n\nYou don't need to do anything — it will go out automatically. If the timing or the recipients aren't right, open it below to change them, or turn auto-send off to hold it.`,
66
+ primaryButton: "View the survey",
67
+ secondaryButton: "Turn off auto-send",
68
+ };
69
+ // ─── Kind definitions ─────────────────────────────────────────────────────────
70
+ /** The leaver thread, in send order. */
71
+ exports.SURVEY_THREADS = {
72
+ leaver: ["leaverIntentions", "leaverResults", "leaverChristmas"],
73
+ };
74
+ const LEAVER_LEGACY_REF = { collectionLink: "leaverGroups", itemIdSource: "threadKey" };
75
+ exports.SURVEY_KINDS = {
76
+ skills: {
77
+ kind: "skills",
78
+ label: "Skills survey",
79
+ studentCard: { title: "New Skills Assessment", description: "Click to complete." },
80
+ authMode: "account",
81
+ raters: { students: true, parents: true, employers: true, trustedContact: false },
82
+ parentMode: "rater",
83
+ staffCanCompleteOnBehalf: true,
84
+ responseStore: "skillAssessments",
85
+ messageTypes: {
86
+ invite: "surveySkillsInvite",
87
+ reminder: "surveySkillsReminder",
88
+ parentInvite: "surveySkillsParentInvite",
89
+ parentReminder: "surveySkillsParentReminder",
90
+ },
91
+ legacy: { collectionLink: "users", itemIdSource: "uid", messageTypes: { invite: "skillRequest" } },
92
+ formPath: ({ requestId }) => `/institutes/surveys/skills/${requestId}`,
93
+ // Students went in-app; parents and employers used the tokenised public form.
94
+ legacyFormPath: ({ rater, oId, assessmentId, externalKey }) => rater === "students" ?
95
+ `/institutes/skills/${assessmentId}` :
96
+ `/skillsRequest/${oId}/${assessmentId}/${externalKey}`,
97
+ copy: {
98
+ invite: {
99
+ // (migrated) subject/title/button are shared across raters; only the
100
+ // preheader and body differ — as in sendSkillsAssessmentRequest.
101
+ student: {
102
+ subject: () => "[Action Required] Complete Skills Assessment",
103
+ preheader: () => "Please complete this self-reflection of your skills development",
104
+ title: () => "Complete Skills Assessment",
105
+ body: () => "Complete this skills questionnaire to assess your transferrable skills and identify areas you could improve. Click the link below to view the questionnaire.",
106
+ button: "Complete Assessment",
107
+ },
108
+ parent: {
109
+ subject: () => "[Action Required] Complete Skills Assessment",
110
+ preheader: (ctx) => `Please complete this skills assessment for ${studentFullName(ctx)}`,
111
+ title: () => "Complete Skills Assessment",
112
+ body: () => "Please support your student's development by completing this short skills assessment that helps us understand how you think your child is progressing. Click the link below to complete the questionnaire.",
113
+ button: "Complete Assessment",
114
+ },
115
+ employer: {
116
+ subject: () => "[Action Required] Complete Skills Assessment",
117
+ preheader: (ctx) => `Please provide feedback for ${studentFullName(ctx)}`,
118
+ title: () => "Complete Skills Assessment",
119
+ body: (ctx) => `Please complete this short questionnaire based on your experience with ${studentFullName(ctx)}. Your feedback will help shape how ${ctx.studentForename} develops their transferrable skills to support their future career. Click the link below to view the skills assessment.`,
120
+ button: "Complete Assessment",
121
+ },
122
+ },
123
+ // Skills has never sent an automatic reminder — see reminderAfterDays.
124
+ reminder: {},
125
+ },
126
+ },
127
+ aspirations: {
128
+ kind: "aspirations",
129
+ label: "Careers aspirations survey",
130
+ studentCard: { title: "Career Aspirations Survey", description: "Your school wants to hear about your career aspirations" },
131
+ authMode: "account",
132
+ raters: { students: true, parents: true, employers: false, trustedContact: false },
133
+ parentMode: "rater",
134
+ staffCanCompleteOnBehalf: true,
135
+ responseStore: "surveyRequests",
136
+ reminderAfterDays: 14,
137
+ defaultCloseAfterDays: 21,
138
+ messageTypes: {
139
+ invite: "surveyAspirationsInvite",
140
+ reminder: "surveyAspirationsReminder",
141
+ parentInvite: "surveyAspirationsParentInvite",
142
+ parentReminder: "surveyAspirationsParentReminder",
143
+ },
144
+ legacy: {
145
+ collectionLink: "aspirationRequests",
146
+ itemIdSource: "requestId",
147
+ messageTypes: { invite: "aspirationInvite", reminder: "aspirationReminder" },
148
+ },
149
+ formPath: ({ requestId }) => `/institutes/surveys/aspirations/${requestId}`,
150
+ legacyFormPath: ({ requestId }) => `/app/destinations/students/form/${requestId}`,
151
+ copy: {
152
+ invite: {
153
+ student: {
154
+ // (migrated) sendAspirationInviteEmail
155
+ subject: (ctx) => `${ctx.instituteName} — share your career aspirations`,
156
+ preheader: () => "It only takes a few minutes and helps your school plan for your future.",
157
+ title: () => "Tell us about your career aspirations",
158
+ body: (ctx) => `${ctx.instituteName} would like to hear about your career interests and ideas for the future.\n\nClick the button below to log in to CareerThread and complete a short form — it takes about 3 minutes. There are no right or wrong answers.`,
159
+ button: "Share my aspirations",
160
+ },
161
+ parent: GENERIC_PARENT_COPY.invite,
162
+ },
163
+ reminder: {
164
+ student: {
165
+ // (migrated) sendAspirationReminderEmail
166
+ subject: (ctx) => `Reminder — share your career aspirations with ${ctx.instituteName}`,
167
+ preheader: () => "The form closes soon — it only takes a few minutes.",
168
+ title: () => "Just a reminder",
169
+ body: (ctx) => `We noticed you haven't yet completed the careers aspirations form from ${ctx.instituteName}.\n\nLog in to CareerThread using the link below — the form takes about 3 minutes and closes soon.`,
170
+ button: "Complete the form",
171
+ },
172
+ parent: GENERIC_PARENT_COPY.reminder,
173
+ },
174
+ },
175
+ },
176
+ leaverIntentions: {
177
+ kind: "leaverIntentions",
178
+ label: "Leaver transition form",
179
+ studentCard: { title: "Leaver Form", description: "Tell us what you're planning to do next" },
180
+ authMode: "account",
181
+ raters: { students: true, parents: true, employers: false, trustedContact: false },
182
+ parentMode: "rater",
183
+ staffCanCompleteOnBehalf: true,
184
+ thread: "leaver",
185
+ threadOrder: 1,
186
+ responseStore: "surveyRequests",
187
+ reminderAfterDays: 14,
188
+ messageTypes: {
189
+ invite: "surveyLeaverIntentionsInvite",
190
+ reminder: "surveyLeaverIntentionsReminder",
191
+ parentInvite: "surveyLeaverIntentionsParentInvite",
192
+ parentReminder: "surveyLeaverIntentionsParentReminder",
193
+ },
194
+ legacy: { ...LEAVER_LEGACY_REF, messageTypes: { invite: "leaverInvite", reminder: "leaverReminder" } },
195
+ formPath: ({ requestId }) => `/institutes/surveys/leaverIntentions/${requestId}`,
196
+ legacyFormPath: ({ requestId }) => `/institutes/leaver/${requestId}`,
197
+ copy: {
198
+ invite: {
199
+ student: {
200
+ // (migrated) sendLeaverInviteEmail
201
+ subject: (ctx) => `${ctx.instituteName} — tell us your next step`,
202
+ preheader: () => "A few minutes now helps your school report your destination and stay in touch.",
203
+ title: () => "Before you leave — tell us your plans",
204
+ body: (ctx) => `As you get ready to leave ${ctx.instituteName}, please complete a short form telling us what you're planning to do next.\n\nIt takes about 5 minutes and helps your school keep your records up to date. If you'd like to, you can also choose to stay connected as an alumnus.`,
205
+ button: "Complete my leaver form",
206
+ },
207
+ parent: GENERIC_PARENT_COPY.invite,
208
+ },
209
+ reminder: {
210
+ student: {
211
+ // (migrated) sendLeaverReminderEmail
212
+ subject: (ctx) => `Reminder — tell ${ctx.instituteName} your next step`,
213
+ preheader: () => "Complete your leaver form before you lose access to your school account.",
214
+ title: () => "Just a reminder",
215
+ body: (ctx) => `We noticed you haven't yet completed your leaver form from ${ctx.instituteName}.\n\nLog in to CareerThread using the link below — it takes about 5 minutes. Once you leave, your school account is deactivated, so it's worth doing now.`,
216
+ button: "Complete my leaver form",
217
+ },
218
+ parent: GENERIC_PARENT_COPY.reminder,
219
+ },
220
+ },
221
+ },
222
+ leaverResults: {
223
+ kind: "leaverResults",
224
+ label: "Results day check-in",
225
+ studentCard: { title: "Results Day Check-in", description: "Now you have your results, tell us what you're actually doing next" },
226
+ // The school email is deactivated by results day, so these go to the personal
227
+ // address captured on the intentions form and authenticate by token.
228
+ authMode: "magicLink",
229
+ raters: { students: true, parents: true, employers: false, trustedContact: false },
230
+ parentMode: "rater",
231
+ staffCanCompleteOnBehalf: true,
232
+ thread: "leaver",
233
+ threadOrder: 2,
234
+ responseStore: "surveyRequests",
235
+ reminderAfterDays: 7,
236
+ messageTypes: {
237
+ invite: "surveyLeaverResultsInvite",
238
+ reminder: "surveyLeaverResultsReminder",
239
+ parentInvite: "surveyLeaverResultsParentInvite",
240
+ parentReminder: "surveyLeaverResultsParentReminder",
241
+ },
242
+ legacy: { ...LEAVER_LEGACY_REF, messageTypes: { invite: "leaverResultsInvite", reminder: "leaverResultsReminder" } },
243
+ formPath: ({ requestId, token, role }) => `/survey/leaverResults/${requestId}?token=${token ?? ""}&role=${role ?? "student"}`,
244
+ legacyFormPath: ({ requestId, token }) => `/leaverCheckpoint/${requestId}/resultsDay/${token ?? ""}`,
245
+ copy: {
246
+ invite: {
247
+ student: {
248
+ // (migrated) CHECKPOINT_COPY.resultsDay
249
+ subject: (ctx) => `Congratulations from ${ctx.instituteName} — what's next?`,
250
+ preheader: () => "Now you have your results, tell us what you're actually doing. It takes 2 minutes.",
251
+ title: () => "Congratulations on finishing!",
252
+ body: (ctx) => `Now you've got your results, ${ctx.instituteName} would love to know what you're actually doing next.\n\nIt takes about 2 minutes and there's no login needed — just tap the button below. Whatever you've decided, we'd love to hear it.`,
253
+ button: "Tell us what's next",
254
+ },
255
+ parent: GENERIC_PARENT_COPY.invite,
256
+ },
257
+ reminder: {
258
+ student: {
259
+ // (migrated) sendLeaverCheckpointReminderEmail — its subject was built
260
+ // from the invite button, lowercased.
261
+ subject: (ctx) => `Reminder — tell us what's next with ${ctx.instituteName}`,
262
+ preheader: () => "Now you have your results, tell us what you're actually doing. It takes 2 minutes.",
263
+ title: () => "Just a reminder",
264
+ body: (ctx) => `We noticed you haven't yet had a chance to update ${ctx.instituteName} on what you're doing.\n\nIt only takes 2 minutes, and there's no login needed — just tap the button below.`,
265
+ button: "Tell us what's next",
266
+ },
267
+ parent: GENERIC_PARENT_COPY.reminder,
268
+ },
269
+ },
270
+ },
271
+ leaverChristmas: {
272
+ kind: "leaverChristmas",
273
+ label: "Christmas check-in",
274
+ studentCard: { title: "Christmas Check-in", description: "Let your school know how you're getting on since you left" },
275
+ authMode: "magicLink",
276
+ raters: { students: true, parents: true, employers: false, trustedContact: false },
277
+ parentMode: "rater",
278
+ staffCanCompleteOnBehalf: true,
279
+ thread: "leaver",
280
+ threadOrder: 3,
281
+ responseStore: "surveyRequests",
282
+ reminderAfterDays: 7,
283
+ messageTypes: {
284
+ invite: "surveyLeaverChristmasInvite",
285
+ reminder: "surveyLeaverChristmasReminder",
286
+ parentInvite: "surveyLeaverChristmasParentInvite",
287
+ parentReminder: "surveyLeaverChristmasParentReminder",
288
+ },
289
+ legacy: { ...LEAVER_LEGACY_REF, messageTypes: { invite: "leaverChristmasInvite", reminder: "leaverChristmasReminder" } },
290
+ formPath: ({ requestId, token, role }) => `/survey/leaverChristmas/${requestId}?token=${token ?? ""}&role=${role ?? "student"}`,
291
+ legacyFormPath: ({ requestId, token }) => `/leaverCheckpoint/${requestId}/christmas/${token ?? ""}`,
292
+ copy: {
293
+ invite: {
294
+ student: {
295
+ // (migrated) CHECKPOINT_COPY.christmas
296
+ subject: (ctx) => `Quick check-in from ${ctx.instituteName} — how's it going?`,
297
+ preheader: () => "Still 2 minutes — let us know how you're getting on since you left.",
298
+ title: () => "How's it going since you left?",
299
+ body: (ctx) => `It's been a few months since you left ${ctx.instituteName}, so we wanted to check in.\n\nAre you still doing what you planned, or has something changed? Either way it's completely fine — just let us know so we can keep your records up to date. It takes about 2 minutes, no login needed.`,
300
+ button: "Quick check-in",
301
+ },
302
+ parent: GENERIC_PARENT_COPY.invite,
303
+ },
304
+ reminder: {
305
+ student: {
306
+ // (migrated) sendLeaverCheckpointReminderEmail
307
+ subject: (ctx) => `Reminder — quick check-in with ${ctx.instituteName}`,
308
+ preheader: () => "Still 2 minutes — let us know how you're getting on since you left.",
309
+ title: () => "Just a reminder",
310
+ body: (ctx) => `We noticed you haven't yet had a chance to update ${ctx.instituteName} on what you're doing.\n\nIt only takes 2 minutes, and there's no login needed — just tap the button below.`,
311
+ button: "Quick check-in",
312
+ },
313
+ parent: GENERIC_PARENT_COPY.reminder,
314
+ },
315
+ },
316
+ },
317
+ alumniCheckIn: {
318
+ kind: "alumniCheckIn",
319
+ label: "Alumni check-in",
320
+ // No student dashboard card — alumni have no CareerThread account.
321
+ authMode: "magicLink",
322
+ raters: { students: true, parents: false, employers: false, trustedContact: true },
323
+ parentMode: "fallback",
324
+ staffCanCompleteOnBehalf: true,
325
+ responseStore: "surveyRequests",
326
+ // Chased by the emailInteractions reminder chain, which escalates to the
327
+ // trusted contact, rather than by a per-cycle reminder.
328
+ messageTypes: {
329
+ invite: "surveyAlumniCheckInInvite",
330
+ reminder: "surveyAlumniCheckInReminder",
331
+ parentInvite: "surveyAlumniCheckInTrustedContact",
332
+ parentReminder: "surveyAlumniCheckInTrustedContactReminder",
333
+ },
334
+ legacy: {
335
+ collectionLink: "alumni",
336
+ itemIdSource: "alumniId",
337
+ messageTypes: {
338
+ invite: "alumniCheckInInvite",
339
+ trustedContact: "alumniCheckInTrustedContact",
340
+ linkRefresh: "alumniCheckInLinkRefresh",
341
+ },
342
+ },
343
+ formPath: ({ requestId, token, role }) => `/survey/alumniCheckIn/${requestId}?token=${token ?? ""}&role=${role ?? "student"}`,
344
+ legacyFormPath: ({ alumniId, token }) => `/alumniCheckIn/${alumniId}/${token ?? ""}`,
345
+ copy: {
346
+ invite: {
347
+ student: {
348
+ // (migrated) sendAlumniCheckInInviteEmail — note the plain hyphens,
349
+ // which this copy uses where the leaver copy uses en dashes.
350
+ subject: (ctx) => `Where are you now? A catch-up from ${ctx.instituteName}`,
351
+ preheader: () => "One click if nothing's changed - two minutes if it has.",
352
+ title: () => "Where has your journey taken you?",
353
+ body: (ctx) => `We love keeping up with where our former students end up. Let us know what you're doing now - it takes about two minutes, and if nothing has changed since we last spoke it's a single click. You can also choose whether to share your journey with our current students, to show them where life after ${ctx.instituteName} can lead.`,
354
+ button: "Update my profile",
355
+ secondaryButton: { title: "I'm in the same place", urlFragment: "#noChange" },
356
+ },
357
+ trustedContact: {
358
+ // (migrated) sendTrustedContactCheckInFallbackEmail. Minimum
359
+ // disclosure: the alumnus's forename only.
360
+ subject: (ctx) => `A quick favour from ${ctx.instituteName}`,
361
+ preheader: (ctx) => `Could you tell us how ${ctx.studentForename} is getting on? It takes a minute.`,
362
+ title: (ctx) => `How is ${ctx.studentForename} getting on?`,
363
+ body: (ctx) => `We're the careers team at ${ctx.instituteName}. We've been trying to reach ${ctx.studentForename} for our annual catch-up but haven't heard back. As their nominated contact, could you let us know what they're doing now? It takes about a minute, and there's no login needed.`,
364
+ button: "Tell us how they're doing",
365
+ },
366
+ },
367
+ reminder: {},
368
+ extra: {
369
+ // (migrated) sendAlumniCheckInFreshLinkEmail — sent when someone
370
+ // returns to a used or expired link.
371
+ linkRefresh: {
372
+ subject: (ctx) => `Your fresh profile link from ${ctx.instituteName}`,
373
+ preheader: () => "Here's a new secure link to your alumni profile.",
374
+ title: () => "Here's a fresh link",
375
+ body: () => "The link you followed had expired, so we've sent you this new one. Click below to view or update your alumni profile.",
376
+ button: "Open my profile",
377
+ },
378
+ },
379
+ },
380
+ },
381
+ };
382
+ // ─── Lookups ──────────────────────────────────────────────────────────────────
383
+ const surveyKindDef = (kind) => exports.SURVEY_KINDS[kind];
384
+ exports.surveyKindDef = surveyKindDef;
385
+ const isSurveyKind = (value) => typeof value === "string" && types_1.SURVEY_KINDS_LIST.includes(value);
386
+ exports.isSurveyKind = isSurveyKind;
387
+ /** The kinds in a kind's thread, in send order. A kind with no thread is alone. */
388
+ const surveyThreadKinds = (kind) => {
389
+ const thread = exports.SURVEY_KINDS[kind].thread;
390
+ return thread ? exports.SURVEY_THREADS[thread] : [kind];
391
+ };
392
+ exports.surveyThreadKinds = surveyThreadKinds;
393
+ /**
394
+ * Kinds that come LATER in this kind's thread. Their having been sent is what makes
395
+ * chasing this one pointless — replaces the hand-maintained SUPERSEDES_INTENTIONS /
396
+ * SUPERSEDES_RESULTS constants in notifications/leavers.ts.
397
+ */
398
+ const supersedingSurveyKinds = (kind) => {
399
+ const order = exports.SURVEY_KINDS[kind].threadOrder;
400
+ if (!order)
401
+ return [];
402
+ return (0, exports.surveyThreadKinds)(kind)
403
+ .filter((k) => (exports.SURVEY_KINDS[k].threadOrder ?? 0) > order);
404
+ };
405
+ exports.supersedingSurveyKinds = supersedingSurveyKinds;
406
+ /**
407
+ * Message types to pass as Email.sendEmail's `supersededBy` — if any has already
408
+ * gone to this person for this thread, the email being built is skipped.
409
+ *
410
+ * Only covers thread supersession. Same-kind supersession (a newer cycle to the same
411
+ * person, plan decision D8) can't be expressed this way, because each cycle is its
412
+ * own collection item — the engine stamps `supersededAt` on the older request and
413
+ * skips it instead.
414
+ */
415
+ const supersedingMessageTypes = (kind) => (0, exports.supersedingSurveyKinds)(kind).flatMap((k) => [exports.SURVEY_KINDS[k].messageTypes.invite, exports.SURVEY_KINDS[k].messageTypes.reminder]);
416
+ exports.supersedingMessageTypes = supersedingMessageTypes;
417
+ /**
418
+ * The same list in pre-cutover message types. Interaction docs written before the
419
+ * cutover carry these AND a different collectionLink (see legacy.collectionLink), so
420
+ * the engine needs a second supersession query against them for one release. Both
421
+ * this and the dual-read go in Phase E.
422
+ */
423
+ const legacySupersedingMessageTypes = (kind) => (0, exports.supersedingSurveyKinds)(kind).flatMap((k) => {
424
+ const legacy = exports.SURVEY_KINDS[k].legacy.messageTypes;
425
+ return [legacy.invite, legacy.reminder].filter((t) => Boolean(t));
426
+ });
427
+ exports.legacySupersedingMessageTypes = legacySupersedingMessageTypes;
428
+ /**
429
+ * The copy for one email, falling back to the shared parent template for kinds that
430
+ * don't override it. Returns undefined when a kind deliberately has no such email
431
+ * (skills sends no reminders; alumni check-ins re-send the invite instead).
432
+ */
433
+ const surveyEmailCopy = (kind, audience, variant) => {
434
+ const def = exports.SURVEY_KINDS[kind];
435
+ const own = def.copy[variant][audience];
436
+ if (own)
437
+ return own;
438
+ if (audience === "parent" && def.parentMode === "rater")
439
+ return GENERIC_PARENT_COPY[variant];
440
+ return undefined;
441
+ };
442
+ exports.surveyEmailCopy = surveyEmailCopy;
443
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/surveys/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * surveys/schedule.ts — resolving survey schedules to concrete dates.
3
+ *
4
+ * Home of the relative survey-date rules ("first Monday of June") that were
5
+ * previously in constants.ts, plus the slot→date and academic-year helpers that
6
+ * provisionDueSkillsSurveys (skillsAssessments.ts) and destinationsProvisionCron
7
+ * (aspirations.ts) each had their own copy of.
8
+ *
9
+ * A rule is year-agnostic; it resolves into a concrete date for a given academic
10
+ * year (each September) via surveyRuleToMMDD / surveyRuleToDate.
11
+ *
12
+ * ── One academic-year boundary: 1 September ───────────────────────────────────
13
+ * Everything here derives the academic year the same way — currentAcademicStartYear,
14
+ * and academicYearOf which labels it. September to August is one academic year.
15
+ *
16
+ * This is a deliberate correction. Pre-engine code had two boundaries: the year
17
+ * LABEL flipped in August (skillsAcademicYear in skillsAssessments.ts, and the
18
+ * inline `sendMonth >= 8` in createAspirationCycle) while rule resolution flipped
19
+ * in September (destinationsStartYear). So a survey sent in August was labelled with
20
+ * the year ahead of the one it was scheduled from. Two consequences for the rollout:
21
+ * • the migration scripts must RE-DERIVE academicYear from scheduledSendDate with
22
+ * academicYearOf rather than copying the stored label, or August-dated cycles
23
+ * (leaver results-day checkpoints ~29 Aug especially) land in the wrong year;
24
+ * • the two legacy derivations must be replaced by academicYearOf when their
25
+ * callers move onto the engine, so new writes agree with migrated ones.
26
+ */
27
+ import type { SurveyDateRule, SurveyOrdinal, SurveyWeekday } from "../typeDefinitions";
28
+ import type { SurveySlot } from "./types";
29
+ /** "MM-DD" for a date, in local time — the format survey slots are stored in. */
30
+ export declare const toMMDD: (date?: Date) => string;
31
+ /**
32
+ * Academic years run September→August. Given the year a school year STARTS in
33
+ * (e.g. 2026 for "2026/27"), return the calendar year the given month falls in:
34
+ * September–December sit in the start year; January–August roll into the next.
35
+ */
36
+ export declare const academicCalendarYear: (month: number, academicStartYear: number) => number;
37
+ /**
38
+ * The academic year a date falls within, as the START year. From September onwards
39
+ * we are in a new academic year (getMonth() is 0-indexed, so >= 8 is September).
40
+ * Matches DestinationsSetup.currentStartYear and destinationsStartYear.
41
+ */
42
+ export declare const currentAcademicStartYear: (date?: Date) => number;
43
+ /** Hyphen academic-year label from a start year, e.g. 2026 → "2026-27". */
44
+ export declare const academicYearLabel: (startYear: number) => string;
45
+ /**
46
+ * The academic-year LABEL for a date, hyphen form (e.g. "2026-27"). Uses the single
47
+ * 1 September boundary, so it always agrees with currentAcademicStartYear: a date in
48
+ * August belongs to the academic year that is ending, not the one about to start.
49
+ */
50
+ export declare const academicYearOf: (date?: Date) => string;
51
+ /** Slash academic-year label from a start year, e.g. 2026 → "2026/27". The form
52
+ * legacy leaverGroups and DestinationsConfig.lastProvisionedYear use. */
53
+ export declare const academicYearSlashLabel: (startYear: number) => string;
54
+ /** Convert either academic-year form to the other's separator, e.g. "2026/27" → "2026-27". */
55
+ export declare const normaliseAcademicYear: (year: string) => string;
56
+ /**
57
+ * Resolve a rule to a concrete Date in a specific CALENDAR year+month, e.g.
58
+ * ("first", Monday, June 2027). "last" walks back from the month's final day.
59
+ */
60
+ export declare const resolveNthWeekday: (calendarYear: number, month: number, ordinal: SurveyOrdinal, weekday: SurveyWeekday) => Date;
61
+ /** Resolve a rule to the concrete Date for the academic year starting in academicStartYear. */
62
+ export declare const surveyRuleToDate: (rule: SurveyDateRule, academicStartYear: number) => Date;
63
+ /** Resolve a rule to an "MM-DD" string for the academic year starting in academicStartYear. */
64
+ export declare const surveyRuleToMMDD: (rule: SurveyDateRule, academicStartYear: number) => string;
65
+ /**
66
+ * "MM-DD" → the ISO datetime it falls on in the given academic year, at 08:00 UTC
67
+ * (when sends fire). Returns "" for a malformed or non-existent date (e.g. "02-30"),
68
+ * which callers treat as "skip this slot". Lifted verbatim from aspirations.ts.
69
+ */
70
+ export declare const mmddToISO: (mmdd: string, academicStartYear: number) => string;
71
+ /** A slot with its date resolved for one academic year. */
72
+ export type ResolvedSurveySlot = SurveySlot & {
73
+ /** MM-DD for the target year: `rule` resolved when present, else the stored sendMMDD. */
74
+ sendMMDD: string;
75
+ /** ISO datetime of the send, or "" when sendMMDD doesn't resolve to a real date. */
76
+ sendISO: string;
77
+ /** False only when the slot explicitly opts out. Absent `enabled` means enabled. */
78
+ enabled: boolean;
79
+ };
80
+ /**
81
+ * The MM-DD a slot fires on in a given academic year. A `rule` is the source of
82
+ * truth and is re-resolved; a slot with no rule keeps its fixed sendMMDD.
83
+ */
84
+ export declare const slotMMDDForYear: (slot: SurveySlot, academicStartYear: number) => string;
85
+ /**
86
+ * Resolve a year's programme of slots to concrete dates, dropping any whose date
87
+ * doesn't exist. Replaces the per-caller `refreshSlot` in provisionDueSkillsSurveys
88
+ * and the inline slot loop in _provisionDestinationsYear.
89
+ *
90
+ * Slots are returned in send order. Disabled slots are INCLUDED (with enabled:false)
91
+ * so provisioning still creates their cycle with autoSend off — staff can see the
92
+ * survey on the timeline and send it by hand.
93
+ */
94
+ export declare const resolveSlotsForYear: (slots: SurveySlot[] | undefined, academicStartYear: number) => ResolvedSurveySlot[];
95
+ /**
96
+ * Is this slot's send day the given date? Compares MM-DD, resolving any rule against
97
+ * the academic year `date` sits in, which is how the daily crons decide what to fire.
98
+ *
99
+ * Deliberately ignores `enabled`: a disabled slot is still "due" (its cycle gets
100
+ * provisioned) — whether it SENDS is the cycle's autoSend flag, checked by the cron.
101
+ */
102
+ export declare const isSlotDueOn: (slot: SurveySlot, date?: Date) => boolean;