Skip to main content

Parse CSV Transactions

Premium

You're on the billing team, asked to build a tool for the finance department. It reads CSV exports from the payment processor and prints a summary report: how many transactions, how much customers paid, how much went back to them, what the processor took, and what's left.

Finance is replacing a spreadsheet with this, and they will check your numbers against it. The brief arrives in stages, and only the first one is revealed up front.

Starter code and solution
Python 3.11+ (standard library)
Download code

What's in the codebase

  • transactions.py. A parse_transactions stub that raises NotImplementedError, and a main() that calls it. Everything past that is yours to design.
  • data/. Four exports from the payment processor. Start with data/01_transactions.csv; the others arrive with the later stages.
  • tests/test_transactions.py. A few self-check tests covering the first stage only.

One ground rule: standard library only, no third-party packages. Python's built-in csv module is fair game and you should use it, but pandas is out. The restriction exists because pandas would collapse the first stage into three lines and skip every decision the problem is actually asking about.

The file format

Each row is one transaction, under a header row.

columnmeaning
transaction_idUnique ID for the transaction
created_atTimestamp
typecharge, refund, payout, or adjustment
amountInteger in cents. Money in is positive, money out is negative
currencyCurrency code
feeWhat the processor took, also in cents
statussucceeded, pending, or failed
customer_emailWho paid
descriptionFree text
source_idFor a refund, the transaction it came from

Your task

Read an export and write finance's summary report to stdout as CSV. Against the default file, that means exactly:

transactions,gross,refunded,fees,net 6,299.95,19.99,10.20,269.76

gross is what customers paid, refunded is what went back to them as a positive number, fees is the processor's cut, and net is what's left after subtracting the other two. The export counts in cents and finance reads dollars.

What to focus on

  • Money in integers. The columns are in cents for a reason. Converting to floats early is the single most common way this problem goes wrong, and the error is small enough that it survives the first stage and surfaces later as a report that's off by a penny.
  • Deciding what counts. The file contains four transaction types and three statuses. Which of those belong in gross, and whether a pending charge is money the customer paid, are questions the brief does not answer. State your assumption before you code it.
  • Structure that survives the next stage. Reading rows, coercing types, and applying finance's policy are three separable jobs. The later stages add validation on top of the same data, and a single function that does all three is painful to extend.

The later stages

The first summary is the setup. The stages that follow use the same tool against new exports.

Finance finds a customer who was refunded more than they ever paid, so you're asked to validate refunds against their charges. A refund can be issued in parts, so a single charge may have several against it and the total is what must not exceed the charge. Checking refunds one at a time passes a file that a correct check rejects.

Then the links themselves come under suspicion. source_id says which transaction a refund came from, and support sometimes points a corrected refund at the one it replaces, so those references can chain. You're asked to prove they never form a cycle. Two refunds naming the same charge is the most ordinary thing in payments and is not a cycle, which is the false positive a careless traversal produces.

The last export is simply dirty: a dollar amount in a column of cents, a blank amount, a reused ID. All of it comes down to one decision, which is whether the tool rejects the whole file or skips the bad row and reports it.

Using AI on this problem

Reading a CSV and summing columns is something an agent does perfectly, so the value is in the decisions around it. Ask it to enumerate what could be wrong with a payments export before you look at the data, then check its list against what's actually in the files.

Watch the cycle detection especially. Naive implementations report a cycle whenever a node is reachable by two paths, which flags exactly the legitimate case this problem plants first.

Leveling signals

Mid-levelProduces the correct summary and states assumptions when asked. Fixes the per-charge refund grouping once shown the case that breaks it. Models the refund links and sketches the traversal.
SeniorKeeps money in integers from the start and groups refunds by charge without being led there. Gets the exact-full-refund boundary right the first time. Detects cycles correctly, names the transactions involved rather than reporting that one exists, and tests each stage as it lands.
Staff+All of that, plus keeps reading, coercion, and policy as separable layers and can say why. Writes the traversal iteratively because exports get long. Treats the error report as something a support engineer will read. Names what changes at two gigabytes and across overlapping daily exports.