#122: Sweden's remaining R-checks as acceptance tests (R38, R40/R125, R90/R91, R95/R96, R119) #134

Merged
art merged 1 commit from issue-122-se-acceptance-tests into main 2026-08-16 05:18:04 +00:00
Owner

Closes #122


#122 — Sweden: acceptance tests for the remaining R-checks

Part of the #115 plan: the manual R-checks still listed as candidates for
Sweden in docs/country-requirements.md are now acceptance tests in
conf/semap.yaml, run on every import of the layer and in -acceptance-only
review runs. Config + doc only; no import was needed — every test reads data
already live in the DB.

What was added (14 tests, all 10 s bucket; measured 1 ms–0.5 s live — R35 stays the only 100 s test)

R where check
R38 cover the boundary classes' (water) total area does not grow by more than the documented bound on any coarse zoom step z9→z3 — 15 000 km² per step, 40 000 km² for the z4→z3 step
R40/R125 cover wetland area at z3 stays above 30 000 km² (3857), below both recorded builds
R95/R96 config-level at the coarsest zoom, roads/waterways/railways are merged chains: avg feature length > 1 km
R90/R91 places no two same-name places are closer than the 15 000-unit cluster eps
R119 country_mask, water_bodies, waterways, water_points, roads, ferries, railways, transit, stations, places no zoom table in the layer's declared range is empty

Design decisions worth the reviewer's eye

  • R38's bound is documented from three builds. The output tables witness the
    aggregate, not the per-polygon sub-pixel rule, so the test pins the aggregate to
    the measured record: the #47 post-fix build (docs/performance.md) kept every
    z9→z4 step within ±9k km² and measured z4→z3 at +33 285 km²; the pre-fix build
    grew +21 204 km² on z5→z4 (the coastline-eater — fails the 15 000 km² finer
    bound); the current build measures z6→z5 +5 977 km² and z4→z3 +27 071 km². The
    z3 step gets its own 40 000 km² bound because it is the standing Mälaren-scale
    collapse (sub-pixel wetland pieces absorbing into water at z3's pixel size,
    issue #117) — accepted, not asserted away.
  • R125/R40 is a floor, not the ideal. The z4→z3 −55.2% wetland collapse is
    #117's recorded, accepted data-resolution consequence. The test asserts z3
    wetland ≥ 30 000 km², below both recorded builds (48 853 km² post-#116
    hardening, 34 028 km² before it), so a cascade that loses the class or
    collapses it to a sliver fails.
  • R95/R96 uses the length half of the signature, not NPoints. R95's signature
    is "2.0 points and ~200 m per feature". The NPoints half alone cannot
    discriminate for waterways: at z3 the importance-5 filter leaves chains that
    simplification legitimately straightens to 2.01 pts. Length discriminates:
    roads z5 28.4 km avg, waterways z3 2.3 km avg, railways z3 168.6 km avg —
    all ≫ the pre-split ~200 m, floor at 1 km.
  • R90/R91 is one test. Same-name rows in the output come from different
    DBSCAN clusters (the places query takes rn = 1 per name-per-cluster), so they
    must be > 15 000 units apart (the se_text_t10_cl eps, ~7.7 km ground at
    59°N). Live: 203 830 rows, 96 499 distinct names, 2 918 492 same-name pairs,
    closest at 15 001.6. That witnesses spatial-not-nationwide dedupe (R90) and
    is R91's whole "no name repeats within one view" for a point layer (the
    closest same-name pair is far wider than a maxz tile, so no tile ever holds
    two same-name labels).
  • R92 is not applicable to Sweden. The check ("GROUP BY feature_id HAVING
    count(*) > 1 after the join") presumes names attach by spatial join against a
    standalone register. Sweden's names ride on the label points themselves (Topo
    10 text → se_text_t10_cl); there is no unnamed geometry plus a separate
    register, and the output has no feature_id column. Said in the R92 Check
    line and here.

A finding: map_water_bodies_3_se is empty (recorded, not fixed)

