# Reference patch — 02-best-agents-of-the-month

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/CustomerSatisfactionApi.java` onto `contract/` — it replaces the
   file of the same name, adding `leaderboardForMonth(YearMonth)` and its javadoc paragraph.
   Contract files are given, so nothing here is charged.
2. **Edit** `reference/src/Summaries.java`: add `leaderboardForMonth(RatingRepository, YearMonth)`,
   just above `monthly(...)`:

   ```java
   /** The leaderboard for one month only, in {@link #LEADERBOARD_ORDER}. */
   public static List<AgentSummary> leaderboardForMonth(RatingRepository repo, YearMonth month) {
       return repo.all().stream()
               .filter(r -> monthOf(r).equals(month))
               .collect(Collectors.groupingBy(Rating::agentId)).values().stream()
               .map(rs -> summarize(rs.get(0).agentId(), rs))
               .sorted(LEADERBOARD_ORDER)
               .toList();
   }
   ```
3. **Edit** `reference/src/SatisfactionDesk.java`: add the `import java.time.YearMonth;` line, and
   add the delegating method just above `exportMonthly`:

   ```java
   @Override
   public List<AgentSummary> leaderboardForMonth(YearMonth month) {
       return Summaries.leaderboardForMonth(ratings, month);
   }
   ```

`edits.json` beside this file is the same three edits in machine-readable form, so the measurement
is reproducible: baseline is `reference/src/*.java`, current is the same files with those
replacements applied, fed to `measureChange(baseline, current)`. There is no new file this time —
the requirement is entirely new *behaviour* on an existing report shape, not a new format.

Measured: `reference_diff = 14` (`SatisfactionDesk.java` 5, `Summaries.java` 9), so
`diff_budget = max(3, 14 + ceil(14/2)) = 21`.

Both the curveball suite (5 tests) and the base suite (15 tests) are green after the patch. **No
base assertion is invalidated** — `leaderboard()`, `rate()` and `exportMonthly()` are untouched;
the new method only reads what is already stored.

## Where the requirement landed, and why the repository is the reason

`RatingRepository` was **not opened**, and `InMemoryRatingRepository` was **not opened**. The
requirement is a new *question about the ratings that exist* — "which agents were rated in this
month" — and there are two shapes it can land in:

- **The tempting shape: a method on the storage interface.** `List<Rating> forMonth(YearMonth)`
  reads well, and it is what most designs reach for. It costs an edit to the interface and to every
  implementation of it, and it sets the precedent: the next question about ratings — this week,
  this quarter, since a given rating id — is another pair of methods to keep in step.
- **The shape the reference took: `all()` plus a filter.** The repository already hands over every
  rating there is, because the all-time leaderboard and the export both need exactly that. A new
  question is therefore a new function in `Summaries`, which already owns "how ratings turn into an
  `AgentSummary`" — not a new method on the thing that stores them.

**Why `LEADERBOARD_ORDER` being shared is what makes the tie-break test in this curveball pass for
free.** The comparator is defined once, in `Summaries`, and `leaderboard()` already sorted with it.
`leaderboardForMonth` reuses the same field. A design that inlined the tie-break logic inside
`leaderboard()` — three chained `Comparator.comparing` calls written directly in that method's body
— would need to either duplicate them here or extract them under time pressure mid-round, which is
exactly the rewrite this whole curveball ladder is built to surface.

**What is genuinely new, and costs real lines:** grouping ratings by agent for one month, and
turning each group into a summary, because nothing in the base problem needed to do that — the
all-time leaderboard already knows its agent ids from `agentIds()`, and the export already has one
agent's ratings handed to it directly. Nine lines for a real, previously-absent piece of logic is
the honest cost, not a rewrite dressed as an addition.
