Most scanners in this space are the same shape: ingest a feed, push it through a hosted model, render a dashboard. That's a fine product. It's also a subscription, a dependency, and a privacy trade-off I didn't want to make for my own trading data.
FIRSTLIGHT inverts that. The scanner runs locally — SQLite on disk, FastAPI serving a Next.js frontend over a WebSocket, and enrichment handled by Ollama on the same machine. Nothing leaves the box unless I decide it should.
The four-layer shape
I've settled on the same architectural pattern across every fullstack project I build now, mapped consistently across both sides of the stack:
- Route — the entry point (
app/api/*on the frontend, a FastAPI router on the backend) - Service — orchestration, no I/O of its own
- Repository — the only layer that touches SQLite
- Model — typed shape shared across the boundary
The satisfying part is that FastAPI's Depends() maps almost exactly onto how I already think about React hooks — a function that resolves what a layer needs before it runs, without that layer knowing where the dependency came from.
What enrichment actually does
Every symbol that crosses a momentum threshold gets queued for enrichment: a local model pass that adds a plain-language read on top of the numeric signal. No round trip, no rate limit, no token bill scaling with how many symbols I want to watch.
The trade-off is real — local models are slower and less capable than anything hosted. For this use case, that trade is worth it. I'm not asking the model to reason hard; I'm asking it to describe what's already computed.
More on the WebSocket push layer and the enrichment queue in the next post.