StikstofNu · Onderzoek · Dit onderzoeksrapport is in het Engels.
AERIUS deconstructed: a code-level technical review of the Dutch nitrogen deposition calculation system (release 2026.0.0)
Version 4.2, published . By StikstofNu.
SHA-256 checksum of the source paper of this edition: 17603edfd2ba3797be81a91cedc35999eb7b3d0d288e862bd807673816b37546
This paper is the first of the aerius-research series.
Version 4.2, 2026-10-30 (public edition, see the edition note below). Upstream pins: gitlab.com/AERIUS/AERIUS@fb8812fd74f9 ("Release 2026.0.0", 2026-10-06), aerius/IMAER-java@ecdc81b0f1e2, aerius/IMAER@f2a401b3eff2 (IMAER 6.0.2), rivm-syso/OPS@e62fdf721d67 (OPS-LT 5.3.1.0), rivm-syso/DepAC@fef2266568b8, and 34 further repositories of the GitHub organisation aerius (Table 2 and Appendix A).
Independence and use of names. This is independent research into published source code. The author has no affiliation with AERIUS, RIVM, the Ministry of Agriculture, Fisheries, Food Security and Nature (LNV) or BIJ12, and the work was neither commissioned nor reviewed by them. AERIUS is the name of the Dutch government's nitrogen deposition calculation tool; the name is used here only to identify the system under study, and no affiliation is implied. Nothing in this paper replaces an official AERIUS Calculator result. Conflict of interest: the research is motivated by a downstream calculation service that uses AERIUS Connect (footnote 1, Section 10), and the Connect batch of Section 3.5 ran on that service's production key.
Edition note. This is the public edition of this paper. It is the text of version 4.2 with these changes: references to working files of the research materials are replaced by plain descriptions or removed; exact site coordinates are withheld; ten findings that concern security properties of a public service, or that cannot be told apart from or overlap with such findings, are not part of this edition, together with the endpoint details, limits and run observations that would let a reader reconstruct them; and an edition note with a change list, a glossary and two appendices (pinned repositories, data and materials) are added. The finding numbers keep the numbering of the full version, so the sequence has gaps, and counts of findings follow from the omission.
Change from version 4.1 to version 4.2, 30 October 2026:
- Abstract and the answer to RQ5: the Monte Carlo rate of finding F9 is now stated per simulated draw, 225 in 1,560,000 draws, 1.44 per 10,000 draws. Version 4.1 stated it per hexagon, but its denominator is simulated draws, not hexagons. No other result, number or finding is changed.
Changes from version 4 to version 4.1, which the public edition lists here because the full version lists them at its end:
- Section 8.2 and the answer to RQ4: the per-kilogram deposition ratio of K07 and K08 to K01 (version 4: 1.26 to 1.84 times on the 488 receptors within 10 km) did not reproduce from the run data. It is replaced by values recomputed receptor by receptor and given by distance band (1 km bands from 11 km to 15 km).
- Abstract: "almost entirely public" is replaced by a statement of what is public (application code, model code, model data packages). In this edition only, the statement that the product database content is closed carries the words "(see erratum)".
- Key results: the rules that decide are in public code, and the inputs that feed them are in part closed.
- Meteorological period (abstract, key results, Table 1, Section 2.3, F22, conclusion): "wrong" and "contradicts" are replaced by "differs from"; in F22 the display "contradicts the decision" became "differs from the decision".
- Section 7.3: the OPS terms of use are reported by article; "obtained only by registration" became "the terms state that the right of use is obtained by registration"; the statement that the matter will be raised with RIVM before distribution is removed.
- Reporting: statements that findings will be reported to others are removed; the paper makes no commitment on whether or when any finding is reported.
- The sentence that AERIUS is a registered trademark in the Benelux, which was not verified, is replaced by a statement of nominative use.
- An erratum pointer after the key results box states what Paper 2 found about published TNO and RIVM source characteristics; it covers the abstract, key results, Sections 3.6, 7.2, 8.2, 8.3, 8.4 item 3, the answers to RQ3 to RQ5, Section 12 and any other statement that the emission factors or source characteristics are closed, and says that the comparison covers 11 of the 13 jobs and is first and partial. Sections 3.6, 8.3, 8.4 item 3, the answer to RQ4 and Section 12 no longer say that the replica was not yet compared or that the source characteristics are closed without qualification.
Abstract
AERIUS Calculator is the program that Dutch regulation prescribes for calculating the nitrogen deposition of projects on Natura 2000 habitats. We review release 2026.0.0 at code level: we pinned 39 public repositories, traced one calculation from IMAER input to a deposition value per hexagon, built and tested the IMAER library, the AERIUS OPS and SRM2 modules and OPS 5.3.1.0, and submitted 13 jobs to the production AERIUS Connect service. The application code, the model code and the model data packages are public. Emissions are recomputed from activity data; construction emissions are read as kg per year and IMAER has no duration field, so within one 12-month window only the total emission counts; receptors are limited to relevant habitat within 25 km; hexagons near sources are averaged over 96 or 384 sub-receptors; OPS runs on 2014 to 2023 meteorology, which the user handbook gives as 2005 to 2014; and a hexagon counts when its unrounded deposition exceeds 0.00499999 mol/ha/y. The product database content is closed (see erratum); through Connect we measured some of it, for example 0.26 g NOx/kWh for a Stage IV machine and identical factors for Stage V. Of seven decisive rules, four are documented, two partly and one with a different meteorological period. This edition reports 15 of the 25 findings of the full version, among them a rounding defect in sub-receptor averaging. In a Monte Carlo on real OPS sub-point fields with illustrative sources, that defect changes the verdict on the NOx + NH3 total near the bound, in both directions, in 225 of 1,560,000 simulated draws (1.44 per 10,000 draws).
Key results
- The rules that decide whether a project "causes deposition" are public code: a hexagon counts when its unrounded nitrogen deposition is above 0.00499999 mol/ha/y, and only relevant habitat within 25 km is calculated. The inputs that feed these rules are in part closed (see the erratum pointer below).
- What is not public is part of the database content, above all the emission factors, which we measured for several classes through 13 AERIUS Connect jobs. For mobile machinery and roads the source characteristics turned out to be published (see the erratum pointer below).
- Within one 12-month window, the duration of construction work cannot enter the calculation: IMAER has no field for it and emissions are read as kg per year, so only the total emission counts. For longer work the user enters the emission of the worst 12 consecutive months, and compressing the work can raise the result. (Two jobs with the same input gave identical results on every receptor; that shows the service is deterministic, it is not a test of duration.)
- The official handbook describes most rules correctly, but not the exact threshold test, and the period it gives for the weather data (2005 to 2014) differs from that of the data set the code reads (2014 to 2023).
- Of the 15 findings in this edition, the most important is a rounding defect that can flip the verdict for hexagons near the bound, in both directions on the NOx + NH3 total that AERIUS tests.
Erratum pointer (added in version 4.1, extended in the second round). Where this paper says that emission factors and source characteristics are closed database content, or that a replica has not yet been compared with Connect (abstract, key results, Sections 3.6, 7.2, 8.2, 8.3 and 8.4 item 3, the answers to RQ3 to RQ5, Section 12, and any other statement that the emission factors or source characteristics are closed), Paper 2 of this series (a working draft, one site) found that for mobile machinery and for roads these values are published: by TNO (a memo of 20 June 2025 and a 2026 annex, which also gives the emission factors of mobile machinery) and by RIVM (the sheet Standaard bronkenmerken 2026). With the published set for each category, a replica that calls AERIUS's OPS and SRM2 code reproduced Connect's results for 11 of the 13 jobs of this paper's batch (D02 repeats D01 and P01 has no receptor file) without any difference at OPS's printed precision, with the aarch64 build of OPS under emulation and given Connect's emissions and receptor ids. That comparison is a first and partial one: nine of the 11 jobs give distinct comparison results, no hexagon lay within 300 m of a source, and it deviates from the method of Section 8.4 (Section 2.3 of Paper 2 lists the deviations). Paper 2 does not show this for other source types, sectors or sites, so the statements of this paper about closed content stand for everything else.
Keywords: AERIUS; nitrogen deposition; Natura 2000; OPS; SRM2; DEPAC; IMAER; source code review; reproducibility; regulatory software; open government code.
1. Title and scope note
The title, version line, abstract, key results box and keywords above constitute Section 1. Scope: the Dutch "OwN2000" (Omgevingswet Natura 2000) calculation path of AERIUS Calculator and AERIUS Connect at release 2026.0.0. The UK variant (ADMS, "NCA"), AERIUS Monitor, Register, GRIP and the CIMLK air-quality path are mentioned only where they share code with that path.
2. Introduction
2.1 What AERIUS is
AERIUS is a family of web applications for nitrogen deposition on Natura 2000 areas. The core monorepo describes the runtime flow as a single-page application that calls a Connect REST service, which queues work through RabbitMQ and a task manager to a chunker and to OPS, SRM and ADMS model workers, with results in a database and a file server and maps served by GeoServer (AERIUS@fb8812f:AGENTS.md:229-247). The products in the shared code base include Calculator (permit calculations), Connect (the machine API), Monitor, Register and GRIP (geoserver-core@83bc6b4:src/site/apt/geoserver_aerius.apt:3-8; AERIUS@fb8812f:API/ lists api-own2000, api-analysis, api-register, api-check, api-cimlk, Section 6). Dispersion and deposition are computed by two models: OPS-LT, the long-term Lagrangian model of RIVM (OPS@e62fdf7:ops_lt/src/ops_lt_main.f90:27-36), and SRM2, a road model that AERIUS implements in Java (AERIUS@fb8812f:SJ/srm2/algorithm/SRM2Algorithm.java).
2.2 Legal role
Under the Omgevingsregeling, AERIUS Calculator is the method for calculating nitrogen deposition when determining whether an activity is a Natura 2000 activity (art. 4.15; art. 6.15 says the same for activities regulated by provincial rules), and an application for an omgevingsvergunning for a Natura 2000 activity must, where nitrogen deposition occurs, include a calculation made with AERIUS Calculator (art. 7.197h, first paragraph, under e) [R1, R2]. The requirement in art. 7.197h applies to that permit only, not to other permit types. The version is fixed in Bijlage II (art. 1.4); with effect from 6 October 2026 a regulation of the Minister of Agriculture, Fisheries, Food Security and Nature changed that annex to version 2026, without transitional law, so the new version also applies to applications still pending [R3]. The legal text does not itself say "only"; AERIUS states that from that date only version 2026 may be used for consent decisions [R4]. In an interim ruling of 2 November 2022 (Porthos, ECLI:NL:RVS:2022:3159) the Council of State set aside the partial construction exemption (art. 2.9a Wnb with art. 2.5 Bnb) for conflict with art. 6 of the Habitats Directive, so construction-phase deposition must again be assessed project by project [R5]. In a ruling of 5 April 2023 that is final for one appellant and interim for the others (ViA15, ECLI:NL:RVS:2023:1299), it held in the interim part that the minister was allowed to apply the 25 km calculation distance, because he had adequately reasoned with RIVM and TNO reports and an expert judgment that 25 km is the limit within which OPS gives scientifically reliable statements on the deposition of an individual source [R6]. The legal texts were read on 22 October 2026 in the primary sources: wetten.overheid.nl (version in force from 6 October 2026), the Staatscourant and data.rechtspraak.nl.
2.3 Why a code-level review matters
Three reasons motivate the review.
- Transparency. A calculation that decides whether a permit is needed has the character of a legal norm. Its rules (which hexagons count, how near-source values are averaged, where the threshold is applied) should be knowable. Most of these rules are described in the official user documentation, but some decisive details are not: the exact threshold comparison, the absence of any time window for short-lived emissions, and the meteorological period, which the Handboek 2026 gives as 2005 to 2014 while the code uses 2014 to 2023 (Table 1).
- Reproducibility. A permit calculation should be reproducible by a third party: the competent authority, an objector, a court expert. That requires the model, the model data and the inputs to be identifiable and obtainable (Sections 7 and 8).
- Product dependence. Downstream services increasingly submit calculations through AERIUS Connect. Their correctness depends on undocumented behaviour such as silent code conversions and output formats (Sections 6 and 10).
Table 1. Seven decisive rules against the official AERIUS user documentation for 2026. Documents searched: Handboek werken met AERIUS Calculator 2026 [R10], Instructie gegevensinvoer [R13], release notes [R7], Handboek data, Actualisatierapport, Handreiking randeffecten 25 km [R15], Handreiking hersteldoel [R14], the product page FAQ and the 2026 news items.
| Rule as implemented | Code | Where documented | Documented? |
|---|---|---|---|
| A hexagon counts when the unrounded NOx + NH3 deposition exceeds 0.00499999 mol/ha/y; two-decimal display | AERIUS@fb8812f:DB/data/sql/nl-latest/permit_lower_bounds/load.sql:1-4; Section 5.8 | 0,005 bound: [R10] Sections 2.2, 2.3, 7.5.2; two decimals: [R13] Section 11.1.5. Not found: the value 0.00499999, the test on the unrounded total, the strict comparison. [R15] pp. 13, 15 says only results "> 0,005" are shown, which would exclude exactly 0.005, which the code counts | Partly |
| Construction emissions are entered and calculated as kg per year, with no time window | AERIUS@fb8812f:OJ/io/BrnFileWriter.java:173; Section 5.10 | kg/jaar input: [R10] Section 6.9; "the 12 consecutive months with the highest deposition": [R13] Section 2.3. Not stated: that duration has no effect at equal total emission only within one 12-month window | Partly |
| OPS uses 2030 prognosis chemistry for every calculation year from 2024 | getPrognoseYear, AERIUS@fb8812f:OJ/io/ControlFile5300WriterUtil.java:47-60 | [R10] Sections 7.1.4 and 4.12 | Yes |
| Sub-receptors: 384 points below 150 m, 96 to 300 m, 20 m exclusion, habitat overlap, arithmetic mean, none for custom points | findLevel, AERIUS@fb8812f:UT/receptor/SubReceptorLevelMap.java:46-63; Section 5.4 | [R10] Section 3.6, pp. 32 to 35. Not stated: the rounding of the mean (F9) | Yes |
| Hersteldoel hexagons excluded from the main statistics and reported in a separate appendix | extra_assessment_hexagons, AERIUS@fb8812f:DB/main/sql/calculator_common/calculator/10-public/03-functions/02-project_calculation.sql:372,391-396; Section 5.9 | [R14] Sections 1.4 to 1.6; [R10] Section 7.6.2 and annex 36 | Yes |
| Roads: SRM2 within 5 km, OPS beyond | MAX_SOURCE_DISTANCE, AERIUS@fb8812f:SJ/srm/SRMConstants.java:59 | [R10] Sections 3.3 and 4.3 | Yes |
OPS long-term meteorology m014123c (2014 to 2023) | AERIUS@fb8812f:OJ/io/ControlFile5300WriterUtil.java:30 | Multi-year meteorology: [R10] Section 4.7, which states the period 2005 to 2014. The period 2014 to 2023 is stated only by RIVM for OPS 5.3.1.0 [R8] | Differs (F22) |
Of the seven rules, four are documented, two partly and one with a period that differs from the one the code reads. The undocumented parts are narrow but decisive near the bound.
2.4 Research questions
- RQ1 (architecture). What are the components of AERIUS 2026.0.0, how are they built and deployed, and how do they interact?
- RQ2 (calculation chain). How does a calculation proceed from an IMAER GML file to a deposition value per hexagon, and which rules decide the reported result?
- RQ3 (openness). Which parts of code and data are public, which are closed, and which of our statements are inferences?
- RQ4 (offline reproducibility). To what extent can a third party reproduce an AERIUS result offline, and what calibration is needed?
- RQ5 (risks and defects). Which defects, risks and documentation mismatches does the code show?
2.5 Contributions
(i) A pinned, citable map of the system (Section 4); (ii) the calculation chain with its constants and code locations (Section 5); (iii) an inventory of public and closed data (Section 7); (iv) a reproducibility assessment backed by builds, offline runs and 13 production Connect jobs, including measured emission factors (Sections 5.2 and 8); (v) a rule-by-rule comparison with the user documentation (Table 1); (vi) 15 findings with severity and evidence (Section 9).
3. Method
3.1 Corpus
The corpus is every public repository of the GitHub organisation aerius that was non-empty on 2026-10-22, the two RIVM model repositories, and the AERIUS core monorepo on GitLab, each pinned at one commit (Appendix A). Table 2 lists the repositories used in this paper; the remaining pins are build and UI utilities listed in Appendix A.
Table 2. Repositories cited (short SHA; the pin list is in Appendix A).
| Alias | Repository | License | Pin | Commit date | Role |
|---|---|---|---|---|---|
| AERIUS | gitlab.com/AERIUS/AERIUS, branch calculator2026.0_fixes (shallow) | AGPL-3.0 | fb8812f | 2026-10-06 | Calculator, Connect, workers, product DB SQL, deployment |
| IMAER-java | aerius/IMAER-java | AGPL-3.0 | ecdc81b | 2026-09-21 | IMAER parser, validators, emission formulas |
| IMAER | aerius/IMAER | none stated | f2a401b | 2026-09-30 | XSD 6.0.2 and code lists |
| QGIS | aerius/IMAER-QGIS-plugin | GPL-2.0 | 2bc81a0 | 2026-08-18 | Independent Connect v8 client |
| OPS | rivm-syso/OPS | GPL-3.0 | e62fdf7 | 2025-03-25 | OPS-LT 5.3.1.0 (Fortran) |
| DepAC | rivm-syso/DepAC | EUPL-1.2 | fef2266 | 2026-10-12 | Stand-alone DEPAC 5.0.5 |
| emission-service | aerius/emission-service | AGPL-3.0 | d172ecf | 2026-08-26 | Emission-factor schema and REST API (dataset 2023) |
| database-build | aerius/database-build | AGPL-3.0 | 673f770 | 2026-10-14 | Ruby database build tooling |
| database-modules | aerius/database-modules | AGPL-3.0 | fba2337 | 2026-08-18 | Grid, nature area and habitat schemas |
| taskmanager | aerius/taskmanager | AGPL-3.0 | c568367 | 2026-10-06 | Queue scheduler |
| file-server | aerius/file-server | AGPL-3.0 | a0ba2f3 | 2026-10-20 | File hand-over (local or S3) |
| webdocument-exporter | aerius/webdocument-exporter | none stated | b2cc505 | 2026-09-28 | Headless Chrome to PDF |
| geoserver-core | aerius/geoserver-core | none stated | 83bc6b4 | 2026-09-16 | GeoServer base |
| source-mesh-matrix | aerius/source-mesh-matrix | AGPL-3.0 | eaef37b | 2026-08-24 | Source-receptor matrix service |
| search, auth, rabbitmq, cicd-scripts, aerius-connect-examples | aerius/* | AGPL-3.0 / none | see Appendix A | Supporting services and history |
Of the 39 pinned repositories, 23 declare AGPL-3.0, 11 state no license and 5 carry another open license (Appendix A). Two repositories (aerius/vue-components, aerius/dockerfiles) were empty and not cloned. The IMAER-java README carries a disclaimer that "no rights are granted" and that all rights not expressly granted are reserved, above its license section (IMAER-java@ecdc81b:README.md:9-11).
3.2 Reading method
Each component was read by a separate pass that produced a working note (unpublished). The passes followed call paths from entry points (ImaerImporter.importStream, CalculateResource, OwN2000CalculatorFactory, OPSPerformer, SRM2Calculator, SQL statistics functions) rather than reading files in isolation. For this paper we then (a) merged the notes, (b) resolved contradictions between them by re-reading the code at the cited location, and (c) re-checked the load-bearing claims of an evidence index of 62 rows in the pinned sources (the index is not part of this edition). Where a note and the code disagreed, the code was taken as authoritative.
Citation check. Every code citation is checked mechanically with a script (Python standard library; per-citation results are kept with the research materials). For each citation in the forms of Section 3.3 the script tests that (a) the alias maps to a pinned submodule, (b) the SHA is a prefix of the pinned commit, (c) the file exists at that commit, (d) every cited line or range lies within the file, and (e) at least one of up to three distinctive identifiers or numbers from the citing sentence or table row occurs within five lines of the cited range. Citations that fail only (e), because the sentence paraphrases what the code names as a constant, were verified by hand and recorded with a note in the research materials. On version 4 the paper has 415 code citations, all passing (a) to (e), 89 of them by a hand-verified keyword. References to data files of the research materials were checked for existence separately, and each number taken from the batch files was re-read in them.
3.3 Evidence convention
A code claim is cited as repo@sha:path:line, with the repository alias and short SHA of Table 2. To keep citations readable, the following path prefixes are used. Within AERIUS@fb8812f:
| Prefix | Path |
|---|---|
CS/ | source/calculator/aerius-connect-service/src/main/java/nl/overheid/aerius/connectservice/ |
API/ | source/calculator/aerius-connect-service/src/main/resources/static/api/v8/ |
WK/ | source/calculator/aerius-worker/src/main/java/nl/overheid/aerius/worker/ |
WUI/ | source/calculator/aerius-calculator-wui-client/src/main/java/nl/overheid/aerius/wui/application/ |
CH/ | source/commons-aerius/aerius-commons-chunker/src/main/java/nl/overheid/aerius/calculation/ |
OJ/ | source/commons-aerius/aerius-commons-ops/src/main/java/nl/overheid/aerius/ops/ |
SJ/ | source/commons-aerius/aerius-commons-srm/src/main/java/nl/overheid/aerius/ |
UT/ | source/commons-aerius/aerius-commons-util/src/main/java/nl/overheid/aerius/ |
IMA/ | source/commons-aerius/aerius-commons-imaer/src/main/java/nl/overheid/aerius/ |
EXP/ | source/commons-aerius/aerius-commons-export/src/main/java/nl/overheid/aerius/export/ |
REPO/ | source/commons-aerius/aerius-commons-repository/src/main/java/nl/overheid/aerius/db/ |
SH/ | source/commons-aerius/aerius-commons-shared/src/main/java/nl/overheid/aerius/shared/ |
DOM/ | source/commons-aerius/aerius-commons-domain/src/main/java/nl/overheid/aerius/ |
DB/ | source/database/src/ |
Within IMAER-java@ecdc81b: G/ = source/imaer-gml/src/main/java/nl/overheid/aerius/, U/ = source/imaer-util/src/main/java/nl/overheid/aerius/, IS/ = source/imaer-shared/src/main/java/nl/overheid/aerius/shared/, T/ = source/imaer-gml/src/test/. Documents are cited as [Rn] (References). Statements that rest on our reading rather than on literal code or document text are marked [INF]. Observations from our own builds and runs are marked [RUN].
3.4 Builds and runs performed
All builds used copies in a scratch directory, so the pinned submodules were not modified (Ubuntu 24.04, OpenJDK 21.0.12, Maven 3.9.11, GNU Fortran 13.3.0). We built IMAER-java and the AERIUS modules aerius-commons-ops and aerius-commons-srm, whose tests pass (171 and 212 run, no failures) [RUN]; built OPS 5.3.1.0 with gfortran (ops -r reports L-5.3.1.0 ; 19 dec 2024) and ran it with the AERIUS production package ops/5.3.1.0-5, at about 1.7 s per substance for 200 receptors [RUN]; ran the DepAC tests; probed Nexus and the open-data WFS anonymously and read-only; ran the sub-receptor harness and the Monte Carlo runs of F9 (the F9 harness); queried the GitLab API for newer commits (Section 3.7); and submitted 13 Connect jobs (Section 3.5).
3.5 AERIUS Connect batch
To test statements that the code alone cannot settle, we submitted one batch of 13 jobs to the production AERIUS Connect service on 2026-10-22 between 17:24:57 and 17:26:30 UTC, through an automated client, with the approval of the owner of the downstream service and a cap of at most 15 jobs (manifest.json; batch-uitkomst.json). The service reported aeriusVersion 2026.0.0_20260929_655bf03b53 and database version 2026.0.0_655bf03b53_calculator_nl_stable both before and after the batch. All jobs used one location in Terwolde (exact coordinates are withheld in this edition), with the Natura 2000 area Rijntakken at about 0.8 km, calculation year 2026 and one PROPOSED situation (manifest.json):
- K01 to K08: one point source of mobile machinery (sector 3300) per job, five categories, 100 operating hours, power 40, 100 or 300 kW where the category takes power;
- K09 and K10: one road of 500 m (NON_URBAN_ROAD_NATIONAL, 60 km/h) with 10,000 light or 1,000 heavy vehicles per year;
- D01 and D02: a construction case (80 hours of a 200 kW Stage IV machine and 60 heavy vehicles), intended as a two-week and a twelve-month spread of the same activity;
- P01: the D01 input with PDF output.
Each request was first validated with utility/validate?strict=true. The client submitted at most 3 jobs at a time and made no retries: the first submission error would have stopped the batch. None occurred. The research materials keep per job the request GML and options, the validation and calculate responses, the status timeline and a summary with the recomputed emissions, the 20 highest receptors and derived factors (K01/, etc.); the per-receptor results are kept as receptoren.csv.gz per job. Results from this batch are marked [RUN] with the data file as evidence.
3.6 What was not done
- No production data from
dbdatawere obtained; the repository requires credentials (HTTP 401) and none were sought. - Apart from the 13 jobs of Section 3.5, made with the downstream service's key, no live endpoint was exercised beyond anonymous read-only probes.
- The same scenarios were not calculated in the Calculator UI; that comparison needs a manual step that has not been carried out (Section 6.4).
- This paper did not compare an offline replica per hexagon with the Connect results; Paper 2 made a first, partial comparison (erratum pointer after the key results box, Section 8.3).
- The full Calculator, worker and database stack was not deployed; the chunker and workers require the PostgreSQL product schemas and RabbitMQ.
- Not every module of the monorepo was read. The PDF print data aggregation and the GML export content were read only at the level needed for Section 6.
3.7 Version identity and drift
On 2026-10-22 the public GitLab project had no tag, branch or commit newer than the pinned fb8812f ("Release 2026.0.0", 2026-10-06), whose parent is 71a6015 "Release 2025.3", and the live Calculator reported "Version 2026.0.0" [RUN]. In 2025 a hotfix release followed one day after the yearly release; for 2026 none exists after 16 days. The only change after release that we found is documentary: annex 1 of the Instructie gegevensinvoer was replaced on 8 October 2026. The version string is the manifest Implementation-Version, of the form ${project.version}_${buildDateTime}_${buildRevision} (AERIUS@fb8812f:source/commons-aerius/aerius-commons-shared/pom.xml:94), so Connect's 2026.0.0_20260929_655bf03b53 names a build of 29 September 2026 from revision 655bf03b53. That revision does not exist in the public repository (GitLab API, HTTP 404) [RUN]. [INF] The public history consists of squashed release snapshots of an internal repository, so the deployed build can be tied to the public code by its version label, not by its commit. We therefore state no code drift at the level of public commits, and we cannot exclude differences between the build and the public snapshot, nor changes in the closed database content.
4. System architecture (RQ1)
4.1 Components
| Component | Implementation | Role | Evidence |
|---|---|---|---|
| Calculator web UI | GWT with vue-gwt and gwt-client-common | Interactive calculations, print route for the PDF | AERIUS@fb8812f:source/calculator/aerius-calculator-wui-client; WUI/print/own2000/ |
| Connect service | Spring Boot, spec-first OpenAPI under /api/v8 | Machine API and UI back end (/ui/**) | AERIUS@fb8812f:API/api-common.yaml:45-46; AGENTS.md "Server APIs are spec-first" |
| IMAER library | Java (JAXB), GWT-compatible DTOs | Parse, validate, convert, compute emissions | IMAER-java@ecdc81b:source/pom.xml:146-151; source/imaer-gml/pom.xml:54,64-65; source/imaer-shared/pom.xml:33,94; source/imaer-shared/src/main/resources/nl/overheid/aerius/ImaerShared.gwt.xml |
| Task manager | Java 21, RabbitMQ client | Priority and capacity share per client queue | taskmanager@c568367:README.md:7-10,36-52 |
| Broker | RabbitMQ 4.2, quorum queues, 512 MB messages | Transport; payloads are gzip-compressed Java serialization | rabbitmq@f8e5558:docker/Dockerfile:1-17; taskmanager@c568367:source/taskmanager-client/.../util/QueueHelper.java:67-75 |
| Chunker worker | Java (aerius-commons-chunker) | Splits jobs, dispatches engine tasks, aggregates | AERIUS@fb8812f:CH/ |
| OPS worker | Java wrapper (aerius-commons-ops) around the RIVM Fortran binary | Writes .ctr, .brn, .rcp; runs OPS; reads results | AERIUS@fb8812f:OJ/runner/OPSPerformer.java |
| SRM worker | Java SRM2 (aerius-commons-srm) | Roads within 5 km | AERIUS@fb8812f:SJ/srm2/ |
| Export and PDF workers | Java; headless Chromium with iText post-processing | GML with results; PDF report | AERIUS@fb8812f:WK/export/, WK/pdf/; webdocument-exporter@b2cc505:README.md:7,27-64 |
| Product database | PostgreSQL/PostGIS, built with Ruby database-build | Receptors, habitats, factors, jobs, results, thresholds | AERIUS@fb8812f:DB/build/versions/calculator_nl_latest.rb:19-32 |
| File server | Spring Boot, local disk or S3 | Inputs and results | file-server@a0ba2f3:README.md:3-11 |
| GeoServer | GeoServer 2.28.5 WAR; SQL-view layers | Result and context maps; open-data WFS | geoserver-core@83bc6b4:pom.xml:25,58; AERIUS@fb8812f:source/geoserver/ |
| Search | Spring Boot; PDOK Locatieserver and N2000 WFS | Map search | search@aa19ff2:README.md:5-23; search-service-pdok-locatieservice/; search-service-n2000-assessment-areas-nl/ |
| Source-mesh-matrix | Spring Boot, ClickHouse | Pre-computed source-receptor values (GRIP context) [INF] | source-mesh-matrix@eaef37b:README.md:1-4 |
4.2 Technologies and deployment
The Java services target Java 21 (taskmanager@c568367:source/Dockerfile:16; AERIUS@fb8812f:docker/common/api/Dockerfile.template:2-19). The Docker composition for NL comprises database-calculator, taskmanager, api, calculator-server, geoserver-calculator, geoserver-opendata, worker, worker-pdf, worker-ops, worker-srm and an OpenTelemetry collector (AERIUS@fb8812f:docker/NL/docker-compose.yaml:21-72). Production runs on AWS (eu-west-1): ECS services, Aurora PostgreSQL 17, S3 for the file server, SES for mail, with Terraform modules from a private repository (AERIUS@fb8812f:terraform/application_services/app.hcl:1-104; terraform/basicinfra/modules.json:1-7). The production sizing allows 2 to 12 chunker tasks of 8 processes, 2 to 36 OPS worker tasks of 8 processes, 2 SRM processes and 2 to 4 PDF tasks on spot capacity (AERIUS@fb8812f:terraform/.../service_sizing.tf:5-25; services.yaml.tftpl:337-338,387-388,618-619), and the task manager advertises 96 connect (chunker) slots, 288 OPS slots and 16 SRM slots (services.yaml.tftpl:100-108). CI uses Jenkins with a shared library and GitHub Actions publishing to Nexus (cicd-scripts@aed3251:jenkins-library/vars/cicdPipeline.groovy; AERIUS@fb8812f:Jenkinsfile-QA).
4.3 Diagram
flowchart LR
subgraph Clients
UI["Calculator UI (GWT)"]
CL["Connect v8 clients"]
end
subgraph API["Connect service /api/v8"]
IMP["Import + validate<br/>IMAER-java + GMLHelperImpl"]
EM["Emissions recomputed<br/>(DB factors, year)"]
JOB["Job: created -> queuing"]
end
DB[("PostgreSQL/PostGIS<br/>product DB (dbdata)")]
FS[("File server<br/>S3")]
TM["RabbitMQ + taskmanager<br/>priority, maxCapacityUse"]
subgraph Workers
CH["Chunker<br/>situations x receptor chunks"]
OPS["OPS worker<br/>OPS 5.3.1.0 Fortran"]
SRM["SRM worker<br/>Java SRM2, within 5 km"]
EXP["Export: GML with results"]
PDF["PDF worker<br/>Chromium + iText"]
end
GS["GeoServer<br/>wms views, open data WFS"]
UI --> API
CL -->|"multipart, api-key"| API
IMP --> EM --> JOB
IMP <--> DB
EM <--> DB
JOB -->|"scenario JSON"| FS
JOB -->|"connect_export_small/large<br/>calculator_ui_*"| TM
TM --> CH
CH -->|"one task per engine"| OPS
CH --> SRM
OPS -->|"results"| CH
SRM -->|"results"| CH
CH -->|"sum per receptor"| DB
DB -->|"threshold, statistics (SQL)"| EXP
DB --> PDF
EXP --> FS
PDF --> FS
FS -->|"resultUrl"| CL
DB --> GS --> UI
| Step | Component | Input | Output |
|---|---|---|---|
| 1 | Connect service | IMAER GML, options, key | Validated scenario, recomputed emissions, job |
| 2 | Task manager | Task per queue | Released task by priority and capacity share |
| 3 | Chunker | Job | Engine tasks per situation and receptor chunk; summed results |
| 4 | OPS and SRM workers | Sources, receptors | Deposition per receptor and substance |
| 5 | Product database | Results | Thresholded statistics (SQL) |
| 6 | Export and PDF workers | Statistics, results | GML with results, PDF report on the file server |
4.4 Walk-through
A client posts IMAER GML files and JSON options to own2000/calculate with an api-key header (AERIUS@fb8812f:API/api-own2000.yaml:44-76). The Connect service imports and validates each file with IMAER-java and a database-backed helper, recomputes emissions, replaces the options with the fixed formal assessment set, creates a job, writes the scenario to the file server and sends a task to the task manager (AERIUS@fb8812f:CS/resource/CalculateResource.java:65-95; CS/service/AbstractCalculateService.java:93-106,242-249,308-322). The task manager releases tasks per client queue according to priority and capacity share (taskmanager@c568367:source/taskmanager/src/main/java/nl/aerius/taskmanager/scheduler/priorityqueue/PriorityTaskScheduler.java:146-173,237-252). The chunker splits the job by situation and receptor chunk and sends one task per engine to OPS and SRM workers; it sums their results per receptor and stores them (Section 5). Thresholding and statistics are SQL functions over the stored values. Export workers write GML with results or render the PDF from a print route, store the result on the file server and set the job's resultUrl (AERIUS@fb8812f:WK/export/ExportEmailHandler.java:68-82).
Answer to RQ1. AERIUS 2026.0.0 is a queue-based Java service architecture on AWS: a spec-first REST service, a priority task manager over RabbitMQ, a chunker that fans out to an OPS worker (wrapping the RIVM Fortran binary) and a Java SRM2 worker, PostgreSQL/PostGIS for data, results and the decision rules, an S3-backed file server and GeoServer. The application code is public in the GitLab monorepo; the infrastructure modules, production configuration values and database content are not.
5. The calculation chain in detail (RQ2)
5.1 IMAER input and validation
Standard. IMAER is a GML 3.2 application schema generated by ShapeChange; the current version is 6.0.2 in namespace http://imaer.aerius.nl/6.0, shared with 6.0.0 and 6.0.1 (IMAER@f2a401b:gmlapplicatieschema/6.0.2/IMAER.xsd:1,9-14). The 6.0.1 to 6.0.2 change adds only power (kW) on StandardOffRoadMobileSource (IMAER.xsd:3666-3670). One file holds one situation with a SituationMetadata whose situationType is REFERENCE, PROPOSED, TEMPORARY, OFF_SITE_REDUCTION, COMBINATION_REFERENCE or COMBINATION_PROPOSED (IMAER.xsd:3287-3350). The required source attribute sectorId is a free integer (IMAER.xsd:1622); valid codes are defined by code lists ("waardelijsten"), not by the schema. In the 6.0.2 list, sectors 3210 and 3220 are absent and 3300 "Mobiele werktuigen" is present (IMAER@f2a401b:waardelijst/6.0.2/20260928/IMAER_sectors_20260928.csv:13, against waardelijst/6.0.0/20240926/IMAER_sectors_20240926.csv:13-14).
Import pipeline. ImaerImporter.importStream detects the IMAER version from the namespace, optionally validates against the XSD, sets the situation year from the import year or <imaer:year>, then reads sources, validates them, recomputes their emissions and validates again (IMAER-java@ecdc81b:G/importer/ImaerImporter.java:135-167,197-213,335-337). Connect imports with USE_IMPORTED_LANDUSES, INCLUDE_SOURCES, USE_VALID_SECTORS, VALIDATE_SOURCES and VALIDATE_AGAINST_SCHEMA, and therefore without INCLUDE_RESULTS, INCLUDE_CALCULATION_POINTS and WARNING_ON_GEOMETRY_LIMITS (AERIUS@fb8812f:CS/service/ScenarioService.java:118-122,139-141; CS/resource/OwN2000CalculateResource.java:66). Results in an uploaded GML are stripped and custom points in it are not read (IMAER-java@ecdc81b:G/importer/ImaerImporter.java:248-257,268-275).
Validation codes. All error and warning codes are in IMAER-java@ecdc81b:IS/exception/ImaerExceptionReason.java. Three mechanisms deserve attention.
- Legacy code remapping and warning 5266. The sector ID passes through
GMLConversionData.getSectorId, which looks up a conversion for the old code in a legacy map; if the conversion is flaggedissueWarning, warning 5266GML_SECTOR_OUT_OF_DATEis added with the old ID, new ID and source label. If the resulting sector is not valid, the sector silently becomes 1800 with warning 5218 (IMAER-java@ecdc81b:G/gml/base/GMLConversionData.java:134-168,201-218;IS/exception/ImaerExceptionReason.java:624-631). The map is read once per IMAER version reader from a supplier (G/gml/base/GMLVersionReaderFactory.java:51), which AERIUS implements againstsystem.gml_conversions (type, old_value, new_value, issue_warning)(AERIUS@fb8812f:IMA/importer/GMLConversionRepository.java:72-75,111-127). For 2026 that table is loaded fromsystem/system.gml_conversions_sector_20260528.txtin dbdata (AERIUS@fb8812f:DB/data/sql/nl-common/system_26/gml_conversions_26.sql:3), which is not public. The public test mock maps 3219 to 3300 with a warning (IMAER-java@ecdc81b:T/java/nl/overheid/aerius/test/TestValidationAndEmissionHelper.java:444-450). [INF] A 3220 to 3300 remap with warning for IMAER 6.0 files is consistent with the code lists and with observed behaviour, but cannot be verified from public sources. Because the namespace did not change, a 6.0 GML that was valid in 2025 can now be silently re-sectored; the XSD cannot detect this. - Other conversions. Pre-3.1 off-road codes become Stage codes with warning 5234 and estimated operating hours (
IMAER-java@ecdc81b:G/gml/base/conversion/MobileSourceOffRoadConversion.java:37-56); removed vehicle codes raise 5267 (G/gml/base/GMLConversionData.java:260-266); a missingmaximumSpeedon NON_URBAN_ROAD_NATIONAL or NON_URBAN_ROAD_GENERAL becomes 60 km/h with warning 5272 (G/gml/base/source/road/GML2Road.java:126-145;G/gml/base/GMLConversionData.java:54). - Off-road method choice. Per sub-source, the validator selects POWER if
power > 0and the database says the category expects power; FUEL if fuel is given and expected; operating hours only (NOT_REQUIRED) if the category expects neither; otherwise error 1041 (IMAER-java@ecdc81b:U/validation/OffRoadValidator.java:76-98,100-105). Power outside the category range gives 1042 (:136-149). Which categories accept power, and their ranges, are database data. In the Connect batch, strict validation accepted power for the four categories we sent with power (40, 100 and 300 kW) and accepted category ZUT with operating hours only, with no error or warning, and no 5266 for sector 3300 [RUN,K01/validate.jsontoK08/validate.json].
How warnings reach the client. Connect copies every import warning into the synchronous CalculateResponse.warnings as {code, message}, and the calculation still starts (AERIUS@fb8812f:CS/resource/CalculateResource.java:80-86; CS/service/ResponseService.java:93-97). Warnings are not stored with the job: JobStatus has no warnings field (AERIUS@fb8812f:API/api-common.yaml:560-600). utility/validate returns them as well and turns them into errors with strict=true (CS/resource/UtilityResource.java:149-164).
5.2 Emission calculation
Recomputation on import. GMLHelper.enforceEmissions "Recalculates the emissions on all given emission sources" (IMAER-java@ecdc81b:G/gml/base/GMLHelper.java:32-41), and Connect calls refreshEmissions/enforceEmissions with the database factors for the calculation year on every source (AERIUS@fb8812f:CS/service/AbstractCalculateService.java:247-249; CS/service/EmissionsSourcesService.java:71-73). Emission values in the GML therefore survive only for generic EmissionSource features ("Nothing to calculate, just return the existing values", IMAER-java@ecdc81b:U/shared/emissions/EmissionsCalculator.java:143-147) and for custom sub-sources (U/shared/emissions/OffRoadMobileEmissionsCalculator.java:72-77). A calculationYear in the Connect options overrides the year of every file (AERIUS@fb8812f:API/api-common-abstract.yaml:31-38; CS/service/AbstractCalculateService.java:242-246).
Mobile machinery (IMAER-java@ecdc81b:U/shared/emissions/OffRoadMobileEmissionsCalculator.java:82-121,123-161):
if power > 0: E_s [kg/y] = EF_kWh,s [g/kWh] * P [kW] * t [h/y] / 1000
else: E_s [kg/y] = max(0, EF_l,s * L_fuel + EF_h,s * t + EF_AdBlue,s * min(L_AdBlue, r_max * L_fuel))
The power branch has no load factor and no idle or AdBlue term. [INF] Any load correction must be folded into the per-category EF_kWh, whose values are not public: the supplier interface returns an empty map by default (U/shared/emissions/OffRoadMobileEmissionFactorSupplier.java:46-51), and the only per-kWh numbers in public code are test mocks (TestValidationAndEmissionHelper.java:167,374-375,760-765).
Road traffic, SRM2 roads (IMAER-java@ecdc81b:U/shared/emissions/SRMRoadEmissionsCalculator.java:40-64,84-134):
EF_v [g/km] = (1 - f_stag,v) * EF_normal(area, roadType, v, vmax, strict) + f_stag,v * EF_stag(...)
E [kg/y] = sum_v ( EF_v * N_v [vehicles/y] ) * tunnelFactor * length [m] / 1e6
with time units converted to per year (HOUR 8760, DAY 365, MONTH 12; IS/domain/v2/base/TimeUnit.java:24-29). The factor key (area, road type, vehicle type, maximum speed, strict enforcement) contains no year; the year is a property of the database snapshot (U/shared/emissions/RoadEmissionFactorSupplier.java:32-51). Stagnation is an input fraction, not a modelled quantity. Emissions scale linearly with the drawn road length.
Measured through Connect [RUN]. The request GMLs of the batch contain activity data only, no emission values; the returned GML carries emissions recomputed by the service, from which the factors below follow (K01/samenvatting.json to K10/samenvatting.json):
| Job | Category or vehicle | Activity | NOx (kg/y) | NH3 (kg/y) | Derived factor |
|---|---|---|---|---|---|
| K01, K02 | SIV75560DSJ (Stage IV, 75 to 560 kW, SCR) | 100 h at 100 and 300 kW | 2.6; 7.8 | 0.21; 0.63 | NOx 0.26, NH3 0.021 g/kWh |
| K05, K06 | SV75560DSJ (Stage V, 75 to 560 kW, SCR) | 100 h at 100 and 300 kW | 2.6; 7.8 | 0.21; 0.63 | identical to Stage IV |
| K03, K04 | SIIIB75560DSN (Stage IIIB, 75 to 560 kW, no SCR) | 100 h at 100 and 300 kW | 11; 33 | 0.007; 0.021 | NOx 1.1, NH3 0.0007 g/kWh |
| K07 | SIV56DSN (Stage IV, below 56 kW, no SCR) | 100 h at 40 kW | 5.84 | 0.0028 | NOx 1.46, NH3 0.0007 g/kWh |
| K08 | ZUT (no power input) | 100 h | 20 | 0.147 | NOx 200, NH3 1.47 g/h |
| K09 | light traffic, 60 km/h | 10,000 vehicles/y on 500 m | 0.7135 | 0.0835 | NOx 0.1427 g per vehicle-km |
| K10 | heavy freight, 60 km/h | 1,000 vehicles/y on 500 m | 1.7643 | 0.0542 | NOx 3.5285 g per vehicle-km |
Tripling the power tripled every emission exactly (K01 against K02, K03 against K04, K05 against K06), as the power formula predicts. Stage V and Stage IV with SCR have identical factors in the 2026 database, and their result summaries are identical down to the 20 highest receptors (K05/samenvatting.json against K01/samenvatting.json). These factors hold for calculation year 2026 and database version 2026.0.0_655bf03b53_calculator_nl_stable; they are measurements of closed data, not a published table.
5.3 Formal options, job splitting and dispatch
Formal options. Connect replaces the user's options with a fixed set: substances NH3, NOx and NO2; result keys NH3 and NOx deposition; method FORMAL_ASSESSMENT (CUSTOM_POINTS on request); stacking on; 25 km maximum distance on; sub-receptors ENABLED_RECEPTORS_ONLY at zoom level 1 without repositioning or split; default meteorology (setMeteo(null)). Two values are taken from the input: the job type (default PROCESS_CONTRIBUTION) and the permit-lower-bound type, for which the bound is read from the database and the options value is set to its label 0.005; while "arithmetic" is the only type, the second has no effect (AERIUS@fb8812f:UT/calculation/OwN2000CalculationOptionsUtil.java:58-92; CS/service/OwN2000CalculateService.java:110-116). Passing a permit lower bound through Connect is disabled ("TODO AER-4028", OwN2000CalculateService.java:114-115). The model versions are stamped from database constants, MODEL_VERSION_OPS = STABLE and MODEL_VERSION_SRM = OWN2000_2026 (AERIUS@fb8812f:DB/data/sql/nl-stable/system/version_constants.sql:10-11; CH/theme/own2000/OwN2000CalculatorFactory.java:143-144,179-180).
Splitting. (1) One JobPacket per situation and year (AERIUS@fb8812f:CH/conversion/JobPackets.java:86-92). (2) Each source is converted for OPS, and road-sector sources are additionally converted for SRM2, the engine per sector coming from the database (CH/theme/own2000/OwN2000SourceConverter.java:65-71; CH/conversion/CalculationEngineProvider.java:30-41). For OPS, lines become point sources every 25 m and polygons a 100 m grid of weighted points (IMA/conversion/OPSSourceConverter.java:113-127,140-163,184-191; DOM/calculation/domain/CalculationConstants.java:27,32). (3) Receptors are handed out per Natura 2000 area, closest area first, within OWN2000_MAX_DISTANCE_M = 25_500 of any source, the comment noting a 500 m margin because "the actual distance is calculated in OPS" (CH/natura2k/Natura2kReceptorStore.java:71-78; CH/theme/own2000/OwN2000DistanceWorkDistributor.java:56-59,89). (4) The chunk size is clamp(ceil(maxEngineUnits / #engineSources), minReceptors, maxReceptors) (CH/base/WorkDistributor.java:78-85), with 100,000 units and 5 to 850,000 receptors for worker exports ("higher will crash ops") and 20,000 units and 5 to 2,000 receptors for UI calculations (DB/data/sql/nl-common/system/product_constants.sql:11-18).
Dispatch. For each situation and chunk the chunker builds one task per engine with the per-sector source groups (CH/base/WorkBySectorHandlerImpl.java:60-73), mapping OPS to WorkerType.OPS and SRM2 to WorkerType.ASRM (CH/theme/own2000/OwN2000CalculationTaskFactory.java:79-80). Road-sector groups get the OPS option -mindist and are listed under ROADS (:102-108,119). The OPS worker runs one OPS process per sector group and substance (OJ/runner/OPSPerformer.java:105-108,145-156).
Queues and priorities. Connect calculations go to connect_export_large when the scenario's complexity (points plus ceil(line length / 25 m) plus ceil(polygon area / 100 m squared)) exceeds calculatorLargeCalculationsSplit (default 100), else to connect_export_small (CS/service/OwN2000CalculateService.java:161-164; CS/config/ConnectProperties.java:42,59; CS/util/ScenarioUtil.java:123-145). In the task manager configuration baked into the image, these queues have priority 0 with maxCapacityUse 0.4 and 0.2, against priority 4 and 1.0 for calculator_ui_small and priority 5 for input_export (AERIUS@fb8812f:docker/common/taskmanager/queue_config/priority-task-scheduler.connect.json; docker/common/taskmanager/Dockerfile.template:2,10). A task is released when the pool has one worker, when more than one worker is free and the queue is below its capacity share, or when the queue has nothing running (taskmanager@c568367:.../PriorityTaskScheduler.java:146-173). [INF] Connect work therefore receives at most 40 % (small) or 20 % (large) of chunker capacity while other queues are busy.
5.4 Receptor selection
Receptor set. Receptors come from permit_required_receptors_view, which is SELECT receptor_id FROM included_receptors (AERIUS@fb8812f:REPO/own2000/ReceptorRepository.java:41-42,57-71; DB/main/sql/common/areas_hexagons_and_receptors/04-views/02-receptors.sql:7-12). included_receptors holds receptors whose zoom-1 hexagon intersects a critical deposition area of type relevant_habitat, united with extra_assessment_receptors ("hexagons with a hersteldoel") (DB/main/sql/common/areas_hexagons_and_receptors/04-views/setup/mapping_areas_receptors.sql:19-35; .../02-tables/02-receptors_hexagons.sql:40-52,124-142). Hexagons of a Natura 2000 area that are not on relevant habitat are not calculated. The public open-data layer built from included_receptors contains 252,818 features [RUN] (DB/main/sql/calculator_common/opendata/general.sql:173-187).
Grid. Receptors are centres of hexagons of 1 ha at zoom level 1 (radius 62.04 m) on an RD New grid with 1,529 hexagons per row (IMAER-java@ecdc81b:IS/domain/geo/ReceptorGridSettings.java:34; database-modules@fba2337:source/modules/src/main/sql/database-modules/aerius_constants/nl-constants.sql:4-24). A receptor ID decodes to coordinates with a closed formula (IMAER-java@ecdc81b:IS/geometry/ReceptorUtil.java:89-98).
25 km. The chunker keeps receptors within 25.5 km; OPS -maxdist then sets every source-receptor pair beyond DISTMAX = 25000 m to zero (OPS@e62fdf7:ops_lt/src/m_commonconst_lt.f90:55; m_ops_check_reken.f90:42-52). Results of exactly zero are not stored in formal assessments (AERIUS@fb8812f:CH/theme/own2000/OwN2000RelevantResultsFilter.java:27-34; CH/TotalResultDBHandler.java:88-90). With the production binary, receptors at 25.0 to 26 km returned exactly zero [RUN].
Sub-receptors. For hexagons near a source the hexagon value is the mean over points inside it (AERIUS@fb8812f:DOM/shared/domain/theme/own2000/OwN2000SubReceptorConstants.java; UT/receptor/SubReceptorLevelMap.java:46-63; UT/receptor/Hexagon2EquilateralTriangles.java:55-63):
| Distance from hexagon centre to nearest source | Level | Points |
|---|---|---|
| d < 150 m | 4 | 384 |
| 150 m <= d < 300 m | 3 | 96 |
| d >= 300 m | none | hexagon centre only |
The points are centres of equilateral triangles tiling the hexagon, rounded to 4 decimals (UT/receptor/HexagonEquilateralTrianglesUtil.java:36,71-80). Only points whose triangle intersects relevant habitat inside the hexagon, and that lie more than 20 m from any source, are kept; if none remains, the hexagon is not calculated (CH/subreceptor/SubReceptorPointSupplier.java:78-131; CH/subreceptor/RelevantGeometryUtil.java:69-101; DOM/shared/domain/theme/own2000/OwN2000SubReceptorConstants.java:49). The centre is then not sent to the model (DOM/calculation/EngineInputData.java:184-188). The mean is arithmetic over sub-points with a non-zero result when the maximum distance is on (UT/receptor/AbstractSubReceptorAggregator.java:175-209; OJ/subreceptor/OPSSubReceptorAggregator.java:33-44); its BigDecimal rounding is Finding F9. Custom points receive no sub-receptors (IMAER-java@ecdc81b:IS/domain/calculation/SubReceptorsMode.java:44-48).
Heights. OPS receives no -varz unless receptors carry a height, so N2000 receptors are calculated at the OPS default of 4 m (AERIUS@fb8812f:OJ/runner/OPSRunnerOptions.java:59-60,84-86; OPS@e62fdf7:USER_MANUAL.md:79). SRM2 uses 1.5 m (AERIUS@fb8812f:SJ/srm/SRMConstants.java:51).
5.5 OPS integration
AERIUS runs RIVM's OPS binary; it does not reimplement OPS. The package is 5.3.1.0-5 and the binary must report OPS-version: L-5.3.1.0 ; Release date: 19 dec 2024 (AERIUS@fb8812f:OJ/version/OPSVersion.java:27-32), matching the pinned source (OPS@e62fdf7:ops_lt/src/m_commonconst_lt.f90:66-67). The 2026 release notes state that OPS and SRM2 are unchanged in Calculator 2026 [R7], and RIVM maps OPS 5.3.1.0 to Calculator 2025 [R8]. OPS 5.3.1.0 embeds its own DEPAC 4.3 (OPS@e62fdf7:ops_lt/ops_lib/src/m_depac.f90:15-22,394); the stand-alone DepAC 5.0.5 repository is not what AERIUS 2026 runs (DepAC@fef2266:VERSION; README.md:82,87,90) [INF].
Command line. -v -i settings.ctr [-perc] [-varz] [-maxdist] [-mindist] [-allow_sigz0_point_source] (AERIUS@fb8812f:OJ/runner/OPSRunnerOptions.java:33-39,75-97): -perc is forced for formal assessments (CH/theme/own2000/OwN2000CalculationTaskFactory.java:126); -maxdist follows useMaxDistance; -mindist is set only for road-sector groups; -allow_sigz0_point_source is always set for 5.3.1.0-5 (OPSRunnerOptions.java:65,93-95; OPS@e62fdf7:ops_lt/src/m_ops_get_arg.f90:223-224).
Control file. The values below are written by OJ/io/ControlFileWriter.java with defaults from OJ/io/OPSSetting.java and version rules from OJ/io/ControlFile5300WriterUtil.java; an expected file is in the test resources (source/commons-aerius/aerius-commons-ops/src/test/resources/nl/overheid/aerius/ops/io/linux_settings_17_latest.ctr).
| Key | Value | Evidence |
|---|---|---|
| YEAR | calculation year; 2030 for every year >= 2024, with suffix prognosis unless chemistry ACTUAL is requested | ControlFile5300WriterUtil.java:47-60; OPSVersion.java:27-28 |
| COMPCODE | 2 (NOx) or 3 (NH3) | ControlFileWriter.java:219-246 |
| CONVRATE | EMEP | OPSSetting.java:43 |
| EMCORFAC | 1.0 | OPSSetting.java:49 |
| ROADS | sector IDs of all road sectors; ROADSopt only when ROADS is non-blank | ControlFile5300WriterUtil.java:43-45; OJ/io/ControlFileOptions.java:52 |
| RECEPTYPE | 2 (point receptors) | OPSSetting.java:59 |
| ROUGHNESS | 0.0 (maps and per-receptor values) | OPSSetting.java:68 |
| Z0FILE / LUFILE | z0_jr_250_lgn2023.ops / lu_250_lgn2023.ops | ControlFile5300WriterUtil.java:33-35; ControlFileWriter.java:58,180-183 |
| METEOTYPE | 0 (interpolate six regions) | OPSSetting.java:71 |
| MTFILE | m014123c.*, the long-term 2014 to 2023 statistics (RIVM documents this set for OPS 5.3.1.0 [R8]; the Handboek 2026 states 2005 to 2014 [R10, Section 4.7], Finding F22) | ControlFile5300WriterUtil.java:30 |
| DEPUNIT | 3 (mol/ha/y) | OPSSetting.java:74 |
Land use. Trajectory roughness and land use come from the LGN2023 maps; at the receptor, -perc columns give average z0, dominant land use and nine land-use percentages per receptor from ops_terrain_properties_20250619 (OJ/io/RcpFileWriter.java:53-56,125-170; OJ/background/TerrainPropertiesReader.java:36-92). A receptor missing from that file gets z0 = 0.1 m and "overige natuur" (OJ/runner/OPSPerformer.java:62-64,126-133).
Emission file. BRN version 4: q [g/s] = E [kg/y] / 31,536 (OJ/io/BrnFileWriter.java:173; IMAER-java@ecdc81b:IS/domain/Substance.java:102), coordinates rounded to 1 m, cat = sector ID, area = 1, zero-emission sources skipped (BrnFileWriter.java:113,157-160,171; OJ/domain/OPSSource.java:108). Machinery takes height, heat content, spread and diurnal profile from the off-road category characteristic set, or the sector default (IMA/conversion/OffRoadMobileSourceExpander.java:68-70,103-117); roads beyond 5 km take the road sector defaults (IMA/importer/GMLHelperImpl.java:203-221). These values are in dbdata.
Reading results. Total deposition is read from fixed columns and negatives are clipped to zero (OJ/io/ResultColumn.java:30-42; OJ/io/ResultDataFileReader.java:147-151). OPS writes four significant digits [RUN]. Returned coordinates are rounded to 1 m, so the chunker re-derives the receptor ID and restores the exact centre (CH/theme/own2000/OwN2000CalculatorFactory.java:129-138).
Consequences [INF]. Every calculation year from 2024 uses the same 2030 prognosis chemistry and background maps in OPS. Because the NH3 compensation point in DEPAC takes the background NH3 and SO2 concentrations as input (OPS@e62fdf7:ops_lt/src/m_ops_resist_rek.f90:589-616), project NH3 deposition depends on that background map. The ROADS option is inert in default runs: OPS applies its SRM2-like road parameterisation only within 5 km (OPS@e62fdf7:USER_MANUAL.md:752-754; ops_lt/src/m_ops_vchem.f90:258), and AERIUS gives road groups to OPS only with -mindist.
5.6 SRM2
SRM2 is a Java implementation selected through AeriusSRMVersion.STABLE("OWN2000_2026", SRM2_2025, "depositionvelocity-vd_zoomlevel-2_all_20250620", "presrm-lu-2.502", "2.504-1", PROGNOSE_METEO_14_23, LANDUSE, false), the last argument disabling OpenCL (AERIUS@fb8812f:SJ/srm/version/AeriusSRMVersion.java:41-42). A development document still says OpenCL is required (AERIUS@fb8812f:doc/development_setup.md:838-839).
Algorithm. Roads are split into segments of at most 2 m with emission per metre E / 31,536 / length (SJ/srm2/algorithm/SRM2Algorithm.java:187-199; SJ/srm2/conversion/AbstractSRMConverter.java:64-83). The concentration is a Gaussian line-segment kernel summed over 36 wind sectors using the receptor's PreSRM wind rose, with sigma_z = a R^b / (1 + 0.5 (1 - exp(-(R/2800)^2))) + sigma_z0, parameters by four roughness classes, and a meteorological correction interpolated between Schiphol and Eindhoven (SRM2Algorithm.java:72-122,204-206; SJ/srm2/algorithm/RoughnessUtil.java:33-71; SJ/srm2/algorithm/MeteoUtil.java:45-144). Initial dispersion depends on freeway or not, elevation, screens and walls (AERIUS@fb8812f:source/commons-aerius/aerius-commons-shared/src/main/java/nl/overheid/aerius/srm2/conversion/Sigma0Calculator.java:27-89). Tunnel factors below 0.001 drop a segment (SJ/srm2/conversion/SRM2SegmentSRMConverter.java:42,102-117). NO2 follows NO2 = NOx f_direct + NO O3 / (NO + K) with K = 100 and per-sector background O3 (SJ/srm2/algorithm/SRM2AlgorithmNOx.java:49,145-195). Deposition is concentration times a receptor deposition velocity from a zoom-2 CSV and a distance factor a ln(clamp(R, 25, 1000)) + b (SJ/srm2/algorithm/DVFactor.java:26-63; SJ/srm/backgrounddata/SRMDepositionVelocityMap.java:30-107).
5 km. MAX_SOURCE_DISTANCE = 5000 (SJ/srm/SRMConstants.java:59); receptors without a source within 5 km get zero (SJ/srm2/calculation/SRM2Calculator.java:55,128). Beyond 5 km the same road is calculated by OPS with -mindist (OPS@e62fdf7:ops_lt/src/m_commonconst_lt.f90:54). [INF] Since OPS uses 25 m points and SRM2 2 m segments, the seam at 5 km is consistent to about 12 m.
5.7 Aggregation
The chunker's result queue waits for all sector-group results of a point and adds them per result key, skipping NaN; OPS plus SRM2 is a plain sum (AERIUS@fb8812f:CH/theme/own2000/OwN2000CalculationResultQueue.java:25-29; CH/base/AggregatedCalculationResultQueue.java:146-201). Total nitrogen NOXNH3_DEPOSITION is NOx deposition plus NH3 deposition, derived on read and stored as its own result set (SH/domain/result/EmissionResults.java:88-103; REPO/calculator/CalculationRepository.java:576-583). Results are stored unrounded (DB/main/sql/calculator_common/calculations/02-tables.sql:147-154). The project contribution per receptor is proposed minus reference minus off-site reduction (DB/main/sql/calculator_common/calculator/10-public/03-functions/02-project_calculation.sql:7-24).
5.8 Threshold and rounding
The permit lower bound is database data: permit_lower_bounds (code, label, value) = ('arithmetic', 0.005, 0.00499999) (AERIUS@fb8812f:DB/data/sql/nl-latest/permit_lower_bounds/load.sql:1-4; identical in nl-stable). The label is the displayed value and the value is "the nearest not included value" for comparison (SH/domain/calculation/PermitLowerBound.java:21-26). A constant PRONOUNCEMENT_THRESHOLD_VALUE = '0.00499999' ("Drempelwaarde. Only show results for receptors that have a deposition above this threshold") also exists (DB/main/sql/common/constants/nl_constants.sql:3-6). The statistics base filters ABS(calculation_results.result) > v_permit_lower_bound on deposition (DB/main/sql/calculator_common/calculator/10-public/03-functions/02-project_calculation.sql:336-341), and the same rule appears for project results, receptor selection, temporary effect, in-combination, archive and the map layers (02-project_calculation.sql:79-95,106-122; 05-temporary_effect.sql:75-91; 06-in_combination.sql:100,241; 07-archive_contribution.sql:96,237; DB/main/sql/calculator_common/calculations/04-views/11-wms.sql:61). The helper ae_abs_threshold applies only to substance 1711 (NOx + NH3) deposition (DB/main/sql/calculator_common/calculations/04-views/02-result_functions.sql:7-16).
Rounding is for display only: two decimals (EMISSION_RESULT_DISPLAY_ROUNDING_LENGTH = 2, DB/data/sql/nl-common/system/product_constants.sql:22-26; WUI/i18n/MessageFormatter.java:220-235), and the PDF overview shows "-" below the label and otherwise derives the precision from the label's scale (WUI/print/own2000/Overview.java:270-295,323-351). [INF] The decision is therefore made on the unrounded NOx + NH3 deposition (strictly greater than 0.00499999), and both increases and decreases below the bound drop out. A value of 0.005 is counted and displayed as 0,01. Label and value differ in a narrow window: a counted value in (0.00499999, 0.005) is shown as 0,00 at two decimals, and the PDF overview shows "-" for an area whose maximum increase is below the label 0.005, which it takes as comparison value and from which it derives two decimals (WUI/print/own2000/Overview.java:275,290,323-326,351; WUI/util/PermitLowerBoundDisplayUtil.java:72-87). Outside that window a displayed 0,00 does not count. Sub-receptor means with a long decimal scale and NOx + NH3 sums can land in the window; in the summed Monte Carlo of F9 this happened in 4 of 1.56 million samples [RUN, sum_near_summary.txt]. The Handreiking on 25 km edge effects says that the Calculator shows only results "> 0,005" [R15, pp. 13 and 15], which at the boundary conflicts with the code. In the Connect batch, the highest hexagon value of the construction case D01 was 0.00228 mol/ha/y, so no hexagon would count; K04 (33 kg NOx/y) and K08 (20 kg NOx/y) were the only single-source jobs with hexagons above the bound [RUN, D01/samenvatting.json, K04/samenvatting.json, K08/samenvatting.json].
5.9 Maximum per Natura 2000 area and hersteldoel hexagons
ae_fill_project_stats_assessment_area computes per assessment area, hexagon type and overlap type the maximum increase max(result) FILTER (result > 0), the maximum decrease, counts and surface, over the statistics base, i.e. over unrounded hexagon values above the bound joined to relevant habitat (AERIUS@fb8812f:DB/.../02-project_calculation.sql:325-398,496-513). The hexagon types are relevant_hexagons (relevant habitat), exceeding_hexagons (receptors in exceeding_receptors, "for which the (nitrogen) deposition exceeds the critical deposition value (KDW - 70 mol)") and extra_assessment_hexagons (hersteldoel) (DB/main/sql/common/areas_hexagons_and_receptors/01-types.sql:82-83; DB/main/sql/common/areas_hexagons_and_receptors/04-views/setup/mapping_areas_receptors.sql:73-92; DB/main/sql/common/areas_hexagons_and_receptors/02-tables/02-receptors_hexagons.sql:96-104). For the OwN2000 project report the PDF uses EXCEEDING_HEXAGONS, and for the hersteldoel appendix EXTRA_ASSESSMENT_HEXAGONS (SH/domain/pdf/ResultsKeyCollector.java:154-162). This matches the Handboek, which says that PDF results are always those on the "OwN2000-registratieset (de overbelaste en bijna overbelaste relevante hexagonen)" [R10, Section 7.6.2]. The 2026 list of such hexagons is exceeding_receptors_20260622.txt in dbdata (DB/data/sql/nl-common/areas_hexagons_and_receptors/data/general_26.sql:32); the open-data layer carries an exceeding flag per hexagon (DB/main/sql/calculator_common/opendata/general.sql:58). [INF] The figures in the PDF therefore cover relevant hexagons whose total deposition is above the critical deposition value minus 70 mol/ha/y; the full set of relevant hexagons is in the GML results. The marker receptor (ae_fill_project_stats_markers) is the first by result descending, then receptor ID (02-project_calculation.sql:551-573). Hexagons with a hersteldoel (restoration goal) are excluded from these statistics and summarised separately (:372,391-396,607,619-655,686,698-712); they appear only in the "Hulpmiddel beoordeling hexagonen met een hersteldoel" appendix (Section 6.3).
5.10 Construction emissions and temporary effect
There is no time-window logic in the chain. OPS receives q = E / 31,536 g/s with EMCORFAC 1.0 (AERIUS@fb8812f:OJ/io/BrnFileWriter.java:173; OJ/io/OPSSetting.java:49), and SRM2 divides by the same constant (Section 5.6). The IMAER activity data are per year (operating hours per year, vehicles per time unit converted to per year; Section 5.2). A construction activity that emits E kg in a few weeks is therefore calculated as E kg per year emitted continuously over the meteorological year with the source's diurnal profile. The Instructie gegevensinvoer puts the annualisation on the user: the deposition is modelled for "de aaneengesloten 12 maanden [...] waarvoor de depositie het hoogst is" [R13, Section 2.3]. IMAER has no field for duration: the off-road source carries fuel, operating hours and AdBlue per year and a power (IMAER@f2a401b:gmlapplicatieschema/6.0.2/IMAER.xsd:3651-3670), and traffic is given per time unit (Section 5.2). [RUN] The two encodings of the batch, D01 (activity spread over two weeks) and D02 (the same activity over twelve months), are therefore the same GML apart from names (D01/request.gml, D02/request.gml), and their results were identical on all 56,071 receptors with a maximum absolute difference of 0 (duurtoets.json). The test confirms that duration cannot enter the calculation and that two separate jobs with the same input give bit-identical results; it does not test a duration parameter, because none exists. For multi-year construction, the job type MAX_TEMPORARY_EFFECT requires TEMPORARY situations (IMAER-java@ecdc81b:IS/domain/calculation/CalculationJobType.java:50-96), and the SQL takes per receptor the maximum over the temporary calculations, with "the hexagon set of the union of all temporary situations" leading (AERIUS@fb8812f:DB/main/sql/calculator_common/calculations/04-views/02-result_functions.sql:375-404). [INF] Duration therefore has no effect only for work that fits within one 12-month window. For longer work the user enters the emission of the 12 consecutive months with the highest deposition [R13, Section 2.3], or one TEMPORARY situation per period with MAX_TEMPORARY_EFFECT; compressing the work raises the emission in the worst window or period and can therefore raise the result.
Answer to RQ2. An IMAER file is parsed and validated; codes may be silently remapped from a database table; standard-source emissions are recomputed from activity data and closed factors for the calculation year; the options are replaced by a fixed formal set; the job is split by situation and by receptor chunks of relevant-habitat hexagons within 25 km; OPS (2014 to 2023 meteorology, 2030 prognosis chemistry for years from 2024, per-receptor land use) calculates all sources and SRM2 adds roads within 5 km; hexagons within 300 m of a source take the mean of 96 or 384 habitat sub-points; engine and sector results are summed; and in SQL a hexagon counts when its unrounded NOx + NH3 deposition exceeds 0.00499999 mol/ha/y, with a two-decimal display.
6. Products and interfaces
6.1 Connect API v8
Endpoints. The public NL profile publishes the common, own2000 and analysis specifications under /api/v8 (AERIUS@fb8812f:source/calculator/aerius-connect-service/src/main/resources/application-nl.properties:1-12; API/api-common.yaml:45-46).
| Endpoint | Behaviour | Evidence |
|---|---|---|
POST /utility/validate (strict) | {successful, errors, warnings}; strict turns warnings into errors | API/api-common.yaml:58-88; CS/resource/UtilityResource.java:113-170 |
POST /utility/validateAndConvert, /utility/convert | Convert to latest IMAER; convert ignores warnings | API/api-common.yaml:113-182 |
GET /jobs, GET /jobs/{key}, POST /jobs/{key}/cancel | List and manage jobs | CS/resource/JobsResource.java:49-74 |
receptorSets | Custom point sets | API/api-common.yaml:333-415 |
GET /info/version | {aeriusVersion, databaseVersion} | API/api-common.yaml:416-429 |
POST /own2000/calculate | Main calculation (Section 5) | API/api-own2000.yaml:44-126 |
POST /analysis/own2000 | Custom-point analysis | API/api-analysis.yaml:45-78 |
Request. Multipart with a JSON options part (outputType GML or PDF; optional calculationYear, sendEmail default true, receptorSetName, appendices, calculationPointsType), a JSON files array and binary fileParts; a part is linked to an entry when its filename ends with the entry's fileName (API/api-common-abstract.yaml:24-68; CS/resource/CalculateResource.java:105-126). Several files of the same situation type without groupId are merged into one situation, and an OFF_SITE_REDUCTION without a factor gets 0.3 (CS/service/ScenarioService.java:177-196,315-319).
Observed behaviour [RUN]. In the batch, 13 submissions within 93 seconds, at most 3 concurrent, met no error (batch-uitkomst.json). Each GML job took 16 to 33 s from creation to end, the PDF job 58 s. A machinery job reported 58,537 points and stored a non-zero result for 55,977 receptors; a job with a road reported 117,224 points, ended with numberOfPointsCalculated 58,612 at COMPLETED, and stored results for 56,027 receptors (56,071 for the combined case) (batch-uitkomst.json; K01/samenvatting.json; K09/samenvatting.json). [INF] Points are counted once per engine (OPS and SRM2) for roads, while the progress counter covers one of them, so the point counters cannot serve as a completion check.
Error behaviour. Validation errors return HTTP 200 with successful:false, not the 440 declared in the specification (CS/resource/CalculateResource.java:79-91; API/api-own2000.yaml:75-76). Option, key, situation-mix and year errors become HTTP 400 with a localized message and a reference, without the numeric code (CS/service/ResponseService.java:71-87). Connect maps internal states to RUNNING (preparing, calculating, post-processing) and INITIALIZED (queuing), and passes other names through, so a just-created job reports CREATED (CS/service/JobsService.java:89-96). For PDF output the job type is forced to PROCESS_CONTRIBUTION: exactly one PROPOSED, optionally one REFERENCE and OFF_SITE_REDUCTION situations, and TEMPORARY is rejected with HTTP 400 before a job is created (CS/service/OwN2000CalculateService.java:101-107; CS/util/CalculationJobTypeValidator.java:57-77).
6.2 The PDF report
The PDF worker produces, per PROPOSED situation, an AERIUS_projectberekening PDF and, for Connect, always an AERIUS_extra_beoordeling appendix, and zips them (AERIUS@fb8812f:WK/pdf/RIVMPdfCustomerProvider.java:40-58; WK/pdf/PdfExportHandler.java:79-125; CS/service/OwN2000CalculateService.java:120-153). Headless Chromium prints the SPA route /print?... which reads the print data of the job from the Connect service (EXP/util/PdfUrlBuilder.java:43-72), using the mechanism of webdocument-exporter@b2cc505:src/main/java/nl/aerius/print/ExportJob.java:139-191. iText then adds page furniture, a watermark if the release type is not PRODUCTION, the attachment and the title, and re-writes the file (EXP/util/PdfContentProcessor.java:114-233).
Embedded ZIP. Only the projectberekening carries an attachment (SH/domain/PdfProductType.java:41-48): a ZIP of GML files for the REFERENCE, OFF_SITE_REDUCTION and the PROPOSED situation, with results but without hexagon polygons, each with a new permit reference; TEMPORARY situations are never attached (WK/pdf/PdfCalculatorWorkerFactory.java:173-178; WK/pdf/PdfSituationFilter.java:36-39,52-53,84-97; IMA/importer/PermitGMLWriter.java:52-55). It is embedded as a file attachment with MIME application/zip. [RUN] The projectberekening of job P01 confirmed this: it carries one embedded file AERIUS_<timestamp>_<token>_onderzoek-connect-batch-1-P01.zip, and the extra-beoordeling PDF has no attachment (P01/samenvatting.json). We extracted the embedded ZIP and compared its receptor results with the result GML of job D01, which had the same input: both hold 56,071 receptors, with identical values on every receptor and none present in only one of the two (P01/embedded-vs-D01.json). The PDF route and the GML route therefore deliver the same calculation result for the same input. Re-import accepts this format and two legacy formats with GML in the Info dictionary (IMA/paa/PAAImport.java:49-98; IMA/paa/LegacyPAAImport.java:42-48).
Data retention. After a PDF job the calculation results are removed from the database unless the worker is configured with keepResultsAfterExport or the situation name contains a fixed string (WK/pdf/PdfCalculatorWorkerFactory.java:73,215-235; Finding F20).
6.3 The hersteldoel appendix
The "Hulpmiddel beoordeling hexagonen met een hersteldoel" is OWN2000_EXTRA_ASSESSMENT_APPENDIX (AERIUS@fb8812f:DB/data/sql/common/i18n/messages/messages_nl.sql:15,17; EXP/PdfGenerator.java:97-104). A hexagon with a hersteldoel is a receptor in extra_assessment_receptors, i.e. covered by a habitat in an earlier dataset such as T0 but no longer, "for instance if the goal is to restore the habitat" (DB/main/sql/common/areas_hexagons_and_receptors/02-tables/02-receptors_hexagons.sql:124-142); the 2026 list is grid.extra_assessment_receptors_20260604.txt (DB/data/sql/nl-common/areas_hexagons_and_receptors/data/general_26.sql:11). The appendix contains, per area, the maximum contribution on such hexagons above the bound (02-project_calculation.sql:619-655). It carries no GML (PdfProductType.java:44-48). The Handreiking states a rule that follows from the sub-receptor logic: when a source lies within 300 m of a hexagon with a hersteldoel, no result is shown for that hexagon [R14, Section 1.5], because sub-receptors are kept only where they overlap relevant habitat, and a hersteldoel hexagon has none (Section 5.4; [R10] Section 3.6).
6.4 Calculator UI versus Connect
| Aspect | UI (/ui/calculation) | Connect (own2000/calculate) | Evidence |
|---|---|---|---|
| Model options | createOwN2000CalculationSetOptions | same | UiCalculateService.java:177; OwN2000CalculateService.java:111 |
| PDF job type | from UI options | forced PROCESS_CONTRIBUTION | UiCalculateService.java:208-219; OwN2000CalculateService.java:101-107 |
| Uploaded results, custom points | read | stripped, not read | ImporterWorker.java:84-95; ScenarioService.java:118-122 |
| Chunking | 2,000 receptors per task | up to 850,000 | CH/theme/own2000/OwN2000CalculatorFactory.java:147-151 |
| Queue priority | 4 (small UI) | 0 | Section 5.3 |
| Export of earlier results | FAST_EXPORT reuse possible | always a new calculation | UiCalculateService.java:243-283 |
[INF] For the same scenario, options and data version both paths should give identical per-receptor results: options come from the same utility, emissions are recomputed by the same code, and the Connect end-to-end tests compare result values exactly against golden fixtures (AERIUS@fb8812f:source/calculator/aerius-connect-service-test/src/test/e2e/cypress/e2e/common/commonPage.ts:1068-1110). Whether chunk size affects per-receptor OPS results was not verified. A comparison of the same inputs in the Calculator UI with the Connect results of K01 and D01 is prepared as a manual step (manifest.json, entry M01) and had not been carried out when this version was written, so this statement remains an inference.
7. Data (RQ3, data side)
7.1 What each step needs
| Step | Data | Status |
|---|---|---|
| Parse and validate | XSD and code lists | Public (IMAER@f2a401b) |
| Code remaps (5266) | system.gml_conversions_sector_20260528 | Closed (dbdata) |
| Emissions | Off-road EF per litre, hour, kWh, AdBlue ratio; power flags and ranges; road EF per speed profile and stagnation; farm, shipping, cold start | Closed (dbdata; e.g. DB/data/sql/nl-common/emission_factors/*_26.sql) |
| Source characteristics | Off-road category sets (mobile_source_off_road_characteristic_sets_20250828, ..._category_characteristics_20250527); sector defaults (sector_default_source_characteristics_20260728) | Closed (DB/data/sql/nl-common/emission_factors/mobile_source_off_road_26.sql:6-7; DB/data/sql/nl-common/sectors/data_26.sql:1-5) |
| Receptor set and habitat geometry | included_receptors, hexagons, relevant habitats, hersteldoel list | Closed as files; public as WFS layers (7.3) |
| Area ordering and statistics | assessment_areas, receptors_to_assessment_areas | Closed (dbdata); [INF] substitutable by PDOK Natura 2000 boundaries |
| OPS | Binary, meteo, chemistry and background maps, LGN2023, building tables, per-receptor terrain | Public (7.3) |
| SRM2 | PreSRM wind roses and backgrounds, z0 maps, deposition velocities | Public (7.3) |
| Threshold, display, limits | permit_lower_bounds, system.constants | Public (SQL in git) |
| Background deposition (for reporting, not for the project contribution) | receptor_background_results_20260727 and related | Closed as files (DB/data/sql/nl-common/receptor_backgrounds/data_26.sql:1-3); public as open-data layer [INF] (7.3) |
7.2 Closed: dbdata
The product database is built by Ruby tooling that downloads data files from https://nexus.aerius.nl/repository with basic authentication (AERIUS@fb8812f:DB/build/config/AeriusSettings.rb:12,21-23; database-build@673f770:bin/SyncDBData.rb:133-141; bin/HTTPSDownloader.rb:93-95). Anonymous requests to .../repository/dbdata/aeriusII/... returned HTTP 401 [RUN]. The older emission-service shows the same pattern for its own (2023) dataset, whose last data change was 2023-12-21 and which has no off-road or per-kWh endpoint (emission-service@d172ecf:source/database/src/data/sql/datasets/load.sql:62-70; source/rest-service/.../repository/). No data file in any repository is accompanied by a checksum or size (Finding F15).
7.3 Public: model packages and open data
The OPS worker downloads model data from ops.model.data.url = https://nexus.aerius.nl/repository/resources with the pattern <model>/<version>.tar.gz, without credentials in code (AERIUS@fb8812f:source/calculator/aerius-worker/src/main/config/worker.template.properties:47,76; UT/util/ModelDataUtil.java:73-92,156-167). Anonymous downloads succeeded [RUN] for:
resources/ops/5.3.1.0-5.tar.gz(338,174,856 bytes): OPS binaries for Linux amd64 and aarch64 and Windows, 140 single-year meteo files and the long-termm014123c.*andm005114c.*sets, actual (2018 to 2024) and prognosis (2024 to 2035, 2040) chemistry maps, LGN2023 z0 and land-use maps,dvepre.ops,pmdpre.ops,gammawater.opsand the building tables;resources/ops/ops_terrain_properties_20250619.tar.gz: per-receptor terrain for 6,778,036 receptors;resources/srm/aerius-presrm-2.504-1.tar.gz,presrm-lu-2.502.tar.gzanddepositionvelocity-vd_zoomlevel-2_all_20250620.tar.gz(AERIUS@fb8812f:SJ/srm/worker/SRMDataUtilPreloader.java:74-89).
The open-data WFS at https://connect.aerius.nl/opendata/wfs exposes base_geometries layers (terrain properties, hexagons with relevance and KDW flags, hexagon-to-habitat links, relevant habitats, hersteldoel hexagons) and a depositions workspace with background deposition (AERIUS@fb8812f:DB/main/sql/calculator_common/opendata/general.sql:1-187; source/geoserver/aerius-geoserver-opendata/); resultType=hits queries succeeded anonymously [RUN]. In the Terraform of the AERIUS repository /opendata/* sits behind Cognito (terraform/application_services/app.hcl:27-28), so the repository does not settle whether production intends it to be anonymous.
Terms of use for OPS, stated neutrally. Three distribution routes exist. (1) The OPS source code is published on GitHub under GPL-3.0 (OPS@e62fdf7:LICENSE); the README says the model is owned by RIVM and that an executable "met omgevingsdata" can be downloaded from the RIVM website (OPS@e62fdf7:README.MD:8-12). (2) RIVM distributes OPS-Pro, with the meteorological statistics and maps, after registration and acceptance of the "Gebruiksvoorwaarden OPS-PRO" [R9]; the link to those terms on the form returned HTTP 404 on 2026-10-22, and the document (version 1.1, 25 July 2012) is published elsewhere on rivm.nl [R16]. The terms state that the right of use is obtained by registration, and that all other ways of acquiring OPS-Pro "zullen worden aangemerkt als een inbreuk op de rechten van het RIVM" (art. 2.1); that copies for others are not allowed (art. 2.4); that use outside the supplied interface needs written permission (art. 2.6); that defects are to be discussed with RIVM before they are made known (art. 2.7); that publications must cite OPS-Pro (art. 4.1); and that software and data are to be destroyed after use (art. 8.2) [R16]. The OPS meteo manual adds that "External users cannot produce meteo statistics themselves" (OPS@e62fdf7:USER_MANUAL.md:821). (3) The AERIUS resources repository serves the package ops/5.3.1.0-5 with RIVM's binaries, meteorology and maps without registration; its 412 entries include no licence, terms-of-use or README file [RUN]. Our OPS runs used route (3), as AERIUS's own workers do; we did not register for OPS-Pro or accept its terms. Whether those terms apply to route (3), and under which terms the package may be used for research, cannot be determined from the published material, and this paper does not decide it. This paper does not redistribute the package. In line with art. 4.1: the OPS results in this paper were obtained in whole or in part with the OPS model of RIVM, version 5.3.1.0 [R17].
Answer to RQ3. Public: all application code (GitLab monorepo and GitHub libraries), the IMAER standard, OPS and DepAC source, the OPS and SRM2 model packages, the per-receptor terrain file, the decision rules and display constants (SQL in git), and the receptor and habitat geometry as WFS layers. Closed: the dbdata files (emission factors, source characteristics, sector defaults, code remaps, area geometries and mappings, background deposition files), production configuration values, Terraform modules and container images. Inferred: the 3220 to 3300 remap, effective production limits and queue configuration, the anonymous status of open data in production, and every statement marked [INF] above.
8. Reproducibility assessment (RQ4)
8.1 What an offline replica can do
A replica can run the public aerius-commons-ops writers, runner and sub-receptor aggregator with the production OPS package, the Java SRM2Calculator with the public SRM data, and the sub-receptor geometry from aerius-commons-util, with receptors and habitat geometry taken from the WFS [RUN for the components; INF for the harness]. The chunker and workers need the product schemas and RabbitMQ, but a thin harness that calls OPSPerformer and SRM2Calculator directly avoids both [INF]. Our OPS run with an AERIUS-format control file completed in about 1.7 s per substance for 200 receptors [RUN].
8.2 What it cannot do without calibration
The closed inputs are the emission factors and the source characteristics (height, heat content, spread, diurnal profile). The batch measured the first directly (Section 5.2). The second enters only through the deposition, which the batch shows for one site (K01/samenvatting.json to D01/samenvatting.json; hexagon values in mol/ha/y):
| Job | NOx; NH3 (kg/y) | Highest hexagon | Of which NOx; NH3 | NOx deposition per kg NOx |
|---|---|---|---|---|
| K01 (Stage IV, SCR, 100 kW) | 2.6; 0.21 | 0.00138 | 0.00066; 0.00072 | 2.5e-4 |
| K02 (same, 300 kW) | 7.8; 0.63 | 0.00414 | 0.00198; 0.00216 | 2.5e-4 |
| K03 (Stage IIIB, 100 kW) | 11; 0.007 | 0.00289 | 0.00287; 0.00002 | 2.6e-4 |
| K04 (Stage IIIB, 300 kW) | 33; 0.021 | 0.00867 | 0.00860; 0.00007 | 2.6e-4 |
| K07 (Stage IV below 56 kW, 40 kW) | 5.84; 0.0028 | 0.00197 | 0.00196; 0.00001 | 3.4e-4 |
| K08 (ZUT, 100 h) | 20; 0.147 | 0.00714 | 0.00658; 0.00056 | 3.3e-4 |
| K09 (light traffic) | 0.714; 0.084 | 0.00068 | 0.00033; 0.00035 | 4.6e-4 |
| K10 (heavy traffic) | 1.764; 0.054 | 0.00119 | 0.00096; 0.00023 | 5.4e-4 |
| D01, D02 (construction case) | 4.27; 0.339 | 0.00228 | 0.00111; 0.00117 | n/a |
Three observations follow [RUN]. First, at fixed category the deposition scales with the emission: tripling the power multiplied the highest hexagon value by 3.000 (K01, K02) and 2.999 (K03, K04), within the four significant digits that OPS prints. Second, the highest hexagon is not the same receptor in every job (receptor 5100297 at 1,156.6 m from the source for K01, K02, K05, K06, K09, K10 and D01; receptor 5098769 at 1,170.4 m for K03, K04, K07 and K08), so the last column compares partly different hexagons. Compared receptor by receptor over all 55,977 receptors, the NOx deposition per kg NOx of K02 to K06 equals that of K01 within 0.1 % (5th to 95th percentile), while K07 (Stage IV below 56 kW) and K08 (ZUT) deposit more per kg at receptors near the source. The table below gives the ratio to K01 (per receptor, the NOx deposition per kg NOx of K07 or K08 divided by that of K01) by distance band from the source point of K01 (computed from K01/receptoren.csv.gz, K07/receptoren.csv.gz and K08/receptoren.csv.gz with the emissions of the samenvatting.json files):
| Distance from the source | Receptors | K07: minimum; median; maximum | K08: minimum; median; maximum |
|---|---|---|---|
| below 2 km | 43 | 1.29; 1.37; 1.51 | 1.26; 1.34; 1.46 |
| 2 to 5 km | 54 | 1.38; 1.66; 1.80 | 1.34; 1.59; 1.72 |
| 5 to 10 km | 391 | 1.03; 1.56; 1.84 | 1.02; 1.51; 1.78 |
| 10 to 10.05 km | 7 | 1.46; 1.48; 1.72 | 1.43; 1.45; 1.67 |
| 10.05 to 11 km | 121 | 1.04; 1.48; 1.66 | 1.04; 1.45; 1.62 |
| 11 to 12 km | 316 | 0.99; 1.19; 1.58 | 0.99; 1.19; 1.54 |
| 12 to 13 km | 878 | 0.99; 1.18; 1.53 | 0.99; 1.18; 1.50 |
| 13 to 14 km | 1,308 | 0.99; 1.01; 1.49 | 0.99; 1.01; 1.47 |
| 14 to 15 km | 2,097 | 0.99; 1.01; 1.43 | 0.99; 1.01; 1.42 |
| beyond 15 km | 50,762 | 0.95; 1.01; 1.41 | 0.95; 1.01; 1.40 |
The ratio does not change at a 10 km boundary: it is highest between 2 and 10 km, its median is still about 1.5 up to 11 km, about 1.2 between 11 and 13 km and about 1.0 from 13 km on, and the 7 receptors between 10 and 10.05 km still have ratios of 1.43 to 1.72. Over the 488 receptors within 10 km the ratio is 1.03 to 1.84 for K07 (median 1.55; 5th to 95th percentile 1.33 to 1.75) and 1.02 to 1.78 for K08 (median 1.51; 1.30 to 1.68); over all 55,977 receptors the median is 1.009 for K07 and 1.011 for K08. The three power categories therefore behave as one set of source characteristics and the two other categories differ. This most plausibly comes from the closed source characteristics (height, heat content, spread or diurnal profile), and it is the size of the error a replica makes if it uses one set of characteristics for all categories. Third, for the Stage IV machine with SCR the 0.21 kg of NH3 contributes more deposition than the 2.6 kg of NOx; per kg, NH3 deposits about 14 times more than NOx at this hexagon. These numbers hold for one site and one release.
8.3 Expected precision of a replica
[INF] With the same OPS binary, data package, control file and inputs, OPS results should be reproducible to the last printed digit, since OPS is deterministic and parallelised by receptor; the bit-identical results of D01 and D02 support this for the production service [RUN, duurtoets.json]. OPS prints four significant digits [RUN], a relative resolution of 1e-4 (5e-7 mol/ha/y near 0.005). The sub-receptor mean adds a rounding error (F9). How closely an offline replica with estimated source characteristics matches Connect per hexagon was not measured in this paper. Paper 2 made a first, partial comparison for 11 of the 13 jobs at one site, with the published source characteristics (erratum pointer after the key results box); its jobs had no hexagon within 300 m of a source, where the sub-receptor mean applies. A replica therefore supports sensitivity studies, and neither paper shows that it supports a verdict near 0.005.
8.4 Calibration through Connect and the planned comparison
- Emission factors (done for seven classes). One job per category with known activity gives the factor, because the power path is linear:
EF_kWh = E * 1000 / (P * t)[RUN, Section 5.2]. Road factors follow per (road type, vehicle type, speed, year) in the same way. - Version. Every batch result records
/info/versionbefore and after the batch (Section 3.5); the release policy says yearly actualisations and hotfixes may change results, functional releases should not (AERIUS@fb8812f:doc/product_versions.md:33-70). - Source characteristics and dispersion (planned here; Paper 2 carried out a first, partial version with deviations). The method was written down before the comparison. (a) Run the replica harness (
OPSPerformerandSRM2Calculatorwith the production packages, sub-receptors as in the F9 harness) on the exact request GMLs of K01 to K10, with the factors of Section 5.2. (b) For each machinery category, fit height, heat content and spread to the Connect per-receptor results of one power level (K01, K03, K07, K08), by least squares on log deposition over all receptors with a non-zero result. (c) Validate without refitting on the held-out jobs (K02, K04, K06) and on the combined case D01. (d) Report per job the relative difference of the highest hexagon, the distribution of per-receptor ratios (median, 5th and 95th percentile), and the agreement of the set of hexagons above 0.00499999, using the per-receptor results kept in the batch data package. (e) Acceptance criteria, set before the comparison: the replica may be used for sensitivity analysis only if the median per-receptor ratio lies within 1 % of 1 and the 95 % range within 5 %; no level of agreement makes it a substitute for a verdict (Section 10).
8.5 Limits of custom points
Custom points receive no sub-receptors (IMAER-java@ecdc81b:IS/domain/calculation/SubReceptorsMode.java:44-48), so within 300 m of a source they give the value at one point at 4 m (OPS) or 1.5 m (SRM2), not the habitat mean of 96 or 384 points. Custom points in a calculation GML are not read; Connect needs a receptorSetName for CUSTOM_POINTS (AERIUS@fb8812f:CS/service/AbstractCalculateService.java:255-269), and custom-point heights are cleared unless receptor heights are enabled (CH/theme/own2000/OwN2000CalculatorFactory.java:181-190). The terrain lookup for custom points was not traced. For calibration near sources, FORMAL results should be used.
Answer to RQ4. Because model code, model data, terrain, receptor set and decision rules are public, an offline replica of the OPS and SRM2 parts is realistic and, given identical inputs, should be close to exact. Emission factors can be measured through Connect, as we did for seven classes. What still prevented a faithful replica, as far as this paper shows, are the source characteristics, which this paper took to be closed (Paper 2 found them published for mobile machinery and roads; erratum pointer after the key results box) and whose effect on the deposition per kg differs between categories: for K07 and K08 the ratio to K01 has a median between 1.34 and 1.66 in each distance band below 10 km and about 1.5 over the 488 receptors within 10 km of our test site (individual receptors 1.02 to 1.84), against about 1.01 beyond 15 km (Section 8.2). Paper 2 made a first, partial per-hexagon comparison of a replica with Connect (11 of the 13 jobs, one site, no hexagon within 300 m of a source); the calibration of Section 8.4 as planned has not been carried out. A replica supports sensitivity analysis, and neither paper shows that it supports a reliable 0,00 versus 0,01 verdict.
9. Findings (RQ5)
Severity scale: High: can change a permit-relevant result or compromise a security property for many users; Medium: affects correctness, integrity or availability under realistic conditions; Low: limited impact or unlikely conditions; Info: documentation, hygiene or interpretation risk.
| ID | Finding | Evidence | Severity |
|---|---|---|---|
| F7 | Undocumented CREATED state. JobStatus.status is a free string in the specification, and the mapping passes CREATED through for a job not yet queued. Clients that enumerate statuses can misclassify it. | API/api-common.yaml:573-575; CS/service/JobsService.java:89-96 | Low |
| F8 | Specification versus behaviour for errors. The spec declares 440 for validation errors; the code returns 200 with successful:false. Option, key, situation-mix and year errors are 400 with localized text and no numeric code. | API/api-own2000.yaml:75-76; CS/resource/CalculateResource.java:79-91; CS/service/ResponseService.java:71-87 | Low |
| F9 | Sub-receptor mean rounded to the decimal scale of the sum. The mean is BigDecimal.valueOf(sum).divide(BigDecimal.valueOf(n), HALF_UP), which keeps the scale of the shortest decimal representation of sum, so the error can reach half a unit of its last decimal; for example 0.48 / 96 = 0.01 (true 0.005) and 0.4 / 96 = 0.0 (true 0.0042) [RUN, jshell]. With the real AERIUS 2026.0.0 classes and the production OPS binary on 4,680 hexagon results (390 hexagons, 6 emission levels, 2 substances), 25.3 % of sums were short (at most 6 decimals); 6.5 % of means were off by more than 1e-4 relative and the largest error was 1.1 % (2.622 / 96 gives 0.027 against 0.0273125); none of these hexagons changed verdict [RUN, main.csv]. Two Monte Carlo runs rescaled the real sub-point fields to means between 0.004 and 0.006 mol/ha/y. (a) On a single substance's mean, the verdict at 0.00499999 differed in 302 of 1.56 million samples (1.9e-4); in 297 of them the mean was counted although the exact mean is below the bound, and the flip rate rises to 0.2 for exact means within 5e-7 below the bound [RUN]. (b) AERIUS applies the bound to the NOx + NH3 total, the sum of two separately rounded means (SH/domain/result/EmissionResults.java:88-103). On the 390 paired NOx and NH3 fields, with a random NH3 share, the verdict on the total differed in 225 of 1.56 million samples (1.4e-4): 119 counted with an exact total below the bound and 106 not counted with an exact total above it, up to 0.006 mol/ha/y; the flip rate was 0.07 for exact totals within 5e-7 below the bound and 1.1e-3 for totals above it up to 0.0051 [RUN, sum_near_summary.txt]. [INF] Each component mean is smaller than the total and is rounded on its own scale, so the error is not anchored at 0.005 and runs both ways; on the decision variable the defect does not err consistently towards counting the hexagon. A sum with one decimal and a mean below 0.05 gives 0.0. The source characteristics are illustrative, and totals over several sector groups, which add further rounded terms, were not modelled. The same division exists in the 2023, 2024 and 2025 releases [RUN]. The correct operation is a division with a fixed MathContext or scale. | AERIUS@fb8812f:UT/receptor/AbstractSubReceptorAggregator.java:203-209 | Low to Medium (correctness near the bound) |
| F10 | Missing foreign key on vehicle category. road_categories_fkey_road_vehicle_categories is declared on road_type_category_id referencing road_type_categories, so the vehicle category is never checked and the road type is checked twice. The defect is in the emission-service template and in the core product schema. | emission-service@d172ecf:source/database/src/main/sql/template/02-emission_factors/02-tables/roads.sql:111; AERIUS@fb8812f:DB/main/sql/calculator_common/emission_factors/02-tables/roads.sql:111 | Low (data integrity) |
| F11 | Dataset nature2022 points to schema nature2021. [INF] A request for nature2022 returns 2021 data. Limited to the frozen emission-service. | emission-service@d172ecf:source/database/src/data/sql/datasets/load.sql:48 | Low |
| F12 | Receptor ID function documented as buggy. ae_determine_receptor_id_from_coordinates(x, y, zoomlevel) carries the comment "Note! This function contains a bug and only works for zoom level 1", and its method is described in a non-public document. We found only unit-test callers (zoom 1). | AERIUS@fb8812f:DB/main/sql/common/geometric_utils/receptors.sql:34-36; database-modules@fba2337:source/modules/src/main/sql/database-modules/build_grid/02-geometric-utils/02-receptors.sql:34,68 | Info (latent) |
| F13 | OPS manual names DIR_BG, code reads DIRBG. A user following the manual gets the default background directory silently. | OPS@e62fdf7:USER_MANUAL.md:733; ops_lt/src/m_ops_read_ctr.f90:272; ops_lt/tst/level_1/resources/tst_m_ops_read_bg/tst_manual_dir_bg.ctr:22 | Low |
| F14 | Stale documentation of defaults. A Javadoc still says 80 km/h is set for NON_URBAN_ROAD_NATIONAL; the code sets 60 since 2026-06-26. A development guide says SRM2 requires OpenCL; the OwN2000 default is CPU. | IMAER-java@ecdc81b:G/gml/base/source/road/GML2Road.java:117,126-145 (commit b21b03ee); AERIUS@fb8812f:doc/development_setup.md:838-839 | Info |
| F15 | Data provenance not verifiable. No checksum or size is recorded for any dbdata file, and the HTTPS downloader writes the response body regardless of status code, so an error page could be loaded as data. | database-build@673f770:bin/HTTPSDownloader.rb:24-40; README.md:75-99 | Low to Medium (reproducibility) |
| F16 | Test configuration versus production uncertainty. Queue priorities, capacity shares, Connect limits and chunker capacity are visible as repository defaults or baked image configs, but production can override them by environment variables and Terraform not in the repository. An earlier reading took queue names from a taskmanager test file that differ from the shipped config. | taskmanager@c568367:source/taskmanager/src/test/resources/queue/priority-task-scheduler.ops.json; AERIUS@fb8812f:docker/common/taskmanager/queue_config/priority-task-scheduler.connect.json; terraform/.../services.yaml.tftpl:151-163; services.tf:2 | Info (threat to validity) |
| F18 | Source-mesh-matrix calculation is a stub. CalculationService.calculate returns random.nextDouble(). Nothing in the OwN2000 path uses it. | source-mesh-matrix@eaef37b:source/api/src/main/java/nl/aerius/smm/api/matrix/service/CalculationService.java:35-46 | Info |
| F19 | Hersteldoel hexagons are outside the main statistics. A reader of the main PDF alone does not see contributions on hexagons with a restoration goal; they are only in the appendix. | DB/.../02-project_calculation.sql:372,391-396,607,619-655,686,698-712 | Info (interpretation) |
| F20 | Name-based retention switch. Results are deleted after a PDF job unless the worker is configured with keepResultsAfterExport or the situation name contains a fixed phrase. Behaviour that depends on user-visible text is fragile and undocumented. | WK/pdf/PdfCalculatorWorkerFactory.java:73,215-235 | Low |
| F22 | User documentation differs from the code on the meteorological period and the boundary. The Handboek 2026 states that OPS uses multi-year meteorology for 2005 to 2014 [R10, Section 4.7]; the code uses m014123c, the 2014 to 2023 statistics that RIVM documents for OPS 5.3.1.0 [R8]. The Handreiking on edge effects says only results "> 0,005" are shown [R15], while the code counts a value of exactly 0.005. Conversely, a counted value in (0.00499999, 0.005) is shown as "-" in the PDF overview, which compares with the label 0.005, and as 0,00 at two decimals (Section 5.8), so in that window the display differs from the decision. | AERIUS@fb8812f:OJ/io/ControlFile5300WriterUtil.java:30; AERIUS@fb8812f:DB/data/sql/nl-latest/permit_lower_bounds/load.sql:1-4; WUI/print/own2000/Overview.java:290,323-326 | Low (documentation of a legal instrument) |
| F23 | Deployed build not traceable to a public commit. Connect reports 2026.0.0_20260929_655bf03b53; the revision 655bf03b53 does not exist in the public repository, whose release commit is dated a week after that build [RUN]. A third party can tie a result to public code by version label only. | AERIUS@fb8812f:source/commons-aerius/aerius-commons-shared/pom.xml:94; Section 3.7 | Info (reproducibility) |
Methodological observations (not defects). Construction emissions are modelled as annual continuous emissions (Section 5.10). Every calculation year from 2024 uses 2030 prognosis chemistry in OPS (Section 5.5). Only relevant-habitat hexagons are calculated (Section 5.4). These are modelling choices whose rationale should be documented by the owners; we found no such documentation in the repositories.
Answer to RQ5. The code shows no defect that we could show to change a reported permit result in our own calculations, but it shows one correctness defect that, in a Monte Carlo on rescaled real OPS fields, changes the verdict on the NOx + NH3 total for exact totals between 0.004 and 0.006 mol/ha/y, in both directions, in 225 of 1,560,000 simulated draws, 1.44 per 10,000 draws (F9), several specification and documentation mismatches (F7, F8, F13, F14, F22), schema and data-provenance weaknesses (F10, F11, F12, F15), and undocumented behaviour that a user of the results should know (F19, F20). The largest threat to independent verification is not a defect but the closed database content, the unpublished production configuration (F16) and the missing link between deployed build and public commit (F23).
9.1 Scope of this edition
The full version of this paper reports 25 findings. Ten of them concern security properties of a public service, or cannot be told apart from or overlap with such findings, and are not part of this edition. The findings reported here are described by impact and code location only. Finding numbers follow the full version.
10. Principles for users of the calculation
The following principles follow from Sections 5 to 9 and apply to anyone who prepares or checks calculations, including services that submit them through AERIUS Connect.1 Product-specific operational advice is outside the scope of this paper.
- Send activity data, not emissions. Standard machinery and road emissions are recomputed from activity data and closed factors for the calculation year; emission values in the GML are kept only for generic sources and custom sub-sources (Section 5.2;
IMAER-java@ecdc81b:U/shared/emissions/EmissionsCalculator.java:143-147). An input file is therefore a statement of activity, and its emissions are whatever the release in force computes. - Apply the verdict rule exactly. A hexagon counts if its unrounded NOx + NH3 deposition exceeds 0.00499999 mol/ha/y, before the two-decimal display (Section 5.8;
AERIUS@fb8812f:DB/main/sql/calculator_common/calculator/10-public/03-functions/02-project_calculation.sql:336-341). A value of exactly 0.005 counts. A displayed 0,00 does not count, except in the window (0.00499999, 0.005), where a counted value is shown as 0,00 and as "-" in the PDF overview; near the bound, read the unrounded value in the GML results. Hersteldoel hexagons are reported separately and need their own assessment (Section 6.3). - Within one 12-month window, only the total construction emission counts. IMAER has no duration field and the calculation reads the emission as kg per year, so shortening work that already fits within 12 months does not change the result, and lowering the total does (Section 5.10). For longer work the user enters the emission of the 12 consecutive months with the highest deposition [R13, Section 2.3], or uses TEMPORARY situations with MAX_TEMPORARY_EFFECT, and compressing the work can raise that emission and so the result.
- Record the AERIUS version with every result. Factors, characteristics and receptor sets are database content that changes with each actualisation;
/info/versiongives the application and database version, and the deployed build is traceable to the public code by label only (Section 3.7;AERIUS@fb8812f:API/api-common.yaml:416-429). - Use an offline replica for analysis only, never for verdicts. A replica can explain and explore a result, but its agreement with the official calculation is not yet measured per hexagon (Section 8.3), and only an AERIUS Calculator calculation meets the legal requirement (Section 2.2).
11. Limitations and threats to validity
- Version drift. Statements hold for the pinned commits. On 2026-10-22 no public commit, tag or branch was newer than
fb8812f, and the live Calculator reported 2026.0.0 (Section 3.7). The deployed Connect build (2026.0.0_20260929_655bf03b53) names a revision that is not in the public repository, so equality of deployed and public code is assumed from the version label, not verified (F23). Closed database content can change without a visible code change. - Code versus deployment. Production can override configuration (F16) and could run model versions other than
STABLE: the version is a database constant (STABLEin the nl-stable build,LATESTin nl-latest), and workers take preload versions and the SRM OpenCL switch from worker properties (AERIUS@fb8812f:DB/data/sql/nl-stable/system/version_constants.sql:10-11;DB/data/sql/nl-latest/system/version_constants.sql:10;UT/worker/EngineWorkerConfigurationBuilder.java:37,138-140;SJ/srm/domain/SRMConfigurationBuilder.java:43,62). Connect reported database versioncalculator_nl_stable(Section 3.5). - Closed data. Emission factors are now measured for seven classes; other categories, other calculation years and the source characteristics remain unmeasured, and statements about code remaps rely on test fixtures.
- One site, one release. The Connect batch used one location and calculation year 2026. Deposition per kg depends on the distance to habitat and on the area, so Section 8.2 illustrates magnitudes and does not generalise them. No receptor of the batch lay within 300 m of a source: the nearest receptor was 1,157 m from the machinery source and 699 m from the road (computed from
*/receptoren.csv.gz), so the live batch did not exercise sub-receptors, F9 or the 20 m exclusion; K04 and K08 gave the values closest to the bound. - Pending comparisons. The UI versus Connect comparison (Section 6.4) and the per-hexagon replica calibration (Section 8.4) have not been made.
- Illustrative characteristics in offline runs. The F9 runs used real AERIUS classes and the production OPS binary but illustrative source characteristics and positions; the Monte Carlo runs assume that the field shape stays fixed when its level changes, and the summed run draws the NH3 share uniformly, which need not match real projects.
- Partial reading. Not every module was read; PDF data aggregation and GML export were read only partially. All code citations pass the mechanical check of Section 3.2, but a citation that points at the right lines can still be read wrongly.
- Single research effort. The reading was done by one research effort motivated by a downstream use (footnote 1), which may bias attention towards the Connect path and small projects.
12. Conclusion
AERIUS 2026.0.0 is more open than its GitHub organisation alone suggests: the Calculator, Connect, the chunker, the OPS wrapper, a Java SRM2 and the SQL that applies the permit lower bound are all public code under AGPL-3.0, and the OPS and SRM2 model data are anonymously downloadable, although the terms of use for the OPS data are unclear (Section 7.3). The rules that decide a reported result can be stated exactly: relevant-habitat hexagons within 25 km; habitat sub-receptor means within 300 m of a source; OPS on 2014 to 2023 meteorology with 2030 prognosis chemistry for every year from 2024; SRM2 for roads within 5 km; annual treatment of construction emissions, so that within one 12-month window only the total counts; and a strict comparison of unrounded NOx + NH3 deposition with 0.00499999 mol/ha/y. Most of these rules are documented in the Handboek, but the exact threshold test and the absence of a time window are not, and the meteorological period it states (2005 to 2014) differs from the data set the code reads (2014 to 2023). Thirteen jobs on the production service confirmed the recomputation of emissions, the linearity of the power formula, the determinism of results and the PDF format (the absence of a duration field follows from the IMAER schema, not from a measurement), and measured emission factors that are otherwise closed, among them the equality of Stage V and Stage IV. What remains closed is the other database content and the production configuration and, for source types other than the mobile machinery and roads that Paper 2 tested, we do not know whether the source characteristics are published (for mobile machinery and roads Paper 2 found them published; erratum pointer after the key results box). Of the 15 findings in this edition, the scale-dependent rounding of sub-receptor means (F9), which on the NOx + NH3 total errs in both directions, deserves the owners' attention first. Publishing checksums for dbdata releases, the commit of each deployed build and a short technical description of the decision rules would make permit calculations substantially easier to verify independently.
Glossary for readers outside the field
- AERIUS Calculator, AERIUS Connect. The Dutch program for calculating nitrogen deposition (Calculator) and its machine interface (Connect, a web API that accepts a calculation as a file and returns results). A Connect job is one submitted calculation.
- IMAER, GML. IMAER is the exchange format for AERIUS input and results. It is an application schema of GML, an XML format for geographic data. One IMAER file describes one situation (sources and, in results, deposition values).
- OPS, SRM2, DEPAC. The dispersion and deposition models behind the results: OPS (the long-term model of the Dutch National Institute for Public Health and the Environment, RIVM, written in Fortran), SRM2 (a road model that AERIUS implements in Java) and DEPAC (the deposition-velocity module inside OPS).
- Natura 2000, relevant habitat, hersteldoel. Natura 2000 areas are protected nature areas. Relevant habitat is the habitat type that AERIUS calculates deposition for. A hersteldoel (restoration goal) hexagon is a hexagon where habitat is to be restored; AERIUS reports it separately.
- Hexagon, receptor, sub-receptor. AERIUS calculates deposition per hexagon of 1 ha (zoom level 1); the hexagon centre is the receptor. Close to a source, the hexagon value is the mean over 96 or 384 sub-receptors inside the hexagon.
- mol/ha/y, NOx, NH3. Deposition is given in mol of nitrogen per hectare per year. NOx (nitrogen oxides) and NH3 (ammonia) are the two emitted substances that are added to a nitrogen total (NOx + NH3).
- The bound 0.00499999. A hexagon counts when its unrounded NOx + NH3 deposition is above 0.00499999 mol/ha/y; the display shows two decimals (Section 5.8 of this paper).
- RD New. The Dutch national coordinate system (Rijksdriehoeksstelsel), in metres.
- Handboek, Instructie gegevensinvoer, Handreiking. Dutch user documents of AERIUS: the user manual, the data-entry instruction and guidance notes.
- Omgevingsregeling, Staatscourant, ECLI. The Dutch ministerial regulation under the Environment and Planning Act; the Dutch Government Gazette; and the European Case Law Identifier of a court ruling.
- [INF], [RUN]. Evidence labels (Section 3.3 of this paper): [INF] marks a statement that rests on our reading rather than on literal code or document text; [RUN] marks an observation from our own builds and runs.
References
Code (see Table 2 and Appendix A for the pins; all at the pinned commits)
- [C1] AERIUS core monorepo, gitlab.com/AERIUS/AERIUS @
fb8812fd74f9b5a548c8772fde3a7e21be238e54, AGPL-3.0. - [C2] aerius/IMAER-java @
ecdc81b0f1e25f484ca5cc28f289c669eea78989; aerius/IMAER @f2a401b3eff26d20103fe1b0fc887d805228b808. - [C3] rivm-syso/OPS @
e62fdf721d67(OPS-LT 5.3.1.0), includingUSER_MANUAL.md; rivm-syso/DepAC @fef2266568b8. - [C4] aerius/emission-service @
d172ecf6b7c1; aerius/database-build @673f770d5e8c; aerius/database-modules @fba2337c90cd. - [C5] aerius/taskmanager @
c5683670a984; aerius/file-server @a0ba2f3bd4b7; aerius/webdocument-exporter @b2cc50546592; aerius/geoserver-core @83bc6b4841cd; aerius/source-mesh-matrix @eaef37bab5c0; aerius/IMAER-QGIS-plugin @2bc81a006091.
Documents
- [R1] Omgevingsregeling, artikelen 1.4, 4.15 and 6.15 and Bijlage II, version in force from 6 October 2026. https://wetten.overheid.nl/BWBR0045528/2026-10-06 (read 2026-10-22).
- [R2] Omgevingsregeling, artikel 7.197h, first paragraph, under e (aanvraag omgevingsvergunning Natura 2000-activiteit). https://wetten.overheid.nl/BWBR0045528/2026-10-06 (read 2026-10-22).
- [R3] Regeling van de Minister van Landbouw, Visserij, Voedselzekerheid en Natuur van 29 september 2026, nr. WJZ/105993054, tot wijziging van de Omgevingsregeling (AERIUS 2026), Staatscourant 2026, 34917, published 30 September 2026; article II (in force 6 October 2026) and explanatory notes Sections 2 and 7. https://zoek.officielebekendmakingen.nl/stcrt-2026-34917.html (read 2026-10-22).
- [R4] AERIUS, nieuwsbericht 6 oktober 2026: AERIUS Calculator en Monitor geactualiseerd. https://www.aeriusproducten.nl/actueel/nieuws/2026/10/6/aerius-calculator-en-monitor-geactualiseerd
- [R5] Afdeling bestuursrechtspraak van de Raad van State, tussenuitspraak 2 november 2022, 202107079/1/R4 (Porthos), ECLI:NL:RVS:2022:3159, r.o. 1, 49 and 50. https://data.rechtspraak.nl/uitspraken/content?id=ECLI:NL:RVS:2022:3159 (read 2026-10-22).
- [R6] Afdeling bestuursrechtspraak van de Raad van State, uitspraak onderscheidenlijk tussenuitspraak 5 april 2023, 201702813/17/R3 (ViA15), ECLI:NL:RVS:2023:1299, r.o. 1.3 and 43. https://data.rechtspraak.nl/uitspraken/content?id=ECLI:NL:RVS:2023:1299 (read 2026-10-22).
- [R7] AERIUS, Release notes AERIUS Calculator 2026, Section 1.1 ("De achterliggende rekenmodellen OPS en SRM2 zijn in AERIUS Calculator 2026 hetzelfde gebleven"). https://www.aeriusproducten.nl/site/binaries/site-content/collections/documents/2026/10/06/release-notes-aerius-calculator-2026/release-notes-aerius-calculator-2026.pdf (downloaded 2026-10-22).
- [R8] RIVM, Versie-overzicht OPS-Pro 5.3.1.0. https://www.rivm.nl/sites/default/files/2025-05/ops_versie_overzicht_5.3.1.0.pdf (downloaded 2026-10-22).
- [R9] RIVM, OPS: beschikbaarstelling en support (download form for OPS-Pro). https://www.rivm.nl/operationele-prioritaire-stoffen-model/beschikbaarstelling-en-support (fetched 2026-10-22).
- [R10] AERIUS, Handboek werken met AERIUS Calculator 2026 (version 2026-09-29), Sections 2.2, 2.3, 3.3, 3.6, 4.3, 4.7, 4.12, 6.9, 7.1.4, 7.5.2, 7.6.2 and annex 36. https://www.aeriusproducten.nl/site/binaries/site-content/collections/documents/2026/10/06/handboek-werken-met-aerius-calculator-2026/handboek-werken-met-calculator-2026.pdf (read 2026-10-22).
- [R11] Van Zanten, M.C. et al. (2010). Description of the DEPAC module. RIVM report 680180001 (as referenced in
DepAC@fef2266:doc/scientific_info.md:1-6). - [R12] RIVM, OPS-LT user manual,
OPS@e62fdf7:USER_MANUAL.md(control file keys at lines 652 to 840; command-line switches at 861 to 879). - [R13] AERIUS, Instructie gegevensinvoer AERIUS Calculator 2026, Sections 2.3 and 11.1.5. https://www.aeriusproducten.nl/site/binaries/site-content/collections/documents/2026/10/06/instructie-gegevensinvoer-aerius-calculator-2026/instructie-gegevensinvoer-aerius-calculator-2026.pdf (read 2026-10-22).
- [R14] AERIUS, Handreiking omgaan met hexagonen met een hersteldoel in AERIUS, version 2, October 2025, Sections 1.4 to 1.6. https://www.aeriusproducten.nl/site/binaries/site-content/collections/documents/2025/10/7/omgaan-met-hexagonen-met-een-hersteldoel-in-aerius/Omgaan+met+hexagonen+met+een+hersteldoel+in+AERIUS+2025.pdf (read 2026-10-22).
- [R15] AERIUS, Handreiking omgaan met randeffecten 25 km in AERIUS Calculator, pp. 13 and 15. https://www.aeriusproducten.nl/site/binaries/site-content/collections/documents/2026/10/06/handreiking-omgaan-met-randeffecten-25-km-in-aerius-calculator/handreiking-omgaan-met-randeffecten-25-km-in-aerius-calculator.pdf (read 2026-10-22).
- [R16] RIVM, Gebruiksvoorwaarden OPS-Pro, version 1.1, 25 July 2012, articles 2.1, 2.4, 2.6, 2.7, 4.1 and 8.2. https://www.rivm.nl/documenten/gebruikersvoorwaarden-ops-pro (file https://www.rivm.nl/sites/default/files/2018-11/Gebruiksvoorwaarden%20OPS-Pro.pdf; read 2026-10-22). The link on the download form [R9], http://rivm.nl/documenten/gebruiksvoorwaarden-ops-pro, returned HTTP 404 on that date.
- [R17] Van Jaarsveld, J.A. (2004). The Operational Priority Substances model. Description and validation of OPS-Pro 4.1. RIVM report 500045001/2004, Bilthoven (citation prescribed by [R16], art. 4.1).
Appendix A. Pinned repositories
All repositories are public; two further repositories of the GitHub organisation were empty and not cloned. Commit ids are the 12-character pins used in the code citations; the full commit ids of the main repositories are in the References.
| Repository | License (upstream) | Pinned commit | Commit date |
|---|---|---|---|
| aerius/vue-gwt | mit | 2edb081b5733 | 2026-08-26 |
| aerius/webdocument-exporter | none | b2cc50546592 | 2026-09-28 |
| aerius/vue-geo-components | agpl-3.0 | 8b48d7dab4a2 | 2026-09-30 |
| aerius/tools | none | 3002bf136b2f | 2026-08-20 |
| aerius/taskmanager | agpl-3.0 | c5683670a984 | 2026-10-06 |
| aerius/sld-generator-maven-plugin | agpl-3.0 | 526086a0fb8b | 2026-08-26 |
| aerius/tomcat-standalone | agpl-3.0 | 8709f18e8727 | 2026-09-09 |
| aerius/tetris | none | 234bab501a13 | 2024-01-19 |
| aerius/search | agpl-3.0 | aa19ff2b8ffa | 2026-10-20 |
| aerius/mongo-initializr | agpl-3.0 | 3c2d66a5f347 | 2026-08-26 |
| aerius/rabbitmq | agpl-3.0 | f8e5558d9a7c | 2026-08-26 |
| aerius/jooq-postgis-spatial | lgpl-3.0 | 12559e4cf25d | 2026-08-26 |
| aerius/source-mesh-matrix | agpl-3.0 | eaef37bab5c0 | 2026-08-24 |
| aerius/IMAER-java | agpl-3.0 | ecdc81b0f1e2 | 2026-09-21 |
| aerius/gwt-client-common-json | agpl-3.0 | 4418f8da4616 | 2023-05-11 |
| aerius/IMAER-QGIS-plugin | gpl-2.0 | 2bc81a006091 | 2026-08-18 |
| aerius/IMAER | none | f2a401b3eff2 | 2026-09-30 |
| aerius/gwt-client-common | agpl-3.0 | b0142dc130a3 | 2026-10-08 |
| aerius/gwt-beans-maven-plugin | none | 947e83340bb0 | 2026-08-26 |
| aerius/github-actions | agpl-3.0 | 202e9f9cbc4b | 2026-10-01 |
| aerius/grip-docs | agpl-3.0 | 83df36ba1f58 | 2026-10-21 |
| aerius/github-actions-test | none | 604c6c0fb3b8 | 2026-08-25 |
| aerius/geoserver-core | none | 83bc6b4841cd | 2026-09-16 |
| aerius/emission-service | agpl-3.0 | d172ecf6b7c1 | 2026-08-26 |
| aerius/easter | none | 3372c98e326e | 2026-08-26 |
| aerius/database-modules | agpl-3.0 | fba2337c90cd | 2026-08-18 |
| aerius/file-server | agpl-3.0 | a0ba2f3bd4b7 | 2026-10-20 |
| aerius/atlas | agpl-3.0 | 9f9e1a1560d3 | 2023-05-11 |
| aerius/database-build | agpl-3.0 | 673f770d5e8c | 2026-10-14 |
| aerius/backlog-tooling | none | 15df1c7eefa1 | 2026-03-11 |
| aerius/auth | agpl-3.0 | 46694608b25d | 2025-10-28 |
| aerius/autovalue-processors | agpl-3.0 | 2d54aaf64238 | 2023-05-11 |
| aerius/cicd-scripts | agpl-3.0 | aed325179b3a | 2026-10-22 |
| aerius/aerius-imaer-templates | none | f1054b4f3dd3 | 2015-11-12 |
| aerius/aerius-root-pom | agpl-3.0 | 6b0f9360cdde | 2026-08-26 |
| aerius/aerius-connect-examples | none | 1e7dfea5570c | 2016-09-29 |
| rivm-syso/OPS | gpl-3.0 | e62fdf721d67 | 2025-03-25 |
| rivm-syso/DepAC | eupl-1.2 | fef2266568b8 | 2026-10-12 |
| gitlab.com/AERIUS/AERIUS (branch calculator2026.0_fixes, shallow) | agpl-3.0 | fb8812fd74f9 | 2026-10-06 |
Appendix B. Data and materials
File names such as K01/samenvatting.json, receptoren.csv.gz or cmp_arm64_K01.json in the text refer to the research data package of the research series: request files, result summaries and per-receptor results of the Connect jobs, and the scripts, harnesses and result files of the offline runs. The names are Dutch where the files were produced by the research client.
- Data availability: The research data package and the harness are not yet published. They are available on request through the contact address below.
- Licence of this text: © 2026 StikstofNu. Quoting with attribution and a link to the paper is welcome; all other rights reserved.
- How to cite and DOI: This paper has no DOI. Cite as: StikstofNu, "AERIUS deconstructed: a code-level technical review of the Dutch nitrogen deposition calculation system (release 2026.0.0)", version 4.2, https://stikstofnu.nl/onderzoek/aerius-deconstructed, 30 October 2026.
- Contact for corrections: info@alexandermooij.com, the contact address that stikstofnu.nl publishes in its terms of use and privacy statement.
Footnotes
-
The motivating case is StikstofNu, a small-project calculation service that uses AERIUS Connect. The research materials are kept separate from that product's code. ↩
Sources
- AERIUS, Handboek werken met AERIUS Calculator 2026 (checked 2026-10-22)
- AERIUS, Instructie gegevensinvoer AERIUS Calculator 2026 (checked 2026-10-22)
- AERIUS, Release notes AERIUS Calculator 2026 (checked 2026-10-22)
- RIVM, Versie-overzicht OPS-Pro 5.3.1.0 (checked 2026-10-22)
- Omgevingsregeling (wetten.overheid.nl) (checked 2026-10-22)