# ADR-0004: FastAPI + Vue User Dashboard Transition Plan

**Status:** Accepted for planning / incremental execution  
**Date:** 2026-05-16  
**Decision owner:** JARVIS Finance System  
**Scope:** Professional User Dashboard transition; no productive data migration and no Streamlit removal.

## Context

The current Streamlit dashboard was the right tool for the MVP phase: it gave us a safe, local, Python-native UI over the Runtime SQLite database, allowed rapid iteration against the existing ledger/read-model code, and kept provider calls, reports, audit checks and operations close to the Python core. That was useful while the foundation was still moving: broker/import review flows, crypto holdings, manual Equity/ETF entry, FX quality flags, reports and Git-safety could be proven without introducing a frontend build system.

The MVP has now reached the point where more Streamlit polishing produces diminishing returns. The system needs a professional, interaction-heavy User Dashboard while keeping the existing Streamlit shell as an Admin/MVP/Operations fallback.

## Streamlit boundaries now visible

Streamlit remains valuable, but it is the wrong long-term fit for the normal user surface:

- **Tables:** usable for read-only dataframes, weak for app-grade portfolio grids.
- **Row click / selection:** possible but clumsy, version-sensitive and not natural enough for portfolio workflows.
- **Detail panels:** sidebars/drawers are approximations rather than first-class components.
- **Layout control:** limited precision for dense finance dashboards, responsive panels and card/grid hierarchies.
- **Complex wizards:** multi-step state, validation and review flows become fragile.
- **Modern UX:** professional dashboards need richer navigation, drawers, sticky actions, keyboard shortcuts and component reuse.
- **State handling:** Streamlit session state works for prototypes; it becomes awkward for nested workflows, filters, selections and optimistic UI.

## Decision

Build a separate professional User Dashboard with:

- **Backend:** FastAPI as API layer only.
- **Frontend:** Vue 3 + TypeScript + Vite + Tailwind CSS.
- **Tables:** AG Grid preferred for the portfolio/position grids; TanStack Table remains an alternative for lighter pages.
- **Charts:** ECharts preferred for dashboard charts; Plotly.js remains acceptable where existing Plotly semantics help.
- **State / data fetching:** Pinia for local UI state and Vue Query for server state where helpful.

Streamlit stays available as Admin/Operations UI. The existing Python core remains the system of record for business logic.

## Why FastAPI + Vue

FastAPI gives a thin, typed, testable HTTP boundary around the existing Python services without moving business logic out of Python. It supports Pydantic DTOs, OpenAPI, TestClient tests and explicit CORS/secrets boundaries.

Vue is a good fit for the User Dashboard because it is lightweight, component-friendly and fast to iterate. With TypeScript, Tailwind, AG Grid and ECharts it can deliver a polished finance UI without turning the backend into a JavaScript application. This matches the target: Python core, API-first, frontend-only UX layer.

## Why no Big-Bang rewrite

A Big-Bang rewrite would create unnecessary risk:

- productive Runtime SQLite data must not be migrated casually;
- current Streamlit pages still provide operations/debug value;
- import/review/audit logic is already implemented in Python and must not be duplicated;
- write workflows need preview/review/confirm/audit semantics before being exposed in a new UI;
- the first goal is to prove superior UX with read-only interaction, not rebuild the whole system.

Therefore migration is incremental and API-first. The first slice is read-only.

## Alternatives considered

- **Streamlit:** correct MVP tool, quickest for Python-only local dashboards, but now too constrained for professional interaction, row-click/detail workflows and complex wizards.
- **Plotly Dash:** strong for Plotly-heavy analytical dashboards, but callback complexity is high and it does not solve the broader portfolio workflow UX as cleanly as a real frontend.
- **NiceGUI:** more app-like than Streamlit while staying Python-only, but still a framework migration without the same ecosystem depth for professional tables/state/routing.
- **FastAPI + Vue:** preferred. Clean Python API boundary, modern frontend ergonomics, TypeScript safety, strong grid/chart options, good incremental migration story.
- **FastAPI + React:** technically excellent and ecosystem-rich, but Vue is preferred here for concise component ergonomics and lower ceremony for this project.

## Target architecture

### Backend

- FastAPI app under `src/jarvis_finance/api/`.
- Existing Python core remains the only place for business rules.
- SQLite Runtime DB remains the current database; PostgreSQL may be introduced later behind service interfaces.
- API routers call services; services call existing core/read models. No wild SQL scattered through routers.
- Every write endpoint must use preview/review/confirm/audit semantics.
- Audit log remains mandatory for productive mutations.
- Provider/API-key access is backend-only.
- Runtime secrets are read only by backend code.
- No API keys, provider credentials, DB files, reports or Runtime data enter the frontend or Git.

### Frontend

