summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGENTS.md115
-rw-r--r--CLAUDE.md118
-rw-r--r--README.md237
3 files changed, 171 insertions, 299 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 96532df..ac36c80 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,100 +1,77 @@
# Repository Guidelines
-## Project Structure & Module Organization
+## Structure
-Prism v2 is split into a FastAPI backend and a Next.js frontend.
+- `backend/app/` — FastAPI entrypoint (`main.py`), Pydantic schemas, `services/data_service.py` (yfinance + TTL cache), `db/watchlist.py` (SQLite).
+- `backend/tests/` — pytest. `pytest.ini` puts `backend/` on `pythonpath`.
+- `frontend/app/` — Next.js App Router. `frontend/components/`, `frontend/lib/`, `frontend/types/api.ts` (shared types — keep in sync with `backend/app/schemas.py`).
+- `systemd/`, `nginx/` — production deployment templates.
+- `design-system/` — brand tokens (`colors_and_type.css`) and Prism UI kit. Mirrored into `frontend/public/design-system/`.
-- `backend/app/` contains the API entrypoint, Pydantic schemas, SQLite watchlist storage, and data-service logic.
-- `backend/tests/` contains pytest coverage for API and watchlist behavior.
-- `frontend/app/` contains the Next.js App Router pages and global styles.
-- `frontend/components/`, `frontend/lib/`, and `frontend/types/` hold reusable UI, API helpers, formatting utilities, and shared TypeScript types.
-- `pytest.ini` sets `backend` on `pythonpath` so tests can import `app`.
-
-## Build, Test, and Development Commands
-
-Backend setup and local API:
+## Development
```bash
-cd backend
-python -m venv .venv
-source .venv/bin/activate
+# Backend (port 8001)
+cd backend && python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
-uvicorn app.main:app --reload
-```
-
-The API runs at `http://localhost:8000`.
-
-Frontend setup and local UI:
-
-```bash
-cd frontend
-npm install
-npm run dev
-```
+uvicorn app.main:app --reload --host 127.0.0.1 --port 8001
-The UI runs at `http://localhost:3000`. Use `npm run build` to validate the production Next.js build and `npm run lint` to run ESLint.
+# Frontend (port 3001)
+cd frontend && npm install
+NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8001 npm run dev -- --hostname 127.0.0.1 --port 3001
-Run backend tests from the repository root:
-
-```bash
+# Tests + lint
pytest
+cd frontend && npm run lint && npm run build
```
-## Coding Style & Naming Conventions
+## Production
-Python code uses type hints, small FastAPI route functions, and snake_case for modules, functions, and variables. Keep service logic in `backend/app/services/` and persistence concerns in `backend/app/db/`.
+Topology: uvicorn on `127.0.0.1:8001`, `next start` on `127.0.0.1:3001`, nginx TLS reverse-proxy (`/api/` → backend, `/` → frontend). See `README.md` for the full deploy and redeploy commands. Service user is `www-data`; code lives at `/var/www/prism-v2/`.
-TypeScript uses React function components, PascalCase component names, camelCase values, and the `@/` import alias for frontend modules. Keep shared API shapes in `frontend/types/api.ts` and formatting helpers in `frontend/lib/format.ts`. Frontend linting is configured in `frontend/eslint.config.mjs`.
+Redeploy in one shot:
-## Testing Guidelines
+```bash
+cd /var/www/prism-v2 && sudo -u www-data git pull origin master
+sudo -u www-data backend/.venv/bin/pip install -r backend/requirements.txt
+sudo -u www-data env HOME=/var/www/prism-v2/frontend NPM_CONFIG_CACHE=/var/www/prism-v2/frontend/.npm npm --prefix frontend ci
+sudo -u www-data env HOME=/var/www/prism-v2/frontend NPM_CONFIG_CACHE=/var/www/prism-v2/frontend/.npm npm --prefix frontend run build
+sudo systemctl restart prismv2-backend.service prismv2-frontend.service
+```
-Backend tests use pytest and should be named `test_*.py`. Prefer focused tests that mock external market-data calls with `monkeypatch`, as existing tests do. Add tests when changing FastAPI routes, watchlist persistence, or data normalization behavior. There is no frontend test runner yet, so use `npm run lint` and `npm run build` for frontend validation.
+## Coding Style
-## Commit & Pull Request Guidelines
+- Python: type hints, snake_case, small route functions. Service logic under `backend/app/services/`, persistence under `backend/app/db/`.
+- TypeScript: React function components, PascalCase components, camelCase values, `@/` alias. Shared API shapes in `frontend/types/api.ts`; formatters in `frontend/lib/format.ts`.
-This checkout has no existing commit history. Use concise, imperative commit subjects such as `Add watchlist tests` or `Fix ticker history error handling`.
+## Testing
-Pull requests should include a short summary, test results (`pytest`, `npm run lint`, `npm run build` when relevant), linked issues if applicable, and screenshots or screen recordings for visible UI changes. Note any required environment variables such as `FMP_API_KEY` or `FINNHUB_API_KEY`.
+Backend tests are `test_*.py` under `backend/tests/`. Stub `data_service` with `monkeypatch` — never hit live yfinance. Use `tmp_path` for any test that touches SQLite. No frontend test runner; rely on `npm run lint` and `npm run build`.
-## Design System (`design-system/`)
+## Commits & PRs
-The repo includes a full personal-brand design system at `design-system/`. All frontend work must follow it — never invent colors, radii, or type choices.
+Concise imperative subjects (`Add watchlist tests`, `Fix history error handling`). PRs include: short summary, `pytest`/`npm run lint`/`npm run build` results, UI screenshots when relevant, and any env vars touched.
-**Token entry point:** `design-system/colors_and_type.css` (also mirrored into `frontend/public/design-system/` for serving). Import it for all new components; it declares every CSS custom property: ink surfaces, fg tints, brass/champagne accent, oxford navy, burgundy, semantic gain/loss/caution/info colors, spacing scale, radii, shadows, and font stacks.
+## Design System
-**Typefaces:**
-- `--font-serif` — EB Garamond (variable). Display + body serif; italic at large sizes is the signature move.
-- `--font-sans` — IBM Plex Sans. UI labels, eyebrows, buttons, badges.
-- `--font-mono` — IBM Plex Mono. All prices, percentages, and tabular numerics (`font-variant-numeric: tabular-nums`).
+All frontend work must follow `design-system/`. Import `design-system/colors_and_type.css` (CSS custom properties for ink/fg/champagne/semantic colors, spacing, radii, fonts). Hard rules:
-**Visual rules that must not be broken:**
-- Background is `#0B0E13` (ink-0), never pure black. Text is `#F2ECDC` (fg-0), never pure white.
-- Primary accent is champagne `#C2AA7A` — for links, focus rings, eyebrows, button fills. Never as a large background fill.
-- Semantic colors: gain `#4F8C5E`, loss `#B5494B`, caution `#C49545`, info `#4A78B5`.
-- Card radius is 6px; buttons are 2px. No radius above 6px except capsule chips (999px). No bubbly rounding.
-- Hairline `1px` borders only (`#232934`). No gradients except the chart area fill. No glass effects.
-- Transitions are 150ms ease. No bounces, springs, or scroll-jacking.
-- No emoji — ever. Use SVG icons from `design-system/assets/icons/` or unicode geometrics (`▲ ▼ ◈ ✦ ↗ ·`).
+- Background `#0B0E13`, text `#F2ECDC` — never pure black/white.
+- Champagne `#C2AA7A` for links/focus/eyebrows/buttons; never a large background.
+- Semantic: gain `#4F8C5E`, loss `#B5494B`, caution `#C49545`, info `#4A78B5`.
+- Card radius 6px, button radius 2px, capsule chips 999px. Nothing else rounded.
+- Hairline `1px` borders only (`#232934`). No gradients except chart fill. No glass effects.
+- Fonts: `--font-serif` EB Garamond (display/body), `--font-sans` IBM Plex Sans (UI), `--font-mono` IBM Plex Mono (all numerics, with `font-variant-numeric: tabular-nums`).
+- 150ms ease transitions. No bounces. No emoji — use SVGs in `design-system/assets/icons/` or unicode geometrics (`▲ ▼ ◈ ✦ ↗ ·`).
-**Prism UI kit:** `design-system/ui_kits/prism/` is a forward-looking redesign showing the target component shapes: `<Sidebar>`, `<TopBar>`, `<TickerHeader>`, `<KPIStrip>`, `<ChartCard>`, `<QuoteTable>`, `<ValuationPanel>`, `<FilingsList>`, `<InsiderRow>`. Read `parts{1,2,3}.jsx` and `prism.css` before building new dashboard surfaces.
+Read `design-system/ui_kits/prism/parts{1,2,3}.jsx` and `prism.css` before building new dashboard surfaces.
## Reference: Prism v1 (`../prism`)
-The old Streamlit-based app at `../prism` is the canonical reference for finance logic being ported into v2. Do not import from it at runtime, but read it when implementing new financial features.
-
-Key source files to consult:
-
-- **`services/data_service.py`** — yfinance wrappers for price history, financials (income/balance/cash flow), options chain, insider transactions, SEC filings, market indices, and analyst targets. Contains `compute_ttm_ratios()` which self-computes P/E, P/B, P/S, EV/EBITDA, margins, ROE/ROA/ROIC, leverage ratios, and dividend metrics from raw quarterly statements (avoiding FMP quota). Includes outlier safeguards (e.g. P/B and EV/Sales capped at 100).
-- **`services/valuation_service.py`** — DCF engine (`run_dcf()`) using Gordon Growth Model: projects FCF with median historical growth (capped ±50%, skips sign flips), computes terminal value, bridges enterprise value to equity per share via net debt/preferred equity/minority interest. Also has `run_ev_ebitda()`, `run_ev_revenue()`, `run_price_to_book()`.
-- **`services/fmp_service.py`** — FMP REST calls for peers, forward analyst estimates, historical ratios, and news. Falls back to yfinance when FMP is unavailable.
-- **`services/news_service.py`** — Finnhub sentiment wrapper.
-- **`components/overview.py`** — signal badge logic (6 signals), 52-week range bar, short interest rendering.
-- **`utils/formatters.py`** — number formatting helpers (market cap abbreviations, percent display, etc.).
-
-**Caching strategy in v1:** `@st.cache_data` with staggered TTLs — financials at 1 h, indices at 5 min, search at 60 s. Mirror these TTLs when adding new cached endpoints in v2's `data_service.py`.
+Read-only reference for finance logic — do not import at runtime. Key files: `services/data_service.py` (TTM ratios, options, filings), `services/valuation_service.py` (`run_dcf`, `run_ev_ebitda`, `run_ev_revenue`, `run_price_to_book`), `services/fmp_service.py`, `services/news_service.py`, `components/overview.py` (signal badges, 52w range, short interest), `utils/formatters.py`.
-**Data source hierarchy in v1:** yfinance primary → FMP fallback → Finnhub for news/sentiment. FMP free tier is 250 req/day; `FMP_API_KEY` and `FINNHUB_API_KEY` are set via `.env`.
+v1 caching TTLs to mirror: financials 1 h, indices 5 min, search 60 s. Data hierarchy: yfinance → FMP fallback → Finnhub for sentiment.
-## Security & Configuration Tips
+## Security
-Do not commit API keys, local virtual environments, `node_modules/`, or generated SQLite data files. Optional market-data keys should be provided through the environment or a local `.env` file.
+Never commit API keys, `.env`, `node_modules/`, `backend/.venv/`, or `backend/data/prism.db`. Optional keys (`FMP_API_KEY`, `FINNHUB_API_KEY`) come from `.env` or the environment.
diff --git a/CLAUDE.md b/CLAUDE.md
index ea7a26f..8ac44a0 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,98 +1,92 @@
# CLAUDE.md
-This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+Guidance for Claude Code working in this repo.
## What This Is
-Prism v2 is a financial overview app: FastAPI backend (Python) + Next.js frontend (TypeScript). The backend pulls market data from yfinance with TTL caching and stores a watchlist in SQLite. The frontend renders a single Overview page with ticker search, price chart (Plotly), market bar, and watchlist.
+Prism v2: FastAPI backend (yfinance + SQLite watchlist) + Next.js frontend (Plotly chart, ticker search, market bar, watchlist). Single Overview page.
## Commands
-**Backend — run from repo root or `backend/`:**
```bash
-# Start API (ports 8001 to avoid conflicts)
+# Backend (port 8001 to avoid conflicts with other local services)
backend/.venv/bin/uvicorn app.main:app --reload --host 127.0.0.1 --port 8001
-
-# Run all tests (pytest.ini sets pythonpath=backend)
-pytest
-
-# Run a single test file
-pytest backend/tests/test_api.py
-
-# Compile-check without running
+pytest # all backend tests
+pytest backend/tests/test_api.py # single file
backend/.venv/bin/python -m py_compile backend/app/main.py backend/app/schemas.py
-```
-**Frontend — run from `frontend/`:**
-```bash
+# Frontend (port 3001)
NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8001 npm run dev -- --hostname 127.0.0.1 --port 3001
-npm run lint # ESLint
-npm run build # Production build (validates TypeScript + Next.js)
+npm run lint
+npm run build
```
+`./scripts/stack.sh {start|stop|restart|status}` runs both locally; logs in `.run/logs/`.
+
## Architecture
-### Backend data flow
-`main.py` routes → `services/data_service.py` → yfinance (primary source).
-`data_service.py` has three data-fetching helpers that `get_ticker_overview()` calls in priority order: `get_company_info()` (full yfinance `.info`), `get_fast_info()` (lightweight), then search + price history as last resort. `OverviewMeta.sources` records which source each field came from.
+**Backend:** `app/main.py` routes → `app/services/data_service.py` → yfinance. `get_ticker_overview()` tries `get_company_info()` (full `.info`), then `get_fast_info()`, then search + price history. `OverviewMeta.sources` records which source supplied each field. TTL caching in `HISTORY_CACHE`.
-### Key backend files
-- `app/main.py` — FastAPI routes, CORS config, lifespan (DB init)
-- `app/schemas.py` — all Pydantic response models
-- `app/services/data_service.py` — yfinance wrapper, TTL cache (`HISTORY_CACHE`), signal/stat computation
-- `app/db/watchlist.py` — SQLite CRUD; watchlist capped at 10 symbols, normalized to uppercase
+Key files:
+- `app/main.py` — routes, CORS, lifespan (DB init)
+- `app/schemas.py` — Pydantic models (keep in sync with `frontend/types/api.ts`)
+- `app/services/data_service.py` — yfinance wrapper, caching, signal/stat computation
+- `app/db/watchlist.py` — SQLite CRUD; uppercase, capped at 10 symbols
-### Frontend data flow
-`app/page.tsx` (Overview shell) → `lib/api.ts` (typed fetch client) → backend.
-Selected ticker lives in the URL query param `?ticker=SYMBOL`. Types are shared in `frontend/types/api.ts` — keep these in sync with `backend/app/schemas.py`.
+**Frontend:** `app/page.tsx` → `lib/api.ts` (typed fetch) → backend. Selected ticker in URL `?ticker=SYMBOL`.
-### Testing approach
-Backend tests use `monkeypatch` to stub `data_service` functions — never make live yfinance calls in tests. Use a `tmp_path` fixture for any test that touches SQLite. There is no frontend test runner; use `npm run lint` and `npm run build` for frontend validation.
+**Tests:** Stub `data_service` with `monkeypatch` — never hit live yfinance. Use `tmp_path` for SQLite tests. No frontend test runner; use lint + build.
-## Design System (`design-system/`)
+## Production
-The repo includes a full personal-brand design system at `design-system/`. All frontend work must follow it — never invent colors, radii, or type choices.
+Topology: uvicorn on `127.0.0.1:8001`, `next start` on `127.0.0.1:3001`, nginx TLS reverse-proxy at `prism.tylerhoang.xyz` (`/api/` → backend, `/` → frontend). Service user `www-data`, code at `/var/www/prism-v2/`. Full deploy instructions in `README.md`.
-**Token entry point:** `design-system/colors_and_type.css` (also mirrored into `frontend/public/design-system/` for serving). Import it for all new components; it declares every CSS custom property: ink surfaces, fg tints, brass/champagne accent, oxford navy, burgundy, semantic gain/loss/caution/info colors, spacing scale, radii, shadows, and font stacks.
+**Redeploy:**
+```bash
+cd /var/www/prism-v2 && sudo -u www-data git pull origin master
+sudo -u www-data backend/.venv/bin/pip install -r backend/requirements.txt
+sudo -u www-data env HOME=/var/www/prism-v2/frontend NPM_CONFIG_CACHE=/var/www/prism-v2/frontend/.npm npm --prefix frontend ci
+sudo -u www-data env HOME=/var/www/prism-v2/frontend NPM_CONFIG_CACHE=/var/www/prism-v2/frontend/.npm npm --prefix frontend run build
+sudo systemctl restart prismv2-backend.service prismv2-frontend.service
+```
-**Typefaces:**
-- `--font-serif` — EB Garamond (variable). Display + body serif; italic at large sizes is the signature move.
-- `--font-sans` — IBM Plex Sans. UI labels, eyebrows, buttons, badges.
-- `--font-mono` — IBM Plex Mono. All prices, percentages, and tabular numerics (`font-variant-numeric: tabular-nums`).
+**Logs / status:**
+```bash
+sudo systemctl status prismv2-backend.service prismv2-frontend.service --no-pager
+sudo journalctl -u prismv2-backend.service -f
+```
-**Visual rules that must not be broken:**
-- Background is `#0B0E13` (ink-0), never pure black. Text is `#F2ECDC` (fg-0), never pure white.
-- Primary accent is champagne `#C2AA7A` — for links, focus rings, eyebrows, button fills. Never as a large background fill.
-- Semantic colors: gain `#4F8C5E`, loss `#B5494B`, caution `#C49545`, info `#4A78B5`.
-- Card radius is 6px; buttons are 2px. No radius above 6px except capsule chips (999px). No bubbly rounding.
-- Hairline `1px` borders only (`#232934`). No gradients except the chart area fill. No glass effects.
-- Transitions are 150ms ease. No bounces, springs, or scroll-jacking.
-- No emoji — ever. Use SVG icons from `design-system/assets/icons/` or unicode geometrics (`▲ ▼ ◈ ✦ ↗ ·`).
+**Rules:**
+- Browser code must NOT hardcode a localhost API base. `frontend/lib/api.ts` defaults to empty so production hits same-origin `/api/*` via nginx.
+- Frontend runs `next start` in production — never `next dev`.
+- nginx config and systemd units are in-tree at `nginx/` and `systemd/`; redeploying those requires sudo cp + reload.
-**Prism UI kit:** `design-system/ui_kits/prism/` is a forward-looking redesign showing the target component shapes: `<Sidebar>`, `<TopBar>`, `<TickerHeader>`, `<KPIStrip>`, `<ChartCard>`, `<QuoteTable>`, `<ValuationPanel>`, `<FilingsList>`, `<InsiderRow>`. Read `parts{1,2,3}.jsx` and `prism.css` before building new dashboard surfaces.
+## Design System (`design-system/`)
-**Skill:** `design-system/SKILL.md` is a Claude Code skill (`tyler-hoang-design`) — invoke it when doing design-heavy frontend work.
+All frontend work must follow it — never invent colors, radii, or type choices. Import `design-system/colors_and_type.css` (mirrored to `frontend/public/design-system/`).
-## Reference: Prism v1 (`../prism`)
+- Background `#0B0E13`, text `#F2ECDC`. Champagne `#C2AA7A` for accents (never a large fill).
+- Semantic: gain `#4F8C5E`, loss `#B5494B`, caution `#C49545`, info `#4A78B5`.
+- Card radius 6px, button 2px, chip 999px. Hairline `1px` borders (`#232934`). No gradients (except chart fill), no glass, no bounces.
+- Fonts: `--font-serif` EB Garamond, `--font-sans` IBM Plex Sans, `--font-mono` IBM Plex Mono (`tabular-nums` on all numerics).
+- No emoji. SVGs from `design-system/assets/icons/` or unicode geometrics (`▲ ▼ ◈ ✦ ↗ ·`).
-The old Streamlit-based app at `../prism` is the canonical reference for finance logic being ported into v2. Do not import from it at runtime, but read it when implementing new financial features.
+Read `design-system/ui_kits/prism/parts{1,2,3}.jsx` and `prism.css` before building new dashboard surfaces. Skill: `design-system/SKILL.md` (`tyler-hoang-design`).
-Key source files to consult:
+## Reference: Prism v1 (`../prism`)
-- **`services/data_service.py`** — yfinance wrappers for price history, financials (income/balance/cash flow), options chain, insider transactions, SEC filings, market indices, and analyst targets. Contains `compute_ttm_ratios()` which self-computes P/E, P/B, P/S, EV/EBITDA, margins, ROE/ROA/ROIC, leverage ratios, and dividend metrics from raw quarterly statements (avoiding FMP quota). Includes outlier safeguards (e.g. P/B and EV/Sales capped at 100).
-- **`services/valuation_service.py`** — DCF engine (`run_dcf()`) using Gordon Growth Model: projects FCF with median historical growth (capped ±50%, skips sign flips), computes terminal value, bridges enterprise value to equity per share via net debt/preferred equity/minority interest. Also has `run_ev_ebitda()`, `run_ev_revenue()`, `run_price_to_book()`.
-- **`services/fmp_service.py`** — FMP REST calls for peers, forward analyst estimates, historical ratios, and news. Falls back to yfinance when FMP is unavailable.
-- **`services/news_service.py`** — Finnhub sentiment wrapper.
-- **`components/overview.py`** — signal badge logic (6 signals), 52-week range bar, short interest rendering.
-- **`utils/formatters.py`** — number formatting helpers (market cap abbreviations, percent display, etc.).
+Read-only reference for finance logic — do not import at runtime.
-**Caching strategy in v1:** `@st.cache_data` with staggered TTLs — financials at 1 h, indices at 5 min, search at 60 s. Mirror these TTLs when adding new cached endpoints in v2's `data_service.py`.
+- `services/data_service.py` — yfinance wrappers; `compute_ttm_ratios()` self-computes P/E, P/B, P/S, EV/EBITDA, margins, ROE/ROA/ROIC, leverage, dividends from quarterly statements. Caps outliers (P/B and EV/Sales at 100).
+- `services/valuation_service.py` — `run_dcf()` (Gordon Growth, median historical FCF growth capped ±50%, skips sign flips, EV→equity bridge), plus `run_ev_ebitda`, `run_ev_revenue`, `run_price_to_book`.
+- `services/fmp_service.py`, `services/news_service.py` — FMP peers/estimates/news, Finnhub sentiment.
+- `components/overview.py`, `utils/formatters.py` — signal badges, 52w range, number formatting.
-**Data source hierarchy in v1:** yfinance primary → FMP fallback → Finnhub for news/sentiment. FMP free tier is 250 req/day; `FMP_API_KEY` and `FINNHUB_API_KEY` are set via `.env`.
+v1 cache TTLs to mirror: financials 1 h, indices 5 min, search 60 s. Data hierarchy: yfinance → FMP fallback → Finnhub for sentiment.
-## Environment Variables
+## Environment
-Copy `.env.example` → `.env`. The current feature set works without API keys; yfinance is the active data source.
+`.env.example` → `.env`. Current feature set works without keys.
-- `NEXT_PUBLIC_API_BASE_URL` — frontend points to backend (default `http://localhost:8000`)
-- `FMP_API_KEY`, `FINNHUB_API_KEY` — reserved for future enrichment endpoints (see v1 `fmp_service.py` / `news_service.py`)
+- `NEXT_PUBLIC_API_BASE_URL` — empty in production (same-origin via nginx); `http://127.0.0.1:8001` locally
+- `FMP_API_KEY`, `FINNHUB_API_KEY` — reserved for future enrichment
diff --git a/README.md b/README.md
index fb492f8..9e7f689 100644
--- a/README.md
+++ b/README.md
@@ -1,226 +1,127 @@
# Prism v2
-Prism v2 is a monorepo rewrite of Prism's Overview experience with a FastAPI backend and a Next.js frontend. This first slice covers:
+Financial overview app. FastAPI backend (yfinance + SQLite watchlist) and Next.js frontend (Plotly chart, ticker search, market bar, watchlist).
-- ticker search
-- selected ticker profile and quote
-- market bar
-- historical price chart
-- overview signal and stat cards
-- persisted watchlist backed by SQLite
+## Stack
-The implementation copies and adapts finance logic from Prism v1 into `backend/`; it does not import code from the old repo at runtime.
+- Backend: Python 3.14+, FastAPI, uvicorn, yfinance, SQLite
+- Frontend: Node 20+, Next.js (App Router), TypeScript
+- Production: systemd + nginx (TLS via certbot)
-## Repo Layout
+## Layout
-- `backend/`
- - `app/main.py` FastAPI application and routes
- - `app/services/data_service.py` yfinance-backed Overview service layer with TTL caching
- - `app/db/watchlist.py` SQLite persistence for the default local profile
- - `data/prism.db` local watchlist database
- - `tests/` backend unit and API smoke tests
-- `frontend/`
- - `app/page.tsx` Overview shell
- - `components/PriceChart.tsx` Plotly price chart
- - `lib/api.ts` typed REST client
- - `types/api.ts` shared frontend response types
-- `scripts/stack.sh` unified start/stop/restart/status script
-- `systemd/` systemd unit files for running as a system service
-- `nginx/` nginx server block config (proxies `/api/` to backend, `/` to frontend)
-- `.env.example` optional environment variables
-- `pytest.ini` backend pytest import path config
-
-## Requirements
-
-- Python 3.14+
-- Node.js 20+
-- npm
+- `backend/app/` — FastAPI app, services, SQLite watchlist
+- `frontend/app/` — Next.js App Router; shared types in `frontend/types/api.ts`
+- `systemd/` — service units (`prismv2-backend.service`, `prismv2-frontend.service`)
+- `nginx/` — TLS server block (`/api/` → :8001, `/` → :3001)
+- `scripts/stack.sh` — local dev start/stop/restart/status
+- `design-system/` — brand tokens + Prism UI kit (mirrored to `frontend/public/design-system/`)
## Environment
-Copy `.env.example` to `.env` if you want to set optional keys.
-
-Supported variable names are unchanged from Prism v1:
-
-- `FMP_API_KEY`
-- `FINNHUB_API_KEY`
-- `NEXT_PUBLIC_API_BASE_URL`
+Copy `.env.example` → `.env`. Current feature set works without keys.
-The current Overview slice works without FMP or Finnhub keys because it relies on yfinance for the active endpoints.
-
-## Backend Setup
-
-```bash
-cd backend
-python -m venv .venv
-source .venv/bin/activate
-pip install -r requirements.txt
-```
+- `NEXT_PUBLIC_API_BASE_URL` — leave empty in production (same-origin `/api/*` via nginx)
+- `FMP_API_KEY`, `FINNHUB_API_KEY` — reserved for future enrichment
-Run the API:
+## Local Development
```bash
+# Backend
+cd backend && python -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/uvicorn app.main:app --reload --host 127.0.0.1 --port 8001
+
+# Frontend
+cd frontend && npm install
+NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8001 npm run dev -- --hostname 127.0.0.1 --port 3001
```
-Primary endpoints:
+Or use `./scripts/stack.sh {start|stop|restart|status}` (logs in `.run/logs/`).
+
+## API
- `GET /health`
-- `GET /api/search?q=...`
+- `GET /api/search?q=`
- `GET /api/market/indices`
- `GET /api/tickers/{symbol}/overview`
- `GET /api/tickers/{symbol}/history?period=1m|3m|6m|1y|5y`
-- `GET /api/watchlist`
-- `POST /api/watchlist/{symbol}`
-- `DELETE /api/watchlist/{symbol}`
-
-Notes:
-
-- SQLite lives at `backend/data/prism.db`
-- the backend seeds one `default` profile automatically
-- watchlist symbols are normalized to uppercase
-- watchlist size is capped at 10 symbols
-
-## Frontend Setup
+- `GET|POST|DELETE /api/watchlist[/{symbol}]` (uppercase, capped at 10)
-```bash
-cd frontend
-npm install
-```
+SQLite lives at `backend/data/prism.db`. Backend seeds a `default` profile on startup.
-Run the frontend against the local backend:
+## Production Deployment
-```bash
-NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8001 npm run dev -- --hostname 127.0.0.1 --port 3001
-```
-
-The UI stores the selected ticker in the URL, for example `/?ticker=AAPL`.
+Target topology: backend on `127.0.0.1:8001`, frontend on `127.0.0.1:3001`, nginx terminates TLS and reverse-proxies. Code lives at `/var/www/prism-v2/` owned by `www-data`.
-Production frontend runtime:
+### 1. Initial install
```bash
-npm run build
-npm run start -- --hostname 127.0.0.1 --port 3001
-```
-
-Important API-base rule:
-
-- In production, browser requests should use same-origin `/api/*` through nginx.
-- Do not hardcode a localhost fallback API base in frontend browser code.
-- `frontend/lib/api.ts` should default to an empty base when `NEXT_PUBLIC_API_BASE_URL` is unset.
-
-## Quick Start
+sudo mkdir -p /var/www && sudo chown www-data:www-data /var/www
+sudo -u www-data git clone <repo> /var/www/prism-v2
+cd /var/www/prism-v2
-`scripts/stack.sh` manages both services as a local development stack:
+# Backend
+sudo -u www-data python3 -m venv backend/.venv
+sudo -u www-data backend/.venv/bin/pip install -r backend/requirements.txt
-```bash
-./scripts/stack.sh start # start backend + frontend in background
-./scripts/stack.sh stop # stop both
-./scripts/stack.sh restart # stop then start
-./scripts/stack.sh status # show running PIDs and URLs
+# Frontend (writable npm cache required for www-data)
+sudo mkdir -p frontend/.npm && sudo chown -R www-data:www-data frontend
+sudo -u www-data env HOME=/var/www/prism-v2/frontend NPM_CONFIG_CACHE=/var/www/prism-v2/frontend/.npm \
+ npm --prefix frontend ci
+sudo -u www-data env HOME=/var/www/prism-v2/frontend NPM_CONFIG_CACHE=/var/www/prism-v2/frontend/.npm \
+ npm --prefix frontend run build
```
-Notes:
-
-- expects `backend/.venv` and `frontend/node_modules` to already exist
-- default ports are backend `8001` and frontend `3001`
-- logs are written to `.run/logs/`
-- override hosts/ports via env vars: `BACKEND_HOST`, `BACKEND_PORT`, `FRONTEND_HOST`, `FRONTEND_PORT`
-
-## Running in Production (systemd + nginx)
-
-The production model is:
-
-- backend on `127.0.0.1:8001` via `uvicorn`
-- frontend on `127.0.0.1:3001` via `next start` (not `next dev`)
-- nginx TLS reverse proxy:
- - `/api/*` -> backend
- - `/` -> frontend
-
-Unit templates live in `systemd/` and nginx server block template lives in `nginx/`.
-
-Install system services:
+### 2. systemd
```bash
-sudo cp systemd/prismv2-backend.service /etc/systemd/system/
-sudo cp systemd/prismv2-frontend.service /etc/systemd/system/
+sudo cp systemd/prismv2-backend.service systemd/prismv2-frontend.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now prismv2-backend.service prismv2-frontend.service
```
-Install nginx site:
+### 3. nginx + TLS
```bash
sudo cp nginx/prism.tylerhoang.xyz.conf /etc/nginx/sites-available/prism.tylerhoang.xyz
-sudo ln -sf /etc/nginx/sites-available/prism.tylerhoang.xyz /etc/nginx/sites-enabled/prism.tylerhoang.xyz
-sudo nginx -t
-sudo systemctl reload nginx
-```
-
-Create writable npm cache for service user:
-
-```bash
-sudo mkdir -p /var/www/prism-v2/frontend/.npm
-sudo chown -R www-data:www-data /var/www/prism-v2/frontend
-```
-
-Service controls:
-
-```bash
-sudo systemctl status prismv2-backend.service --no-pager
-sudo systemctl status prismv2-frontend.service --no-pager
-sudo systemctl restart prismv2-backend.service prismv2-frontend.service
-sudo journalctl -u prismv2-backend.service -f
-sudo journalctl -u prismv2-frontend.service -f
+sudo ln -sf /etc/nginx/sites-available/prism.tylerhoang.xyz /etc/nginx/sites-enabled/
+sudo certbot --nginx -d prism.tylerhoang.xyz # first time only
+sudo nginx -t && sudo systemctl reload nginx
```
-Production deploy refresh:
+### 4. Redeploy
```bash
cd /var/www/prism-v2
-git pull origin master
-
-cd backend
-python3 -m venv .venv
-.venv/bin/pip install -r requirements.txt
-
-cd ../frontend
-npm ci
-npm run build
-
+sudo -u www-data git pull origin master
+sudo -u www-data backend/.venv/bin/pip install -r backend/requirements.txt
+sudo -u www-data env HOME=/var/www/prism-v2/frontend NPM_CONFIG_CACHE=/var/www/prism-v2/frontend/.npm \
+ npm --prefix frontend ci
+sudo -u www-data env HOME=/var/www/prism-v2/frontend NPM_CONFIG_CACHE=/var/www/prism-v2/frontend/.npm \
+ npm --prefix frontend run build
sudo systemctl restart prismv2-backend.service prismv2-frontend.service
-sudo systemctl reload nginx
```
-## Verification
-
-Backend:
+### Ops
```bash
-backend/.venv/bin/python -m py_compile backend/app/main.py backend/app/schemas.py backend/app/services/data_service.py backend/app/db/watchlist.py
-backend/.venv/bin/python -m pytest backend/tests
-```
-
-Frontend:
+sudo systemctl status prismv2-backend.service prismv2-frontend.service --no-pager
+sudo journalctl -u prismv2-backend.service -f
+sudo journalctl -u prismv2-frontend.service -f
-```bash
-cd frontend
-npm run lint
-npm run build
+curl -i http://127.0.0.1:8001/health
+curl -i https://prism.tylerhoang.xyz/api/search?q=AAPL
```
-Production smoke checks:
+## Verification
```bash
-curl -i http://127.0.0.1:8001/health
-curl -i "https://prism.tylerhoang.xyz/api/search?q=AAPL"
+pytest # backend tests (pytest.ini sets pythonpath)
+cd frontend && npm run lint && npm run build
```
-## Current Local URLs
-
-If port `8000` is already occupied on your machine, use:
-
-- backend: `http://127.0.0.1:8001`
-- frontend: `http://127.0.0.1:3001`
+## Production Rules
-Those are the ports used by the current local bootstrap.
+- Do not hardcode a localhost API base in frontend browser code. `frontend/lib/api.ts` must default to empty so the browser hits same-origin `/api/*`.
+- Never commit `.env`, `node_modules/`, `backend/.venv/`, or `backend/data/prism.db`.
+- Watchlist is normalized to uppercase and capped at 10 symbols.