Skip to content

Commit a63520c

Browse files
CCM-13371 - Determine Eligible packs
1 parent a657f27 commit a63520c

File tree

4 files changed

+219
-6
lines changed

4 files changed

+219
-6
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodejs 22.22.0

lambdas/supplier-allocator/src/handler/allocate-handler.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import z from "zod";
1414
import { Unit } from "aws-embedded-metrics";
1515
import { MetricEntry, MetricStatus, buildEMFObject } from "@internal/helpers";
1616
import {
17+
filterPacksForLetter,
1718
getPackSpecification,
1819
getPreferredSupplierPacks,
1920
getSupplierAllocationsForVolumeGroup,
@@ -95,12 +96,14 @@ async function getSupplierFromConfig(letterEvent: PreparedEvents, deps: Deps) {
9596
deps,
9697
);
9798

99+
const eligiblePacks: string[] = await filterPacksForLetter(
100+
letterEvent,
101+
variantDetails.packSpecificationIds,
102+
deps,
103+
);
104+
98105
const preferredSupplierPacks: SupplierPack[] =
99-
await getPreferredSupplierPacks(
100-
variantDetails.packSpecificationIds,
101-
allocatedSuppliers,
102-
deps,
103-
);
106+
await getPreferredSupplierPacks(eligiblePacks, allocatedSuppliers, deps);
104107

105108
const preferredPack: PackSpecification = await getPackSpecification(
106109
preferredSupplierPacks[0].packSpecificationId,
@@ -119,7 +122,8 @@ async function getSupplierFromConfig(letterEvent: PreparedEvents, deps: Deps) {
119122
volumeGroupId: volumeGroupDetails.id,
120123
supplierAllocationIds: supplierAllocations.map((a) => a.id),
121124
allocatedSuppliers,
122-
eligiblePacks: variantDetails.packSpecificationIds,
125+
variantPacks: variantDetails.packSpecificationIds,
126+
eligiblePacks,
123127
preferredSupplierPacks,
124128
preferredPack,
125129
suppliersForPack,

lambdas/supplier-allocator/src/services/__tests__/supplier-config.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
filterPacksForLetter,
23
getPackSpecification,
34
getPreferredSupplierPacks,
45
getSupplierAllocationsForVolumeGroup,
@@ -479,4 +480,129 @@ describe("supplier-config service", () => {
479480
]);
480481
});
481482
});
483+
484+
describe("filterPacksForLetter", () => {
485+
it("returns eligible packs for letter", async () => {
486+
const deps = makeDeps();
487+
deps.supplierConfigRepo.getPackSpecification = jest
488+
.fn()
489+
.mockResolvedValue({
490+
id: "spec1",
491+
constraints: {
492+
sheets: { operator: "LESS_THAN", value: 2 },
493+
},
494+
} as any);
495+
const letterEvent = {
496+
data: {
497+
pageCount: 1,
498+
},
499+
} as any;
500+
501+
const result = await filterPacksForLetter(letterEvent, ["spec1"], deps);
502+
503+
expect(result).toEqual(["spec1"]);
504+
});
505+
it("throws when no eligible packs found for letter", async () => {
506+
const deps = makeDeps();
507+
deps.supplierConfigRepo.getPackSpecification = jest
508+
.fn()
509+
.mockResolvedValue({
510+
id: "spec1",
511+
constraints: {
512+
sheets: { operator: "LESS_THAN", value: 2 },
513+
},
514+
} as any);
515+
const letterEvent = {
516+
data: {
517+
pageCount: 3,
518+
},
519+
} as any;
520+
521+
await expect(
522+
filterPacksForLetter(letterEvent, ["spec1"], deps),
523+
).rejects.toThrow(
524+
"No eligible pack specifications found for letter variant id undefined and pack specification ids spec1",
525+
);
526+
expect(deps.logger.error).toHaveBeenCalledWith(
527+
expect.objectContaining({
528+
description: "No eligible pack specifications found for letter",
529+
letterVariantId: undefined,
530+
packSpecificationIds: ["spec1"],
531+
}),
532+
);
533+
});
534+
it("returns eligible packs for all constraint types", async () => {
535+
const deps = makeDeps();
536+
const constraints: { operator: string; value: number }[] = [
537+
{ operator: "EQUALS", value: 2 },
538+
{ operator: "NOT_EQUALS", value: 1 },
539+
{ operator: "GREATER_THAN", value: 1 },
540+
{ operator: "LESS_THAN", value: 3 },
541+
{ operator: "GREATER_THAN_OR_EQUAL", value: 2 },
542+
{ operator: "LESS_THAN_OR_EQUAL", value: 2 },
543+
];
544+
const letterEvent = {
545+
data: {
546+
pageCount: 2,
547+
},
548+
} as any;
549+
550+
for (const constraint of constraints) {
551+
deps.supplierConfigRepo.getPackSpecification = jest
552+
.fn()
553+
.mockResolvedValue({
554+
id: "spec1",
555+
constraints: {
556+
sheets: {
557+
operator: constraint.operator,
558+
value: constraint.value,
559+
},
560+
},
561+
} as any);
562+
563+
const result = await filterPacksForLetter(letterEvent, ["spec1"], deps);
564+
565+
expect(result).toEqual(["spec1"]);
566+
}
567+
});
568+
it("throws an error for unsupported operator", async () => {
569+
const deps = makeDeps();
570+
deps.supplierConfigRepo.getPackSpecification = jest
571+
.fn()
572+
.mockResolvedValue({
573+
id: "spec1",
574+
constraints: {
575+
sheets: { operator: "UNSUPPORTED_OP", value: 2 },
576+
},
577+
} as any);
578+
const letterEvent = {
579+
data: {
580+
pageCount: 2,
581+
},
582+
} as any;
583+
584+
await expect(
585+
filterPacksForLetter(letterEvent, ["spec1"], deps),
586+
).rejects.toThrow(
587+
"Unsupported operator UNSUPPORTED_OP in pack specification constraints",
588+
);
589+
});
590+
it("returns all packs when no constraints defined", async () => {
591+
const deps = makeDeps();
592+
deps.supplierConfigRepo.getPackSpecification = jest
593+
.fn()
594+
.mockResolvedValue({
595+
id: "spec1",
596+
} as any);
597+
const letterEvent = {
598+
data: {
599+
pageCount: 5,
600+
},
601+
} as any;
602+
603+
const result = await filterPacksForLetter(letterEvent, ["spec1"], deps);
604+
605+
expect(result).toEqual(["spec1"]);
606+
});
607+
});
482608
});