- `frontend/` in the same repo for MVP transition, with hard separation from Python source.
- Vue 3 + TypeScript + Vite.
- Tailwind CSS for layout/design tokens.
- Pinia and/or Vue Query for state/data fetching.
- AG Grid or TanStack Table for positions lists; AG Grid is preferred for grouping/filtering/column control.
- ECharts or Plotly.js for charts; ECharts is preferred for dashboard-native allocation charts.
- Pages:
  - Command Center
  - Portfolio
  - Crypto
  - Wallets
  - Aktien & ETFs
  - Position hinzufügen
  - Reports
  - Settings/Admin later, not in first User slice.

## API contracts

All decimal and money values are transferred as strings, never floats.

### Health / Runtime

- `GET /api/health`
  - Returns API liveness, version and mode.
- `GET /api/runtime/status`
  - Returns Runtime DB availability, mode, base currency, read/write posture, and whether runtime is outside Git.
- `GET /api/provider/status`
  - Returns sanitized provider status only: configured/missing/auth_failed/rate_limited/network_error/unknown. Never returns key values or raw secret-bearing URLs.

### Command Center

- `GET /api/overview`
  - `total_portfolio_chf`
  - `crypto_chf`
  - `stock_chf`
  - `etf_chf`
  - `cash_chf`
  - `unvalued_position_count`
  - `critical_hints`
  - `last_price_update_at`

### Portfolio

- `GET /api/portfolio/summary`
- `GET /api/portfolio/positions`
- `GET /api/portfolio/positions/{id}`
- `GET /api/portfolio/groups?by=platform|asset_class`

### Crypto

- `GET /api/crypto/summary`
- `GET /api/crypto/positions`
- `GET /api/crypto/positions/{asset_id}`
- `GET /api/crypto/wallets`
- `GET /api/crypto/wallets/{wallet_id}`
- `POST /api/crypto/price-update`
- `POST /api/crypto/report`

### Aktien/ETF

- `GET /api/equity/summary`
- `GET /api/equity/positions`
- `GET /api/equity/positions/{id}`
- `POST /api/equity/search`
- `POST /api/equity/price-update`

### Cash

- `GET /api/cash/summary`
- `POST /api/cash/initial-snapshot`
- `POST /api/cash/adjustment`

### Actions / Transactions

- `POST /api/transactions/initial-snapshot`
- `POST /api/transactions/buy`
- `POST /api/transactions/sell`
- `POST /api/transactions/dividend`
- `POST /api/transactions/manual-adjustment`

### Audit

- `GET /api/audit?entity_type=&entity_id=`

### Reports

- `GET /api/reports`
- `POST /api/reports/crypto`
- `GET /api/reports/{id}`

### Write endpoint rule

Every writing endpoint must implement:

1. preview payload validation;
2. review result with exact intended changes;
3. explicit confirm request;
4. audit log write;
5. no silent mutation.

No automated trading endpoint exists. No broker execution endpoint exists.

## DTO definitions

DTOs live under `src/jarvis_finance/api/schemas/`. User labels hide technical IDs, but API objects include stable IDs for follow-up actions.

### Money

```json
{
  "amount": "123.45",
  "currency": "CHF",
  "display": "CHF 123.45"
}
```

### Quantity

```json
{
  "amount": "0.12345678",
  "unit": "BTC",
  "display": "0.12345678 BTC"
}
```

### PortfolioSummary

- `base_currency`
- `total_portfolio_chf: Money`
- `crypto_chf: Money`
- `stock_chf: Money`
- `etf_chf: Money`
- `cash_chf: Money`
- `unvalued_position_count: int`
- `critical_alert_count: int`
- `last_price_update_at: string | null`
- `data_quality_status: user-friendly string`

### PositionSummary

- `id`
- `label`
- `asset_class`
- `platform`
- `quantity: Quantity`
- `market_value_chf: Money | null`
- `status`

### CryptoPosition

- `asset_id`
- `label`
- `symbol`
- `quantity: Quantity`
- `market_value_chf: Money | null`
- `price_chf: Money | null`
- `wallet_count`
- `status`
- `last_price_update_at`

### WalletSummary

- `wallet_id`
- `label`
- `wallet_type`
- `asset_count`
- `market_value_chf: Money | null`
- `status`

### EquityPosition

- `id`
- `instrument_id`
- `label`
- `ticker`
- `isin`
- `platform`
- `account_label`
- `asset_type`
- `quantity: Quantity`
- `price_original: Money | null`
- `market_value_chf: Money | null`
- `price_status`
- `fx_status`
- `status`

### CashPosition

- `id`
- `platform`
- `account_label`
- `currency`
- `amount: Money`
- `amount_chf: Money | null`
- `status`

### AlertSummary

- `severity`
- `label`
- `count`
- `message`

### AuditEvent

