# Vastgoeddossier XXL — PDOK + CBS + Klimaat + Fundering (`codeclouds/vastgoed-xxl`) Actor

Samengesteld Nederlands vastgoed-indicatiedossier per adres: PDOK-geocoding, CBS-buurtcijfers, overstromingsrisico (RWS) en funderingsaandachtsgebied (RVO) in één genormaliseerd JSON-record. Combineert meerdere officiële open-databronnen die los al gratis zijn, maar nooit samen worden aangeboden.

- **URL**: https://apify.com/codeclouds/vastgoed-xxl.md
- **Developed by:** [Dennis](https://apify.com/codeclouds) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 dossier-resultaats

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Vastgoeddossier XXL — PDOK + CBS + Klimaat + Fundering + Geluid + Veiligheid 🇳🇱🏠

One request per Dutch address → a combined property-risk dossier assembled from **seven official open
data sources**: PDOK geocoding, CBS neighbourhood statistics, CBS proximity-to-amenities figures, RIVM
noise-exposure mapping, CBS registered-crime figures, RWS flood-risk zoning, and RVO foundation
(subsidence) attention areas. Every single source is free and public on its own — nobody combines,
normalizes and caches them into one address-level record. That combination is the product.

Manually looking each of these seven sources up per address — a different site, map layer, or CSV
download for each — realistically costs 10-15 minutes per property. This Actor turns that into a single
request per address, with every value source-attributed and every failure marked `onbekend` instead of
guessed.

### When should an AI agent use this?

- "Build a property dossier for this address: location, neighbourhood stats, flood risk, foundation risk."
- "Screen these 10 candidate properties for climate and foundation risk before a viewing."
- "Does this address sit in a flood-risk zone or a pre-1970 foundation attention area?"
- "Get CBS neighbourhood income/density figures plus climate risk for this postcode in one call."
- "Pre-screen a shortlist of addresses for a real estate due-diligence report."
- "How school-friendly, walkable and green is this neighbourhood — is there a school, GP and supermarket
  nearby?"
- "How noisy is this address — road, rail or air traffic — before I book a viewing?"
- "How does this municipality's registered crime rate compare to the national average?"

### What this Actor does

- Geocodes your input (address, postcode, place name, or `"lat,lon"`) via the official **PDOK
  Locatieserver**, matched to `adres`-level by default so postcode/neighbourhood codes are always present
- Enriches with **CBS Kerncijfers Wijken en Buurten** (population, income, households, urbanity) for the
  matched neighbourhood/district/municipality
- Enriches with **CBS Nabijheid van voorzieningen** (road-distance in km to the nearest school, GP
  practice, large supermarket, train station, motorway access, childcare, and public green space) for the
  matched municipality, plus a derived `schoolomgeving_score` (school-proximity rating)
- Checks **RWS Richtlijn Overstromingsrisico (ROR)** via a live PDOK WMS lookup: does this point sit in a
  flood-risk zone, and what's the qualitative classification/description
- Checks the **RVO "Indicatieve aandachtsgebieden funderingsproblematiek"** layer via PDOK WMS: what
  percentage of buildings in this address's postcode area (PC6) were built before 1970 — the standard
  proxy for wooden pile-foundation vulnerability
- Checks the **RIVM Atlas Leefomgeving basisgeluidkaart** (Lden 2020) via four live WMS lookups: modelled
  noise exposure from road traffic, rail traffic, and air traffic individually, plus a cumulative total and
  a plain-language `geluidsniveau` classification (`laag`/`gemiddeld`/`hoog`/`zeer hoog`)
- Enriches with **CBS Geregistreerde criminaliteit** (registered crime) for the matched municipality:
  crimes per 1,000 residents, total registered count, clearance rate, plus a derived `veiligheidsniveau`
  classification
- Adds a **`samenvatting`** (summary) block per dossier: a plain risk-level rollup (`laag`/`middel`/`hoog`/
  `onbekend`) so you don't have to inspect nested objects to see whether an address stands out
- Processes multiple addresses per run, in parallel, fail-safe per item (one bad address never blocks the
  batch), with a free `RUN_SUMMARY` written to the key-value store at the end of every run
- Honest about lookup failures: if a flood-risk, foundation-area, noise, or crime-data check itself fails
  (e.g. a temporary outage), the field is `null` with `_status: "onbekend"` — never silently reported as
  "no risk found"

### Who is this for?

- **Real estate agents & buyers** — a fast, structured first-pass dossier before a viewing or offer
- **Mortgage advisors & insurers** — a documented, source-attributed risk signal to support (not replace)
  formal underwriting
- **Property developers & investors** — batch-screen a shortlist of addresses in one run
- **AI agents** — structured JSON, ideal as a due-diligence tool in an agentic property-research workflow

### Input

| Field | Type | Description |
|---|---|---|
| `query` / `queries` | string / array | Address(es), postcode(s), place name(s), or `"lat,lon"` — see the [input schema](.actor/input_schema.json) for the full bulk-item format |
| `lookupId` / `datasetId` | string | Direct PDOK object-ID lookup, or a linked Apify dataset as bulk input |
| `mode` | string | `auto` (default), `forward`, `reverse`, or `lookup` |
| `typeFilter` | string | PDOK object-type filter for forward search, default `adres` (recommended — see FAQ) |
| `enrichWithCbs` | boolean | Add CBS neighbourhood statistics (default `true`) |
| `enrichWithNabijheid` | boolean | Add CBS proximity-to-amenities figures (school/GP/supermarket/station/motorway/childcare/green-space distances + school-proximity score, default `true`) |
| `enrichWithVeiligheid` | boolean | Add CBS registered-crime figures (crimes per 1,000 residents, total count, clearance rate + classification, default `true`) |
| `includeKlimaat` | boolean | Add the flood-risk-zone check (default `true`) |
| `includeFundering` | boolean | Add the foundation-attention-area check (default `true`) |
| `includeGeluid` | boolean | Add the noise-exposure check (road/rail/air traffic + cumulative total + classification, default `true`) |
| `concurrency` | integer | Addresses processed in parallel, 1-20 (default `5`) |

### Output

One dossier per input address:

```json
{
  "input_query": "Damrak 1 Amsterdam",
  "mode": "forward",
  "basis": {
    "weergavenaam": "Damrak 1, 1012LG Amsterdam",
    "postcode": "1012LG",
    "gemeente": "Amsterdam",
    "provincie": "Noord-Holland",
    "lat": 52.37714446,
    "lon": 4.89803846,
    "buurtcode": "BU0363AD03",
    "match_kwaliteit": "hoog",
    "_bron": "PDOK Locatieserver (Kadaster/BZK)"
  },
  "cbs": {
    "_bron": "CBS StatLine 86165NED (CC-BY, cbs.nl)",
    "aantal inwoners": 934526,
    "gemiddeld inkomen per inwoner": null,
    "huishoudens totaal": 516096,
    "stedelijkheid": 1
  },
  "nabijheid": {
    "_bron": "CBS StatLine 80305NED (CC-BY, cbs.nl), jaargang 2025JJ00",
    "afstand tot school": 0.6,
    "afstand tot huisartsenpraktijk": 0.6,
    "afstand tot grote supermarkt": 0.6,
    "afstand tot treinstations totaal": 2.8,
    "afstand tot oprit hoofdverkeersweg": 2.2,
    "afstand tot kinderdagverblijf": 0.4,
    "afstand tot openbaar groen totaal": null,
    "schoolomgeving_score": "dichtbij (<1km)",
    "_status": "ok",
    "_disclaimer": "Gemiddelde afstand voor de hele GEMEENTE, niet per buurt of specifiek adres — CBS publiceert deze nabijheidscijfers niet op een fijnere schaal."
  },
  "geluid": {
    "geluid_totaal_db": 68,
    "geluid_wegverkeer_db": 68,
    "geluid_treinverkeer_db": 57,
    "geluid_vliegverkeer_db": 44,
    "geluidsniveau": "hoog",
    "_status": "ok",
    "_bron": "RIVM Atlas Leefomgeving basisgeluidkaart (Lden 2020) WMS, data.rivm.nl/geo/alo/wms",
    "_disclaimer": "Gemodelleerde jaargemiddelde geluidsbelasting (Lden, RIVM basisgeluidkaart 2020) per bron — geen meting op dit specifieke adres. Een waarde van 0 betekent \"geen bron van dit type gemodelleerd op deze locatie\", niet noodzakelijk stilte."
  },
  "veiligheid": {
    "geregistreerde misdrijven per 1000 inw": 87.4,
    "totaal geregistreerde misdrijven": 81685,
    "opgehelderde misdrijven, relatief": 21,
    "veiligheidsniveau": "zeer hoog",
    "_status": "ok",
    "_bron": "CBS StatLine 83648NED (CC-BY, cbs.nl), jaargang 2025JJ00",
    "_disclaimer": "Gemiddelde voor de hele GEMEENTE, niet per buurt of specifiek adres. Betreft GEREGISTREERDE misdrijven (bij de politie bekend/aangegeven), niet alle daadwerkelijk voorgevallen criminaliteit — er is altijd een mate van ondermelding."
  },
  "klimaat": {
    "overstroming_risicogebied": true,
    "overstroming_classificatie": "Area of Potential Significant Flood Risk",
    "overstroming_omschrijving": "Rijn type B - beschermd langs hoofdwatersysteem",
    "_status": "ok",
    "_bron": "RWS Richtlijn Overstromingsrisico (ROR) WMS, service.pdok.nl/rws/overstromingen-risicogebied"
  },
  "fundering": {
    "funderingsaandachtsgebied": true,
    "funderingsgebied_postcode": "1012JS",
    "funderingsgebied_gemeente": "Amsterdam",
    "funderingsgebied_percentage_voor_1970": 75,
    "funderingsgebied_aantal_panden": 8,
    "_status": "ok",
    "_bron": "RVO Indicatieve aandachtsgebieden funderingsproblematiek WMS, service.pdok.nl/rvo/indgebfunderingsproblematiek",
    "_disclaimer": "Percentage/aantal panden geldt voor het POSTCODEGEBIED, niet per se voor dit specifieke pand. Grove indicatie, geen funderingsonderzoek."
  },
  "samenvatting": {
    "risiconiveau": "hoog",
    "omschrijving": "Let op: overstromingsrisicogebied, funderingsaandachtsgebied, geluidsbelasting, veiligheidsrisico.",
    "aantal_aandachtspunten": 4,
    "aandachtspunten": ["overstromingsrisicogebied", "funderingsaandachtsgebied", "geluidsbelasting", "veiligheidsrisico"],
    "onvolledige_modules": [],
    "_disclaimer": "Grove, opgetelde indicatie op basis van het aantal gevonden aandachtspunten — geen gewogen of gevalideerd risicomodel."
  },
  "_dossier_disclaimer": "Dit dossier combineert publieke open-databronnen (PDOK, CBS, RWS, RVO) en is uitsluitend indicatief. Het is geen taxatie, bouwkundige keuring of formeel risico-advies, en schept geen aansprakelijkheid voor koop-, verhuur-, verzekerings- of financieringsbeslissingen. Raadpleeg voor dergelijke beslissingen altijd een professioneel adviseur.",
  "error": null
}
```

- **`samenvatting.risiconiveau`** is a coarse, purely additive rollup (0 flags → `laag`, 1 → `middel`, 2 or
  more → `hoog`; `onbekend` if a module's lookup failed and there are otherwise no flags; `niet gecontroleerd`
  if `includeKlimaat`/`includeFundering`/`includeGeluid`/`enrichWithVeiligheid` are all disabled, so nothing
  was actually checked) — not a weighted or validated risk model. Always check the underlying
  `klimaat`/`fundering`/`geluid`/`veiligheid` fields for detail. A `geluidsbelasting` flag is added when
  `geluid.geluidsniveau` is `"hoog"` or `"zeer hoog"`; a `veiligheidsrisico` flag is added when
  `veiligheid.veiligheidsniveau` is `"hoog"` or `"zeer hoog"`.

- **`_status: "onbekend"`** on `klimaat`/`fundering`/`geluid`/`veiligheid` means the underlying lookup itself
  failed (e.g. a temporary outage) — the corresponding fields are `null` in that case, never a silent
  `false`/`0`. This is different from a confirmed "no risk found" (`_status: "ok"`).

- A free **`RUN_SUMMARY`** object is written to the run's key-value store at the end of every run
  (batch totals, counts per flag, average pre-1970 percentage, risk-level distribution) — no extra charge,
  useful when screening many addresses in one run.

- **`match_kwaliteit`** (`"hoog"` / `"middel"` / `"laag"` / `"onbekend"`) is a best-effort classification
  based on PDOK's own relevance score — not a guarantee. A `"laag"` value means "verify this address
  manually", not "this address doesn't exist".

- **`nabijheid` distances are gemeente (municipality)-level averages, not per-neighbourhood or
  per-address** — CBS publishes this dataset (80305NED) at municipality granularity only, coarser than the
  neighbourhood-level `cbs` block. Stated explicitly in `nabijheid._disclaimer` on every result.

- **`schoolomgeving_score`** (`"dichtbij (<1km)"` / `"gemiddeld (1-3km)"` / `"ver (>3km)"` / `"onbekend"`)
  is a plain-language rating derived from `afstand tot school` — a rough rule of thumb, not a validated
  scoring model.

- **`geluidsniveau`** (`"laag"` / `"gemiddeld"` / `"hoog"` / `"zeer hoog"` / `"geen bron gemodelleerd"` /
  `"onbekend"`) is a rough rule-of-thumb classification of `geluid_totaal_db` — not a legal noise-limit
  threshold. `"geen bron gemodelleerd"` (value `0`) means RIVM did not map a significant source at this
  exact location, not that it is measured as silent.

- **`veiligheidsniveau`** (`"laag"` / `"gemiddeld"` / `"hoog"` / `"zeer hoog"` / `"onbekend"`) is a rough
  rule-of-thumb classification of `geregistreerde misdrijven per 1000 inw`, calibrated against the national
  average (~45 per 1,000 residents, live-verified 2026-08-05) — not a validated risk score.

- **`veiligheid` figures are gemeente (municipality)-level averages, not per-neighbourhood or per-address**
  (same CBS-methodology caveat as `nabijheid`), and cover *registered* crime only — there is always some
  under-reporting, stated explicitly in `veiligheid._disclaimer`.

- **`klimaat`/`fundering`/`geluid` are `null`** if the corresponding `include*` flag is off, or if no
  coordinate was available (failed geocoding). `nabijheid`/`veiligheid` are `null` if
  `enrichWithNabijheid`/`enrichWithVeiligheid` are off, or if the matched address has no gemeentecode
  (failed geocoding).

- **`funderingsgebied_*` fields are area-level (per PC6 postcode), not building-specific** — there is no
  landelijk (nationwide) foundation register at the individual-address level. This is stated explicitly in
  `_disclaimer` on every result and must not be read as a building-specific foundation assessment.

- **`klimaat` has no water-depth scenario** — despite being commonly shown on flood-risk maps, there is no
  single nationwide PDOK layer with per-coordinate water-depth scenarios (verified live 2026-07-18); only a
  qualitative risk-zone classification exists at national scale.

- If geocoding fails entirely, `basis` is `null` and `error` explains why — no charge is made for that item.

### Use cases

**Single-address dossier before a viewing:**

```json
{ "query": "Damrak 1 Amsterdam" }
```

**Batch-screen a shortlist:**

```json
{ "queries": ["Damrak 1 Amsterdam", "Markt 1, Gouda", "52.3789,4.8952"] }
```

### Pricing

This Actor uses Apify's Pay-Per-Event (PPE) pricing model.

- **Actor Start:** $0.00005 (Apify default)
- **`dossier-resultaat`:** $0.02 per address successfully geocoded and assembled into a dossier (includes
  the PDOK basis, flood-risk check, and foundation-area check — the core combined value of this actor).
  Failed geocodes are never charged.
- **`cbs-verrijking`:** $0.006 extra, only when `enrichWithCbs` is enabled and CBS data was actually found
  for the matched neighbourhood/municipality.
- **`nabijheid-verrijking`:** $0.006 extra, only when `enrichWithNabijheid` is enabled and proximity data
  was actually found for the matched municipality.
- **`geluid-verrijking`:** $0.006 extra, only when `includeGeluid` is enabled and a noise-exposure figure
  was actually found for the matched coordinate.
- **`veiligheid-verrijking`:** $0.006 extra, only when `enrichWithVeiligheid` is enabled and registered-crime
  data was actually found for the matched municipality.

### Legal

- Data sources: **PDOK Locatieserver** (Kadaster/BZK), **CBS StatLine** (CC-BY — neighbourhood statistics,
  proximity-to-amenities, and registered-crime datasets), **RIVM Atlas Leefomgeving basisgeluidkaart**
  (public WMS service), **RWS Richtlijn Overstromingsrisico** (public PDOK WMS service), and **RVO
  Indicatieve aandachtsgebieden funderingsproblematiek** (public PDOK WMS service). All public government
  data, no authentication required, no personal data involved (property/area/municipality-level only).
- **This is not a valuation, structural survey, or formal risk assessment.** It combines public
  indicators into one convenient lookup and is explicitly disclaimed as such on every single result
  (`_dossier_disclaimer`). It creates no liability for property, rental, insurance, or financing
  decisions — always consult a qualified professional (taxateur, bouwkundig inspecteur, hypotheekadviseur)
  before acting on this data.
- Foundation-risk data is area-level (postcode), not building-specific, and flood-risk data is a
  qualitative zone classification, not a water-depth or probability forecast — both are stated explicitly
  in the output, never silently implied to be more precise than they are.

### FAQ

**Q: Why does `typeFilter` default to `"adres"` instead of matching anything?**
A: PDOK's `/free` endpoint does fuzzy, best-effort matching across all object types. For a query like
"Damrak 1 Amsterdam", it can match the *street* ("weg") instead of the specific address if no type filter
is applied — that loses postcode/neighbourhood data and degrades the whole dossier. Filtering to `adres`
(the default) fixes this; clear the field only if you deliberately want postcode/place-level dossiers.

**Q: Why is there no flood water-depth or probability percentage?**
A: There is no single nationwide PDOK layer that publishes water-depth scenarios per coordinate — that data
exists only fragmented per waterschap/dijkring (e.g. via LIWO), not as one generically queryable service.
This actor only reports the confirmed, nationwide Richtlijn Overstromingsrisico zone classification.

**Q: Why is `geluid_wegverkeer_db` sometimes `0` at an address I know is quiet, but not `null`?**
A: RIVM's basisgeluidkaart only models noise above a mapped threshold for each source — `0` means "no
significant source of this type was modelled here", which is usually a genuinely quiet result, but it is
technically "not calculated" rather than a measured 0 dB. `null` means the WMS lookup itself failed
(`geluid._status: "onbekend"`) — a materially different, "we don't know" situation. Never treat the two the
same.

**Q: My address shows `funderingsaandachtsgebied: true` — does that mean this specific house has a bad
foundation?**
A: No. It means the postcode area (PC6) this address falls in has a documented share of pre-1970
buildings, a known risk factor for wooden pile foundations. It is not a per-building assessment — order an
actual funderingsonderzoek before making any decision based on this.

**Q: Why is `nabijheid` the same for every address in the same city, but `cbs` differs per neighbourhood?**
A: CBS publishes the "Nabijheid van voorzieningen" dataset (80305NED) only at municipality level — it does
not break the distance figures down by neighbourhood or address. The neighbourhood-level `cbs` block (from
a different CBS table, 86165NED) is a finer granularity. This is stated explicitly in `nabijheid._disclaimer`.
`veiligheid` (83648NED, registered crime) has the same municipality-level limitation, for the same reason:
CBS does not publish crime figures at neighbourhood granularity in this dataset.

**Q: Does a high `veiligheidsniveau` mean this specific street is unsafe?**
A: No — it's a municipality-wide average. A city can have a high overall registered-crime rate while the
specific street or neighbourhood in the dossier is perfectly quiet, and vice versa. Use it as a general
context signal, not a street-level safety assessment. It also only counts crimes reported to/recorded by
the police (`geregistreerde` misdrijven) — actual crime levels are typically somewhat higher due to
under-reporting, which is why this is never presented as a precise or complete figure.

### Related Actors

- **[PDOK Adres Geocoding & Buurtdata](https://apify.com/codeclouds/pdok-locatieserver)** — the same
  official PDOK Locatieserver this actor uses internally, useful for broader geocoding/CBS use cases beyond
  a full property dossier.
- **[NL Bodemrisico Check](https://apify.com/codeclouds/nl-bodemrisico-check)** — a complementary
  due-diligence check: soil contamination (Bodemloket/Wbb) risk on the same kind of address input.

***

*Zoekwoorden: vastgoeddossier, woningdossier, funderingsrisico check, overstromingsrisico per adres,
klimaatrisico vastgoed, taxatie-ondersteuningsrapport, CBS buurtdata woning, schoolomgeving score,
nabijheid voorzieningen, geluidsbelasting woning, geluidshinder check, veiligheid buurt, criminaliteitscijfers
gemeente, due diligence vastgoed.*

### Keywords

vastgoeddossier, property dossier, funderingsrisico, flood risk, foundation risk, netherlands real estate,
due diligence, cbs buurtdata, climate risk property, school proximity score, walkability, amenities distance,
noise exposure, noise pollution property, geluidsbelasting, crime rate municipality, neighbourhood safety,
veiligheid criminaliteit

### Changelog

#### 0.6.0

- Added: **`veiligheid`** block — CBS Geregistreerde criminaliteit (83648NED): registered crimes per 1,000
  residents, total registered count, and clearance rate for the matched municipality, plus a derived
  `veiligheidsniveau` classification (`laag`/`gemiddeld`/`hoog`/`zeer hoog`), calibrated against the
  national average. A `veiligheidsrisico` flag is added to `samenvatting.aandachtspunten` when
  `veiligheidsniveau` is `hoog` or `zeer hoog`. New optional `veiligheid-verrijking` PPE event ($0.006, only
  charged when a result is actually found). Controlled by the new
  `enrichWithVeiligheid`/`veiligheidTableId`/`veiligheidFields` input fields (default on).
- Documented: `veiligheid` figures are municipality-level averages of *registered* crime only (same
  granularity caveat as `nabijheid`) — never implied to be street-level or to capture unreported crime.

#### 0.5.0

- Added: **`geluid`** block — RIVM Atlas Leefomgeving basisgeluidkaart (Lden 2020): modelled noise exposure
  from road traffic, rail traffic, and air traffic individually via live WMS lookups, plus a cumulative
  total (`geluid_totaal_db`) and a plain-language `geluidsniveau` classification (`laag`/`gemiddeld`/
  `hoog`/`zeer hoog`). A `geluidsbelasting` flag is added to `samenvatting.aandachtspunten` when
  `geluidsniveau` is `hoog` or `zeer hoog`. New optional `geluid-verrijking` PPE event ($0.006, only charged
  when a result is actually found). Controlled by the new `includeGeluid` input field (default on).
- Documented: `GRAY_INDEX`/`geluid_*_db` value `0` means "no source of this type modelled at this exact
  location" (RIVM only maps above a threshold per source), not a measured 0 dB — explicitly disclaimed,
  and distinguished from `_status: "onbekend"` (lookup failure) which uses `null` instead.

#### 0.4.0

- Added: **`nabijheid`** block — CBS "Nabijheid van voorzieningen" (80305NED): road-distance in km to the
  nearest school, GP practice, large supermarket, train station, motorway access, childcare, and public
  green space for the matched municipality, plus a derived `schoolomgeving_score`. New optional
  `nabijheid-verrijking` PPE event ($0.006, only charged when data is actually found). Controlled by the
  new `enrichWithNabijheid`/`nabijheidTableId`/`nabijheidFields` input fields (default on).
- Documented: `nabijheid` distances are municipality-level averages (CBS does not publish this dataset at
  neighbourhood/address level) — explicitly disclaimed in the output, never implied to be finer-grained.

#### 0.3.0

- Fixed: the within-run WMS lookup cache rounded coordinates to ~11m, which could theoretically merge
  results for two adjacent-but-distinct addresses straddling a flood-risk-zone boundary (e.g. along a
  dike/street). Tightened to ~1.1m — still deduplicates units within the same building, no longer close
  enough to merge different buildings.
- Fixed: if both `includeKlimaat` and `includeFundering` are disabled, `samenvatting.risiconiveau`
  previously showed `"laag"` — indistinguishable from "we checked, nothing found". It now shows
  `"niet gecontroleerd"` when no module was actually checked.
- Added: `samenvatting.omschrijving` — one plain-language sentence summarizing the result, useful when
  exporting to a spreadsheet/CRM where nobody inspects nested JSON fields.

#### 0.2.0

- Fixed: a failed flood-risk/foundation-area WMS lookup was previously indistinguishable from a confirmed
  "no risk found" result. Both modules now report `_status: "onbekend"` and `null` fields on a lookup
  failure, never a silent `false`.
- Fixed: a run no longer fails entirely just because one address (in a single- or multi-address run)
  had no PDOK match — that's a legitimate per-item result (already reflected in `error`), not a system
  failure. Only an empty input, or literally zero processed items, fails the run.
- Added: `samenvatting` block per dossier — a coarse `risiconiveau` (laag/middel/hoog/onbekend) rollup of
  all flagged modules, so a user doesn't need to inspect three nested objects to spot a notable address.
- Added: free `RUN_SUMMARY` written to the run's key-value store (batch totals, flagged counts, average
  pre-1970 percentage, risk-level distribution) — no extra charge.
- Performance: WMS lookups are now deduplicated within a run for addresses sharing the same rounded
  coordinate (e.g. multiple units in one building), reducing redundant external calls.

#### 0.1.0

- Initial release (fase 1 per de gefaseerde bouwspec): PDOK-basis (adres-gefilterd), CBS-buurtverrijking,
  RWS-overstromingsrisicozone en RVO-funderingsaandachtsgebied in één dossier per adres.

# Actor input Schema

## `mode` (type: `string`):

Welke geocoding-modus gebruiken. 'auto' detecteert op basis van input: coördinaten → reverse, lookupId → lookup, anders → forward (adres/postcode/plaatsnaam).

## `query` (type: `string`):

Eén adres, postcode, plaatsnaam of 'lat,lon'-coördinaat voor een los dossier. Voor meerdere adressen in één run: gebruik 'queries' (array) of koppel een dataset.

## `queries` (type: `array`):

Array van adressen/coördinaten/lookup-ID's voor bulk-verwerking (één dossier per item). Elk element is óf een plain string (bv. "Damrak 1 Amsterdam") óf een object {"query": string, "lookupId": string, "mode": "auto"|"forward"|"reverse"|"lookup"} (bv. {"query": "Coolsingel 40, Rotterdam", "mode": "forward"}) — meng beide vormen niet door elkaar binnen dezelfde array. Wordt genegeerd als leeg.

## `lookupId` (type: `string`):

PDOK object-ID (bv. 'adr-8f4d573be765b4c80dd635ba73747903') om een specifiek adres direct op te halen. Alleen relevant bij mode='lookup' of auto-detectie.

## `datasetId` (type: `string`):

ID of naam van een bestaand Apify-dataset waaruit adressen gelezen worden (velden 'query'/'address'/'adres', optioneel 'lookupId'/'mode'). Alleen gebruikt als 'query' en 'queries' beide leeg zijn.

## `typeFilter` (type: `string`):

Beperkt forward-zoekopdrachten tot dit PDOK-objecttype. Standaard 'adres' — zonder filter matcht PDOK bij 'Straatnaam 1, Plaats'-achtige queries soms op straatniveau i.p.v. het exacte adres, waardoor postcode/buurtcode ontbreken. Leeg maken staat elk type toe (bv. voor postcode- of plaatsnaam-dossiers).

## `concurrency` (type: `integer`):

Aantal adressen dat tegelijk verwerkt wordt bij bulk-input. Hoger = sneller, maar meer load op de onderliggende overheidsservices. Max 20.

## `enrichWithCbs` (type: `boolean`):

Voeg CBS Kerncijfers Wijken en Buurten (inwoners, inkomen, huishoudens, stedelijkheid) toe. Staat standaard aan — dit is onderdeel van het kern-dossier. Uitzetten bespaart het 'cbs-verrijking'-event bij een resultaat.

## `cbsTableId` (type: `string`):

CBS StatLine-tabel voor de buurtverrijking. Standaard de meest recente 'Kerncijfers Wijken en Buurten'-tabel.

## `cbsFields` (type: `array`):

Zoektermen die dynamisch tegen de kolomtitels van de gekozen CBS-tabel gematcht worden. CBS wijzigt kolomcodes per jaargang, dus matching gebeurt op zoekterm i.p.v. hardcoded kolomcode.

## `enrichWithNabijheid` (type: `boolean`):

Voeg CBS 'Nabijheid van voorzieningen' toe: afstand over de weg (km) tot school, huisartsenpraktijk, supermarkt, treinstation, oprit hoofdverkeersweg, kinderdagverblijf en openbaar groen — inclusief een afgeleide schoolomgeving-score. Staat standaard aan. Uitzetten bespaart het 'nabijheid-verrijking'-event bij een resultaat.

## `nabijheidTableId` (type: `string`):

CBS StatLine-tabel voor de nabijheidsverrijking. Standaard de 'Nabijheid voorzieningen'-tabel.

## `nabijheidFields` (type: `array`):

Zoektermen die dynamisch tegen de kolomtitels van de gekozen nabijheidstabel gematcht worden — zelfde matching-mechanisme als 'CBS-velden'. Laat 'afstand tot school' erin staan als je de schoolomgeving-score wilt behouden.

## `enrichWithVeiligheid` (type: `boolean`):

Voeg CBS 'Geregistreerde criminaliteit'-cijfers toe voor de gemeente van dit adres: geregistreerde misdrijven per 1.000 inwoners, totaal aantal, opgehelderd-percentage, en een afgeleid veiligheidsniveau. Staat standaard aan. Uitzetten bespaart het 'veiligheid-verrijking'-event bij een resultaat.

## `veiligheidTableId` (type: `string`):

CBS StatLine-tabel voor de veiligheidsverrijking. Standaard de 'Geregistreerde criminaliteit; soort misdrijf, regio'-tabel.

## `veiligheidFields` (type: `array`):

Zoektermen die dynamisch tegen de kolomtitels van de gekozen veiligheidstabel gematcht worden — zelfde matching-mechanisme als 'CBS-velden'. Laat 'geregistreerde misdrijven per 1000 inw' erin staan als je het veiligheidsniveau wilt behouden.

## `includeKlimaat` (type: `boolean`):

Voeg een overstromingsrisico-indicatie toe (RWS Richtlijn Overstromingsrisico, via PDOK WMS): ligt het adres in een risicogebied + kwalitatieve classificatie. Bevat GEEN waterdiepte-scenario's — die bestaan niet als landelijke, generiek bevraagbare laag.

## `includeFundering` (type: `boolean`):

Voeg een funderingsaandachtsgebied-indicatie toe (RVO, via PDOK WMS): percentage panden gebouwd vóór 1970 in het POSTCODEGEBIED van dit adres. Grove, gebiedsgerichte indicatie, geen funderingsonderzoek per pand.

## `includeGeluid` (type: `boolean`):

Voeg een geluidsbelasting-indicatie toe (RIVM Atlas Leefomgeving basisgeluidkaart, Lden jaargang 2020, via PDOK/RIVM WMS): gemodelleerde geluidsbelasting per bron (wegverkeer, treinverkeer, vliegverkeer) plus een gecumuleerd totaal en een grove classificatie (laag/gemiddeld/hoog/zeer hoog). Geen meting op dit specifieke adres.

## Actor input object example

```json
{
  "mode": "auto",
  "query": "Damrak 1 Amsterdam",
  "queries": [
    "Damrak 1 Amsterdam",
    "Markt 1, Gouda"
  ],
  "typeFilter": "adres",
  "concurrency": 5,
  "enrichWithCbs": true,
  "cbsTableId": "86165NED",
  "cbsFields": [
    "aantal inwoners",
    "gemiddeld inkomen per inwoner",
    "huishoudens totaal",
    "stedelijkheid"
  ],
  "enrichWithNabijheid": true,
  "nabijheidTableId": "80305NED",
  "nabijheidFields": [
    "afstand tot school",
    "afstand tot huisartsenpraktijk",
    "afstand tot grote supermarkt",
    "afstand tot treinstations totaal",
    "afstand tot oprit hoofdverkeersweg",
    "afstand tot kinderdagverblijf",
    "afstand tot openbaar groen totaal"
  ],
  "enrichWithVeiligheid": true,
  "veiligheidTableId": "83648NED",
  "veiligheidFields": [
    "geregistreerde misdrijven per 1000 inw",
    "totaal geregistreerde misdrijven",
    "opgehelderde misdrijven, relatief"
  ],
  "includeKlimaat": true,
  "includeFundering": true,
  "includeGeluid": true
}
```

# Actor output Schema

## `results` (type: `string`):

Alle vastgoeddossiers in het default dataset.

## `runSummary` (type: `string`):

Gratis aggregatie van deze run: aantal succesvol/mislukt, aantal in overstromingsrisicogebied/funderingsaandachtsgebied, en verdelingen per geluids-/veiligheidsniveau. Kost geen extra events, puur samengevat uit al opgehaalde data.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "query": "Damrak 1 Amsterdam",
    "queries": [
        "Damrak 1 Amsterdam",
        "Markt 1, Gouda"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("codeclouds/vastgoed-xxl").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "query": "Damrak 1 Amsterdam",
    "queries": [
        "Damrak 1 Amsterdam",
        "Markt 1, Gouda",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("codeclouds/vastgoed-xxl").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "query": "Damrak 1 Amsterdam",
  "queries": [
    "Damrak 1 Amsterdam",
    "Markt 1, Gouda"
  ]
}' |
apify call codeclouds/vastgoed-xxl --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,codeclouds/vastgoed-xxl"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/E0AllrtnsfgD5I29A/builds/bbqEUXwXzSJJFXNA9/openapi.json
