# CLAUDE.md Bitcoin price tracker UI — Python FastAPI web application. Serves the HTML/Tailwind UI and proxies live price data from btcdata over WebSocket. ## Build & Development Commands - `make run` — Start dev server on port 4000; creates `.venv` and installs `requirements.txt` on first run (the `venv` target, keyed off `requirements.txt`) - `cp .env.example .env` — optional; `.env` is `-include`d, so a missing one is not fatal - `make run-local` — Same, but with `BTCDATA_URL=$BTCDATA_LOCAL_URL` - `make tailwind` — Watch Tailwind CSS for changes - `make test` — Runs `pytest tests/`, but there is no `tests/` directory yet and pytest is not in `requirements.txt` - `make venv` — Bootstrap the virtualenv without starting the server ## Docker Docker runs alongside local dev on different ports: | Service | Local dev | Docker | |----------|----------------|--------------------------------------| | btcprice | localhost:4000 | localhost:4041 (container port 4040) | | btcdata | localhost:4100 | localhost:4102 | `docker compose up -d` here starts **btcprice only**. Both projects attach to the external `btc-network` (`docker network create btc-network` once), and btcdata must be started from `../btcdata`. Inside the network btcprice reaches it at `http://btcdata:${DOCKER_BTCDATA_PORT:-4102}`. Note: `Dockerfile` bakes in a stale `ENV BTCDATA_URL=http://btcdata:4101`; compose always overrides it, so it only matters if the image is run outside compose. `ENV PORT` is likewise unused — the port in `CMD` is hardcoded. ## Deployment `./deploy-citadel.sh` — build locally for the remote's arch, `docker save | ssh docker load`, `docker compose up -d --no-build` in `/home/pmp/apps/btcprice` on citadel (192.168.50.195). Mirrors `../elprice/deploy-citadel.sh`; no registry. - `docker-compose.citadel.yml` → remote `docker-compose.yml`; `.env.citadel` → remote `.env`. - btcdata also runs on citadel, so both attach to the external `btc-network` there and `BTCDATA_URL` is `http://btcdata:4102` (service name, not a LAN address). The deploy script creates the network on the remote if it is missing. - Live at http://192.168.50.195:4041. ## Architecture - **Framework:** FastAPI - **Server:** Uvicorn - **Templates:** Inline HTML strings + HTMX (WebSocket extension) - **Styling:** Tailwind CSS v4 - **Data backend:** btcdata (separate service at `BTCDATA_URL`, default `http://localhost:4100`) - **No database** — all data comes from btcdata ### btcdata connection The browser connects only to btcprice: `ws-connect="/ws/price"` and `ws-connect="/ws/strike"`. Each endpoint runs `_ws_proxy`, which opens an upstream WebSocket to btcdata's `/api/price/ws` and `/api/strike/ws`, converts each JSON message to an HTML fragment (`price_json_to_html`, `strike_json_to_html`) and forwards it. `BTCDATA_URL` is the single point of contact; `_btcdata_ws_url` rewrites its scheme to `ws`/`wss`. ## Source Layout btcprice acts as a WebSocket proxy between the browser and btcdata: 1. Browser connects via HTMX WebSocket to `/ws/price` or `/ws/strike` 2. btcprice proxies to btcdata's `/api/price/ws` or `/api/strike/ws` 3. JSON from btcdata is transformed into HTML fragments (in `price_json_to_html` / `strike_json_to_html`) 4. HTMX swaps the HTML into the DOM by matching element IDs (`price-display`, `strike-display`) ### Key Conventions - Numbers use Danish locale formatting (dots for thousands, commas for decimals) via `da_fmt()` - Price changes are color-coded: green (up), red (down), gray (unchanged) - All HTML is built as string concatenation in Python — no template engine