placementt-core 1.400.1039 → 1.400.1040

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.
@@ -45,6 +45,34 @@ export declare const destinationsConfigCallables: {
45
45
  created?: number | undefined;
46
46
  }>;
47
47
  };
48
+ "destinationsConfig-saveLeaverDestinations": {
49
+ request: z.ZodObject<{
50
+ oId: z.ZodString;
51
+ /** The school's own leaver destination categories, keyed by id. The nine
52
+ * NCCIS codes are fixed and are never sent — only what sits under them.
53
+ * Validated server side, which also stamps provenance and turns a
54
+ * removal into an archive. */
55
+ destinations: z.ZodRecord<z.ZodString, z.ZodUnknown>;
56
+ }, "strip", z.ZodTypeAny, {
57
+ oId: string;
58
+ destinations: Record<string, unknown>;
59
+ }, {
60
+ oId: string;
61
+ destinations: Record<string, unknown>;
62
+ }>;
63
+ response: z.ZodObject<{
64
+ saved: z.ZodBoolean;
65
+ /** The stored map, including anything the server archived rather than
66
+ * deleted — the client adopts this rather than its own copy. */
67
+ destinations: z.ZodRecord<z.ZodString, z.ZodUnknown>;
68
+ }, "strip", z.ZodTypeAny, {
69
+ destinations: Record<string, unknown>;
70
+ saved: boolean;
71
+ }, {
72
+ destinations: Record<string, unknown>;
73
+ saved: boolean;
74
+ }>;
75
+ };
48
76
  "destinationsConfig-provisionDestinationsNow": {
49
77
  request: z.ZodObject<{
50
78
  oId: z.ZodString;
@@ -32,6 +32,22 @@ exports.destinationsConfigCallables = {
32
32
  created: zod_1.z.number().optional(),
33
33
  }),
34
34
  },