- `audit_id`
- `timestamp`
- `entity_type`
- `entity_id`
- `action`
- `summary`
- `created_by`

### ReportMetadata

- `report_id`
- `report_type`
- `title`
- `format`
- `generated_at`
- `data_quality_status`
- `location_label`

### InstrumentSearchCandidate

- `candidate_id`
- `label`
- `asset_class`
- `isin`
- `ticker`
- `exchange`
- `currency`
- `provider`
- `confidence_label`
- `review_required`

### TransactionPreview

- `preview_id`
- `action_type`
- `summary`
- `warnings`
- `required_confirmations`
- `expires_at`

### TransactionConfirmRequest

- `preview_id`
- `confirm: true`
- `user_note`
- `idempotency_key`

## Frontend UX target

### Command Center

Cards:

- Gesamtportfolio
- Crypto
- Aktien
- ETFs
- Cash
- Kritische Hinweise

Below cards:

- Asset Allocation chart
- Top Positionen
- Data Quality Hinweise
- Schnellaktionen

### Portfolio

- AG Grid table with row click, sorting, filtering, grouping by depot/asset class and column visibility.
- Right detail sidebar with position details, history and actions.

### Crypto

- Coin table with row click.
- Detail drawer with wallet allocation, CoinGecko link, price status and actions.
- Charts for allocation by coin and by wallet.

### Aktien & ETFs

- Positions grouped by depot/asset type.
- Row click and detail drawer.
- Price status and FX status are visible in user language.
- Actions: Korrigieren, Kaufen, Verkaufen, Dividende, Verlauf. Writes stay disabled until preview/confirm APIs exist.

### Position hinzufügen

Wizard:

1. Assetklasse
2. Suche
3. Auswahl
4. Menge/Depot/Datum
5. FX/Cost Basis
6. Review
7. Speichern

## Minimal vertical slice

### Backend v0

- FastAPI app starts.
- `GET /api/health`
- `GET /api/overview`
- `GET /api/crypto/positions`
- `GET /api/equity/positions`
- `GET /api/cash/summary`

### Frontend v0

- Vue app starts.
- Command Center page.
- Crypto positions table.
- Equity/Cash read-only table.
- Row click with detail panel for Crypto.
- No write actions.

Goal: prove better overview and row-detail interaction without rebuilding the system.

## Proposed repo structure

```text
src/jarvis_finance/
  api/
    main.py
    routers/
      health.py
      overview.py
      crypto.py
      equity.py
      cash.py
    schemas/
      common.py
      overview.py
      positions.py
      audit.py
      reports.py
      transactions.py
    dependencies.py
  services/
    portfolio_service.py
    crypto_service.py
    equity_service.py
    cash_service.py
    report_service.py

frontend/
  package.json
  vite.config.ts
  src/
    main.ts
    App.vue
    api/
    stores/
    components/
    pages/
    router/
```

For the MVP transition, `frontend/` stays in the same repository because API contracts and DTO changes are easier to review together. It must remain clearly separated and Git-safety must block build artifacts and environment files. A separate repo can be revisited after the API stabilizes.

## Security rules

- CORS only for local development origins.
- No API keys in frontend code or build artifacts.
- No real Runtime data in frontend fixtures or dist assets.
- No DB files in Git.
- No reports in Git.
- Git-safety blocks `.env`, `.env.local`, `frontend/dist`, `node_modules`, cache/build artifacts and runtime data formats.
- Backend reads Runtime secrets.
- Frontend uses only backend API.
- No automated trading.

## Test strategy

### Backend

- `pytest`
- FastAPI `TestClient`
- API returns expected read models on empty and synthetic Runtime DBs.
- Decimal values are strings, never JSON floats.
- Secret names may appear as configuration labels only; secret values must never leak.
- Routers delegate to services.
- Direct DB mutation is forbidden outside service/core write workflows.

### Frontend

- Vitest for component and store tests.
- API mock tests for tables/cards/drawers.
- Optional later Playwright smoke tests for the core User path.

## Non-goals for this sprint

- No complete migration.
- No full frontend build.
- No write actions in Vue.
- No Auth.
- No deployment.
- No broker import changes.
- No new provider integration.
- No Streamlit removal.

## Streamlit continuity

Streamlit remains:

- Admin/Operations UI;
- emergency fallback;
- quick debug/read-model view;
- host for existing MVP functions until Vue equivalents are proven.

## Consequences

Positive:

- Clean API boundary.
- Better UX potential without destabilizing the ledger.
- Reuse of Python business logic.
- Incremental migration possible.

Risks:

- Two UIs temporarily exist.
- DTO/service contracts must be maintained carefully.
- Frontend build artifacts need strict Git-safety.

Mitigation:

- Start with read-only vertical slice.
- Keep Streamlit stable.
- Add API tests before frontend write workflows.
- Enforce runtime/Git safety before every commit.
