TerraSentinel
A $0 anomaly-detection platform over free satellite and sensor data. Four public sources flow into a versioned Hugging Face lake, dbt and DuckDB build seven gold marts, an IsolationForest scores anomalies, and an edge dashboard serves it from Turso.
- gold marts
- 7
- dbt nodes
- 117
- unit tests
- 592
- rows served
- 76,607
- public sources
- 4
- model features
- 27
- model
- IsolationForest
- cost
- $0
TerraSentinel watches the planet's slow disasters — wildfires, vanishing sea ice, deforestation and glacier change — using only free public data and free compute. GitHub Actions runs the jobs, Hugging Face Hub stores the lake and the model registry, Turso serves the gold tables, and a Cloudflare Pages dashboard reads them at the edge.
What it is
Four public sources feed the pipeline: NASA FIRMS active-fire detections, Copernicus Sentinel-1 and Sentinel-2 imagery processed in Google Earth Engine, NOAA OISST v2.1 sea-surface temperature plus the NSIDC Sea Ice Index, and ENTSO-E day-ahead prices and load. Collectors land raw data into a versioned bronze lake on Hugging Face. dbt on DuckDB builds staging, intermediate and seven gold marts. An IsolationForest scores the latest features, and batch scoring writes predictions back into the serving database.
The transform layer
Bronze parquet is read straight from the Hugging Face Hub. Staging deduplicates fire detections and unifies MODIS and VIIRS brightness, builds region series and a resolution-5 grid for Sentinel, and computes sea-ice and SST anomalies against the NSIDC 1981-2010 climatology. Intermediate models build a dense region-by-day spine, H3 cells for the map, a circular plus-or-minus-15-day fire baseline, and monthly Sentinel change. Gold ships one mart per source arm, so an outage in one source can only remove its own table.
Anomaly definitions
Every mart states its baseline, because the anomaly is the whole product: daily fire counts against a median over the same plus-or-minus-15 days scaled by MAD, monthly NDVI against the same month a year earlier, sea-ice extent against the published NSIDC normal, and SAR backscatter year-over-year. Fires use median and MAD rather than mean and standard deviation, because a large fire sits inside its own baseline window and would inflate the yardstick it is measured against.
The ML layer
An IsolationForest is trained on 27 strictly causal features, meaning every rolling window ends before the day being scored. The score is a percentile against the training distribution, so it reads the same at serving time as in training. MLflow logs the exact dataset commit behind each run, and the model is published to a Hugging Face model repo.
The dashboard
Eight pages run on Cloudflare's edge: Overview, Analysis, Stories, Catalog, Explorer, SQL and Ops. The charts are hand-rolled SVG rather than a charting library, so the first paint contains the chart and a JavaScript failure cannot blank a panel. The map uses real H3 cells, and severity uses a luminance ramp rather than red-green. Every API route is read-only and precomputed; no inference runs on the request path.
The Databricks hybrid path
Alongside the free-tier pipeline, the same lake is mirrored as a Databricks Asset Bundle: six Workflows jobs on Quartz crons, declarative Spark and Delta Lake pipelines, Unity Catalog DDL and MLflow registration. It ships paused by design, so handing over from GitHub Actions to Databricks is a one-line, side-effect-free flip. The Unity Catalog DDL and MERGE statements are generated from a single source of truth and checked in CI, so the schema cannot drift.
Proving it works
An unsupervised model can always claim to work, so the pipeline ships a falsifiable check. A synthetic lake plants one obvious event per anomaly type, the full dbt build runs against it, and CI asserts each event is flagged while the overall flag rate stays under 2%, which rejects a degenerate baseline that flags everything.
The hard part
- DuckDB over the Hub re-lists the repo tree on every query. With staging as views, about 50 dbt tests meant 50-plus tree listings against a 1,000-per-5-minute API quota. Mirroring the bronze lake locally first turns a full 117-node build into zero API calls and about four seconds.
- Choosing FIRMS products by "is this a backfill?" returns empty data for the most recent quarter. Standard processing lags real time by about three months and near-real-time is retained for about three months, so the product is chosen by window age with a fallback through the overlap.
- Sentinel at per-pixel H3 is unaffordable. Bucketing at resolution 7 means 203,485 cells for Iberia and 677,760 for Norway, which no free Earth Engine quota absorbs. A region-grain series plus a resolution-5 change map answers the same questions at a cost that runs.
- An unsupervised model can always claim to work. The fix is a falsifiable test: synthetic anomalies injected into a synthetic lake, a real dbt build, and an assertion that they are flagged without the flag rate blowing up.
- A chart island that fails to hydrate renders nothing. A charting library failed three separate ways, so the charts became server-rendered SVG where a JavaScript failure cannot blank the panel.
Outcome
- Seven gold marts and 117 dbt nodes, all tests green, on a fully offline CI build.
- 592 unit tests plus a full dbt build and a type-checked dashboard build.
- 76,607 rows synced into Turso: four gold marts plus 1,462 model predictions, read at sub-10 ms from Cloudflare's edge.
- An IsolationForest published to a Hugging Face model repo, traced to a bronze commit hash.
- A live dashboard with eight pages and seven read-only API routes.
- A Databricks Asset Bundle that mirrors the lake with six Workflows jobs and Unity Catalog DDL, tested and paused by default.
- Total infrastructure cost: $0.
What I'd do differently
- Median and MAD, not mean and standard deviation. Measured on the same fire event: a z-score of 7.2 with mean and standard deviation versus 16.9 with median and MAD, because the event inflates the yardstick it is measured against.
- Pick the grain before the tool. The right bucket size differs by an order of magnitude between a 375 m fire detection and a 0.25-degree ocean grid.
- Probe the archives instead of trusting the docs. A documented endpoint redirected to a host that timed out, a temperature copy turned out to be a stale 2002-2011 slice, and the ice index had moved from v3.0 to v4.0.
- Store the lake as dataset repositories, not buckets. Dataset repos are git-backed, which is what makes it possible for MLflow to log the exact commit behind a model.
- Never let an unsupervised result go unmeasured. Compare against an independent rule and report the overlap, including when the model adds no information.
Architecture & screenshots
More in Data Engineering
EU Air Traffic
Live and historical European airspace: ADS-B positions, OpenSky movements, schedules and weather stream through Kafka into a Bronze and Silver lake, a dbt warehouse on DuckDB and a read-only serving copy behind a public dashboard.
EuroStream
GDPR-native streaming and medallion lakehouse for European commerce: real-time fraud scoring, DuckDB and Turso serving, and a six-layer Article 17 erasure cascade with a verifiable audit trail.