# Story 4.3: First-Use Budget Template (50/30/20)

Status: review

## Story

As a user,
I want the app to offer a 50/30/20 budget template when I have fewer than 2 months of transaction history,
so that I can get a sensible starting point without guessing at every category.

## Acceptance Criteria

1. `GET /budgets/recommend` — if `monthly_income` is not set, shows a prompt with a link to `/settings/income`.
2. If income is set, shows the 50/30/20 template: 50% needs (Housing, Utilities, Groceries, Transportation, Healthcare, Debt Payments), 30% wants (Dining, Entertainment, Shopping, Subscriptions, Personal Care), 20% savings/debt (informational note).
3. `budget_recommender.fifty_thirty_twenty(monthly_income)` in `services/insights/budget_recommender.py` produces the allocations and defines the `BudgetRecommendation` interface contract (Story 10.7 `from_history()` must satisfy the same type).
4. User can adjust any allocation before saving; total must not exceed `monthly_income`.
5. Blank/zero amounts are silently skipped (not saved as Budget records).
6. "Apply These Budgets" saves Budget records for current month (upsert); PRG redirect to `/budgets/`, flash `'success'`.
7. `pytest tests/` passes — all prior 295 tests still green (39 new tests, 334 total).

## Tasks / Subtasks

- [x] **Task 1: Write `tests/test_services/test_budget_recommender.py`** (TDD RED — 13 tests)
- [x] **Task 2: Write `tests/test_blueprints/test_budgets_recommend.py`** (TDD RED — 26 tests)
- [x] **Task 3: Implement `BudgetRecommendation` dataclass and `fifty_thirty_twenty()` in `services/insights/budget_recommender.py`**
- [x] **Task 4: Add `BudgetRecommendForm` (CSRF-only) to `app/blueprints/budgets/forms.py`**
- [x] **Task 5: Add `GET+POST /budgets/recommend` route to `app/blueprints/budgets/routes.py`**
- [x] **Task 6: Create `budgets/recommend.html` template**
- [x] **Task 7: Run full suite GREEN**

## Dev Notes

### BudgetRecommendation interface contract

```python
@dataclass
class BudgetRecommendation:
    category_name: str
    suggested_amount: Decimal
    bucket: str  # 'needs' | 'wants' | 'savings'
```

`fifty_thirty_twenty(monthly_income)` returns 12 items: 6 needs + 5 wants + 1 savings.
Story 10.7 must return the same type from `from_history(rolling_avgs)`.

### Amount distribution

- Needs (50%): `monthly_income * 0.50 / 6` per category
- Wants (30%): `monthly_income * 0.30 / 5` per category
- Savings (20%): `monthly_income * 0.20` as a single informational entry (not saved as Budget)

All amounts quantized to 2 decimal places.

### Category matching

Route looks up each recommendation's `category_name` in active DB categories (`cat_map`).
Categories not found in the DB are shown as informational-only (no form input).
The "Savings" bucket entry has no system category, so it is always informational.

### Form fields

`amount_<cat_id>` fields — blank or zero amounts are skipped (not errors, not saved).
Total validation: sum of non-blank, positive amounts must not exceed `monthly_income`.

### Template fix

`monthly_income` is a `Decimal`. The template uses `monthly_income|float * 0.5` (not
`monthly_income * 0.5`) to avoid `TypeError: unsupported operand type(s) for *: Decimal and float`.

### File List

| File | Status |
|------|--------|
| `tests/test_services/test_budget_recommender.py` | NEW (13 tests) |
| `tests/test_blueprints/test_budgets_recommend.py` | NEW (26 tests) |
| `app/services/insights/budget_recommender.py` | MODIFIED (BudgetRecommendation + fifty_thirty_twenty) |
| `app/blueprints/budgets/forms.py` | MODIFIED (added BudgetRecommendForm) |
| `app/blueprints/budgets/routes.py` | MODIFIED (added recommend route) |
| `app/blueprints/budgets/templates/budgets/recommend.html` | NEW |

---

## Dev Agent Record

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

### Debug Log References
- Fixed `TypeError: unsupported operand type(s) for *: Decimal and float` in template — used `monthly_income|float * 0.5`
- Fixed `NameError: name 'cid' is not defined` in POST error handler — was `cid`, corrected to `cat.id`

### Completion Notes List
- 39 new tests (13 service + 26 route), all green — zero regressions against 295 prior tests (334 passed, 1 skipped)
- `BudgetRecommendation` dataclass defined with `category_name`, `suggested_amount`, `bucket` — interface contract for Epic 10 Story 10.7
- Savings bucket (20%) shown as informational note only — no system "Savings" category exists, no Budget record saved
- Blank/zero amounts silently skipped; only positive amounts counted toward total / saved
- Uses `BudgetRecommendForm` (CSRF-only FlaskForm) + manual `amount_<cat_id>` field processing

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