placementt-core 1.300.1061 → 1.300.1062

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 @@
1
+ export {};
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const compassTemplate_1 = require("./compassTemplate");
4
+ const compassExport_1 = require("./compassExport");
5
+ const compassGuidanceExport_1 = require("./compassGuidanceExport");
6
+ // Column positions from CEC's own Activities sheet, 1-indexed as the workbook
7
+ // numbers them. Written out by hand from the template rather than derived from
8
+ // the code, so this test can actually catch the code drifting.
9
+ const COL = {
10
+ activityName: 1, gb2: 2, gb5: 5, gb6: 6, gb7: 7, gb8: 8, deliveryMethod: 9,
11
+ schoolLead: 10, start: 11, end: 12, partnerEmployer: 13, partnerAdviser: 16,
12
+ partnerLast: 21, typeEmployerEncounter: 24, typeWorkplaces: 25,
13
+ typeGuidance: 28, typeVolunteering: 30, meaningBm5First: 32,
14
+ meaningBm7Last: 50, pal: 51, learners: 52, parentAttendance: 53,
15
+ outcomeFirst: 54, outcomeLast: 68, industries: 69, notes: 70,
16
+ };
17
+ const cell = (row, column) => row[column - 1];
18
+ const event = (over = {}) => ({
19
+ id: "ev1",
20
+ name: "Employer insight morning",
21
+ description: "Six local employers ran carousel sessions.",
22
+ designatedStaffText: "Mrs Smith",
23
+ gatsbyBenchmarks: [5, 7],
24
+ deliveryMethod: "In person",
25
+ compassActivityTypes: ["Employer encounter"],
26
+ compassCareersPartners: ["Employer"],
27
+ compassMeaningfulness: {
28
+ 5: ["clear purpose shared"],
29
+ 7: ["Clear purpose shared"],
30
+ },
31
+ startDate: "2027-03-04",
32
+ startTime: "09:30",
33
+ endDate: "2027-03-04",
34
+ endTime: "12:00",
35
+ ...over,
36
+ });
37
+ const noFeedback = {
38
+ students: { completed: false }, parent: { completed: false },
39
+ provider: { completed: false }, staff: { completed: false },
40
+ };
41
+ const placement = (over = {}) => ({
42
+ id: "pl1",
43
+ uid: "pupil-1",
44
+ oId: "org-1",
45
+ name: "Bartlett Engineering",
46
+ title: "",
47
+ startDate: "2027-06-21",
48
+ endDate: "2027-06-25",
49
+ feedback: {
50
+ students: { completed: true },
51
+ parent: { completed: false },
52
+ provider: { completed: true },
53
+ staff: { completed: false },
54
+ },
55
+ ...over,
56
+ });
57
+ const activity = (over = {}) => ({
58
+ id: "ac1",
59
+ uid: "pupil-1",
60
+ oId: "org-1",
61
+ title: "Saturday job at Greggs",
62
+ description: "Counter service.",
63
+ activityType: "partTimeWork",
64
+ locationType: "inPerson",
65
+ startDate: "2027-01-10",
66
+ endDate: "2027-01-10",
67
+ dateCreated: "2027-01-11T09:00:00.000Z",
68
+ ...over,
69
+ });
70
+ const guidanceEntry = (over = {}) => ({
71
+ id: "cr1",
72
+ uid: "pupil-1",
73
+ oId: "org-1",
74
+ entryType: "personalGuidance",
75
+ date: "2027-02-11",
76
+ adviserUid: "staff-1",
77
+ adviserQualified: true,
78
+ academicYear: "2026-27",
79
+ mode: "inPerson",
80
+ summary: "Talked through apprenticeships.",
81
+ staffNotes: "Safeguarding: do not share.",
82
+ createdBy: "staff-1",
83
+ dateCreated: "2027-02-11T10:00:00.000Z",
84
+ ...over,
85
+ });
86
+ describe("the template's column spec", () => {
87
+ it("is the 70 columns of CEC's Activities sheet, in its order", () => {
88
+ expect(compassTemplate_1.COMPASS_ACTIVITY_COLUMNS).toHaveLength(70);
89
+ expect(compassTemplate_1.COMPASS_ACTIVITY_HEADERS).toHaveLength(70);
90
+ expect((0, compassTemplate_1.compassActivityRowToArray)((0, compassTemplate_1.emptyCompassActivityRow)("event", "x")))
91
+ .toHaveLength(70);
92
+ expect(cell(compassTemplate_1.COMPASS_ACTIVITY_HEADERS, COL.activityName))
93
+ .toBe("Activity Name*\n(Enter name)");
94
+ expect(cell(compassTemplate_1.COMPASS_ACTIVITY_HEADERS, COL.gb2))
95
+ .toBe("GB2 - Learning from careers and labour market information");
96
+ expect(cell(compassTemplate_1.COMPASS_ACTIVITY_HEADERS, COL.gb8)).toBe("GB8 - Personal guidance");
97
+ expect(cell(compassTemplate_1.COMPASS_ACTIVITY_HEADERS, COL.deliveryMethod)).toBe("Delivery method*");
98
+ expect(cell(compassTemplate_1.COMPASS_ACTIVITY_HEADERS, COL.pal))
99
+ .toBe("Provider Access Legislation eligible encounter?");
100
+ expect(cell(compassTemplate_1.COMPASS_ACTIVITY_HEADERS, COL.parentAttendance)).toBe("Parent Attendance?");
101
+ expect(cell(compassTemplate_1.COMPASS_ACTIVITY_HEADERS, COL.industries))
102
+ .toBe("Industries involved in the activity");
103
+ expect(cell(compassTemplate_1.COMPASS_ACTIVITY_HEADERS, COL.notes)).toBe("Notes");
104
+ });
105
+ it("keeps the header row's counts, not the Input guide's", () => {
106
+ // The workbook's Input guide says "Type of activity (7 in total)" and
107
+ // "Type of Careers Partner involved (8 in total)". The header carries
108
+ // ten and nine, and the header is what the upload parses.
109
+ expect(compassTemplate_1.COMPASS_ACTIVITY_TYPES).toHaveLength(10);
110
+ expect(compassTemplate_1.COMPASS_CAREERS_PARTNER_COLUMNS).toHaveLength(9);
111
+ expect(compassTemplate_1.COMPASS_MEANINGFULNESS_COLUMNS.filter((c) => c.benchmark === 5)).toHaveLength(4);
112
+ expect(compassTemplate_1.COMPASS_MEANINGFULNESS_COLUMNS.filter((c) => c.benchmark === 6)).toHaveLength(8);
113
+ expect(compassTemplate_1.COMPASS_MEANINGFULNESS_COLUMNS.filter((c) => c.benchmark === 7)).toHaveLength(7);
114
+ expect(compassTemplate_1.COMPASS_LEARNER_OUTCOME_COLUMNS).toHaveLength(15);
115
+ expect(compassTemplate_1.COMPASS_INDUSTRIES).toHaveLength(23);
116
+ expect(compassTemplate_1.COMPASS_INDUSTRIES[0]).toEqual({ code: 1, name: "Agriculture/farming" });
117
+ expect(compassTemplate_1.COMPASS_INDUSTRIES[22])
118
+ .toEqual({ code: 23, name: "Transport/storage/driving" });
119
+ });
120
+ it("writes every boolean block as Yes or empty, never No", () => {
121
+ const row = (0, compassTemplate_1.emptyCompassActivityRow)("event", "x");
122
+ row.benchmarks = [5];
123
+ row.parentAttendance = true;
124
+ const cells = (0, compassTemplate_1.compassActivityRowToArray)(row);
125
+ expect(cell(cells, COL.gb2)).toBe("");
126
+ expect(cell(cells, COL.gb5)).toBe("Yes");
127
+ expect(cell(cells, COL.parentAttendance)).toBe("Yes");
128
+ expect(cell(cells, COL.pal)).toBe("");
129
+ });
130
+ it("formats dates the way the template asks for them", () => {
131
+ expect((0, compassTemplate_1.formatCompassDateTime)("2027-03-04", "09:30")).toBe("04/03/2027 09:30");
132
+ // No time recorded: a school-day default rather than a blank mandatory cell.
133
+ expect((0, compassTemplate_1.formatCompassDateTime)("2027-03-04")).toBe("04/03/2027 09:00");
134
+ expect((0, compassTemplate_1.formatCompassDateTime)(undefined, "09:30")).toBe("");
135
+ expect((0, compassTemplate_1.formatCompassDateTime)("nonsense")).toBe("");
136
+ });
137
+ });
138
+ describe("events", () => {
139
+ it("is one row per activity, with a Yes per tagged benchmark", () => {
140
+ // The old model was one row per benchmark. The real template carries
141
+ // GB2-GB8 as seven columns, so a BM5 + BM7 event is a single row.
142
+ const rows = (0, compassExport_1.buildCompassActivityRows)(event(), ["A1", "A2"]);
143
+ expect(rows).toHaveLength(1);
144
+ const cells = (0, compassTemplate_1.compassActivityRowToArray)(rows[0]);
145
+ expect(cell(cells, COL.activityName)).toBe("Employer insight morning");
146
+ expect(cell(cells, COL.gb5)).toBe("Yes");
147
+ expect(cell(cells, COL.gb7)).toBe("Yes");
148
+ expect(cell(cells, COL.gb6)).toBe("");
149
+ expect(cell(cells, COL.deliveryMethod)).toBe("In person");
150
+ expect(cell(cells, COL.schoolLead)).toBe("Mrs Smith");
151
+ expect(cell(cells, COL.start)).toBe("04/03/2027 09:30");
152
+ expect(cell(cells, COL.end)).toBe("04/03/2027 12:00");
153
+ expect(cell(cells, COL.partnerEmployer)).toBe("Yes");
154
+ expect(cell(cells, COL.typeEmployerEncounter)).toBe("Yes");
155
+ expect(cell(cells, COL.meaningBm5First)).toBe("Yes");
156
+ expect(cell(cells, COL.learners)).toBe("A1;A2");
157
+ expect(cell(cells, COL.notes))
158
+ .toBe("Six local employers ran carousel sessions.");
159
+ });
160
+ it("only carries meaningfulness for the benchmarks the event still claims", () => {
161
+ const [row] = (0, compassExport_1.buildCompassActivityRows)(event({ gatsbyBenchmarks: [5], compassMeaningfulness: {
162
+ 5: ["clear purpose shared"], 6: ["Tasks or work produced"],
163
+ } }), ["A1"]);
164
+ expect(row.meaningfulness[5]).toEqual(["clear purpose shared"]);
165
+ expect(row.meaningfulness[6]).toBeUndefined();
166
+ });
167
+ it("only stamps PAL on a PAL-relevant benchmark", () => {
168
+ const [pal] = (0, compassExport_1.buildCompassActivityRows)(event({ palCompliant: true }), ["A1"]);
169
+ expect(pal.palEligible).toBe(true);
170
+ const [notPal] = (0, compassExport_1.buildCompassActivityRows)(event({ palCompliant: true, gatsbyBenchmarks: [2] }), ["A1"]);
171
+ expect(notPal.palEligible).toBe(false);
172
+ });
173
+ it("exports nothing for an event with no benchmark in 2-8", () => {
174
+ expect((0, compassExport_1.buildCompassActivityRows)(event({ gatsbyBenchmarks: [1] }), ["A1"]))
175
+ .toEqual([]);
176
+ expect((0, compassExport_1.buildCompassActivityRows)(event({ gatsbyBenchmarks: [] }), ["A1"]))
177
+ .toEqual([]);
178
+ });
179
+ it("blocks a row CEC's sheet would reject", () => {
180
+ expect((0, compassExport_1.validateCompassExportEvent)(event(), 2, 2)).toEqual([]);
181
+ const noMethod = (0, compassExport_1.validateCompassExportEvent)(event({ deliveryMethod: undefined }), 2, 2);
182
+ expect(noMethod.some((i) => i.level === "blocking" &&
183
+ /delivery method/i.test(i.message))).toBe(true);
184
+ const noType = (0, compassExport_1.validateCompassExportEvent)(event({ compassActivityTypes: [] }), 2, 2);
185
+ expect(noType.some((i) => i.level === "blocking" &&
186
+ /activity type/i.test(i.message))).toBe(true);
187
+ const noBenchmark = (0, compassExport_1.validateCompassExportEvent)(event({ gatsbyBenchmarks: [1] }), 2, 2);
188
+ expect(noBenchmark.some((i) => i.level === "blocking" &&
189
+ /Benchmark/.test(i.message))).toBe(true);
190
+ });
191
+ it("blocks an event whose attendees have no UPN, and warns on a partial gap", () => {
192
+ // Compass+ silently drops an unmatched learner, so a row with no UPN
193
+ // uploads as an activity nobody attended.
194
+ const none = (0, compassExport_1.validateCompassExportEvent)(event(), 12, 0);
195
+ expect(none.some((i) => i.level === "blocking" && /UPN/.test(i.message)))
196
+ .toBe(true);
197
+ const some = (0, compassExport_1.validateCompassExportEvent)(event(), 12, 9);
198
+ expect(some.some((i) => i.level === "warning" && /3 of 12/.test(i.message)))
199
+ .toBe(true);
200
+ expect(some.some((i) => i.level === "blocking")).toBe(false);
201
+ });
202
+ it("warns rather than guesses when a mandatory block has no source", () => {
203
+ const issues = (0, compassExport_1.validateCompassExportEvent)(event({
204
+ compassCareersPartners: [],
205
+ compassMeaningfulness: {},
206
+ }), 2, 2);
207
+ expect(issues.length).toBeGreaterThan(0);
208
+ expect(issues.every((i) => i.level === "warning")).toBe(true);
209
+ expect(issues.some((i) => /careers partner/i.test(i.message))).toBe(true);
210
+ expect(issues.some((i) => /Benchmark 5 is claimed/.test(i.message))).toBe(true);
211
+ expect(issues.some((i) => /Benchmark 7 is claimed/.test(i.message))).toBe(true);
212
+ // and the columns really are left empty, not filled in.
213
+ const [row] = (0, compassExport_1.buildCompassActivityRows)(event({
214
+ compassCareersPartners: [], compassMeaningfulness: {},
215
+ }), ["A1"]);
216
+ const cells = (0, compassTemplate_1.compassActivityRowToArray)(row);
217
+ expect(cells.slice(COL.partnerEmployer - 1, COL.partnerLast)
218
+ .every((c) => c === "")).toBe(true);
219
+ expect(cells.slice(COL.meaningBm5First - 1, COL.meaningBm7Last)
220
+ .every((c) => c === "")).toBe(true);
221
+ });
222
+ it("never fills the optional learner-outcome block", () => {
223
+ // Fifteen columns we hold nothing equivalent to. Optional on CEC's own
224
+ // guide, so they are left empty and not warned about.
225
+ const [row] = (0, compassExport_1.buildCompassActivityRows)(event(), ["A1"]);
226
+ const cells = (0, compassTemplate_1.compassActivityRowToArray)(row);
227
+ expect(cells.slice(COL.outcomeFirst - 1, COL.outcomeLast)
228
+ .every((c) => c === "")).toBe(true);
229
+ expect((0, compassExport_1.validateCompassExportEvent)(event(), 1, 1)).toEqual([]);
230
+ });
231
+ });
232
+ describe("placements", () => {
233
+ it("is a Benchmark 6 employer encounter, derived not guessed", () => {
234
+ const [row] = (0, compassExport_1.buildCompassPlacementRows)(placement(), "A123456789012");
235
+ const cells = (0, compassTemplate_1.compassActivityRowToArray)(row);
236
+ expect(row.benchmarks).toEqual([6]);
237
+ expect(cell(cells, COL.activityName))
238
+ .toBe("Work experience at Bartlett Engineering");
239
+ expect(cell(cells, COL.typeWorkplaces)).toBe("Yes");
240
+ expect(cell(cells, COL.partnerEmployer)).toBe("Yes");
241
+ expect(cell(cells, COL.deliveryMethod)).toBe("In person");
242
+ expect(cell(cells, COL.start)).toBe("21/06/2027 09:00");
243
+ expect(cell(cells, COL.end)).toBe("25/06/2027 16:00");
244
+ expect(cell(cells, COL.learners)).toBe("A123456789012");
245
+ });
246
+ it("reads BM6 meaningfulness off the feedback that actually came back", () => {
247
+ const [both] = (0, compassExport_1.buildCompassPlacementRows)(placement(), "A1");
248
+ expect(both.meaningfulness[6]).toEqual([
249
+ "Employer feedback provided", "Reflection opportunity",
250
+ ]);
251
+ const [neither] = (0, compassExport_1.buildCompassPlacementRows)(placement({ feedback: noFeedback }), "A1");
252
+ expect(neither.meaningfulness[6]).toBeUndefined();
253
+ expect((0, compassExport_1.validateCompassExportPlacement)(placement({ feedback: noFeedback }), "A1")
254
+ .some((i) => /Benchmark 6 is claimed/.test(i.message))).toBe(true);
255
+ });
256
+ it("calls a remote placement Online", () => {
257
+ const [row] = (0, compassExport_1.buildCompassPlacementRows)(placement({ remote: true }), "A1");
258
+ expect(row.deliveryMethod).toBe("Online");
259
+ });
260
+ it("drops a pupil with no UPN and says why", () => {
261
+ expect((0, compassExport_1.buildCompassPlacementRows)(placement(), "")).toEqual([]);
262
+ const issues = (0, compassExport_1.validateCompassExportPlacement)(placement(), undefined, "Ada Test");
263
+ expect(issues.some((i) => i.level === "blocking" && /UPN/.test(i.message)))
264
+ .toBe(true);
265
+ });
266
+ });
267
+ describe("logged student activities", () => {
268
+ it("takes its benchmarks from the dashboard's own rule", () => {
269
+ const [row] = (0, compassExport_1.buildCompassStudentActivityRows)(activity(), "A1");
270
+ expect(row.benchmarks).toEqual([6]);
271
+ const cells = (0, compassTemplate_1.compassActivityRowToArray)(row);
272
+ expect(cell(cells, COL.typeWorkplaces)).toBe("Yes");
273
+ expect(cell(cells, COL.partnerEmployer)).toBe("Yes");
274
+ expect(cell(cells, COL.activityName)).toBe("Saturday job at Greggs");
275
+ });
276
+ it("maps every location type to one of Compass's three delivery methods", () => {
277
+ expect((0, compassExport_1.buildCompassStudentActivityRows)(activity(), "A1")[0].deliveryMethod)
278
+ .toBe("In person");
279
+ expect((0, compassExport_1.buildCompassStudentActivityRows)(activity({ locationType: "online" }), "A1")[0].deliveryMethod).toBe("Online");
280
+ expect((0, compassExport_1.buildCompassStudentActivityRows)(activity({ locationType: "hybrid" }), "A1")[0].deliveryMethod).toBe("Hybrid");
281
+ });
282
+ it("gives volunteering both of the activity types it genuinely is", () => {
283
+ const [row] = (0, compassExport_1.buildCompassStudentActivityRows)(activity({ activityType: "volunteering" }), "A1");
284
+ const cells = (0, compassTemplate_1.compassActivityRowToArray)(row);
285
+ expect(cell(cells, COL.typeWorkplaces)).toBe("Yes");
286
+ expect(cell(cells, COL.typeVolunteering)).toBe("Yes");
287
+ });
288
+ it("leaves the careers partner empty where the type does not settle it", () => {
289
+ // A workshop may be run by an employer or by a training provider, so
290
+ // nothing is ticked and the preview asks the school.
291
+ const [row] = (0, compassExport_1.buildCompassStudentActivityRows)(activity({ activityType: "workshop" }), "A1");
292
+ expect(row.careersPartners).toEqual([]);
293
+ expect((0, compassExport_1.validateCompassExportStudentActivity)(activity({ activityType: "workshop" }), "A1")
294
+ .some((i) => i.level === "warning" && /careers partner/i.test(i.message)))
295
+ .toBe(true);
296
+ });
297
+ it("exports nothing for enrichment that isn't Gatsby evidence", () => {
298
+ expect((0, compassExport_1.buildCompassStudentActivityRows)(activity({ activityType: "sport" }), "A1")).toEqual([]);
299
+ expect((0, compassExport_1.buildCompassStudentActivityRows)(activity({ isArchived: true }), "A1")).toEqual([]);
300
+ expect((0, compassExport_1.validateCompassExportStudentActivity)(activity({ activityType: "epq" }), "A1")
301
+ .some((i) => i.level === "blocking")).toBe(true);
302
+ });
303
+ });
304
+ describe("guidance meetings", () => {
305
+ it("is a complete Benchmark 8 row with nothing left for the school", () => {
306
+ const [row] = (0, compassGuidanceExport_1.buildCompassGuidanceRows)(guidanceEntry(), "A1", "Sam Staff");
307
+ const cells = (0, compassTemplate_1.compassActivityRowToArray)(row);
308
+ expect(cell(cells, COL.gb8)).toBe("Yes");
309
+ expect(cell(cells, COL.typeGuidance)).toBe("Yes");
310
+ expect(cell(cells, COL.partnerAdviser)).toBe("Yes");
311
+ expect(cell(cells, COL.schoolLead)).toBe("Sam Staff");
312
+ expect((0, compassTemplate_1.validateCompassActivityRow)(row)).toEqual([]);
313
+ });
314
+ it("never exports the private staff notes", () => {
315
+ // The record splits a pupil-facing summary from staff notes precisely
316
+ // so the summary can leave the building. A file emailed onward is
317
+ // where that matters.
318
+ const [row] = (0, compassGuidanceExport_1.buildCompassGuidanceRows)(guidanceEntry(), "A1");
319
+ const cells = (0, compassTemplate_1.compassActivityRowToArray)(row);
320
+ expect(cell(cells, COL.notes)).toBe("Talked through apprenticeships.");
321
+ expect(cells.join(" ")).not.toContain("Safeguarding");
322
+ expect(JSON.stringify(row)).not.toContain("Safeguarding");
323
+ });
324
+ });
325
+ describe("delivery method mapping", () => {
326
+ it("maps every careers-record mode to one of Compass's three values", () => {
327
+ expect((0, compassExport_1.compassDeliveryMethod)("inPerson")).toBe("In person");
328
+ expect((0, compassExport_1.compassDeliveryMethod)("video")).toBe("Online");
329
+ // No exact equivalent; "Online" is the honest remote option, and the
330
+ // field is mandatory so it cannot be left blank.
331
+ expect((0, compassExport_1.compassDeliveryMethod)("phone")).toBe("Online");
332
+ });
333
+ });
334
+ //# sourceMappingURL=compassExport.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compassExport.test.js","sourceRoot":"","sources":["../src/compassExport.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "placementt-core",
4
4
  "author": "Placementt",
