# Reference patch — 03-multi-currency-expenses

The answer key for how the reference absorbs this change, and the thing that makes `budget.json`
reproducible. **Never shipped to the candidate.**

1. **Overlay** `../contract-delta/SplitwiseApi.java` (a full revised interface, now including the
   `addExpense(String, ExpenseRequest, ConversionRate)` overload) and
   `../contract-delta/ConversionRate.java` (new) onto `contract/`. Contract files are given, so
   nothing there is charged.
2. **Add** `CurrencyConversion.java` (in this directory) to `reference/src/`. New file, so
   `measureChange` charges nothing for it — this is where the conversion and the rounding
   reconciliation live.
3. **Edit** `reference/src/SplitwiseSystem.java`: one new 15-line override, nothing else.

`edits.json` beside this file is the exact edit in machine-readable form, so the measurement can
be re-run: baseline is `reference/src/*.java`, current is the same files with the
`reference-patch` `*.java` added and `edits.json`'s replacement applied, fed to
`measureChange(baseline, current)`.

Measured: `reference_diff = 15` (15 added, 0 removed, one file touched), so
`diff_budget = max(3, 15 + ceil(15/2)) = 23`.

Both the curveball suite (5 tests) and the base suite (29 tests) are green after the patch — 34
tests, no regression.

## This one cost 30 lines at first — here is the fix, not the excuse

The first attempt at this curveball measured **30 charged lines**, over the 20-line signal this
authoring process treats as proof the reference has the wrong seam, not proof the curveball is
inherently expensive. The cause: `addExpense(String, ExpenseRequest)` had all of its validation —
member checks, the empty/duplicate participant check, the positive-amount check — written inline
in one method, so the new foreign-currency overload had nowhere to get that validation from except
copying it, and the diff counted that whole copy as new lines in an old file.

**The fix was to `reference/src/SplitwiseSystem.java` itself**, not to this curveball's budget:
`addExpense`'s validation was pulled out into a private `validateAndResolve(Group, ExpenseRequest)`
that does everything up to and including asking `SplitEngine` to resolve the split, and the plain
`addExpense` now just calls it. That refactor is committed to the base reference — it is not part
of this patch, and the base suite (29 tests) is still green after it, because behaviour did not
change, only where it lives.

With that seam in place, this curveball's only remaining edit is the new overload itself, and it
is *entirely* delegation: look the group up, call `validateAndResolve` (same validation, same
order, same exceptions, just in the request's own currency), convert what came back, store it.
Measured at 15 lines — under the budget the first attempt would have needed, and under the 20-line
signal that would have meant trying again.

**The general lesson, not specific to currency:** a second overload of an existing operation is
exactly the shape of requirement that exposes whether a method's validation was ever really
separable from its "do the thing" step. If it was not — if extracting it takes more than a few
minutes — that is worth knowing about the design before this curveball, or the next one shaped
like it, arrives.

## Where the requirement landed, and where it did not

`SplitCalculator` never learns that currency exists. `CurrencyConversion` converts *splits*, not
requests — see its own class doc for why: every split type's output is the same shape (amounts
that sum to a total), so converting that output needs one rule, where converting `EXACT`'s input
shares and `EQUAL`'s or `PERCENTAGE`'s (which have no money-shaped shares at all) would have needed
two. `Group` and `Ledger` do not know a foreign currency was ever involved — by the time either of
them sees the expense, it is already in the group's own currency, which is exactly what "nothing
about the foreign currency is remembered afterwards" in the contract means in code.

**What a candidate is most likely to get wrong here** is converting before validating — checking
whether the converted numbers add up, rather than the numbers the user actually typed. That passes
every case where the rate happens not to introduce rounding, and fails the moment it does, for a
reason that has nothing to do with what the user got wrong.
