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

  1. 1Resolve the address via api.pdok.nl (PDOK Locatieserver). Fuzzy matches below score 10 are refused instead of guessed.
  2. 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).
  3. 3Count dwellings sharing the building (same WFS) and classify the dwelling type — a documented heuristic, since BAG has no dwelling-type field.
  4. 4Pull the consumption benchmark for that dwelling type + municipality from opendata.cbs.nl (StatLine 81528NED, data through 2024), falling back municipality → national → dated constants.
  5. 5Fetch today's dynamic electricity prices from api.energyzero.nl — decorative context; the scan continues if it's down.
  6. 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

  1. 1Accept a PDF (max 8 MB, type-checked), base64 it, and send it to api.openai.com/v1/responses with a strict JSON schema that mirrors the domain schema.
  2. 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.
  3. 3The extracted quote prefills the form. A human reviews it before comparing — AI reads, people decide.

POST /api/compare — the counter-offer

  1. 1Normalize the competitor quote to net-after-subsidy so it compares fairly against Quatt's advertised net prices.
  2. 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.
  3. 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

FactSourceEndpoint / locationFreshness
Address, coordinates, BAG IDsPDOK Locatieserverapi.pdok.nllive, per request
Build year, surface, building useBAG (Kadaster)service.pdok.nl/lv/bag/wfslive, per request
Dwelling typeheuristic (units-in-building + surface)packages/core/src/domain/enrich.tscomputed; rationale shown in UI
Energy labelestimated from build year (EP-Online API slot ready)packages/core/src/domain/enrich.tsestimate; marked as such
Avg. gas / electricity consumptionCBS StatLine 81528NEDopendata.cbs.nllive; 2024 data; dated fallback if down
Dynamic electricity pricesEnergyZeroapi.energyzero.nllive, hourly
Prices & ISDE subsidy amountsQuatt's published price listquatt.io/subsidie-prijzen, captured 2026-07-10, pinned by a teststatic by nature; dated & test-guarded
Savings, payback, CO₂deterministic energy modelpackages/core/src/domain/energyModel.tscomputed; every assumption listed per offer
Quote fields from PDFsOpenAI structured outputsapi.openai.com/v1/responsesper 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 /leadsname, email, phone, address and selected offer; PII never appears in the public log view
Cachingin-process TTL (CBS 24h, EnergyZero 15m)packages/core/src/cache.tsevery 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.