Reading a building site
from orbit.
This one is unfinished, and the log says so. Two of three agents run on real imagery today. The third is scaffolding.
- GRID
- H3 r13
- CELL EDGE
- ~3.7 m
- FEATURE VECTOR
- 20-dim
- MATERIAL CLASSES
- 25
- SATELLITE FEEDS
- 3
A construction loan does not pay out all at once. It releases in draws, and each draw is supposed to be verified: did the work the borrower is claiming actually happen? Today a lender answers that by sending a person to stand on the site with a clipboard and a camera.
That visit runs $800 to $2,000, and because of the cost it happens rarely. A lender with dozens of active projects is making release decisions on evidence that is weeks old and thin. Satellites pass over every one of those sites on a schedule, for free.
The obvious approach is to train a single model that eats imagery and emits a verdict. I did not want that, for two reasons.
The first is evidence. A lender releasing money needs to know why, and a single end-to-end model gives you a number with no traceable path back to the pixels. The second is debugging. When one model does everything, a wrong answer tells you nothing about which part was wrong.
So the pipeline is three agents with a written contract between them: ingestion turns imagery into a structured grid, classification labels the grid, analysis reasons over the labels. Each stage persists its output to GeoParquet, so any claim in a final report can be walked backwards to the cells that produced it.
Everything is indexed to an H3 hexagonal grid at resolution 13, roughly a 3.7 metre edge. Every cell carries its position, its capture date, which satellites contributed to it, a cloud percentage, and a 20-dimensional feature vector.
The vector comes from Clay, a geospatial foundation model, rather than from hand-designed spectral indices. Three feeds fuse into each cell through a STAC catalog: Sentinel-2 optical, Sentinel-1 synthetic aperture radar, and Landsat. SAR matters more than it sounds like it should, because it sees through the cloud cover that makes optical-only monitoring unreliable exactly when you need it.
Choosing the hex grid as the primitive is the decision the whole project rests on. It means every later stage operates on a consistent, joinable spatial unit, and comparing this month to last month is a join rather than an image registration problem.
Construction progress is mostly vertical, and vertical is the thing a nadir-looking satellite cannot see directly. The workaround is geometry: compute the sun’s elevation and azimuth at the exact moment of the satellite overpass, find the shadow, and the shadow length gives you the height of whatever cast it.
That path is built. Classification tags shadow cells and estimates an above-ground height for the object casting each one. It is the part of this project I find most satisfying, because it gets a third dimension out of a two-dimensional image using nothing but arithmetic and the position of the sun.
Agent 1, ingestion, runs. It pulls from the STAC catalog, filters scenes above 40% cloud, builds the hex grid, embeds every cell, and writes GeoParquet. An end-to-end test asserts a real run produces the expected cell count with no null feature vectors. There are outputs on disk from live runs over a real site.
Agent 2, classification, runs. It assigns one of 25 material classes per cell with a calibrated confidence and a full probability vector, rolls those up into six simplified classes, and computes the solar geometry and shadow heights above. Object classification is defined but not trained: twenty classes exist and version zero returns unknown for all of them.
Agent 3, analysis and reporting, does not run. Vertical progress, change detection, and the report writer are one-line stubs behind a fourteen-line scaffold. The thing a lender would actually receive does not exist yet. That is the next build.
The unresolved design decision is how much reasoning to hand the model versus encode in the representation upstream.
A rich enough feature vector plus a well-labeled grid might let a small amount of reasoning produce the report, with most of the work done before any language model sees anything. Or the grid might be the wrong altitude entirely, and what the report needs is an agent reasoning over relationships between cells: this crane stands beside this slab, this excavation connects to this access road.
I do not know yet, and I would rather measure it than argue about it. That is what Agent 3 is really for.