The R119 pass found the one empty partition in Sweden's range:
map_water_bodies_3_se (0 rows). It is the R31 deliberate-empty case, and the
water_bodies test records it as such (asserting z4–z14, naming z3 in the
comment): the 4-px² area floor at z3's pixel scale drops every lake piece the
pre-tessellated mark coverage delivers (largest source pieces cap at
~100 km²). It is a shared-contract property, not Sweden's — measured across
countries: fi 0, is 0, pl 1 rows at z3. cover paints Sweden's lakes at z3 in
the same blue (water is deliberately drawn twice), so nothing is lost on the
map. If water_bodies should actually draw at z3 (a stop_z: 4 on the
contract, or merging lake pieces), that is a follow-up — this issue is about
recording the state, and #125 (global checks) will hit the same empty z3 for
every country.

Verification (live DB, no writes)

$ make build && make test
ok  github.com/art4711/ismap/importer
ok  github.com/art4711/ismap/mapsrv

$ ./importer/importer -acceptance-only -acceptance all conf/semap.yaml
acceptance: 27 test(s): 27 passed, 0 failed, 0 skipped, 0 not run

$ make acceptance
conf/ismap.yaml   26 passed / 0 failed / 0 skipped / 0 not run
conf/semap.yaml   26 passed / 0 failed / 0 skipped / 1 not run (R35, 100 s, needs -acceptance all)
conf/plmap.yaml   13 passed
conf/fimap.yaml   12 passed
conf/world.yaml    9 passed

New-test highlights from the -acceptance all run:

R38:    PASS step_growth_km2=8: -4108, 7: -2096, 6: 1082, 5: 5977, 4: -11137, 3: 27071
R125/R40: PASS wetland_km2_3857=48853
R95/R96: PASS roads: 2.83 pts, 28.4 km avg (n=470); waterways: 2.01 pts, 2.3 km avg (n=4655); railways: 3.82 pts, 168.6 km avg (n=115)
R90/R91: PASS close_pairs=0
R119:   PASS (11 tables water_bodies z4–z14, 12 waterways, 7 water_points, 10 roads,
              12 ferries, 10 railways, 6 transit, 6 stations, 11 places, 1 country_mask)

Nothing left undone that I know of: R92's register-join shape is genuinely not
applicable to Sweden (above), and the water_bodies z3 empty is recorded as the
R31 deliberate-empty case rather than fixed (also above).

