Skip to content

Commit 7ee04fa

Browse files
store current allocations
1 parent 49afa05 commit 7ee04fa

3 files changed

Lines changed: 127 additions & 36 deletions

File tree

internal/datastore/src/supplier-quotas-repository.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class SupplierQuotasRepository {
9595
async getDailyAllocation(
9696
groupId: string,
9797
date: string,
98-
): Promise<DailyAllocation> {
98+
): Promise<DailyAllocation | undefined> {
9999
const result = await this.ddbClient.send(
100100
new GetCommand({
101101
TableName: this.config.supplierQuotasTableName,
@@ -106,9 +106,7 @@ export class SupplierQuotasRepository {
106106
}),
107107
);
108108
if (!result.Item) {
109-
throw new Error(
110-
`No daily allocation found for volume group id ${groupId} and date ${date}`,
111-
);
109+
return undefined;
112110
}
113111
return $DailyAllocation.parse(result.Item);
114112
}
@@ -133,7 +131,8 @@ export class SupplierQuotasRepository {
133131
newAllocation: number,
134132
): Promise<void> {
135133
const dailyAllocation = await this.getDailyAllocation(groupId, date);
136-
const currentAllocation = dailyAllocation.allocations[supplierId] ?? 0;
134+
const allocations = dailyAllocation?.allocations ?? {};
135+
const currentAllocation = allocations[supplierId] ?? 0;
137136
const updatedAllocation = currentAllocation + newAllocation;
138137

139138
await this.ddbClient.send(

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

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import {
2323
getVariantDetails,
2424
getVolumeGroupDetails,
2525
} from "../services/supplier-config";
26-
import { calculateSupplierAllocatedFactor } from "../services/supplier-quotas";
26+
import {
27+
calculateSupplierAllocatedFactor,
28+
updateSupplierAllocation,
29+
} from "../services/supplier-quotas";
2730
import { Deps } from "../config/deps";
2831

2932
type SupplierSpec = {
@@ -32,6 +35,12 @@ type SupplierSpec = {
3235
priority: number;
3336
billingId: string;
3437
};
38+
39+
type SupplierDetails = {
40+
supplierSpec: SupplierSpec;
41+
volumeGroupId: string;
42+
};
43+
3544
type PreparedEvents = LetterRequestPreparedEventV2 | LetterRequestPreparedEvent;
3645

3746
// small envelope that must exist in all inputs
@@ -71,7 +80,10 @@ function validateType(event: unknown) {
7180
}
7281
}
7382

74-
async function getSupplierFromConfig(letterEvent: PreparedEvents, deps: Deps) {
83+
async function getSupplierFromConfig(
84+
letterEvent: PreparedEvents,
85+
deps: Deps,
86+
): Promise<SupplierDetails | undefined> {
7587
try {
7688
const variantDetails: LetterVariant = await getVariantDetails(
7789
letterEvent.data.letterVariantId,
@@ -119,7 +131,7 @@ async function getSupplierFromConfig(letterEvent: PreparedEvents, deps: Deps) {
119131

120132
let supplierAllocationsForPack: SupplierAllocation[] = [];
121133
let supplierFactors: { supplierId: string; factor: number }[] = [];
122-
134+
let selectedSupplierId = "unknown"; // Default to first supplier if no allocations or factors can be calculated
123135
if (suppliersForPack && suppliersForPack.length > 0) {
124136
supplierAllocationsForPack = supplierAllocations.filter((alloc) =>
125137
suppliersForPack.some((supplier) => supplier.id === alloc.supplier),
@@ -137,6 +149,16 @@ async function getSupplierFromConfig(letterEvent: PreparedEvents, deps: Deps) {
137149
console.log("Supplier factors calculated for allocation", {
138150
supplierFactors,
139151
});
152+
153+
// Get the supplierid with the lowest factor
154+
selectedSupplierId = supplierFactors[0].supplierId;
155+
let lowestFactor = supplierFactors[0].factor;
156+
for (const supplierFactor of supplierFactors) {
157+
if (supplierFactor.factor < lowestFactor) {
158+
lowestFactor = supplierFactor.factor;
159+
selectedSupplierId = supplierFactor.supplierId;
160+
}
161+
}
140162
}
141163

142164
deps.logger.info({
@@ -152,16 +174,26 @@ async function getSupplierFromConfig(letterEvent: PreparedEvents, deps: Deps) {
152174
suppliersForPack,
153175
supplierAllocationsForPack,
154176
supplierFactors,
177+
selectedSupplierId,
155178
});
156179

157-
return allocatedSuppliers;
180+
const supplierDetails: SupplierDetails = {
181+
supplierSpec: {
182+
supplierId: selectedSupplierId,
183+
specId: preferredPack.id,
184+
priority: 0,
185+
billingId: preferredPack.billingId,
186+
},
187+
volumeGroupId: volumeGroupDetails.id,
188+
};
189+
return supplierDetails;
158190
} catch (error) {
159191
deps.logger.error({
160192
description: "Error fetching supplier from config",
161193
err: error,
162194
variantId: letterEvent.data.letterVariantId,
163195
});
164-
return [];
196+
return undefined;
165197
}
166198
}
167199

@@ -170,6 +202,7 @@ function getSupplier(letterEvent: PreparedEvents, deps: Deps): SupplierSpec {
170202
}
171203

172204
type AllocationMetrics = Map<string, Map<string, number>>;
205+
type VolumeGroupAllocation = Map<string, Record<string, number>>;
173206

174207
function incrementMetric(
175208
map: AllocationMetrics,
@@ -203,12 +236,40 @@ function emitMetrics(
203236
}
204237
}
205238

239+
function incrementAllocation(
240+
map: VolumeGroupAllocation,
241+
volumeGroupId: string,
242+
supplierId: string,
243+
allocation: number,
244+
) {
245+
const groupAllocations = map.get(volumeGroupId) ?? {};
246+
groupAllocations[supplierId] =
247+
(groupAllocations[supplierId] ?? 0) + allocation;
248+
map.set(volumeGroupId, groupAllocations);
249+
}
250+
251+
async function saveAllocations(
252+
deps: Deps,
253+
volumeGroupAllocations: VolumeGroupAllocation,
254+
) {
255+
for (const [volumeGroupId, allocations] of volumeGroupAllocations) {
256+
for (const [supplierId, allocation] of Object.entries(allocations)) {
257+
await updateSupplierAllocation(
258+
volumeGroupId,
259+
supplierId,
260+
allocation,
261+
deps,
262+
);
263+
}
264+
}
265+
}
266+
206267
export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
207268
return async (event: SQSEvent) => {
208269
const batchItemFailures: SQSBatchItemFailure[] = [];
209270
const perAllocationSuccess: AllocationMetrics = new Map();
210271
const perAllocationFailure: AllocationMetrics = new Map();
211-
272+
const volumeGroupAllocations: VolumeGroupAllocation = new Map(); // Map of volume group id to supplier allocations for that group, used to track the allocations calculated in this batch for emitting metrics and updating the quotas after processing the batch
212273
// Initialise the supplier quotas.
213274

214275
const tasks = event.Records.map(async (record) => {
@@ -225,7 +286,17 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
225286
validateType(letterEvent);
226287

227288
const supplierSpec = getSupplier(letterEvent as PreparedEvents, deps);
228-
await getSupplierFromConfig(letterEvent as PreparedEvents, deps);
289+
const supplierDetails = await getSupplierFromConfig(
290+
letterEvent as PreparedEvents,
291+
deps,
292+
);
293+
294+
incrementAllocation(
295+
volumeGroupAllocations,
296+
supplierDetails?.volumeGroupId ?? "unknown",
297+
supplierSpec.supplierId,
298+
1,
299+
);
229300

230301
supplier = supplierSpec.supplierId;
231302
priority = String(supplierSpec.priority);
@@ -276,6 +347,7 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
276347

277348
emitMetrics(perAllocationSuccess, MetricStatus.Success, deps);
278349
emitMetrics(perAllocationFailure, MetricStatus.Failure, deps);
350+
await saveAllocations(deps, volumeGroupAllocations);
279351
return { batchItemFailures };
280352
};
281353
}

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

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { OverallAllocation } from "@internal/datastore";
1+
import { DailyAllocation, OverallAllocation } from "@internal/datastore";
22
import { SupplierAllocation } from "@nhsdigital/nhs-notify-event-schemas-supplier-config";
33
import { Deps } from "../config/deps";
44

@@ -33,34 +33,54 @@ export async function calculateSupplierAllocatedFactor(
3333
});
3434
}
3535

36-
export async function updateSupplierQuota(
37-
groupId: string,
36+
// function to either update or create a new overall allocation and daily allocation for a given supplier, volume group and allocation amount
37+
// if the overall allocation for the volume group does not exist, it will be created with the new allocation for the supplier and 0 for the other suppliers
38+
39+
export async function updateSupplierAllocation(
40+
volumeGroupId: string,
3841
supplierId: string,
3942
newAllocation: number,
4043
deps: Deps,
4144
): Promise<void> {
4245
const overallAllocation =
43-
await deps.supplierQuotasRepo.getOverallAllocation(groupId);
44-
45-
const updatedAllocations = overallAllocation
46-
? {
47-
...overallAllocation.allocations,
46+
await deps.supplierQuotasRepo.getOverallAllocation(volumeGroupId);
47+
if (overallAllocation) {
48+
await deps.supplierQuotasRepo.updateOverallAllocation(
49+
volumeGroupId,
50+
supplierId,
51+
newAllocation,
52+
);
53+
} else {
54+
const newOverallAllocation: OverallAllocation = {
55+
id: volumeGroupId,
56+
volumeGroup: volumeGroupId,
57+
allocations: {
4858
[supplierId]: newAllocation,
49-
}
50-
: {
59+
},
60+
};
61+
await deps.supplierQuotasRepo.putOverallAllocation(newOverallAllocation);
62+
}
63+
const dailyAllocationDate = new Date().toISOString().split("T")[0]; // Get current date in YYYY-MM-DD format
64+
const dailyAllocation = await deps.supplierQuotasRepo.getDailyAllocation(
65+
volumeGroupId,
66+
dailyAllocationDate,
67+
);
68+
if (dailyAllocation) {
69+
await deps.supplierQuotasRepo.updateDailyAllocation(
70+
volumeGroupId,
71+
dailyAllocationDate,
72+
supplierId,
73+
newAllocation,
74+
);
75+
} else {
76+
const newDailyAllocation: DailyAllocation = {
77+
id: `${volumeGroupId}#DATE#${dailyAllocationDate}`,
78+
date: dailyAllocationDate,
79+
volumeGroup: volumeGroupId,
80+
allocations: {
5181
[supplierId]: newAllocation,
52-
};
53-
54-
const updatedOverallAllocation: OverallAllocation = overallAllocation
55-
? {
56-
...overallAllocation,
57-
allocations: updatedAllocations,
58-
}
59-
: {
60-
id: groupId,
61-
volumeGroup: groupId,
62-
allocations: updatedAllocations,
63-
};
64-
65-
await deps.supplierQuotasRepo.putOverallAllocation(updatedOverallAllocation);
82+
},
83+
};
84+
await deps.supplierQuotasRepo.putDailyAllocation(newDailyAllocation);
85+
}
6686
}

0 commit comments

Comments
 (0)