placementt-core 1.400.1016 → 1.400.1018
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/benchmarkEvidenceRules.d.ts +83 -9
- package/lib/benchmarkEvidenceRules.js +78 -9
- package/lib/benchmarkEvidenceRules.js.map +1 -1
- package/lib/benchmarkEvidenceRules.test.js +72 -6
- package/lib/benchmarkEvidenceRules.test.js.map +1 -1
- package/lib/productResolver.d.ts +5 -0
- package/lib/productResolver.js +1 -0
- package/lib/productResolver.js.map +1 -1
- package/lib/typeDefinitions.d.ts +62 -0
- package/package.json +1 -1
- package/src/benchmarkEvidenceRules.test.ts +86 -7
- package/src/benchmarkEvidenceRules.ts +134 -13
- package/src/productResolver.ts +6 -0
- package/src/typeDefinitions.ts +83 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExternalEvent, StudentActivity } from "./typeDefinitions";
|
|
1
|
+
import { CareersRecordEntry, ExternalEvent, StudentActivity } from "./typeDefinitions";
|
|
2
2
|
/**
|
|
3
3
|
* ─── Single source of truth for Gatsby Benchmark *evidence* ───────────────────
|
|
4
4
|
*
|
|
@@ -20,7 +20,25 @@ export type GatsbyBenchmarkNumber = (typeof GATSBY_BENCHMARK_NUMBERS)[number];
|
|
|
20
20
|
* Where a benchmark's evidence comes from on CareerThread. Drives how the dashboard
|
|
21
21
|
* renders each card and whether it can show a coverage percentage at all.
|
|
22
22
|
*/
|
|
23
|
-
export type BenchmarkEvidenceSource = "provision" | "destinations" | "events" | "eventsAndActivities" | "eventsAndPlacements" | "
|
|
23
|
+
export type BenchmarkEvidenceSource = "provision" | "destinations" | "events" | "eventsAndActivities" | "eventsAndPlacements" | "guidance";
|
|
24
|
+
/**
|
|
25
|
+
* ─── Time model ───────────────────────────────────────────────────────────────
|
|
26
|
+
* The period a benchmark's coverage is measured over. This is NOT cosmetic: the
|
|
27
|
+
* benchmarks genuinely differ, and measuring them all annually (as this module
|
|
28
|
+
* originally did) understates coverage badly.
|
|
29
|
+
*
|
|
30
|
+
* BM5 renews every year → "Every year, from the age of 11, at least one
|
|
31
|
+
* meaningful encounter with an employer."
|
|
32
|
+
* BM2 runs per Key Stage → "During each Key Stage, all pupils should access
|
|
33
|
+
* and use information about careers…"
|
|
34
|
+
* BM6/7/8 are cumulative → "By the age of 16… and a further one by 18."
|
|
35
|
+
*
|
|
36
|
+
* The cumulative case is the one that bites. Measured annually, a Year 11 who did
|
|
37
|
+
* their work experience in Year 10 reads as uncovered, and every pupil in Years 7-9
|
|
38
|
+
* sits in the denominator of a duty that does not apply to them until 16 — which is
|
|
39
|
+
* why `biggestGap` currently keeps nominating Year 7 as a school's worst gap on BM6.
|
|
40
|
+
*/
|
|
41
|
+
export type BenchmarkTimeModel = "annual" | "keyStage" | "cumulativeByAge" | "provision";
|
|
24
42
|
export type BenchmarkMeta = {
|
|
25
43
|
number: GatsbyBenchmarkNumber;
|
|
26
44
|
/** Short title (no "GB"/"score" wording). */
|
|
@@ -30,22 +48,61 @@ export type BenchmarkMeta = {
|
|
|
30
48
|
source: BenchmarkEvidenceSource;
|
|
31
49
|
/**
|
|
32
50
|
* Whether this benchmark yields a per-student coverage number. False for BM1
|
|
33
|
-
* (provision-level, shown as an informational card)
|
|
34
|
-
*
|
|
35
|
-
* benchmark where this is false.
|
|
51
|
+
* (provision-level, shown as an informational card). The dashboard must not
|
|
52
|
+
* render a coverage % for a benchmark where this is false.
|
|
36
53
|
*/
|
|
37
54
|
countsTowardCoverage: boolean;
|
|
38
55
|
/** True for 5-7: the PAL Compliance flag is meaningful (mirrors COMPASS_PAL_BENCHMARKS). */
|
|
39
56
|
palRelevant: boolean;
|
|
57
|
+
/** How coverage is periodised. Drives both the denominator and the card's label. */
|
|
58
|
+
timeModel: BenchmarkTimeModel;
|
|
40
59
|
};
|
|
41
60
|
/**
|
|
42
|
-
* Benchmarks that produce a coverage percentage on the matrix / cards.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
61
|
+
* Benchmarks that produce a coverage percentage on the matrix / cards. BM1 is the
|
|
62
|
+
* only exclusion: it is whole-programme provision and can never be a per-pupil
|
|
63
|
+
* number. Derived from the Compass export's valid set (2-8) so the two files can't
|
|
64
|
+
* drift.
|
|
65
|
+
*
|
|
66
|
+
* BM8 was previously excluded here because personal guidance was untracked. It is
|
|
67
|
+
* now recorded on the careers record, so it earns a coverage number like the rest —
|
|
68
|
+
* but a cumulative, age-banded one. See BENCHMARK_AGE_CHECKPOINTS.
|
|
46
69
|
*/
|
|
47
70
|
export declare const BENCHMARK_COVERAGE_NUMBERS: GatsbyBenchmarkNumber[];
|
|
48
71
|
export declare const BENCHMARK_META: Record<GatsbyBenchmarkNumber, BenchmarkMeta>;
|
|
72
|
+
/**
|
|
73
|
+
* ─── Age checkpoints for cumulative benchmarks ────────────────────────────────
|
|
74
|
+
* BM6, BM7 and BM8 are all phrased as "by the age of 16… and by 18". Coverage for
|
|
75
|
+
* these is judged at the two points where the duty actually falls due, not spread
|
|
76
|
+
* across every year group: asking what proportion of Year 7 has had a personal
|
|
77
|
+
* guidance meeting measures nothing, because none of them are due one yet.
|
|
78
|
+
*
|
|
79
|
+
* The headline figure for each checkpoint is therefore the *due* cohort — the year
|
|
80
|
+
* group at which the duty has just become expected. Earlier year groups still get a
|
|
81
|
+
* progression figure (are we on track?), but they must not drag the headline down.
|
|
82
|
+
*/
|
|
83
|
+
export type BenchmarkAgeCheckpoint = {
|
|
84
|
+
/** The age the benchmark names. */
|
|
85
|
+
age: 16 | 18;
|
|
86
|
+
/** The year group at which the duty has fallen due in full. */
|
|
87
|
+
dueYearGroup: number;
|
|
88
|
+
/** Year groups the duty is accumulating across, up to and including the due one. */
|
|
89
|
+
accumulatingYearGroups: number[];
|
|
90
|
+
label: string;
|
|
91
|
+
};
|
|
92
|
+
export declare const BENCHMARK_AGE_CHECKPOINTS: BenchmarkAgeCheckpoint[];
|
|
93
|
+
/** The checkpoint a year group is working towards, or undefined if off-scale. */
|
|
94
|
+
export declare function ageCheckpointForYearGroup(yearGroup: number): BenchmarkAgeCheckpoint | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* The pupil's year group at some earlier date, back-derived from their current year
|
|
97
|
+
* group. Records written before `yearGroupAtEntry` existed (and every placement and
|
|
98
|
+
* event, which never carried one) have no snapshot, so cumulative coverage has to
|
|
99
|
+
* reconstruct it: each academic year that has elapsed since the record is one year
|
|
100
|
+
* group to subtract.
|
|
101
|
+
*
|
|
102
|
+
* Returns undefined when the result falls outside schooling, which is the honest
|
|
103
|
+
* answer for a record whose date predates the pupil joining.
|
|
104
|
+
*/
|
|
105
|
+
export declare function yearGroupAtDate(currentYearGroup: number, recordDate: string | undefined, now?: Date | string): number | undefined;
|
|
49
106
|
/**
|
|
50
107
|
* StudentActivity.activityType → the benchmark(s) that activity is evidence for.
|
|
51
108
|
* Employer-facing encounters count towards BM5; first-hand workplace experiences
|
|
@@ -80,6 +137,23 @@ export declare function eventEvidenceBenchmarks(event: Pick<ExternalEvent, "gats
|
|
|
80
137
|
export declare function eventProvidesBenchmark(event: Pick<ExternalEvent, "gatsbyBenchmarks">, benchmark: GatsbyBenchmarkNumber): boolean;
|
|
81
138
|
/** The benchmarks a logged student activity is evidence for. */
|
|
82
139
|
export declare function activityEvidenceBenchmarks(activity: Pick<StudentActivity, "activityType">): GatsbyBenchmarkNumber[];
|
|
140
|
+
/**
|
|
141
|
+
* ─── Careers record entries as evidence ───────────────────────────────────────
|
|
142
|
+
* Every entry is BM3 evidence: the updated Benchmark 3 asks schools to record the
|
|
143
|
+
* individual advice given to each pupil and any decisions subsequently agreed, and
|
|
144
|
+
* makes no distinction between a formal guidance interview and an option-choice
|
|
145
|
+
* conversation for that purpose.
|
|
146
|
+
*
|
|
147
|
+
* BM8 is far narrower. It is satisfied only by a personal guidance meeting with a
|
|
148
|
+
* careers adviser trained to an appropriate level. A school that logs fifty tutor-time
|
|
149
|
+
* careers chats has kept excellent records and has NOT delivered personal guidance, so
|
|
150
|
+
* only `personalGuidance` entries with `adviserQualified` count here. Loosening this
|
|
151
|
+
* would manufacture compliance with a statutory expectation, which is the one thing
|
|
152
|
+
* this module exists to prevent.
|
|
153
|
+
*/
|
|
154
|
+
export declare function isPersonalGuidanceEntry(entry: Pick<CareersRecordEntry, "entryType" | "adviserQualified">): boolean;
|
|
155
|
+
/** The benchmarks a careers record entry is evidence for. */
|
|
156
|
+
export declare function careersRecordEvidenceBenchmarks(entry: Pick<CareersRecordEntry, "entryType" | "adviserQualified">): GatsbyBenchmarkNumber[];
|
|
83
157
|
/**
|
|
84
158
|
* A PAL-compliant provider encounter: the event carries the existing PAL flag AND
|
|
85
159
|
* is tagged with a PAL-relevant benchmark (5-7). Same rule the export uses when
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PAL_PHASES = exports.PAL_ENCOUNTERS_PER_PHASE = exports.ACTIVITY_TYPE_BENCHMARKS = exports.BENCHMARK_META = exports.BENCHMARK_COVERAGE_NUMBERS = exports.GATSBY_BENCHMARK_NUMBERS = void 0;
|
|
3
|
+
exports.PAL_PHASES = exports.PAL_ENCOUNTERS_PER_PHASE = exports.ACTIVITY_TYPE_BENCHMARKS = exports.BENCHMARK_AGE_CHECKPOINTS = exports.BENCHMARK_META = exports.BENCHMARK_COVERAGE_NUMBERS = exports.GATSBY_BENCHMARK_NUMBERS = void 0;
|
|
4
|
+
exports.ageCheckpointForYearGroup = ageCheckpointForYearGroup;
|
|
5
|
+
exports.yearGroupAtDate = yearGroupAtDate;
|
|
4
6
|
exports.eventEvidenceBenchmarks = eventEvidenceBenchmarks;
|
|
5
7
|
exports.eventProvidesBenchmark = eventProvidesBenchmark;
|
|
6
8
|
exports.activityEvidenceBenchmarks = activityEvidenceBenchmarks;
|
|
9
|
+
exports.isPersonalGuidanceEntry = isPersonalGuidanceEntry;
|
|
10
|
+
exports.careersRecordEvidenceBenchmarks = careersRecordEvidenceBenchmarks;
|
|
7
11
|
exports.isPalCompliantProviderEncounter = isPalCompliantProviderEncounter;
|
|
8
12
|
exports.palPhaseForYearGroup = palPhaseForYearGroup;
|
|
9
13
|
exports.academicYearForDate = academicYearForDate;
|
|
@@ -28,12 +32,16 @@ const compassExport_1 = require("./compassExport");
|
|
|
28
32
|
*/
|
|
29
33
|
exports.GATSBY_BENCHMARK_NUMBERS = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
30
34
|
/**
|
|
31
|
-
* Benchmarks that produce a coverage percentage on the matrix / cards.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
+
* Benchmarks that produce a coverage percentage on the matrix / cards. BM1 is the
|
|
36
|
+
* only exclusion: it is whole-programme provision and can never be a per-pupil
|
|
37
|
+
* number. Derived from the Compass export's valid set (2-8) so the two files can't
|
|
38
|
+
* drift.
|
|
39
|
+
*
|
|
40
|
+
* BM8 was previously excluded here because personal guidance was untracked. It is
|
|
41
|
+
* now recorded on the careers record, so it earns a coverage number like the rest —
|
|
42
|
+
* but a cumulative, age-banded one. See BENCHMARK_AGE_CHECKPOINTS.
|
|
35
43
|
*/
|
|
36
|
-
exports.BENCHMARK_COVERAGE_NUMBERS = compassExport_1.COMPASS_VALID_ACTIVITY_BENCHMARKS
|
|
44
|
+
exports.BENCHMARK_COVERAGE_NUMBERS = [...compassExport_1.COMPASS_VALID_ACTIVITY_BENCHMARKS];
|
|
37
45
|
exports.BENCHMARK_META = {
|
|
38
46
|
1: {
|
|
39
47
|
number: 1,
|
|
@@ -42,6 +50,7 @@ exports.BENCHMARK_META = {
|
|
|
42
50
|
source: "provision",
|
|
43
51
|
countsTowardCoverage: false,
|
|
44
52
|
palRelevant: false,
|
|
53
|
+
timeModel: "provision",
|
|
45
54
|
},
|
|
46
55
|
2: {
|
|
47
56
|
number: 2,
|
|
@@ -50,6 +59,9 @@ exports.BENCHMARK_META = {
|
|
|
50
59
|
source: "events",
|
|
51
60
|
countsTowardCoverage: true,
|
|
52
61
|
palRelevant: false,
|
|
62
|
+
// The benchmark's unit is the Key Stage, not the year. Still measured annually
|
|
63
|
+
// until the Key Stage denominator lands — see BENCHMARK_AGE_CHECKPOINTS.
|
|
64
|
+
timeModel: "annual",
|
|
53
65
|
},
|
|
54
66
|
3: {
|
|
55
67
|
number: 3,
|
|
@@ -58,6 +70,7 @@ exports.BENCHMARK_META = {
|
|
|
58
70
|
source: "destinations",
|
|
59
71
|
countsTowardCoverage: true,
|
|
60
72
|
palRelevant: false,
|
|
73
|
+
timeModel: "annual",
|
|
61
74
|
},
|
|
62
75
|
4: {
|
|
63
76
|
number: 4,
|
|
@@ -66,6 +79,7 @@ exports.BENCHMARK_META = {
|
|
|
66
79
|
source: "events",
|
|
67
80
|
countsTowardCoverage: true,
|
|
68
81
|
palRelevant: false,
|
|
82
|
+
timeModel: "annual",
|
|
69
83
|
},
|
|
70
84
|
5: {
|
|
71
85
|
number: 5,
|
|
@@ -74,6 +88,9 @@ exports.BENCHMARK_META = {
|
|
|
74
88
|
source: "eventsAndActivities",
|
|
75
89
|
countsTowardCoverage: true,
|
|
76
90
|
palRelevant: true,
|
|
91
|
+
// The one benchmark whose annual model matches its own wording: "every year,
|
|
92
|
+
// from the age of 11, at least one meaningful encounter with an employer".
|
|
93
|
+
timeModel: "annual",
|
|
77
94
|
},
|
|
78
95
|
6: {
|
|
79
96
|
number: 6,
|
|
@@ -82,6 +99,7 @@ exports.BENCHMARK_META = {
|
|
|
82
99
|
source: "eventsAndPlacements",
|
|
83
100
|
countsTowardCoverage: true,
|
|
84
101
|
palRelevant: true,
|
|
102
|
+
timeModel: "cumulativeByAge",
|
|
85
103
|
},
|
|
86
104
|
7: {
|
|
87
105
|
number: 7,
|
|
@@ -90,16 +108,46 @@ exports.BENCHMARK_META = {
|
|
|
90
108
|
source: "events",
|
|
91
109
|
countsTowardCoverage: true,
|
|
92
110
|
palRelevant: true,
|
|
111
|
+
timeModel: "cumulativeByAge",
|
|
93
112
|
},
|
|
94
113
|
8: {
|
|
95
114
|
number: 8,
|
|
96
115
|
name: "Personal guidance",
|
|
97
|
-
description: "Every pupil has personal guidance
|
|
98
|
-
source: "
|
|
99
|
-
countsTowardCoverage:
|
|
116
|
+
description: "Every pupil has a personal guidance meeting with a careers adviser by 16, and a further one by 18.",
|
|
117
|
+
source: "guidance",
|
|
118
|
+
countsTowardCoverage: true,
|
|
100
119
|
palRelevant: false,
|
|
120
|
+
timeModel: "cumulativeByAge",
|
|
101
121
|
},
|
|
102
122
|
};
|
|
123
|
+
exports.BENCHMARK_AGE_CHECKPOINTS = [
|
|
124
|
+
{ age: 16, dueYearGroup: 11, accumulatingYearGroups: [7, 8, 9, 10, 11], label: "By age 16" },
|
|
125
|
+
{ age: 18, dueYearGroup: 13, accumulatingYearGroups: [12, 13], label: "By age 18" },
|
|
126
|
+
];
|
|
127
|
+
/** The checkpoint a year group is working towards, or undefined if off-scale. */
|
|
128
|
+
function ageCheckpointForYearGroup(yearGroup) {
|
|
129
|
+
return exports.BENCHMARK_AGE_CHECKPOINTS.find((c) => c.accumulatingYearGroups.includes(yearGroup));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The pupil's year group at some earlier date, back-derived from their current year
|
|
133
|
+
* group. Records written before `yearGroupAtEntry` existed (and every placement and
|
|
134
|
+
* event, which never carried one) have no snapshot, so cumulative coverage has to
|
|
135
|
+
* reconstruct it: each academic year that has elapsed since the record is one year
|
|
136
|
+
* group to subtract.
|
|
137
|
+
*
|
|
138
|
+
* Returns undefined when the result falls outside schooling, which is the honest
|
|
139
|
+
* answer for a record whose date predates the pupil joining.
|
|
140
|
+
*/
|
|
141
|
+
function yearGroupAtDate(currentYearGroup, recordDate, now = new Date()) {
|
|
142
|
+
if (!recordDate)
|
|
143
|
+
return undefined;
|
|
144
|
+
const recordYear = parseInt(academicYearForDate(recordDate).split("-")[0], 10);
|
|
145
|
+
const currentYear = parseInt(academicYearForDate(now).split("-")[0], 10);
|
|
146
|
+
if (isNaN(recordYear) || isNaN(currentYear))
|
|
147
|
+
return undefined;
|
|
148
|
+
const derived = currentYearGroup - (currentYear - recordYear);
|
|
149
|
+
return derived >= 1 && derived <= 15 ? derived : undefined;
|
|
150
|
+
}
|
|
103
151
|
/**
|
|
104
152
|
* StudentActivity.activityType → the benchmark(s) that activity is evidence for.
|
|
105
153
|
* Employer-facing encounters count towards BM5; first-hand workplace experiences
|
|
@@ -174,6 +222,27 @@ function eventProvidesBenchmark(event, benchmark) {
|
|
|
174
222
|
function activityEvidenceBenchmarks(activity) {
|
|
175
223
|
return exports.ACTIVITY_TYPE_BENCHMARKS[activity.activityType] || [];
|
|
176
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* ─── Careers record entries as evidence ───────────────────────────────────────
|
|
227
|
+
* Every entry is BM3 evidence: the updated Benchmark 3 asks schools to record the
|
|
228
|
+
* individual advice given to each pupil and any decisions subsequently agreed, and
|
|
229
|
+
* makes no distinction between a formal guidance interview and an option-choice
|
|
230
|
+
* conversation for that purpose.
|
|
231
|
+
*
|
|
232
|
+
* BM8 is far narrower. It is satisfied only by a personal guidance meeting with a
|
|
233
|
+
* careers adviser trained to an appropriate level. A school that logs fifty tutor-time
|
|
234
|
+
* careers chats has kept excellent records and has NOT delivered personal guidance, so
|
|
235
|
+
* only `personalGuidance` entries with `adviserQualified` count here. Loosening this
|
|
236
|
+
* would manufacture compliance with a statutory expectation, which is the one thing
|
|
237
|
+
* this module exists to prevent.
|
|
238
|
+
*/
|
|
239
|
+
function isPersonalGuidanceEntry(entry) {
|
|
240
|
+
return entry.entryType === "personalGuidance" && Boolean(entry.adviserQualified);
|
|
241
|
+
}
|
|
242
|
+
/** The benchmarks a careers record entry is evidence for. */
|
|
243
|
+
function careersRecordEvidenceBenchmarks(entry) {
|
|
244
|
+
return isPersonalGuidanceEntry(entry) ? [3, 8] : [3];
|
|
245
|
+
}
|
|
177
246
|
/**
|
|
178
247
|
* A PAL-compliant provider encounter: the event carries the existing PAL flag AND
|
|
179
248
|
* is tagged with a PAL-relevant benchmark (5-7). Same rule the export uses when
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"benchmarkEvidenceRules.js","sourceRoot":"","sources":["../src/benchmarkEvidenceRules.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"benchmarkEvidenceRules.js","sourceRoot":"","sources":["../src/benchmarkEvidenceRules.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,16 +4,27 @@ const benchmarkEvidenceRules_1 = require("./benchmarkEvidenceRules");
|
|
|
4
4
|
const compassExport_1 = require("./compassExport");
|
|
5
5
|
describe("benchmarkEvidenceRules single-source consistency", () => {
|
|
6
6
|
it("stays consistent with the Compass export's valid activity benchmarks (2-8)", () => {
|
|
7
|
-
// Coverage numbers are the export-valid set
|
|
8
|
-
|
|
7
|
+
// Coverage numbers are exactly the export-valid set. BM8 joined it when personal
|
|
8
|
+
// guidance became recordable on the careers record; BM1 was never in it.
|
|
9
|
+
expect(benchmarkEvidenceRules_1.BENCHMARK_COVERAGE_NUMBERS).toEqual([...compassExport_1.COMPASS_VALID_ACTIVITY_BENCHMARKS]);
|
|
9
10
|
expect(benchmarkEvidenceRules_1.BENCHMARK_COVERAGE_NUMBERS).not.toContain(1);
|
|
10
|
-
expect(benchmarkEvidenceRules_1.BENCHMARK_COVERAGE_NUMBERS).
|
|
11
|
+
expect(benchmarkEvidenceRules_1.BENCHMARK_COVERAGE_NUMBERS).toContain(8);
|
|
11
12
|
});
|
|
12
|
-
it("marks BM1 as provision-level and BM8 as
|
|
13
|
+
it("marks BM1 as the only provision-level benchmark, and BM8 as guidance-sourced", () => {
|
|
13
14
|
expect(benchmarkEvidenceRules_1.BENCHMARK_META[1].countsTowardCoverage).toBe(false);
|
|
14
15
|
expect(benchmarkEvidenceRules_1.BENCHMARK_META[1].source).toBe("provision");
|
|
15
|
-
expect(benchmarkEvidenceRules_1.BENCHMARK_META[8].countsTowardCoverage).toBe(
|
|
16
|
-
expect(benchmarkEvidenceRules_1.BENCHMARK_META[8].source).toBe("
|
|
16
|
+
expect(benchmarkEvidenceRules_1.BENCHMARK_META[8].countsTowardCoverage).toBe(true);
|
|
17
|
+
expect(benchmarkEvidenceRules_1.BENCHMARK_META[8].source).toBe("guidance");
|
|
18
|
+
});
|
|
19
|
+
it("periodises each benchmark the way its own wording does", () => {
|
|
20
|
+
// BM5 is the only benchmark that genuinely renews annually ("every year, from
|
|
21
|
+
// the age of 11"). BM6/7/8 are cumulative duties pinned to 16 and 18, and
|
|
22
|
+
// measuring them annually understates coverage — see BenchmarkTimeModel.
|
|
23
|
+
expect(benchmarkEvidenceRules_1.BENCHMARK_META[5].timeModel).toBe("annual");
|
|
24
|
+
expect(benchmarkEvidenceRules_1.BENCHMARK_META[6].timeModel).toBe("cumulativeByAge");
|
|
25
|
+
expect(benchmarkEvidenceRules_1.BENCHMARK_META[7].timeModel).toBe("cumulativeByAge");
|
|
26
|
+
expect(benchmarkEvidenceRules_1.BENCHMARK_META[8].timeModel).toBe("cumulativeByAge");
|
|
27
|
+
expect(benchmarkEvidenceRules_1.BENCHMARK_META[1].timeModel).toBe("provision");
|
|
17
28
|
});
|
|
18
29
|
it("flags PAL relevance for exactly benchmarks 5-7", () => {
|
|
19
30
|
[1, 2, 3, 4, 5, 6, 7, 8].forEach((b) => {
|
|
@@ -74,4 +85,59 @@ describe("coverage bands are neutral (no pass/fail theatre)", () => {
|
|
|
74
85
|
expect((0, benchmarkEvidenceRules_1.coverageBand)(1)).toBe("full");
|
|
75
86
|
});
|
|
76
87
|
});
|
|
88
|
+
describe("careers record entries as benchmark evidence", () => {
|
|
89
|
+
it("counts a qualified adviser's personal guidance towards BM8", () => {
|
|
90
|
+
const entry = { entryType: "personalGuidance", adviserQualified: true };
|
|
91
|
+
expect((0, benchmarkEvidenceRules_1.isPersonalGuidanceEntry)(entry)).toBe(true);
|
|
92
|
+
expect((0, benchmarkEvidenceRules_1.careersRecordEvidenceBenchmarks)(entry)).toEqual([3, 8]);
|
|
93
|
+
});
|
|
94
|
+
it("does NOT count personal guidance from an unqualified adviser towards BM8", () => {
|
|
95
|
+
// The benchmark is satisfied by a meeting with a careers adviser trained to an
|
|
96
|
+
// appropriate level. Counting an untrained member of staff would fabricate
|
|
97
|
+
// compliance with a statutory expectation.
|
|
98
|
+
const entry = { entryType: "personalGuidance", adviserQualified: false };
|
|
99
|
+
expect((0, benchmarkEvidenceRules_1.isPersonalGuidanceEntry)(entry)).toBe(false);
|
|
100
|
+
expect((0, benchmarkEvidenceRules_1.careersRecordEvidenceBenchmarks)(entry)).toEqual([3]);
|
|
101
|
+
});
|
|
102
|
+
it("does NOT let other entry types inflate BM8, however many are logged", () => {
|
|
103
|
+
const others = [
|
|
104
|
+
"careersConversation", "optionsMeeting", "applicationSupport",
|
|
105
|
+
"sendReview", "parentMeeting", "groupGuidance", "other",
|
|
106
|
+
];
|
|
107
|
+
others.forEach((entryType) => {
|
|
108
|
+
const entry = { entryType, adviserQualified: true };
|
|
109
|
+
expect((0, benchmarkEvidenceRules_1.isPersonalGuidanceEntry)(entry)).toBe(false);
|
|
110
|
+
expect((0, benchmarkEvidenceRules_1.careersRecordEvidenceBenchmarks)(entry)).toEqual([3]);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
it("still treats every entry as BM3 record-keeping evidence", () => {
|
|
114
|
+
expect((0, benchmarkEvidenceRules_1.careersRecordEvidenceBenchmarks)({ entryType: "careersConversation", adviserQualified: false })).toContain(3);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe("cumulative age-banded coverage", () => {
|
|
118
|
+
it("maps year groups onto the checkpoint they are working towards", () => {
|
|
119
|
+
expect((0, benchmarkEvidenceRules_1.ageCheckpointForYearGroup)(9)?.age).toBe(16);
|
|
120
|
+
expect((0, benchmarkEvidenceRules_1.ageCheckpointForYearGroup)(11)?.age).toBe(16);
|
|
121
|
+
expect((0, benchmarkEvidenceRules_1.ageCheckpointForYearGroup)(12)?.age).toBe(18);
|
|
122
|
+
expect((0, benchmarkEvidenceRules_1.ageCheckpointForYearGroup)(13)?.age).toBe(18);
|
|
123
|
+
expect((0, benchmarkEvidenceRules_1.ageCheckpointForYearGroup)(6)).toBeUndefined();
|
|
124
|
+
});
|
|
125
|
+
it("back-derives the year group a pupil was in when an older record was made", () => {
|
|
126
|
+
// A Year 11 pupil "now" (2025-26) did work experience in 2024-25, so they were
|
|
127
|
+
// in Year 10 at the time. Without this, BM6 reads them as uncovered.
|
|
128
|
+
expect((0, benchmarkEvidenceRules_1.yearGroupAtDate)(11, "2025-03-01", "2026-03-01")).toBe(10);
|
|
129
|
+
expect((0, benchmarkEvidenceRules_1.yearGroupAtDate)(11, "2025-11-01", "2026-03-01")).toBe(11);
|
|
130
|
+
expect((0, benchmarkEvidenceRules_1.yearGroupAtDate)(13, "2023-10-01", "2026-03-01")).toBe(11);
|
|
131
|
+
});
|
|
132
|
+
it("rolls over at the start of the academic year, not the calendar year", () => {
|
|
133
|
+
// August starts the new academic year, so a record dated 2025-09-01 and one
|
|
134
|
+
// dated 2026-01-01 sit in the same year group.
|
|
135
|
+
expect((0, benchmarkEvidenceRules_1.yearGroupAtDate)(11, "2025-09-01", "2026-01-01")).toBe(11);
|
|
136
|
+
expect((0, benchmarkEvidenceRules_1.yearGroupAtDate)(11, "2025-07-01", "2026-01-01")).toBe(10);
|
|
137
|
+
});
|
|
138
|
+
it("returns undefined rather than guessing when a date predates schooling", () => {
|
|
139
|
+
expect((0, benchmarkEvidenceRules_1.yearGroupAtDate)(11, undefined)).toBeUndefined();
|
|
140
|
+
expect((0, benchmarkEvidenceRules_1.yearGroupAtDate)(7, "2005-01-01", "2026-03-01")).toBeUndefined();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
77
143
|
//# sourceMappingURL=benchmarkEvidenceRules.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"benchmarkEvidenceRules.test.js","sourceRoot":"","sources":["../src/benchmarkEvidenceRules.test.ts"],"names":[],"mappings":";;AAAA,
|
|
1
|
+
{"version":3,"file":"benchmarkEvidenceRules.test.js","sourceRoot":"","sources":["../src/benchmarkEvidenceRules.test.ts"],"names":[],"mappings":"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"}
|
package/lib/productResolver.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ import { InstituteData } from "./typeDefinitions";
|
|
|
16
16
|
/** The product a surface can gate on. */
|
|
17
17
|
export type ProductKey = "wex" | "network" | "parents" | "events" | "skills" | "activities" | "destinations" | "alumni";
|
|
18
18
|
export type ResolvedProducts = Record<ProductKey, boolean> & {
|
|
19
|
+
/** How many parents are actually in the database, across `options.oIds`.
|
|
20
|
+
* `parents` is just this over zero; the number is surfaced so a caller that
|
|
21
|
+
* wants to show the count doesn't run the same aggregate a second time.
|
|
22
|
+
* Zero until the count lands, the same as the flag. */
|
|
23
|
+
parentCount: number;
|
|
19
24
|
/** Package shape, so callers stop testing `package ===` themselves. */
|
|
20
25
|
isTrust: boolean;
|
|
21
26
|
isHub: boolean;
|
package/lib/productResolver.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"productResolver.js","sourceRoot":"","sources":["../src/productResolver.ts"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"productResolver.js","sourceRoot":"","sources":["../src/productResolver.ts"],"names":[],"mappings":"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"}
|
package/lib/typeDefinitions.d.ts
CHANGED
|
@@ -3323,6 +3323,68 @@ export type StudentActivity = {
|
|
|
3323
3323
|
reflectionCompletedByUid?: string;
|
|
3324
3324
|
evidenceFileIds?: string[];
|
|
3325
3325
|
};
|
|
3326
|
+
/**
|
|
3327
|
+
* ─── Careers record ───────────────────────────────────────────────────────────
|
|
3328
|
+
* A staff-authored record of a careers interaction with one pupil. Deliberately
|
|
3329
|
+
* modelled as the pupil's *careers record* rather than a meetings log, because
|
|
3330
|
+
* Gatsby Benchmark 3 asks schools to keep systematic records of "the individual
|
|
3331
|
+
* advice given to each pupil, and any subsequent agreed decisions" — not only of
|
|
3332
|
+
* formal guidance interviews.
|
|
3333
|
+
*
|
|
3334
|
+
* Only `personalGuidance` entries delivered by a qualified adviser are evidence
|
|
3335
|
+
* for Benchmark 8. Every other type is BM3 record-keeping and must NOT move the
|
|
3336
|
+
* BM8 number: a school that logs fifty option-choice chats has not delivered
|
|
3337
|
+
* personal guidance, and showing otherwise would fabricate compliance.
|
|
3338
|
+
*/
|
|
3339
|
+
export type CareersRecordEntryType = "personalGuidance" | "careersConversation" | "optionsMeeting" | "applicationSupport" | "sendReview" | "parentMeeting" | "groupGuidance" | "other";
|
|
3340
|
+
/** A single agreed next step arising from a careers record entry. */
|
|
3341
|
+
export type CareersRecordAction = {
|
|
3342
|
+
label: string;
|
|
3343
|
+
owner?: "student" | "staff";
|
|
3344
|
+
dueDate?: string;
|
|
3345
|
+
done?: boolean;
|
|
3346
|
+
};
|
|
3347
|
+
export type CareersRecordEntry = {
|
|
3348
|
+
id: string;
|
|
3349
|
+
uid: string;
|
|
3350
|
+
oId: string;
|
|
3351
|
+
entryType: CareersRecordEntryType;
|
|
3352
|
+
date: string;
|
|
3353
|
+
/** Staff member who delivered it. Advisers are always platform users. */
|
|
3354
|
+
adviserUid: string;
|
|
3355
|
+
/**
|
|
3356
|
+
* Whether the adviser was, at the time of this entry, trained to a level the
|
|
3357
|
+
* school considers appropriate for personal guidance. Snapshotted rather than
|
|
3358
|
+
* read live from the staff record so that later changes to a staff member's
|
|
3359
|
+
* qualification cannot silently restate a previous year's BM8 coverage.
|
|
3360
|
+
*
|
|
3361
|
+
* Note the framework says "trained to an appropriate level", not Level 6 —
|
|
3362
|
+
* Level 6 is the CDI's professional standard and what most hubs expect, but
|
|
3363
|
+
* gating strictly on it would wrongly zero out schools whose adviser
|
|
3364
|
+
* qualified by another route. The school asserts this; we record it.
|
|
3365
|
+
*/
|
|
3366
|
+
adviserQualified: boolean;
|
|
3367
|
+
/**
|
|
3368
|
+
* The pupil's year group when the interaction happened. Snapshotted because
|
|
3369
|
+
* BM8 is a cumulative "by age 16 / by age 18" duty and pupils roll up a year
|
|
3370
|
+
* group every August — without this, a Year 9 meeting becomes unattributable
|
|
3371
|
+
* once the pupil reaches Year 11.
|
|
3372
|
+
*/
|
|
3373
|
+
yearGroupAtEntry?: number;
|
|
3374
|
+
/** Academic year label, e.g. "2025-26". See academicYearForDate. */
|
|
3375
|
+
academicYear: string;
|
|
3376
|
+
mode: "inPerson" | "video" | "phone";
|
|
3377
|
+
durationMins?: number;
|
|
3378
|
+
/** Shared with the pupil. BM3 expects pupils to be able to access their record. */
|
|
3379
|
+
summary: string;
|
|
3380
|
+
/** Never shown to the pupil. Kept separate so the summary can be surfaced safely. */
|
|
3381
|
+
staffNotes?: string;
|
|
3382
|
+
/** "Any subsequent agreed decisions" — named in the benchmark, so evidence in its own right. */
|
|
3383
|
+
actions?: CareersRecordAction[];
|
|
3384
|
+
createdBy: string;
|
|
3385
|
+
dateCreated: string;
|
|
3386
|
+
dateUpdated?: string;
|
|
3387
|
+
};
|
|
3326
3388
|
export type SuggestedFixRepo = "placementt-web" | "placementt-backend" | "placementt-core";
|
|
3327
3389
|
export type SuggestedFix = {
|
|
3328
3390
|
isCodeChange: boolean;
|
package/package.json
CHANGED
|
@@ -2,31 +2,46 @@ import {
|
|
|
2
2
|
academicYearDateRange,
|
|
3
3
|
academicYearForDate,
|
|
4
4
|
activityEvidenceBenchmarks,
|
|
5
|
+
ageCheckpointForYearGroup,
|
|
5
6
|
BENCHMARK_COVERAGE_NUMBERS,
|
|
6
7
|
BENCHMARK_META,
|
|
8
|
+
careersRecordEvidenceBenchmarks,
|
|
7
9
|
coverageBand,
|
|
8
10
|
dateInAcademicYear,
|
|
9
11
|
eventEvidenceBenchmarks,
|
|
10
12
|
eventProvidesBenchmark,
|
|
11
13
|
isPalCompliantProviderEncounter,
|
|
14
|
+
isPersonalGuidanceEntry,
|
|
12
15
|
palPhaseForYearGroup,
|
|
16
|
+
yearGroupAtDate,
|
|
13
17
|
} from "./benchmarkEvidenceRules";
|
|
14
18
|
import {COMPASS_PAL_BENCHMARKS, COMPASS_VALID_ACTIVITY_BENCHMARKS} from "./compassExport";
|
|
15
19
|
|
|
16
20
|
describe("benchmarkEvidenceRules single-source consistency", () => {
|
|
17
21
|
it("stays consistent with the Compass export's valid activity benchmarks (2-8)", () => {
|
|
18
|
-
// Coverage numbers are the export-valid set
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
// Coverage numbers are exactly the export-valid set. BM8 joined it when personal
|
|
23
|
+
// guidance became recordable on the careers record; BM1 was never in it.
|
|
24
|
+
expect(BENCHMARK_COVERAGE_NUMBERS).toEqual([...COMPASS_VALID_ACTIVITY_BENCHMARKS]);
|
|
21
25
|
expect(BENCHMARK_COVERAGE_NUMBERS).not.toContain(1);
|
|
22
|
-
expect(BENCHMARK_COVERAGE_NUMBERS).
|
|
26
|
+
expect(BENCHMARK_COVERAGE_NUMBERS).toContain(8);
|
|
23
27
|
});
|
|
24
28
|
|
|
25
|
-
it("marks BM1 as provision-level and BM8 as
|
|
29
|
+
it("marks BM1 as the only provision-level benchmark, and BM8 as guidance-sourced", () => {
|
|
26
30
|
expect(BENCHMARK_META[1].countsTowardCoverage).toBe(false);
|
|
27
31
|
expect(BENCHMARK_META[1].source).toBe("provision");
|
|
28
|
-
expect(BENCHMARK_META[8].countsTowardCoverage).toBe(
|
|
29
|
-
expect(BENCHMARK_META[8].source).toBe("
|
|
32
|
+
expect(BENCHMARK_META[8].countsTowardCoverage).toBe(true);
|
|
33
|
+
expect(BENCHMARK_META[8].source).toBe("guidance");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("periodises each benchmark the way its own wording does", () => {
|
|
37
|
+
// BM5 is the only benchmark that genuinely renews annually ("every year, from
|
|
38
|
+
// the age of 11"). BM6/7/8 are cumulative duties pinned to 16 and 18, and
|
|
39
|
+
// measuring them annually understates coverage — see BenchmarkTimeModel.
|
|
40
|
+
expect(BENCHMARK_META[5].timeModel).toBe("annual");
|
|
41
|
+
expect(BENCHMARK_META[6].timeModel).toBe("cumulativeByAge");
|
|
42
|
+
expect(BENCHMARK_META[7].timeModel).toBe("cumulativeByAge");
|
|
43
|
+
expect(BENCHMARK_META[8].timeModel).toBe("cumulativeByAge");
|
|
44
|
+
expect(BENCHMARK_META[1].timeModel).toBe("provision");
|
|
30
45
|
});
|
|
31
46
|
|
|
32
47
|
it("flags PAL relevance for exactly benchmarks 5-7", () => {
|
|
@@ -96,3 +111,67 @@ describe("coverage bands are neutral (no pass/fail theatre)", () => {
|
|
|
96
111
|
expect(coverageBand(1)).toBe("full");
|
|
97
112
|
});
|
|
98
113
|
});
|
|
114
|
+
|
|
115
|
+
describe("careers record entries as benchmark evidence", () => {
|
|
116
|
+
it("counts a qualified adviser's personal guidance towards BM8", () => {
|
|
117
|
+
const entry = {entryType: "personalGuidance", adviserQualified: true} as const;
|
|
118
|
+
expect(isPersonalGuidanceEntry(entry)).toBe(true);
|
|
119
|
+
expect(careersRecordEvidenceBenchmarks(entry)).toEqual([3, 8]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("does NOT count personal guidance from an unqualified adviser towards BM8", () => {
|
|
123
|
+
// The benchmark is satisfied by a meeting with a careers adviser trained to an
|
|
124
|
+
// appropriate level. Counting an untrained member of staff would fabricate
|
|
125
|
+
// compliance with a statutory expectation.
|
|
126
|
+
const entry = {entryType: "personalGuidance", adviserQualified: false} as const;
|
|
127
|
+
expect(isPersonalGuidanceEntry(entry)).toBe(false);
|
|
128
|
+
expect(careersRecordEvidenceBenchmarks(entry)).toEqual([3]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("does NOT let other entry types inflate BM8, however many are logged", () => {
|
|
132
|
+
const others = [
|
|
133
|
+
"careersConversation", "optionsMeeting", "applicationSupport",
|
|
134
|
+
"sendReview", "parentMeeting", "groupGuidance", "other",
|
|
135
|
+
] as const;
|
|
136
|
+
others.forEach((entryType) => {
|
|
137
|
+
const entry = {entryType, adviserQualified: true} as const;
|
|
138
|
+
expect(isPersonalGuidanceEntry(entry)).toBe(false);
|
|
139
|
+
expect(careersRecordEvidenceBenchmarks(entry)).toEqual([3]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("still treats every entry as BM3 record-keeping evidence", () => {
|
|
144
|
+
expect(careersRecordEvidenceBenchmarks(
|
|
145
|
+
{entryType: "careersConversation", adviserQualified: false})).toContain(3);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("cumulative age-banded coverage", () => {
|
|
150
|
+
it("maps year groups onto the checkpoint they are working towards", () => {
|
|
151
|
+
expect(ageCheckpointForYearGroup(9)?.age).toBe(16);
|
|
152
|
+
expect(ageCheckpointForYearGroup(11)?.age).toBe(16);
|
|
153
|
+
expect(ageCheckpointForYearGroup(12)?.age).toBe(18);
|
|
154
|
+
expect(ageCheckpointForYearGroup(13)?.age).toBe(18);
|
|
155
|
+
expect(ageCheckpointForYearGroup(6)).toBeUndefined();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("back-derives the year group a pupil was in when an older record was made", () => {
|
|
159
|
+
// A Year 11 pupil "now" (2025-26) did work experience in 2024-25, so they were
|
|
160
|
+
// in Year 10 at the time. Without this, BM6 reads them as uncovered.
|
|
161
|
+
expect(yearGroupAtDate(11, "2025-03-01", "2026-03-01")).toBe(10);
|
|
162
|
+
expect(yearGroupAtDate(11, "2025-11-01", "2026-03-01")).toBe(11);
|
|
163
|
+
expect(yearGroupAtDate(13, "2023-10-01", "2026-03-01")).toBe(11);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("rolls over at the start of the academic year, not the calendar year", () => {
|
|
167
|
+
// August starts the new academic year, so a record dated 2025-09-01 and one
|
|
168
|
+
// dated 2026-01-01 sit in the same year group.
|
|
169
|
+
expect(yearGroupAtDate(11, "2025-09-01", "2026-01-01")).toBe(11);
|
|
170
|
+
expect(yearGroupAtDate(11, "2025-07-01", "2026-01-01")).toBe(10);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("returns undefined rather than guessing when a date predates schooling", () => {
|
|
174
|
+
expect(yearGroupAtDate(11, undefined)).toBeUndefined();
|
|
175
|
+
expect(yearGroupAtDate(7, "2005-01-01", "2026-03-01")).toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {ExternalEvent, StudentActivity} from "./typeDefinitions";
|
|
1
|
+
import {CareersRecordEntry, ExternalEvent, StudentActivity} from "./typeDefinitions";
|
|
2
2
|
import {COMPASS_PAL_BENCHMARKS, COMPASS_VALID_ACTIVITY_BENCHMARKS} from "./compassExport";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -30,7 +30,30 @@ export type BenchmarkEvidenceSource =
|
|
|
30
30
|
| "events" // tagged events only.
|
|
31
31
|
| "eventsAndActivities" // tagged events + StudentActivity encounter records.
|
|
32
32
|
| "eventsAndPlacements" // tagged events + work-experience placements.
|
|
33
|
-
| "
|
|
33
|
+
| "guidance"; // BM8 — personal guidance entries on the pupil's careers record.
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* ─── Time model ───────────────────────────────────────────────────────────────
|
|
37
|
+
* The period a benchmark's coverage is measured over. This is NOT cosmetic: the
|
|
38
|
+
* benchmarks genuinely differ, and measuring them all annually (as this module
|
|
39
|
+
* originally did) understates coverage badly.
|
|
40
|
+
*
|
|
41
|
+
* BM5 renews every year → "Every year, from the age of 11, at least one
|
|
42
|
+
* meaningful encounter with an employer."
|
|
43
|
+
* BM2 runs per Key Stage → "During each Key Stage, all pupils should access
|
|
44
|
+
* and use information about careers…"
|
|
45
|
+
* BM6/7/8 are cumulative → "By the age of 16… and a further one by 18."
|
|
46
|
+
*
|
|
47
|
+
* The cumulative case is the one that bites. Measured annually, a Year 11 who did
|
|
48
|
+
* their work experience in Year 10 reads as uncovered, and every pupil in Years 7-9
|
|
49
|
+
* sits in the denominator of a duty that does not apply to them until 16 — which is
|
|
50
|
+
* why `biggestGap` currently keeps nominating Year 7 as a school's worst gap on BM6.
|
|
51
|
+
*/
|
|
52
|
+
export type BenchmarkTimeModel =
|
|
53
|
+
| "annual" // renews each academic year (BM5)
|
|
54
|
+
| "keyStage" // once per Key Stage (BM2)
|
|
55
|
+
| "cumulativeByAge" // ever, judged at the age-16 and age-18 checkpoints (BM6, BM7, BM8)
|
|
56
|
+
| "provision"; // no per-pupil period (BM1, BM4)
|
|
34
57
|
|
|
35
58
|
export type BenchmarkMeta = {
|
|
36
59
|
number: GatsbyBenchmarkNumber,
|
|
@@ -41,23 +64,28 @@ export type BenchmarkMeta = {
|
|
|
41
64
|
source: BenchmarkEvidenceSource,
|
|
42
65
|
/**
|
|
43
66
|
* Whether this benchmark yields a per-student coverage number. False for BM1
|
|
44
|
-
* (provision-level, shown as an informational card)
|
|
45
|
-
*
|
|
46
|
-
* benchmark where this is false.
|
|
67
|
+
* (provision-level, shown as an informational card). The dashboard must not
|
|
68
|
+
* render a coverage % for a benchmark where this is false.
|
|
47
69
|
*/
|
|
48
70
|
countsTowardCoverage: boolean,
|
|
49
71
|
/** True for 5-7: the PAL Compliance flag is meaningful (mirrors COMPASS_PAL_BENCHMARKS). */
|
|
50
72
|
palRelevant: boolean,
|
|
73
|
+
/** How coverage is periodised. Drives both the denominator and the card's label. */
|
|
74
|
+
timeModel: BenchmarkTimeModel,
|
|
51
75
|
};
|
|
52
76
|
|
|
53
77
|
/**
|
|
54
|
-
* Benchmarks that produce a coverage percentage on the matrix / cards.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
78
|
+
* Benchmarks that produce a coverage percentage on the matrix / cards. BM1 is the
|
|
79
|
+
* only exclusion: it is whole-programme provision and can never be a per-pupil
|
|
80
|
+
* number. Derived from the Compass export's valid set (2-8) so the two files can't
|
|
81
|
+
* drift.
|
|
82
|
+
*
|
|
83
|
+
* BM8 was previously excluded here because personal guidance was untracked. It is
|
|
84
|
+
* now recorded on the careers record, so it earns a coverage number like the rest —
|
|
85
|
+
* but a cumulative, age-banded one. See BENCHMARK_AGE_CHECKPOINTS.
|
|
58
86
|
*/
|
|
59
87
|
export const BENCHMARK_COVERAGE_NUMBERS: GatsbyBenchmarkNumber[] =
|
|
60
|
-
COMPASS_VALID_ACTIVITY_BENCHMARKS
|
|
88
|
+
[...COMPASS_VALID_ACTIVITY_BENCHMARKS] as GatsbyBenchmarkNumber[];
|
|
61
89
|
|
|
62
90
|
export const BENCHMARK_META: Record<GatsbyBenchmarkNumber, BenchmarkMeta> = {
|
|
63
91
|
1: {
|
|
@@ -67,6 +95,7 @@ export const BENCHMARK_META: Record<GatsbyBenchmarkNumber, BenchmarkMeta> = {
|
|
|
67
95
|
source: "provision",
|
|
68
96
|
countsTowardCoverage: false,
|
|
69
97
|
palRelevant: false,
|
|
98
|
+
timeModel: "provision",
|
|
70
99
|
},
|
|
71
100
|
2: {
|
|
72
101
|
number: 2,
|
|
@@ -75,6 +104,9 @@ export const BENCHMARK_META: Record<GatsbyBenchmarkNumber, BenchmarkMeta> = {
|
|
|
75
104
|
source: "events",
|
|
76
105
|
countsTowardCoverage: true,
|
|
77
106
|
palRelevant: false,
|
|
107
|
+
// The benchmark's unit is the Key Stage, not the year. Still measured annually
|
|
108
|
+
// until the Key Stage denominator lands — see BENCHMARK_AGE_CHECKPOINTS.
|
|
109
|
+
timeModel: "annual",
|
|
78
110
|
},
|
|
79
111
|
3: {
|
|
80
112
|
number: 3,
|
|
@@ -83,6 +115,7 @@ export const BENCHMARK_META: Record<GatsbyBenchmarkNumber, BenchmarkMeta> = {
|
|
|
83
115
|
source: "destinations",
|
|
84
116
|
countsTowardCoverage: true,
|
|
85
117
|
palRelevant: false,
|
|
118
|
+
timeModel: "annual",
|
|
86
119
|
},
|
|
87
120
|
4: {
|
|
88
121
|
number: 4,
|
|
@@ -91,6 +124,7 @@ export const BENCHMARK_META: Record<GatsbyBenchmarkNumber, BenchmarkMeta> = {
|
|
|
91
124
|
source: "events",
|
|
92
125
|
countsTowardCoverage: true,
|
|
93
126
|
palRelevant: false,
|
|
127
|
+
timeModel: "annual",
|
|
94
128
|
},
|
|
95
129
|
5: {
|
|
96
130
|
number: 5,
|
|
@@ -99,6 +133,9 @@ export const BENCHMARK_META: Record<GatsbyBenchmarkNumber, BenchmarkMeta> = {
|
|
|
99
133
|
source: "eventsAndActivities",
|
|
100
134
|
countsTowardCoverage: true,
|
|
101
135
|
palRelevant: true,
|
|
136
|
+
// The one benchmark whose annual model matches its own wording: "every year,
|
|
137
|
+
// from the age of 11, at least one meaningful encounter with an employer".
|
|
138
|
+
timeModel: "annual",
|
|
102
139
|
},
|
|
103
140
|
6: {
|
|
104
141
|
number: 6,
|
|
@@ -107,6 +144,7 @@ export const BENCHMARK_META: Record<GatsbyBenchmarkNumber, BenchmarkMeta> = {
|
|
|
107
144
|
source: "eventsAndPlacements",
|
|
108
145
|
countsTowardCoverage: true,
|
|
109
146
|
palRelevant: true,
|
|
147
|
+
timeModel: "cumulativeByAge",
|
|
110
148
|
},
|
|
111
149
|
7: {
|
|
112
150
|
number: 7,
|
|
@@ -115,17 +153,73 @@ export const BENCHMARK_META: Record<GatsbyBenchmarkNumber, BenchmarkMeta> = {
|
|
|
115
153
|
source: "events",
|
|
116
154
|
countsTowardCoverage: true,
|
|
117
155
|
palRelevant: true,
|
|
156
|
+
timeModel: "cumulativeByAge",
|
|
118
157
|
},
|
|
119
158
|
8: {
|
|
120
159
|
number: 8,
|
|
121
160
|
name: "Personal guidance",
|
|
122
|
-
description: "Every pupil has personal guidance
|
|
123
|
-
source: "
|
|
124
|
-
countsTowardCoverage:
|
|
161
|
+
description: "Every pupil has a personal guidance meeting with a careers adviser by 16, and a further one by 18.",
|
|
162
|
+
source: "guidance",
|
|
163
|
+
countsTowardCoverage: true,
|
|
125
164
|
palRelevant: false,
|
|
165
|
+
timeModel: "cumulativeByAge",
|
|
126
166
|
},
|
|
127
167
|
};
|
|
128
168
|
|
|
169
|
+
/**
|
|
170
|
+
* ─── Age checkpoints for cumulative benchmarks ────────────────────────────────
|
|
171
|
+
* BM6, BM7 and BM8 are all phrased as "by the age of 16… and by 18". Coverage for
|
|
172
|
+
* these is judged at the two points where the duty actually falls due, not spread
|
|
173
|
+
* across every year group: asking what proportion of Year 7 has had a personal
|
|
174
|
+
* guidance meeting measures nothing, because none of them are due one yet.
|
|
175
|
+
*
|
|
176
|
+
* The headline figure for each checkpoint is therefore the *due* cohort — the year
|
|
177
|
+
* group at which the duty has just become expected. Earlier year groups still get a
|
|
178
|
+
* progression figure (are we on track?), but they must not drag the headline down.
|
|
179
|
+
*/
|
|
180
|
+
export type BenchmarkAgeCheckpoint = {
|
|
181
|
+
/** The age the benchmark names. */
|
|
182
|
+
age: 16|18,
|
|
183
|
+
/** The year group at which the duty has fallen due in full. */
|
|
184
|
+
dueYearGroup: number,
|
|
185
|
+
/** Year groups the duty is accumulating across, up to and including the due one. */
|
|
186
|
+
accumulatingYearGroups: number[],
|
|
187
|
+
label: string,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const BENCHMARK_AGE_CHECKPOINTS: BenchmarkAgeCheckpoint[] = [
|
|
191
|
+
{age: 16, dueYearGroup: 11, accumulatingYearGroups: [7, 8, 9, 10, 11], label: "By age 16"},
|
|
192
|
+
{age: 18, dueYearGroup: 13, accumulatingYearGroups: [12, 13], label: "By age 18"},
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
/** The checkpoint a year group is working towards, or undefined if off-scale. */
|
|
196
|
+
export function ageCheckpointForYearGroup(yearGroup: number): BenchmarkAgeCheckpoint|undefined {
|
|
197
|
+
return BENCHMARK_AGE_CHECKPOINTS.find((c) => c.accumulatingYearGroups.includes(yearGroup));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The pupil's year group at some earlier date, back-derived from their current year
|
|
202
|
+
* group. Records written before `yearGroupAtEntry` existed (and every placement and
|
|
203
|
+
* event, which never carried one) have no snapshot, so cumulative coverage has to
|
|
204
|
+
* reconstruct it: each academic year that has elapsed since the record is one year
|
|
205
|
+
* group to subtract.
|
|
206
|
+
*
|
|
207
|
+
* Returns undefined when the result falls outside schooling, which is the honest
|
|
208
|
+
* answer for a record whose date predates the pupil joining.
|
|
209
|
+
*/
|
|
210
|
+
export function yearGroupAtDate(
|
|
211
|
+
currentYearGroup: number,
|
|
212
|
+
recordDate: string|undefined,
|
|
213
|
+
now: Date|string = new Date(),
|
|
214
|
+
): number|undefined {
|
|
215
|
+
if (!recordDate) return undefined;
|
|
216
|
+
const recordYear = parseInt(academicYearForDate(recordDate).split("-")[0], 10);
|
|
217
|
+
const currentYear = parseInt(academicYearForDate(now).split("-")[0], 10);
|
|
218
|
+
if (isNaN(recordYear) || isNaN(currentYear)) return undefined;
|
|
219
|
+
const derived = currentYearGroup - (currentYear - recordYear);
|
|
220
|
+
return derived >= 1 && derived <= 15 ? derived : undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
129
223
|
/**
|
|
130
224
|
* StudentActivity.activityType → the benchmark(s) that activity is evidence for.
|
|
131
225
|
* Employer-facing encounters count towards BM5; first-hand workplace experiences
|
|
@@ -204,6 +298,33 @@ export function activityEvidenceBenchmarks(activity: Pick<StudentActivity, "acti
|
|
|
204
298
|
return ACTIVITY_TYPE_BENCHMARKS[activity.activityType] || [];
|
|
205
299
|
}
|
|
206
300
|
|
|
301
|
+
/**
|
|
302
|
+
* ─── Careers record entries as evidence ───────────────────────────────────────
|
|
303
|
+
* Every entry is BM3 evidence: the updated Benchmark 3 asks schools to record the
|
|
304
|
+
* individual advice given to each pupil and any decisions subsequently agreed, and
|
|
305
|
+
* makes no distinction between a formal guidance interview and an option-choice
|
|
306
|
+
* conversation for that purpose.
|
|
307
|
+
*
|
|
308
|
+
* BM8 is far narrower. It is satisfied only by a personal guidance meeting with a
|
|
309
|
+
* careers adviser trained to an appropriate level. A school that logs fifty tutor-time
|
|
310
|
+
* careers chats has kept excellent records and has NOT delivered personal guidance, so
|
|
311
|
+
* only `personalGuidance` entries with `adviserQualified` count here. Loosening this
|
|
312
|
+
* would manufacture compliance with a statutory expectation, which is the one thing
|
|
313
|
+
* this module exists to prevent.
|
|
314
|
+
*/
|
|
315
|
+
export function isPersonalGuidanceEntry(
|
|
316
|
+
entry: Pick<CareersRecordEntry, "entryType"|"adviserQualified">,
|
|
317
|
+
): boolean {
|
|
318
|
+
return entry.entryType === "personalGuidance" && Boolean(entry.adviserQualified);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** The benchmarks a careers record entry is evidence for. */
|
|
322
|
+
export function careersRecordEvidenceBenchmarks(
|
|
323
|
+
entry: Pick<CareersRecordEntry, "entryType"|"adviserQualified">,
|
|
324
|
+
): GatsbyBenchmarkNumber[] {
|
|
325
|
+
return isPersonalGuidanceEntry(entry) ? [3, 8] : [3];
|
|
326
|
+
}
|
|
327
|
+
|
|
207
328
|
/**
|
|
208
329
|
* A PAL-compliant provider encounter: the event carries the existing PAL flag AND
|
|
209
330
|
* is tagged with a PAL-relevant benchmark (5-7). Same rule the export uses when
|
package/src/productResolver.ts
CHANGED
|
@@ -23,6 +23,11 @@ export type ProductKey =
|
|
|
23
23
|
"wex"|"network"|"parents"|"events"|"skills"|"activities"|"destinations"|"alumni";
|
|
24
24
|
|
|
25
25
|
export type ResolvedProducts = Record<ProductKey, boolean> & {
|
|
26
|
+
/** How many parents are actually in the database, across `options.oIds`.
|
|
27
|
+
* `parents` is just this over zero; the number is surfaced so a caller that
|
|
28
|
+
* wants to show the count doesn't run the same aggregate a second time.
|
|
29
|
+
* Zero until the count lands, the same as the flag. */
|
|
30
|
+
parentCount: number,
|
|
26
31
|
/** Package shape, so callers stop testing `package ===` themselves. */
|
|
27
32
|
isTrust: boolean,
|
|
28
33
|
isHub: boolean,
|
|
@@ -145,6 +150,7 @@ export function useProductResolver(
|
|
|
145
150
|
|
|
146
151
|
return {
|
|
147
152
|
...products,
|
|
153
|
+
parentCount,
|
|
148
154
|
isTrust,
|
|
149
155
|
isHub,
|
|
150
156
|
isHubSchool,
|
package/src/typeDefinitions.ts
CHANGED
|
@@ -3380,6 +3380,89 @@ export type StudentActivity = {
|
|
|
3380
3380
|
evidenceFileIds?: string[];
|
|
3381
3381
|
};
|
|
3382
3382
|
|
|
3383
|
+
/**
|
|
3384
|
+
* ─── Careers record ───────────────────────────────────────────────────────────
|
|
3385
|
+
* A staff-authored record of a careers interaction with one pupil. Deliberately
|
|
3386
|
+
* modelled as the pupil's *careers record* rather than a meetings log, because
|
|
3387
|
+
* Gatsby Benchmark 3 asks schools to keep systematic records of "the individual
|
|
3388
|
+
* advice given to each pupil, and any subsequent agreed decisions" — not only of
|
|
3389
|
+
* formal guidance interviews.
|
|
3390
|
+
*
|
|
3391
|
+
* Only `personalGuidance` entries delivered by a qualified adviser are evidence
|
|
3392
|
+
* for Benchmark 8. Every other type is BM3 record-keeping and must NOT move the
|
|
3393
|
+
* BM8 number: a school that logs fifty option-choice chats has not delivered
|
|
3394
|
+
* personal guidance, and showing otherwise would fabricate compliance.
|
|
3395
|
+
*/
|
|
3396
|
+
export type CareersRecordEntryType =
|
|
3397
|
+
| "personalGuidance" // 1:1 with a careers adviser — the only BM8-valid type
|
|
3398
|
+
| "careersConversation" // tutor-time or drop-in conversation
|
|
3399
|
+
| "optionsMeeting" // GCSE/A-level option choices
|
|
3400
|
+
| "applicationSupport" // UCAS, apprenticeship or college applications
|
|
3401
|
+
| "sendReview" // careers input to an EHCP annual review (Y9+)
|
|
3402
|
+
| "parentMeeting" // careers conversation with a parent or carer
|
|
3403
|
+
| "groupGuidance" // small-group guidance session
|
|
3404
|
+
| "other";
|
|
3405
|
+
|
|
3406
|
+
/** A single agreed next step arising from a careers record entry. */
|
|
3407
|
+
export type CareersRecordAction = {
|
|
3408
|
+
label: string,
|
|
3409
|
+
owner?: "student"|"staff",
|
|
3410
|
+
dueDate?: string, // ISO date
|
|
3411
|
+
done?: boolean,
|
|
3412
|
+
};
|
|
3413
|
+
|
|
3414
|
+
export type CareersRecordEntry = {
|
|
3415
|
+
id: string,
|
|
3416
|
+
uid: string, // pupil
|
|
3417
|
+
oId: string,
|
|
3418
|
+
|
|
3419
|
+
entryType: CareersRecordEntryType,
|
|
3420
|
+
date: string, // ISO date the interaction happened
|
|
3421
|
+
|
|
3422
|
+
/** Staff member who delivered it. Advisers are always platform users. */
|
|
3423
|
+
adviserUid: string,
|
|
3424
|
+
|
|
3425
|
+
/**
|
|
3426
|
+
* Whether the adviser was, at the time of this entry, trained to a level the
|
|
3427
|
+
* school considers appropriate for personal guidance. Snapshotted rather than
|
|
3428
|
+
* read live from the staff record so that later changes to a staff member's
|
|
3429
|
+
* qualification cannot silently restate a previous year's BM8 coverage.
|
|
3430
|
+
*
|
|
3431
|
+
* Note the framework says "trained to an appropriate level", not Level 6 —
|
|
3432
|
+
* Level 6 is the CDI's professional standard and what most hubs expect, but
|
|
3433
|
+
* gating strictly on it would wrongly zero out schools whose adviser
|
|
3434
|
+
* qualified by another route. The school asserts this; we record it.
|
|
3435
|
+
*/
|
|
3436
|
+
adviserQualified: boolean,
|
|
3437
|
+
|
|
3438
|
+
/**
|
|
3439
|
+
* The pupil's year group when the interaction happened. Snapshotted because
|
|
3440
|
+
* BM8 is a cumulative "by age 16 / by age 18" duty and pupils roll up a year
|
|
3441
|
+
* group every August — without this, a Year 9 meeting becomes unattributable
|
|
3442
|
+
* once the pupil reaches Year 11.
|
|
3443
|
+
*/
|
|
3444
|
+
yearGroupAtEntry?: number,
|
|
3445
|
+
|
|
3446
|
+
/** Academic year label, e.g. "2025-26". See academicYearForDate. */
|
|
3447
|
+
academicYear: string,
|
|
3448
|
+
|
|
3449
|
+
mode: "inPerson"|"video"|"phone",
|
|
3450
|
+
durationMins?: number,
|
|
3451
|
+
|
|
3452
|
+
/** Shared with the pupil. BM3 expects pupils to be able to access their record. */
|
|
3453
|
+
summary: string,
|
|
3454
|
+
|
|
3455
|
+
/** Never shown to the pupil. Kept separate so the summary can be surfaced safely. */
|
|
3456
|
+
staffNotes?: string,
|
|
3457
|
+
|
|
3458
|
+
/** "Any subsequent agreed decisions" — named in the benchmark, so evidence in its own right. */
|
|
3459
|
+
actions?: CareersRecordAction[],
|
|
3460
|
+
|
|
3461
|
+
createdBy: string, // uid of the staff member who wrote the record
|
|
3462
|
+
dateCreated: string, // ISO timestamp
|
|
3463
|
+
dateUpdated?: string,
|
|
3464
|
+
};
|
|
3465
|
+
|
|
3383
3466
|
export type SuggestedFixRepo = "placementt-web"|"placementt-backend"|"placementt-core";
|
|
3384
3467
|
|
|
3385
3468
|
export type SuggestedFix = {
|