# The measured pair: one seam, two requirement changes, four numbers

Two designs of the same lot. `a/` puts the tariff in a switch inside `ParkingLot`. `b/` puts it
behind `PricingPolicy`. Both compile, and both pass `BaseTest.java` — the comparison is between two
working designs, not between good code and code nobody would write.

Then two requirement changes arrive. The first travels along `b/`'s seam. The second does not.

## Change one, in the interviewer's words

Taken from `corpus/parking-lot/curveballs/02-tiered-pricing/REQUIREMENT-CHANGE.md`:

> Finance signed off on a new tariff this morning and it is live from now on. The first hour is
> free, for every vehicle. The second and third started hours are charged at the standard rate:
> motorbike 500, car 1000, truck 2000 per started hour. From the fourth started hour onward it is
> the long-stay rate: motorbike 1500, car 3000, truck 6000.
>
> Worked example, so we agree on the arithmetic: a car staying three hours and one minute has
> started four hours. Free, then 1000, then 1000, then 3000. It pays 5000.

`CurveballTest.java` asserts that 5000, and it passes against `a-tiered/` and `b-tiered/`.

## Change two, in the interviewer's words

> Night shift changed. A vehicle that entered between 22:00 and 06:00 pays a flat 3000, whatever it
> is and however long it stayed. Daytime arrivals are unaffected.

`OvernightTest.java` asserts that, and it passes against `a-overnight/` and `b-overnight/`. Note what
it needs and `PricingPolicy` does not have: the *entry* time. Elapsed duration cannot answer "did
this vehicle arrive at half past eleven at night".

## The numbers

Run it yourself:

```
node lessons/C2/contrast/measure.mjs
```

Real output, from exactly these directories:

```
tiered pricing   (on B's axis)   a            -> a-tiered      diffLines  22  touched 1  new 0  [ParkingLot.java +19/-3]
tiered pricing   (on B's axis)   b            -> b-tiered      diffLines   2  touched 1  new 1  [ParkingLot.java +1/-1]
overnight rate   (off B's axis)  a            -> a-overnight   diffLines  14  touched 1  new 0  [ParkingLot.java +13/-1]
overnight rate   (off B's axis)  b            -> b-overnight   diffLines  27  touched 3  new 0  [FlatHourlyPricing.java +13/-1, ParkingLot.java +3/-2, PricingPolicy.java +6/-2]

size of a    2 file(s)  33 normalised lines
size of b    4 file(s)  66 normalised lines
```

| Change | Design A, no seam | Design B, seam on `(type, duration)` |
|---|---|---|
| tiered pricing — a new function of type and duration | **22** lines, 1 file | **2** lines, 1 file, 1 new file |
| overnight rate — needs a third input | **14** lines, 1 file | **27** lines, **3** files |

`measureChange` is the same function that scores D4 in a graded attempt, so these are the numbers
the grader would produce. New files cost nothing; edits to files that already existed are charged,
additions included. See the header of `server/lib/diff.mjs` for why.

## What each number means

**B's 2 is the corpus's own 2.** `corpus/parking-lot/curveballs/02-tiered-pricing/budget.json`
records `reference_diff: 2` for this requirement against the real reference solution. Its note:
*"adds TieredPricing.java (a new file, free) and rewrites the one line of ParkingLot's convenience
constructor that names the default tariff"*. One added, one removed. This contrast reproduces that
number independently, on smaller code.

**Those 2 lines are avoidable, and `worked/` avoids them.** Move the default out of `ParkingLot` and
into an entry point and the tariff change touches nothing that was already there.
`corpus/lru-cache/curveballs/01-least-frequently-used/budget.json` is the corpus's `reference_diff:
0` for exactly that reason. The new policy is a new file, and the line naming it lives in
`contract-delta/Entry.java`, which the note calls *"contract, not reference/src, so not measured at
all"*.

**A's 22 is not incompetence.** `a/ParkingLot.java` is shorter than `b/`, passes the same base
suite, and has an exhaustive switch so a new vehicle type is a compile error. It is what gets
written with twelve minutes left, and the 22 is the interest payment, not the principal.

**B's 27 against A's 14 is the finding this lesson exists for.** The overnight rule needs the entry
instant, which `PricingPolicy.feeMinor(VehicleType, Duration)` does not carry. Widening the
signature costs the interface, every implementation of it, and every call site — three files instead
of one. The seam did not merely fail to help. It roughly doubled the price of a change that moved
the other way.

## The alternatives, so the choice is a choice

**Could `b/` have absorbed the overnight rule by adding a class?** Only if the entry time were
already in the signature. A decorator implementing `PricingPolicy` and reading a clock does not
work: the rule depends on when the vehicle *arrived*, and only the lot knows that. Reading "now" at
exit time gives a different answer for a car that came in at 23:00 and left at 09:00.

**Could the signature take a `Stay` value object instead, so the next new input is free?** Yes, and
it is the trap `when-not.md` is about. That widens the seam for inputs nobody has asked for, which
is speculative generality with a `record` in front of it. It was not measured here, because it is a
redesign of the seam rather than an absorption of the change, and a number for it would compare two
different questions.

**Is the smallest diff always the right answer?** No, and the corpus has the counter-example.
`corpus/middleware-router/curveballs/02-method-aware-routing/reference-patch/PATCH.md` records an
earlier draft that *"measured smaller"* and was rejected, because it made `Router` reach past
`MatchPolicy` into one concrete implementation. That absorption cost **47** lines, the most expensive
in the corpus, and the author took the larger number on purpose. Use the diff to check a claim about
a seam. Do not use it to choose a design.

## What this pair does not show

The overnight change is 27 against 14 on 66 normalised lines of code. In a real problem the numbers
are larger. `corpus/trip-state-machine/curveballs/03-no-show-timeout` is the full-scale version: 28
lines, 19 of them in one class. That rule depends on a third input too, which is why
`reference-patch/PATCH.md` says no table of rows absorbs it at any price. Same shape, at the size of
a real problem.