35
+ "destinationsConfig-saveLeaverDestinations": {
36
+ request: zod_1.z.object({
37
+ oId: zod_1.z.string(),
38
+ /** The school's own leaver destination categories, keyed by id. The nine
39
+ * NCCIS codes are fixed and are never sent — only what sits under them.
40
+ * Validated server side, which also stamps provenance and turns a
41
+ * removal into an archive. */
42
+ destinations: zod_1.z.record(zod_1.z.unknown()),
43
+ }),
44
+ response: zod_1.z.object({
45
+ saved: zod_1.z.boolean(),
46
+ /** The stored map, including anything the server archived rather than
47
+ * deleted — the client adopts this rather than its own copy. */
48
+ destinations: zod_1.z.record(zod_1.z.unknown()),
49
+ }),
50
+ },
35
51
  "destinationsConfig-provisionDestinationsNow": {
36
52
  request: zod_1.z.object({
37
53
  oId: zod_1.z.string(),
@@ -1 +1 @@
1
- {"version":3,"file":"destinationsConfig.js","sourceRoot":"","sources":["../../src/callables/destinationsConfig.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;;;;;;;;GAUG;AACU,QAAA,2BAA2B,GAAG;IACvC,2CAA2C,EAAE;QACzC,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf;0EAC8D;YAC9D,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,OAAO,EAAE,CAAC;YAC7B;;gFAEoE;YACpE,aAAa,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;SAClD,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,OAAC,CAAC,OAAO,EAAE;YAClB,2EAA2E;YAC3E,WAAW,EAAE,OAAC,CAAC,OAAO,EAAE;YACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SACjC,CAAC;KACL;IACD,6CAA6C,EAAE;QAC3C,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf,uEAAuE;YACvE,QAAQ,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;YAChC,8EAA8E;YAC9E,KAAK,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE;SACvD,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,WAAW,EAAE,OAAC,CAAC,OAAO,EAAE;YACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;YACnB,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;SAC3B,CAAC;KACL;CACoB,CAAC"}
1
+ {"version":3,"file":"destinationsConfig.js","sourceRoot":"","sources":["../../src/callables/destinationsConfig.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;;;;;;;;GAUG;AACU,QAAA,2BAA2B,GAAG;IACvC,2CAA2C,EAAE;QACzC,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf;0EAC8D;YAC9D,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,OAAO,EAAE,CAAC;YAC7B;;gFAEoE;YACpE,aAAa,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;SAClD,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,OAAC,CAAC,OAAO,EAAE;YAClB,2EAA2E;YAC3E,WAAW,EAAE,OAAC,CAAC,OAAO,EAAE;YACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SACjC,CAAC;KACL;IACD,2CAA2C,EAAE;QACzC,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf;;;0CAG8B;YAC9B,YAAY,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,OAAO,EAAE,CAAC;SACtC,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,OAAC,CAAC,OAAO,EAAE;YAClB;4EACgE;YAChE,YAAY,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,OAAO,EAAE,CAAC;SACtC,CAAC;KACL;IACD,6CAA6C,EAAE;QAC3C,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC;YACd,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;YACf,uEAAuE;YACvE,QAAQ,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;YAChC,8EAA8E;YAC9E,KAAK,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE;SACvD,CAAC;QACF,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,WAAW,EAAE,OAAC,CAAC,OAAO,EAAE;YACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;YACnB,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;SAC3B,CAAC;KACL;CACoB,CAAC"}
@@ -787,6 +787,28 @@ export declare const allCallables: {
787
787
  created?: number | undefined;
788
788
  }>;
789
789
  };
790
+ "destinationsConfig-saveLeaverDestinations": {
791
+ request: z.ZodObject<{
792
+ oId: z.ZodString;
793
+ destinations: z.ZodRecord<z.ZodString, z.ZodUnknown>;
794
+ }, "strip", z.ZodTypeAny, {
795
+ oId: string;
796
+ destinations: Record<string, unknown>;
797
+ }, {
798
+ oId: string;
799
+ destinations: Record<string, unknown>;
800
+ }>;
801
+ response: z.ZodObject<{
802
+ saved: z.ZodBoolean;
803
+ destinations: z.ZodRecord<z.ZodString, z.ZodUnknown>;
804
+ }, "strip", z.ZodTypeAny, {
805
+ destinations: Record<string, unknown>;
806
+ saved: boolean;
807
+ }, {
808
+ destinations: Record<string, unknown>;
809
+ saved: boolean;
810
+ }>;
811
+ };
790
812
  "destinationsConfig-provisionDestinationsNow": {
791
813
  request: z.ZodObject<{
792
814
  oId: z.ZodString;
@@ -860,6 +882,28 @@ export declare const callableGroups: {
860
882
  created?: number | undefined;
861
883
  }>;
862
884
  };
885
+ "destinationsConfig-saveLeaverDestinations": {
886
+ request: z.ZodObject<{
887
+ oId: z.ZodString;
888
+ destinations: z.ZodRecord<z.ZodString, z.ZodUnknown>;
889
+ }, "strip", z.ZodTypeAny, {
890
+ oId: string;
891
+ destinations: Record<string, unknown>;
892
+ }, {
893
+ oId: string;
894
+ destinations: Record<string, unknown>;
895
+ }>;
896
+ response: z.ZodObject<{
897
+ saved: z.ZodBoolean;
898
+ destinations: z.ZodRecord<z.ZodString, z.ZodUnknown>;
899
+ }, "strip", z.ZodTypeAny, {
900
+ destinations: Record<string, unknown>;
901
+ saved: boolean;
902
+ }, {
903
+ destinations: Record<string, unknown>;
904
+ saved: boolean;
905
+ }>;
906
+ };
863
907
  "destinationsConfig-provisionDestinationsNow": {
864
908
  request: z.ZodObject<{
865
909
  oId: z.ZodString;
@@ -0,0 +1,140 @@
1
+ import { DestinationCode, DestinationValue, InstituteData, LeaverDestination } from "./typeDefinitions";
2
+ /**
3
+ * destinations.ts — the nine NCCIS codes plus whatever a school has added to them.
4
+ *
5
+ * NCCIS gives every leaver one of nine activity codes, and two of them — NEET and
6
+ * NK — cover situations that could hardly be less alike: a leaver in hospital, one
7
+ * in care, one in custody and one who simply hasn't decided are all the same row of
8
+ * the return. Schools with SEND cohorts need that row broken up, so a school can
9
+ * define its own categories (`InstituteData.leaverDestinations`) and every one of
10
+ * them names the NCCIS code it counts within.
11
+ *
12
+ * That mapping is the whole design: a custom category's id is what gets stored and
13
+ * counted, so the school's own charts get the finer breakdown, while anything
14
+ * statutory (Compass+, the LA MI return) resolves it back to one of the nine.
15
+ */
16
+ export type LeaverDestinationMap = {
17
+ [id: string]: LeaverDestination;
18
+ };
19
+ /**
20
+ * One option as the forms and charts see it — standard and custom side by side, so
21
+ * nothing downstream has to know which it is unless it cares.
22
+ */
23
+ export type DestinationOption = {
24
+ /** What gets stored: an NCCIS code, or a custom category's id. */
25
+ code: DestinationValue;
26
+ label: string;
27
+ /** The NCCIS code this counts within — its own code, for the standard nine. */
28
+ nccisCode: DestinationCode;
29
+ custom: boolean;
30
+ description?: string;
31
+ archived?: boolean;
32
+ };
33
+ /** Ids become Firestore field-path segments (`aggregates.destinations.<id>`), so
34
+ * they may not contain a dot, a slash or anything else a path splits on. */
35
+ export declare const DESTINATION_ID_PATTERN: RegExp;
36
+ /** Custom ids carry a prefix so nothing can shadow a standard code, now or when
37
+ * NCCIS adds one. */
38
+ export declare const CUSTOM_DESTINATION_PREFIX = "custom_";
39
+ /** The NCCIS codes that mean "not in education, employment or training, or we don't
40
+ * know" — the pair schools break down, and the pair a positive-destination rate
41
+ * counts against. */
42
+ export declare const NEGATIVE_DESTINATION_CODES: DestinationCode[];
43
+ /**
44
+ * A stable id for a new custom category, derived from its label.
45
+ *
46
+ * @param {string} label the category's display name.
47
+ * @param {object} [existing] categories already defined, so ids stay unique.
48
+ * @return {string} a field-path-safe id.
49
+ */
50
+ export declare const customDestinationId: (label: string, existing?: LeaverDestinationMap) => string;
51
+ /** The nine NCCIS codes as options, in the order the MI return lists them. */
52
+ export declare const standardDestinationOptions: () => DestinationOption[];
53
+ /**
54
+ * Every destination a school offers: the nine standard codes, then its own
55
+ * categories grouped after the NCCIS code each one belongs to.
56
+ *
57
+ * Archived categories are left out unless asked for — they exist so that historical
58
+ * records keep their label, not so that new leavers can pick them.
59
+ *
60
+ * @param {object} [customs] the institute's leaverDestinations map.
61
+ * @param {object} [opts] `includeArchived` to keep withdrawn categories in the list.
62
+ * @return {DestinationOption[]} the ordered option list.
63
+ */
64
+ export declare const destinationOptions: (customs?: LeaverDestinationMap, opts?: {
65
+ includeArchived?: boolean;
66
+ }) => DestinationOption[];
67
+ /**
68
+ * A school's categories, plus the ones its trust supplies.
69
+ *
70
+ * A trust's categories are extra vocabulary, not a mandate: they are added to
71
+ * whatever the school has of its own, and the school can neither edit nor remove
72
+ * them. Nothing is copied onto the school — resolution is a read-time merge, so a
73
+ * school that leaves the trust simply stops seeing them (see ./trustPolicies for why
74
+ * that is how every trust-supplied setting works here).
75
+ *
76
+ * On an id collision the SCHOOL's entry wins. Both keys are the same, so responses
77
+ * and aggregates land in the same place either way; what differs is that the school
78
+ * keeps the category it made, and keeps being able to edit it.
79
+ *
80
+ * @param {object} [school] the school's own institute doc.
81
+ * @param {object} [trust] the trust's institute doc, when the school is in one.
82
+ * @return {object} the merged map, and the ids the trust owns.
83
+ */
84
+ export declare const resolveLeaverDestinations: (school?: Pick<InstituteData, "leaverDestinations">, trust?: Pick<InstituteData, "leaverDestinations">) => {
85
+ destinations: LeaverDestinationMap;
86
+ trustIds: string[];
87
+ };
88
+ /**
89
+ * What to show for a stored destination — a standard label, or a school's own.
90
+ *
91
+ * A category defined by a DIFFERENT school (a student browsing trust-wide alumni
92
+ * meets these) isn't in the map we hold, so its id is turned back into words rather
93
+ * than shown raw: an id is derived from the label it was created with, which makes
94
+ * "custom_in_hospital" recoverable as "In hospital".
95
+ *
96
+ * @param {string} [code] the stored destination.
97
+ * @param {object} [customs] the institute's leaverDestinations map.
98
+ * @return {string|undefined} the display label.
99
+ */
100
+ export declare const destinationLabel: (code?: DestinationValue, customs?: LeaverDestinationMap) => string | undefined;
101
+ /**
102
+ * The NCCIS code a stored destination counts within. Statutory exports and the
103
+ * NEET/NK figures go through this, never through the raw value.
104
+ *
105
+ * @param {string} [code] the stored destination.
106
+ * @param {object} [customs] the institute's leaverDestinations map.
107
+ * @return {DestinationCode|undefined} the NCCIS code to report it as.
108
+ */
109
+ export declare const destinationNccisCode: (code?: DestinationValue, customs?: LeaverDestinationMap) => DestinationCode | undefined;
110
+ /**
111
+ * Whether a destination is one of the two the school is trying to shrink. Custom
112
+ * categories count exactly as the NCCIS code they name does.
113
+ *
114
+ * @param {string} [code] the stored destination.
115
+ * @param {object} [customs] the institute's leaverDestinations map.
116
+ * @return {boolean} true for NEET / NK and anything mapped to them.
117
+ */
118
+ export declare const isNegativeDestination: (code?: DestinationValue, customs?: LeaverDestinationMap) => boolean;
119
+ /**
120
+ * Re-keys a `{destination: count}` aggregate onto the nine NCCIS codes, folding each
121
+ * custom category into its parent. For anything that has to report in NCCIS terms
122
+ * while the raw aggregate stays as granular as the school made it.
123
+ *
124
+ * @param {object} [counts] the raw aggregate.
125
+ * @param {object} [customs] the institute's leaverDestinations map.
126
+ * @return {object} counts keyed by NCCIS code.
127
+ */
128
+ export declare const rollUpDestinationCounts: (counts?: {
129
+ [code: string]: number;
130
+ }, customs?: LeaverDestinationMap) => {
131
+ [code: string]: number;
132
+ };
133
+ /**
134
+ * Validates a set of custom categories before it is stored. Shared by the setup card
135
+ * and the callable so both refuse the same things.
136
+ *
137
+ * @param {object} destinations the map to check.
138
+ * @return {string|undefined} the first problem found, or undefined if it's valid.
139
+ */
140
+ export declare const validateLeaverDestinations: (destinations: LeaverDestinationMap) => string | undefined;
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateLeaverDestinations = exports.rollUpDestinationCounts = exports.isNegativeDestination = exports.destinationNccisCode = exports.destinationLabel = exports.resolveLeaverDestinations = exports.destinationOptions = exports.standardDestinationOptions = exports.customDestinationId = exports.NEGATIVE_DESTINATION_CODES = exports.CUSTOM_DESTINATION_PREFIX = exports.DESTINATION_ID_PATTERN = void 0;
4
+ const constants_1 = require("./constants");
5
+ /** Ids become Firestore field-path segments (`aggregates.destinations.<id>`), so
6
+ * they may not contain a dot, a slash or anything else a path splits on. */
7
+ exports.DESTINATION_ID_PATTERN = /^[A-Za-z0-9_]{1,60}$/;
8
+ /** Custom ids carry a prefix so nothing can shadow a standard code, now or when
9
+ * NCCIS adds one. */
10
+ exports.CUSTOM_DESTINATION_PREFIX = "custom_";
11
+ /** The NCCIS codes that mean "not in education, employment or training, or we don't
12
+ * know" — the pair schools break down, and the pair a positive-destination rate
13
+ * counts against. */
14
+ exports.NEGATIVE_DESTINATION_CODES = ["NEET", "NK"];
15
+ /**
16
+ * A stable id for a new custom category, derived from its label.
17
+ *
18
+ * @param {string} label the category's display name.
19
+ * @param {object} [existing] categories already defined, so ids stay unique.
20
+ * @return {string} a field-path-safe id.
21
+ */
22
+ const customDestinationId = (label, existing) => {
23
+ const slug = (label || "")
24
+ .trim().toLowerCase()
25
+ .replace(/[^a-z0-9]+/g, "_")
26
+ .replace(/^_+|_+$/g, "")
27
+ .slice(0, 40) || "category";
28
+ const base = `${exports.CUSTOM_DESTINATION_PREFIX}${slug}`;
29
+ if (!existing?.[base])
30
+ return base;
31
+ // Two categories can legitimately slug the same ("In care" / "In-care"), so the
32
+ // second one gets a suffix rather than silently overwriting the first.
33
+ let n = 2;
34
+ while (existing[`${base}_${n}`])
35
+ n++;
36
+ return `${base}_${n}`;
37
+ };
38
+ exports.customDestinationId = customDestinationId;
39
+ /** The nine NCCIS codes as options, in the order the MI return lists them. */
40
+ const standardDestinationOptions = () => Object.entries(constants_1.DESTINATION_CODES).map(([code, label]) => ({
41
+ code, label, nccisCode: code, custom: false,
42
+ }));
43
+ exports.standardDestinationOptions = standardDestinationOptions;
44
+ /**
45
+ * Every destination a school offers: the nine standard codes, then its own
46
+ * categories grouped after the NCCIS code each one belongs to.
47
+ *
48
+ * Archived categories are left out unless asked for — they exist so that historical
49
+ * records keep their label, not so that new leavers can pick them.
50
+ *
51
+ * @param {object} [customs] the institute's leaverDestinations map.
52
+ * @param {object} [opts] `includeArchived` to keep withdrawn categories in the list.
53
+ * @return {DestinationOption[]} the ordered option list.
54
+ */
55
+ const destinationOptions = (customs, opts) => {
56
+ const custom = Object.entries(customs ?? {})
57
+ .filter(([, d]) => d && (opts?.includeArchived || !d.archived))
58
+ .map(([id, d]) => ({
59
+ code: id,
60
+ label: d.label,
61
+ nccisCode: (constants_1.DESTINATION_CODES[d.nccisCode] ? d.nccisCode : "NEET"),
62
+ custom: true,
63
+ description: d.description,
64
+ archived: d.archived,
65
+ }))
66
+ .sort((a, b) => a.label.localeCompare(b.label));
67
+ // Grouped under their parent so "In hospital" reads as a kind of NEET rather than
68
+ // as a tenth code of its own.
69
+ return (0, exports.standardDestinationOptions)().flatMap((std) => [
70
+ std, ...custom.filter((c) => c.nccisCode === std.code),
71
+ ]);
72
+ };
73
+ exports.destinationOptions = destinationOptions;
74
+ /**
75
+ * A school's categories, plus the ones its trust supplies.
76
+ *
77
+ * A trust's categories are extra vocabulary, not a mandate: they are added to
78
+ * whatever the school has of its own, and the school can neither edit nor remove
79
+ * them. Nothing is copied onto the school — resolution is a read-time merge, so a
80
+ * school that leaves the trust simply stops seeing them (see ./trustPolicies for why
81
+ * that is how every trust-supplied setting works here).
82
+ *
83
+ * On an id collision the SCHOOL's entry wins. Both keys are the same, so responses
84
+ * and aggregates land in the same place either way; what differs is that the school
85
+ * keeps the category it made, and keeps being able to edit it.
86
+ *
87
+ * @param {object} [school] the school's own institute doc.
88
+ * @param {object} [trust] the trust's institute doc, when the school is in one.
89
+ * @return {object} the merged map, and the ids the trust owns.
90
+ */
91
+ const resolveLeaverDestinations = (school, trust) => {
92
+ const own = school?.leaverDestinations ?? {};
93
+ const fromTrust = trust?.leaverDestinations ?? {};
94
+ const trustIds = Object.keys(fromTrust).filter((id) => !own[id]);
95
+ return {
96
+ destinations: { ...fromTrust, ...own },
97
+ trustIds,
98
+ };
99
+ };
100
+ exports.resolveLeaverDestinations = resolveLeaverDestinations;
101
+ /**
102
+ * What to show for a stored destination — a standard label, or a school's own.
103
+ *
104
+ * A category defined by a DIFFERENT school (a student browsing trust-wide alumni
105
+ * meets these) isn't in the map we hold, so its id is turned back into words rather
106
+ * than shown raw: an id is derived from the label it was created with, which makes
107
+ * "custom_in_hospital" recoverable as "In hospital".
108
+ *
109
+ * @param {string} [code] the stored destination.
110
+ * @param {object} [customs] the institute's leaverDestinations map.
111
+ * @return {string|undefined} the display label.
112
+ */
113
+ const destinationLabel = (code, customs) => {
114
+ if (!code)
115
+ return undefined;
116
+ if (constants_1.DESTINATION_CODES[code])
117
+ return constants_1.DESTINATION_CODES[code];
118
+ const known = customs?.[code]?.label;
119
+ if (known)
120
+ return known;
121
+ if (!code.startsWith(exports.CUSTOM_DESTINATION_PREFIX))
122
+ return code;
123
+ const words = code.slice(exports.CUSTOM_DESTINATION_PREFIX.length).replace(/_/g, " ").trim();
124
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : code;
125
+ };
126
+ exports.destinationLabel = destinationLabel;
127
+ /**
128
+ * The NCCIS code a stored destination counts within. Statutory exports and the
129
+ * NEET/NK figures go through this, never through the raw value.
130
+ *
131
+ * @param {string} [code] the stored destination.
132
+ * @param {object} [customs] the institute's leaverDestinations map.
133
+ * @return {DestinationCode|undefined} the NCCIS code to report it as.
134
+ */
135
+ const destinationNccisCode = (code, customs) => {
136
+ if (!code)
137
+ return undefined;
138
+ if (constants_1.DESTINATION_CODES[code])
139
+ return code;
140
+ const custom = customs?.[code];
141
+ // An id we no longer recognise is still somebody's leaver: "not known" is the
142
+ // honest reading, and it keeps them in the cohort rather than dropping them.
143
+ if (!custom)
144
+ return "NK";
145
+ return constants_1.DESTINATION_CODES[custom.nccisCode] ? custom.nccisCode : "NEET";
146
+ };
147
+ exports.destinationNccisCode = destinationNccisCode;
148
+ /**
149
+ * Whether a destination is one of the two the school is trying to shrink. Custom
150
+ * categories count exactly as the NCCIS code they name does.
151
+ *
152
+ * @param {string} [code] the stored destination.
153
+ * @param {object} [customs] the institute's leaverDestinations map.
154
+ * @return {boolean} true for NEET / NK and anything mapped to them.
155
+ */
156
+ const isNegativeDestination = (code, customs) => {
157
+ const nccis = (0, exports.destinationNccisCode)(code, customs);
158
+ return Boolean(nccis && exports.NEGATIVE_DESTINATION_CODES.includes(nccis));
159
+ };
160
+ exports.isNegativeDestination = isNegativeDestination;
161
+ /**
162
+ * Re-keys a `{destination: count}` aggregate onto the nine NCCIS codes, folding each
163
+ * custom category into its parent. For anything that has to report in NCCIS terms
164
+ * while the raw aggregate stays as granular as the school made it.
165
+ *
166
+ * @param {object} [counts] the raw aggregate.
167
+ * @param {object} [customs] the institute's leaverDestinations map.
168
+ * @return {object} counts keyed by NCCIS code.
169
+ */
170
+ const rollUpDestinationCounts = (counts, customs) => {
171
+ const out = {};
172
+ for (const [code, n] of Object.entries(counts ?? {})) {
173
+ const key = (0, exports.destinationNccisCode)(code, customs) ?? "NK";
174
+ out[key] = (out[key] ?? 0) + (n ?? 0);
175
+ }
176
+ return out;
177
+ };
178
+ exports.rollUpDestinationCounts = rollUpDestinationCounts;
179
+ /**
180
+ * Validates a set of custom categories before it is stored. Shared by the setup card
181
+ * and the callable so both refuse the same things.
182
+ *
183
+ * @param {object} destinations the map to check.
184
+ * @return {string|undefined} the first problem found, or undefined if it's valid.
185
+ */
186
+ const validateLeaverDestinations = (destinations) => {
187
+ const entries = Object.entries(destinations ?? {});
188
+ if (entries.length > 50)
189
+ return "You can add up to 50 destination categories.";
190
+ const seen = new Set();
191
+ for (const [id, d] of entries) {
192
+ if (!d)
193
+ return "One of your categories is empty.";
194
+ if (!exports.DESTINATION_ID_PATTERN.test(id) || id !== d.id)
195
+ return "A category id is invalid.";
196
+ if (!id.startsWith(exports.CUSTOM_DESTINATION_PREFIX))
197
+ return "A category id is invalid.";
198
+ if (constants_1.DESTINATION_CODES[id])
199
+ return "A category can't reuse a standard destination code.";
200
+ const label = (d.label ?? "").trim();
201
+ if (!label)
202
+ return "Give every category a name.";
203
+ if (label.length > 60)
204
+ return "Category names must be 60 characters or fewer.";
205
+ if (!constants_1.DESTINATION_CODES[d.nccisCode])
206
+ return `Choose which group "${label}" counts within.`;
207
+ const key = label.toLowerCase();
208
+ if (seen.has(key))
209
+ return `You already have a category called "${label}".`;
210
+ // A category that duplicates a standard label would show twice in the picker
211
+ // with no way to tell them apart.
212
+ if (Object.values(constants_1.DESTINATION_CODES).some((l) => l.toLowerCase() === key)) {
213
+ return `"${label}" is already a standard destination.`;
214
+ }
215
+ seen.add(key);
216
+ }
217
+ return undefined;
218
+ };
219
+ exports.validateLeaverDestinations = validateLeaverDestinations;
220
+ //# sourceMappingURL=destinations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destinations.js","sourceRoot":"","sources":["../src/destinations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const destinations_1 = require("./destinations");
4
+ const customs = {
5
+ custom_in_hospital: { id: "custom_in_hospital", label: "In hospital", nccisCode: "NEET" },
6
+ custom_in_custody: { id: "custom_in_custody", label: "In custody", nccisCode: "NEET" },
7
+ custom_supported_internship: { id: "custom_supported_internship", label: "Supported internship", nccisCode: "EMP" },
8
+ custom_moved_away: { id: "custom_moved_away", label: "Moved away", nccisCode: "NK", archived: true },
9
+ };
10
+ describe("destinationOptions", () => {
11
+ it("lists the nine NCCIS codes when a school has added nothing", () => {
12
+ const options = (0, destinations_1.destinationOptions)();
13
+ expect(options).toHaveLength(9);
14
+ expect(options.every((o) => !o.custom)).toBe(true);
15
+ });
16
+ it("puts each custom category directly after the code it counts within", () => {
17
+ const codes = (0, destinations_1.destinationOptions)(customs).map((o) => o.code);
18
+ expect(codes.indexOf("custom_supported_internship")).toEqual(codes.indexOf("EMP") + 1);
19
+ // Both NEET categories follow NEET, alphabetically.
20
+ expect(codes.slice(codes.indexOf("NEET"), codes.indexOf("NEET") + 3))
21
+ .toEqual(["NEET", "custom_in_custody", "custom_in_hospital"]);
22
+ });
23
+ it("hides archived categories unless asked for them", () => {
24
+ expect((0, destinations_1.destinationOptions)(customs).map((o) => o.code)).not.toContain("custom_moved_away");
25
+ expect((0, destinations_1.destinationOptions)(customs, { includeArchived: true }).map((o) => o.code))
26
+ .toContain("custom_moved_away");
27
+ });
28
+ });
29
+ describe("resolveLeaverDestinations", () => {
30
+ const trust = { leaverDestinations: {
31
+ custom_in_care: { id: "custom_in_care", label: "In care", nccisCode: "NEET" },
32
+ custom_in_hospital: { id: "custom_in_hospital", label: "In hospital (trust)", nccisCode: "NEET" },
33
+ } };
34
+ it("offers the trust's categories alongside the school's, and names the trust's", () => {
35
+ const { destinations, trustIds } = (0, destinations_1.resolveLeaverDestinations)({ leaverDestinations: customs }, trust);
36
+ expect(Object.keys(destinations).sort()).toEqual([
37
+ "custom_in_care", "custom_in_custody", "custom_in_hospital",
38
+ "custom_moved_away", "custom_supported_internship",
39
+ ]);
40
+ expect(trustIds).toEqual(["custom_in_care"]);
41
+ });
42
+ it("leaves the school's own version of a shared id in the school's hands", () => {
43
+ const { destinations, trustIds } = (0, destinations_1.resolveLeaverDestinations)({ leaverDestinations: customs }, trust);
44
+ expect(destinations.custom_in_hospital.label).toEqual("In hospital");
45
+ expect(trustIds).not.toContain("custom_in_hospital");
46
+ });
47
+ it("is the school's own map when there is no trust", () => {
48
+ expect((0, destinations_1.resolveLeaverDestinations)({ leaverDestinations: customs }).trustIds).toEqual([]);
49
+ expect((0, destinations_1.resolveLeaverDestinations)().destinations).toEqual({});
50
+ });
51
+ });
52
+ describe("destinationLabel", () => {
53
+ it("reads standard codes and the school's own categories", () => {
54
+ expect((0, destinations_1.destinationLabel)("APP")).toEqual("Apprenticeship");
55
+ expect((0, destinations_1.destinationLabel)("custom_in_hospital", customs)).toEqual("In hospital");
56
+ });
57
+ it("recovers words from an id defined by another school", () => {
58
+ // A student browsing trust-wide alumni meets categories we don't hold.
59
+ expect((0, destinations_1.destinationLabel)("custom_in_care")).toEqual("In care");
60
+ });
61
+ });
62
+ describe("destinationNccisCode", () => {
63
+ it("maps a custom category onto the code it reports as", () => {
64
+ expect((0, destinations_1.destinationNccisCode)("custom_in_hospital", customs)).toEqual("NEET");
65
+ expect((0, destinations_1.destinationNccisCode)("custom_supported_internship", customs)).toEqual("EMP");
66
+ });
67
+ it("treats an unknown id as not known rather than dropping the leaver", () => {
68
+ expect((0, destinations_1.destinationNccisCode)("custom_gone", customs)).toEqual("NK");
69
+ });
70
+ it("leaves standard codes alone", () => {
71
+ expect((0, destinations_1.destinationNccisCode)("HE", customs)).toEqual("HE");
72
+ });
73
+ });
74
+ describe("isNegativeDestination", () => {
75
+ it("counts a school's NEET-mapped categories as NEET", () => {
76
+ expect((0, destinations_1.isNegativeDestination)("custom_in_hospital", customs)).toBe(true);
77
+ expect((0, destinations_1.isNegativeDestination)("custom_supported_internship", customs)).toBe(false);
78
+ expect((0, destinations_1.isNegativeDestination)("NK")).toBe(true);
79
+ expect((0, destinations_1.isNegativeDestination)("HE")).toBe(false);
80
+ });
81
+ });
82
+ describe("rollUpDestinationCounts", () => {
83
+ it("folds custom categories into their NCCIS code", () => {
84
+ const rolled = (0, destinations_1.rollUpDestinationCounts)({ HE: 10, NEET: 2, custom_in_hospital: 3, custom_in_custody: 1, custom_supported_internship: 4 }, customs);
85
+ expect(rolled).toEqual({ HE: 10, NEET: 6, EMP: 4 });
86
+ });
87
+ });
88
+ describe("customDestinationId", () => {
89
+ it("derives a field-path-safe id from the label", () => {
90
+ expect((0, destinations_1.customDestinationId)("In hospital")).toEqual("custom_in_hospital");
91
+ });
92
+ it("suffixes rather than overwriting a category that slugs the same", () => {
93
+ expect((0, destinations_1.customDestinationId)("In-hospital", customs)).toEqual("custom_in_hospital_2");
94
+ });
95
+ });
96
+ describe("validateLeaverDestinations", () => {
97
+ it("accepts a well-formed set", () => {
98
+ expect((0, destinations_1.validateLeaverDestinations)(customs)).toBeUndefined();
99
+ });
100
+ it("rejects a category with no name, a bad group, or a duplicate label", () => {
101
+ expect((0, destinations_1.validateLeaverDestinations)({
102
+ custom_x: { id: "custom_x", label: " ", nccisCode: "NEET" },
103
+ })).toBeDefined();
104
+ expect((0, destinations_1.validateLeaverDestinations)({
105
+ custom_x: { id: "custom_x", label: "In hospital", nccisCode: "XX" },
106
+ })).toBeDefined();
107
+ expect((0, destinations_1.validateLeaverDestinations)({
108
+ ...customs,
109
+ custom_in_hospital_2: { id: "custom_in_hospital_2", label: "in hospital", nccisCode: "NEET" },
110
+ })).toBeDefined();
111
+ });
112
+ it("rejects an id that could collide with a standard code or a field path", () => {
113
+ expect((0, destinations_1.validateLeaverDestinations)({
114
+ HE: { id: "HE", label: "Uni", nccisCode: "HE" },
115
+ })).toBeDefined();
116
+ expect((0, destinations_1.validateLeaverDestinations)({
117
+ "custom_a.b": { id: "custom_a.b", label: "Dotted", nccisCode: "NEET" },
118
+ })).toBeDefined();
119
+ });
120
+ });
121
+ //# sourceMappingURL=destinations.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destinations.test.js","sourceRoot":"","sources":["../src/destinations.test.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export * from "./groups";
21
21
  export * from "./emailSections";
22
22
  export * from "./exportTransformers";
23
23
  export * from "./compassExport";
24
+ export * from "./destinations";
24
25
  export * from "./benchmarkEvidenceRules";
25
26
  export * from "./portfolioAggregate";
26
27
  export * from "./tutorialTips";
package/lib/index.js CHANGED
@@ -41,6 +41,7 @@ __exportStar(require("./groups"), exports);
41
41
  __exportStar(require("./emailSections"), exports);
42
42
  __exportStar(require("./exportTransformers"), exports);
43
43
  __exportStar(require("./compassExport"), exports);
44
+ __exportStar(require("./destinations"), exports);
44
45
  __exportStar(require("./benchmarkEvidenceRules"), exports);
45
46
  __exportStar(require("./portfolioAggregate"), exports);
46
47
  __exportStar(require("./tutorialTips"), exports);