Skip to content

Draw the walking score, and draw the streets it cannot score - #95

Merged
richardkfm merged 1 commit into
mainfrom
claude/parking-walkable-streets-711krs
Sep 18, 2026
Merged

richardkfm merged 1 commit into
mainfrom
claude/parking-walkable-streets-711krs

Conversation

@richardkfm

Copy link
Copy Markdown
Owner

Summary

Slice 3c of three, and the last one. #94 computed a walking class for every street; this puts it on the map, beside the cars the same street stores, and completes the Parking vs walking view.

One line layer, two readings of one request. Quality for people on foot colours each street comfortable / usable / tight / hostile / not enough data. Who gets the space colours the same streets by how their width divides between parked cars and people. Every street carries both readings in the one response, so switching mode repaints what is on screen rather than fetching again.

No number is drawn over a street. The classes are thresholds on an auditable 0–100 score, but a score printed on a map claims a precision that banded OpenStreetMap inputs cannot support, and a city-sized network of them is noise before it is information. "Show numeric scores" is unticked by default and puts the values in the popup only. They are always in the API, because principle 3 requires the calculation to be auditable — not displayed.

Streets with too little data are drawn, in their own pale grey, with their own legend row. Omitting them would let a city nobody has surveyed pass for a city with nothing wrong, and the survey gap is usually the most actionable thing the layer can say. Confidence is carried by opacity, so a class resting on thin data — or on a pavement matched by nearness rather than by id — literally looks faint. The popup names the inputs the score could not see, and says in words when a pavement was matched by proximity.

The derived-layer registry. activateStoryView() reaches only .layer-toggle checkboxes, so its blanket sweep could hide a derived overlay but nothing could switch one back on. Components now register {activate, deactivate, sync} and the sweeps go through the registry instead of the selector. That is what lets the new story view bring both halves up together — and it closes a live bug in the same stroke: a saved view restored the parked-car and availability-gap overlays on the map but left their checkboxes unticked and their data unloaded, so the panel and the map disagreed.

loadStyleState() rebuilds its object from a whitelist, so walkability had to be named there or the mode and the numeric opt-in would have silently reset on every reload.

No model change and no migration: walkability is a derived endpoint, not a synced layer kind.

Type of change

  • Bug fix
  • New feature / connector / measure rule
  • Refactor or cleanup
  • Documentation
  • CI / infrastructure

Checklist (CLAUDE.md governance)

  • I have read CLAUDE.md and my changes comply with all core principles
  • CHANGELOG.md has a new entry under [Unreleased]
  • README.md is updated if user-facing behavior changed
  • VERSION is bumped if warranted (see versioning policy in CLAUDE.md) — 0.53.0 → 0.54.0
  • No logic is hard-wired to a specific city, country, or language
  • All new user-facing strings use gettext / {% trans %}
  • docker compose up --build runs cleanly and core flows work
  • python manage.py test passes

The last two are unticked because they could not be run. This container has Django but no GDAL, so every GeoDjango import fails and neither manage.py test nor docker compose up can start. CI runs both. Rather than tick a box on a command I did not execute, they are left open.

Testing notes

No new Python tests. The change is template JavaScript, documentation and one gating flag; the repo has no JavaScript test harness, and none of the other story-view gating flags carries a test either. CLAUDE.md asks for UI changes to be verified manually in Docker, so the steps for that are below.

Verification run here:

  • ruff check backend/ — clean.
  • map.html compiles as a Django template, and its extracted JavaScript passes node --check.
  • python3 -m unittest measures.test_walkability measures.test_geo measures.test_street_space measures.test_parking_estimate — 199 tests green, unchanged.
  • python3 -m unittest connectors.tests — 232 tests at the same 1 pre-existing failure and 16 pre-existing "settings are not configured" errors as on main. Nothing here touches the connectors.

Manual checks for docker compose up --build, in a workspace with streets_with_speed, footways, pedestrian_crossings and street_parking synced:

  1. Tick Walking quality. Streets colour by class; unclassed streets are pale grey and thin, not missing. No number appears anywhere on the map.
  2. Click a street: the popup reads the class in words, gives the two bands, and lists what it could not see. Tick Show numeric scores and the 0–100 values appear in the popup only.
  3. Switch to Who gets the space: the line recolours without a second request; streets without both widths tagged stay grey, and the panel says what share that leaves.
  4. Save PNG — the exported legend carries the five class rows in classes mode and the ramp plus "widths not tagged" in space_split.
  5. Activate the Parking vs walking story view: both derived overlays come on together with the pedestrian layers, and both checkboxes stay ticked. Clear preset restores what was there before.
  6. Save a view with the walking and parked-car layers on, reload the page, load the view: both come back ticked and populated (this is the bug fixed here).
  7. Reload: the mode and the numeric-score setting survive.
  8. Switch the UI to German and confirm the new strings are translated.

