DataEngr

Companion note, Retail orders

Late-arriving data can revise an earlier day

A correction can arrive after an order has changed yesterday's total. See how the pipeline chooses the winning state, removes the old contribution and adds the corrected one.

What you will see

Fig. 01, before → after, late correction

One accepted update moves a reporting contribution

The current fact changes first; the daily groups are rebuilt from that winning fact in the same commit.

Before correction

Initial current fact

Order
o100, e1, version 1
Reporting group
2026-01-01, web, A$100.00

After correction

Winning current fact

Order
o100, e6, version 2
Reporting group
2026-01-02, web, A$120.00
Old group loses the contribution2026-01-01, web− A$100.00, o100 removed
New group gains the contribution2026-01-02, web+ A$120.00, o100 added

Values come from the sample data and a saved follow-up run. The diagram shows how the order moves; the runnable pipeline is in the flagship note.

Why a correction can change a past day

An order can arrive with the wrong business date, channel or amount. A later event arrives with a higher source version and the correct values. The daily report for the wrong day has already been read; the question is how to revise the order fact and the affected daily group without hiding the change.

This companion story uses the same synthetic retail-orders project as the flagship. The amount is integer AUD cents, dates are UTC, and the orders are not recognised accounting revenue. The pipeline that decides which day the corrected order belongs to is the one explained here.

Fig. 02, correction diff, daily martDerived from initial.json and followup.json

Correction e6 replaces order o100’s fact. The previous daily contribution is removed and the corrected contribution is added, in one atomic commit. This figure isolates that one correction; the full follow-up batch in the runner also accepts a new order, so its total is higher.

StateOrderDate (UTC)ChannelAmount
Beforeo1002026-01-01webA$100.00
Aftero1002026-01-02webA$120.00
BeforeDaily order value, all dates and channelsA$195.00
AfterDaily order value, all dates and channelsA$215.00

Event time and reporting date are different

Every event carries a source update timestamp that records when the source system wrote the row. Each event also carries a business order date that records which day the order belongs to in the report. The two values can disagree: an event written on 3 January can describe an order that belongs to 1 January, and a later correction can move that business date again.

  • The source update timestamp decides ordering between competing events for the same order.
  • The business order date decides which daily group the order contributes to.
  • An accepted correction can change the business order date, which moves the contribution between daily groups.

How the accepted implementation replaces an existing fact

The current order fact is one row per order. When a corrected event wins by source version, timestamp and stable event identifier, the order state and the order fact are replaced with the corrected values. There is no edit history per field; the order fact always reflects the winning complete state.

Because the order fact has been replaced, the daily groups that aggregate over the order fact change at the same time: the original date-channel group loses the previous contribution and the corrected date-channel group gains the corrected contribution. The two daily groups are rebuilt together inside one commit so the change is atomic.

Which daily result changes and why

Only the daily groups whose date or channel is affected change. Groups whose date and channel stay the same are rebuilt from the same set of contributing orders and therefore show the same values. The committed daily groups are an aggregate of the current order facts, so changing an order fact changes the aggregate.

The change is not a separate post-processing step. Rebuilding the daily groups is part of the same atomic commit that updates the order fact. If any quality check fails on the rebuilt groups, the commit is rolled back and the previous values remain visible.

How stale updates are handled

An event with a lower source version, an equal version with an earlier timestamp, or an equal version and timestamp with a lexicographically smaller event identifier cannot win against the committed order state. The pipeline records the event in the event ledger but does not change the order fact, the daily groups or the quality checks.

These stale or no-op events appear in the run summary as ignored events. They are not errors; they are the documented behaviour when a retry delivers an older correction after a newer correction has already been committed.

How conflicts are handled

If two events in the same batch or one event against the ledger carry the same event identifier with a different raw payload, the pipeline stops before any mutation. The batch is reported as a conflict and the committed state stays authoritative.

When two events share the same order identifier but have different event identifiers and the same source version, the deterministic winner tuple is source version, source update timestamp, then lexicographically greater event identifier. The runner surfaces this situation as a warning rather than a failure, because both payloads are valid; the pipeline simply picks the documented winner.

Why late-correction handling matters operationally

A daily report that cannot move a corrected order between days would mislead anyone reading the previous day. A correction that overwrites silently would make the audit trail unreadable. Recognising that a higher source version wins, rebuilding the affected daily groups atomically, and leaving stale events visible gives an operator a clear answer for every scenario.

For this synthetic project, late-correction handling is demonstrated inside one browser session against a small sample dataset. It does not prove production durable recovery, regulatory retention, accounting treatment or how this rule should be reconciled with downstream financial systems.

One meaningful design trade-off

The pipeline uses a strict tuple (source version, source update timestamp, then lexicographically greater event identifier) to choose the winning complete order state. The alternative would let the most recently processed event win regardless of the tuple, which would make the order fact depend on the order in which retries arrived.

Choosing the tuple keeps the answer reproducible across retries and across processes. The cost is that two competing events at the same tuple position with different payloads must be surfaced as a conflict or a warning rather than absorbed into a silent overwrite.

Evidence and limits of this explanation

The saved result below is shown only when its build, sample data, schema, SQL and runtime identities match this application. It is a captured browser run from a real pipeline, not a fabricated outcome. Its sample is the follow-up batch, which contains a higher-version event for an already-known order, a stale lower-version event for another order and an event that reuses an already-committed event identifier.

