# Reference patch — 02-top-n-per-category

The answer key for how the reference absorbs this change. **Never shipped to the candidate.**

1. **Add** `CategoryBooks.java` (in this directory) to `reference/src/`. New file, so
   `measureChange` charges nothing for it. It keeps one `AgentRatingBook` per category, created
   lazily on that category's first rating, and validates `category` itself.
2. **Replace** `reference/src/RatingSystem.java` with the version in this directory. The change is
   one field (`categories`) and two `@Override` methods, each a one-line delegation to
   `CategoryBooks`. See `edits.json` for the exact insertions.
3. `contract-delta/RatingSystemApi.java` is the given, revised interface — it is what forces
   `RatingSystem` to add the two overrides at all. Contract files are excluded from the diff
   measurement.

`AgentRatingBook`, `InMemoryAgentRatingBook`, `RankingOrder`, `StandardRankingOrder`, `Rankings`,
`AveragingStrategy`, `SimpleMeanStrategy`, `RatingSystemFactory` and `Validation` were not opened.

Measured: `reference_diff = 11` (one file touched, eleven lines added, zero removed), so
`diff_budget = max(3, 11 + ceil(11/2)) = 17`.

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

The instinct that would have cost more is putting category-awareness inside `AgentRatingBook`
itself — a `record(String category, String agentId, int stars)` overload, or a second
implementation that is "the same but keyed by category too". Either would have opened
`InMemoryAgentRatingBook` and made every future category-shaped question a second edit to the
storage interface, for every category-shaped question, forever — the exact `library`/
`customer-satisfaction` lesson about repository interfaces this corpus keeps re-teaching.

Instead, a category's ranking is "a second book, not a second kind of book": `CategoryBooks` is
nothing but a `Map<String, AgentRatingBook>` that reuses the one existing implementation
unchanged. `RankingOrder` and `Rankings` did not need to know a category exists at all — `topN`
and `topNInCategory` both end in the same `Rankings.topN(Rankings.ordered(..., order), n)` call,
which is the proof that "where the ordering policy lives" was the right seam: a second ranked view
is a second list handed to the same two functions, never a second sorting rule written out again.

The test worth watching in a candidate's design is the second one, which rates an agent overall
and in a category and checks the overall standing did not move. A design that stored a category
as a field on the same accumulator the overall rating uses — rather than as an entirely separate
book — would leak one into the other the first time both were touched for the same agent.
