placementt-core 1.400.1063 → 1.400.1065

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.
@@ -1929,6 +1929,15 @@ export type ScheduledVisit = {
1929
1929
  * a new target type never needs a lookup table. */
1930
1930
  targetCollection: "placements" | "users";
1931
1931
  targetId: string;
1932
+ /** The target's name, copied in at write time by whoever creates the check.
1933
+ *
1934
+ * Denormalised on purpose: a list of checks is a list of people, and reading the
1935
+ * target doc per row costs one read per pupil every time a staff member opens a
1936
+ * cohort's checks — a two-hundred-wide read for a name that never changes. Written
1937
+ * by every generator and by the ad-hoc dialog; absent on checks written before
1938
+ * this existed, so every reader falls back rather than showing a blank. */
1939
+ targetForename?: string;
1940
+ targetSurname?: string;
1932
1941
  date: string;
1933
1942
  checkType: "call" | "in-person" | "video" | "email" | "other";
1934
1943
  createdByUid: string;
@@ -1949,8 +1958,23 @@ export type ScheduledVisit = {
1949
1958
  /** Snapshotted off `forms` when assigned, so editing the form later never
1950
1959
  * rewrites the questions on checks already answered. */
1951
1960
  formSchema?: CustomFormSchema;
1952
- /** Raised by the assignee on completion when something needs escalating. */
1961
+ /** LEGACY. Escalation flags now live on the check's private subdocument see
1962
+ * ScheduledCheckPrivate. Still declared because docs written before the split
1963
+ * carry it and the backfill has to read it; nothing writes it any more.
1964
+ *
1965
+ * It moved because Firestore rules are document-level: a guidance check is read
1966
+ * by the pupil it is about, and a pupil learning that staff flagged a concern
1967
+ * about them, with no context, is the harm the flag exists to avoid. Filtering
1968
+ * it out in the component hides it visually and nothing else. */
1953
1969
  concern?: boolean;
1970
+ /** Denormalised "a pupil may read this check".
1971
+ *
1972
+ * True only on careersGuidance checks. It exists because the rule that lets a
1973
+ * pupil read their own 1:1s cannot tell a guidance check from a leaver check
1974
+ * without it — and a blanket "read your own checks" rule would hand a SEND
1975
+ * leaver the pastoral notes on their own transition calls. The client query
1976
+ * carries the same filter, because Firestore requires it to. */
1977
+ studentVisible?: boolean;
1954
1978
  /** The slot's booking window, on checks generated from a window-mode guidance
1955
1979
  * slot. Absent on fixed-date and ad-hoc checks, which are booked from birth.
1956
1980
  *
@@ -1987,6 +2011,31 @@ export type ScheduledVisit = {
1987
2011
  /** Stamped when a check passes the grace period unanswered. */
1988
2012
  missedAt?: string;
1989
2013
  };
2014
+ /** The subcollection and document id both private subdocuments use. One name, in
2015
+ * core, so the web client and the backend cannot address different documents.
2016
+ *
2017
+ * `<parent>/{id}/private/staff` — staff-only fields split out of a document a pupil
2018
+ * is allowed to read. Firestore has no field-level read, so the only way to withhold
2019
+ * a field from a reader who may have the document is to put it in another one. */
2020
+ export declare const PRIVATE_SUBCOLLECTION = "private";
2021
+ export declare const PRIVATE_STAFF_DOC = "staff";
2022
+ /** Staff-only fields belonging to a scheduledChecks doc. See PRIVATE_SUBCOLLECTION. */
2023
+ export type ScheduledCheckPrivate = {
2024
+ /** Raised by the assignee on completion when something needs escalating. */
2025
+ concern?: boolean;
2026
+ /** The series the parent check belongs to, copied in so a cycle's concern count
2027
+ * is one collection-group query rather than a read per completed check. */
2028
+ seriesId?: string;
2029
+ oId: string;
2030
+ };
2031
+ /** Staff-only fields belonging to a careersRecord doc. See PRIVATE_SUBCOLLECTION. */
2032
+ export type CareersRecordPrivate = {
2033
+ /** Never shown to the pupil. The safeguarding-adjacent context a member of staff
2034
+ * writes about a meeting, kept in its own document so the entry the pupil reads
2035
+ * cannot carry it. */
2036
+ staffNotes?: string;
2037
+ oId: string;
2038
+ };
1990
2039
  /**
1991
2040
  * One dated 1:1 in a school's careers programme — "Year 11 get an options
1992
2041
  * conversation in October and a personal guidance meeting in February".
@@ -2012,6 +2061,14 @@ export type GuidanceSlot = SurveySlot & {
2012
2061
  /** Window mode only: how long the window runs from the slot's resolved date.
2013
2062
  * Absent = up to the next slot. */
2014
2063
  windowDays?: number;
2064
+ /** Whether the PUPIL is emailed before their booked meeting, overriding the
2065
+ * school-wide GuidanceConfig.remindPupils for this slot. Per slot because the
2066
+ * answer genuinely differs: a booked personal guidance meeting is an appointment
2067
+ * the pupil is expected to turn up to, and a whole-cohort assembly is not.
2068
+ *
2069
+ * Absent falls back to the school's setting, which is itself off by default —
2070
+ * see guidanceRemindsPupil. */
2071
+ remindPupil?: boolean;
2015
2072
  };
