Skip to content
Reliable Data Engineering
Practice problem medium batchreconciliationsoxdata-modelingidempotency
Practise with timer, notes and rubric

Design an Auditable Financial Reporting Pipeline

Problem

Finance needs daily and monthly revenue reporting (gross, refunds, net, by product, country, currency) built from the order system, payment provider files and the refund service. Numbers must match the general ledger, be reproducible months later (“why did March revenue change?”) and satisfy auditors (SOX).


Clarifying questions

QuestionAssumed answer
Volume?5 M orders/day, 30 currencies
Sources?Orders DB (CDC), payment provider settlement files (daily CSV via SFTP), refunds service events, FX rates API
Close process?Month closes on business day 3; after close, numbers are frozen; corrections go to the next period
Precision?Exact to the cent in original and reporting currency
Audit?Every reported number traceable to source records; changes require approval

1. Architecture

flowchart LR
    ODB[(Orders DB)] -->|CDC| BR[(Bronze)]
    PSP[PSP settlement files] -->|SFTP → landing,<br/>checksum + manifest| BR
    REF[Refund events] --> BR
    FX[FX rates API] --> BR
    BR --> SIL[Silver: typed, deduped,<br/>immutable facts]
    SIL --> REC[Reconciliation jobs<br/>orders ↔ payments ↔ refunds]
    REC --> EXC[(Exceptions queue<br/>unmatched, mismatched amounts)]
    SIL --> LED[(gold.revenue_ledger<br/>append-only journal entries)]
    LED --> REP[(gold.revenue_daily / monthly<br/>by product, country, currency)]
    REP --> WAP{WAP checks +<br/>GL tie-out}
    WAP -->|pass| PUB[Published reports<br/>finance BI]
    WAP -->|fail| HOLD[Hold + alert finance data team]
    CLOSE[Period close: snapshot + lock] --> REP

2. Deep dives

2.1 Model revenue as an append-only ledger

Instead of updating “order revenue” in place, record journal entries: every business event produces immutable rows.

entry_idorder_ideventamount_minorcurrencyfx_rateamount_eur_minoreffective_dateposted_period
e1o42capture10000USD0.9292002026-03-302026-03
e2o42refund-2500USD0.93-23252026-04-022026-04
e3o42correction-100USD0.92-922026-03-302026-04 (March closed)

2.2 Idempotency and reproducibility

2.3 Three-way reconciliation

flowchart LR
    O[Orders captured] <-->|order_id, amount| P[PSP settlements]
    P <-->|payout batch| B[Bank statements]
    O <-->|refund_id| R[Refunds]
    O --> M{Match?}
    P --> M
    M -->|exact| OK[Reconciled]
    M -->|timing difference| T[Expected: settles T+2]
    M -->|amount / missing| X[Exception queue → finance ops]

Match rates and ageing of open exceptions are KPIs; reports show reconciled vs unreconciled amounts.

2.4 SOX controls in the data platform

2.5 Quality gates before publishing

Totals tie to the GL (tolerance 0); no duplicate entry ids; every order with capture has a ledger entry; FX rates present for all currencies/dates; day-over-day anomaly checks. Failures block publishing.

3. Trade-offs

DecisionChoiceAlternative
ModellingAppend-only ledgerMutable order facts (simpler, not auditable)
Late correctionsPost to current open periodRestate closed periods (breaks audit)
FXStored rate per entryRecompute with latest rates (non-reproducible)
ProcessingBatch (daily)Streaming (no requirement; harder to control)

4. What separates a senior answer

5. Follow-up questions

Finance asks why February revenue changed between two report runs.

If February was still open: diff the two report versions (Delta versions/snapshots), trace the delta to new ledger entries (late refunds, corrections) by posted_at. If February was closed, it shouldn’t change, and if it did, that’s a control failure: investigate who/what wrote to a locked period and add a guard (writes to closed periods rejected by the pipeline).

How do you handle partial refunds and multi-currency orders?

Each refund is its own entry with its own amount, currency and FX rate at refund time (per policy), linked to the original order; net revenue = captures + refunds (negative). Multi-currency orders split into lines per currency. Reporting currency conversions use the stored rates; FX gains/losses are a finance concept that may need separate entries.


Self-assessment rubric