Late-correction numbers a reader can read directly off the saved result: the order o100 changed from the initial web order on 2026-01-01 to the corrected web order on 2026-01-02 with a new amount, the inserted count is the order o106 that appeared for the first time in this batch, and the ignored-event count of three includes the stale event that could not outrank the existing order state and the raw-identical repeat of an already-committed event.

The grain of the saved evidence is per-batch: input event rows reconcile to invalid rows, identical valid duplicates removed and unique valid events accepted. The current order facts are one row per order; the daily groups are aggregates by UTC date and channel. Event totals, fact rows and daily groups are separate grains and should not be added together. The example proves behaviour for these small synthetic inputs and this browser pipeline. It does not establish production scale, accounting treatment, durable recovery or regulatory retention. If the engine cannot start, this explanation and the bundled SQL remain available.

Evidence mode: saved browser run captured from build 821aed49a48d with sample file SHA-256 464d7430a9cf and SQL SHA-256 cd70dcad203d. New local runs from the flagship are labelled separately.

Saved browser run, follow-up batch

A captured result from the synthetic project

This is a saved result from a real browser run, captured . It is separate from any new local run below.

Input event rows
7
Quarantined rows
1
Identical valid repeats
1
Orders inserted
1
Orders updated
1
Stale/no-op events
3
Current order facts
5
Current order value
A$245.00

3 quality checks passed for this saved run.

Saved daily order value by UTC date and channel; amounts are AUD, stored in integer cents. Every row comes from build 821aed49 and sample file 464d7430.
Order date (UTC)VARCHARChannelVARCHAROrdersBIGINTOrder valueHUGEINT
2026-01-01store1A$25.00 (2,500 cents)
2026-01-02store2A$30.00 (3,000 cents)
2026-01-02web2A$190.00 (19,000 cents)
  • Passed: state fact reconciliation
  • Passed: daily mart reconciliation
  • Passed: nonnegative order amounts
Build identity, asset hashes and measured timings
Captured at
Identity schema version
2
Build identity
821aed49a48d31e2405d54a1f79325ff678736846762ad4efc63acf7ec878460
Sample file SHA-256
464d7430a9cf05b71e02ed1a83d178760ad05cb1a97d5a5b1e203f265562b7c7
Schema SHA-256
29db3589925d4d9265565c187378e8dffae68c8f6fe1f81cd46ef8f063542f9c
SQL SHA-256
cd70dcad203d30cea9c10e5510eef3282e247a20b8007109101630477badd991
Execution source SHA-256
f878c8f580ca044df92b6bf9899e9a6839d22920e9fced3733fb2fcdcd5a3ac7
Evidence trust source SHA-256
43cc24c47850b136bb5f029f49d249c485b0606b2a18d5940ddccd64dbb3876f
DuckDB-Wasm package
1.32.0
DuckDB SQL engine
v1.4.3
Worker initialization
0 ms
Sample file load
7 ms
SQL pipeline
101 ms
Total run
119 ms

Timings describe this captured browser run; they are not a benchmark promise.

The pipeline steps that decide which day wins

The architecture fragment below is the part of the flagship pipeline that decides which corrected order replaces the previous one and which daily groups change. The event identity gate prevents conflicting payloads from mutating state; the ledger and current state stages pick the winning complete order state; the order fact and the daily groups are rebuilt and checked together.

Select a stage to see its details. The diagram explains the pipeline and does not run it; the full explanation follows below.

  1. 01

    Late-correction event identity gate

    Reject the batch if a usable event identifier carries a different raw payload than what is already in the committed ledger or in the same batch. Late corrections must arrive with a fresh event identifier so the order of arrivals is preserved.

    Inputs
    Original event rows from the late batch; Committed event ledger with stored payload hashes
    Outputs
    Either a fatal conflict result that prevents mutation, or rows eligible for validation
    SQL queries
    get_event_ledger, insert_event_ledger
    Quality checks
    No quality check at this stage
  2. 02

    Late-correction ledger and current state

    Apply each unique accepted event to the event ledger and choose the winning complete order state for each affected order by source version, source update timestamp, then lexicographically greater event identifier. Stale and no-op events are recorded but do not change the order fact.

    Inputs
    Unique accepted events from this batch; Committed event ledger and order state
    Outputs
    Updated event ledger; Winning current state per order, with stale events identified
    SQL queries
    begin_transaction, insert_event_ledger, upsert_order_state, commit_transaction, rollback_transaction
    Quality checks
    No quality check at this stage
  3. 03

    Late-correction order fact

    Materialise the corrected winning complete order state as the current fact for the order. Replace the previous fact atomically; the previous daily contribution is removed at the same commit point.

    Inputs
    Winning current state per order from this batch
    Outputs
    Replaced current order fact; Insert, update and ignored event counts
    SQL queries
    upsert_order_fact, query_orders_fact
    Quality checks
    State-to-fact reconciliation
  4. 04

    Late-correction daily groups

    Rebuild the daily groups by UTC date and channel from the replaced order facts. The original date-channel group loses the previous contribution and the corrected date-channel group gains the new one.

    Inputs
    Replaced current order facts
    Outputs
    Updated daily groups that reflect the correction
    SQL queries
    create_daily_mart, query_daily_mart
    Quality checks
    Daily mart reconciliation

On a narrow screen, follow the complete ordered list above.