# Story 4.4: Budget Actuals View with Spending Chart

Status: review

## Story

As a user,
I want to see how much I've spent against each category budget this month, with a visual chart,
so that I can immediately see where I'm on track and where I'm overspending.

## Acceptance Criteria

1. `GET /budgets/` shows each active category with: budgeted amount, amount spent this month, remaining amount, and percentage used.
2. `progress_bar.html` renders each row with color-state: safe (< 80%), warning (80–99%), danger (≥ 100%).
3. A Chart.js bar chart shows budgeted vs. actual spend per category; loads via `GET /api/budgets` returning `[{category, budgeted, spent}]`.
4. `/api/budgets` uses SQL aggregation (`func.sum + group_by`) — no Python loops over all rows.
5. Credit transactions (`is_credit=True`) and prior-month transactions excluded from "spent".
6. Categories with no transactions show $0 spent; categories with no budget show "No budget set" with a link.
7. `{% block scripts %}` added to `base.html` so pages can inject page-specific JS.
8. `pytest tests/` passes — all prior 334 tests still green (27 new, 361 total).

## Tasks / Subtasks

- [x] **Task 1: Write `tests/test_blueprints/test_budgets_actuals.py`** (TDD RED — 15 tests)
- [x] **Task 2: Write `tests/test_blueprints/test_api_budgets.py`** (TDD RED — 12 tests)
- [x] **Task 3: Implement `GET /api/budgets` in `app/blueprints/api/budgets.py`**
- [x] **Task 4: Register api/budgets in `app/blueprints/api/routes.py`**
- [x] **Task 5: Add `_build_actuals()` helper + update `GET /budgets/` route**
- [x] **Task 6: Redesign `budgets/index.html`** — actuals + progress bars + inline edit + canvas
- [x] **Task 7: Add `{% block scripts %}` to `base.html`**
- [x] **Task 8: Run full suite GREEN**

## Dev Notes

### `_build_actuals()` helper

Pre-computes per-category display dict:
```python
{
    'category': Category,
    'budgeted': Decimal,
    'has_budget': bool,
    'spent': Decimal,      # sum of non-credit current-month transactions
    'remaining': Decimal,
    'pct': int,            # 0..N (can exceed 100 if over budget)
    'state': str,          # 'safe' | 'warning' | 'danger' (AC thresholds)
}
```

AC thresholds: `pct >= 100` → danger; `pct >= 80` → warning; else safe.  
Note: `progress_bar.html` component defaults differ (70/90) — explicit `state=` parameter overrides.

### API SQL aggregation pattern

```python
spent_rows = (
    db.session.query(Transaction.category_id, func.sum(Transaction.amount).label('total'))
    .filter(Transaction.date.like(month_prefix), Transaction.is_credit == False)
    .group_by(Transaction.category_id)
    .all()
)
```

`Transaction.date` is stored as TEXT 'YYYY-MM-DD'. Month filter uses `.like(f'{year}-{month:02d}-%')`.

### Chart

`<canvas id="budgetChart">` in the content block. JS in `{% block scripts %}` fetches
`/api/budgets` and renders a grouped bar chart using Chart.js + `window.FIN_PALETTE`.

### `base.html` change

Added `{% block scripts %}{% endblock %}` just before `</body>` so pages can inject
page-specific scripts (Chart.js init code, etc.) after the shared JS utilities load.

### File List

| File | Status |
|------|--------|
| `tests/test_blueprints/test_budgets_actuals.py` | NEW (15 tests) |
| `tests/test_blueprints/test_api_budgets.py` | NEW (12 tests) |
| `app/blueprints/api/budgets.py` | MODIFIED (GET /api/budgets) |
| `app/blueprints/api/routes.py` | MODIFIED (import budgets) |
| `app/blueprints/budgets/routes.py` | MODIFIED (_build_actuals + display_rows) |
| `app/blueprints/budgets/templates/budgets/index.html` | MODIFIED (actuals + progress + chart) |
| `app/templates/base.html` | MODIFIED (added block scripts) |

---

## Dev Agent Record

### Agent Model Used
claude-sonnet-4-6

### Debug Log References
- `{% block scripts %}` not in `base.html` — chart canvas present in HTML but scripts block silently dropped; fixed by adding the block before `</body>`

### Completion Notes List
- 27 new tests (15 actuals view + 12 API), all green — zero regressions against 334 prior tests (361 passed, 1 skipped)
- Index page redesigned: actuals header + progress bar + inline edit form per category row
- `_build_actuals()` helper keeps all arithmetic out of the template and the route clean
- API endpoint uses single SQL aggregation query per design constraint (no Python loops over rows)
- AC progress thresholds (80/100) differ from `progress_bar.html` defaults (70/90) — solved by passing explicit `state=row.state`

### Change Log
- 2026-05-29: Story implemented and moved to review status
