Skip to content

Commit c47c9a2

Browse files
committed
feat(gsul): exclude post-dated updates that have not yet taken effect
1 parent 9cd69ef commit c47c9a2

4 files changed

Lines changed: 131 additions & 15 deletions

File tree

packages/gsul/src/dynamoDBclient.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ export async function getItemsUpdatesForPrescription(
4040
itemId: String(singleUpdate.LineItemID),
4141
latestStatus: String(singleUpdate.Status),
4242
isTerminalState: String(singleUpdate.TerminalStatus) === "completed",
43-
lastUpdateDateTime: String(singleUpdate.LastModified)
43+
lastUpdateDateTime: String(singleUpdate.LastModified),
44+
...(singleUpdate.PostDatedLastModifiedSetAt && {
45+
postDatedLastModifiedSetAt: String(singleUpdate.PostDatedLastModifiedSetAt)
46+
})
4447
}))
4548
}
4649

packages/gsul/src/getStatusUpdates.ts

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const lambdaHandler = async (event: requestType): Promise<responseType> => {
2727
// this is an async map so it returns an array of promises
2828
const itemResults = event.prescriptions.map(async (prescription) => {
2929
const queryResult = await getItemsUpdatesForPrescription(prescription.prescriptionID, prescription.odsCode, logger)
30-
return buildResult(prescription, queryResult)
30+
return filterOutPostDatedUpdates(prescription, queryResult)
3131
})
3232

3333
// wait for all the promises to complete
@@ -40,21 +40,60 @@ const lambdaHandler = async (event: requestType): Promise<responseType> => {
4040
return response
4141
}
4242

43-
export const buildResult = (
43+
export const filterOutPostDatedUpdates = (
4444
inputPrescription: inputPrescriptionType,
45-
items: Array<itemType>
45+
items: Array<itemType>,
46+
currentTime: number = Date.now() // injectable for testing
4647
): outputPrescriptionType => {
47-
// get unique item ids with the latest update based on lastUpdateDateTime
48-
const uniqueItems: Array<itemType> = Object.values(
49-
items.reduce(function (r, e) {
50-
if (!r[e.itemId] || Date.parse(e.lastUpdateDateTime) > Date.parse(r[e.itemId].lastUpdateDateTime)) r[e.itemId] = e
51-
return r
52-
}, {})
53-
)
48+
49+
// filter out items with future lastUpdateDateTime
50+
const validTimeUpdates = items.filter(item => {
51+
const updateTime = Date.parse(item.lastUpdateDateTime)
52+
return updateTime <= currentTime
53+
})
54+
55+
// group by itemId and separate post-dated from regular updates
56+
const itemGroups: Record<string, {regular: itemType | null, postDated: itemType | null}> = {}
57+
58+
validTimeUpdates.forEach(item => {
59+
if (!itemGroups[item.itemId]) {
60+
itemGroups[item.itemId] = {regular: null, postDated: null}
61+
}
62+
const group = itemGroups[item.itemId]
63+
64+
if (item.postDatedLastModifiedSetAt) { // this is a post-dated update
65+
if (!group.postDated) {
66+
group.postDated = item
67+
} else {
68+
const existingTime = Date.parse(group.postDated.postDatedLastModifiedSetAt!)
69+
const newTime = Date.parse(item.postDatedLastModifiedSetAt)
70+
if (newTime > existingTime) {
71+
group.postDated = item
72+
}
73+
}
74+
} else { // this is a regular update
75+
if (!group.regular) {
76+
group.regular = item
77+
} else {
78+
const existingTime = Date.parse(group.regular.lastUpdateDateTime)
79+
const newTime = Date.parse(item.lastUpdateDateTime)
80+
if (newTime > existingTime) {
81+
group.regular = item
82+
}
83+
}
84+
}
85+
})
86+
87+
// flatten both regular and post-dated updates into single array
88+
const uniqueItems: Array<itemType> = []
89+
Object.values(itemGroups).forEach(group => {
90+
if (group.regular) uniqueItems.push(group.regular)
91+
if (group.postDated) uniqueItems.push(group.postDated)
92+
})
5493

5594
const result: outputPrescriptionType = {
5695
prescriptionID: inputPrescription.prescriptionID,
57-
onboarded: items.length > 0,
96+
onboarded: items.length > 0, // consider onboarded even if all updates were post-dated
5897
items: uniqueItems
5998
}
6099
return result

packages/gsul/src/schema/response.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const itemSchema = {
1515
},
1616
lastUpdateDateTime: {
1717
type: "string"
18+
},
19+
postDatedLastModifiedSetAt: {
20+
type: "string"
1821
}
1922
}
2023
} as const

packages/gsul/tests/testBuildResult.test.ts

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {buildResult} from "../src/getStatusUpdates"
1+
import {filterOutPostDatedUpdates} from "../src/getStatusUpdates"
22
import {inputPrescriptionType} from "../src/schema/request"
33
import {outputPrescriptionType, itemType} from "../src/schema/response"
44

@@ -83,7 +83,7 @@ const scenarios: Array<scenariosType> = [
8383
}
8484
},
8585
{
86-
scenarioDescription: "should return correct data for multiple items",
86+
scenarioDescription: "should return latest item for multiple updates for each of multiple statuses",
8787
inputPrescriptions: {
8888
prescriptionID: "abc",
8989
odsCode: "123"
@@ -132,11 +132,82 @@ const scenarios: Array<scenariosType> = [
132132
}
133133
]
134134
}
135+
},
136+
{
137+
scenarioDescription: "should exclude item when post-dated update hasn't matured",
138+
inputPrescriptions: {
139+
prescriptionID: "abc",
140+
odsCode: "123"
141+
},
142+
queryResults: [
143+
{
144+
itemId: "item_1",
145+
latestStatus: "Ready to collect",
146+
isTerminalState: false,
147+
lastUpdateDateTime: "2030-01-01T00:00:00Z", // Future, no fallback
148+
postDatedLastModifiedSetAt:"1972-01-01T00:00:00Z"
149+
}
150+
],
151+
expectedResult: {
152+
prescriptionID: "abc",
153+
onboarded: true,
154+
items: []
155+
}
156+
},
157+
{
158+
scenarioDescription: "should use latest post-dated update when multiple have matured",
159+
inputPrescriptions: {
160+
prescriptionID: "abc",
161+
odsCode: "123"
162+
},
163+
queryResults: [
164+
{
165+
itemId: "item_1",
166+
latestStatus: "With pharmacy",
167+
isTerminalState: false,
168+
lastUpdateDateTime: "1970-01-01T00:00:00Z"
169+
},
170+
{
171+
itemId: "item_1",
172+
latestStatus: "Ready to collect",
173+
isTerminalState: false,
174+
lastUpdateDateTime: "1971-01-01T00:00:00Z",
175+
postDatedLastModifiedSetAt: "1970-01-02T00:00:00Z"
176+
},
177+
{
178+
itemId: "item_1",
179+
latestStatus: "Ready to collect",
180+
isTerminalState: false,
181+
lastUpdateDateTime: "1972-01-01T00:00:00Z",
182+
postDatedLastModifiedSetAt: "1971-01-02T00:00:00Z"
183+
}
184+
],
185+
expectedResult: {
186+
prescriptionID: "abc",
187+
onboarded: true,
188+
items: [
189+
{
190+
itemId: "item_1",
191+
latestStatus: "With pharmacy",
192+
isTerminalState: false,
193+
lastUpdateDateTime: "1970-01-01T00:00:00Z"
194+
},
195+
{
196+
itemId: "item_1",
197+
latestStatus: "Ready to collect",
198+
isTerminalState: false,
199+
lastUpdateDateTime: "1972-01-01T00:00:00Z",
200+
postDatedLastModifiedSetAt: "1971-01-02T00:00:00Z"
201+
}
202+
]
203+
}
135204
}
136205
]
137206
describe("Unit tests for buildResults", () => {
138207
it.each<scenariosType>(scenarios)("$scenarioDescription", ({inputPrescriptions, queryResults, expectedResult}) => {
139-
const result = buildResult(inputPrescriptions, queryResults)
208+
// Use a fixed time of 2000-01-01 for tests (946684800000 ms since epoch)
209+
const fixedCurrentTime = new Date("2000-01-01T00:00:00Z").getTime()
210+
const result = filterOutPostDatedUpdates(inputPrescriptions, queryResults, fixedCurrentTime)
140211
expect(result).toMatchObject(expectedResult)
141212
})
142213
})

0 commit comments

Comments
 (0)