본문 바로가기
C.W.K.
Stream
Lesson 01 of 04 · published

위치도 없이 "응"이라고 한 날

~13 min · origin, honesty, location, navigation

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

운전 중에 나온 질문

아빠가 운전하다가 피파한테 물었어. 이 길 맞아? 피파는 응이라고 했어. 위치는 하나도 없었어. 폰에서 온 측위도 없고, 경로도 없고, 비교할 게 아무것도 없었지. 다정하고 자신 있는 대답이었는데, 속은 텅 비어 있었어.

아빠는 그걸 잡아냈어. 그리고 남길 만한 실수를 대할 때마다 하는 걸 했지. 일거리로 바꾼 거야. 틀린 "응" 하나에서 coop 할 일이 셋 나왔어. 피파한테 GPS 넣기(#288), Waystone의 위치 기반 여행 도우미(#289), 그리고 독립 내비 앱(#290). 그 앱이 Pathfinder고, 이 퀘스트 전체가 그 얘기야. 아키텍처 문서엔 목표가 아직 한 줄로 박혀 있어. 먼저 아빠가 길을 안 잃게. 키우는 건 그다음.

자신 있는 "응"이 제일 나쁜 이유

"이 길 맞아?"에 대한 정직한 대답은 셋이야. 근거 있는 "응". 근거 있는 "아니". 그리고 "모르겠어, 지금 어디야?" 정직하지 않은 대답이 하나 더 있어. 아무 근거 없는 "응".

넷째가 셋째보다 나빠. 그것도 꽤 많이. "모르겠어"는 답답하긴 해도 문제를 아직 풀 수 있는 사람한테 돌려줘. 아빠가 표지판을 보든, 폰을 보든, 속도를 줄이든 할 수 있지. 근거 없는 "응"은 그 순간을 뺏어. 도움이 안 되는 데서 끝나지 않아. 원래 했을 확인을 막아버려. 사실 없이 매끄럽기만 한 말이 무서운 게 이거야. 겉으로는 아는 소리랑 똑같이 들리고, 사람은 기계가 아는 듯이 하는 말에 기대게 돼 있어.

사실 없이 내놓은 답은 "모르겠어"보다 나빠. "모르겠어"는 확인할 틈을 남겨. 아무것도 없이 자신만만한 답은 그 틈을 없애고, 없앴다는 사실까지 감춰.

고친 건 둘, 층도 둘

첫 번째는 규칙이야. cwkPippa 위치 레이어의 1번 규칙. 측위 없이 주장하지 않는다. 아빠가 어디 있는지, 무슨 길 위인지, 제대로 가는지를 측정값 없이 말하지 않아. 측정값이 있으면 얼마나 오래됐고 얼마나 정확한지도 같이 말해. 없으면 아빠한테 물어. 측위는 기기가 있었던 자리의 증거고, "지금"으로 쳐주는 건 길어야 120초야.

근데 위치만으로는 아빠 질문에 답이 안 나와. "이 길 맞아?"는 "나 어디야?"가 아니거든. "가기로 한 경로에 비해 내가 어디 있어?"야. 거기 답하려면 계획된 경로가 있어야 하고, 그 경로에 위치를 맞춰보는 맵 매칭이 있어야 해. 피파는 길 안내를 안 해. 누군가는 해야 하고, 아는 걸 피파한테 알려줘야 해. 그게 두 번째 해결책, Pathfinder야.

Pathfinder가 피파한테 넘기는 것

폰은 측위를 POST /api/location에 보고자 pathfinder로 올릴 때마다 옆에 PippaNavigationReport를 붙여 보내.

  • 상태: idle, guiding, off_route, rerouting, arrived 중 하나. 여기에 모드도 붙어. 운전이냐 도보냐인데, 걸을 때 필요한 도움은 차랑 다르거든.
  • 목적지와 경로: 이름, 제공자, 거리, 예상 시간, 출발 시각, 재탐색 횟수.
  • 진행: 남은 거리(미터)와 시간(초), 도착 예정 시각, 다음 안내 지점과 거기까지 거리, 경로에서 벗어난 거리.
  • 다음 과속 카메라.

안내 중엔 별일 없으면 20초마다, 무슨 일이 생기면 바로 보내. cwkPippa는 이 보고를 그냥 믿지 않고 검증해. 보고자마다 하나씩만 두고, active_navigation이 idle이 아니면서 120초를 안 넘긴 것 중 제일 최신을 골라. 그다음 턴 블록이 그걸 말로 풀어줘. ON the planned route: its map matching places him on it. 아니면 OFF the planned route by about 84 m; a new route is coming. 같은 식으로.

그래서 이제 아빠가 물으면 피파의 "응"은 Pathfinder의 맵 매칭이고, 몇 초 전 얘긴지까지 달려 있어. 2분이 지나면 그 보고는 더는 진행 중인 주행이 아니야. 그럼 피파는 다시 물어봐.

쌓아두지 않고 버려

눈에 잘 안 띄지만 이 그림을 마저 채우는 디테일이 하나 있어. 폰이 피파한테 닿지 못하면 보고는 버려져. 나중에 보내려고 쌓아두지 않아. 앱은 "Pippa unreachable"이라고 표시하고 넘어가. 큐가 더 튼튼해 보이겠지만, 그건 같은 거짓말을 느리게 하는 거야. 늦게 도착한 위치는 지금 위치처럼 읽히거든. 신선함도 증거의 일부라서, 상한 건 늦게라도 배달하지 않고 그냥 버려.

Code

최소 스케치: "이 길 맞아?"를 신선한 근거로만 답하기 (swiftc로 컴파일해서 돌렸어. 출력은 맨 끝 주석)·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 (원문 그대로)·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

자기 프로젝트에서 빠지거나 낡을 수 있는 데이터로 질문에 답하는 함수를 하나 찾아봐. 캐시, 센서 값, 마지막 동기화, 시작할 때 받아온 기능 플래그 같은 거. 반환 타입을 바꿔서 "알 수 없음"을 이유가 달린 별도 갈래로 만들고, 읽는 데이터에 신선도 한계를 걸어. 그 한계를 넘긴 값을 넣는 테스트도 하나 써. 그다음 호출하는 곳을 하나씩 돌면서, 답을 모를 때 각자 이제 뭐라고 하는지 적어봐.
Hint
지금 데이터가 없을 때 그 함수가 뭘 돌려주는지부터 봐. 기본값(false, 0, 직전 값, 빈 목록)이 나온다면 그게 바로 근거 없는 "응"이야. 데이터에 타임스탬프는 대개 이미 붙어 있어. 빠진 건 일정 나이를 넘기면 답하길 거부하는 함수 하나야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.