Parse CSV Transactions
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.
What's in the codebase
transactions.py. Aparse_transactionsstub that raisesNotImplementedError, and amain()that calls it. Everything past that is yours to design.data/. Four exports from the payment processor. Start withdata/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.
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.