placementt-core 1.400.1103 → 1.400.1104

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.
@@ -18,6 +18,13 @@
18
18
  * timeline it does not belong on. Being wrong here is worse than being silent: a
19
19
  * cycle stamped with the wrong year group shows up in another cohort's numbers.
20
20
  */
21
+ import type { WhereFilterOp } from "firebase/firestore";
22
+ /** One clause of a DataViewer selection, as it is stored. */
23
+ export type SelectionFilter = {
24
+ k: string;
25
+ e?: WhereFilterOp;
26
+ v: unknown;
27
+ };
21
28
  /** The field a year group is filtered on. `resolveSelectionStudents` builds its own
22
29
  * fallback selection with this key, so a year group is always a `details.year`
23
30
  * clause however the selection was made. */
@@ -30,3 +37,29 @@ export declare const YEAR_GROUP_SELECTION_FIELD = "details.year";
30
37
  * selection is not one year group.
31
38
  */
32
39
  export declare const yearGroupFromSelection: (selectionJson?: string) => string | undefined;
40
+ /** A selection that names one year group, and the clauses that cut it down. */
41
+ export type YearGroupSubset = {
42
+ /** The year group it sits under, the same tag yearGroupFromSelection gives. */
43
+ yearGroup: string;
44
+ /** Every clause other than the year itself, in the order staff applied them. */
45
+ narrowing: SelectionFilter[];
46
+ };
47
+ /**
48
+ * The part of a year group a selection describes, when it is only part of one.
49
+ *
50
+ * A task sent to "Year 10, pupil premium" is stamped Year 10, because the year is
51
+ * the one thing the filter says for certain, and the cycle belongs on Year 10's
52
+ * timeline. It is NOT Year 10 though, and a row that draws it beside a survey sent
53
+ * to the whole year invites somebody to read two response rates as comparable when
54
+ * they have different denominators.
55
+ *
56
+ * So this answers the second question the first one leaves open: is this all of the
57
+ * year group, or some of it? `undefined` covers both the selections that are no
58
+ * year group at all and the ones that are the whole of it; a result means some of
59
+ * it, and carries what narrowed it so a page can say so.
60
+ *
61
+ * @param {string} [selectionJson] JSON-serialised DataViewerSelectionBackend.
62
+ * @return {YearGroupSubset|undefined} the year group and what narrows it, or
63
+ * nothing when the selection is a whole year group or not a year group at all.
64
+ */
65
+ export declare const yearGroupSubsetFromSelection: (selectionJson?: string) => YearGroupSubset | undefined;
@@ -20,7 +20,7 @@
20
20
  * cycle stamped with the wrong year group shows up in another cohort's numbers.
21
21
  */
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
- exports.yearGroupFromSelection = exports.YEAR_GROUP_SELECTION_FIELD = void 0;
23
+ exports.yearGroupSubsetFromSelection = exports.yearGroupFromSelection = exports.YEAR_GROUP_SELECTION_FIELD = void 0;
24
24
  /** The field a year group is filtered on. `resolveSelectionStudents` builds its own
25
25
  * fallback selection with this key, so a year group is always a `details.year`
26
26
  * clause however the selection was made. */
@@ -72,4 +72,43 @@ const yearGroupFromSelection = (selectionJson) => {
72
72
  return tag || undefined;
73
73
  };
74
74
  exports.yearGroupFromSelection = yearGroupFromSelection;
