gap: the Kadaster verdict measured the BGT side with the ghost-version filter #250

Closed
opened 2026-09-01 00:38:16 +00:00 by viberfox-agent · 0 comments
Collaborator

What I did

QA of what #187 shipped (4f96f4c, docs-only: docs/notes/kadaster-parcels.md +
the docs/direction.md row). Re-ran every measurement in the note against the
live services on 2026-09-01.

Everything on the Kadaster side reproduced exactly — the five feature counts,
all four paging traps, 34 requests / 30.4 MB, and 313.1 km of KadastraleGrens
against the note's 312.4 km (0.2 %, three days of daily refresh).

The one number that did not hold is the other half of the comparison, the
surveyed-boundary length the verdict is measured against.

What happened

The note (line 30-34) says:

312.4 km of cadastral boundary (KadastraleGrens) against 21.9 km of
surveyed physical boundary (BGT scheiding_lijn, 1,232 current features after
the termination_date filter — consistent with the "~18 km at the worst"
surveyed-street-objects.md records).

termination_date alone is the filter
surveyed-street-objects.md
documents as insufficient, in the section that calls it "the one that silently
ruins an extract — twice":

A feature is current only when both fields are empty; exactly one version per
lokaal_id then survives.

That is the ghost-version bug fixed in 19383b1 — up to 7 coplanar copies of one
footway, "the map's worst z-fight". The kadaster measurement was taken with the
pre-fix filter.

Walking scheiding_lijn over the note's own bbox
(6.555,53.207,6.580,53.222), 2,514 versions returned:

Filter Features Distinct lokaal_id Length
none 2,514 1,714 50.5 km
termination_date empty only — the note's 1,232 1,112 21.9 km
both fields empty — the repo's rule 1,072 1,072 16.2 km

The note's 1,232/21.9 km reproduces exactly, so this is the filter and not drift.
Note the third row is 1,072 features over 1,072 ids — the clean one-version-per-id
partition surveyed-street-objects.md describes; the note's row carries 120
superseded duplicates.

Narrower still: of that 16.2 km, only the forms the client actually draws count.
Current scheiding_lijn by type in this box:

hek             368   5.72 km   -> LineKind::Fence
muur            334   3.47 km   -> LineKind::Wall
kademuur        210   3.04 km   \
walbescherming  134   1.71 km    > dropped on purpose (bank protections)
damwand          16   1.68 km   /
geluidsscherm     7   0.58 km   no LineKind
niet-bgt          3   0.01 km

No heg/haag at all, so LineKind::Hedge draws nothing here. Drawn length is
≈9.2 km, not 21.9 km. (The exact type -> LineKind mapping lives in
cartopy's bgt.ts, which this container cannot read, so 9.2 km is bounded from
the BGT types rather than pinned from the mapping.)

Why it matters, and why it is not urgent

The verdict is unaffected and in fact strengthened. The gap goes from the
note's 93 % to ~95 % against current features, or ~97 % against drawn ones. Every
error runs in the conservative direction, so #187's "not taken" stands on its own.

What is worth fixing is that the note teaches the wrong method. Its "How every
number above was measured" section is written to be re-run, and a reader who
re-runs it inherits the exact filter the codebase spent a fix removing. Worse, the
note reconciles its inflated figure — it calls 21.9 km "consistent with the ~18
km at the worst" — so the one cross-check that would have caught the inflation was
used to wave it through. With the correct filter, 16.2 km sits under that ~18 km
rather than 22 % over it.

What a reader would expect instead

A note citing a BGT feature count to state the filter the repo actually uses, and
for two notes written the same day (both dated 2026-08-29) not to disagree about
what "current" means.

Where the seam is

  • docs/notes/kadaster-parcels.md lines 30-34 (the figure and its reconciliation)
    and the measurement block at the bottom.
  • docs/direction.md, the Kadaster row: "21.9 km of surveyed fence, wall and
    hedge" and the derived "93 %".
  • The rule they contradict: docs/notes/surveyed-street-objects.md, "The API
    behaves nothing like the BAG WFS next door".
  • No code is wrong. crates/cartopolis/src/systems/map/surveyed.rs and cartopy's
    items() already apply the both-fields rule; only the notes are stale.

Suggested fix: 21.9 -> 16.2 km in both files, drop the false "consistent with ~18
km" reconciliation, and say termination_date and eind_registratie in the
method block. Optionally add the ≈9.2 km drawn-forms figure, which is the honest
like-for-like against a cadastral line.

How to reproduce

U="https://api.pdok.nl/lv/bgt/ogc/v1/collections/scheiding_lijn/items"
curl -sL "$U?bbox=6.555,53.207,6.580,53.222&limit=1000"   # + follow rel=next
# then count features with termination_date empty, vs both it and
# eind_registratie empty -> 1232 vs 1072, 21.9 km vs 16.2 km

Filed by the QA pass on #249.

