Measuring Indoor Route Quality in Production
Part of Wayfinding API Observability. A route that is valid, fast to compute and impossible to follow produces no error anywhere. These are the measures that notice it.
Proxies for an Experience Nobody Reports
Route quality has no organic failure signal. A user who is sent the long way round either walks it or gives up, and neither outcome reaches an error log. So the measures are proxies: quantities that can be computed from data the service already has, and that move in the right direction when the experience degrades.
Each one is defined precisely enough to be comparable across releases, which matters more than whether the definition is the ideal one. Detour ratio compares a profile’s route length against the default profile’s for the same origin-destination pair. Reroute rate counts how many times a session requests a new route after the first. Instruction gap is the largest distance between consecutive turn instructions on a route. Abandonment requires the session state machine below.
Detour Ratio Points at the Building
Detour ratio is the most valuable of the six because it is the only one that measures accessibility outcomes, and it is the one most often absent. A wheelchair user routed four times as far as an ambulant user has been served successfully by every conventional metric: the request returned 200, the route was valid, the profile was honoured.
Computing it is cheap when a route is requested with a non-default profile — solve twice, once with the requested profile and once with the default, and record the ratio. The second solve costs a few milliseconds on a campus graph and only happens for the minority of requests that use a profile.
What makes it worth the trouble is that the outlier buildings are actionable in a way software metrics rarely are. A p90 of 4.1 in one building is not a routing bug — it is a building with one lift at the wrong end — and it belongs on an estates backlog with a number attached.
Defining Abandonment
Abandonment needs a session model, and the model is where the judgement sits:
import logging
from dataclasses import dataclass, field
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
@dataclass
class Session:
session_id: str
destination: tuple[float, float]
level: float
started_at: float
fixes: list[tuple[float, float, float, float]] = field(default_factory=list)
reroutes: int = 0
ARRIVAL_M = 5.0
SILENCE_S = 90.0
def outcome(self, now: float) -> str:
"""Classify a session as arrived, abandoned, or still in progress."""
if not self.fixes:
return "no_fix" # never got a usable position at all
t, x, y, level = self.fixes[-1]
if level == self.level:
dx, dy = x - self.destination[0], y - self.destination[1]
if (dx * dx + dy * dy) ** 0.5 <= self.ARRIVAL_M:
return "arrived"
return "abandoned" if now - t > self.SILENCE_S else "in_progress"
def summarise(sessions: list[Session], now: float) -> dict[str, float]:
outcomes = [s.outcome(now) for s in sessions]
total = max(len(outcomes), 1)
summary = {o: outcomes.count(o) / total for o in set(outcomes)}
summary["reroutes_per_session"] = sum(s.reroutes for s in sessions) / total
logger.info("session summary: %s", {k: round(v, 3) for k, v in summary.items()})
return summary
Three definitional choices are visible there and all three are arguable. Five metres as arrival
is generous enough to survive positioning error and tight enough to distinguish arriving from
passing. Ninety seconds of silence as abandonment is long enough to survive a lift ride and a
conversation. And no_fix is kept separate from abandoned, because a session that never got
a position is a positioning failure rather than a routing one — collapsing them would make a
positioning outage look like a route-quality collapse.
Which Metric Points Where
| Metric moves | Likely cause | Owner |
|---|---|---|
detour_ratio up in one building |
lift coverage | estates |
detour_ratio up everywhere |
a profile’s weights changed | routing |
reroute_rate up after a publish |
graph moved under users | map build |
reroute_rate up with no publish |
positioning degraded | positioning |
instruction_gap up |
junction nodes lost in a rebuild | graph construction |
abandonment up, others flat |
the map is wrong in a way routing cannot see | survey |
no_fix up |
beacons or APs down | infrastructure |
The last row is the reason no_fix is separated out. A building whose beacons have failed produces
sessions that never start, and every other metric on this page is computed only over sessions that
did — so a positioning outage makes route quality look better by removing the difficult sessions
from the denominator.
Common Errors & Fixes
Detour ratio is 1.0 everywhere. It is being computed against the same profile, not against the default. The comparison must be profile-versus-default for the identical pair, which means solving twice.
Reroute rate spikes on Mondays. Sessions are being counted across app restarts. A session id that survives a restart merges two genuine sessions into one and inflates the reroute count; scope it to the navigation session rather than to the app install.
Abandonment is 60% and nothing is wrong. The silence threshold is too short, or arrival is too tight. Both are worth validating by walking a handful of routes with the metric running — a five-minute exercise that usually moves both numbers.
Instruction gap is huge on long corridors. That may be correct: a 60 m straight corridor genuinely has no turns. The metric is more useful as a change detector than as an absolute, which is why the threshold matters less than the trend against map version.
Integration Point
These measures are derived from the spans and metrics that OpenTelemetry instrumentation emits, and they belong on the route-quality dashboard described in Wayfinding API Observability — the one whose audience is the product owner rather than the on-call engineer.
Two of them feed further. Reroute rate is one of the inputs alerting on positioning degradation uses to separate a positioning regression from a map regression. And detour ratio, broken down by building, is the number that makes a case for a second lift — which is the rare metric whose consumer is not an engineering team at all.
Frequently Asked Questions
Is detour ratio worth the second solve?
Yes, because the second solve only runs for non-default profiles and those are a small minority of traffic. On a campus graph a solve is single-digit milliseconds, so computing the default route alongside a step-free one adds a few milliseconds to perhaps five percent of requests — an overhead nobody will notice. What it buys is the only quantitative view of accessibility outcomes most estates ever get, and the measurement is otherwise impossible after the fact, because the default route for that specific pair at that specific map version is not recoverable later.
How do I tell a bad route from a user who changed their mind?
You cannot, per session, and you do not need to. Individual sessions are noisy — people stop for coffee, take a call, decide to go somewhere else — and no definition of abandonment separates that from a failed route. What the metric is for is comparison: the same definition applied to the same building before and after a publish, or to two buildings in the same week. A rate that jumps from 18% to 34% after a map release is signal regardless of what any individual session meant.
Should route quality metrics be per building or per estate?
Per building, aggregated to the estate for reporting. Buildings differ enormously in layout, lift coverage and signage quality, so an estate-wide average is dominated by whichever buildings have the most traffic and hides the ones with the worst outcomes. The detour-ratio chart above is the clearest case: the estate average is a perfectly respectable 2.0, and two buildings in it route wheelchair users four times as far as everyone else.
Related
- Wayfinding API Observability — the three layers these metrics sit in.
- Instrumenting a Wayfinding API with OpenTelemetry — the spans and metrics these are derived from.
- Accessible Routing Profiles — the profiles detour ratio compares against the default.
This page is a companion to Wayfinding API Observability, part of the Production-Ready Indoor Map Deployment section.