Network Pulse

← All notes

The network overview

Status: built 2026-09-17. Punctuality, average speed and in-service vehicle hours on one page, over a window you choose, for the operators, routes and direction you choose. Every figure is a sum over the same journeys.

The page is /overview. It was built to stand next to what the commercial products show an authority, and to be honest about the three places where our figure and theirs mean different things.

Why a new table

The three numbers already existed here, and they were drawn from different populations. Punctuality came from punctuality_daily, grouped by operator, line and hour, carrying no direction at all. Speed and mileage came from a query over journey_times. Filtering both by direction was impossible, and filtering them by anything else gave a punctuality measured over every observation against a speed measured over only the journeys with enough fixes to compute a leg. Two true numbers about two different things, side by side, labelled as if they described one.

overview_daily is one row per service date, administrative area, operator, line and direction, rolled up from journey_times alone. Whatever the filters, all three figures are sums over the same rows.

overview_rollup.py                # the last 3 days
overview_rollup.py 2026-09-15     # one day
overview_rollup.py 2026-08-04 2026-09-17

Nightly at 04:05 (overview-rollup.timer), last in the chain: it reads what journey-times-rollup writes at 03:10, and repairs the same three days that job does, so a day repaired there is recomputed here rather than keeping its pre-repair figures. About 12 seconds a day.

What the three numbers mean

Punctuality is timing points only, −60 to +359 seconds. That is what punctuality_rollup.py uses and what /punctuality publishes, and the constants are imported from it rather than repeated, so the two cannot drift.

It is journey-scoped here and observation-scoped there: a journey is counted wholly in the area it started in, rather than each stop being counted in its own area. On South Yorkshire for 15 September that is 76.4% against 76.7% — 39,601 timing points against 39,030. Neither is wrong; they answer "how did journeys from this area run" and "how did stops in this area perform". The page says so and links across.

In-service vehicle hours is first observed stop to last observed stop, per journey. It is not duty time. Layover, dead running and time parked between journeys are all outside it, because nothing in BODS says when a shift begins. A supplier's "vehicle hours" is usually the larger duty figure, so ours reads low against one — for 4–28 August, 115,256 hours against a CitySwift screenshot's 163,925 for 3–28 August, about 30% lower on a window one day shorter.

Average speed is measured distance over in-service time. Distance runs along the road where the journey's shape covers the leg and great-circle where it doesn't, and the share measured on road is printed under the figure rather than averaged away.

The 30 August boundary

tt_shape_distance is built from the export's shapes.txt, and that import landed on 2026-08-30. Journeys before it have no shape distance to look up, so every leg falls back to the straight line between stops.

The effect is not subtle:

before 30 Aug after
distance measured on road ~21% ~90–96%
South Yorkshire speed 9.7–11.4 mph 12.3–14.0 mph

That step is our measurement changing, not the network. Drawing it without saying so would be publishing an artefact as a finding, so the page finds the boundary rather than hard-coding it — the earliest date from which the on-road share stays above 60% — marks it on the chart, and puts a warning on the speed tile whenever the window crosses it. When somebody backfills the older vintages' shapes, the warning should stop appearing by itself. If it is still there and the table above no longer holds, that query is the thing to look at.

What the page does not have

A depot filter. The comparable products have one, with routes assigned to nine depots. Depot is not in BODS. The only depot data we hold is vix_observations, which is one depot — Olive Grove — for one service date, 10 September, and that is the day with the capture hole. A control that never works is worse than an honest absence, so the page says the word once, under the filters, and does not draw it.

Coverage outside South and West Yorkshire. journey_times is written only for the areas in AVL_ALL_STOPS_AREAS. Other areas appear in the area menu where a journey starts in one and runs into ours, and their figures are a fragment of that area rather than the area. The menu shows them because hiding a number somebody can compute from the same data is its own kind of dishonesty, but they should not be quoted.

Reading the chart

The punctuality axis is fixed at 0–100 and should stay that way. The product this was modelled on scales its punctuality axis to the data — 78% to 88% — which turns a three-point spread into a cliff. Vehicle hours, which have no natural ceiling, are scaled to their own maximum on the right, and the weekly sawtooth in that line is Sundays.

Route benchmarks compare each route to the network under the same filters, and exclude routes with fewer than 200 measured timing points in the window. A route with nine observations and 100% on time is a route nobody watched, and putting it top of a league table is how a report loses an argument. Columns sort.

Files

Path What
frontend/overview_web.py the page and /api/overview
frontend/overview_rollup.py fills overview_daily
frontend/migrations/versions/c7e41a9d52bf_add_overview_daily.py the table
frontend/overview-rollup.{service,timer} the nightly job
frontend/templates/overview.html, static/js/overview.js, static/css/overview.css the page