## What I did QA of what #187 shipped (`4f96f4c`, docs-only: `docs/notes/kadaster-parcels.md` + the `docs/direction.md` row). Re-ran every measurement in the note against the live services on 2026-09-01. **Everything on the Kadaster side reproduced exactly** — the five feature counts, all four paging traps, 34 requests / 30.4 MB, and 313.1 km of `KadastraleGrens` against the note's 312.4 km (0.2 %, three days of daily refresh). The one number that did not hold is the *other* half of the comparison, the surveyed-boundary length the verdict is measured against. ## What happened The note (line 30-34) says: > **312.4 km** of cadastral boundary (`KadastraleGrens`) against **21.9 km** of > surveyed physical boundary (BGT `scheiding_lijn`, 1,232 current features after > the `termination_date` filter — consistent with the "~18 km at the worst" > surveyed-street-objects.md records). `termination_date` alone is the filter [surveyed-street-objects.md](../src/branch/main/docs/notes/surveyed-street-objects.md) documents as **insufficient**, in the section that calls it "the one that silently ruins an extract — twice": > A feature is current only when **both** fields are empty; exactly one version per > `lokaal_id` then survives. That is the ghost-version bug fixed in `19383b1` — up to 7 coplanar copies of one footway, "the map's worst z-fight". The kadaster measurement was taken with the pre-fix filter. Walking `scheiding_lijn` over the note's own bbox (`6.555,53.207,6.580,53.222`), 2,514 versions returned: | Filter | Features | Distinct `lokaal_id` | Length | |---|---|---|---| | none | 2,514 | 1,714 | 50.5 km | | `termination_date` empty only — **the note's** | 1,232 | 1,112 | **21.9 km** | | both fields empty — **the repo's rule** | 1,072 | 1,072 | **16.2 km** | The note's 1,232/21.9 km reproduces exactly, so this is the filter and not drift. Note the third row is 1,072 features over 1,072 ids — the clean one-version-per-id partition surveyed-street-objects.md describes; the note's row carries 120 superseded duplicates. Narrower still: of that 16.2 km, only the forms the client actually draws count. Current `scheiding_lijn` by type in this box: ``` hek 368 5.72 km -> LineKind::Fence muur 334 3.47 km -> LineKind::Wall kademuur 210 3.04 km \ walbescherming 134 1.71 km > dropped on purpose (bank protections) damwand 16 1.68 km / geluidsscherm 7 0.58 km no LineKind niet-bgt 3 0.01 km ``` No `heg`/`haag` at all, so `LineKind::Hedge` draws nothing here. Drawn length is **≈9.2 km**, not 21.9 km. (The exact `type` -> `LineKind` mapping lives in cartopy's `bgt.ts`, which this container cannot read, so 9.2 km is bounded from the BGT types rather than pinned from the mapping.) ## Why it matters, and why it is not urgent **The verdict is unaffected and in fact strengthened.** The gap goes from the note's 93 % to ~95 % against current features, or ~97 % against drawn ones. Every error runs in the conservative direction, so #187's "not taken" stands on its own. What is worth fixing is that the note **teaches the wrong method**. Its "How every number above was measured" section is written to be re-run, and a reader who re-runs it inherits the exact filter the codebase spent a fix removing. Worse, the note *reconciles* its inflated figure — it calls 21.9 km "consistent with the ~18 km at the worst" — so the one cross-check that would have caught the inflation was used to wave it through. With the correct filter, 16.2 km sits under that ~18 km rather than 22 % over it. ## What a reader would expect instead A note citing a BGT feature count to state the filter the repo actually uses, and for two notes written the same day (both dated 2026-08-29) not to disagree about what "current" means. ## Where the seam is - `docs/notes/kadaster-parcels.md` lines 30-34 (the figure and its reconciliation) and the measurement block at the bottom. - `docs/direction.md`, the Kadaster row: "**21.9 km** of surveyed fence, wall and hedge" and the derived "93 %". - The rule they contradict: `docs/notes/surveyed-street-objects.md`, "The API behaves nothing like the BAG WFS next door". - No code is wrong. `crates/cartopolis/src/systems/map/surveyed.rs` and cartopy's `items()` already apply the both-fields rule; only the notes are stale. Suggested fix: 21.9 -> 16.2 km in both files, drop the false "consistent with ~18 km" reconciliation, and say `termination_date` **and** `eind_registratie` in the method block. Optionally add the ≈9.2 km drawn-forms figure, which is the honest like-for-like against a cadastral line. ## How to reproduce ```bash U="https://api.pdok.nl/lv/bgt/ogc/v1/collections/scheiding_lijn/items" curl -sL "$U?bbox=6.555,53.207,6.580,53.222&limit=1000" # + follow rel=next # then count features with termination_date empty, vs both it and # eind_registratie empty -> 1232 vs 1072, 21.9 km vs 16.2 km ``` <sub>Filed by the QA pass on #249.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jeroen/cartopolis#250
No description provided.