San Juan Riparian Watch
← back to the story
// system-architecture · end to end

From a satellite pixel to a cited answer

One person built every layer here: the Earth-observation pipeline, the spatial database, two model tracks, a typed services API, the map front-end, and the grounded agent — each behind its own quality gate. Below is how they fit together, and the distributed-systems app they share a discipline with.

The riparian pipeline

Data flows one direction — raw imagery in, a cited, mapped answer out — with every stage reproducible and independently checked.

ingest
STAC ETL
Sentinel-2 / Landsat via Planetary Computer
store
PostGIS medallion
bronze → silver → gold
model
RF · OlmoEarth
per-pixel vs fine-tuned FM
serve
.NET API
GeoJSON + MVT vector tiles
map + ask
React · RAG
MapLibre + grounded agent
// earth observation

STAC ingest → phenology cubes

Python ETL pulls the Landsat and Sentinel-2 archives from Microsoft Planetary Computer, composites each reach into phenology-aligned 12-month median-mosaic cubes (identical compositing is the experiment), and writes to the store. rasterio.mask clips to geometry; everything is EPSG:4269.

// spatial store

PostGIS, medallion architecture

Bronze (raw ingest) → silver (buffers, intersections, NDVI health, riparian extent) → gold (aggregated analytics). GiST indexes and && bbox pre-filters on every spatial op; geography casts for real distances. Data never flows back upstream.

// two model tracks

Random Forest vs OlmoEarth

A 144-D spectro-temporal Random Forest baseline, and Ai2's OlmoEarth foundation model fine-tuned on the pooled reaches — scored head-to-head under a pre-registered leave-one-reach-out contract. The FM earns its keep only on under-represented morphology.

// services + tiles

.NET services, vector tiles

ASP.NET Core minimal APIs on .NET 10, SOLID service/repository split, Dapper + NpgsqlDataSource over PostGIS, 29 routes returning GeoJSON via NetTopologySuite and .pbf MVT tiles through one canonical, injection-safe tile-SQL builder. Orchestrated by .NET Aspire.

Python ETL · GeoPandas · rasterio PostgreSQL + PostGIS .NET 10 · Aspire · Dapper React 18 · MapLibre GL · MVT OpenTelemetry tracing scikit-learn · OlmoEarth (rslearn)

The distributed-systems app it shares a discipline with

The same encoding-loop and the same senior standards run a full microservices reference platform (NextAurora) — proof the approach isn't specific to one codebase.

edge
React 19 SPA
Vite · Tailwind · TanStack
services
Catalog · Order
Payment · Shipping · Notification
data
EF Core · Postgres
per-service stores
platform
.NET Aspire
discovery · OTel · Fly.io
// backend

Aspire-orchestrated microservices

Independent Catalog, Order, Payment, Shipping and Notification services with their own EF Core stores, wired by .NET Aspire for service discovery, health, and OpenTelemetry context propagation. Deployed to Fly.io. DDD boundaries, security requirements, and performance rules enforced by the canon.

// front-end

A React SPA built to a canon

React 19 (+ the React Compiler), Vite, Tailwind 4, TanStack Query + Router, Zustand, MSW + Vitest. Feature-folder boundaries are enforced by lint; query keys come from factories; cache invalidation lives in the write path; no request waterfalls; a CI-enforced bundle budget. Auth is PKCE/BFF.


Cross-cutting

OTel
tracing across every service + span-per-method
CI
lint, tests, drift-gates, AI review — green to merge
TLS
hardened deploy: key-only SSH, CSP, rate limits
IaC
Aspire + azd / Fly.io + docker-compose
The through-line Right tool for each job — Python for EO, .NET for services, React for interactive UI, static HTML for this narrative — each with observability and a quality gate, none of it copy-pasted from a template.