placementt-core 1.400.938 → 1.400.940

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,60 @@
1
+ import { ExternalEvent } from "./typeDefinitions";
2
+ /**
3
+ * Benchmarks Compass+ accepts on an individual Activity row. GB1 ("a stable careers programme")
4
+ * describes whole-provision leadership, not a single encounter — CEC's Activities Upload template
5
+ * only offers Benchmarks 2-8 in its Benchmark dropdown, so GB1-only events have nothing to export.
6
+ */
7
+ export declare const COMPASS_VALID_ACTIVITY_BENCHMARKS: number[];
8
+ /**
9
+ * Benchmarks where the PAL Compliance column is meaningful. Provider Access Legislation covers
10
+ * encounters with technical education/apprenticeship providers, which sit under GB5-7 — Compass+
11
+ * only reflects this field on activities tagged with one of these three.
12
+ */
13
+ export declare const COMPASS_PAL_BENCHMARKS: number[];
14
+ /**
15
+ * Whether CareerThread is currently a CEC-recognised Compass+ "Partner Platform" (the status
16
+ * Unifrog, Xello/Cascaid, Morrisby etc. have). While this is false, Compass+ can't auto-detect our
17
+ * CSV as a known source on upload — schools may hit a "Missing Activity Source Data" error and have
18
+ * no listed option to pick us from the resolution dropdown. Flip this once CEC confirms we're
19
+ * listed as a partner; until then, exports carry a standing warning so staff don't assume the file
20
+ * will upload cleanly.
21
+ */
22
+ export declare const COMPASS_PARTNER_PLATFORM_RECOGNISED = false;
23
+ export declare const COMPASS_ACTIVITY_CSV_HEADERS: string[];
24
+ export type CompassActivityRow = {
25
+ benchmark: number;
26
+ category: string;
27
+ activityName: string;
28
+ startDateTime: string;
29
+ endDateTime: string;
30
+ learnerUpns: string;
31
+ schoolLead: string;
32
+ notes: string;
33
+ palCompliance: "Yes" | "";
34
+ parentAttendance: "Yes" | "";
35
+ };
36
+ export type CompassExportIssue = {
37
+ eventId: string;
38
+ eventName: string;
39
+ level: "blocking" | "warning";
40
+ message: string;
41
+ };
42
+ /**
43
+ * Checks whether an event is safe to include in a Compass+ export. "blocking" issues mean the
44
+ * event is left out of the file entirely (it would fail Compass+'s own validation and, worse,
45
+ * could silently upload with the wrong benchmark/category). "warning" issues still get exported,
46
+ * but should be shown to staff before they send the file on to a school.
47
+ */
48
+ export declare function validateCompassExportEvent(event: Partial<ExternalEvent> & {
49
+ id?: string;
50
+ }, learnerCount: number, learnersWithUpnCount: number): CompassExportIssue[];
51
+ /**
52
+ * Expands a single event into one Compass+ Activities-Upload row per valid benchmark. Compass+'s
53
+ * template is one benchmark + one category per row, so an event tagged with multiple benchmarks
54
+ * (e.g. an employer talk logged as both GB5 and GB7) becomes multiple rows sharing the same
55
+ * activity name, dates and learner list. Rows for benchmarks without a mandatory category are
56
+ * skipped — callers should run validateCompassExportEvent first and surface that as a blocking
57
+ * issue rather than silently dropping the row here.
58
+ */
59
+ export declare function buildCompassActivityRows(event: Partial<ExternalEvent>, learnerUpns: string[]): CompassActivityRow[];
60
+ export declare function compassActivityRowToCsvArray(row: CompassActivityRow): string[];
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COMPASS_ACTIVITY_CSV_HEADERS = exports.COMPASS_PARTNER_PLATFORM_RECOGNISED = exports.COMPASS_PAL_BENCHMARKS = exports.COMPASS_VALID_ACTIVITY_BENCHMARKS = void 0;
4
+ exports.validateCompassExportEvent = validateCompassExportEvent;
5
+ exports.buildCompassActivityRows = buildCompassActivityRows;
6
+ exports.compassActivityRowToCsvArray = compassActivityRowToCsvArray;
7
+ /**
8
+ * Benchmarks Compass+ accepts on an individual Activity row. GB1 ("a stable careers programme")
9
+ * describes whole-provision leadership, not a single encounter — CEC's Activities Upload template
10
+ * only offers Benchmarks 2-8 in its Benchmark dropdown, so GB1-only events have nothing to export.
11
+ */
12
+ exports.COMPASS_VALID_ACTIVITY_BENCHMARKS = [2, 3, 4, 5, 6, 7, 8];
13
+ /**
14
+ * Benchmarks where the PAL Compliance column is meaningful. Provider Access Legislation covers
15
+ * encounters with technical education/apprenticeship providers, which sit under GB5-7 — Compass+
16
+ * only reflects this field on activities tagged with one of these three.
17
+ */
18
+ exports.COMPASS_PAL_BENCHMARKS = [5, 6, 7];
19
+ /**
20
+ * Whether CareerThread is currently a CEC-recognised Compass+ "Partner Platform" (the status
21
+ * Unifrog, Xello/Cascaid, Morrisby etc. have). While this is false, Compass+ can't auto-detect our
22
+ * CSV as a known source on upload — schools may hit a "Missing Activity Source Data" error and have
23
+ * no listed option to pick us from the resolution dropdown. Flip this once CEC confirms we're
24
+ * listed as a partner; until then, exports carry a standing warning so staff don't assume the file
25
+ * will upload cleanly.
26
+ */
27
+ exports.COMPASS_PARTNER_PLATFORM_RECOGNISED = false;
28
+ exports.COMPASS_ACTIVITY_CSV_HEADERS = [
29
+ "Benchmark", "Main category", "Activity name", "Start Date and Time",
30
+ "End Date and Time", "Learners involved", "School Lead", "Notes",
31
+ "PAL Compliance", "Parent Attendance",
32
+ ];
33
+ const formatDbDateTime = (dateStr, timeStr) => {
34
+ if (!dateStr)
35
+ return "";
36
+ const [y, m, d] = dateStr.split("-");
37
+ if (!y || !m || !d)
38
+ return "";
39
+ return `${d}/${m}/${y} ${timeStr || "09:00"}`;
40
+ };
41
+ /**
42
+ * Checks whether an event is safe to include in a Compass+ export. "blocking" issues mean the
43
+ * event is left out of the file entirely (it would fail Compass+'s own validation and, worse,
44
+ * could silently upload with the wrong benchmark/category). "warning" issues still get exported,
45
+ * but should be shown to staff before they send the file on to a school.
46
+ */
47
+ function validateCompassExportEvent(event, learnerCount, learnersWithUpnCount) {
48
+ const issues = [];
49
+ const eventId = event.id || "";
50
+ const eventName = event.name || "Untitled event";
51
+ const push = (level, message) => issues.push({ eventId, eventName, level, message });
52
+ const validBenchmarks = (event.gatsbyBenchmarks || [])
53
+ .filter((b) => exports.COMPASS_VALID_ACTIVITY_BENCHMARKS.includes(b));
54
+ if (!validBenchmarks.length) {
55
+ push("blocking", "No Gatsby Benchmark (2-8) selected — Benchmark 1 alone can't be uploaded as an individual Compass+ activity.");
56
+ }
57
+ validBenchmarks.forEach((b) => {
58
+ if (!event.gatsbyCategories?.[b]) {
59
+ push("blocking", `Benchmark ${b} is selected but has no Compass+ category. Category is mandatory — Compass+ will reject this row.`);
60
+ }
61
+ });
62
+ if (!event.name)
63
+ push("blocking", "Event has no name.");
64
+ if (!event.startDate)
65
+ push("blocking", "Event has no start date.");
66
+ if (learnerCount > 0 && learnersWithUpnCount === 0) {
67
+ push("warning", "None of the attending learners have a UPN on file — Compass+ won't be able to match this activity to individual learner profiles.");
68
+ }
69
+ else if (learnersWithUpnCount < learnerCount) {
70
+ push("warning", `${learnerCount - learnersWithUpnCount} of ${learnerCount} attending learners are missing a UPN and will be left off this row.`);
71
+ }
72
+ if (!exports.COMPASS_PARTNER_PLATFORM_RECOGNISED) {
73
+ push("warning", "CareerThread isn't yet a CEC-recognised Compass+ partner platform. The school may need to manually pick a source on upload, or the upload may be rejected until we're listed.");
74
+ }
75
+ return issues;
76
+ }
77
+ /**
78
+ * Expands a single event into one Compass+ Activities-Upload row per valid benchmark. Compass+'s
79
+ * template is one benchmark + one category per row, so an event tagged with multiple benchmarks
80
+ * (e.g. an employer talk logged as both GB5 and GB7) becomes multiple rows sharing the same
81
+ * activity name, dates and learner list. Rows for benchmarks without a mandatory category are
82
+ * skipped — callers should run validateCompassExportEvent first and surface that as a blocking
83
+ * issue rather than silently dropping the row here.
84
+ */
85
+ function buildCompassActivityRows(event, learnerUpns) {
86
+ const validBenchmarks = (event.gatsbyBenchmarks || [])
87
+ .filter((b) => exports.COMPASS_VALID_ACTIVITY_BENCHMARKS.includes(b) && event.gatsbyCategories?.[b]);
88
+ return validBenchmarks.map((b) => ({
89
+ benchmark: b,
90
+ category: event.gatsbyCategories?.[b] || "",
91
+ activityName: event.name || "",
92
+ startDateTime: formatDbDateTime(event.startDate, event.startTime),
93
+ endDateTime: formatDbDateTime(event.endDate || event.startDate, event.endTime || event.startTime),
94
+ learnerUpns: learnerUpns.join(";"),
95
+ schoolLead: event.designatedStaffText || "",
96
+ notes: event.description || "",
97
+ palCompliance: (event.palCompliant && exports.COMPASS_PAL_BENCHMARKS.includes(b)) ? "Yes" : "",
98
+ parentAttendance: event.parentAttendance ? "Yes" : "",
99
+ }));
100
+ }
101
+ function compassActivityRowToCsvArray(row) {
102
+ return [
103
+ String(row.benchmark),
104
+ row.category,
105
+ row.activityName,
106
+ row.startDateTime,
107
+ row.endDateTime,
108
+ row.learnerUpns,
109
+ row.schoolLead,
110
+ row.notes,
111
+ row.palCompliance,
112
+ row.parentAttendance,
113
+ ];
114
+ }
115
+ //# sourceMappingURL=compassExport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compassExport.js","sourceRoot":"","sources":["../src/compassExport.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export * from "./firebase/firebaseQuery";
9
9
  export * from "./typeDefinitions";
