## Why
The analysis drawer was built for **bounded** filters — a trip, a
project — where "total spent" and "avg / day" are the answer.
Filter by something open-ended instead, like a single category, and it
surfaces a year or two of expenses. There both numbers stop meaning
anything: the cumulative line only ever climbs, `€12.32 / day` is not a
figure anyone decides on, and the largest-expenses table fills with the
same monthly transfer repeated five times.
## What
A fourth analysis view that answers the open-ended question instead:
**the monthly rate, and where the recent months sit against it**.
Automatic picks it past a 120-day span; the existing per-filter override
now carries four options, each named after the number it produces.
`Automatic` · `Total spent` · `Income & expenses` · **`Monthly trend`**
| Panel | Bounded view | Monthly trend |
|---|---|---|
| Rate | Total spent + avg / day | **Monthly average** and **last 3
months** with the change between them |
| Chart | Bars + cumulative line | **Monthly bars**, dashed line at the
average, no cumulative |
| Largest expenses | top 10 | **not rendered** — across an open-ended
span the biggest single transaction is trivia, and the payee and
category breakdowns already answer where the money went |
| Category · payee · account · label | unchanged | unchanged |
Footer names the months covered rather than counting days. When income
is a meaningful share the cards report net figures and the chart adds
income bars.
### Only whole months count
Every monthly figure covers calendar months the series spans end to end.
Both edges would otherwise drag the average down, in the same direction:
- The **trailing** month is still in progress — on the 2nd it holds two
days of spending.
- The **leading** month is partial whenever the series does not start on
its 1st, whether a filter clipped it or the first transaction simply
landed on the 18th.
Skipping only the trailing one manufactured a rise: flat €300/month over
a 120-day span starting 18 March reported **+16%** in amber on spending
that never changed. Both are excluded now, which is what the one new
backend field (`summary.first_date`) is for. The part-month bars are
faded rather than dropped, so the solid bars are exactly the months
behind the average.
The average card states how many whole months it spans and the
percentage says it is against the average — two figures that otherwise
silently covered less than they appeared to.
### Edge cases
- Forcing the view onto a span with no whole month falls back to the
bounded shape, toggle label included, and the popover says why.
- The recent card is hidden when whole months do not outnumber its own
window, rather than repeating the overall average.
- A **day override** is the user stating the real duration, so it
decides the automatic view too: a trip whose transactions posted over
eight months stays bounded instead of jumping to trend and leaving that
override inert.
## Cost
**Backend: one enum case and one summary field.** No migration —
`analysis_mode` is already a nullable string behind `Rule::enum`. Every
other figure is derived in the browser from the `over_time` series the
endpoint already returned.
## Tests
`resolveAnalysisView`, `monthlyRates` and `isAdverseChange` are pure and
unit-tested directly — the part-month cutoff, the change against a
negative average, the zero-average guard, and the adverse-direction rule
that inverts for net. 32 component tests, plus feature coverage for the
new field and the persisted mode.
## QA
Browser QA against a year of grocery spending on the demo account (352
transactions, July 2025 – July 2026): the trend view resolves
automatically, all four options switch correctly, and a 30-day date
filter still lands on the bounded view. 22 assertions, no console
errors.
## Demo
https://github.com/user-attachments/assets/f4a14349-3d58-4da4-b85c-9a4ca076551f