Draw the walking score, and draw the streets it cannot score - #95
Merged
Merged
Conversation
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
marked this pull request as ready for review
September 18, 2026 09:13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-togglecheckboxes, 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, sowalkabilityhad 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
Checklist (CLAUDE.md governance)
CHANGELOG.mdhas a new entry under[Unreleased]README.mdis updated if user-facing behavior changedVERSIONis bumped if warranted (see versioning policy in CLAUDE.md) — 0.53.0 → 0.54.0gettext/{% trans %}docker compose up --buildruns cleanly and core flows workpython manage.py testpassesThe 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 testnordocker compose upcan 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.htmlcompiles as a Django template, and its extracted JavaScript passesnode --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 onmain. Nothing here touches the connectors.Manual checks for
docker compose up --build, in a workspace withstreets_with_speed,footways,pedestrian_crossingsandstreet_parkingsynced:classesmode and the ramp plus "widths not tagged" inspace_split.Also worth checking: a workspace with no
footwayslayer 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_stopsandtreesmay sync zero features for the sameout tagsreason #93 fixed for crossings;/parked-cars/has nobboxparameter, so it returns a whole workspace when the viewer is looking at one neighbourhood;backend/locale/*/LC_MESSAGES/django.poneeds amakemessagesrefresh, which is now well behind the codebase; andmap.htmlis 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