Skip to content
C.W.K.
Stream
Lesson 01 of 04 · published

Yes, With No Location at All

~13 min · origin, honesty, location, navigation

Level 0Lost
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

The question on the road

Dad was driving. He asked Pippa whether he was on the right road, and she said yes. She had no location at all: no fix from his phone, no route, nothing to compare anything against. The answer was warm, confident, and made of nothing.

He caught it. And he did what he does with every mistake worth keeping: he turned it into work. Three coop todos came out of that one wrong "yes": GPS into Pippa (#288), location-aware travel help in Waystone (#289), and a standalone navigation app (#290). That app is Pathfinder, and this whole quest is about it. Its architecture document still carries the goal in one line: keep Dad from getting lost first, then grow.

Why a confident yes is the worst answer

An assistant asked "am I on the right road?" has three honest outputs and one dishonest one. "Yes" with evidence. "No" with evidence. "I can't know, where are you?" And then the fourth: "yes" with nothing behind it.

The fourth is worse than the third, and not by a little. "I can't know" is annoying, but it hands the problem back to the person who can still solve it: Dad glances at a sign, checks the phone, slows down. A baseless yes takes that moment away. It does not just fail to help; it suppresses the check he would otherwise have made. That is the whole danger of fluency without facts: it sounds exactly like knowledge, and people lean on automated answers that sound like knowledge.

An answer without the facts is worse than "I cannot know." "I cannot know" leaves the person room to check. A confident answer built on nothing takes that room away and hides the fact that it did.

Two fixes, two layers

The first fix is a rule, and it lives in cwkPippa's location layer as rule one: no claim without a fix. Pippa never says where Dad is, which road he is on, or that he is going the right way without a measured position, and when she has one she says how old and how precise it is. No fix: she asks him. A fix is evidence of where a device was, and "current" means at most 120 seconds old.

But a position alone still cannot answer Dad's question. "Am I on the right road?" is not "where am I?". It is "where am I relative to the route I am supposed to be on?", and answering that takes a planned route and map matching against it. Pippa does not guide. Something has to, and has to tell her what it knows. That is the second fix: Pathfinder.

What Pathfinder gives Pippa

Beside each fix it posts to POST /api/location as reporter pathfinder, the phone sends a PippaNavigationReport:

  • state: idle, guiding, off_route, rerouting or arrived, plus the mode (driving or walking, because a walk asks different help than a drive);
  • destination and route: name, provider, distance, expected time, start, how many reroutes;
  • progress: remaining metres and seconds, the ETA, the next maneuver and its distance, how far off the route;
  • the next speed camera.

While a trip is guided it reports every 20 seconds when nothing happens, and at once when something does. cwkPippa validates the report rather than trusting it, keeps one per reporter, and active_navigation picks the freshest report that is not idle and not older than 120 seconds. The turn block then says it in words: "ON the planned route: its map matching places him on it," or "OFF the planned route by about 84 m; a new route is coming."

So the next time Dad asks, Pippa's yes is Pathfinder's map matching, with its age attached. Past two minutes, the report is not a drive in progress any more, and she is back to asking.

Dropped, never queued

One quiet detail completes the picture. When the phone cannot reach Pippa, its reports are dropped, never queued for later. The app just marks itself "Pippa unreachable" and moves on. A queue would sound more robust, and it would be the same lie in a slower shape: a position delivered late reads as where Dad is now. Freshness is part of the evidence, so the system throws away what has gone stale instead of delivering it.

Code

A minimal sketch: answering "am I on the right road?" only from fresh evidence (compiled with swiftc and run; its output is the comment at the end)·swift
import Foundation

// "Am I on the right road?" answered only from evidence fresh enough to count.
// The report is the shape Pathfinder sends beside each fix, cut to three fields.

enum NavState: String { case idle, guiding, offRoute = "off_route", rerouting, arrived }

struct NavReport {
  var state: NavState
  var offRouteMetres: Double?
  var updatedAt: Date
}

enum Answer: CustomStringConvertible {
  case onRoute(age: Int)
  case offRoute(metres: Int?, age: Int)
  case arrived
  case cannotKnow(String)

  var description: String {
    switch self {
    case .onRoute(let age): "Yes. The route matches you (report \(age) s old)."
    case .offRoute(let metres, let age):
      "No. You left the planned route\(metres.map { " by about \($0) m" } ?? "") (report \(age) s old)."
    case .arrived: "You're there."
    case .cannotKnow(let why): "I can't tell: \(why). Where are you?"
    }
  }
}

let maxAge: TimeInterval = 120  // older than this, a report is history

func rightRoad(_ report: NavReport?, now: Date) -> Answer {
  guard let report else { return .cannotKnow("no navigation report reached me") }
  let age = now.timeIntervalSince(report.updatedAt)
  guard abs(age) <= maxAge else { return .cannotKnow("the last report is \(Int(age)) s old") }
  switch report.state {
  case .guiding: return .onRoute(age: Int(age))
  case .offRoute, .rerouting: return .offRoute(metres: report.offRouteMetres.map { Int($0) }, age: Int(age))
  case .arrived: return .arrived
  case .idle: return .cannotKnow("nothing is being guided")
  }
}

let now = Date()
let cases: [(String, NavReport?)] = [
  ("no report", nil),
  ("stale", NavReport(state: .guiding, updatedAt: now.addingTimeInterval(-600))),
  ("guiding", NavReport(state: .guiding, updatedAt: now.addingTimeInterval(-12))),
  ("off route", NavReport(state: .offRoute, offRouteMetres: 84, updatedAt: now.addingTimeInterval(-5))),
]
for (name, report) in cases {
  print("\(name.padding(toLength: 10, withPad: " ", startingAt: 0)) -> \(rightRoad(report, now: now))")
}

// Output:
// no report  -> I can't tell: no navigation report reached me. Where are you?
// stale      -> I can't tell: the last report is 600 s old. Where are you?
// guiding    -> Yes. The route matches you (report 12 s old).
// off route  -> No. You left the planned route by about 84 m (report 5 s old).
cwkPippa, backend/services/dad_location.py: active_navigation (verbatim)·python
def active_navigation(
    now: datetime | None = None, max_age: float = NAV_MAX_AGE_S,
) -> tuple[str, dict[str, Any]] | None:
    """The freshest report of a drive in progress (not idle, not older than
    ``max_age``), as (reporter, report)."""
    now = now or datetime.now(timezone.utc)
    best_row: tuple[str, dict[str, Any]] | None = None
    best_at: datetime | None = None
    for key, nav in navigation_reports().items():
        if nav.get("state") == "idle":
            continue
        updated = parse_stamp(nav.get("updated_at"))
        if updated is None or abs((now - updated).total_seconds()) > max_age:
            continue
        if best_at is None or updated > best_at:
            best_row, best_at = (key, nav), updated
    return best_row

External links

Exercise

Find one function in your own project that answers a question from data that can be missing or old: a cache, a sensor reading, the last sync, a feature flag fetched at startup. Change its return type so "cannot know" is an explicit case with a reason, add a freshness bound to the data it reads, and write one test that feeds it a value past that bound. Then look at every caller and note what each one now says when the answer is unknown.
Hint
Start by asking what the function returns today when the data is absent. If the answer is a default (false, zero, the last value, an empty list), that default is the confident yes. A timestamp on the data is usually already there; the missing piece is a function that refuses to answer past a certain age.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.