Methodology

STCP uses 'timing points' to measure the punctuality of buses. These are stops along a route for which a scheduled arrival time is published.

Every 10 seconds, we collect the GPS position of every bus in service, and store it. Once a day, we read those positions back to find the moments when each bus passed each timing point along its route, calculate how far off schedule it was, and store that measurement. Every statistic, table and chart on this site is then built from those stored 'off-schedule' measurements.

The sections below describe each step, and its limits, in more detail.

Where the data comes from

The data is obtained directly from STCP and Porto Digital. There are no other sources, and nothing is estimated.

Bus GPS positions in real time come from Porto Digital's public FIWARE broker. The timetable comes from STCP's own schedule API.

How we deal with timetable changes

We collect the timetable for all bus lines daily. A trip is always compared against the version of the timetable that was in force on that day.

How we aggregate off-schedule times

Averages and medians are calculated over the off-schedule times per timing point, rather than over entire trips. Thus, a trip may have records of early, on-time, and late arrivals, and all of these records will individually contribute to our statistics.

What counts as "on time"

A stop counts as on time if the bus was between 1 minute early and 5 minutes late. This is in line with the "on-time performance" (OTP) indicator, widely used for public transport.

That band is deliberately lopsided. From a passenger's point of view the two failures are not equivalent: a bus that leaves before its scheduled time is one you miss entirely, however early you arrived, while a bus five minutes late is one you still catch.

What gets excluded, and why

Trips whose GPS coverage is too sparse to place them confidently against the timetable are not measured. The share that survives this filter is reported on every page as the "measurable" percentage — if it is low, treat the accompanying figures with proportionate caution.

The live feed occasionally mislabels which scheduled trip a vehicle is running. Left alone this produces absurd results — a bus apparently hours off schedule — that would distort any average they entered. Measurements that are implausible on their face are flagged and excluded.

Days when collection covered only part of the service, due to an error on our part or in the data provided by STCP, are marked as "partial." The failure to measure some trips does not affect the quality of our statistics, it only reduces the universe of observations.

Limitations worth knowing

Delays are measured at timing points rather than at every stop a bus serves, so a line is characterised by a sample of its journey rather than the whole of it. How dense that sample is depends on how many timing points STCP defines for the route.

A scheduled trip that never runs is counted, not ignored. It produces no positions of its own, so instead it is charged the wait it actually cost: the time from its timetabled minute until the next bus that did serve the stop. A cancelled departure followed by an on-time one 30 minutes later is recorded as 30 minutes late, and counts against the line the same way a bus running 30 minutes late would. Where no bus came at all within 45 minutes, no delay figure is invented — the trip is counted under "scheduled trips that never ran" instead, and still counts as not on time.

Collection depends on the broker being available. Outages produce gaps in coverage, which appear as partial days rather than as good days. Because an hour in which nothing was recorded cannot be told apart from an hour in which nothing ran, trips scheduled during a network-wide blackout are left out of the cancellation figures altogether rather than counted as never having run.

How long data is kept

Raw position reports are pruned after roughly 45 days — they are voluminous and, once a trip has been matched and measured, no longer needed. The computed per-stop measurements and the aggregates built from them are kept indefinitely, so the historical record stays intact while storage stays bounded.

Check the working

Every choice described here is a judgement call, and reasonable people would make some of them differently. The entire pipeline — collection, matching, the on-time band, the exclusions — is open source and readable at github.com/goncalovf/stcpontual. If you think something is wrong, the code is the argument, and you are welcome to contest it.