placementt-core 1.400.969 → 1.400.971

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/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from "./customerCareConfig";
7
7
  export * from "./firebase/firebaseConfig";
8
8
  export * from "./firebase/firebaseQuery";
9
9
  export * from "./typeDefinitions";
10
+ export * from "./skillsResolver";
10
11
  export * from "./emailSections";
11
12
  export * from "./exportTransformers";
12
13
  export * from "./compassExport";
package/lib/index.js CHANGED
@@ -27,6 +27,7 @@ __exportStar(require("./customerCareConfig"), exports);
27
27
  __exportStar(require("./firebase/firebaseConfig"), exports);
28
28
  __exportStar(require("./firebase/firebaseQuery"), exports);
29
29
  __exportStar(require("./typeDefinitions"), exports);
30
+ __exportStar(require("./skillsResolver"), exports);
30
31
  __exportStar(require("./emailSections"), exports);
31
32
  __exportStar(require("./exportTransformers"), exports);
32
33
  __exportStar(require("./compassExport"), exports);
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,kDAAgC;AAChC,uDAAqC;AACrC,kDAAgC;AAChC,2DAAyC;AACzC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,0CAAwB;AACxB,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,mDAAiC;AACjC,kDAAgC;AAChC,uDAAqC;AACrC,kDAAgC;AAChC,2DAAyC;AACzC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,0CAAwB;AACxB,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
@@ -0,0 +1,41 @@
1
+ import { InstituteData, SkillLabel, SkillsMapping } from "./typeDefinitions";
2
+ /**
3
+ * Shared resolution of which skills an institute — and a specific student — should see.
4
+ * Used by the web app (assessment forms, setup, analytics) and the backend (external
5
+ * parent/employer assessment forms), so keep it dependency-free.
6
+ *
7
+ * Model:
8
+ * - Institute base set: labels tagged with the institute's skillsFramework, or the
9
+ * stored `skills` id list when the framework is "custom".
10
+ * - Per-student set: staff-defined skillsMappings rules are evaluated against the
11
+ * student's detail fields at view time. Matching rules contribute the union of
12
+ * their skills; a student matching no rule gets the full base set.
13
+ */
14
+ type InstituteSkillsConfig = Pick<InstituteData, "skillsFramework" | "skillsMappings"> & {
15
+ skills?: string[];
16
+ };
17
+ /** Ids of the institute's base skill set (no student context). */
18
+ export declare function resolveInstituteSkillIds(institute: InstituteSkillsConfig, allLabels: {
19
+ [id: string]: SkillLabel;
20
+ }): string[];
21
+ /** Does this student's detail fields satisfy a mapping's filters?
22
+ * Fields are ANDed; values within a field are ORed. An empty rule matches nobody. */
23
+ export declare function studentMatchesSkillsMapping(studentDetails: {
24
+ [field: string]: unknown;
25
+ } | undefined, filters: SkillsMapping["filters"]): boolean;
26
+ /** Ids of the skills this specific student should be served.
27
+ * No mappings configured (or no student context) → full base set. */
28
+ export declare function resolveStudentSkillIds(institute: InstituteSkillsConfig, allLabels: {
29
+ [id: string]: SkillLabel;
30
+ }, studentDetails?: {
31
+ [field: string]: unknown;
32
+ }): string[];
33
+ /** Convenience: same as resolveStudentSkillIds but returns the label objects keyed by id. */
34
+ export declare function resolveStudentSkills(institute: InstituteSkillsConfig, allLabels: {
35
+ [id: string]: SkillLabel;
36
+ }, studentDetails?: {
37
+ [field: string]: unknown;
38
+ }): {
39
+ [id: string]: SkillLabel;
40
+ };
41
+ export {};
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveInstituteSkillIds = resolveInstituteSkillIds;
4
+ exports.studentMatchesSkillsMapping = studentMatchesSkillsMapping;
5
+ exports.resolveStudentSkillIds = resolveStudentSkillIds;
6
+ exports.resolveStudentSkills = resolveStudentSkills;
7
+ /** Ids of the institute's base skill set (no student context). */
8
+ function resolveInstituteSkillIds(institute, allLabels) {
9
+ const framework = institute.skillsFramework;
10
+ if (framework && framework !== "custom") {
11
+ return Object.entries(allLabels)
12
+ .filter(([, label]) => label.framework === framework)
13
+ .map(([id]) => id);
14
+ }
15
+ return (institute.skills || []).filter((id) => allLabels[id]);
16
+ }
17
+ /** Does this student's detail fields satisfy a mapping's filters?
18
+ * Fields are ANDed; values within a field are ORed. An empty rule matches nobody. */
19
+ function studentMatchesSkillsMapping(studentDetails, filters) {
20
+ const entries = Object.entries(filters || {}).filter(([, values]) => values?.length);
21
+ if (entries.length === 0)
22
+ return false;
23
+ return entries.every(([field, values]) => {
24
+ const studentValue = String(studentDetails?.[field] ?? "").trim().toLowerCase();
25
+ return values.some((v) => String(v).trim().toLowerCase() === studentValue);
26
+ });
27
+ }
28
+ /** Ids of the skills this specific student should be served.
29
+ * No mappings configured (or no student context) → full base set. */
30
+ function resolveStudentSkillIds(institute, allLabels, studentDetails) {
31
+ const base = resolveInstituteSkillIds(institute, allLabels);
32
+ const mappings = institute.skillsMappings || [];
33
+ if (!studentDetails || mappings.length === 0)
34
+ return base;
35
+ const matched = mappings.filter((m) => studentMatchesSkillsMapping(studentDetails, m.filters));
36
+ if (matched.length === 0)
37
+ return base;
38
+ const ids = Array.from(new Set(matched.flatMap((m) => m.skills)))
39
+ .filter((id) => allLabels[id] && base.includes(id));
40
+ return ids.length ? ids : base;
41
+ }
42
+ /** Convenience: same as resolveStudentSkillIds but returns the label objects keyed by id. */
43
+ function resolveStudentSkills(institute, allLabels, studentDetails) {
44
+ return Object.fromEntries(resolveStudentSkillIds(institute, allLabels, studentDetails)
45
+ .map((id) => [id, allLabels[id]]));
46
+ }
47
+ //# sourceMappingURL=skillsResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillsResolver.js","sourceRoot":"","sources":["../src/skillsResolver.ts"],"names":[],"mappings":"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"}
@@ -931,8 +931,19 @@ export type InstituteData = {
931
931
  };
