An idempotency key store
Implement the store that lets each invoice through once, expires keys on a clock duplicates never reset, and refuses a changed amount under a live id.
On this page
At-least-once delivery means any invoice webhook may arrive twice, so the automation needs a memory of what it has already sent. The store is a dozen lines; the two rules it keeps, a clock that duplicates never reset and a first payload that nothing overwrites, are the whole exercise.
The situation
The 40-person wholesaler this module uses in place of the agency, from why we write these five by hand, supplies 900 cafés and issues invoices in its accounting system, which announces each one by webhook, at least once. The automation emails every invoice to the café and records the send. Last week 1,240 deliveries covered 1,180 invoices; 60 were duplicates, and two reached clients twice, because nothing remembered the first copy.
The sender retries a delivery it believes failed for up to 48 hours, so a duplicate can arrive two days late. Now and then an invoice is re-issued under the same id with a corrected amount; that must never silently overwrite the first, because the café may already have paid it. Keys expire after 72 hours, the 48-hour retry span plus a day of margin. At 1,180 invoices a week that is about 1,180 × 72 ÷ 168 ≈ 506 live keys; duplicates add none. The tests use a TTL of 60 seconds to keep the numbers short; production passes 259,200.
The question
Implement the store so the cases below hold, with each key's clock started at first sight and never refreshed.
def solve(events, ttl):
"""events: [(t, key, payload)], t non-decreasing, in seconds; ttl in seconds.
A key is live while t - first_seen < ttl. An absent key is processed and stored;
the same payload under a live key is skipped and does not refresh its clock; a
different payload under a live key is refused: neither processed nor stored, so
the first payload stands. Returns the payloads that were processed, in order."""
TESTS = [
(([(0, "inv-1", "A"), (5, "inv-1", "A"), (9, "inv-2", "B")], 60), ["A", "B"]),
(([(0, "inv-1", "A"), (30, "inv-1", "A"), (65, "inv-1", "A")], 60), ["A", "A"]),
(([(0, "inv-1", "A"), (60, "inv-1", "A")], 60), ["A", "A"]),
(([(0, "inv-1", "A"), (1, "inv-1", "A2")], 60), ["A"]),
(([], 60), []),
# added: a refused correction leaves the first payload, so the original's retry is still a duplicate
(([(0, "inv-1", "A"), (10, "inv-1", "A2"), (20, "inv-1", "A")], 60), ["A"]),
# added: the production clock, a retry 48 hours late inside a 72-hour TTL
(([(0, "inv-7", "4200.00"), (172800, "inv-7", "4200.00")], 259200), ["4200.00"]),
]
A strong answer · Rubric · Where answers go wrong · Solution
Write your answer first, then open