placementt-core 1.400.1089 → 1.400.1091

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.
@@ -27,18 +27,23 @@ export declare const apprenticeshipsCallables: {
27
27
  unchanged: z.ZodNumber;
28
28
  deleted: z.ZodNumber;
29
29
  requests: z.ZodNumber;
30
+ /** Adverts stored without a SOC because their standard was not in the
31
+ * lmiMeta/apprenticeshipStandards crosswalk. */
32
+ unlinked: z.ZodOptional<z.ZodNumber>;
30
33
  }, "strip", z.ZodTypeAny, {
31
34
  deleted: number;
32
35
  fetched: number;
33
36
  written: number;
34
37
  unchanged: number;
35
38
  requests: number;
39
+ unlinked?: number | undefined;
36
40
  }, {
37
41
  deleted: number;
38
42
  fetched: number;
39
43
  written: number;
40
44
  unchanged: number;
41
45
  requests: number;
46
+ unlinked?: number | undefined;
42
47
  }>;
43
48
  /** Requests this step made, so an admin can see the pacing held. */
44
49
  stepRequests: z.ZodNumber;
@@ -51,6 +56,7 @@ export declare const apprenticeshipsCallables: {
51
56
  written: number;
52
57
  unchanged: number;
53
58
  requests: number;
59
+ unlinked?: number | undefined;
54
60
  };
55
61
  stepRequests: number;