932
932
  skillsAssessments?: boolean;
933
933
  assessActivitySkills?: boolean;
934
+ /** The framework the institute assesses. When "skillsBuilder"/"sdsMetaskills", the
935
+ * skill set is resolved dynamically from skillsLabels tagged with that framework —
936
+ * NOT from a stored id list. When "custom", `skills` holds the selected label ids. */
934
937
  skillsFramework?: SkillsFrameworkKey;
938
+ /** Only meaningful when skillsFramework is "custom": the selected skillsLabels ids
939
+ * (generic public labels and/or the school's own custom labels). */
935
940
  skills: string[];
941
+ /** Staff-defined student groups serving specific skills to specific students based
942
+ * on their detail fields (studentsFields). Evaluated at assessment view time. */
943
+ skillsMappings?: SkillsMapping[];
944
+ /** Annual survey schedule for the default "All students" group. Auto-provisioned
945
+ * into each academic year by the daily cron. */
946
+ skillsSurveys?: SkillsSurveySlot[];
936
947
  scheduledSkillsAssessments?: {
937
948
  [key in "yearStart" | "yearEnd" | "eachTerm" | "preWorkExperience" | "postWorkExperience" | "employerInteraction"]?: {
938
949
  students?: boolean;
@@ -2852,7 +2863,7 @@ export type EmailInteraction = {
2852
2863
  * must NOT set this, or it would escalate recursively. */
2853
2864
  fallback?: ReminderFallbackType;
2854
2865
  };
2855
- /** Skills framework a skill can be tagged against. "custom" is the user's own selection
2866
+ /** Skills framework a skill can be tagged against. "custom" is the school's own selection
2856
2867
  * and is never used as a tag on a SkillLabel. */
2857
2868
  export type SkillsFrameworkKey = "custom" | "skillsBuilder" | "sdsMetaskills";
2858
2869
  export type SkillLabel = {
@@ -2860,7 +2871,42 @@ export type SkillLabel = {
2860
2871
  description: string;
2861
2872
  category: keyof typeof skillLabelColours;
2862
2873
  public: boolean;
2863
- frameworks?: SkillsFrameworkKey[];
2874
+ /** The single framework this label belongs to. Skills on different frameworks are
2875
+ * completely separate documents — duplicates across frameworks are intentional.
2876
+ * Absent for generic/custom labels. */
2877
+ framework?: SkillsFrameworkKey;
2878
+ /** Set when a school created this label as a custom skill (scoped to that school).
2879
+ * Absent for platform-wide labels. */
2880
+ oId?: string;
2881
+ };
2882
+ /** One scheduled skills survey in a group's annual programme. `sendMMDD` is "MM-DD".
2883
+ * Auto-provisioned into each new academic year by the daily cron (mirrors the
2884
+ * aspirations AspirationSurveySlot model). */
2885
+ export type SkillsSurveySlot = {
2886
+ sendMMDD: string;
2887
+ label?: string;
2888
+ students?: boolean;
2889
+ parents?: boolean;
2890
+ };
2891
+ /**
2892
+ * A staff-defined student group mapping students to the skills they are served on
2893
+ * assessments. `filters` keys are student detail field names (from
2894
+ * InstituteData.studentsFields); values are the accepted values for that field. A
2895
+ * student matches a group when EVERY filtered field's value is one of the accepted
2896
+ * values (values within a field are OR, fields are AND). A student matching multiple
2897
+ * groups is served the UNION of their skills. A student matching no group falls back
2898
+ * to the institute's "All students" base set (skillsFramework/skills).
2899
+ * Each group carries its own annual survey schedule; the "All students" default
2900
+ * group's schedule lives on InstituteData.skillsSurveys.
2901
+ */
2902
+ export type SkillsMapping = {
2903
+ id: string;
2904
+ name?: string;
2905
+ filters: {
2906
+ [field: string]: string[];
2907
+ };
2908
+ skills: string[];
2909
+ surveys?: SkillsSurveySlot[];
2864
2910
  };
2865
2911
  /** Rater types for skills assessments (mirrors SkillAssessment.userType). */
2866
2912
  export type SkillRater = "students" | "parents" | "employers";
@@ -2952,6 +2998,8 @@ export type SkillAssessmentRequest = {
2952
2998
  oId: string;
2953
2999
  academicYear?: string;
2954
3000
  yearGroup?: number;
3001
+ groupId?: string;
3002
+ preResolved?: boolean;
2955
3003
  sendTime?: string;
2956
3004
  students: string;
2957
3005
  requestParents?: boolean;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "placementt-core",
4
4
  "author": "Placementt",
5
- "version": "1.400.969",
5
+ "version": "1.400.971",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export * from "./firebase/firebaseConfig";
9
9
  export * from "./firebase/firebaseQuery";
10
10
 
11
11
  export * from "./typeDefinitions";
12
+ export * from "./skillsResolver";
12
13
  export * from "./emailSections";
13
14
  export * from "./exportTransformers";
14
15
  export * from "./compassExport";
@@ -0,0 +1,73 @@
1
+ import {InstituteData, SkillLabel, SkillsMapping} from "./typeDefinitions";
2
+
3
+ /**
4
+ * Shared resolution of which skills an institute — and a specific student — should see.
5
+ * Used by the web app (assessment forms, setup, analytics) and the backend (external
6
+ * parent/employer assessment forms), so keep it dependency-free.
7
+ *
8
+ * Model:
9
+ * - Institute base set: labels tagged with the institute's skillsFramework, or the
10
+ * stored `skills` id list when the framework is "custom".
11
+ * - Per-student set: staff-defined skillsMappings rules are evaluated against the
12
+ * student's detail fields at view time. Matching rules contribute the union of
13
+ * their skills; a student matching no rule gets the full base set.
14
+ */
15
+
16
+ type InstituteSkillsConfig = Pick<InstituteData, "skillsFramework" | "skillsMappings"> & {skills?: string[]};
17
+
18
+ /** Ids of the institute's base skill set (no student context). */
19
+ export function resolveInstituteSkillIds(
20
+ institute: InstituteSkillsConfig,
21
+ allLabels: {[id: string]: SkillLabel},
22
+ ): string[] {
23
+ const framework = institute.skillsFramework;
24
+ if (framework && framework !== "custom") {
25
+ return Object.entries(allLabels)
26
+ .filter(([, label]) => label.framework === framework)
27
+ .map(([id]) => id);
28
+ }
29
+ return (institute.skills || []).filter((id) => allLabels[id]);
30
+ }
31
+
32
+ /** Does this student's detail fields satisfy a mapping's filters?
33
+ * Fields are ANDed; values within a field are ORed. An empty rule matches nobody. */
34
+ export function studentMatchesSkillsMapping(
35
+ studentDetails: {[field: string]: unknown} | undefined,
36
+ filters: SkillsMapping["filters"],
37
+ ): boolean {
38
+ const entries = Object.entries(filters || {}).filter(([, values]) => values?.length);
39
+ if (entries.length === 0) return false;
40
+ return entries.every(([field, values]) => {
41
+ const studentValue = String(studentDetails?.[field] ?? "").trim().toLowerCase();
42
+ return values.some((v) => String(v).trim().toLowerCase() === studentValue);
43
+ });
44
+ }
45
+
46
+ /** Ids of the skills this specific student should be served.
47
+ * No mappings configured (or no student context) → full base set. */
48
+ export function resolveStudentSkillIds(
49
+ institute: InstituteSkillsConfig,
50
+ allLabels: {[id: string]: SkillLabel},
51
+ studentDetails?: {[field: string]: unknown},
52
+ ): string[] {
53
+ const base = resolveInstituteSkillIds(institute, allLabels);
54
+ const mappings = institute.skillsMappings || [];
55
+ if (!studentDetails || mappings.length === 0) return base;
56
+
57
+ const matched = mappings.filter((m) => studentMatchesSkillsMapping(studentDetails, m.filters));
58
+ if (matched.length === 0) return base;
59
+
60
+ const ids = Array.from(new Set(matched.flatMap((m) => m.skills)))
61
+ .filter((id) => allLabels[id] && base.includes(id));
62
+ return ids.length ? ids : base;
63
+ }
64
+
65
+ /** Convenience: same as resolveStudentSkillIds but returns the label objects keyed by id. */
66
+ export function resolveStudentSkills(
67
+ institute: InstituteSkillsConfig,
68
+ allLabels: {[id: string]: SkillLabel},
69
+ studentDetails?: {[field: string]: unknown},
70
+ ): {[id: string]: SkillLabel} {
71
+ return Object.fromEntries(resolveStudentSkillIds(institute, allLabels, studentDetails)
72
+ .map((id) => [id, allLabels[id]]));
73
+ }
@@ -940,8 +940,19 @@ export type InstituteData = {
940
940
  // SKILLS ASSESSMENT CONFIGURATION
941
941
  skillsAssessments?: boolean,
942
942
  assessActivitySkills?: boolean, // ask students to self-assess skills when logging an extracurricular activity
943
- skillsFramework?: SkillsFrameworkKey, // which preset the institute started its skill selection from
943
+ /** The framework the institute assesses. When "skillsBuilder"/"sdsMetaskills", the
944
+ * skill set is resolved dynamically from skillsLabels tagged with that framework —
945
+ * NOT from a stored id list. When "custom", `skills` holds the selected label ids. */
946
+ skillsFramework?: SkillsFrameworkKey,
947
+ /** Only meaningful when skillsFramework is "custom": the selected skillsLabels ids
948
+ * (generic public labels and/or the school's own custom labels). */
944
949
  skills: string[]
950
+ /** Staff-defined student groups serving specific skills to specific students based
951
+ * on their detail fields (studentsFields). Evaluated at assessment view time. */
952
+ skillsMappings?: SkillsMapping[],
953
+ /** Annual survey schedule for the default "All students" group. Auto-provisioned
954
+ * into each academic year by the daily cron. */
955
+ skillsSurveys?: SkillsSurveySlot[],
945
956
  scheduledSkillsAssessments?: {
946
957
  [key in "yearStart" | "yearEnd" | "eachTerm" | "preWorkExperience" | "postWorkExperience" | "employerInteraction"]?: {
947
958
  students?: boolean,
@@ -2771,7 +2782,7 @@ export type EmailInteraction = {
2771
2782
  fallback?: ReminderFallbackType,
2772
2783
  }
2773
2784
 
2774
- /** Skills framework a skill can be tagged against. "custom" is the user's own selection
2785
+ /** Skills framework a skill can be tagged against. "custom" is the school's own selection
2775
2786
  * and is never used as a tag on a SkillLabel. */
2776
2787
  export type SkillsFrameworkKey = "custom" | "skillsBuilder" | "sdsMetaskills";
2777
2788
 
@@ -2780,7 +2791,42 @@ export type SkillLabel = {
2780
2791
  description: string,
2781
2792
  category: keyof typeof skillLabelColours,
2782
2793
  public: boolean,
2783
- frameworks?: SkillsFrameworkKey[], // which framework presets this skill belongs to
2794
+ /** The single framework this label belongs to. Skills on different frameworks are
2795
+ * completely separate documents — duplicates across frameworks are intentional.
2796
+ * Absent for generic/custom labels. */
2797
+ framework?: SkillsFrameworkKey,
2798
+ /** Set when a school created this label as a custom skill (scoped to that school).
2799
+ * Absent for platform-wide labels. */
2800
+ oId?: string,
2801
+ }
2802
+
2803
+ /** One scheduled skills survey in a group's annual programme. `sendMMDD` is "MM-DD".
2804
+ * Auto-provisioned into each new academic year by the daily cron (mirrors the
2805
+ * aspirations AspirationSurveySlot model). */
2806
+ export type SkillsSurveySlot = {
2807
+ sendMMDD: string,
2808
+ label?: string,
2809
+ students?: boolean,
2810
+ parents?: boolean,
2811
+ }
2812
+
2813
+ /**
2814
+ * A staff-defined student group mapping students to the skills they are served on
2815
+ * assessments. `filters` keys are student detail field names (from
2816
+ * InstituteData.studentsFields); values are the accepted values for that field. A
2817
+ * student matches a group when EVERY filtered field's value is one of the accepted
2818
+ * values (values within a field are OR, fields are AND). A student matching multiple
2819
+ * groups is served the UNION of their skills. A student matching no group falls back
2820
+ * to the institute's "All students" base set (skillsFramework/skills).
2821
+ * Each group carries its own annual survey schedule; the "All students" default
2822
+ * group's schedule lives on InstituteData.skillsSurveys.
2823
+ */
2824
+ export type SkillsMapping = {
2825
+ id: string,
2826
+ name?: string,
2827
+ filters: {[field: string]: string[]},
2828
+ skills: string[],
2829
+ surveys?: SkillsSurveySlot[],
2784
2830
  }
2785
2831
 
2786
2832
  /** Rater types for skills assessments (mirrors SkillAssessment.userType). */
@@ -2874,6 +2920,9 @@ export type SkillAssessmentRequest = {
2874
2920
  // documents stay valid — legacy requests without them surface as "Ungrouped".
2875
2921
  academicYear?: string, // e.g. "2024-25", derived from sendTime/creation
2876
2922
  yearGroup?: number, // the single year group this request targets
2923
+ // ── Auto-provisioned group surveys (set by the daily cron) ──
2924
+ groupId?: string, // SkillsMapping.id, or "all" for the All students group
2925
+ preResolved?: boolean, // per-student assessments created by the cron — onRequestCreated skips fan-out
2877
2926
  sendTime?: string,
2878
2927
  students: string // JSON stringify
2879
2928
  // {