Statement identity and versions
Store statements under statement_id and keep the latest version_id. Those two ids are what let you take corrections without creating duplicates.
- A re-extraction keeps the ids. If we extract a bill again, for example after improving how a provider's layout is read, each statement keeps its
statement_idand arrives as a new version. Statements are matched on their content (account, address, period, meters, services and total), not on their position in the bill. The new version replaces the one you hold. Every delivered version saysis_latest: true, so decide which is newer byextracted_at, compared as a time. - The same bill in a later request keeps the ids too. If you send a bill again in a new request, and it is not byte-for-byte the file you sent before, we recognize it by account number, statement date and amount due. Its statements keep their ids and arrive as new versions under the new
request_id. This never matches across clients. - A statement that can no longer be matched gets a new id, never a neighbor's. If a re-extraction drops a statement you were sent, you receive
statement.withdrawnfor it. - One request can produce several statements, and one statement can arrive under several requests.
request_idtells you which request delivered a version; it is not the statement's identity.
A minimal consumer:
on statement.available:
if version_id already stored: return 2xx # a repeat delivery
upsert statements[statement_id] = payload # unless the stored one has a later extracted_at
on statement.withdrawn:
mark statements[statement_id] withdrawn
Do not rely on arrival order: a delivery retried after an outage on your side can arrive after a newer one. Of two versions of one statement, keep the one with the later extracted_at, compared as a time. A statement.withdrawn is final: ignore any version of that statement_id that arrives after it.