75
+ /**
76
+ * The part of a year group a selection describes, when it is only part of one.
77
+ *
78
+ * A task sent to "Year 10, pupil premium" is stamped Year 10, because the year is
79
+ * the one thing the filter says for certain, and the cycle belongs on Year 10's
80
+ * timeline. It is NOT Year 10 though, and a row that draws it beside a survey sent
81
+ * to the whole year invites somebody to read two response rates as comparable when
82
+ * they have different denominators.
83
+ *
84
+ * So this answers the second question the first one leaves open: is this all of the
85
+ * year group, or some of it? `undefined` covers both the selections that are no
86
+ * year group at all and the ones that are the whole of it; a result means some of
87
+ * it, and carries what narrowed it so a page can say so.
88
+ *
89
+ * @param {string} [selectionJson] JSON-serialised DataViewerSelectionBackend.
90
+ * @return {YearGroupSubset|undefined} the year group and what narrows it, or
91
+ * nothing when the selection is a whole year group or not a year group at all.
92
+ */
93
+ const yearGroupSubsetFromSelection = (selectionJson) => {
94
+ const yearGroup = (0, exports.yearGroupFromSelection)(selectionJson);
95
+ // Not one year group, so there is no year group for it to be a subset OF. A
96
+ // hand-picked list of pupils is its own thing, not "some of Year 10".
97
+ if (!yearGroup)
98
+ return undefined;
99
+ let selection;
100
+ try {
101
+ // Safe to assume: yearGroupFromSelection only returns a tag for a string it
102
+ // parsed into an object with filters.
103
+ selection = JSON.parse(selectionJson);
104
+ }
105
+ catch {
106
+ return undefined;
107
+ }
108
+ if (!selection || Array.isArray(selection))
109
+ return undefined;
110
+ const narrowing = (selection.filters ?? []).filter((f) => f?.k !== exports.YEAR_GROUP_SELECTION_FIELD);
111
+ return narrowing.length ? { yearGroup, narrowing } : undefined;
112
+ };
113
+ exports.yearGroupSubsetFromSelection = yearGroupSubsetFromSelection;
75
114
  //# sourceMappingURL=selection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/surveys/selection.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAIH;;4CAE4C;AAC/B,QAAA,0BAA0B,GAAG,cAAc,CAAC;AAEzD,oFAAoF;AACpF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;AAE5C;;;;;;GAMG;AACI,MAAM,sBAAsB,GAAG,CAAC,aAAsB,EAAoB,EAAE;IAC/E,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IAErC,IAAI,SAAqC,CAAC;IAC1C,IAAI,CAAC;QACD,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAA+B,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,8EAA8E;IAC9E,yEAAyE;IACzE,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC9F,8EAA8E;IAC9E,2EAA2E;IAC3E,IAAI,SAAS,CAAC,WAAW,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACrD,gFAAgF;IAChF,kDAAkD;IAClD,IAAI,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,SAAS,CAAC;IAEtF,MAAM,WAAW,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,kCAA0B,CAAC,CAAC;IACjG,iFAAiF;IACjF,cAAc;IACd,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE/C,MAAM,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC;IAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,gFAAgF;IAChF,8EAA8E;IAC9E,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAEnD,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5B,OAAO,GAAG,IAAI,SAAS,CAAC;AAC5B,CAAC,CAAC;AAjCW,QAAA,sBAAsB,0BAiCjC"}
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/surveys/selection.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAQH;;4CAE4C;AAC/B,QAAA,0BAA0B,GAAG,cAAc,CAAC;AAEzD,oFAAoF;AACpF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;AAE5C;;;;;;GAMG;AACI,MAAM,sBAAsB,GAAG,CAAC,aAAsB,EAAoB,EAAE;IAC/E,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IAErC,IAAI,SAAqC,CAAC;IAC1C,IAAI,CAAC;QACD,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAA+B,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,8EAA8E;IAC9E,yEAAyE;IACzE,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC9F,8EAA8E;IAC9E,2EAA2E;IAC3E,IAAI,SAAS,CAAC,WAAW,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACrD,gFAAgF;IAChF,kDAAkD;IAClD,IAAI,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,SAAS,CAAC;IAEtF,MAAM,WAAW,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,kCAA0B,CAAC,CAAC;IACjG,iFAAiF;IACjF,cAAc;IACd,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE/C,MAAM,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC;IAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,gFAAgF;IAChF,8EAA8E;IAC9E,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAEnD,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5B,OAAO,GAAG,IAAI,SAAS,CAAC;AAC5B,CAAC,CAAC;AAjCW,QAAA,sBAAsB,0BAiCjC;AAUF;;;;;;;;;;;;;;;;;GAiBG;AACI,MAAM,4BAA4B,GAAG,CAAC,aAAsB,EAA6B,EAAE;IAC9F,MAAM,SAAS,GAAG,IAAA,8BAAsB,EAAC,aAAa,CAAC,CAAC;IACxD,4EAA4E;IAC5E,sEAAsE;IACtE,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAEjC,IAAI,SAAqC,CAAC;IAC1C,IAAI,CAAC;QACD,4EAA4E;QAC5E,sCAAsC;QACtC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,aAAuB,CAA+B,CAAC;IAClF,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IAE7D,MAAM,SAAS,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,kCAA0B,CAAC,CAAC;IAC/F,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAC,SAAS,EAAE,SAAS,EAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACjE,CAAC,CAAC;AAlBW,QAAA,4BAA4B,gCAkBvC"}
@@ -71,4 +71,57 @@ describe("yearGroupFromSelection", () => {
71
71
  expect((0, selection_1.yearGroupFromSelection)("\"9\"")).toBeUndefined();
72
72
  });
