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 |