blessed_moon@studio
studio: Blessed Moon Studio
discipline: Strategy · Design · Engineering
stack: Next.js · Tailwind · WebGL
scene: ASCII moon · 5 keyframes
type: Space Grotesk / JetBrains Mono
accent: #ff6a1f
sound: muted
status: ready
B / S / MCASE_06 / 26

— Case study 06

Flora

An operations console for regenerative farming, built on a real satellite pipeline

All work
flora.capture● verified
Flora Crop Stress screen reading NDMI over a real field boundary on a Mapbox satellite basemap

project overview

A farm operations console where a scheduled worker — never the app itself — asks satellites for ten different plant-health readings per field, so a farmer opens a map and sees exactly which corner of which field is stressed, backed by real Sentinel-2 imagery.

shipped outcome

A working spine — register a field, watch a real stress zone appear from real Sentinel-2 data, act on it as a task — plus a home dashboard scored by a sourced formula, a ten-index spectral switcher, and a weather screen. Several of the sharpest bugs never showed up in a code review: they only appeared once a manual refresh ran against a real Copernicus account and a real field boundary, and got fixed the same way — by looking at what actually rendered.

Role
Full-stack product engineering
Timeline
Ongoing
Year
2026
Team
Solo delivery
The problem01

A first version of this idea existed already, built on Google Earth Engine — but Earth Engine only ships Python and JavaScript bindings, and it's a research compute platform, not an imagery API, which made it slow and awkward to build a real product on. Beyond that starting point: a smaller regenerative farm has no in-house GIS or remote-sensing expertise, but still needs to know which part of which field is stressed, this week, without walking every hectare.

The approach02

The whole system is rebuilt around one invariant: no request ever calls the satellite provider. A NestJS worker polls Copernicus's Data Space Ecosystem on a schedule, decodes the returned imagery, computes ten spectral indices from a single Process API call, and writes pre-rendered PNGs to object storage plus rows to Postgres. The API — a separate NestJS service — only ever reads Postgres and Redis, a split enforced by an actual test. Every tenant table sits behind PostGIS row-level security and a repository-level filter, checked by a cross-tenant suite that asserts a 404, never a 403, on another org's data.

The result03

A working spine — register a field, watch a real stress zone appear from real Sentinel-2 data, act on it as a task — plus a home dashboard scored by a sourced formula, a ten-index spectral switcher, and a weather screen. Several of the sharpest bugs never showed up in a code review: they only appeared once a manual refresh ran against a real Copernicus account and a real field boundary, and got fixed the same way — by looking at what actually rendered.

— Interface evidence

The product, not a placeholder

These are the verified interface captures from the project build. Scroll inside taller frames to inspect each complete page.

Flora home dashboard with Regeneration Score, planting productivity, and crops stocked01 / 06
Flora home dashboard with Regeneration Score, planting productivity, and crops stocked
Flora Fields screen with field cards and a Mapbox satellite basemap02 / 06
Flora Fields screen with field cards and a Mapbox satellite basemap
Flora Crop Stress screen reading Contrasted NDVI over a real field boundary03 / 06
Flora Crop Stress screen reading Contrasted NDVI over a real field boundary
The same field switched to RECI, a red-edge chlorophyll index04 / 06
The same field switched to RECI, a red-edge chlorophyll index
Flora Tasks board with a real drag-and-drop Kanban05 / 06
Flora Tasks board with a real drag-and-drop Kanban
Flora Weather screen with six instrument cards over Open-Meteo data06 / 06
Flora Weather screen with six instrument cards over Open-Meteo data

— Delivery process

From operating constraint to shipped system

The work is sequenced around risk. Domain rules and failure states come before interface polish; automation arrives before handoff.

01

Foundations, schema, tenancy

A pnpm/Turborepo monorepo, a Drizzle + PostGIS schema for ten domain tables with composite foreign keys, and identity with RLS enforced twice before any product screen existed.

02

The spine: Fields → Crop Stress → Tasks

Field CRUD and boundary drawing on a Mapbox map, then the satellite write path (a CDSE HTTP client, a decode-to-PNG raster pipeline, a BullMQ per-field scheduler), then the screen that reads it, and finally a Kanban task board with a real @dnd-kit drag.

03

Ten spectral indices

Widened the pipeline from one scheduled index to ten in a single Process API call — verified live to cost 4.667 Processing Units against a 4.0 baseline, since output count turns out to be free and only the input bands matter.

04

Home dashboard and weather

Daily rollups, a Regeneration Score built from AAFC's published agri-environmental performance index instead of an invented composite, and an Open-Meteo weather screen with six instrument cards.

— System architecture

Clear boundaries, explicit responsibilities

Each layer has one job and a narrow contract. That keeps external services replaceable and product behavior testable.

Next.jsNestJSTypeScriptPostgreSQL + PostGISDrizzle ORMBullMQ + RedisMapbox GL JSSentinel Hub (CDSE)
flora.sys● online

field_01

25.4 ac

15.9469°S / 48.6035°W

stress detected

20 zones

worker polling · request path idle

layers / 4.667 pu

ndvi[x]
ndmi[x]
reci[x]
mcari[x]
pri (proxy)[x]
plain ndvi[ ]
10 indices / 1 process call
Web

Next.js App Router

AlignUI shell, Mapbox map, Recharts — reads only Postgres-backed API endpoints, never a satellite provider directly.

API

NestJS

A separate service from the web app, reading only Postgres and Redis — enforced by a test, not a convention.

Worker

NestJS standalone + BullMQ

The only service allowed to call Sentinel Hub — a scheduled per-field job, not a request handler.

Data

PostgreSQL 16 + PostGIS 3.4

Drizzle ORM with a custom geography type; every tenant table behind RLS and a repository filter, both.

— What shipped

Product capabilities

01

Ten-index spectral switcher

NDVI, NDRE, EVI, MSAVI, RECI, MCARI, a labelled PRI proxy, NDMI, NDWI, and VSDI, each with its own colour ramp — and an honest disabled state for the ones this pipeline can't produce yet.

02

Real stress-zone detection

A raster overlay clipped to the field's true polygon boundary via its imagery's own 'no data' class, with grouped detections and a popover wired to real task mutations.

03

Tenancy enforced twice

Row-level security and a repository filter on every tenant table, checked by a cross-tenant suite asserting a 404, never a 403, against real RLS.

— Engineering pressure

Challenges resolved

01

A missing header that looked like an auth failure

No `Accept: application/tar` on the Process API request meant Copernicus silently returned a bare TIFF instead of the requested archive, and the parser's own error read exactly like a token problem — only testing against the real, live account surfaced the actual cause.

02

A masking bug two indices had and eight didn't

Eight of ten spectral indices are NDVI-shaped ratios, where a masked pixel naturally produces `NaN` outside the field boundary. Two indices with no division in their formula evaluated to a real, in-range number instead, painting a rectangle where a field polygon should have been — fixed by masking on the imagery's own 'no data' class instead of a formula side-effect.

03

A synthetic seed that never clipped to a real boundary

The satellite seed script filled its field's entire bounding-box rectangle with valid pixels, since a synthetic raster has no reason to know the field's real, non-rectangular boundary. Found by looking at a rendered field, not by inspecting the script.

verified project destinations

next case studyART'hur

next transmission

Need a system this deliberate?

Bring the operating problem. We will shape it into a clear product brief and a buildable next step.

Start a project