10
10
  export * from "./emailSections";
11
11
  export * from "./exportTransformers";
12
+ export * from "./compassExport";
12
13
  export * from "./tutorialTips";
13
14
  export * from "./constants";
14
15
  export * from "./hooks";
package/lib/index.js CHANGED
@@ -29,6 +29,7 @@ __exportStar(require("./firebase/firebaseQuery"), exports);
29
29
  __exportStar(require("./typeDefinitions"), exports);
30
30
  __exportStar(require("./emailSections"), exports);
31
31
  __exportStar(require("./exportTransformers"), exports);
32
+ __exportStar(require("./compassExport"), exports);
32
33
  __exportStar(require("./tutorialTips"), exports);
33
34
  __exportStar(require("./constants"), exports);
34
35
  __exportStar(require("./hooks"), exports);
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,kDAAgC;AAChC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,0CAAwB;AACxB,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,kDAAgC;AAChC,uDAAqC;AACrC,kDAAgC;AAChC,iDAA+B;AAC/B,8CAA4B;AAC5B,0CAAwB;AACxB,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
@@ -367,6 +367,7 @@ export type UserData = {
367
367
  [key: string]: any;
368
368
  };
369
369
  parentEmails?: WondeParentContact[];
370
+ upn?: string;
370
371
  wonde?: {
371
372
  upi: string;
372
373
  id: string;
@@ -405,6 +406,18 @@ export type UserData = {
405
406
  [oId: string]: string[];
406
407
  };
407
408
  analytics?: AnalyticsItem;
409
+ recentSkillScores?: {
410
+ [skillId: string]: {
411
+ students?: number;
412
+ parents?: number;
413
+ employers?: number;
414
+ };
415
+ };
416
+ averageRecentSkillScore?: {
417
+ students?: number;
418
+ parents?: number;
419
+ employers?: number;
420
+ };
408
421
  units?: string;
409
422
  activationCode: number;
410
423
  cohort?: string;
@@ -470,6 +483,11 @@ export type UserData = {
470
483
  };
471
484
  };