Closes #122 --- #122 — Sweden: acceptance tests for the remaining R-checks Part of the #115 plan: the manual R-checks still listed as candidates for Sweden in `docs/country-requirements.md` are now acceptance tests in `conf/semap.yaml`, run on every import of the layer and in `-acceptance-only` review runs. Config + doc only; no import was needed — every test reads data already live in the DB. ## What was added (14 tests, all 10 s bucket; measured 1 ms–0.5 s live — R35 stays the only 100 s test) | R | where | check | |---|---|---| | R38 | cover | the boundary classes' (water) total area does not grow by more than the documented bound on any coarse zoom step z9→z3 — 15 000 km² per step, 40 000 km² for the z4→z3 step | | R40/R125 | cover | wetland area at z3 stays above 30 000 km² (3857), below both recorded builds | | R95/R96 | config-level | at the coarsest zoom, roads/waterways/railways are merged chains: avg feature length > 1 km | | R90/R91 | places | no two same-name places are closer than the 15 000-unit cluster eps | | R119 | country_mask, water_bodies, waterways, water_points, roads, ferries, railways, transit, stations, places | no zoom table in the layer's declared range is empty | ## Design decisions worth the reviewer's eye - **R38's bound is documented from three builds.** The output tables witness the aggregate, not the per-polygon sub-pixel rule, so the test pins the aggregate to the measured record: the #47 post-fix build (docs/performance.md) kept every z9→z4 step within ±9k km² and measured z4→z3 at +33 285 km²; the pre-fix build grew +21 204 km² on z5→z4 (the coastline-eater — fails the 15 000 km² finer bound); the current build measures z6→z5 +5 977 km² and z4→z3 +27 071 km². The z3 step gets its own 40 000 km² bound because it is the standing Mälaren-scale collapse (sub-pixel wetland pieces absorbing into water at z3's pixel size, issue #117) — accepted, not asserted away. - **R125/R40 is a floor, not the ideal.** The z4→z3 −55.2% wetland collapse is #117's recorded, accepted data-resolution consequence. The test asserts z3 wetland ≥ 30 000 km², below both recorded builds (48 853 km² post-#116 hardening, 34 028 km² before it), so a cascade that loses the class or collapses it to a sliver fails. - **R95/R96 uses the length half of the signature, not NPoints.** R95's signature is "2.0 points **and ~200 m per feature**". The NPoints half alone cannot discriminate for waterways: at z3 the importance-5 filter leaves chains that simplification legitimately straightens to 2.01 pts. Length discriminates: roads z5 28.4 km avg, waterways z3 2.3 km avg, railways z3 168.6 km avg — all ≫ the pre-split ~200 m, floor at 1 km. - **R90/R91 is one test.** Same-name rows in the output come from different DBSCAN clusters (the places query takes rn = 1 per name-per-cluster), so they must be > 15 000 units apart (the `se_text_t10_cl` eps, ~7.7 km ground at 59°N). Live: 203 830 rows, 96 499 distinct names, 2 918 492 same-name pairs, closest at 15 001.6. That witnesses spatial-not-nationwide dedupe (R90) and is R91's whole "no name repeats within one view" for a point layer (the closest same-name pair is far wider than a maxz tile, so no tile ever holds two same-name labels). - **R92 is not applicable to Sweden.** The check ("GROUP BY feature_id HAVING count(*) > 1 after the join") presumes names attach by spatial join against a standalone register. Sweden's names ride on the label points themselves (Topo 10 `text` → `se_text_t10_cl`); there is no unnamed geometry plus a separate register, and the output has no `feature_id` column. Said in the R92 Check line and here. ## A finding: map_water_bodies_3_se is empty (recorded, not fixed) The R119 pass found the one empty partition in Sweden's range: `map_water_bodies_3_se` (0 rows). It is the R31 deliberate-empty case, and the water_bodies test records it as such (asserting z4–z14, naming z3 in the comment): the 4-px² area floor at z3's pixel scale drops every lake piece the pre-tessellated `mark` coverage delivers (largest source pieces cap at ~100 km²). It is a **shared-contract property, not Sweden's** — measured across countries: fi 0, is 0, pl 1 rows at z3. `cover` paints Sweden's lakes at z3 in the same blue (water is deliberately drawn twice), so nothing is lost on the map. If water_bodies should actually draw at z3 (a `stop_z: 4` on the contract, or merging lake pieces), that is a follow-up — this issue is about recording the state, and #125 (global checks) will hit the same empty z3 for every country. ## Verification (live DB, no writes) ``` $ make build && make test ok github.com/art4711/ismap/importer ok github.com/art4711/ismap/mapsrv $ ./importer/importer -acceptance-only -acceptance all conf/semap.yaml acceptance: 27 test(s): 27 passed, 0 failed, 0 skipped, 0 not run $ make acceptance conf/ismap.yaml 26 passed / 0 failed / 0 skipped / 0 not run conf/semap.yaml 26 passed / 0 failed / 0 skipped / 1 not run (R35, 100 s, needs -acceptance all) conf/plmap.yaml 13 passed conf/fimap.yaml 12 passed conf/world.yaml 9 passed ``` New-test highlights from the -acceptance all run: ``` R38: PASS step_growth_km2=8: -4108, 7: -2096, 6: 1082, 5: 5977, 4: -11137, 3: 27071 R125/R40: PASS wetland_km2_3857=48853 R95/R96: PASS roads: 2.83 pts, 28.4 km avg (n=470); waterways: 2.01 pts, 2.3 km avg (n=4655); railways: 3.82 pts, 168.6 km avg (n=115) R90/R91: PASS close_pairs=0 R119: PASS (11 tables water_bodies z4–z14, 12 waterways, 7 water_points, 10 roads, 12 ferries, 10 railways, 6 transit, 6 stations, 11 places, 1 country_mask) ``` Nothing left undone that I know of: R92's register-join shape is genuinely not applicable to Sweden (above), and the water_bodies z3 empty is recorded as the R31 deliberate-empty case rather than fixed (also above).
Part of the #115 plan: the manual R-checks still listed as candidates for
Sweden in docs/country-requirements.md are now acceptance tests in
conf/semap.yaml, run on every import and in -acceptance-only review runs.
Config + doc only; no import was needed — every test reads data already live.

Fourteen new tests, all 10 s bucket (measured 1 ms–0.5 s live; the
pre-existing R35 stays the only 100 s test):

