How it works
Nothing here is mocked. This page shows how the APIs are wired together, where every number comes from, and where the code lives. Each home scan also renders its own live pipeline trace — per request, with real latencies.
GET /api/scan — the home scan
- 1Resolve the address via
api.pdok.nl(PDOK Locatieserver). Fuzzy matches below score 10 are refused instead of guessed. - 2Fetch building facts — build year, surface, use — from the national building registry via
service.pdok.nl/lv/bag/wfs(FES 2.0 XML filters; the WFS silently ignores the GeoServer-style ones). - 3Count dwellings sharing the building (same WFS) and classify the dwelling type — a documented heuristic, since BAG has no dwelling-type field.
- 4Pull the consumption benchmark for that dwelling type + municipality from
opendata.cbs.nl(StatLine 81528NED, data through 2024), falling back municipality → national → dated constants. - 5Fetch today's dynamic electricity prices from
api.energyzero.nl— decorative context; the scan continues if it's down. - 6Run the energy model + sizing + price/subsidy math in
@quatt/core— deterministic, unit-tested, and every output carries its full assumption list.
POST /api/extract — the quote reader
- 1Accept a PDF (max 8 MB, type-checked), base64 it, and send it to
api.openai.com/v1/responseswith a strict JSON schema that mirrors the domain schema. - 2The model must return exactly that shape — and the result is still validated with zod before it touches the UI. Schema-violating output is rejected, not repaired.
- 3The extracted quote prefills the form. A human reviews it before comparing — AI reads, people decide.
POST /api/compare — the counter-offer
- 1Normalize the competitor quote to net-after-subsidy so it compares fairly against Quatt's advertised net prices.
- 2Match capacity apples-to-apples: a 6 kW quote is compared against the nearest Quatt variant, with an explicit undersizing watchout if your home needs more.
- 3Detect inclusion gaps — subsidy handling, the ISDE 2026 smart-control requirement, installation — and produce a verdict with advantages and watchouts, not just a price delta.
Data provenance — where every fact comes from
| Fact | Source | Endpoint / location | Freshness |
|---|---|---|---|
| Address, coordinates, BAG IDs | PDOK Locatieserver | api.pdok.nl | live, per request |
| Build year, surface, building use | BAG (Kadaster) | service.pdok.nl/lv/bag/wfs | live, per request |
| Dwelling type | heuristic (units-in-building + surface) | packages/core/src/domain/enrich.ts | computed; rationale shown in UI |
| Energy label | estimated from build year (EP-Online API slot ready) | packages/core/src/domain/enrich.ts | estimate; marked as such |
| Avg. gas / electricity consumption | CBS StatLine 81528NED | opendata.cbs.nl | live; 2024 data; dated fallback if down |
| Dynamic electricity prices | EnergyZero | api.energyzero.nl | live, hourly |
| Prices & ISDE subsidy amounts | Quatt's published price list | quatt.io/subsidie-prijzen, captured 2026-07-10, pinned by a test | static by nature; dated & test-guarded |
| Savings, payback, CO₂ | deterministic energy model | packages/core/src/domain/energyModel.ts | computed; every assumption listed per offer |
| Quote fields from PDFs | OpenAI structured outputs | api.openai.com/v1/responses | per upload; zod-validated, human-reviewed |
| Leads ("lock in this offer") | append-only JSONL log (ADR-0004) | DATA_DIR/leads.jsonl · privacy-safe audit view at /leads | name, email, phone, address and selected offer; PII never appears in the public log view |
| Caching | in-process TTL (CBS 24h, EnergyZero 15m) | packages/core/src/cache.ts | every trace line says "fresh fetch" or "cache hit, Xs old" |
Where everything lives
packages/core— the engine: framework-free TypeScript, zod at every external boundary, 45+ unit tests plus live-gated integration tests against the real APIs. No HTTP framework, no React, no mocks in production paths.apps/web— this app: a thin Next.js shell. The API routes are plain Request/Response handlers so they are unit-testable and reusable as agent tool endpoints.Dockerfile— multi-stage standalone build → deployed on Coolify (Hetzner) behind Cloudflare, configured entirely via APIs.- The repo builds itself through four documented loops —
/architect → /build → /review → /ship— with architecture decision records, milestone tags and an externally readable build log.
The honesty box
- Dwelling type is a heuristic. BAG doesn't classify tussenwoning vs hoekwoning; we infer from units-in-building and surface, and say so in every result.
- The energy label is an estimate from the build year until an EP-Online API key is configured — the result marks which one you got.
- Savings are shown as bands (conservative–expected), never a single flattering number, and the assumptions behind them are one click away.
- Garbage in, nothing out: addresses that only fuzzy-match are refused with the actual match score instead of silently guessing a house.