472
485
  };
486
+ savedAnalyticsViews?: {
487
+ [cohortId: string]: {
488
+ [viewKey: string]: DataViewerFilterView;
489
+ };
490
+ };
473
491
  hasReservedListings?: boolean;
474
492
  reservedListings?: {
475
493
  expiryDate: string;
@@ -1117,6 +1135,9 @@ export type CohortData = {
1117
1135
  [key: string]: DataViewerFilterView;
1118
1136
  };
1119
1137
  };
1138
+ savedAnalyticsViews?: {
1139
+ [viewKey: string]: DataViewerFilterView;
1140
+ };
1120
1141
  userFilters?: {
1121
1142
  allSelected: boolean;
1122
1143
  search: string | undefined;
@@ -1638,6 +1659,16 @@ export type ExternalEvent = {
1638
1659
  activityId?: string;
1639
1660
  visibleTo: string[];
1640
1661
  gatsbyBenchmarks?: number[];
1662
+ gatsbyCategories?: {
1663
+ [benchmark: number]: string;
1664
+ };
1665
+ palCompliant?: boolean;
1666
+ parentAttendance?: boolean;
1667
+ compassExport?: {
1668
+ exportedAt: string;
1669
+ exportedBy: string;
1670
+ batchId?: string;
1671
+ };
1641
1672
  oId: string;
1642
1673
  submissionClose: string;
1643
1674
  acceptingEmployers?: boolean;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "placementt-core",
4
4
  "author": "Placementt",
5
- "version": "1.400.938",
5
+ "version": "1.400.940",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
@@ -0,0 +1,148 @@
1
+ import {ExternalEvent} from "./typeDefinitions";
2
+
3
+ /**
4
+ * Benchmarks Compass+ accepts on an individual Activity row. GB1 ("a stable careers programme")
5
+ * describes whole-provision leadership, not a single encounter — CEC's Activities Upload template
6
+ * only offers Benchmarks 2-8 in its Benchmark dropdown, so GB1-only events have nothing to export.
7
+ */
8
+ export const COMPASS_VALID_ACTIVITY_BENCHMARKS = [2, 3, 4, 5, 6, 7, 8];
9
+
10
+ /**
11
+ * Benchmarks where the PAL Compliance column is meaningful. Provider Access Legislation covers
12
+ * encounters with technical education/apprenticeship providers, which sit under GB5-7 — Compass+
13
+ * only reflects this field on activities tagged with one of these three.
14
+ */
15
+ export const COMPASS_PAL_BENCHMARKS = [5, 6, 7];
16
+
17
+ /**
18
+ * Whether CareerThread is currently a CEC-recognised Compass+ "Partner Platform" (the status
19
+ * Unifrog, Xello/Cascaid, Morrisby etc. have). While this is false, Compass+ can't auto-detect our
20
+ * CSV as a known source on upload — schools may hit a "Missing Activity Source Data" error and have
21
+ * no listed option to pick us from the resolution dropdown. Flip this once CEC confirms we're
22
+ * listed as a partner; until then, exports carry a standing warning so staff don't assume the file
23
+ * will upload cleanly.
24
+ */
25
+ export const COMPASS_PARTNER_PLATFORM_RECOGNISED = false;
26
+
27
+ export const COMPASS_ACTIVITY_CSV_HEADERS = [
28
+ "Benchmark", "Main category", "Activity name", "Start Date and Time",
29
+ "End Date and Time", "Learners involved", "School Lead", "Notes",
30
+ "PAL Compliance", "Parent Attendance",
31
+ ];
32
+
33
+ export type CompassActivityRow = {
34
+ benchmark: number,
35
+ category: string,
36
+ activityName: string,
37
+ startDateTime: string,
38
+ endDateTime: string,
39
+ learnerUpns: string,
40
+ schoolLead: string,
41
+ notes: string,
42
+ palCompliance: "Yes"|"",
43
+ parentAttendance: "Yes"|"",
44
+ }
45
+
46
+ export type CompassExportIssue = {
47
+ eventId: string,
48
+ eventName: string,
49
+ level: "blocking"|"warning",
50
+ message: string,
51
+ }
52
+
53
+ const formatDbDateTime = (dateStr?: string, timeStr?: string): string => {
54
+ if (!dateStr) return "";
55
+ const [y, m, d] = dateStr.split("-");
56
+ if (!y || !m || !d) return "";
57
+ return `${d}/${m}/${y} ${timeStr || "09:00"}`;
58
+ };
59
+
60
+ /**
61
+ * Checks whether an event is safe to include in a Compass+ export. "blocking" issues mean the
62
+ * event is left out of the file entirely (it would fail Compass+'s own validation and, worse,
63
+ * could silently upload with the wrong benchmark/category). "warning" issues still get exported,
64
+ * but should be shown to staff before they send the file on to a school.
65
+ */
66
+ export function validateCompassExportEvent(
67
+ event: Partial<ExternalEvent>&{id?: string},
68
+ learnerCount: number,
69
+ learnersWithUpnCount: number,
70
+ ): CompassExportIssue[] {
71
+ const issues: CompassExportIssue[] = [];
72
+ const eventId = event.id || "";
73
+ const eventName = event.name || "Untitled event";
74
+ const push = (level: CompassExportIssue["level"], message: string) =>
75
+ issues.push({eventId, eventName, level, message});
76
+
77
+ const validBenchmarks = (event.gatsbyBenchmarks || [])
78
+ .filter((b) => COMPASS_VALID_ACTIVITY_BENCHMARKS.includes(b));
79
+
80
+ if (!validBenchmarks.length) {
81
+ push("blocking", "No Gatsby Benchmark (2-8) selected — Benchmark 1 alone can't be uploaded as an individual Compass+ activity.");
82
+ }
83
+
84
+ validBenchmarks.forEach((b) => {
85
+ if (!event.gatsbyCategories?.[b]) {
86
+ push("blocking", `Benchmark ${b} is selected but has no Compass+ category. Category is mandatory — Compass+ will reject this row.`);
87
+ }
88
+ });
89
+
90
+ if (!event.name) push("blocking", "Event has no name.");
91
+ if (!event.startDate) push("blocking", "Event has no start date.");
92
+
93
+ if (learnerCount > 0 && learnersWithUpnCount === 0) {
94
+ push("warning", "None of the attending learners have a UPN on file — Compass+ won't be able to match this activity to individual learner profiles.");
95
+ } else if (learnersWithUpnCount < learnerCount) {
96
+ push("warning", `${learnerCount - learnersWithUpnCount} of ${learnerCount} attending learners are missing a UPN and will be left off this row.`);
97
+ }
98
+
99
+ if (!COMPASS_PARTNER_PLATFORM_RECOGNISED) {
100
+ push("warning", "CareerThread isn't yet a CEC-recognised Compass+ partner platform. The school may need to manually pick a source on upload, or the upload may be rejected until we're listed.");
101
+ }
102
+
103
+ return issues;
104
+ }
105
+
106
+ /**
107
+ * Expands a single event into one Compass+ Activities-Upload row per valid benchmark. Compass+'s
108
+ * template is one benchmark + one category per row, so an event tagged with multiple benchmarks
109
+ * (e.g. an employer talk logged as both GB5 and GB7) becomes multiple rows sharing the same
110
+ * activity name, dates and learner list. Rows for benchmarks without a mandatory category are
111
+ * skipped — callers should run validateCompassExportEvent first and surface that as a blocking
112
+ * issue rather than silently dropping the row here.
113
+ */
114
+ export function buildCompassActivityRows(
115
+ event: Partial<ExternalEvent>,
116
+ learnerUpns: string[],
117
+ ): CompassActivityRow[] {
118
+ const validBenchmarks = (event.gatsbyBenchmarks || [])
119
+ .filter((b) => COMPASS_VALID_ACTIVITY_BENCHMARKS.includes(b) && event.gatsbyCategories?.[b]);
120
+
121
+ return validBenchmarks.map((b) => ({
122
+ benchmark: b,
123
+ category: event.gatsbyCategories?.[b] || "",
124
+ activityName: event.name || "",
125
+ startDateTime: formatDbDateTime(event.startDate, event.startTime),
126
+ endDateTime: formatDbDateTime(event.endDate || event.startDate, event.endTime || event.startTime),
127
+ learnerUpns: learnerUpns.join(";"),
128
+ schoolLead: event.designatedStaffText || "",
129
+ notes: event.description || "",
130
+ palCompliance: (event.palCompliant && COMPASS_PAL_BENCHMARKS.includes(b)) ? "Yes" : "",
131
+ parentAttendance: event.parentAttendance ? "Yes" : "",
132
+ }));
133
+ }
134
+
135
+ export function compassActivityRowToCsvArray(row: CompassActivityRow): string[] {
136
+ return [
137
+ String(row.benchmark),
138
+ row.category,
139
+ row.activityName,
140
+ row.startDateTime,
141
+ row.endDateTime,
142
+ row.learnerUpns,
143
+ row.schoolLead,
144
+ row.notes,
145
+ row.palCompliance,
146
+ row.parentAttendance,
147
+ ];
148
+ }
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ export * from "./firebase/firebaseQuery";
11
11
  export * from "./typeDefinitions";
12
12
  export * from "./emailSections";
13
13
  export * from "./exportTransformers";
14
+ export * from "./compassExport";
14
15
  export * from "./tutorialTips";
15
16
  export * from "./constants";
16
17
  export * from "./hooks";
@@ -379,6 +379,7 @@ export type UserData = {
379
379
  [key: string]: any
380
380
  },
381
381
  parentEmails?: WondeParentContact[],
382
+ upn?: string, // DfE Unique Pupil Number — needed to match learners for Compass+ activity exports. Populated from Wonde's education_details include (Wonde-integrated schools only; not yet collected via CSV upload).
382
383
  wonde?: {
383
384
  upi: string,
384
385
  id: string,
@@ -411,6 +412,13 @@ export type UserData = {
411
412
  cohortRequests: {[oId: string]: string[]},
412
413
  cohorts: {[oId: string]: string[]},
413
414
  analytics?: AnalyticsItem,
415
+ // ── Recent skills snapshot (maintained by onSkillsAssessmentWriteRecentSkills) ──
416
+ // Latest score per skill per rater, plus the mean across the institute's assessed
417
+ // skills per rater. Recomputed from the student's own skillsAssessments on every
418
+ // write to that collection, so edits and deletes stay correct without storing dates.
419
+ // Rater keys are the plurals used by SkillAssessment.userType.
420
+ recentSkillScores?: {[skillId: string]: {students?: number, parents?: number, employers?: number}},
421
+ averageRecentSkillScore?: {students?: number, parents?: number, employers?: number},
414
422
  units?: string,
415
423
  activationCode: number,
416
424
  cohort?: string,
@@ -482,6 +490,12 @@ export type UserData = {
482
490
  }
483
491
  }
484
492
  },
493
+ // Personal ("me") saved analytics views, keyed by cohort id then view key.
494
+ savedAnalyticsViews?: {
495
+ [cohortId: string]: {
496
+ [viewKey: string]: DataViewerFilterView
497
+ }
498
+ },
485
499
  hasReservedListings?: boolean,
486
500
  reservedListings?: {
487
501
  expiryDate: string,
@@ -1126,6 +1140,10 @@ export type CohortData = {
1126
1140
  [key: string]: DataViewerFilterView
1127
1141
  }
1128
1142
  },
1143
+ // Shared ("everyone") saved analytics views, keyed by view key.
1144
+ savedAnalyticsViews?: {
1145
+ [viewKey: string]: DataViewerFilterView
1146
+ },
1129
1147
  userFilters?: {
1130
1148
  allSelected: boolean;
1131
1149
  search: string | undefined;
@@ -1598,6 +1616,22 @@ export type ExternalEvent = {
1598
1616
  activityId?: string,
1599
1617
  visibleTo: string[] // All institutes that can see this event. E.g. Careers Hubs and schools
1600
1618
  gatsbyBenchmarks?: number[], // String array
1619
+ // Compass+ "Main category" per selected benchmark (keyed by benchmark number, e.g. 5). Left as free text
1620
+ // for now — CEC's category options are benchmark-specific and only visible inside the Compass+ Activities
1621
+ // Upload template itself, so this isn't a closed enum yet. Tighten to a per-benchmark union once we've
1622
+ // sourced the authoritative list.
1623
+ gatsbyCategories?: {[benchmark: number]: string},
1624
+ palCompliant?: boolean, // Provider Access Legislation compliance (Compass+ Activities Upload Column I). Only meaningful when gatsbyBenchmarks includes 5, 6 or 7.
1625
+ parentAttendance?: boolean, // Did a parent/carer attend this event (Compass+ Activities Upload Column J).
1626
+ // Dedup guardrail: stamped whenever this event is included in a generated Compass+ export, from
1627
+ // either the single-event export or the bulk Exports tab. Means "a file containing this event was
1628
+ // generated", not "this reached Compass+" — we have no way to confirm the latter. Both export
1629
+ // surfaces must check and set this so an event can't be silently re-exported.
1630
+ compassExport?: {
1631
+ exportedAt: string,
1632
+ exportedBy: string, // uid of the staff member who triggered the export
1633
+ batchId?: string, // links back to the export run, for tracing duplicate-data reports
1634
+ },
1601
1635
  oId: string,
1602
1636
  submissionClose: string,
1603
1637
  acceptingEmployers?: boolean,