- R38 (cover): the boundary classes' (water) total area must not grow by
  more than the documented bound on any coarse zoom step z9→z3 — 15 000 km²
  per step, 40 000 km² for the z4→z3 step. The bounds are documented from
  the three builds measured: the #47 post-fix record (worst finer step
  +6 693 km², z4→z3 +33 285 km²), the pre-fix build the issue was filed on
  (z5→z4 +21 204 km² — the coastline-eater — fails the finer bound), and
  the current build (z6→z5 +5 977 km², z4→z3 +27 071 km²).
- R40/R125 (cover): wetland area at z3 must stay above 30 000 km² (3857),
  below both recorded builds (48 853 km² post-#116 hardening, 34 028 km²
  before it), so a cascade that loses the class or collapses it to a sliver
  fails. The −55.2% z4→z3 collapse itself is #117's recorded, accepted
  measurement, not asserted away.
- R95/R96 (config-level): at the coarsest zoom the road, waterway and rail
  networks are merged chains — avg feature length > 1 km (roads z5 28.4 km,
  waterways z3 2.3 km, railways z3 168.6 km) vs R95's pre-split signature
  of ~200 m. The length half, not the NPoints half: z3 waterways measure
  2.01 pts, indistinguishable from the bug by points alone, because the
  importance-5 filter leaves chains that simplification legitimately
  straightens (the comment says so).
- R90/R91 (places): no two same-name places are closer than the 15 000-unit
  cluster eps (se_text_t10_cl's ST_ClusterDBSCAN eps, ~7.7 km ground at
  59°N) — 2 918 492 same-name pairs, closest at 15 001.6. Witnesses
  spatial-not-nationwide dedupe (R90) and one-anchor-per-place (R91: the
  same-name separation is far wider than a maxz tile, so no tile ever holds
  two same-name labels). R92 does not apply to Sweden — names ride on the
  label points themselves, there is no unnamed geometry plus a separate
  register, and the output has no feature_id to group by.
- R119 (country_mask + the nine other shared partitions): no zoom table in
  a layer's declared range is empty — count(*) = N AND
  bool_and(pg_relation_size > 0), so a *missing* zoom table fails rather
  than passing vacuously; each layer's comment records that none of its
  zoom_filters empties a zoom. One deliberate empty found and recorded:
  map_water_bodies_3_se is empty — the 4-px² floor at z3's pixel scale
  drops every lake piece of the pre-tessellated mark coverage, and it is
  empty for every country (fi 0, is 0, pl 1 rows), i.e. a shared-contract
  property, not Sweden's; cover paints the lakes at z3 (water drawn twice).
  The test asserts z4–z14 and names z3 in the comment. If water_bodies
  should draw at z3 (stop_z: 4 on the contract, or merging lake pieces),
  that is a follow-up.

