Skip to main content

Predicted Traffic Learns When to Trust Recent Roads

· 8 min read
Evgen Bodunov
Evgen Bodunov
CEO @ Globus Software

The first Globus predicted-traffic release for Valhalla had a deliberately simple shape: coverage everywhere, richer temporal detail only where the data could carry it.

That was the right place to start.

The June navigation data update keeps the same product promise. There is still no new API, no separate traffic overlay, and no integration change for SDK customers. The traffic layer is still baked into the regular map and navigation data update.

What changed is the model's judgment underneath.

It now handles local time correctly, treats recent evidence more carefully, and refuses to widen weekly profiles where freshness already explains the signal better.

From coverage to judgment

The May release made Valhalla understand that roads are not equally fast all the time.

It shipped two layers:

  • scalar predicted traffic across the drivable graph
  • a narrow DCT weekly-profile canary on corridors with enough evidence

That split still matters. A traffic model should not confuse detail with accuracy. Sparse roads need conservative fallbacks. Dense roads can earn more shape. Every richer layer has to abstain when the evidence is not strong enough.

Since that release, the work has been less about adding a bigger model and more about improving the decisions around the model:

  • what local time means on each road
  • when recent data should outweigh older history
  • when weekly shape helps after recency is already handled
  • whether the generated CSV still matches the exact Valhalla graph that receives it

Those are quiet changes. They are also the kind that make a traffic layer safer to ship.

The clock now belongs to the road

Traffic is local.

Our observations are stored as UTC instants. That is still the right primary record. A timestamp should identify one exact moment in time, independent of where the road is.

But day and night traffic features should not be computed in UTC. Monday 08:00 on a road in Berlin is not Monday 08:00 on a road in New York. A road in Warsaw should not inherit its rush-hour bucket from the database clock.

The scalar model now derives traffic windows in local road time.

That means:

  • the source timestamp remains UTC
  • the derived traffic feature uses the road's IANA timezone
  • daylight saving time follows the timezone database
  • missing timezone data is treated as an export problem, not silently guessed

This is not a visible product feature. It is a correction that makes every later traffic feature less suspicious.

Sparse windows need a softer prior

The scalar layer exports two speeds per directed edge:

  • free_flow for the night window
  • constrained_flow for the day window

That works well when both windows have evidence. But a road with no local night observations should not receive an aggressive night speed just because similar roads are usually faster at night.

So the model now guards cohort day/night movement by local window support:

support = window_obs × reliability
ratio_weight = support / (support + 5)
effective_ratio = 1 + ratio_weight × (cohort_ratio - 1)

When the window has no evidence, the prior stays close to the road's own level. When the window has support, the cohort can still help.

That is the theme of the whole update: move when the evidence earns movement, stay quiet when it does not.

Recent roads get a vote

Historical traffic has a long memory. That is useful on stable roads and dangerous on roads that changed.

A new bypass opens. Construction ends. A speed limit changes. A road that used to be quiet becomes the preferred route after a nearby change. If the model treats all observations equally, old behavior can keep pulling the prediction toward a world that no longer exists.

The first freshness layer used one global half-life: 180 days.

That was a strong baseline. But one half-life is not the final answer. Dense roads with strong recent evidence can move faster. Sparse roads should often move slower.

For the June update, we added a deterministic adaptive freshness selector.

It does not invent new speeds. It runs the same scalar prediction three ways:

  • HL90 - shorter memory for dense recent-vs-stale conflicts
  • HL180 - the default freshness baseline
  • HL365 - slower movement for sparse or low-support roads

Then it chooses one candidate per edge and per window.

This is intentionally bounded. The selector is not a black-box model and not an unconstrained blend. It chooses between three already-understood predictions. That makes the result inspectable and easy to roll back.

What the selector actually did

The full freshness-fair benchmark did not produce a dramatic global win. That is expected. Most roads do not have a strong recent-vs-stale conflict.

The best selector improved global MAE by 0.0075 kph against the HL180 baseline.

