Evolve ParcelOS
A parcel-first real-estate intelligence platform that replaced a paid parcel-lookup app with a self-built system covering every parcel in Florida for $0 in vendor fees.
- 15,603/15,603
- parcels scored in one Bradford County batch run (16 min)
- ~10.8M
- Florida parcels in the statewide cadastral source being ingested
- 67
- Florida counties in scope for statewide scoring
- 51
- database tables in the initial schema (provenance spine, RLS, scoring)
- Next.js 15 (App Router, TypeScri…
- Tailwind CSS
- Cloudflare Tunnel
- FastAPI (Python 3.12, uv)
- SQLAlchemy 2 async / asyncpg
- arq task queue
- Redis 7
- Claude CLI (keyless, subscriptio…
- Docker Compose
- Alembic
- PostGIS 16/3.4
- MapLibre GL JS
Evolve ParcelOS holds live data, so this shows the verified technology stack by layer rather than a screenshot. Hosts, ports and topology are deliberately absent.
Problem
Evolve Estates relied on LandGlide, a paid third-party app, for parcel research, with no execution layer behind it: no way to move from "here's a parcel" to verify, score, field-inspect, assign, and track outcomes in one system. Parcel data, CRM data (contacts, campaigns), and transaction documents had no clear ownership boundaries, and any attempt to build opportunity tracking risked duplicating the CRM the team already had in Chrysalis. There was also no owner-level, sourced view of county zoning, permits, or code-enforcement status per parcel, and no scoring model to prioritize outreach. County-level data that does exist (zoning layers, active code-enforcement cases, permit history) is scattered across dozens of separate, inconsistent government GIS endpoints with no unified access point, and any given county might expose some of these publicly, expose none of them, or expose them only for unincorporated land, none of which a paid national parcel app like LandGlide surfaces. Statewide coverage at LandGlide's pricing was also cost-prohibitive to scale beyond a handful of priority areas, so most of Florida was effectively invisible to the team's research workflow.
What was built
ParcelOS is an internal, single-tenant platform that ingests every parcel in Florida from the free state cadastral feed, attaches sourced facts (zoning, permits, code cases, ownership) with full provenance, and runs an explainable score against each parcel. Agents and analysts map, verify, and evaluate a parcel, then field-inspect it, assign it, and push qualifying opportunities into the Chrysalis CRM rather than duplicating contact and deal tracking locally. A natural-language search bar ("vacant residential lots in Seminole County under half an acre") translates to real map filters through an AI gateway, watchlists notify on changes and saved-search diffs, and a field PWA supports offline inspection capture (with GPS accuracy) that syncs when back online. A per-county registry of verified government data sources layers zoning, active code-enforcement cases, and permit history onto individual parcels as they become available; each county is added only once its specific public endpoint has been live-verified against a real record, so coverage grows honestly rather than being guessed at. A media library, LLC/trust entity link-outs to Florida's public business registry, and voice-note capture via local transcription round out the field and research workflow, and role-based access control and full audit logging govern who can see and act on what.
Technical approach
The system draws a hard system-of-record line: ParcelOS owns parcels, geometry, attribute observations, scores, and field data; Chrysalis owns people, campaigns, and transactions; SkySlope owns closing documents. Every fact ingested (zoning, permits, ownership) is stored as a versioned attribute_observations row carrying source and license_scope, so MLS-restricted fields can be excluded from any external AI call by a query filter rather than a refactor. Postgres/PostGIS with row-level security (ported from the Chrysalis RLS pattern, including a fail-closed CI guard) backs a FastAPI/async-SQLAlchemy API and a Next.js/MapLibre front end. The statewide ingest walks the FGIO layer's object-ID space sequentially (1 to ~10.8M) in chunks of 100, because spatial queries against the layer 400 after ~55 seconds on dense cells and returnCountOnly is rejected outright — a resumable single-cursor design was the only viable shape. The pipeline was rebuilt three times under load: sequential (~30 ids/s), then parallel-fetch (which just moved the bottleneck to a single-connection DB write), then pipelined with prefetch and parallel chunk ingest — the last version caused a real production deadlock crash-loop from concurrent chunk transactions racing on shared owner rows (HOAs/builders), fixed by resolving all of a batch's owners in one sorted-order transaction before parallel per-parcel ingest. A separate root cause (an orphaned sweep worker surviving a deploy and racing the live worker on the same cursor) was fixed by making the deploy script pkill and drain orphaned workers before proceeding. Search across the full 10.8M-row target required trigram GIN indexes and rewriting owner search as a UNION of three trigram-backed branches after the original OR-EXISTS shape produced an unindexable 19.6M-cost plan. A separate batch scorer was built once on-demand scoring proved quadratic at statewide scale (recomputing county-level demand context per parcel); it aggregates county context once and reuses the same per-parcel evaluators, verified to produce identical scores to the on-demand path on spot-checked parcels. The AI gateway design (ADR 0005) keeps factual answers to tool calls only against internal records so citations are structural rather than generated, separates read and mutation tool registries, and requires mutations to carry a confirmation token minted server-side — prompts are never the security boundary. AI runs on the keyless Claude CLI (subscription OAuth, not per-call API billing), the same pattern used elsewhere in the Evolve stack.
Creative approach
Craft
The UX concept centers on a single "Parcel 360" drawer as the canonical view of any parcel, surfaced from a MapLibre satellite map rather than a table-first interface, matching how the field team actually thinks about a property: click a shape on the map, read everything known about it in one place. County data sources (zoning, permits, code cases) are labeled by provenance in the UI rather than blended into a single opaque score, and municipal parcels that fall outside a county layer's jurisdiction are honestly labeled "municipal (jurisdiction)" instead of silently showing nothing or a misleading blank. An "ingest area" button lets a user pull fresh parcel data for whatever the map is currently showing, so research and data acquisition happen in the same view instead of a separate admin step. The admin surface (audit log, ingest progress, sweep percentage/rate/ETA) is treated as a first-class page rather than a hidden debug tool, on the logic that a system ingesting government data at this scale needs its operator to be able to see its state at a glance.
Reframe
The unlock was refusing to buy parcel or MLS data and instead treating Florida's own free FGIO/DOR statewide cadastral feed as the primary source, which flipped the project from a recurring vendor cost into a $0-infrastructure system the moment coverage was proven at scale, and made statewide reach (not just a few priority ZIP codes) financially viable for the first time. The second unlock was drawing the ParcelOS/Chrysalis boundary early and holding it under pressure: opportunity records live in ParcelOS only as foreign references into the CRM, with every execution action (assign to ISA, add to campaign, create task) implemented as an API call outward rather than a duplicated leads table — a discipline explicitly modeled on a prior mistake in the A.I. Portal where a leads/deals layer crept in and had to be removed. The county-data registry pattern is a third, smaller reframe: rather than promising uniform nationwide coverage a vendor would advertise, the system commits to verifying each county's actual public data shape one at a time and recording true absences (no public GIS server, no relevant layer) as explicitly as it records presences, which makes the coverage claim trustworthy instead of aspirational.
Process and what failed
The statewide sweep went through three build-fail-rebuild cycles before it held: a naive sequential ingest was too slow, a parallel-fetch version just relocated the bottleneck, and the first pipelined/parallel version triggered a real production deadlock crash-loop from concurrent transactions on shared owner rows, requiring a structural fix (pre-resolving owners in one ordered transaction) rather than a patch. Separately, an orphaned worker process that survived a deploy silently raced the live sweep on the same cursor for a period before the root cause was found. The Mac Mini's disk hit 99% overnight during the sweep and wedged Docker (a repeat of an earlier incident); recovery revealed a second-order failure where killed Docker port-proxies stayed half-dead — accepting TCP connections but hanging on protocol — which froze host services mid-transaction until each container was individually restarted. That incident forced the nightly backup to split re-derivable bulk ingest data from user-generated data, since the unsplit dump would have hit roughly 15 GB against a database on track to run 50-80 GB.
Outcome
As of the most recent status update, application code across the API, database, and web packages is substantially built out with a passing test suite (94 API + 19 database + 10 web/vitest tests), and the statewide Florida parcel sweep is actively running with resumable, self-healing ingest. A batch scoring run has been proven end-to-end on a full county (15,603 of 15,603 parcels scored, with batch scores verified to match on-demand scores exactly on spot-checked parcels). The platform is deployed to the Mac Mini behind Docker Compose but remains loopback-only; public exposure via Cloudflare Tunnel and Google Workspace SSO are prepared but not yet flipped on, pending an owner action (OAuth client creation) outside the codebase.