How It Works

The path a Winnipeg pump price takes before it reaches this site, and what can go wrong along the way.

The easiest way to explain the site is to follow one price. Say a driver fills up at a station on Pembina Highway, notices regular posted at 164.9, and reports it on GasBuddy. Here is what happens next.

1. The report is collected

A scheduled job on our server wakes up once an hour, a few minutes before the top of the hour, and reads GasBuddy’s reports for Winnipeg one page of stations at a time. A single “Winnipeg” search doesn’t reliably reach every station near the city limits, so the job also runs searches centred on neighbourhoods near the edges — Transcona, St. Vital, St. James, Fort Richmond, St. Boniface, Garden City, North Kildonan, Charleswood, The Maples and Sage Creek — and then covers Headingley, Selkirk, Stonewall, Oakbank, Niverville and Steinbach. The whole pass is paced to avoid overloading the source and can take a good part of the hour.

Separately, every hour the site reads the GasBuddy widget on CBC Manitoba’s gas-prices page, which lists Winnipeg’s ten cheapest current reports. That is a small sample, but it lets a new low price show up quickly.

Our Pembina price from above is picked up by whichever of these runs first, together with the time the driver posted it. That posting time, not the time our job ran, is what the station card later shows.

2. The number is checked

Before anything is saved, the price has to look like a real pump price. Values below 60¢/L or above 350¢/L are discarded. Some reports arrive in dollars (“1.649”) rather than cents, and those are converted instead of being mistaken for a one-cent station. Our 164.9 passes and is stored as a whole number of cents.

Next the report is matched to a station. If we have seen that GasBuddy station before, the match is by its GasBuddy ID. If not, we look for a station in our catalogue within about 120 metres with a compatible brand, which is how price reports attach to the station locations we imported from OpenStreetMap instead of creating a second pin for the same forecourt. The station’s current price is updated and a copy goes into our price history.

3. The price is ranked, then retired

For the cheapest-today list, a price counts only if it was reported in the last 36 hours. Within that window, prices reported in the last 12 hours rank ahead of older ones. Without that rule, a station nobody has visited since before a citywide increase would sit at the top of the list with yesterday’s lower number. After the tier, stations are ordered by price.

If nobody reports a new price at our Pembina station for 14 days, its price is cleared. The station stays on the map, without a number, until a fresh report arrives. We never estimate or interpolate a current price for a station that has no report.

4. It appears on the site

Our server (Node.js with Express, backed by PostgreSQL) answers the site’s requests through a few JSON endpoints:

  • /api/stations/cheapest — up to 20 stations with a current price, cheapest first
  • /api/stations?lat=&lng=&radiusKm= — stations inside a radius of a point, ranked by report freshness, then price, with distance breaking ties
  • /api/stations/:id — everything we hold for one station

Distances are straight-line distances calculated with the haversine formula. They are not driving distances, which matters in a city split by the Red and Assiniboine rivers: a station 2 km away across the water can be a longer drive than one 4 km away on your side.

The map is drawn with Leaflet, an open-source mapping library, on OpenStreetMap tiles. Each station is a marker; selecting it shows the name, price, address and a link that opens the station in Google Maps for directions. When you share your location, your position is marked too and the map zooms to the stations around you.

5. Your location, if you share it

The nearby search asks your browser for your position through the standard geolocation API. Your browser or phone asks your permission first, and nothing is sent if you decline. If you allow it, the coordinates go to our server over HTTPS as part of a request like this one:

GET /api/stations?lat=49.90&lng=-97.14&radiusKm=10

The server uses them to filter the station list for that request and then discards them. They aren’t written to our database or sent to analytics, and our web server is configured not to write API requests to its access log.

Prefer not to share? Type a postal code instead. We read only the first three characters and look them up in a short built-in table of Winnipeg areas, from R2C in Transcona to R3K around Crestview and Sturgeon Creek, then search from a representative point in that area. That is precise enough to find nearby stations and nowhere near precise enough to locate a home. Codes outside the table, including the R0 codes used by towns outside the city, fall back to a search from Portage and Main.

6. The daily snapshot

Once a day, overnight Winnipeg time, the server records the median regular price across Winnipeg stations with a price, and fetches the latest West Texas Intermediate crude price and an attempt at the Winnipeg wholesale (rack) price. Those daily records feed the fill-up-or-wait forecast, whose page explains how it works and what happens when an input is missing.

What this system can’t do

Every price here depends on someone having reported it. Busy stations on the big corridors are usually reported often; a station on a quiet street, or in a small town, may not be. A report can be mistyped in a way that still looks plausible, and a station can change its sign five minutes after someone reports it. Prices tend to move fastest when the whole market resets upward, which is exactly when an older report is most likely to be too low.

So treat the time on each station card as part of the price. A “stale” label means the report has aged past our current-price window. Whatever the site says, the number on the pump is the one you pay; check it before you start fuelling. The disclaimer covers the rest.