# Budget Phase 1.11 — Status categories, Income page, and formatting

Use this reference when a FinanceManager Budget/Cashflow sprint focuses on **Budgetstatus correctness, Income/Einkünfte UX, and dashboard formatting**, not import engines.

## Scope boundaries

- Do **not** add VISA/Migros/Cumulus imports, OCR, rule engines, or duplicate engines in this class of sprint unless explicitly re-authorized.
- Keep real budget files, runtime SQLite DBs, secrets, build artifacts, and frontend env secrets out of Git.
- Before runtime checks or cleanup, create a runtime DB backup outside the repo and report only aggregate counts/no real amounts.

## Budgetstatus category logic

Budgetstatus by category must not be an inner join on budget plans. Use a Full-Outer-Join-style result over:

1. active budget categories with active budget plans for the active year,
2. active categories with confirmed budget transactions for the active year,
3. categories with both plan and actuals,
4. categories with actuals but no plan.

Acceptance rules:

- Actual-only categories appear in the table.
- Plan-only categories appear with neutral/no-actuals status.
- Empty categories without plan and without actuals do not appear.
- Actual-only expense categories get status `ohne Budget` plus a hint like `Ausgaben vorhanden, aber kein Budgetplan`.
- Income and expense status semantics differ:
  - income: green when actual/prognosis meets or exceeds plan; yellow/red when below plan,
  - expenses: green when forecast stays under budget; yellow near budget; red/critical over budget.

## Parent/subcategory rollups

Transactions posted to subcategories must not disappear. For MVP:

- include the booked subcategory row directly,
- also aggregate actuals into active ancestors/parent categories for rollup visibility,
- expose simple rollup metadata such as `is_rollup` and child-count if useful for UI/drilldown.

Avoid silently moving transactions between categories just to make the status table look right.

## Decimal formatting pattern

For Vue/FastAPI dashboard user-facing values:

- create central frontend formatters such as `formatMoney`, `formatPercent`, `formatQuantity`, `formatMaybeMoney`,
- display money as e.g. `CHF 4’019.31`, `CHF 0.00`, and no scientific notation,
- display percentages with two decimals, e.g. `82.45 %`,
- use formatting only at DTO/UI boundaries; keep DB values as Decimal/TEXT and do not introduce float arithmetic in domain logic,
- update tests when output contracts intentionally move from `0` to `0.00`.

Regression test a long decimal such as `4019.308333333333482 -> CHF 4’019.31`.

## Einkünfte / Income page MVP

Prefer existing `budget_plan_items` with `category_type='income'` over a parallel income model.

Minimum MVP actions:

- list income plans,
- add income plan with audit,
- archive income plan with audit,
- show Excel/seed income candidates as review items,
- show summary cards for planned monthly/yearly income, actual current month, actual YTD, deviation, open review,
- show effective/actual income rows grouped by date with filters and a simple detail drawer,
- provide edit/update flow as Preview → Confirm → Audit, not direct mutation.

Model mapping tips:

- monthly amount → yearly = monthly × 12,
- annual amount → monthly average = annual ÷ 12,
- one-time can remain explicit/one-time depending on existing cadence semantics,
- store source/person/type metadata in structured columns if available; notes parsing is acceptable as an MVP bridge only,
- for empty income templates, do **not** invent a new `source_type` unless the DB enum allows it. FinanceManager currently allows `manual`, `excel_seed_dry_run`, and `system`; use `source_type='system'` plus notes metadata such as `template=true`, `person=...`, `income_type=...`, `target_month=...`,
- allow amount-less templates only when explicit template metadata is present; normal manual income plans should still require monthly or annual amount.

Template seeding pattern:

- Create a runtime DB backup outside the repo before seeding visible templates.
- Seed templates without amounts, then verify every expected name is visible and every template has monthly/yearly display value `0.00`.
- Report only counts and template names; do not print real values.
- If one template already exists as an active income plan, skip it idempotently rather than duplicating it.

Excel income candidate reporting:

- aggregate-only: count recognized, clear, review-required,
- never print amounts or raw source rows in chat,
- keep Review/Confirm as the productive write boundary.

## Runtime and verification pattern

- Runtime backup before checks/mutations.
- Runtime check for named categories should report counts/statuses only, not real amounts.
- A category not appearing after the fix can be valid if there are zero confirmed active-year transactions and no plan; report that distinction clearly.
- Full gate: Python compile, full pytest, frontend vitest, frontend build, `git diff --check`, changed-source secret scan, frontend build secret scan, Git-safety for runtime artifacts, commit, push, remote hash equality.
- If a broad legacy secret scan flags existing OpenFIGI-related source/tests, do not conflate that with the current sprint. Scan changed files for newly introduced secrets, still run Git-safety, and separately scan `frontend/dist` before deleting build artifacts.
- After frontend build/tests, remove `frontend/dist`, `frontend/node_modules`, Vite caches, pytest caches, and `__pycache__` before final Git-safety/commit.
- Restart with `./scripts/stop_vue_dashboard.sh` and `./scripts/start_vue_dashboard.sh`; if a stale Vite/node process still owns port 5173, identify and terminate only that dashboard process, then restart.
- Verify local backend, local frontend, and Tailscale frontend URL (`tailscale ip -4`) before reporting links. If `tailscale serve status` has no config, report IP URL only plus local URLs.