73
73
  });
74
+ describe("yearGroupSubsetFromSelection", () => {
75
+ const withFilters = (filters) => json({ allSelected: true, search: undefined, filters });
76
+ it("reports the year group and what narrows it", () => {
77
+ expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([
78
+ { k: "details.year", v: "9" },
79
+ { k: "details.pupilPremium", v: "Yes" },
80
+ ]))).toEqual({
81
+ yearGroup: "9",
82
+ narrowing: [{ k: "details.pupilPremium", v: "Yes" }],
83
+ });
84
+ });
85
+ it("keeps every narrowing clause, in the order staff applied them", () => {
86
+ expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([
87
+ { k: "details.pupilPremium", v: "Yes" },
88
+ { k: "details.year", v: "9" },
89
+ { k: "details.form", e: "in", v: ["9A", "9B"] },
90
+ ]))?.narrowing).toEqual([
91
+ { k: "details.pupilPremium", v: "Yes" },
92
+ { k: "details.form", e: "in", v: ["9A", "9B"] },
93
+ ]);
94
+ });
95
+ it("gives nothing for the whole year group", () => {
96
+ // The point of the type: a result means SOME of a year group, so a selection
97
+ // that is all of one has to be indistinguishable from one that is no year
98
+ // group at all.
99
+ expect((0, selection_1.yearGroupSubsetFromSelection)(yearSelection("9"))).toBeUndefined();
100
+ expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([{ k: "details.year", v: "9" }]))).toBeUndefined();
101
+ });
102
+ it("gives nothing when there is no single year group to be a subset of", () => {
103
+ // A hand-picked list of pupils is its own thing, not some of Year 9, even
104
+ // when every one of them is in Year 9.
105
+ expect((0, selection_1.yearGroupSubsetFromSelection)(json(["uid1", "uid2"]))).toBeUndefined();
106
+ expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([
107
+ { k: "details.year", v: "9" },
108
+ { k: "details.year", v: "10" },
109
+ ]))).toBeUndefined();
110
+ expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([{ k: "details.pupilPremium", v: "Yes" }])))
111
+ .toBeUndefined();
112
+ });
113
+ it("gives nothing for a search box, however else it is filtered", () => {
114
+ // yearGroupFromSelection already refuses these, and a subset of a selection
115
+ // we could not read in the first place is not something to draw a badge from.
116
+ expect((0, selection_1.yearGroupSubsetFromSelection)(json({
117
+ allSelected: true,
118
+ search: "smith",
119
+ filters: [{ k: "details.year", v: "9" }, { k: "details.pupilPremium", v: "Yes" }],
120
+ }))).toBeUndefined();
121
+ });
122
+ it("gives nothing for missing or unreadable JSON", () => {
123
+ expect((0, selection_1.yearGroupSubsetFromSelection)(undefined)).toBeUndefined();
124
+ expect((0, selection_1.yearGroupSubsetFromSelection)("{not json")).toBeUndefined();
125
+ });
126
+ });
74
127
  //# sourceMappingURL=selection.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"selection.test.js","sourceRoot":"","sources":["../../src/surveys/selection.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"selection.test.js","sourceRoot":"","sources":["../../src/surveys/selection.test.ts"],"names":[],"mappings":"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"}
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.1103",
5
+ "version": "1.400.1104",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
@@ -1,4 +1,4 @@
1
- import {yearGroupFromSelection} from "./selection";
1
+ import {yearGroupFromSelection, yearGroupSubsetFromSelection} from "./selection";
2
2
 