lambdas/supplier-allocator/src/services/supplier-config.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ import {
66
SupplierPack,
77
VolumeGroup,
88
} from "@nhsdigital/nhs-notify-event-schemas-supplier-config";
9+
import { LetterRequestPreparedEventV2 } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering";
10+
import { LetterRequestPreparedEvent } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering-v1";
11+
912
import { Deps } from "../config/deps";
1013

14+
type PreparedEvents = LetterRequestPreparedEventV2 | LetterRequestPreparedEvent;
15+
1116
export async function getVariantDetails(
1217
variantId: string,
1318
deps: Deps,
@@ -188,3 +193,80 @@ export async function getSuppliersWithValidPack(
188193

189194
return suppliersWithValidPack;
190195
}
196+
197+
function evaluateContraint(
198+
actualValue: number,
199+
constraintValue: number,
200+
operator: string,
201+
): boolean {
202+
console.log(
203+
`Evaluating constraint: actualValue ${actualValue}, constraintValue ${constraintValue}, operator ${operator}`,
204+
);
205+
switch (operator) {
206+
case "EQUALS": {
207+
return actualValue === constraintValue;
208+
}
209+
case "NOT_EQUALS": {
210+
return actualValue !== constraintValue;
211+
}
212+
case "GREATER_THAN": {
213+
return actualValue > constraintValue;
214+
}
215+
case "LESS_THAN": {
216+
return actualValue < constraintValue;
217+
}
218+
case "GREATER_THAN_OR_EQUAL": {
219+
return actualValue >= constraintValue;
220+
}
221+
case "LESS_THAN_OR_EQUAL": {
222+
return actualValue <= constraintValue;
223+
}
224+
default: {
225+
throw new Error(
226+
`Unsupported operator ${operator} in pack specification constraints`,
227+
);
228+
}
229+
}
230+
}
231+
232+
// This function is used to filter the pack specifications for a letter based on the letter data pages and pack specification constraints sheets
233+
234+
export async function filterPacksForLetter(
235+
letterEvent: PreparedEvents,
236+
packSpecificationIds: string[],
237+
deps: Deps,
238+
): Promise<string[]> {
239+
const filteredPackIds: string[] = [];
240+
for (const packSpecId of packSpecificationIds) {
241+
const packSpec =
242+
await deps.supplierConfigRepo.getPackSpecification(packSpecId);
243+
if (
244+
!packSpec.constraints ||
245+
!packSpec.constraints.sheets ||
246+
!packSpec.constraints.sheets.value ||
247+
!packSpec.constraints.sheets.operator
248+
) {
249+
filteredPackIds.push(packSpecId);
250+
} else {
251+
const isValid = evaluateContraint(
252+
letterEvent.data.pageCount,
253+
packSpec.constraints.sheets.value,
254+
packSpec.constraints.sheets.operator,
255+
);
256+
if (isValid) {
257+
filteredPackIds.push(packSpecId);
258+
}
259+
}
260+
}
261+
if (filteredPackIds.length === 0) {
262+
deps.logger.error({
263+
description: "No eligible pack specifications found for letter",
264+
letterVariantId: letterEvent.data.letterVariantId,
265+
packSpecificationIds,
266+
});
267+
throw new Error(
268+
`No eligible pack specifications found for letter variant id ${letterEvent.data.letterVariantId} and pack specification ids ${packSpecificationIds.join(", ")}`,
269+
);
270+
}
271+
return filteredPackIds;
272+
}

0 commit comments

Comments
 (0)