2016
2073
  /**
2017
2074
  * A named cohort inside the guidance programme, with its own slots — "pupil premium
@@ -2055,6 +2112,11 @@ export type GuidanceConfig = {
2055
2112
  };
2056
2113
  /** Named filtered cohorts whose slots supersede the year group's — see GuidanceGroup. */
2057
2114
  groups?: GuidanceGroup[];
2115
+ /** Whether pupils are emailed before their booked 1:1s, school-wide. Off unless
2116
+ * a careers leader turns it on: emailing a cohort of children is a decision for
2117
+ * the school, not something that should arrive in a deploy. Individual slots may
2118
+ * override it — see GuidanceSlot.remindPupil. */
2119
+ remindPupils?: boolean;
2058
2120
  setupCompletedAt?: string;
2059
2121
  setupBy?: string;
2060
2122
  updatedAt?: string;
@@ -3869,7 +3931,13 @@ export type CareersRecordEntry = {
3869
3931
  durationMins?: number;
3870
3932
  /** Shared with the pupil. BM3 expects pupils to be able to access their record. */
3871
3933
  summary: string;
3872
- /** Never shown to the pupil. Kept separate so the summary can be surfaced safely. */
3934
+ /** LEGACY. Staff notes now live on the entry's private subdocument see
3935
+ * CareersRecordPrivate. Still declared because entries written before the split
3936
+ * carry it, the backfill has to read it, and the Compass exports assert it never
3937
+ * leaves the platform.
3938
+ *
3939
+ * It moved because the pupil reads this document. Keeping the notes out of the
3940
+ * component was never enough: the whole doc reaches the browser. */
3873
3941
  staffNotes?: string;
3874
3942
  /** "Any subsequent agreed decisions" — named in the benchmark, so evidence in its own right. */
3875
3943
  actions?: CareersRecordAction[];
@@ -3903,6 +3971,32 @@ export declare const CAREERS_RECORD_MODE_LABELS: Record<CareersRecordEntry["mode
3903
3971
  * @return {CareersRecordEntry["mode"]} the record's mode.
3904
3972
  */
3905
3973
  export declare function checkTypeToRecordMode(checkType: ScheduledVisit["checkType"]): CareersRecordEntry["mode"];
3974
+ /**
3975
+ * Whether the pupil is emailed before a 1:1 from this slot.
3976
+ *
3977
+ * The slot has the final say and the school's setting is the default, so a school can
3978
+ * turn the whole thing on and still leave the assembly silent. Both absent means no:
3979
+ * pupil email is opt-in, because the reminders this feature is built on were written
3980
+ * for staff and a school running the programme has not thereby agreed to email its
3981
+ * children.
3982
+ *
3983
+ * @param {Pick<GuidanceSlot, "remindPupil">} [slot] the slot the check came from.
3984
+ * @param {Pick<GuidanceConfig, "remindPupils">} [config] the school's guidance setup.
3985
+ * @return {boolean} whether to email the pupil.
3986
+ */
3987
+ export declare function guidanceRemindsPupil(slot?: Pick<GuidanceSlot, "remindPupil">, config?: Pick<GuidanceConfig, "remindPupils">): boolean;
3988
+ /**
3989
+ * Who a check is about, off the check itself.
3990
+ *
3991
+ * The name is denormalised onto the check precisely so a list of them needs no read
3992
+ * per row — see ScheduledVisit.targetForename. Returns an empty string for a check
3993
+ * written before that field existed, which is the caller's cue to fall back to
3994
+ * whatever roster it already has rather than print a uid at a member of staff.
3995
+ *
3996
+ * @param {Pick<ScheduledVisit, "targetForename"|"targetSurname">} check the check.
3997
+ * @return {string} their name, or "" when the check does not carry one.
3998
+ */
3999
+ export declare function checkTargetName(check: Pick<ScheduledVisit, "targetForename" | "targetSurname">): string;
3906
4000
  export type SuggestedFixRepo = "placementt-web" | "placementt-backend" | "placementt-core";
3907
4001
  export type SuggestedFix = {
3908
4002
  isCodeChange: boolean;
@@ -1,7 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CAREERS_RECORD_MODE_LABELS = exports.CAREERS_RECORD_TYPE_LABELS = void 0;
3
+ exports.CAREERS_RECORD_MODE_LABELS = exports.CAREERS_RECORD_TYPE_LABELS = exports.PRIVATE_STAFF_DOC = exports.PRIVATE_SUBCOLLECTION = void 0;
4
4
  exports.checkTypeToRecordMode = checkTypeToRecordMode;
5
+ exports.guidanceRemindsPupil = guidanceRemindsPupil;
6
+ exports.checkTargetName = checkTargetName;
7
+ /** The subcollection and document id both private subdocuments use. One name, in
8
+ * core, so the web client and the backend cannot address different documents.
9
+ *
10
+ * `<parent>/{id}/private/staff` — staff-only fields split out of a document a pupil
11
+ * is allowed to read. Firestore has no field-level read, so the only way to withhold
12
+ * a field from a reader who may have the document is to put it in another one. */
13
+ exports.PRIVATE_SUBCOLLECTION = "private";
14
+ exports.PRIVATE_STAFF_DOC = "staff";
5
15
  /** Staff-facing labels for each entry type. In core because the web form, the setup
6
16
  * wizard and the backend CSV export all name them and must not drift. */
7
17
  exports.CAREERS_RECORD_TYPE_LABELS = {
@@ -43,4 +53,34 @@ function checkTypeToRecordMode(checkType) {
43
53
  default: return "inPerson";
44
54
  }
45
55
  }
56
+ /**
57
+ * Whether the pupil is emailed before a 1:1 from this slot.
58
+ *
59
+ * The slot has the final say and the school's setting is the default, so a school can
60
+ * turn the whole thing on and still leave the assembly silent. Both absent means no:
61
+ * pupil email is opt-in, because the reminders this feature is built on were written
62
+ * for staff and a school running the programme has not thereby agreed to email its
63
+ * children.
64
+ *
65
+ * @param {Pick<GuidanceSlot, "remindPupil">} [slot] the slot the check came from.
66
+ * @param {Pick<GuidanceConfig, "remindPupils">} [config] the school's guidance setup.
67
+ * @return {boolean} whether to email the pupil.
68
+ */
69
+ function guidanceRemindsPupil(slot, config) {
70
+ return slot?.remindPupil ?? config?.remindPupils ?? false;
71
+ }
72
+ /**
73
+ * Who a check is about, off the check itself.
74
+ *
75
+ * The name is denormalised onto the check precisely so a list of them needs no read
76
+ * per row — see ScheduledVisit.targetForename. Returns an empty string for a check
77
+ * written before that field existed, which is the caller's cue to fall back to
78
+ * whatever roster it already has rather than print a uid at a member of staff.
79
+ *
80
+ * @param {Pick<ScheduledVisit, "targetForename"|"targetSurname">} check the check.
81
+ * @return {string} their name, or "" when the check does not carry one.
82
+ */
83
+ function checkTargetName(check) {
84
+ return `${check.targetForename ?? ""} ${check.targetSurname ?? ""}`.trim();
85
+ }
46
86
  //# sourceMappingURL=typeDefinitions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;AA66HA,sDASC;AA1CD;yEACyE;AAC5D,QAAA,0BAA0B,GAA2C;IAC9E,gBAAgB,EAAE,2BAA2B;IAC7C,mBAAmB,EAAE,sBAAsB;IAC3C,cAAc,EAAE,iBAAiB;IACjC,kBAAkB,EAAE,qBAAqB;IACzC,UAAU,EAAE,oCAAoC;IAChD,aAAa,EAAE,yBAAyB;IACxC,aAAa,EAAE,wBAAwB;IACvC,KAAK,EAAE,OAAO;CACjB,CAAC;AAEW,QAAA,0BAA0B,GAA+C;IAClF,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,SAAgB,qBAAqB,CAAC,SAAsC;IACxE,QAAQ,SAAS,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC;QAC5B,KAAK,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC;QAC7B,KAAK,WAAW,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,OAAO,CAAC;QACb,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC;IAC3B,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;AAs/HA,sDASC;AAeD,oDAKC;AAaD,0CAEC;AA5kED;;;;;kFAKkF;AACrE,QAAA,qBAAqB,GAAG,SAAS,CAAC;AAClC,QAAA,iBAAiB,GAAG,OAAO,CAAC;AAw/DzC;yEACyE;AAC5D,QAAA,0BAA0B,GAA2C;IAC9E,gBAAgB,EAAE,2BAA2B;IAC7C,mBAAmB,EAAE,sBAAsB;IAC3C,cAAc,EAAE,iBAAiB;IACjC,kBAAkB,EAAE,qBAAqB;IACzC,UAAU,EAAE,oCAAoC;IAChD,aAAa,EAAE,yBAAyB;IACxC,aAAa,EAAE,wBAAwB;IACvC,KAAK,EAAE,OAAO;CACjB,CAAC;AAEW,QAAA,0BAA0B,GAA+C;IAClF,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,SAAgB,qBAAqB,CAAC,SAAsC;IACxE,QAAQ,SAAS,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC;QAC5B,KAAK,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC;QAC7B,KAAK,WAAW,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,OAAO,CAAC;QACb,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC;IAC3B,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,oBAAoB,CAChC,IAAwC,EACxC,MAA6C;IAE7C,OAAO,IAAI,EAAE,WAAW,IAAI,MAAM,EAAE,YAAY,IAAI,KAAK,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAAC,KAA6D;IACzF,OAAO,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;AAC/E,CAAC"}
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.1063",
5
+ "version": "1.400.1065",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
@@ -1918,6 +1918,15 @@ export type ScheduledVisit = {
1918
1918
  * a new target type never needs a lookup table. */
1919
1919
  targetCollection: "placements"|"users",
1920
1920
  targetId: string,
1921
+ /** The target's name, copied in at write time by whoever creates the check.
1922
+ *
1923
+ * Denormalised on purpose: a list of checks is a list of people, and reading the
1924
+ * target doc per row costs one read per pupil every time a staff member opens a
1925
+ * cohort's checks — a two-hundred-wide read for a name that never changes. Written
1926
+ * by every generator and by the ad-hoc dialog; absent on checks written before
1927
+ * this existed, so every reader falls back rather than showing a blank. */
1928
+ targetForename?: string,
1929
+ targetSurname?: string,
1921
1930
  date: string, // yyyy-mm-dd
1922
1931
  checkType: "call"|"in-person"|"video"|"email"|"other",
1923
1932
  createdByUid: string,
@@ -1937,9 +1946,25 @@ export type ScheduledVisit = {
1937
1946
  /** Snapshotted off `forms` when assigned, so editing the form later never
1938
1947
  * rewrites the questions on checks already answered. */
1939
1948
  formSchema?: CustomFormSchema,
1940
- /** Raised by the assignee on completion when something needs escalating. */
1949
+ /** LEGACY. Escalation flags now live on the check's private subdocument see
1950
+ * ScheduledCheckPrivate. Still declared because docs written before the split
1951
+ * carry it and the backfill has to read it; nothing writes it any more.
1952
+ *
1953
+ * It moved because Firestore rules are document-level: a guidance check is read
1954
+ * by the pupil it is about, and a pupil learning that staff flagged a concern
1955
+ * about them, with no context, is the harm the flag exists to avoid. Filtering
1956
+ * it out in the component hides it visually and nothing else. */
1941
1957
  concern?: boolean,
1942
1958
 
1959
+ /** Denormalised "a pupil may read this check".
1960
+ *
1961
+ * True only on careersGuidance checks. It exists because the rule that lets a
1962
+ * pupil read their own 1:1s cannot tell a guidance check from a leaver check
1963
+ * without it — and a blanket "read your own checks" rule would hand a SEND
1964
+ * leaver the pastoral notes on their own transition calls. The client query
1965
+ * carries the same filter, because Firestore requires it to. */
1966
+ studentVisible?: boolean,
1967
+
1943
1968
  // ── Booking (D9) ──
1944
1969
  /** The slot's booking window, on checks generated from a window-mode guidance
1945
1970
  * slot. Absent on fixed-date and ad-hoc checks, which are booked from birth.
@@ -1979,6 +2004,34 @@ export type ScheduledVisit = {
1979
2004
  missedAt?: string,
1980
2005
  }
1981
2006
 
2007
+ /** The subcollection and document id both private subdocuments use. One name, in
2008
+ * core, so the web client and the backend cannot address different documents.
2009
+ *
2010
+ * `<parent>/{id}/private/staff` — staff-only fields split out of a document a pupil
2011
+ * is allowed to read. Firestore has no field-level read, so the only way to withhold
2012
+ * a field from a reader who may have the document is to put it in another one. */
2013
+ export const PRIVATE_SUBCOLLECTION = "private";
2014
+ export const PRIVATE_STAFF_DOC = "staff";
2015
+
2016
+ /** Staff-only fields belonging to a scheduledChecks doc. See PRIVATE_SUBCOLLECTION. */
2017
+ export type ScheduledCheckPrivate = {
2018
+ /** Raised by the assignee on completion when something needs escalating. */
2019
+ concern?: boolean,
2020
+ /** The series the parent check belongs to, copied in so a cycle's concern count
2021
+ * is one collection-group query rather than a read per completed check. */
2022
+ seriesId?: string,
2023
+ oId: string,
2024
+ };
2025
+
2026
+ /** Staff-only fields belonging to a careersRecord doc. See PRIVATE_SUBCOLLECTION. */
2027
+ export type CareersRecordPrivate = {
2028
+ /** Never shown to the pupil. The safeguarding-adjacent context a member of staff
2029
+ * writes about a meeting, kept in its own document so the entry the pupil reads
2030
+ * cannot carry it. */
2031
+ staffNotes?: string,
2032
+ oId: string,
2033
+ };
2034
+
1982
2035
  /**
1983
2036
  * One dated 1:1 in a school's careers programme — "Year 11 get an options
1984
2037
  * conversation in October and a personal guidance meeting in February".
@@ -2005,6 +2058,15 @@ export type GuidanceSlot = SurveySlot & {
2005
2058
  /** Window mode only: how long the window runs from the slot's resolved date.
2006
2059
  * Absent = up to the next slot. */
2007
2060
  windowDays?: number,
2061
+
2062
+ /** Whether the PUPIL is emailed before their booked meeting, overriding the
2063
+ * school-wide GuidanceConfig.remindPupils for this slot. Per slot because the
2064
+ * answer genuinely differs: a booked personal guidance meeting is an appointment
2065
+ * the pupil is expected to turn up to, and a whole-cohort assembly is not.
2066
+ *
2067
+ * Absent falls back to the school's setting, which is itself off by default —
2068
+ * see guidanceRemindsPupil. */
2069
+ remindPupil?: boolean,
2008
2070
  };
2009
2071
 
2010
2072
  /**
@@ -2046,6 +2108,11 @@ export type GuidanceConfig = {
2046
2108
  slotsByYearGroup?: {[yearGroup: string]: GuidanceSlot[]},
2047
2109
  /** Named filtered cohorts whose slots supersede the year group's — see GuidanceGroup. */
2048
2110
  groups?: GuidanceGroup[],
2111
+ /** Whether pupils are emailed before their booked 1:1s, school-wide. Off unless
2112
+ * a careers leader turns it on: emailing a cohort of children is a decision for
2113
+ * the school, not something that should arrive in a deploy. Individual slots may
2114
+ * override it — see GuidanceSlot.remindPupil. */
2115
+ remindPupils?: boolean,
2049
2116
  setupCompletedAt?: string, // ISO — stamped server-side on first save
2050
2117
  setupBy?: string, // staff uid — stamped server-side
2051
2118
  updatedAt?: string,
@@ -3958,7 +4025,13 @@ export type CareersRecordEntry = {
3958
4025
  /** Shared with the pupil. BM3 expects pupils to be able to access their record. */
3959
4026
  summary: string,
3960
4027
 
3961
- /** Never shown to the pupil. Kept separate so the summary can be surfaced safely. */
4028
+ /** LEGACY. Staff notes now live on the entry's private subdocument see
4029
+ * CareersRecordPrivate. Still declared because entries written before the split
4030
+ * carry it, the backfill has to read it, and the Compass exports assert it never
4031
+ * leaves the platform.
4032
+ *
4033
+ * It moved because the pupil reads this document. Keeping the notes out of the
4034
+ * component was never enough: the whole doc reaches the browser. */
3962
4035
  staffNotes?: string,
3963
4036
 
3964
4037
  /** "Any subsequent agreed decisions" — named in the benchmark, so evidence in its own right. */
@@ -4022,6 +4095,41 @@ export function checkTypeToRecordMode(checkType: ScheduledVisit["checkType"]): C
4022
4095
  }
4023
4096
  }
4024
4097
 
4098
+ /**
4099
+ * Whether the pupil is emailed before a 1:1 from this slot.
4100
+ *
4101
+ * The slot has the final say and the school's setting is the default, so a school can
4102
+ * turn the whole thing on and still leave the assembly silent. Both absent means no:
4103
+ * pupil email is opt-in, because the reminders this feature is built on were written
4104
+ * for staff and a school running the programme has not thereby agreed to email its
4105
+ * children.
4106
+ *
4107
+ * @param {Pick<GuidanceSlot, "remindPupil">} [slot] the slot the check came from.
4108
+ * @param {Pick<GuidanceConfig, "remindPupils">} [config] the school's guidance setup.
4109
+ * @return {boolean} whether to email the pupil.
4110
+ */
4111
+ export function guidanceRemindsPupil(
4112
+ slot?: Pick<GuidanceSlot, "remindPupil">,
4113
+ config?: Pick<GuidanceConfig, "remindPupils">,
4114
+ ): boolean {
4115
+ return slot?.remindPupil ?? config?.remindPupils ?? false;
4116
+ }
4117
+
4118
+ /**
4119
+ * Who a check is about, off the check itself.
4120
+ *
4121
+ * The name is denormalised onto the check precisely so a list of them needs no read
4122
+ * per row — see ScheduledVisit.targetForename. Returns an empty string for a check
4123
+ * written before that field existed, which is the caller's cue to fall back to
4124
+ * whatever roster it already has rather than print a uid at a member of staff.
4125
+ *
4126
+ * @param {Pick<ScheduledVisit, "targetForename"|"targetSurname">} check the check.
4127
+ * @return {string} their name, or "" when the check does not carry one.
4128
+ */
4129
+ export function checkTargetName(check: Pick<ScheduledVisit, "targetForename"|"targetSurname">): string {
4130
+ return `${check.targetForename ?? ""} ${check.targetSurname ?? ""}`.trim();
4131
+ }
4132
+
4025
4133
  export type SuggestedFixRepo = "placementt-web"|"placementt-backend"|"placementt-core";
4026
4134
 
4027
4135
  export type SuggestedFix = {