docs/country-requirements.md: the Check lines for R38, R40, R90, R91, R92,
R95, R96, R119 and R125 now name their acceptance tests (or, for R92, say
why Sweden's shape is not applicable).

Verification (live DB, no writes):
  make build && make test                       # both Go packages green
  importer/importer -acceptance-only -acceptance all conf/semap.yaml
    acceptance: 27 test(s): 27 passed, 0 failed, 0 skipped, 0 not run
  make acceptance
    ismap 26 / semap 26 (+ R35 not run, 100 s) / plmap 13 / fimap 12 / world 9
art changed title from Closes #122 to #122: Sweden's remaining R-checks as acceptance tests (R38, R40/R125, R90/R91, R95/R96, R119) 2026-08-16 00:22:38 +00:00
Collaborator

Review

Verified: go test ./... green, and the semap suite runs 26 passed / 0 failed against the live database (R35 correctly not run outside -acceptance all). Every R119 table list matches its layer's declared zoom range, and the single omission is real — map_water_bodies_3_se is 0 rows, and the comment's cross-country note checks out too (fi 0, is 0, pl 1).

The R38 measurement reproduces: step_growth_km2 = 8: -4108, 7: -2096, 6: 1082, 5: 5977, 4: -11137, 3: 27071, so the 15 000 / 40 000 split is the right shape for the data, with the z3 exception carrying the Mälaren-scale collapse the comment describes.

min_distance_3857 prints as a Go struct, not a distance

The R90/R91 test's diagnostic column comes back unreadable:

R90/R91: no two same-name places are closer than the 15 km cluster eps
  — PASS (439ms, 1 row(s), close_pairs=0, min_distance_3857=&{0 0 2})

round(...)::numeric arrives as a *pgtype.Numeric, and acceptance.go:390 formats values with %v, which prints the struct. The sibling column works because round(...)::bigint maps to an int64 — same reason wetland_km2_3857=48853 renders fine in the R40/R125 test right above.

It only matters on failure, but that is exactly when it matters: the whole reason to carry min_distance_3857 is that a red test should tell you how close the offending pair was, and today it would say &{0 0 2}. Casting to ::float8 (or ::text) fixes it without touching the formatter.

Note on R38's direction

Like R40/R125's floor, R38 is one-sided — it bounds growth from above, so a cascade that lost water area entirely would pass it. R119 catches a collapse to zero and R40/R125 puts a floor under wetland specifically, so the gap is narrow, and I don't think it needs fixing here. Worth knowing it exists if the boundary classes ever get a regression that shrinks rather than grows them.

## Review Verified: `go test ./...` green, and the semap suite runs 26 passed / 0 failed against the live database (R35 correctly not run outside `-acceptance all`). Every R119 table list matches its layer's declared zoom range, and the single omission is real — `map_water_bodies_3_se` is 0 rows, and the comment's cross-country note checks out too (`fi 0, is 0, pl 1`). The R38 measurement reproduces: `step_growth_km2 = 8: -4108, 7: -2096, 6: 1082, 5: 5977, 4: -11137, 3: 27071`, so the 15 000 / 40 000 split is the right shape for the data, with the z3 exception carrying the Mälaren-scale collapse the comment describes. ### `min_distance_3857` prints as a Go struct, not a distance The R90/R91 test's diagnostic column comes back unreadable: ``` R90/R91: no two same-name places are closer than the 15 km cluster eps — PASS (439ms, 1 row(s), close_pairs=0, min_distance_3857=&{0 0 2}) ``` `round(...)::numeric` arrives as a `*pgtype.Numeric`, and `acceptance.go:390` formats values with `%v`, which prints the struct. The sibling column works because `round(...)::bigint` maps to an `int64` — same reason `wetland_km2_3857=48853` renders fine in the R40/R125 test right above. It only matters on failure, but that is exactly when it matters: the whole reason to carry `min_distance_3857` is that a red test should tell you *how* close the offending pair was, and today it would say `&{0 0 2}`. Casting to `::float8` (or `::text`) fixes it without touching the formatter. ### Note on R38's direction Like R40/R125's floor, R38 is one-sided — it bounds growth from above, so a cascade that lost water area entirely would pass it. R119 catches a collapse to zero and R40/R125 puts a floor under wetland specifically, so the gap is narrow, and I don't think it needs fixing here. Worth knowing it exists if the boundary classes ever get a regression that shrinks rather than grows them.
art-bot force-pushed issue-122-se-acceptance-tests from 21546dfc30 to a74ec01ed1 2026-08-16 04:31:56 +00:00 Compare
art-bot changed target branch from main to issue-113-fi-name-casing 2026-08-16 04:44:58 +00:00
Collaborator

Rebased onto issue-113-fi-name-casing (#130) to turn the doc conflicts into an explicit dependency:

main ← #130 (#113) ← #134 (this) ← #136 (#123) ← #137 (#124)

Force-pushed 21546df → a74ec01. Content is unchanged — the rebase was clean, no conflicts on this branch. The PR base is retargeted to #130's branch, so the diff above shows only Sweden's work (2 files); Forgejo will retarget it to main when #130 merges.

Re-verified after the rebase: go test ./... green, semap 26 passed / 0 failed (R35 not run outside -acceptance all).

**Rebased onto `issue-113-fi-name-casing`** (#130) to turn the doc conflicts into an explicit dependency: ``` main ← #130 (#113) ← #134 (this) ← #136 (#123) ← #137 (#124) ``` Force-pushed `21546df → a74ec01`. Content is unchanged — the rebase was clean, no conflicts on this branch. The PR base is retargeted to #130's branch, so the diff above shows only Sweden's work (2 files); Forgejo will retarget it to `main` when #130 merges. Re-verified after the rebase: `go test ./...` green, semap 26 passed / 0 failed (R35 not run outside `-acceptance all`).
art changed target branch from issue-113-fi-name-casing to main 2026-08-16 05:17:33 +00:00
art merged commit a74ec01ed1 into main 2026-08-16 05:18:04 +00:00
art deleted branch issue-122-se-acceptance-tests 2026-08-16 05:18:05 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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
art/ismap!134
No description provided.