AC Transit Board of Directors · Briefing

Measuring AC Transit From the Rider's Seat

An independent, real-time performance tracker built entirely from the agency's own public data — and what it shows about the service we actually deliver.

Prepared by Kuan Butts  ·  June 2026  ·  Live tools: kuanbutts.com/actransit · performance dashboards
Public data only Live & running today

This briefing has three parts: why I built this system, how it works, and a walk-through of what it shows — followed by a short closing thought on what a transit agency's product really is.

1 · Why I built this

It started as a personal annoyance. The official AC Transit app often shows arrival predictions and bus locations that don't match what's actually happening on the street. The usual culprit is how vehicles get registered by dispatch and operations: the app can confidently predict a bus that isn't really coming, while the bus that is coming goes unrecognized.

So I built a simple live map. It reads the agency's own GTFS‑Realtime feed directly — the same public data stream the official app is built on — and plots the real vehicles operating on a route. When I'm standing at a stop wondering "is my bus actually coming?", I can look at the real positions and decide for myself.

Side-by-side: my live map vs the official AC Transit app
Figure 1 — The problem that started it. The same moment, the same Route P bus. Left: my live map reads the GTFS‑Realtime feed and shows the one vehicle actually operating the route. Right: the official AC Transit app puts vehicles in the wrong place and predicts arrivals for a bus that isn't really there — an artifact of how trips get registered by dispatch and operations. Standing at the stop, only the left view tells you whether your bus is genuinely coming.

A technical aside: why the official app is more fragile

From what I can tell, the official app doesn't consume the agency's standard GTFS‑Realtime feed. Instead it calls AC Transit's ActRealtime prediction service (see api.actransit.org/transit/Help), with requests shaped roughly like this:

GET actrealtime/prediction?stpid={stpid}&rt={rt}&vid={vid}&top={top}&tmres={tmres}&callback={callback}&showocprd={showocprd}

That prediction is keyed on a vehicle being correctly registered in several ways by dispatch and the operator (vehicle ID, run, assignment, and so on). When any piece of that registration is missing or wrong — which, as far as I can tell, happens fairly often — the prediction binds to the wrong vehicle or to none at all, and the rider is shown a bus that isn't really coming.

GTFS‑Realtime is far less brittle. So long as the vehicle is simply registered to its route (e.g. the on-vehicle headsign/display is set correctly), it is at least correctly associated with that route. From there I can use the vehicle's reported location to impute arrival accuracy, speed, and everything else — a much higher success rate even when the deeper dispatch registration is incomplete.

ActRealtime prediction (official app)GTFS‑Realtime (this tool)
Depends onFull, correct dispatch/operator registration of each vehicle (ID, run, assignment).The vehicle simply being on the right route, plus a GPS position.
Failure modeA mis-registered vehicle yields wrong or phantom arrivals.Degrades gracefully — the route association still holds.
Net effectBrittle, and visibly wrong at the stop.Higher success rate; reliable enough to compute accuracy metrics.
Live map of AC Transit buses
Figure 2 — The live map. Every vehicle currently reporting on the GTFS‑Realtime feed, refreshed every 30 seconds. Here, Route 6 is filtered: 9 of 291 system-wide vehicles are actually running that line right now. This is the agency's data, shown without the prediction layer that can mislabel which bus is which.

Then came the bigger realization. To draw that map, I poll the real-time feed every minute for every vehicle in the system. Accumulated over days and weeks, that stream becomes a complete record of where every bus was, minute by minute. From that record I can reconstruct each scheduled trip and measure what riders actually experienced:

The live map grew into a full performance-analytics system — built with no privileged data access. Everything here is computed from feeds AC Transit already publishes. Anything I can measure, the agency can measure too.

2 · How the system works

The whole thing is a small, serverless pipeline on Google Cloud. It is deliberately cheap and boring to run: there is nothing to babysit, and it costs on the order of a few dollars a month.

~$6–8/mo
Total cloud cost
every 60s
Whole-fleet polling
100%
Public data
~110k
Trips graded / 4 wks

2.1  The data flow

A scheduler pings a single small service every minute. That service reads the live feed, figures out where each bus is along its route, detects when it passes each scheduled stop, and stores the results. Static dashboards read those results — no database server to keep running.

System data-flow architecture diagram
Figure 3 — End-to-end architecture. Cloud Scheduler triggers a single Go service on Cloud Run. /scrape records vehicle positions; /refresh-gtfs ingests the nightly static schedule; /track-performance turns raw positions into per-stop on-time results. Results land in Cloud Storage and BigQuery, which feed the public dashboards.

2.2  From GPS pings to "was the bus on time?"

The interesting work happens once a minute. The service takes each vehicle's latest position, projects it onto the shape of the route it's running to get a precise distance-along-route, and watches for the moment that distance crosses each scheduled stop. That crossing is the bus's actual arrival; comparing it to the timetable gives the delay.