Also worth checking: a workspace with no footways layer shows the amber panel warning and most streets grey — that is the designed conservative reading, not a failure.

Related issues

Completes the five-slice Parking vs Walking feature: #91 (pedestrian and parking data), #92 (parked cars), #93 (the data the score stands on), #94 (the score), this one (the map).

Still open as separate follow-ups, deliberately not folded in here: transit_stops and trees may sync zero features for the same out tags reason #93 fixed for crossings; /parked-cars/ has no bbox parameter, so it returns a whole workspace when the viewer is looking at one neighbourhood; backend/locale/*/LC_MESSAGES/django.po needs a makemessages refresh, which is now well behind the codebase; and map.html is past 3 900 lines and wants splitting, which needs a JavaScriptCatalog first.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CKiSPbDwCocoqdk3zoty9w


Generated by Claude Code

Slice 3c of three, and the last. #94 computed a walking class for every
street; this puts it on the map, next to the cars the same street stores.

One line layer, two readings of one request. `classes` colours a street by
what it is like on foot — comfortable, usable, tight, hostile, or not enough
data. `space_split` colours the same streets by how their width divides
between parked cars and people. Every street carries both readings in the
one response, so switching mode repaints rather than refetches.

No number is drawn over a street. The classes are thresholds on an auditable
0-100 score, but a score printed on a map claims a precision that banded OSM
inputs cannot support, and a city-sized network of them is noise before it is
information. "Show numeric scores" is unticked by default and puts the values
in the popup only; they are always in the API, because principle 3 needs the
calculation auditable, not displayed.

Streets with too little data are drawn, in their own pale grey, with their own
legend row. Omitting them would let a city nobody has surveyed pass for a city
with nothing wrong, and the survey gap is usually the most actionable thing the
layer can say. Confidence is carried by opacity, so a class resting on thin
data — or on a pavement matched by nearness rather than by id — literally looks
faint. The popup says which inputs the score could not see, and says in words
when a pavement was matched by proximity.

The derived-layer registry. `activateStoryView` reaches only `.layer-toggle`
checkboxes, so it could hide a derived overlay but never switch one back on.
Components now register `{activate, deactivate, sync}` and the sweeps go
through the registry. That is what lets the new "Parking vs walking" story
view bring both halves up together — and it closes a live bug in the same
stroke: a saved view restored the parked-car and availability-gap overlays on
the map but left their checkboxes unticked and their data unloaded, so the
panel and the map disagreed.

`loadStyleState()` rebuilds its object from a whitelist, so `walkability` had
to be named there or the mode and the score opt-in would have reset on every
reload.

Docs: new `docs/PARKING_AND_WALKING.md` covers both halves — what each layer
claims, the ten factors and their weights, the classes, coverage and
confidence, the footway join, the space split, the per-workspace overrides,
the API, and the limits. Linked from the README and summarised in the user
guide.

VERSION 0.53.0 -> 0.54.0 (MINOR: new map layer, new display mode, new story
view, new doc). CHANGELOG under [Unreleased]; README updated.

Verification: `ruff check backend/` clean; map.html compiles as a Django
template and its extracted JavaScript passes `node --check`; the pure module
suites (`measures.test_walkability`, `test_geo`, `test_street_space`,
`test_parking_estimate`) stay green at 199 tests; `connectors.tests` is
unchanged at its pre-existing 1 failure / 16 "settings are not configured"
errors. `manage.py test` and `docker compose up` could not be run here — this
container has Django but no GDAL, so every GeoDjango import fails. CI runs
both. No new Python tests: the change is template JavaScript, docs and one
gating flag, and the repo has no JS test harness; CLAUDE.md asks for UI
changes to be verified manually in Docker, which is listed in the PR body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKiSPbDwCocoqdk3zoty9w
@richardkfm
richardkfm marked this pull request as ready for review September 18, 2026 09:13
@richardkfm
richardkfm merged commit 407344b into main Sep 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants