Skip to content
From
Enric Trillo · London
Date
Wavelength
520nm · Systems
Series
FIRSTLIGHT
Read
2 min

Building FIRSTLIGHT: why local-first won over another SaaS dashboard

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.