Per-minute processing pipeline diagram
Figure 4 — The per-minute pipeline. Each step is deterministic and replayable. Every scheduled stop on a finalized trip yields one on-time observation; every raw position is also archived so any number can be audited back to source.

2.3  Tracking a single trip

Each bus-on-a-trip is followed as a small state machine. While it's running, the system accumulates positions and ticks off stops. A trip is finalized when it reaches its last stop — or, if a bus's GPS goes quiet for 20 minutes, the trip is closed out with whatever was observed and flagged as incomplete. Both outcomes are written to the warehouse.

Trip lifecycle state machine diagram
Figure 5 — Trip lifecycle. "Stale" trips (the bus that simply stops reporting) are captured deliberately rather than discarded — a dropped or abandoned trip is exactly the kind of failure riders feel most.

2.4  Design choices worth highlighting

ChoiceWhy it matters for the board
Public data onlyBuilt on GTFS‑Realtime + GTFS‑Static, both published by the agency. There is no special access and nothing proprietary — the results are independently reproducible.
Rider-centric definitionsA stop counts toward On Time Service Delivered only if the bus arrived between 1 minute early and 7 minutes late. Stops the bus never reached remain in the denominator. This is intentionally stricter than a typical internal on-time metric: a bus running 10 minutes late technically ran its trip, but it did not arrive inside the rider window.
Fully auditableEvery raw position is retained, so any figure on a dashboard can be traced to the underlying observations and any individual trip can be replayed minute by minute.
Cheap & serverless~$6–8/month, no servers held warm, no operations burden. This is a weekend project that runs itself — not a procurement.

3 · What it shows: the dashboards

The output is four linked views, each answering a different question — from a one-glance agency report card down to a single stop on a single route.

3.1  Report card — "How is each route doing, overall?"

Every route gets a single letter grade for the last four weeks: a 0–100 composite that blends On Time Service Delivered (60%), on-time rate (15%), speed versus a 13 mph ideal (15%), and headway regularity (10%). The agency-wide grade is shown up top.

Report card view
Figure 6 — Report card. The system-wide grade for the four weeks ending 2026-06-03 is a C (74.3 / 100): 73.5% of scheduled stop-arrivals fell inside the rider window, 40.7% were strictly on time, average speed 15.7 mph. Below this card, all 122 routes are graded and sortable best-to-worst.

3.2  Daily — "What happened yesterday?"

A single service day in detail: headline rates, the full minute-by-minute delay distribution, schedule compliance (including dropped and not-completed trips), arrivals by time of day, headway distortion, and a per-route breakdown.

Daily overview view
Figure 7 — Daily overview. For 2026-06-03: 182,490 stop arrivals observed across 390 vehicles; 40.8% on time, 36.0% more than 3 minutes late, p50 delay 1.8 min. The looser cumulative thresholds (within 5 / 7 min) show how on-time rates climb as the window widens.

3.3  Weekly — "What are the patterns over a week?"

The seven-day rollup is where structure appears: On Time Service Delivered by day, a delay heatmap across day × hour, per-route delay curves through the day, and a route × day grid of schedule compliance. This is where you can see, for example, that the afternoon peak is consistently the system's weakest window.

Weekly overview view
Figure 8 — Weekly overview. Week ending 2026-05-30: 1,214,549 stop arrivals across 491 vehicles, 41.9% on time, average speed 15.9 mph. Further down, a day×hour heatmap and per-route hourly curves expose when and where reliability degrades.

3.4  Route — "Where on this line do things break down?"

The deepest view drops onto a single route. Every stop is mapped and colored by how late its worst arrivals run, sized by severity — so a problem segment is visible at a glance. The page also charts passenger wait time (derived from observed headways) and speed by hour and direction.

Route stop-adherence map
Figure 9 — Route stop-adherence map (Route 6). Each circle is a stop; color shows how late the 95th-percentile (worst ~1-in-20) bus arrives there, and size scales with severity. Reading the line from Berkeley down into Oakland, you can see exactly where delay accumulates along the corridor.
Route page passenger wait-time analysis
Figure 10 — Passenger wait time, same route. Using the "inspection paradox," observed bus-to-bus gaps are converted into the wait a rider arriving at a random moment would actually experience. On Route 6 the weekday median wait is 7.8 min, but the 95th-percentile wait is 23.2 min — the long tail riders remember.

4 · Closing thought: what is the product?

I'll end where a recent op-ed in Mass Transit magazine (April 14, 2026, "Who should run your transit agency?") lands. Its thesis is simple and, I think, exactly right:

Transit agencies describe themselves as operators, planners, engineers, customer-service organizations, infrastructure managers. But from a rider's perspective, the product is much simpler: a bus or train arrives at a particular place at a particular time.

The schedule determines that product. Everything else exists to support it.

That is precisely what this system measures — the agency's actual product, scored from the rider's side of the windshield, using only data the agency already publishes.

Why this is useful to the Board

Explore it live: the real-time map and the performance dashboards (report card · daily · weekly · route). Source and architecture notes: github.com/kuanb/actransit_perf_v2.