56
62
  cursor?: {
@@ -67,6 +73,7 @@ export declare const apprenticeshipsCallables: {
67
73
  written: number;
68
74
  unchanged: number;
69
75
  requests: number;
76
+ unlinked?: number | undefined;
70
77
  };
71
78
  stepRequests: number;
72
79
  cursor?: {
@@ -23,6 +23,9 @@ const syncTotals = zod_1.z.object({
23
23
  unchanged: zod_1.z.number(),
24
24
  deleted: zod_1.z.number(),
25
25
  requests: zod_1.z.number(),
26
+ /** Adverts stored without a SOC because their standard was not in the
27
+ * lmiMeta/apprenticeshipStandards crosswalk. */
28
+ unlinked: zod_1.z.number().optional(),
26
29
  });
27
30
  exports.apprenticeshipsCallables = {
28
31
  /** Run one crawl step now (about eight minutes of paced requests), starting
@@ -1 +1 @@
1
- {"version":3,"file":"apprenticeships.js","sourceRoot":"","sources":["../../src/callables/apprenticeships.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;;;;;;;GASG;AAEH,MAAM,UAAU,GAAG,OAAC,CAAC,MAAM,CAAC;IACxB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE;IACtB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE;IACtB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE;CACzB,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,OAAC,CAAC,MAAM,CAAC;IACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;IACrB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;CACvB,CAAC,CAAC;AAEU,QAAA,wBAAwB,GAAG;IACpC;oCACgC;IAChC,0BAA0B,EAAE;QACxB,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACrB,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;YACjB,oEAAoE;YACpE,QAAQ,EAAE,OAAC,CAAC,OAAO,EAAE;YACrB,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE;YAC7B,MAAM,EAAE,UAAU;YAClB,oEAAoE;YACpE,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;SAC3B,CAAC;KACL;IACD;kCAC8B;IAC9B,0BAA0B,EAAE;QACxB,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACrB,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,EAAE,EAAE,OAAC,CAAC,OAAO,CAAC,IAAI,CAAC;SACtB,CAAC;KACL;IACD,8DAA8D;IAC9D,+BAA+B,EAAE;QAC7B,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACrB,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,MAAM,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;SAC9B,CAAC;KACL;CACoB,CAAC"}
1
+ {"version":3,"file":"apprenticeships.js","sourceRoot":"","sources":["../../src/callables/apprenticeships.ts"],"names":[],"mappings":";;;AAAA,6BAAsB;AAGtB;;;;;;;;;GASG;AAEH,MAAM,UAAU,GAAG,OAAC,CAAC,MAAM,CAAC;IACxB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE;IACtB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE;IACtB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE;CACzB,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,OAAC,CAAC,MAAM,CAAC;IACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;IACrB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;IACpB;oDACgD;IAChD,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEU,QAAA,wBAAwB,GAAG;IACpC;oCACgC;IAChC,0BAA0B,EAAE;QACxB,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACrB,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;YACjB,oEAAoE;YACpE,QAAQ,EAAE,OAAC,CAAC,OAAO,EAAE;YACrB,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE;YAC7B,MAAM,EAAE,UAAU;YAClB,oEAAoE;YACpE,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;SAC3B,CAAC;KACL;IACD;kCAC8B;IAC9B,0BAA0B,EAAE;QACxB,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACrB,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,EAAE,EAAE,OAAC,CAAC,OAAO,CAAC,IAAI,CAAC;SACtB,CAAC;KACL;IACD,8DAA8D;IAC9D,+BAA+B,EAAE;QAC7B,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACrB,QAAQ,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,MAAM,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;SAC9B,CAAC;KACL;CACoB,CAAC"}
@@ -337,18 +337,21 @@ export declare const allCallables: {
337
337
  unchanged: z.ZodNumber;
338
338
  deleted: z.ZodNumber;
339
339
  requests: z.ZodNumber;
340
+ unlinked: z.ZodOptional<z.ZodNumber>;
340
341
  }, "strip", z.ZodTypeAny, {
341
342
  deleted: number;
342
343
  fetched: number;
343
344
  written: number;
344
345
  unchanged: number;
345
346
  requests: number;
347
+ unlinked?: number | undefined;
346
348
  }, {
347
349
  deleted: number;
348
350
  fetched: number;
349
351
  written: number;
350
352
  unchanged: number;
351
353
  requests: number;
354
+ unlinked?: number | undefined;
352
355
  }>;
353
356
  stepRequests: z.ZodNumber;
354
357
  }, "strip", z.ZodTypeAny, {
@@ -360,6 +363,7 @@ export declare const allCallables: {
360
363
  written: number;
361
364
  unchanged: number;
362
365
  requests: number;
366
+ unlinked?: number | undefined;
363
367
  };
364
368
  stepRequests: number;
365
369
  cursor?: {
@@ -376,6 +380,7 @@ export declare const allCallables: {
376
380
  written: number;
377
381
  unchanged: number;
378
382
  requests: number;
383
+ unlinked?: number | undefined;
379
384
  };
380
385
  stepRequests: number;
381
386
  cursor?: {
@@ -1646,18 +1651,21 @@ export declare const callableGroups: {
1646
1651
  unchanged: z.ZodNumber;
1647
1652
  deleted: z.ZodNumber;
1648
1653
  requests: z.ZodNumber;
1654
+ unlinked: z.ZodOptional<z.ZodNumber>;
1649
1655
  }, "strip", z.ZodTypeAny, {
1650
1656
  deleted: number;
1651
1657
  fetched: number;
1652
1658
  written: number;
1653
1659
  unchanged: number;
1654
1660
  requests: number;
1661
+ unlinked?: number | undefined;
1655
1662
  }, {
1656
1663
  deleted: number;
1657
1664
  fetched: number;
1658
1665
  written: number;
1659
1666
  unchanged: number;
1660
1667
  requests: number;
1668
+ unlinked?: number | undefined;
1661
1669
  }>;
1662
1670
  stepRequests: z.ZodNumber;
1663
1671
  }, "strip", z.ZodTypeAny, {
@@ -1669,6 +1677,7 @@ export declare const callableGroups: {
1669
1677
  written: number;
1670
1678
  unchanged: number;
1671
1679
  requests: number;
1680
+ unlinked?: number | undefined;
1672
1681
  };
1673
1682
  stepRequests: number;
1674
1683
  cursor?: {
@@ -1685,6 +1694,7 @@ export declare const callableGroups: {
1685
1694
  written: number;
1686
1695
  unchanged: number;
1687
1696
  requests: number;
1697
+ unlinked?: number | undefined;
1688
1698
  };
1689
1699
  stepRequests: number;
1690
1700
  cursor?: {
package/lib/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export * from "./activities";
18
18
  export * from "./activityLogging";
19
19
  export * from "./lmi";
20
20
  export * from "./lmiCategories";
21
+ export * from "./pathways";
21
22
  export * from "./productResolver";
22
23
  export * from "./pricing";
23
24
  export * from "./invoicing";
package/lib/index.js CHANGED
@@ -38,6 +38,7 @@ __exportStar(require("./activities"), exports);
38
38
  __exportStar(require("./activityLogging"), exports);
39
39
  __exportStar(require("./lmi"), exports);
40
40
  __exportStar(require("./lmiCategories"), exports);
41
+ __exportStar(require("./pathways"), exports);
41
42
  __exportStar(require("./productResolver"), exports);
42
43
  __exportStar(require("./pricing"), exports);
43
44
  __exportStar(require("./invoicing"), 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,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,mDAAiC;AACjC,+CAA6B;AAC7B,oDAAkC;AAClC,wCAAsB;AACtB,kDAAgC;AAChC,oDAAkC;AAClC,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB;AACzB,kDAAgC;AAChC,uDAAqC;AACrC,oDAAkC;AAClC,kDAAgC;AAChC,0DAAwC;AACxC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,qDAAmC;AACnC,0CAAwB;AACxB,kDAAgC;AAChC,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,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,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,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,mDAAiC;AACjC,+CAA6B;AAC7B,oDAAkC;AAClC,wCAAsB;AACtB,kDAAgC;AAChC,6CAA2B;AAC3B,oDAAkC;AAClC,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB;AACzB,kDAAgC;AAChC,uDAAqC;AACrC,oDAAkC;AAClC,kDAAgC;AAChC,0DAAwC;AACxC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,qDAAmC;AACnC,0CAAwB;AACxB,kDAAgC;AAChC,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,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Pathways: the joins between the careers library (SOC 2020), the apprenticeship
3
+ * mirror and the university course mirror, and what a student's own interests
4
+ * mean as a filter over the two.
5
+ *
6
+ * Everything routes through the 4-digit SOC 2020 unit group. Neither mirror
7
+ * carries one directly, so each is one hop away:
8
+ *
9
+ * vacancy.larsCode ─► IfATE standard.occupationCode ─► skillsEngland[].stdCode ─► soc
10
+ * course.commonJobs[i] (a HESA label) ─► SOC 2020 group title ─► prefix ("213")
11
+ *
12
+ * The HESA labels are SOC groups at whatever level HESA chose — mostly minor
13
+ * groups, a few major or unit — so a course links to a *prefix*, and both
14
+ * indexes carry each code's full prefix chain (`socPrefixChain`) so one exact
15
+ * Algolia facet term can match at any level.
16
+ *
17
+ * The functions here are pure and shared: the backend importers use the chain
18
+ * and the label matcher, the web uses the student-side helpers to build the
19
+ * "For you" filter and to say why a row matched.
20
+ */
21
+ import { HeCommonJobGroup, UserData } from "./typeDefinitions";
22
+ /**
23
+ * A SOC 2020 code's ancestors and itself, shortest first.
24
+ *
25
+ * Accepts a unit group, a sub-unit ("2134/03") or a preference key ("2134-03");
26
+ * only the leading digits count. A code shorter than four digits (a HESA label
27
+ * that resolved to a minor group) yields the chain up to its own length.
28
+ *
29
+ * @param {string} code the SOC 2020 code, any of the forms above.
30
+ * @return {string[]} e.g. ["2", "21", "213", "2134"]; empty for non-SOC input.
31
+ */
32
+ export declare function socPrefixChain(code: string): string[];
33
+ /**
34
+ * The form HESA labels and ONS group titles are compared in.
35
+ *
36
+ * Case, punctuation and spacing differ between the two sources ("Teaching
37
+ * Professionals" vs "Teaching professionals", "(RandD)" vs "(R&D)"), and one
38
+ * HESA label is singular where ONS is plural. Nothing semantic is done: if a
39
+ * future label needs more than this, it belongs in `unmatched`, not here.
40
+ *
41
+ * @param {string} title a label or group title.
42
+ * @return {string} the comparison key.
43
+ */
44
+ export declare function normaliseSocGroupTitle(title: string): string;
45
+ /**
46
+ * The SOC 2020 group a HESA common-job label names.
47
+ *
48
+ * @param {string} label the label as JOBLIST prints it.
49
+ * @param {{[code: string]: string}} titles the SOC hierarchy, code → title.
50
+ * @return {string|undefined} the group code, or undefined for a label no
51
+ * title matches.
52
+ */
53
+ export declare function socPrefixForLabel(label: string, titles: {
54
+ [code: string]: string;
55
+ }): string | undefined;
56
+ /**
57
+ * The 3-digit minor groups an interest category covers, plus any unit groups
58
+ * filed under it by override. Sorted, so a filter built from it is stable.
59
+ *
60
+ * @param {string} category one of CAREER_LIBRARY_CATEGORIES.
61
+ * @return {string[]} the codes; empty for an unknown category.
62
+ */
63
+ export declare function socCodesForCategory(category: string): string[];
64
+ /** One reason a course or vacancy is recommended: a hearted job, or an
65
+ * interest category. */
66
+ export type ForYouSource = {
67
+ kind: "job";
68
+ /** The preference key, so a chip can open the job page. */
69
+ key: string;
70
+ title: string;
71
+ /** The 4-digit SOC. */
72
+ codes: string[];
73
+ } | {
74
+ kind: "category";
75
+ title: string;
76
+ /** The category's minor groups and overridden unit groups. */
77
+ codes: string[];
78
+ };
79
+ /**
80
+ * What a student's careers tab says they are interested in, as filter sources.
81
+ *
82
+ * Hearted jobs first (exact unit groups), then interest categories (sets of
83
+ * minor groups), in the order the student holds them. The categories are the
84
+ * live set if they have edited it, otherwise their latest survey answer — the
85
+ * same rule the library sidebar shows them under.
86
+ *
87
+ * @param {Pick<UserData, "currentJobPreferences"|"currentCareerInterests"|"aspirationHistory">} user the student.
88
+ * @return {ForYouSource[]} possibly empty, in which case there is nothing to recommend from.
89
+ */
90
+ export declare function forYouSources(user?: Pick<UserData, "currentJobPreferences" | "currentCareerInterests" | "aspirationHistory">): ForYouSource[];
91
+ /** The facet terms a "For you" search filters each index on. */
92
+ export type ForYouFilter = {
93
+ /** Exact codes — a vacancy's chain always reaches unit level, so a
94
+ * hearted 2134 or an interest's 213 is matched as itself. */
95
+ vacancyTerms: string[];
96
+ /** Codes and every ancestor — a course's label can sit at any level, so
97
+ * a hearted 2134 must also find a course filed under "213" or "21". */
98
+ courseTerms: string[];
99
+ };
100
+ /**
101
+ * Turn the sources into the two term lists, deduped and sorted.
102
+ *
103
+ * @param {ForYouSource[]} sources from `forYouSources`, possibly narrowed to
104
+ * the chips the student tapped.
105
+ * @return {ForYouFilter} empty lists when there are no sources.
106
+ */
107
+ export declare function forYouFilter(sources: ForYouSource[]): ForYouFilter;
108
+ /**
109
+ * Which source a row is there because of.
110
+ *
111
+ * A row matches a source code when the row sits under the code (a vacancy at
112
+ * 2134 under an interest's 213) or the code sits under the row's own group (a
113
+ * course filed under "213" for a hearted 2134). Sharing an ancestor is not
114
+ * enough — 2211 and 2134 both start with "2" and have nothing to do with each
115
+ * other — so the row side is compared at the ends of its chains, never at
116
+ * an ancestor. The longest such overlap wins, which is what makes a hearted
117
+ * job beat a category covering the same minor group, and the depth comes
118
+ * back so a list can sort exact matches above group-level ones.
119
+ *
120
+ * @param {string[]} rowPrefixes the record's `socPrefixes`.
121
+ * @param {ForYouSource[]} sources the sources the filter was built from.
122
+ * @return {{source: ForYouSource, depth: number}|undefined} the best source,
123
+ * or undefined when nothing overlaps (a row from an unfiltered search).
124
+ */
125
+ export declare function forYouMatch(rowPrefixes: string[] | undefined, sources: ForYouSource[]): {
126
+ source: ForYouSource;
127
+ depth: number;
128
+ } | undefined;
129
+ /** Why a course is recommended: the source it matched and, where the course
130
+ * carries its common-job groups, the group that did the matching with its
131
+ * share of graduates. */
132
+ export type ForYouCourseMatch = {
133
+ source: ForYouSource;
134
+ depth: number;
135
+ group?: HeCommonJobGroup;
136
+ };
137
+ /**
138
+ * Which source a course matched, and through which of its common-job groups.
139
+ *
140
+ * The course's `socPrefixes` say whether it matches at all; its
141
+ * `commonJobGroups` say which group did it, and that group's `perc` is the
142
+ * figure a row quotes — "41% went into information technology professionals".
143
+ * Where two groups match, the deeper one wins, then the larger share. A course
144
+ * indexed before the groups existed still matches on its prefixes, just with
145
+ * no group to quote.
146
+ *
147
+ * @param {object} course the index record, or the part of it that matters.
148
+ * @param {string[]} [course.socPrefixes] its prefix chains.
149
+ * @param {HeCommonJobGroup[]} [course.commonJobGroups] its groups.
150
+ * @param {ForYouSource[]} sources the sources the filter was built from.
151
+ * @return {ForYouCourseMatch|undefined} the match, or nothing.
152
+ */
153
+ export declare function forYouCourseMatch(course: {
154
+ socPrefixes?: string[];
155
+ commonJobGroups?: HeCommonJobGroup[];
156
+ }, sources: ForYouSource[]): ForYouCourseMatch | undefined;
157
+ /**
158
+ * Order recommendations so the most direct matches lead, keeping the input
159
+ * order (nearest first, as Algolia returns a geo search) within a depth.
160
+ * Items that match nothing are dropped: they can only be there because the
161
+ * index and the student's sources disagree, and a row with no reason reads
162
+ * as an advert.
163
+ *
164
+ * @param {T[]} items the hits, in the order they came back.
165
+ * @param {(item: T) => {depth: number}|undefined} matchOf how each matched.
166
+ * @return {T[]} the matched items, deepest first.
167
+ */
168
+ export declare function rankForYou<T>(items: T[], matchOf: (item: T) => {
169
+ depth: number;
170
+ } | undefined): T[];
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ /**
3
+ * Pathways: the joins between the careers library (SOC 2020), the apprenticeship
4
+ * mirror and the university course mirror, and what a student's own interests
5
+ * mean as a filter over the two.
6
+ *
7
+ * Everything routes through the 4-digit SOC 2020 unit group. Neither mirror
8
+ * carries one directly, so each is one hop away:
9
+ *
10
+ * vacancy.larsCode ─► IfATE standard.occupationCode ─► skillsEngland[].stdCode ─► soc
11
+ * course.commonJobs[i] (a HESA label) ─► SOC 2020 group title ─► prefix ("213")
12
+ *
13
+ * The HESA labels are SOC groups at whatever level HESA chose — mostly minor
14
+ * groups, a few major or unit — so a course links to a *prefix*, and both
15
+ * indexes carry each code's full prefix chain (`socPrefixChain`) so one exact
16
+ * Algolia facet term can match at any level.
17
+ *
18
+ * The functions here are pure and shared: the backend importers use the chain
19
+ * and the label matcher, the web uses the student-side helpers to build the
20
+ * "For you" filter and to say why a row matched.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.socPrefixChain = socPrefixChain;
24
+ exports.normaliseSocGroupTitle = normaliseSocGroupTitle;
25
+ exports.socPrefixForLabel = socPrefixForLabel;
26
+ exports.socCodesForCategory = socCodesForCategory;
27
+ exports.forYouSources = forYouSources;
28
+ exports.forYouFilter = forYouFilter;
29
+ exports.forYouMatch = forYouMatch;
30
+ exports.forYouCourseMatch = forYouCourseMatch;
31
+ exports.rankForYou = rankForYou;
32
+ const lmiCategories_1 = require("./lmiCategories");
33
+ /**
34
+ * A SOC 2020 code's ancestors and itself, shortest first.
35
+ *
36
+ * Accepts a unit group, a sub-unit ("2134/03") or a preference key ("2134-03");
37
+ * only the leading digits count. A code shorter than four digits (a HESA label
38
+ * that resolved to a minor group) yields the chain up to its own length.
39
+ *
40
+ * @param {string} code the SOC 2020 code, any of the forms above.
41
+ * @return {string[]} e.g. ["2", "21", "213", "2134"]; empty for non-SOC input.
42
+ */
43
+ function socPrefixChain(code) {
44
+ const digits = String(code ?? "").trim().match(/^\d{1,4}/)?.[0] ?? "";
45
+ return Array.from({ length: digits.length }, (_, i) => digits.slice(0, i + 1));
46
+ }
47
+ /**
48
+ * The form HESA labels and ONS group titles are compared in.
49
+ *
50
+ * Case, punctuation and spacing differ between the two sources ("Teaching
51
+ * Professionals" vs "Teaching professionals", "(RandD)" vs "(R&D)"), and one
52
+ * HESA label is singular where ONS is plural. Nothing semantic is done: if a
53
+ * future label needs more than this, it belongs in `unmatched`, not here.
54
+ *
55
+ * @param {string} title a label or group title.
56
+ * @return {string} the comparison key.
57
+ */
58
+ function normaliseSocGroupTitle(title) {
59
+ return String(title ?? "")
60
+ .toLowerCase()
61
+ .replace(/\(randd\)/g, "(r&d)")
62
+ .replace(/[^a-z0-9 ]/g, "")
63
+ .replace(/\s+/g, " ")
64
+ .trim()
65
+ .replace(/ occupation$/, " occupations");
66
+ }
67
+ /**
68
+ * The SOC 2020 group a HESA common-job label names.
69
+ *
70
+ * @param {string} label the label as JOBLIST prints it.
71
+ * @param {{[code: string]: string}} titles the SOC hierarchy, code → title.
72
+ * @return {string|undefined} the group code, or undefined for a label no
73
+ * title matches.
74
+ */
75
+ function socPrefixForLabel(label, titles) {
76
+ const wanted = normaliseSocGroupTitle(label);
77
+ if (!wanted)
78
+ return undefined;
79
+ for (const [code, title] of Object.entries(titles)) {
80
+ if (normaliseSocGroupTitle(title) === wanted)
81
+ return code;
82
+ }
83
+ return undefined;
84
+ }
85
+ /**
86
+ * The 3-digit minor groups an interest category covers, plus any unit groups
87
+ * filed under it by override. Sorted, so a filter built from it is stable.
88
+ *
89
+ * @param {string} category one of CAREER_LIBRARY_CATEGORIES.
90
+ * @return {string[]} the codes; empty for an unknown category.
91
+ */
92
+ function socCodesForCategory(category) {
93
+ const codes = new Set();
94
+ for (const [minor, c] of Object.entries(lmiCategories_1.CAREER_CATEGORY_BY_SOC_MINOR)) {
95
+ if (c === category)
96
+ codes.add(minor);
97
+ }
98
+ for (const [unit, c] of Object.entries(lmiCategories_1.CAREER_CATEGORY_BY_SOC_UNIT)) {
99
+ if (c === category && !codes.has(unit.slice(0, 3)))
100
+ codes.add(unit);
101
+ }
102
+ return [...codes].sort();
103
+ }
104
+ /**
105
+ * What a student's careers tab says they are interested in, as filter sources.
106
+ *
107
+ * Hearted jobs first (exact unit groups), then interest categories (sets of
108
+ * minor groups), in the order the student holds them. The categories are the
109
+ * live set if they have edited it, otherwise their latest survey answer — the
110
+ * same rule the library sidebar shows them under.
111
+ *
112
+ * @param {Pick<UserData, "currentJobPreferences"|"currentCareerInterests"|"aspirationHistory">} user the student.
113
+ * @return {ForYouSource[]} possibly empty, in which case there is nothing to recommend from.
114
+ */
115
+ function forYouSources(user) {
116
+ const sources = [];
117
+ const prefs = user?.currentJobPreferences ?? {};
118
+ Object.entries(prefs)
119
+ .sort(([, a], [, b]) => (a.added || "").localeCompare(b.added || ""))
120
+ .forEach(([key, p]) => {
121
+ const soc = (p.soc || key.split("-")[0] || "").trim();
122
+ if (!/^\d{4}$/.test(soc))
123
+ return;
124
+ sources.push({ kind: "job", key, title: p.title || soc, codes: [soc] });
125
+ });
126
+ const interests = user?.currentCareerInterests ??
127
+ [...(user?.aspirationHistory ?? [])]
128
+ .sort((a, b) => (b.date || "").localeCompare(a.date || ""))[0]?.careerInterests ??
129
+ [];
130
+ interests
131
+ .filter((c) => lmiCategories_1.CAREER_LIBRARY_CATEGORIES.includes(c))
132
+ .forEach((category) => {
133
+ const codes = socCodesForCategory(category);
134
+ if (codes.length)
135
+ sources.push({ kind: "category", title: category, codes });
136
+ });
137
+ return sources;
138
+ }
139
+ /**
140
+ * Turn the sources into the two term lists, deduped and sorted.
141
+ *
142
+ * @param {ForYouSource[]} sources from `forYouSources`, possibly narrowed to
143
+ * the chips the student tapped.
144
+ * @return {ForYouFilter} empty lists when there are no sources.
145
+ */
146
+ function forYouFilter(sources) {
147
+ const vacancy = new Set();
148
+ const course = new Set();
149
+ for (const s of sources) {
150
+ for (const code of s.codes) {
151
+ vacancy.add(code);
152
+ socPrefixChain(code).forEach((p) => course.add(p));
153
+ }
154
+ }
155
+ return {
156
+ vacancyTerms: [...vacancy].sort(),
157
+ courseTerms: [...course].sort(),
158
+ };
159
+ }
160
+ /**
161
+ * Which source a row is there because of.
162
+ *
163
+ * A row matches a source code when the row sits under the code (a vacancy at
164
+ * 2134 under an interest's 213) or the code sits under the row's own group (a
165
+ * course filed under "213" for a hearted 2134). Sharing an ancestor is not
166
+ * enough — 2211 and 2134 both start with "2" and have nothing to do with each
167
+ * other — so the row side is compared at the ends of its chains, never at
168
+ * an ancestor. The longest such overlap wins, which is what makes a hearted
169
+ * job beat a category covering the same minor group, and the depth comes
170
+ * back so a list can sort exact matches above group-level ones.
171
+ *
172
+ * @param {string[]} rowPrefixes the record's `socPrefixes`.
173
+ * @param {ForYouSource[]} sources the sources the filter was built from.
174
+ * @return {{source: ForYouSource, depth: number}|undefined} the best source,
175
+ * or undefined when nothing overlaps (a row from an unfiltered search).
176
+ */
177
+ function forYouMatch(rowPrefixes, sources) {
178
+ const rows = new Set(rowPrefixes ?? []);
179
+ // The codes the row is actually filed under: chain ends, not ancestors.
180
+ const leaves = [...rows].filter((p) => ![...rows].some((q) => q.length === p.length + 1 && q.startsWith(p)));
181
+ let best;
182
+ for (const source of sources) {
183
+ for (const code of source.codes) {
184
+ const depth = rows.has(code) ? code.length :
185
+ leaves.filter((leaf) => code.startsWith(leaf))
186
+ .reduce((n, leaf) => Math.max(n, leaf.length), 0);
187
+ if (depth && (!best || depth > best.depth))
188
+ best = { source, depth };
189
+ }
190
+ }
191
+ return best;
192
+ }
193
+ /**
194
+ * Which source a course matched, and through which of its common-job groups.
195
+ *
196
+ * The course's `socPrefixes` say whether it matches at all; its
197
+ * `commonJobGroups` say which group did it, and that group's `perc` is the
198
+ * figure a row quotes — "41% went into information technology professionals".
199
+ * Where two groups match, the deeper one wins, then the larger share. A course
200
+ * indexed before the groups existed still matches on its prefixes, just with
201
+ * no group to quote.
202
+ *
203
+ * @param {object} course the index record, or the part of it that matters.
204
+ * @param {string[]} [course.socPrefixes] its prefix chains.
205
+ * @param {HeCommonJobGroup[]} [course.commonJobGroups] its groups.
206
+ * @param {ForYouSource[]} sources the sources the filter was built from.
207
+ * @return {ForYouCourseMatch|undefined} the match, or nothing.
208
+ */
209
+ function forYouCourseMatch(course, sources) {
210
+ const overall = forYouMatch(course.socPrefixes, sources);
211
+ if (!overall)
212
+ return undefined;
213
+ const best = (course.commonJobGroups ?? [])
214
+ .filter((g) => g.prefix)
215
+ .map((g) => ({ group: g, match: forYouMatch(socPrefixChain(g.prefix), sources) }))
216
+ .filter((x) => Boolean(x.match))
217
+ .sort((a, b) => b.match.depth - a.match.depth ||
218
+ (b.group.perc ?? -1) - (a.group.perc ?? -1))[0];
219
+ return best ?
220
+ { source: best.match.source, depth: best.match.depth, group: best.group } :
221
+ overall;
222
+ }
223
+ /**
224
+ * Order recommendations so the most direct matches lead, keeping the input
225
+ * order (nearest first, as Algolia returns a geo search) within a depth.
226
+ * Items that match nothing are dropped: they can only be there because the
227
+ * index and the student's sources disagree, and a row with no reason reads
228
+ * as an advert.
229
+ *
230
+ * @param {T[]} items the hits, in the order they came back.
231
+ * @param {(item: T) => {depth: number}|undefined} matchOf how each matched.
232
+ * @return {T[]} the matched items, deepest first.
233
+ */
234
+ function rankForYou(items, matchOf) {
235
+ return items
236
+ .map((item, index) => ({ item, index, depth: matchOf(item)?.depth ?? 0 }))
237
+ .filter((x) => x.depth > 0)
238
+ .sort((a, b) => b.depth - a.depth || a.index - b.index)
239
+ .map((x) => x.item);
240
+ }
241
+ //# sourceMappingURL=pathways.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathways.js","sourceRoot":"","sources":["../src/pathways.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};