5
- "version": "1.300.1061",
5
+ "version": "1.300.1062",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
@@ -0,0 +1,405 @@
1
+ import {
2
+ COMPASS_ACTIVITY_COLUMNS,
3
+ COMPASS_ACTIVITY_HEADERS,
4
+ COMPASS_ACTIVITY_TYPES,
5
+ COMPASS_CAREERS_PARTNER_COLUMNS,
6
+ COMPASS_INDUSTRIES,
7
+ COMPASS_MEANINGFULNESS_COLUMNS,
8
+ COMPASS_LEARNER_OUTCOME_COLUMNS,
9
+ compassActivityRowToArray,
10
+ emptyCompassActivityRow,
11
+ formatCompassDateTime,
12
+ validateCompassActivityRow,
13
+ } from "./compassTemplate";
14
+ import {
15
+ buildCompassActivityRows,
16
+ buildCompassPlacementRows,
17
+ buildCompassStudentActivityRows,
18
+ compassDeliveryMethod,
19
+ validateCompassExportEvent,
20
+ validateCompassExportPlacement,
21
+ validateCompassExportStudentActivity,
22
+ } from "./compassExport";
23
+ import {buildCompassGuidanceRows} from "./compassGuidanceExport";
24
+ import {
25
+ CareersRecordEntry, ExternalEvent, StudentActivity, StudentPlacementData,
26
+ } from "./typeDefinitions";
27
+
28
+ // Column positions from CEC's own Activities sheet, 1-indexed as the workbook
29
+ // numbers them. Written out by hand from the template rather than derived from
30
+ // the code, so this test can actually catch the code drifting.
31
+ const COL = {
32
+ activityName: 1, gb2: 2, gb5: 5, gb6: 6, gb7: 7, gb8: 8, deliveryMethod: 9,
33
+ schoolLead: 10, start: 11, end: 12, partnerEmployer: 13, partnerAdviser: 16,
34
+ partnerLast: 21, typeEmployerEncounter: 24, typeWorkplaces: 25,
35
+ typeGuidance: 28, typeVolunteering: 30, meaningBm5First: 32,
36
+ meaningBm7Last: 50, pal: 51, learners: 52, parentAttendance: 53,
37
+ outcomeFirst: 54, outcomeLast: 68, industries: 69, notes: 70,
38
+ };
39
+ const cell = (row: string[], column: number) => row[column - 1];
40
+
41
+ const event = (over: Partial<ExternalEvent> = {}): Partial<ExternalEvent>&{id: string} => ({
42
+ id: "ev1",
43
+ name: "Employer insight morning",
44
+ description: "Six local employers ran carousel sessions.",
45
+ designatedStaffText: "Mrs Smith",
46
+ gatsbyBenchmarks: [5, 7],
47
+ deliveryMethod: "In person",
48
+ compassActivityTypes: ["Employer encounter"],
49
+ compassCareersPartners: ["Employer"],
50
+ compassMeaningfulness: {
51
+ 5: ["clear purpose shared"],
52
+ 7: ["Clear purpose shared"],
53
+ },
54
+ startDate: "2027-03-04",
55
+ startTime: "09:30",
56
+ endDate: "2027-03-04",
57
+ endTime: "12:00",
58
+ ...over,
59
+ } as Partial<ExternalEvent>&{id: string});
60
+
61
+ const noFeedback = {
62
+ students: {completed: false}, parent: {completed: false},
63
+ provider: {completed: false}, staff: {completed: false},
64
+ };
65
+
66
+ const placement = (over: Partial<StudentPlacementData> = {}): StudentPlacementData => ({
67
+ id: "pl1",
68
+ uid: "pupil-1",
69
+ oId: "org-1",
70
+ name: "Bartlett Engineering",
71
+ title: "",
72
+ startDate: "2027-06-21",
73
+ endDate: "2027-06-25",
74
+ feedback: {
75
+ students: {completed: true},
76
+ parent: {completed: false},
77
+ provider: {completed: true},
78
+ staff: {completed: false},
79
+ },
80
+ ...over,
81
+ } as unknown as StudentPlacementData);
82
+
83
+ const activity = (over: Partial<StudentActivity> = {}): StudentActivity => ({
84
+ id: "ac1",
85
+ uid: "pupil-1",
86
+ oId: "org-1",
87
+ title: "Saturday job at Greggs",
88
+ description: "Counter service.",
89
+ activityType: "partTimeWork",
90
+ locationType: "inPerson",
91
+ startDate: "2027-01-10",
92
+ endDate: "2027-01-10",
93
+ dateCreated: "2027-01-11T09:00:00.000Z",
94
+ ...over,
95
+ } as unknown as StudentActivity);
96
+
97
+ const guidanceEntry = (over: Partial<CareersRecordEntry> = {}): CareersRecordEntry => ({
98
+ id: "cr1",
99
+ uid: "pupil-1",
100
+ oId: "org-1",
101
+ entryType: "personalGuidance",
102
+ date: "2027-02-11",
103
+ adviserUid: "staff-1",
104
+ adviserQualified: true,
105
+ academicYear: "2026-27",
106
+ mode: "inPerson",
107
+ summary: "Talked through apprenticeships.",
108
+ staffNotes: "Safeguarding: do not share.",
109
+ createdBy: "staff-1",
110
+ dateCreated: "2027-02-11T10:00:00.000Z",
111
+ ...over,
112
+ });
113
+
114
+ describe("the template's column spec", () => {
115
+ it("is the 70 columns of CEC's Activities sheet, in its order", () => {
116
+ expect(COMPASS_ACTIVITY_COLUMNS).toHaveLength(70);
117
+ expect(COMPASS_ACTIVITY_HEADERS).toHaveLength(70);
118
+ expect(compassActivityRowToArray(emptyCompassActivityRow("event", "x")))
119
+ .toHaveLength(70);
120
+
121
+ expect(cell(COMPASS_ACTIVITY_HEADERS, COL.activityName))
122
+ .toBe("Activity Name*\n(Enter name)");
123
+ expect(cell(COMPASS_ACTIVITY_HEADERS, COL.gb2))
124
+ .toBe("GB2 - Learning from careers and labour market information");
125
+ expect(cell(COMPASS_ACTIVITY_HEADERS, COL.gb8)).toBe("GB8 - Personal guidance");
126
+ expect(cell(COMPASS_ACTIVITY_HEADERS, COL.deliveryMethod)).toBe("Delivery method*");
127
+ expect(cell(COMPASS_ACTIVITY_HEADERS, COL.pal))
128
+ .toBe("Provider Access Legislation eligible encounter?");
129
+ expect(cell(COMPASS_ACTIVITY_HEADERS, COL.parentAttendance)).toBe("Parent Attendance?");
130
+ expect(cell(COMPASS_ACTIVITY_HEADERS, COL.industries))
131
+ .toBe("Industries involved in the activity");
132
+ expect(cell(COMPASS_ACTIVITY_HEADERS, COL.notes)).toBe("Notes");
133
+ });
134
+
135
+ it("keeps the header row's counts, not the Input guide's", () => {
136
+ // The workbook's Input guide says "Type of activity (7 in total)" and
137
+ // "Type of Careers Partner involved (8 in total)". The header carries
138
+ // ten and nine, and the header is what the upload parses.
139
+ expect(COMPASS_ACTIVITY_TYPES).toHaveLength(10);
140
+ expect(COMPASS_CAREERS_PARTNER_COLUMNS).toHaveLength(9);
141
+ expect(COMPASS_MEANINGFULNESS_COLUMNS.filter((c) => c.benchmark === 5)).toHaveLength(4);
142
+ expect(COMPASS_MEANINGFULNESS_COLUMNS.filter((c) => c.benchmark === 6)).toHaveLength(8);
143
+ expect(COMPASS_MEANINGFULNESS_COLUMNS.filter((c) => c.benchmark === 7)).toHaveLength(7);
144
+ expect(COMPASS_LEARNER_OUTCOME_COLUMNS).toHaveLength(15);
145
+ expect(COMPASS_INDUSTRIES).toHaveLength(23);
146
+ expect(COMPASS_INDUSTRIES[0]).toEqual({code: 1, name: "Agriculture/farming"});
147
+ expect(COMPASS_INDUSTRIES[22])
148
+ .toEqual({code: 23, name: "Transport/storage/driving"});
149
+ });
150
+
151
+ it("writes every boolean block as Yes or empty, never No", () => {
152
+ const row = emptyCompassActivityRow("event", "x");
153
+ row.benchmarks = [5];
154
+ row.parentAttendance = true;
155
+ const cells = compassActivityRowToArray(row);
156
+ expect(cell(cells, COL.gb2)).toBe("");
157
+ expect(cell(cells, COL.gb5)).toBe("Yes");
158
+ expect(cell(cells, COL.parentAttendance)).toBe("Yes");
159
+ expect(cell(cells, COL.pal)).toBe("");
160
+ });
161
+
162
+ it("formats dates the way the template asks for them", () => {
163
+ expect(formatCompassDateTime("2027-03-04", "09:30")).toBe("04/03/2027 09:30");
164
+ // No time recorded: a school-day default rather than a blank mandatory cell.
165
+ expect(formatCompassDateTime("2027-03-04")).toBe("04/03/2027 09:00");
166
+ expect(formatCompassDateTime(undefined, "09:30")).toBe("");
167
+ expect(formatCompassDateTime("nonsense")).toBe("");
168
+ });
169
+ });
170
+
171
+ describe("events", () => {
172
+ it("is one row per activity, with a Yes per tagged benchmark", () => {
173
+ // The old model was one row per benchmark. The real template carries
174
+ // GB2-GB8 as seven columns, so a BM5 + BM7 event is a single row.
175
+ const rows = buildCompassActivityRows(event(), ["A1", "A2"]);
176
+ expect(rows).toHaveLength(1);
177
+
178
+ const cells = compassActivityRowToArray(rows[0]);
179
+ expect(cell(cells, COL.activityName)).toBe("Employer insight morning");
180
+ expect(cell(cells, COL.gb5)).toBe("Yes");
181
+ expect(cell(cells, COL.gb7)).toBe("Yes");
182
+ expect(cell(cells, COL.gb6)).toBe("");
183
+ expect(cell(cells, COL.deliveryMethod)).toBe("In person");
184
+ expect(cell(cells, COL.schoolLead)).toBe("Mrs Smith");
185
+ expect(cell(cells, COL.start)).toBe("04/03/2027 09:30");
186
+ expect(cell(cells, COL.end)).toBe("04/03/2027 12:00");
187
+ expect(cell(cells, COL.partnerEmployer)).toBe("Yes");
188
+ expect(cell(cells, COL.typeEmployerEncounter)).toBe("Yes");
189
+ expect(cell(cells, COL.meaningBm5First)).toBe("Yes");
190
+ expect(cell(cells, COL.learners)).toBe("A1;A2");
191
+ expect(cell(cells, COL.notes))
192
+ .toBe("Six local employers ran carousel sessions.");
193
+ });
194
+
195
+ it("only carries meaningfulness for the benchmarks the event still claims", () => {
196
+ const [row] = buildCompassActivityRows(
197
+ event({gatsbyBenchmarks: [5], compassMeaningfulness: {
198
+ 5: ["clear purpose shared"], 6: ["Tasks or work produced"],
199
+ }}), ["A1"]);
200
+ expect(row.meaningfulness[5]).toEqual(["clear purpose shared"]);
201
+ expect(row.meaningfulness[6]).toBeUndefined();
202
+ });
203
+
204
+ it("only stamps PAL on a PAL-relevant benchmark", () => {
205
+ const [pal] = buildCompassActivityRows(event({palCompliant: true}), ["A1"]);
206
+ expect(pal.palEligible).toBe(true);
207
+ const [notPal] = buildCompassActivityRows(
208
+ event({palCompliant: true, gatsbyBenchmarks: [2]}), ["A1"]);
209
+ expect(notPal.palEligible).toBe(false);
210
+ });
211
+
212
+ it("exports nothing for an event with no benchmark in 2-8", () => {
213
+ expect(buildCompassActivityRows(event({gatsbyBenchmarks: [1]}), ["A1"]))
214
+ .toEqual([]);
215
+ expect(buildCompassActivityRows(event({gatsbyBenchmarks: []}), ["A1"]))
216
+ .toEqual([]);
217
+ });
218
+
219
+ it("blocks a row CEC's sheet would reject", () => {
220
+ expect(validateCompassExportEvent(event(), 2, 2)).toEqual([]);
221
+
222
+ const noMethod = validateCompassExportEvent(
223
+ event({deliveryMethod: undefined}), 2, 2);
224
+ expect(noMethod.some((i) => i.level === "blocking" &&
225
+ /delivery method/i.test(i.message))).toBe(true);
226
+
227
+ const noType = validateCompassExportEvent(
228
+ event({compassActivityTypes: []}), 2, 2);
229
+ expect(noType.some((i) => i.level === "blocking" &&
230
+ /activity type/i.test(i.message))).toBe(true);
231
+
232
+ const noBenchmark = validateCompassExportEvent(
233
+ event({gatsbyBenchmarks: [1]}), 2, 2);
234
+ expect(noBenchmark.some((i) => i.level === "blocking" &&
235
+ /Benchmark/.test(i.message))).toBe(true);
236
+ });
237
+
238
+ it("blocks an event whose attendees have no UPN, and warns on a partial gap", () => {
239
+ // Compass+ silently drops an unmatched learner, so a row with no UPN
240
+ // uploads as an activity nobody attended.
241
+ const none = validateCompassExportEvent(event(), 12, 0);
242
+ expect(none.some((i) => i.level === "blocking" && /UPN/.test(i.message)))
243
+ .toBe(true);
244
+
245
+ const some = validateCompassExportEvent(event(), 12, 9);
246
+ expect(some.some((i) => i.level === "warning" && /3 of 12/.test(i.message)))
247
+ .toBe(true);
248
+ expect(some.some((i) => i.level === "blocking")).toBe(false);
249
+ });
250
+
251
+ it("warns rather than guesses when a mandatory block has no source", () => {
252
+ const issues = validateCompassExportEvent(event({
253
+ compassCareersPartners: [],
254
+ compassMeaningfulness: {},
255
+ }), 2, 2);
256
+ expect(issues.length).toBeGreaterThan(0);
257
+ expect(issues.every((i) => i.level === "warning")).toBe(true);
258
+ expect(issues.some((i) => /careers partner/i.test(i.message))).toBe(true);
259
+ expect(issues.some((i) => /Benchmark 5 is claimed/.test(i.message))).toBe(true);
260
+ expect(issues.some((i) => /Benchmark 7 is claimed/.test(i.message))).toBe(true);
261
+
262
+ // and the columns really are left empty, not filled in.
263
+ const [row] = buildCompassActivityRows(event({
264
+ compassCareersPartners: [], compassMeaningfulness: {},
265
+ }), ["A1"]);
266
+ const cells = compassActivityRowToArray(row);
267
+ expect(cells.slice(COL.partnerEmployer - 1, COL.partnerLast)
268
+ .every((c) => c === "")).toBe(true);
269
+ expect(cells.slice(COL.meaningBm5First - 1, COL.meaningBm7Last)
270
+ .every((c) => c === "")).toBe(true);
271
+ });
272
+
273
+ it("never fills the optional learner-outcome block", () => {
274
+ // Fifteen columns we hold nothing equivalent to. Optional on CEC's own
275
+ // guide, so they are left empty and not warned about.
276
+ const [row] = buildCompassActivityRows(event(), ["A1"]);
277
+ const cells = compassActivityRowToArray(row);
278
+ expect(cells.slice(COL.outcomeFirst - 1, COL.outcomeLast)
279
+ .every((c) => c === "")).toBe(true);
280
+ expect(validateCompassExportEvent(event(), 1, 1)).toEqual([]);
281
+ });
282
+ });
283
+
284
+ describe("placements", () => {
285
+ it("is a Benchmark 6 employer encounter, derived not guessed", () => {
286
+ const [row] = buildCompassPlacementRows(placement(), "A123456789012");
287
+ const cells = compassActivityRowToArray(row);
288
+ expect(row.benchmarks).toEqual([6]);
289
+ expect(cell(cells, COL.activityName))
290
+ .toBe("Work experience at Bartlett Engineering");
291
+ expect(cell(cells, COL.typeWorkplaces)).toBe("Yes");
292
+ expect(cell(cells, COL.partnerEmployer)).toBe("Yes");
293
+ expect(cell(cells, COL.deliveryMethod)).toBe("In person");
294
+ expect(cell(cells, COL.start)).toBe("21/06/2027 09:00");
295
+ expect(cell(cells, COL.end)).toBe("25/06/2027 16:00");
296
+ expect(cell(cells, COL.learners)).toBe("A123456789012");
297
+ });
298
+
299
+ it("reads BM6 meaningfulness off the feedback that actually came back", () => {
300
+ const [both] = buildCompassPlacementRows(placement(), "A1");
301
+ expect(both.meaningfulness[6]).toEqual([
302
+ "Employer feedback provided", "Reflection opportunity",
303
+ ]);
304
+
305
+ const [neither] = buildCompassPlacementRows(
306
+ placement({feedback: noFeedback}), "A1");
307
+ expect(neither.meaningfulness[6]).toBeUndefined();
308
+ expect(validateCompassExportPlacement(placement({feedback: noFeedback}), "A1")
309
+ .some((i) => /Benchmark 6 is claimed/.test(i.message))).toBe(true);
310
+ });
311
+
312
+ it("calls a remote placement Online", () => {
313
+ const [row] = buildCompassPlacementRows(placement({remote: true}), "A1");
314
+ expect(row.deliveryMethod).toBe("Online");
315
+ });
316
+
317
+ it("drops a pupil with no UPN and says why", () => {
318
+ expect(buildCompassPlacementRows(placement(), "")).toEqual([]);
319
+ const issues = validateCompassExportPlacement(placement(), undefined, "Ada Test");
320
+ expect(issues.some((i) => i.level === "blocking" && /UPN/.test(i.message)))
321
+ .toBe(true);
322
+ });
323
+ });
324
+
325
+ describe("logged student activities", () => {
326
+ it("takes its benchmarks from the dashboard's own rule", () => {
327
+ const [row] = buildCompassStudentActivityRows(activity(), "A1");
328
+ expect(row.benchmarks).toEqual([6]);
329
+ const cells = compassActivityRowToArray(row);
330
+ expect(cell(cells, COL.typeWorkplaces)).toBe("Yes");
331
+ expect(cell(cells, COL.partnerEmployer)).toBe("Yes");
332
+ expect(cell(cells, COL.activityName)).toBe("Saturday job at Greggs");
333
+ });
334
+
335
+ it("maps every location type to one of Compass's three delivery methods", () => {
336
+ expect(buildCompassStudentActivityRows(activity(), "A1")[0].deliveryMethod)
337
+ .toBe("In person");
338
+ expect(buildCompassStudentActivityRows(
339
+ activity({locationType: "online"}), "A1")[0].deliveryMethod).toBe("Online");
340
+ expect(buildCompassStudentActivityRows(
341
+ activity({locationType: "hybrid"}), "A1")[0].deliveryMethod).toBe("Hybrid");
342
+ });
343
+
344
+ it("gives volunteering both of the activity types it genuinely is", () => {
345
+ const [row] = buildCompassStudentActivityRows(
346
+ activity({activityType: "volunteering"}), "A1");
347
+ const cells = compassActivityRowToArray(row);
348
+ expect(cell(cells, COL.typeWorkplaces)).toBe("Yes");
349
+ expect(cell(cells, COL.typeVolunteering)).toBe("Yes");
350
+ });
351
+
352
+ it("leaves the careers partner empty where the type does not settle it", () => {
353
+ // A workshop may be run by an employer or by a training provider, so
354
+ // nothing is ticked and the preview asks the school.
355
+ const [row] = buildCompassStudentActivityRows(
356
+ activity({activityType: "workshop"}), "A1");
357
+ expect(row.careersPartners).toEqual([]);
358
+ expect(validateCompassExportStudentActivity(
359
+ activity({activityType: "workshop"}), "A1")
360
+ .some((i) => i.level === "warning" && /careers partner/i.test(i.message)))
361
+ .toBe(true);
362
+ });
363
+
364
+ it("exports nothing for enrichment that isn't Gatsby evidence", () => {
365
+ expect(buildCompassStudentActivityRows(
366
+ activity({activityType: "sport"}), "A1")).toEqual([]);
367
+ expect(buildCompassStudentActivityRows(
368
+ activity({isArchived: true}), "A1")).toEqual([]);
369
+ expect(validateCompassExportStudentActivity(activity({activityType: "epq"}), "A1")
370
+ .some((i) => i.level === "blocking")).toBe(true);
371
+ });
372
+ });
373
+
374
+ describe("guidance meetings", () => {
375
+ it("is a complete Benchmark 8 row with nothing left for the school", () => {
376
+ const [row] = buildCompassGuidanceRows(guidanceEntry(), "A1", "Sam Staff");
377
+ const cells = compassActivityRowToArray(row);
378
+ expect(cell(cells, COL.gb8)).toBe("Yes");
379
+ expect(cell(cells, COL.typeGuidance)).toBe("Yes");
380
+ expect(cell(cells, COL.partnerAdviser)).toBe("Yes");
381
+ expect(cell(cells, COL.schoolLead)).toBe("Sam Staff");
382
+ expect(validateCompassActivityRow(row)).toEqual([]);
383
+ });
384
+
385
+ it("never exports the private staff notes", () => {
386
+ // The record splits a pupil-facing summary from staff notes precisely
387
+ // so the summary can leave the building. A file emailed onward is
388
+ // where that matters.
389
+ const [row] = buildCompassGuidanceRows(guidanceEntry(), "A1");
390
+ const cells = compassActivityRowToArray(row);
391
+ expect(cell(cells, COL.notes)).toBe("Talked through apprenticeships.");
392
+ expect(cells.join(" ")).not.toContain("Safeguarding");
393
+ expect(JSON.stringify(row)).not.toContain("Safeguarding");
394
+ });
395
+ });
396
+
397
+ describe("delivery method mapping", () => {
398
+ it("maps every careers-record mode to one of Compass's three values", () => {
399
+ expect(compassDeliveryMethod("inPerson")).toBe("In person");
400
+ expect(compassDeliveryMethod("video")).toBe("Online");
401
+ // No exact equivalent; "Online" is the honest remote option, and the
402
+ // field is mandatory so it cannot be left blank.
403
+ expect(compassDeliveryMethod("phone")).toBe("Online");
404
+ });
405
+ });