The useful result was in the slice it was designed for:

  • window-conflict slice: -0.029 kph
  • no global regression
  • no service-road regression
  • no KZ/RU/US sparse-tail failure

The production export choices show the same conservative shape:

day:
HL365 27,186,418
HL180 15,045,541
HL90 61,779

night:
HL365 31,108,322
HL180 11,180,226
HL90 5,190

Across day and night windows, roughly 69% of choices use HL365, roughly 31% use HL180, and less than 0.1% use HL90.

That is not a model trying to make every road exciting. It is mostly conservative, with a narrow permission to react quickly where recent data is dense and contradictory enough.

Weekly profiles still need to earn their place

The May DCT canary proved that weekly shape exists on selected corridors.

After the scalar baseline became stronger, we tested DCT again against the new freshness-aware baseline. That was necessary. A weekly profile that beats an older scalar model may still lose once the scalar model understands recency.

On a freshness-fair temporal holdout, raw DCT lost:

  • latest 28-day fold: 47.72% win rate vs freshness
  • four folds pooled: 48.50% win rate
  • combined MAE delta: +0.054 kph against the freshness baseline

The signal did not disappear. DCT still helped on some dense weekly-shape corridors. But it lost exactly where freshness is supposed to help: roads where recent and older behavior disagree.

So we did not widen raw DCT.

That is progress, even though it is not a rollout. It tells us the next DCT version has to be gated or recency-aware. Weekly shape should add rush-hour structure on stable corridors, not overwrite evidence that a road has recently changed.

The June tile build

The June update was generated against the current Valhalla graph.

That detail is operationally important. Valhalla predicted-traffic CSVs contain graph IDs with tile-local edge indexes. If you build CSV against one graph and ingest it into another, a speed can attach to the wrong edge.

For this release we rebuilt the observation table and generated CSV for the same graph that received the traffic:

  • 1,282,056,165 matched observations
  • fresh Scenic archive data
  • Guru/Postgres route data
  • tracks split and filtered before matching
  • generated against the June Valhalla graph

The export produced:

  • 42,293,738 directed edges with predicted traffic
  • 52,871 per-tile CSV files
  • 0 duplicate graph IDs in Valhalla ingest
  • 0 lower-bound speed violations
  • 0 upper-bound speed violations

Valhalla ingested every exported graph ID into the June tile directory:

Parsed 42,293,738 constrained traffic speeds.
Parsed 42,293,738 free flow traffic speeds.
Updated 42,293,738 directed edges.
Duplicate count 0.
Speeds below lower bound count 0.
Speeds above upper bound count 0.

Parsed equals updated. That is the integrity check that matters most for this kind of release.

What users get

Users get the update through the normal map and navigation data refresh.

There is no new request option. No new API response. No separate traffic product to wire in.

What changes is the behavior of the baked prediction:

  • local-road-time bucketing is now the scalar foundation
  • sparse day/night windows are less overconfident
  • freshness is support-guarded
  • dense recent-vs-stale conflicts can react faster
  • raw DCT does not expand until it beats freshness on the right benchmark

The goal is not to make every ETA change. The goal is to make the right ETAs change, for reasons we can explain.

What comes next

The next work is measurement-heavy.

We now have fresh held-out tracks that the June model did not see during training. That lets us measure route-level error directly: actual travel time versus Valhalla ETA, by country, continent, road class, and navigation context.

That will answer more practical questions than edge MAE alone:

  • are we optimistic or pessimistic?
  • which countries carry most of the error?
  • how much of the error is urban versus long-distance?
  • where would another model actually help?
  • where is the data already good enough?

Only after that should we widen the next modeling layer.

Predicted traffic is not a race toward the most complicated model. It is a discipline of small, bounded decisions that a router can survive.

If you are building routing, fleet dispatch, delivery, field service, or offline navigation, start with Globus. Register at user.globus.software, create an API key, and try the routing stack in your own product. If you want to evaluate predicted-traffic tiles or discuss a Valhalla deployment, write us at [email protected].