3
3
  const json = (value: unknown) => JSON.stringify(value);
4
4
 
@@ -83,3 +83,63 @@ describe("yearGroupFromSelection", () => {
83
83
  expect(yearGroupFromSelection("\"9\"")).toBeUndefined();
84
84
  });
85
85
  });
86
+
87
+ describe("yearGroupSubsetFromSelection", () => {
88
+ const withFilters = (filters: unknown[]) => json({allSelected: true, search: undefined, filters});
89
+
90
+ it("reports the year group and what narrows it", () => {
91
+ expect(yearGroupSubsetFromSelection(withFilters([
92
+ {k: "details.year", v: "9"},
93
+ {k: "details.pupilPremium", v: "Yes"},
94
+ ]))).toEqual({
95
+ yearGroup: "9",
96
+ narrowing: [{k: "details.pupilPremium", v: "Yes"}],
97
+ });
98
+ });
99
+
100
+ it("keeps every narrowing clause, in the order staff applied them", () => {
101
+ expect(yearGroupSubsetFromSelection(withFilters([
102
+ {k: "details.pupilPremium", v: "Yes"},
103
+ {k: "details.year", v: "9"},
104
+ {k: "details.form", e: "in", v: ["9A", "9B"]},
105
+ ]))?.narrowing).toEqual([
106
+ {k: "details.pupilPremium", v: "Yes"},
107
+ {k: "details.form", e: "in", v: ["9A", "9B"]},
108
+ ]);
109
+ });
110
+
111
+ it("gives nothing for the whole year group", () => {
112
+ // The point of the type: a result means SOME of a year group, so a selection
113
+ // that is all of one has to be indistinguishable from one that is no year
114
+ // group at all.
115
+ expect(yearGroupSubsetFromSelection(yearSelection("9"))).toBeUndefined();
116
+ expect(yearGroupSubsetFromSelection(withFilters([{k: "details.year", v: "9"}]))).toBeUndefined();
117
+ });
118
+
119
+ it("gives nothing when there is no single year group to be a subset of", () => {
120
+ // A hand-picked list of pupils is its own thing, not some of Year 9, even
121
+ // when every one of them is in Year 9.
122
+ expect(yearGroupSubsetFromSelection(json(["uid1", "uid2"]))).toBeUndefined();
123
+ expect(yearGroupSubsetFromSelection(withFilters([
124
+ {k: "details.year", v: "9"},
125
+ {k: "details.year", v: "10"},
126
+ ]))).toBeUndefined();
127
+ expect(yearGroupSubsetFromSelection(withFilters([{k: "details.pupilPremium", v: "Yes"}])))
128
+ .toBeUndefined();
129
+ });
130
+
131
+ it("gives nothing for a search box, however else it is filtered", () => {
132
+ // yearGroupFromSelection already refuses these, and a subset of a selection
133
+ // we could not read in the first place is not something to draw a badge from.
134
+ expect(yearGroupSubsetFromSelection(json({
135
+ allSelected: true,
136
+ search: "smith",
137
+ filters: [{k: "details.year", v: "9"}, {k: "details.pupilPremium", v: "Yes"}],
138
+ }))).toBeUndefined();
139
+ });
140
+
141
+ it("gives nothing for missing or unreadable JSON", () => {
142
+ expect(yearGroupSubsetFromSelection(undefined)).toBeUndefined();
143
+ expect(yearGroupSubsetFromSelection("{not json")).toBeUndefined();
144
+ });
145
+ });
@@ -19,8 +19,12 @@
19
19
  * cycle stamped with the wrong year group shows up in another cohort's numbers.
