Stack
A transparent technical breakdown of how Field Property Partners is built.
Frontend
| Next.js 16 | App Router, server components for SSR memo rendering |
| React 19 | Memo template + interactive analyzer page |
| TypeScript | Strict mode across all client + server code |
| Tailwind CSS 4 | Design tokens, dark theme palette |
| Recharts | Sensitivity table heatmaps |
| Playwright | PDF capture of the live React memo |
Backend
| FastAPI | Python 3.11, async throughout |
| PostgreSQL 16 | DigitalOcean managed instance |
| asyncpg | Direct async driver — no SQLAlchemy layer |
| Pydantic | Validation on every API boundary |
| httpx | Async client for every external API |
| APScheduler | Macro snapshot cron (FRED daily refresh) |
Data Providers
| ATTOM Data | Property metadata, sales comps, tax records |
| RentCast | Rent AVMs + value AVMs (Foundation tier) |
| HouseCanary | Residential AVMs with forward forecasts (Teams tier) |
| FRED | Treasury yield curve + SOFR |
| Anthropic Claude Sonnet 4.5 | Reconciliation prose + listing-facts extraction |
| CoStar | Stub class — ready for enterprise integration |
Infrastructure
| DigitalOcean Droplet | Ubuntu 22.04, 4GB RAM |
| Docker + Docker Compose | Two services: fpp-api + fpp-web |
| Caddy | Reverse proxy + Let's Encrypt SSL |
| GitHub | Private repo |
Underwriting Engine
| underwriting/multifamily.py | NOI/cap-rate, DCF with terminal value, European waterfall (70/30 LP/GP, 8% pref, 20% promote) |
| underwriting/sfr.py | Monthly + annual pro forma, BRRRR refi math, fix-and-flip cost basis, multi-scenario classifier |
| metrics_snapshot.py | Single source of truth — every metric on the memo reads from deal_metrics_snapshot |
| Internal consistency assertions | Pre-PDF assertion that Page 1 numbers match Page 6 prose within 0.5% |
Memo Generation
| React-rendered components | Same component tree drives /analyze and /api/pdf |
| Playwright PDF capture | Captures the live HTML to PDF |
| Anthropic prose validation | Regex-extracts every $ and % from generated text, asserts against snapshot ±tolerance, retries up to 2× on mismatch |
| Snapshot-only fallback | If all 3 LLM attempts contain hallucinated numbers, the prose falls back to a static template that quotes only canonical values |
Calibration Data
| Property tax rates | 120+ counties verified from county assessor records + 50-state median fallback |
| Insurance defaults | Market-specific (FL/CA/TX coastal, fire zones, mountain west) |
| Rent control flags | 20+ jurisdictions with annual-cap overrides on rent_growth default |
| Closing costs | State-tiered (NY mansion tax, NJ RTF, IL Cook, HI conveyance) |
| Multi-word cities | 200+ city names so 'San Francisco' / 'Las Vegas' / 'Saint Louis' parse correctly from URLs |
Pipeline / Workflow
| Status workflow | NEW → REVIEWED → REACHED OUT → REPLIED → UNDER CONTRACT → PASSED / CLOSED |
| Inline edits | Status dropdown + notes textarea persist on blur with status_history trail |
| Listing URL ingestion | Zillow, Realtor.com, LoopNet, Crexi — 37+ URL variants covered |
| Race-condition guard | Same-URL re-ingest within 5s returns existing deal_id |
| LLM listing-facts extraction | Paste any listing's Property Facts section → structured JSON → underwriting |
Validation
| Pre-deploy E2E tests | Real listing URLs run through production HTTPS endpoints |
| URL parser corpus | 33+ URL variants with PASS/FAIL per variant |
| Internal consistency strict mode | 30+ verification items on every phase release |
| No graceful 'PARTIAL' | Strict-mode verification — any FAIL blocks the deploy |