# Cost Explorer — decision log

The Phase 5 answer key. Every entry is a question an interviewer actually asks ("why is that a
separate class?") and the answer that earns the mark: **what change the seam anticipates, and what
the change would cost without it.**

Eleven files, none over ~90 lines including javadoc and most under 50. `CostExplorer` holds the
*sequence* of an operation — mint an id, read the clock once, delegate — and no rule that a pricing
or billing requirement could reach.

| File | One reason to change |
|---|---|
| `CostExplorer` | the *sequence* of an operation (mint an id, read the clock, delegate) changes |
| `CostExplorerFactory` | what a standard explorer is made of changes |
| `PricingPolicy` / `StandardPricing` | what a plan costs per month changes |
| `Proration` | the rounding rule, or how a day's rate is worked out, changes — i.e. never on its own |
| `Segment` | what one stretch of a subscription's timeline needs to know changes |
| `Subscription` | how a timeline of plan segments turns into a month's charge changes |
| `BillingAccount` | the operations the orchestrator needs from *any* kind of account change |
| `SubscriptionRepository` / `InMemorySubscriptionRepository` | how subscriptions are stored changes |
| `Demo` | what we show when we run it changes |

## The measurement, first, because it is the argument

Three requirement changes, each absorbed with the reference's own diff measured by
`server/lib/diff.mjs`:

| Curveball | Where it landed | Measured cost |
|---|---|---|
| a free trial month | one new file (`TrialAccount`, a decorator) + one new method | **7 lines** |
| an annual plan billed once, at a discount | one new file (`AnnualSubscription`, a second `BillingAccount`) + one new method | **6 lines** |
| a usage-based add-on | one new file (`UsageLedger`) + a field, a new method, and two existing methods widened | **13 lines** |

`Subscription` — the class that holds every rule about how a plan timeline turns into money — was
**not opened by any of the three**. That is the claim the design makes, and it is measurable rather
than aesthetic. `CostExplorer` was opened by all three, and that is not a defect: it is the
orchestrator, and every one of these requirements is, at bottom, a new thing the orchestrator can be
asked to do. What matters is that opening it never meant *reading* it — every edit is an addition,
and the usage curveball's two "widened" methods each grew by wrapping a call that used to be the
whole method body, not by branching inside existing logic.

---

## Money · integer minor units, and the one place rounding exists

**Every amount is a `long` of minor units.** No `double`, no `float`, anywhere, including in the
records that cross the API boundary — see `MonthlyCharge` and `AnnualProjection`. The reasons are
the same ones the shopping-cart problem's decision log gives, and they apply harder here because
this problem's arithmetic is a division, not just a multiplication: a `double` cannot represent
$9.99 exactly, so a plan's daily rate computed as a `double` would drift by a different amount
depending on which month it was divided by, and two customers on the same plan in the same month
could end up billed a cent apart from each other for no reason either could explain.

**There is exactly one rounding rule, and it lives in exactly one class, `Proration`:**
`(monthlyPriceMinor * daysInSegment * 2 + daysInMonth) / (daysInMonth * 2)` — half up, in integer
arithmetic, with no `RoundingMode` and no floating point anywhere near it. The `* 2` / `/ 2` trick
is the standard integer form of "add half the denominator before dividing", and it is worth being
able to derive on a whiteboard rather than only recognise: rounding `n / d` half up is
`floor((n + d/2) / d)`, and multiplying both the numerator and the padding by 2 avoids ever needing
`d` to be even.

**A whole month reduces to the flat price under the same formula, with no second code path.**
`Proration.segmentCostMinor(price, daysInMonth, daysInMonth)` simplifies to exactly `price` — see
the worked example in the contract's javadoc. This is not a coincidence I had to guard with an
`if (daysInSegment == daysInMonth) return monthlyPriceMinor;`; it falls out of the arithmetic, which
is one fewer place a maintainer could accidentally make the two paths disagree.

**`long` cannot overflow for any plan and any segment this problem can produce.** The largest
intermediate is `monthlyPriceMinor * daysInSegment * 2`, and with a monthly price in the thousands
of minor units and at most 31 days, that is nowhere near `Long.MAX_VALUE`.

## Seam 1 · `PricingPolicy` — a plan's price is asked for, never inlined

**Anticipates:** a promotion, a regional price list, a repriced plan, a price that varies by how
long someone has been a customer — anything that changes *what a plan costs* without changing
*how a month's cost is worked out from days and rates*.

One method, `monthlyPriceMinor(Plan)`. `Proration` and `Subscription` call it and never see a price
literal. The base problem has one implementation, `StandardPricing`, and that is the right amount
of seam for a fact this likely to change: a subscription product's price list is not a hypothetical
future requirement, it is closer to a certainty, and Atlassian's own stated discriminator for this
bank of problems is candidates who rewrite large portions when a scale-up lands — a price list is
exactly the kind of scale-up a design should not need surgery for.

**What this buys, concretely.** Neither curveball that changes *what something costs* —
annual billing's 15% discount — needed to touch `PricingPolicy` or `StandardPricing` at all.
`AnnualSubscription` asks `pricing.monthlyPriceMinor(plan)` for the one fact it needs and does its
own arithmetic from there, which is the same reason a candidate's own code should ask for a fact
rather than hard-code a finished answer when only one caller needs the finished answer.

## Seam 2 · `Proration` — a static utility, deliberately not an interface

**Anticipates:** nothing, on purpose. There is exactly one way this problem counts a partial month
and no requirement anywhere suggests a second one — the same call the library problem's `Lateness`
makes, for the same reason: *"any part of a day past the deadline is a whole day"* is a definition,
not a policy, and a definition with one interpretation does not need to be swappable.

**The one thing worth defending here is the choice not to build a seam.** An interface with one
implementation and no foreseeable second one is a seam that costs a file and earns nothing — naming
what is *not* a seam is as much a part of this design as naming what is.

## Seam 3 · `Subscription` — a timeline of segments, queried, never asked "what plan is active"

**Anticipates:** every question about what a subscription cost in some month, without the class
ever holding a *cached* answer to "what plan is active right now" that a mutator could forget to
invalidate.

A subscription is a `List<Segment>`, each an immutable `(plan, start, endExclusive)`. Every mutator
— `changePlan`, `cancel` — closes the most recent segment's end date and, for a change, opens a new
one. `costForMonth` never asks "what plan is active"; it asks "which segments overlap this month,
and for how many days each", which is the question a bill actually needs answered and the reason a
month split across a plan change is a query result rather than a special case written into the
mutators.

**The commit discipline is the library problem's, reused on purpose.** Close the open segment, then
either open a new one (`changePlan`) or don't (`cancel`), in that order, so there is never a moment
with two open-ended segments or none. This is what makes "changing to the plan already active is a
no-op" a one-line early return rather than a state a later bug could reach: if it were not a no-op,
two adjacent segments on the same plan would silently mean the same thing as one, and a month's
`prorated` flag would lie about whether anything actually changed.

**Why `prorated` is computed from the segment overlap, not stored.** `costForMonth` counts how many
segments overlap the month and how many days they cover in total; `prorated` is true only when that
is not "exactly one segment, covering the whole month". A month before the subscription existed
correctly reports `prorated = false` under this rule — nothing was blended, there was simply
nothing to bill — which is exactly the distinction `MonthlyCharge`'s javadoc draws and a test in the
base suite checks for directly.

## Seam 4 · `BillingAccount` — the interface both curveballs actually needed

**Anticipates:** a second kind of account, billed on a shape `Subscription`'s day-by-day timeline
was never built to express — and a decorator over an existing account, for a fact that has nothing
to do with the timeline at all.

The base problem has exactly one implementation, `Subscription`. The interface exists anyway
because `SubscriptionRepository` needs something to store that is not the concrete class — a
repository typed to its own single implementation has nothing to abstract, which is reason enough
on its own. What makes this seam worth its place in the log rather than a paragraph in
`SubscriptionRepository`'s is that **both curveballs that were not the usage add-on plugged into it
without `CostExplorer` or the repository changing at all**:

- **`TrialAccount`** decorates a `Subscription`, answering one question — "is this the free month?"
  — before delegating everything else. The same move the shopping-cart problem's `MemberShipping`
  makes over `ShippingPolicy`.
- **`AnnualSubscription`** is not a decorator at all; it is a second, complete implementation with a
  wholly different billing shape — a start month and a cycle length, no segments, no day-level
  proration. It could not have been a mode inside `Subscription`, because `Subscription`'s entire
  design is built around a question ("how many days of this month does each segment cover") that
  annual billing does not ask.

**The cost of not having it.** If `SubscriptionRepository` stored `Subscription` directly, neither
of the above could exist without `Subscription` itself growing a flag or a mode — which is exactly
the shortcut the first draft of the trial patch took, and it measured twice the lines (14 against
the 7 the decorator costs) for teaching a class about a concept, trials, that has nothing to do with
what a timeline of plan changes is.

## Seam 5 · `SubscriptionRepository` — two methods, not one per question

**Anticipates:** nothing about what questions get asked of subscriptions collectively — this
problem never asked for a report across accounts, so `outstanding()`-style querying (the library
problem's `LoanRepository`) is not needed here. What it does anticipate is storage changing shape
independently of everything that uses it.

`save` and `find`, and `find` throws its own `IllegalArgumentException` rather than returning `null`
and pushing the check onto every caller. `CostExplorer` therefore never repeats "is this id known"
logic — `changePlan`, `cancel`, `costForMonth`, `projectYear` and the usage curveball's
`recordUsage` all get the same refusal for free by calling `find` and letting it throw.

## Seam 6 · `CostExplorerFactory` — construction is a job, and it has an owner

**Anticipates:** everything about what a *standard* explorer is made of, as opposed to what it
does. `standard(TimeSource)` is the only place `StandardPricing` and
`InMemorySubscriptionRepository` are wired together, and `standardPricing()` is exposed on its own
so that a variation — a promotional price list, a different repository — reuses it instead of
copying it, the same reasoning `CartFactory` and `LibraryFactory` give in the other two problems'
logs.

## D4 · the monthly-breakdown shape, deliberately chosen

`AnnualProjection` carries a `List<MonthlyCharge>`, not twelve separate fields and not a bare
`long[]`. Three decisions, each with a plausible alternative:

- **A list of a named record, not a `long[12]`.** A candidate could return "the twelve totals" as a
  bare array and pass every base test; the design loses the ability to say *which* month a blended
  charge landed in without recomputing it, and it loses `prorated` entirely. `MonthlyCharge` exists
  because a customer who cannot see which month carried the blended rate does not believe the yearly
  figure, the same argument the shopping-cart problem's `CartTotal` breakdown makes for a single
  total.
- **The record self-validates its own shape.** `AnnualProjection`'s compact constructor refuses
  anything other than exactly twelve entries, in order, for the stated year, summing to the stated
  total. A projection that does not add up cannot exist, which turns a whole class of "off by one
  month" bug into a constructor exception naming the mismatch rather than a silent wrong answer a
  test has to notice.
- **`costForMonth` and `projectYear` are the same underlying question, asked once or asked twelve
  times.** `projectYear` calls `costForMonth` in a loop rather than duplicating its logic — which is
  exactly what made the usage-based add-on a change to *one* method's meaning rather than two: fixing
  `costForMonth` to include a usage charge fixed `projectYear` for free, once `projectYear` was
  routed through it instead of talking to a `BillingAccount` directly.

## C3 · the injected clock, and where "now" is read

Every operation that changes state — `subscribe`, `subscribeWithTrial`, `subscribeAnnual`,
`changePlan`, `cancel` — reads `clock.now()` exactly once, converts it to a `LocalDate` in UTC, and
uses that single value for everything the operation needs. Two readings inside one operation is how
a subscription could end up with a start date computed from a different instant than the one an
enclosing check used — the same bug the library problem's decision log warns about for `borrow()`,
and the same fix: read once, pass the value down.

**Queries never touch the clock at all.** `costForMonth` and `projectYear` are pure functions of the
recorded timeline and the month asked about — this is what makes a yearly projection able to look
either backward or forward from "now" without the query methods needing to know which. The clock's
only job in this design is to timestamp events; once an event is recorded, every later question
about it is answered from the recorded date, not from asking the clock again.

## B1 · why pricing is the seam this problem is built to exercise

The syllabus assigns this problem the plan-pricing policy seam specifically, and it is worth being
able to say why *pricing* rather than, say, *proration* is the one built as a swappable interface.
Proration is a definition with one interpretation stated in the contract; pricing is the one fact in
this domain that a real product changes constantly — a promotion, a regional list, a repriced tier —
and every one of those changes is "what does `Plan.STANDARD` cost", never "how do we count a partial
day". Building the seam around the fact most likely to actually move is the judgement call this
problem is testing, not a reflex to wrap every enum lookup in an interface.

## Errors · `IllegalArgumentException` and `IllegalStateException`, and where the line is drawn

- **A bad argument is `IllegalArgumentException`:** an unknown subscription id, wherever it is
  looked up. `SubscriptionRepository.find` throws it once, so every caller inherits the same
  refusal without repeating the check.
- **A bad situation is `IllegalStateException`:** changing the plan on, or cancelling, a
  subscription that is already cancelled — or, after the annual curveball, one that is billed
  annually at all. The argument (a real subscription id) was fine; the subscription was not in a
  state that could accept the operation.
- **A month with nothing to bill is not an error.** `costForMonth` for a month before a subscription
  existed, or safely after it was cancelled, returns a zero `MonthlyCharge` rather than throwing —
  asking about a quiet month is an ordinary question, and forcing every caller to catch an exception
  to find that out would be the wrong shape for something this common. This mirrors the shopping-cart
  problem's "removing more than is in the cart is not an error" — the arbitrary rule most likely to
  be guessed differently, which is why the contract states it in bold.

## Immutability at the boundary

`MonthlyCharge` and `AnnualProjection` are records that validate in their compact constructors, so
neither can exist in an invalid shape: a negative amount, or a projection whose twelve months are
out of order, wrong for the stated year, or do not sum to the stated total, all fail at the point of
construction with a message naming what disagreed, rather than as a mismatched assertion a test
happens to catch later. `AnnualProjection.months()` is `List.copyOf`, so a caller can read every
month and change none of them — the same guarantee the shopping-cart and library problems' totals
and catalogues give.

## What was deliberately *not* built

Named so the scope is explicit rather than forgotten:

- **No `Money` type and no currency.** One currency was stated, so a wrapper round a `long` would be
  a class earning nothing today — the same call the shopping-cart problem makes, for the same reason.
- **No domain exception hierarchy.** The contract fixes `IllegalArgumentException` and
  `IllegalStateException`, which constrains a `CostExplorerException` family out from the start —
  worth saying out loud rather than pretending the choice was free.
- **No caching of a month's or a year's charge.** `costForMonth` recomputes from the segment list
  every time; a subscription's timeline is at most a handful of segments, and a cache here would
  need to be invalidated by every mutator for a saving that is not measurable at this scale, the same
  trade-off the shopping-cart problem's `Cart.total()` makes.
- **No locks.** The contract states one caller at a time. The shared mutable state worth naming out
  loud is the segment list inside each `Subscription` and the map inside
  `InMemorySubscriptionRepository`; a version of this system behind a network interface would want a
  lock per subscription, not a global one, since two different customers' billing has nothing to do
  with each other.
- **No `BillingCycle` enum threaded through the base design in anticipation of annual billing.**
  It would have been speculative generality for a fact the base problem never asked about, and it is
  exactly the trap the shopping-cart problem's decision log names for its own absent `StackingRule`:
  an interface with one foreseeable implementation and no second one yet in sight. `BillingAccount`
  turned out to be the seam annual billing needed, and it existed for a different, better reason —
  the repository storing an abstraction rather than a concrete class — not because annual billing was
  predicted.

## Honest note on the weakest tag

**`B2` (factory) is the thinnest of the tags claimed here.** `CostExplorerFactory.standard(clock)` is
one line wiring two collaborators together, and the "parts exposed as well as the whole" pattern
that earns `CartFactory` and `LibraryFactory` their place in the other two problems' logs is present
only weakly: `standardPricing()` is exposed, but there is no second collaborator (no coupon book, no
membership roll) for a variation to reuse selectively. It is a genuine factory and it is genuinely
used, but it is doing less work here than in the two problems it is modelled on, and a reviewer who
pressed on "what would a second factory method actually reuse" would get a shorter answer than for
`CartFactory.standardCatalog()`.