20
20
  */
21
21
 
22
+ import type {WhereFilterOp} from "firebase/firestore";
22
23
  import type {DataViewerSelectionBackend} from "../typeDefinitions";
23
24
 
25
+ /** One clause of a DataViewer selection, as it is stored. */
26
+ export type SelectionFilter = {k: string, e?: WhereFilterOp, v: unknown};
27
+
24
28
  /** The field a year group is filtered on. `resolveSelectionStudents` builds its own
25
29
  * fallback selection with this key, so a year group is always a `details.year`
26
30
  * clause however the selection was made. */
@@ -70,3 +74,49 @@ export const yearGroupFromSelection = (selectionJson?: string): string|undefined
70
74
  const tag = clause.v.trim();
71
75
  return tag || undefined;
72
76
  };
77
+
78
+ /** A selection that names one year group, and the clauses that cut it down. */
79
+ export type YearGroupSubset = {
80
+ /** The year group it sits under, the same tag yearGroupFromSelection gives. */
81
+ yearGroup: string,
82
+ /** Every clause other than the year itself, in the order staff applied them. */
83
+ narrowing: SelectionFilter[],
84
+ };
85
+
86
+ /**
87
+ * The part of a year group a selection describes, when it is only part of one.
88
+ *
89
+ * A task sent to "Year 10, pupil premium" is stamped Year 10, because the year is
90
+ * the one thing the filter says for certain, and the cycle belongs on Year 10's
91
+ * timeline. It is NOT Year 10 though, and a row that draws it beside a survey sent
92
+ * to the whole year invites somebody to read two response rates as comparable when
93
+ * they have different denominators.
94
+ *
95
+ * So this answers the second question the first one leaves open: is this all of the
96
+ * year group, or some of it? `undefined` covers both the selections that are no
97
+ * year group at all and the ones that are the whole of it; a result means some of
98
+ * it, and carries what narrowed it so a page can say so.
99
+ *
100
+ * @param {string} [selectionJson] JSON-serialised DataViewerSelectionBackend.
101
+ * @return {YearGroupSubset|undefined} the year group and what narrows it, or
102
+ * nothing when the selection is a whole year group or not a year group at all.
103
+ */
104
+ export const yearGroupSubsetFromSelection = (selectionJson?: string): YearGroupSubset|undefined => {
105
+ const yearGroup = yearGroupFromSelection(selectionJson);
106
+ // Not one year group, so there is no year group for it to be a subset OF. A
107
+ // hand-picked list of pupils is its own thing, not "some of Year 10".
108
+ if (!yearGroup) return undefined;
109
+
110
+ let selection: DataViewerSelectionBackend;
111
+ try {
112
+ // Safe to assume: yearGroupFromSelection only returns a tag for a string it
113
+ // parsed into an object with filters.
114
+ selection = JSON.parse(selectionJson as string) as DataViewerSelectionBackend;
115
+ } catch {
116
+ return undefined;
117
+ }
118
+ if (!selection || Array.isArray(selection)) return undefined;
119
+
120
+ const narrowing = (selection.filters ?? []).filter((f) => f?.k !== YEAR_GROUP_SELECTION_FIELD);
121
+ return narrowing.length ? {yearGroup, narrowing} : undefined;
122
+ };