Skip to content
C.W.K.
Stream
Lesson 02 of 05 · published

Door Sources, Ranked

~14 min · swift, python, arrival, validation, data-model

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

An entrance is a small value

Everything this track does hangs off one struct in PathfinderKit, Entrance. Its fields are the whole vocabulary of a door:

  • lat, lon: the door itself.
  • level: floors above the street the walk arrives on. 2 means "up two levels", -1 means "one level down", nil means nobody knows. Not the building's floor numbering: the climb from where Dad will actually be standing.
  • altitude: metres above sea level, as the phone's barometer measured it standing at this door. Only Dad's own confirmation records one, and it is the level check Pathfinder trusts first (still a barometer reading, stored and used only when accurate to 6 m).
  • note: a short Korean line shown and spoken on the way in, such as "2층 출발 홀, 이 계단으로 올라가".
  • source: who said this is the way in.
  • label: the door's own name or reference when it has one ("Salidas", "P2").
  • venue: the building's centre, set only when Pathfinder chose this door for a destination. It never travels on Pippa's wire.

The route preview turns all of that into one Korean line, summaryKorean: "2층 출발 홀 · 길에서 2층 위 · 피파가 알려준 입구". Dad sees which door, how far up, and whose word it is before he starts walking.

Three witnesses, one order

Entrance.Source has three cases, and its doc comment is the policy: Dad's own correction outranks Pippa's pin, which outranks OpenStreetMap's tags. The order follows who has the best evidence. OpenStreetMap's tags are volunteers' mapping, of uneven age and detail, written for every reader and none in particular. Pippa's door is chosen for this trip, by name, with a note written for Dad. Dad's door is the one he stood at, with the barometer's reading taken there. Nobody outranks the person who was physically at the door.

The type writes the order down as Comparable. Read the code that picks doors, though, and you will find it does not sort by that rank. ArrivalPlan.make builds its list by hand in the same order: the memory's confirmed door, then the door on Pippa's pin, then OpenStreetMap's ranked candidates. The rule lives in two places, and the kit test dadsDoorBeatsPippasWhichBeatsTheMap is what keeps them saying the same thing.

Pippa's half: a door she must actually know

Pippa hands Pathfinder destinations two ways, a recommended pin (pin) and a handoff that starts guidance (navigate), and both take the door as four flat tool arguments: entrance_lat, entrance_lon, entrance_level and entrance_note. Her location tool's description sets the bar in one sentence: "Give a door only when you know it; Pathfinder looks one up itself otherwise." A guessed door is worse than no door, because no door falls through to OpenStreetMap while a confident wrong door leads the plan.

cwkPippa's dad_location.normalize_entrance is the gate both paths share. It accepts a nested entrance object or the flat entrance_* arguments, and then refuses anything a door cannot be:

  • latitude and longitude must be finite and on the globe;
  • the door must lie within ENTRANCE_MAX_M = 1500.0 metres of the place (a haversine distance on a 6,371,008.8 m mean Earth radius), because farther than that is a different place, not its entrance;
  • level must be a whole number of floors from -5 to 10;
  • the note is cut to 200 characters.

The failure mode is the point. A malformed or far door raises, so the tool call fails and Pippa hears that the save failed, instead of the pin being saved quietly without its door. A note with no coordinates fails the same way. place_pins.save also keeps an earlier entrance when the same pin is saved again without one, so a later, lazier save does not erase a door.

The phone's Entrance is that wire's twin. The project notes say so explicitly: the pin's entrance has a twin in cwkPippa's place_pins and handoff. Neither repo imports the other, so a field changes in both or not at all. On the phone side, a door that arrives without a source is Pippa's by default, and a door off the globe is dropped while the pin itself survives.

Routing to the door without losing the building

Once a door is chosen, Place.routed(to:) makes a new destination: the door's coordinate, the same name and address, and the building's centre riding along in entrance.venue. The id changes too, to entrance: plus the old id. That prefix is not decoration. A provider handed a place id it recognises can route to its own idea of that place (Google's place id would), which is the station's centre all over again. A fresh id forces it to route to the coordinate. venueID strips the prefix back off, so a mode switch plans again from the building rather than from the door, and every screen still knows it is the same destination.

Rank your witnesses by where they stood. When several sources describe one fact, order them by how close each came to the thing itself, write the order down once, and make the weakest source fail loudly rather than guess.

Code

PathfinderKit, Arrival/Entrance.swift: the three sources and their order (verbatim excerpt)·swift
public struct Entrance: Codable, Hashable, Sendable {
  /// Who said this is the way in. Dad's own correction outranks Pippa's pin,
  /// which outranks OpenStreetMap's tags.
  public enum Source: String, Codable, Hashable, Sendable, Comparable {
    case osm, pippa, dad

    var rank: Int {
      switch self {
      case .osm: 0
      case .pippa: 1
      case .dad: 2
      }
    }

    public static func < (lhs: Self, rhs: Self) -> Bool { lhs.rank < rhs.rank }
  }
cwkPippa, backend/services/dad_location.py: normalize_entrance's bounds (verbatim excerpt)·python
    door_lat, door_lon = number(source.get("lat")), number(source.get("lon"))
    if door_lat is None or door_lon is None or not (-90 <= door_lat <= 90 and -180 <= door_lon <= 180):
        raise ValueError("An entrance needs finite latitude/longitude of the verified door.")
    rad = math.radians
    h = (math.sin(rad(door_lat - lat) / 2) ** 2
         + math.cos(rad(lat)) * math.cos(rad(door_lat)) * math.sin(rad(door_lon - lon) / 2) ** 2)
    if 2 * 6_371_008.8 * math.asin(min(1.0, math.sqrt(h))) > ENTRANCE_MAX_M:
        raise ValueError(f"The entrance is more than {ENTRANCE_MAX_M:.0f} m from the place; check the door.")
    entrance: dict[str, Any] = {"lat": door_lat, "lon": door_lon}
    level = source.get("level")
    if level not in (None, ""):
        floors = number(level)
        if floors is None or floors != int(floors) or not -5 <= floors <= 10:
            raise ValueError("entrance_level is whole floors above the street (-5 to 10).")
        entrance["level"] = int(floors)
    note = _text(source.get("note"), 200)
    if note:
        entrance["note"] = note
    return entrance
PathfinderKit, Arrival/Entrance.swift: Place.routed(to:) (verbatim excerpt)·swift
extension Place {
  /// The same destination, routed to its entrance: the walk ends at the door,
  /// the flag stands there, and the building's centre rides along in
  /// `entrance.venue`. The id changes so no provider routes to its own idea of
  /// the place instead (Google's place id would).
  public func routed(to entrance: Entrance) -> Place {
    var door = entrance
    door.venue = door.venue ?? self.entrance?.venue ?? coordinate
    var place = Place(
      id: id.hasPrefix("entrance:") ? id : "entrance:\(id)", name: name, coordinate: door.coordinate,
      address: address, category: category, phone: phone, providerID: providerID)
    place.attributions = attributions
    place.entrance = door
    return place
  }

External links

Exercise

Find a value in your own project that more than one source can supply: a user's address from a form, a profile import and a payment provider, say, or a timezone from settings, the browser and an IP lookup. Write the struct that carries it with a source field and an explicit order, and write the validation the weakest source must pass before it is accepted. Then decide, for each validation failure, whether the caller should hear an error or whether the value should be dropped silently, and justify each choice.
Hint
Ask the same question cwkPippa asked: if this check fails quietly, who ends up acting on a wrong value, and would they ever find out? A save that silently loses part of its input is the case that deserves a loud error.

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.