# Reference patch — 03-a-90-day-window

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 `leaderboardWithinDays(int)` and its javadoc paragraph. Contract
   files are given, so nothing here is charged.
2. **Edit** `reference/src/Summaries.java`: add the `Duration` and `Instant` imports, and add
   `leaderboardWithinDays(RatingRepository, Instant, int)` just above `monthly(...)`:

   ```java
   /** {@link #leaderboard} using only ratings on or after {@code now.minus(Duration.ofDays(days))}. */
   public static List<AgentSummary> leaderboardWithinDays(RatingRepository repo, Instant now, int days) {
       Instant cutoff = now.minus(Duration.ofDays(days));
       return repo.all().stream()
               .filter(r -> !r.recordedAt().isBefore(cutoff))
               .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 delegating method, with its own argument
   check, just above `exportMonthly`:

   ```java
   @Override
   public List<AgentSummary> leaderboardWithinDays(int days) {
       if (days <= 0) {
           throw new IllegalArgumentException("days must be positive: " + days);
       }
       return Summaries.leaderboardWithinDays(ratings, clock.now(), days);
   }
   ```

`edits.json` beside this file is the same 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)`. No new file, and — deliberately —
no change to `Rating`, `RatingRepository` or `InMemoryRatingRepository`: the precise instant every
rating already carries (`Rating.recordedAt()`) was never thrown away in favour of month-only
precision, so a day-level window costs nothing to make possible; it only costs the lines that
actually compute one.

Measured: `reference_diff = 19` (`Summaries.java` 12, `SatisfactionDesk.java` 7), so
`diff_budget = max(3, 19 + ceil(19/2)) = 29`.

Both the curveball suite (6 tests) and the base suite (15 tests) are green after the patch. **No
base assertion is invalidated** — `leaderboard()` and `exportMonthly()` read exactly the same
ratings they always did; the window is a new, separate query, never a modification of the old one.

## Where the requirement landed, and why keeping the instant paid off

The whole reason this curveball is affordable is a decision made **before any curveball existed**:
`Rating` carries `recordedAt()` — the full instant — rather than a pre-reduced `YearMonth`. Nothing
in the base problem's reporting needed day-level precision; a lesser design would have thrown it
away the moment a rating was recorded, computed a month once, and stored only that. This
requirement is exactly the bill for that choice, and it is a large one: without the instant,
"which ratings are within 30 days of now" would need a redefinition of what a `Rating` is and a
migration of every rating already on file, which is not a `reference_diff` a curveball measures —
it is a rewrite of the data model under fire, precisely what the discriminator this corpus is built
around is looking for.

**Why this landed in `Summaries`, not `RatingRepository`.** Exactly as with the previous curveball,
the tempting shape is a new storage method — `findSince(Instant)` — costing an edit to the interface
and to `InMemoryRatingRepository`. The reference again reaches for `all()` plus a filter, because
that is already the shape every other report in this class is built from. The two most recent
curveballs absorbed in the identical shape — filter, group, summarize, sort with the one shared
comparator — is the measurable evidence that the repository's four methods really were enough, and
that the "one method per question" alternative would by now have cost two extra interface methods
and two extra implementations for the same behaviour.

**What is genuinely new:** the cutoff arithmetic and the inclusive boundary, because nothing in the
base problem measured "how long ago" — it only ever asked "which month". That is the real, honest
cost of this curveball, and it is a handful of lines, not a rewrite.
