Skip to content

Latest commit

 

History

History
226 lines (178 loc) · 30.6 KB

algorithm.md

File metadata and controls

226 lines (178 loc) · 30.6 KB

바로가 — 하이브리드 경로 알고리즘 (현재 구현)

지금 코드가 하이브리드 후보를 어떻게 만들고 견주는지만 적는다. 설계가 왜 이렇게 됐는지(실험·기각)는 method.md, 로드맵은 plan.md 에 있다. 알고리즘은 algo/search.py(후보 규칙·검증) · algo/reach.py(노선 한 번 더 타기 탐색) · algo/anchors.py(택시·승차 시간 근사·실시간 대기) · algo/lazy_pareto.py(순차 검증) · algo/compare.py(비교·제시) · algo/cli.py(실험 실행)에 있고, 데모 웹은 web/backend/app/hybrid.py 가 같은 함수로 같은 흐름을 돈다(§6).

  • 기준일: 2026-09-16
  • 구현 반영일: 2026-10-29 — U·V 를 만드는 탐색 하나, 택시 최대 2회, 순차 검증, 택시 상한·계수 근거, 기본 대기, 공개 요금 규칙과 지원 범위

1. 하나의 꼴

모든 하이브리드는 O →[택시 →](대중교통·도보*) u →(택시) v →(대중교통·도보*) D 꼴이다(algo/pareto_search.md). 후보는 앞부분(u 까지) 과 뒷부분(v 부터) 의 모든 짝이고, 유형을 따로 두지 않는다. U·V 는 탐색 하나로 만든다(§3) — 시간을 아는 곳(시작 표지)에서 정적 노선표의 노선을 최대 1번 더 타 닿는 정류소까지가 u 이고, 목적지 쪽에서 거꾸로 같은 것이 v 다. 앞부분이 택시로 시작하면 택시는 2회다 — 짝이 목적지면 택시 → 철도 → 택시가 된다.

쪽 출처 부분의 내용 시간 검증 때 더 드는 질의
앞부분 u origin 없음(출발지에서 바로 택시) 0 —
route 기준 경로를 타고 가다 내리는 모든 정류소(중간 정류소 포함)까지 응답 —
taxi_route 택시로 기준 경로의 승차 지점까지 가서 그 경로를 타고 가다 내리는 정류소까지 택시 추정 + 응답 자동차(O→승차 지점) 1
line 출발지에서 걸어서, 또는 route 정류소에서 (300 m 안으로 걸어) 노선을 1번 더 타 내리는 정류소까지 (응답 +) 추정 대중교통(O→u) 1
taxi_line 택시로 간 정류장, 또는 taxi_route 정류소에서 노선을 1번 더 타 내리는 정류소까지 추정 자동차(O→b) 1 · 대중교통(b→u) 1
뒷부분 v destination 없음(택시로 목적지까지) 0 —
route 기준 경로의 승차 지점부터 그 경로를 그대로 응답 —
line 노선을 1번 타 목적지까지 걷거나 route 승차 지점(300 m 안)으로 이어지는 그 노선의 승차 정류소부터 추정 (+ 응답) 대중교통(v→D) 1

택시 구간은 자동차 경로 1콜이다. 출발지 × 목적지(전 구간 택시)는 하이브리드가 아니라 뺀다. 기준선은 E — 전 구간 대중교통(카카오 대중교통 경로 1콜, 대안 최대 15개)이다.

2. 요청 한 번의 흐름

입력: 출발 O · 도착 D · 선호(시간 중시 / 균형 / 비용 중시 → 시간가치 VOT) · 기준 시각

1. 기준 경로 조회 → 정류장 위치 복원 · 정적 대기               대중교통 1
2. 첫 승차 대기를 실시간으로 바꾼 경로 사본                    실시간 도착 조회(정류장당 1)
     기준선 · 앞부분은 사본, 뒷부분은 원래 경로
3. 앞부분 · 뒷부분 만들기 → 모든 짝 → 거르기                    쿼터 0
4. 순차 검증 — 결과의 Pareto 를 보며 다음 질의, 고유 질의 30회  자동차·대중교통
5. 같은 잣대로 비교 → 5분 기준 → Pareto → 원/분 → 제시

후보 생성(3)은 정적 표와 기준 경로 응답만 쓰는 오프라인 단계이고, 확정 값은 온라인 검증(4)이 준다 — method.md §2.4 의 2단 구조다. 기준 시각을 따로 주면 '지금' 의 도착정보가 그 시각과 맞지 않으므로 2단계를 건너뛰고 정적 배차만 쓴다.

3. 후보 만들기 — algo/search.py

3.1 기준 경로의 앞부분 — route_heads

  • 정류장 시각: 경로 위 모든 정류장에 출발부터 닿는 시각을 매긴다 — 구간 시간을 정류장 사이 직선거리 비율로 나누고 첫 도보·대기를 앞에 더한다(anchors.route_points). 탄 정류장에서 곧바로 내리는 점은 뺀다 — 앞 하차 지점(또는 출발지)과 같은 곳이다
  • 구간 자르기: 중간 정류장에서 내리면 그 구간의 시간·거리·정류장 목록·경로선을 같은 비율로 자른다(anchors.prefix_steps)
  • 첫 대기를 실시간으로 바꾼 사본에서 자른다 — 출발지에서 걸어서 탄다
  • 같은 정류소가 여러 경로에 나오면 시간 + 요금 근사의 일반화 비용이 낮은 것 하나만 둔다

3.1.1 택시로 올라타는 앞부분 — route_taxi_heads

  • 기준 경로(정적 배차 사본)의 각 승차 지점 b 에 출발지에서 택시로 가서, 그 경로를 타고 가다 내리는 정류소마다 앞부분을 만든다. 시간 = 택시(호출 대기 + 추정 ETA + 연결 버퍼) + b 부터 그 정류소까지의 구간·대기, 요금 = 택시 요금 근사 + 구간 요금
  • 택시는 b 까지 원래 경로(도보·앞 구간·대기 전까지)보다 빨라야 하고, 걷기보다 빠르며 30분 안이어야 한다
  • 같은 정류소에는 이 출처의 앞부분을 하나만 둔다(일반화 비용이 낮은 승차 지점). 시간 하한은 택시 호출 대기·버퍼 + 구간 시간이다
  • 뒷부분에는 택시가 없어 택시는 최대 2회다

3.2 기준 경로의 뒷부분 — route_tails

  • 각 승차 구간의 승차 지점부터 경로 끝까지 그대로 탄다. 시간 = 구간 시간 + 승차 대기 + 끝 도보(직선 추정)
  • 원래 경로(정적 배차 대기)에서 자른다 — 택시로 정류장에 닿으므로 '걸어서 닿는다' 는 실시간 대기의 전제가 맞지 않는다
  • 중간 정류장에서 다시 타지는 않는다 — 같은 노선에 다시 타면 줄인 시간이 배차간격을 넘어야만 이득이다(R28)

3.3 노선 한 번 더 — search._extend, reach

U·V 의 나머지는 시작 표지에서 정적 노선표의 노선을 최대 1번(LOCAL_RIDES) 더 타는 노선 단위 탐색(RAPTOR 류) 하나로 만든다.

  • 시작 표지(정방향): 출발지에서 걸어서 닿는 정류소(400 m, 비면 800 m) · route 앞부분의 정류소(응답 시간)와 거기서 300 m 안으로 걸어 닿는 정류소. 택시로 시작하는 표지는 따로 돈다 — 출발지에서 택시 30분(직선) 안 · 약 150 m 이상의 정류소(택시 추정 시간)와 taxi_route 정류소
  • 시작 표지(역방향): 목적지까지 걸어서 닿는 정류소 · route 뒷부분의 승차 지점(남은 시간은 응답)과 거기로 300 m 안에서 걸어오는 정류소
  • 노선: 버스 노선 순서표(수요응답형 제외, 그 시각에 운행하는 노선만) · 도시철도 덩어리 양방향. 경기 순서표의 상·하행 한 순번열도 그대로 훑는다 — 반대 방향 정류소는 한 바퀴 돈 긴 시간이 붙어 시간 상한에 걸린다
  • 시간: 대기(그 노선 배차의 절반, 모르면 900초) + 차내(순서표 직선 거리 ÷ 버스 유형별 · 도시철도 노선군별 표정속도). 내린 뒤 300 m 안 정류소로 한 번 걷는다
  • 시간 상한: (닿은 시각 + 남은 길의 하한)이 기준선 − 5분 − 호출 대기를 넘는 정류소는 펼치지 않는다 — 그 뒤로는 어떤 짝도 5분을 줄일 수 없다. 남은 길의 하한은 남은 직선거리를 택시(그 시각의 추정)와 전철(34 km/h + 역 연결 2분) 중 빠른 쪽으로 간 시간이다(search.rest_s) — 정방향은 목적지까지, 역방향은 출발지에서. 목적지에서 멀어지는 쪽으로 뻗는 것이 여기서 잘린다
  • 정류소마다 표지 하나(가장 빠른 것). 택시로 시작한 표지는 걸어서 시작한 것보다 빠를 때만 남긴다
  • 노선을 더 탄 부분의 대중교통 요금은 구간들을 이어 fares.estimate 로 추정한다(못 하면 노선 유형별 대체값)

3.4 짝 짓기와 거르기 — pair

  • 앞부분 × 뒷부분 전부. 다음 짝은 만들지 않는다
    • 택시 직선 약 150 m 미만(TAXI_MIN_M, 택시가 걷기보다 느린 거리) · 추정 택시 시간 30분 초과(T_MAX_S)
    • 택시 ETA 가 0 이어도 기준선보다 5분 빨라질 수 없는 짝 — 앞부분 + 호출 대기(+ 뒤에 탈 차가 있으면 버퍼) + 뒷부분으로 잰다
  • 추정 시간 = 앞부분 + 택시 구간(호출 대기 + 추정 ETA + 뒤에 탈 차가 있으면 연결 버퍼) + 뒷부분
  • 추정 요금 = 택시 요금 근사(택시를 타는 곳의 요금 유형, 그 시각의 심야 할증) + 앞부분 요금 + 뒷부분 요금
  • 시간 하한 = 기준 경로에서 자른 부분 + 호출 대기(+ 버퍼). 노선을 더 탄 부분은 다시 부르므로 0 으로 둔다 — 순차 검증이 확인을 건너뛸 근거다
  • 차례(추정 Pareto 띠): 다른 후보가 추정으로 5분 넘게 빠르면서 2,000원 이상 싼 후보는 띠 밖(차례 1), 나머지는 띠 안(차례 0)이다. 버리지 않고 순차 검증이 띠 안을 먼저 다룬다 — 장거리에서 수만 개인 후보가 수십 ~ 백여 개로 준다

4. 공통 규칙

항목 규칙 코드
택시 시간 근사 370초 × (직선 km)^0.70 × 시간대 배수 — 평일 출퇴근(7–10시 · 17–19시 30분) 1.20, 심야(22–6시) 0.84, 그 밖 1.0. 멀리 갈수록 1 km 당 시간이 준다. 택시를 타는 요청 시각으로 고른다 (직선 0.3–12 km 450표본) taxi_time_s, taxi_band
택시 요금 근사 기본요금 4,800원 + 기본거리 초과분 × 거리단가. 기본거리·단가는 시군 유형(표준 1.6 km · 가 1.8 km · 나 2.0 km)별, 심야(서울 22–04시 ×1.2 · 경기 23–04시 ×1.3)·시계외(×1.2, 공동사업구역 제외) 할증. 후보 순위용이고 확정 요금은 자동차 경로 응답값 taxi_fare
택시 구간에 잡는 시간 호출 대기 90초 + 이동 시간 + (내려서 차를 탈 때만) 연결 버퍼 60초 — 후보 점수와 검증에 같은 값을 쓴다 taxi_plan_s, taxi_connect_s
택시 최소 기준 택시 구간이 걷기보다 빠를 것(직선 약 150 m 이상 — 이 거리에서 택시 시간(호출 대기 90초 + 평시 이동 + 연결 버퍼 60초)이 도보(우회 1.39, 0.83 m/s)와 같아진다), 기준선보다 5분 이상 빠를 것. 도보 대체 후보는 만들지 않는다 TAXI_MIN_M, saves_enough
첫 승차 대기 실시간 도착에서 정류장까지 걷는 시간을 뺀다 — 걷는 동안 지나가는 차는 탈 수 없다. 버스는 노선 id, 도시철도는 행선의 '○○방면' 으로 탈 차를 가린다. 알려진 차를 모두 놓치면 배차간격으로 다음 차를 잇는다 with_realtime_first_wait
남은 역·정류소 수 예측 초가 없으면 남은 수를 시간으로 바꾼다 — 도시철도는 그 열차가 지나올 역 사이 실제 거리 ÷ 32 km/h(순서표로 못 읽으면 역당 134초), 버스는 정류소당 72초. 1역 전 열차는 남은 역 문구 없이 도착 코드(전역 출발·진입·도착)로 와서 1로 읽는다. 수신 경과를 뺀다 arrival, subway_upstream_s, realtime.subway
첫 대기 출처 realtime(도착 예측 초) · realtime_stops(남은 수 추정) · realtime+headway(놓쳐서 배차로 이음) — 앞의 것만 '실시간' realtime_first_wait
뒤 구간 대기 정적 배차의 절반. 버스는 노선·요일 유형(최소·최대의 중간값, 그 요일 값이 없으면 평일), 도시철도는 역·시간대(앞뒤 1시간을 합친 3시간 창으로 센 배차, 방향 평균). 함께 탈 수 있는 노선은 빈도 합. 배차 미확인 구간은 기본 대기로 합산한다 app.wait, app.headwaydb
배차를 모르는 대기 승차 구간마다 900초(버스 평일 배차 중앙 30분의 절반). 후보 점수·경로 합계·비교 시간에 같은 값을 쓰며 실제 0초는 보존한다. 화면에 기본값 적용을 표시한다 DEFAULT_WAIT_S, WAIT_UNKNOWN_S, apply_wait_defaults, reconstruct
도보 직선거리 × 우회율 1.39 ÷ 0.83 m/s (블랙박스 자신의 도보에서 역산). 앞·뒤 부분의 첫·끝 도보도 이 직선 추정이다 walk_s
대중교통 요금 근사 공개 성인 교통카드 기본·거리·환승 규칙. 경기 일반 1,650 · 직행 3,200 · 일반 전철 1,550원 등. 택시를 사이에 둔 앞뒤 구간은 다음 승차 시각 기준 환승 인정 시간(평시 30분 · 21–07시 60분), 최대 5회 승차, 동일 버스 노선 재승차를 판정하고 끊기면 새로 계산한다. 미지원 정보는 대체값(앞·뒤 부분 요금의 합)과 사유 표시 fares.estimate; 요금 규칙
시간 계산 온전한 경로는 API 총시간 + 추정 대기. 앞·뒤를 잘라 도보를 분리해야 할 때는 자체 추정하며, API 총시간이 없을 때도 자체 추정으로 보완한다 compare.reconstruct
후보 점수 일반화 비용 = 시간 + (택시 + 대중교통 요금) ÷ VOT — 요금을 넣어야 상위 후보가 택시 도달 경계 한 점으로 쏠리지 않는다 search.pair

5. 검증과 제시

5.1 검증 — search.verify

부분 검증 호출 확정 시간·구간
택시 자동차(u→v) 호출 대기 + 응답 ETA + (뒷부분에 승차 구간이 있으면) 버퍼
첫 택시(택시 → 기준 경로 앞부분) 자동차(O→승차 지점) 호출 대기 + 응답 ETA + 버퍼, 요금은 응답의 택시 요금·통행료
기준 경로 부분 없음 후보를 만들 때 자른 구간·시간 그대로
노선을 더 탄 부분 대중교통(O→u · b→u · v→D) 대안을 compare.reconstruct 로 같은 잣대에 맞춘 뒤 일반화 비용이 가장 작은 것
  • 시간 = 앞부분 + 택시 + 뒷부분. 다시 받은 경로의 첫 대기는 정적 배차다
  • 요금 = 택시·통행료(응답) + 앞뒤 구간을 택시로 이어 붙여 다시 계산한 대중교통 요금. 환승 인정 여부는 응답의 ETA 로 판정한다
  • 자동차 경로·택시 요금이 없거나 다시 부른 대중교통 경로가 없으면 그 후보는 결과가 없다

5.2 순차 검증 — search.run, lazy_pareto.LazyParetoSearch

  • 첫 질의는 추정 일반화 비용이 가장 낮은 후보로 시작하고, 이후에는 확인한 결과(요금이 알려진 기준 경로 포함)의 시간·요금 Pareto 를 보며 다음 후보를 고른다 — 추천 점수를 낮출 것으로 추정되는 후보를 남은 질의 수당 개선액이 큰 순으로 먼저, 없으면 Pareto 가 아직 덮지 못한 절충안의 공백이 큰 순으로
  • 순서는 아직 확인하지 않은 후보 중 가장 앞 차례(§3.4) 안에서만 매긴다 — 띠 안 후보가 모두 끝나고 예산이 남으면 띠 밖으로 넘어간다
  • 같은 질의를 쓰는 후보끼리 응답을 나눠 쓴다. 확인한 경로가 후보의 시간 하한·요금 0 을 지배하면 그 후보는 확인하지 않는다
  • 기준선보다 5분 이상 빠르지 않은 결과는 Pareto 에 넣지 않는다
  • 예산: 요청당 카카오 고유 질의 30회(자동차 + 대중교통) — 기준 경로를 새로 부르면 대중교통 1콜이 더 든다. 실패한 질의도 센다
  • 묶음: 질의를 3개씩 함께 보낸다 — 첫 질의는 위 순서의 1순위 후보, 나머지는 다음 순위 후보들의 아직 안 낸 질의다

5.3 제시 — compare

  • 기준선: CLI 는 기준 경로 대안 중 일반화 비용이 가장 작은 것, 데모 웹은 가장 빠른 것(§6)
  1. 시간 절약 기준: 결과는 기준선보다 5분 이상 빠르지 않으면 뺀다(saves_enough)
  2. Pareto: 기준선을 포함해 (시간, 요금) 비지배 결과를 표시한다(pareto)
  3. 원/분: 추가 요금 ÷ 절약 시간. 고른 선호의 VOT 보다 비싸면 "비추천" 이다(saving_per_min)

6. 데모 웹 — web/backend/app/hybrid.py

후보 생성·검증·순차 탐색은 §3–§5 와 같은 algo.search 를 쓴다. 웹에서 정한 것은 다음과 같다.

  • 선호: 시간 중시(VOT 456원/분)로 고정한다
  • 같은 카드 정리: 시간(분)·요금이 같고 택시 양끝이 200 m 안인 결과는 하나만 보인다 — 정류소 DB 와 경로 응답이 같은 역을 이름·좌표를 조금 달리 준다
  • 경로 표시: 대중교통 구간들(transit.steps)의 taxi_at 자리에 택시 구간을 끼운다. 택시를 타고 내리는 곳은 from·to(출발지·목적지면 없음)다. 첫 택시(first_taxi)가 있으면 출발지에서 first_taxi.to 까지 맨 앞에 하나 더 끼우고 지도에도 함께 그린다
  • 최적 정렬: 대중교통·하이브리드를 요금 + 소요시간(분) × VOT의 오름차순으로 정렬한다. 대중교통 시간은 reconstruct의 도보·대기 포함 시간이며, 하이브리드 범위 요금은 최댓값을 쓴다.
  • 대표 시간·정렬: 일반 대중교통은 API 총시간에 추정 대기를 더한 값을 크게 표시하고, 툴팁에 API 원래 시간과 추정 대기를 표시한다. 최소 시간순과 다른 정렬의 시간 동률 기준도 이 값을 사용한다. ‘최적’이 첫 번째 선택지이자 기본 정렬이다. 거리·요금·환승·도보순의 주 기준은 유지한다.
  • VOT 슬라이더·추천 배지: ‘최적’에서 0~1,500원/분(50원 단위, 초기 500)을 선택하면 현재 조회된 카드의 점수와 추천 배지를 즉시 재계산한다. 일반 대중교통·하이브리드·택시 전체에서 일반화 비용이 가장 낮은 하나에만 추천을 표시하며, 나머지 배지는 생략한다. 동률이면 시간, 요금, 원래 순서로 결정한다. 탭 필터와 무관하게 전체 결과 기준이다. 검색 API를 호출하지 않으며 후보 탐색은 456원/분 기준을 사용한다. 0은 요금만 비교한다.
  • 기준선: 같은 잣대(reconstruct)로 잰 가장 빠른 대중교통 경로다. 시간이 같으면 요금이 싼 쪽이다
  • 기준 경로 재사용: 화면이 [경로 검색] 결과를 넘기면 대중교통을 다시 부르지 않는다. 첫 승차 대기를 배차 추정으로 되돌린 사본(static_copy)을 받아 로컬 배차 대기를 사용한다
  • 첫 승차 대기: 일반 검색과 하이브리드 검색 모두 다른 승차와 동일하게 로컬 배차간격의 절반을 쓴다. 함께 탈 수 있는 노선이 여럿이면 빈도 = Σ(60 / 배차분), 유효 배차분 = 60 / 빈도로 합산한 뒤 절반을 적용한다. 실시간 도착 API는 카드의 갱신 버튼을 눌렀을 때만 호출하며, 기존 갱신 동작은 유지한다. 버스 도착 정보는 동일 정류장·노선 ID별로 서울→경기→인천 순서의 유효한 예측을 선택한다. 첫 차와 다음 차는 원천을 섞지 않는다. 모두 예측이 없으면 같은 우선순위로 제공된 상태를 표시한다.
  • 제시: 5분 기준을 적용한 뒤 모든 결과를 일반화 비용 순으로 돌려주고, Pareto 여부와 추천(원/분이 VOT 이하)을 표시한다
  • 추정 표시: 배차 미확인 구간은 기본 대기 15분 적용을, 요금은 fare_info의 추정·대체 여부와 사유를 표시한다
  • 카드 비교: 기준이 되는 최소시간 대중교통 카드 하나를 제외한 모든 카드에 같은 기준선 대비 시간·요금 차이를 표시한다. 시간과 요금이 모두 다르면 요금 차이의 절댓값 ÷ 시간 차이(분)의 절댓값을 계산해 ‘단축 1분당 ○원 추가’ 또는 ‘추가 1분당 ○원 절약’ 등으로 방향을 표시한다. 시간이 같으면 분당 값을 계산하지 않는다. 택시 요금은 통행료 포함, 시간은 자동차 API 소요 시간이며, 대중교통은 도보·대기 포함 시간이다.
  • 노선별 요금 범위: 로컬 요금 계산에서 승차 가능한 버스가 여럿이면 노선 선택 조합별 전체 대중교통 요금을 계산한다. 모두 계산 가능하면 최소~최대에 택시·통행료를 더해 표시하고, 같으면 단일 금액으로 표시한다. 후보 점수·정렬·추천·추가 요금은 최댓값 기준이다. 미지원 조합이 있으면 기존 대체 방식을 유지하며, 카드에는 로컬 계산·대체값·경로 API 제공값 중 계산 방식을 표시한다.

개발자 모드의 탐색 과정

웹의 개발자 → 개발자 모드를 켜고 하이브리드를 탐색하면 /api/hybrid?debug=true가 요청 동안의 후보 처리 기록을 함께 반환한다. 일반 모드의 선택·검증 로직과 API 호출 수는 그대로이며, 기록을 위한 외부 조회나 파일 저장은 없다.

  1. 후보 생성: 앞부분 × 뒷부분의 모든 짝(§3). 후보마다 출처(출발지 / 기준 경로 / 노선 더 탐 / 택시 → … → …)를 표시한다. 이 단계에서만 출발 쪽 · 도착 쪽과 버스/지하철을 골라 걸어서 닿는 정류소를 찾은 도보 반경을 지도에 표시한다. 종류별로 실제 넓혀 쓴 마지막 반경이다.
  2. 검증 순서·예산: 질의 30회 안에 차례가 오지 않은 후보와, 이미 확인한 경로가 하한을 지배해 확인하지 않은 후보를 기록한다.
  3. 경로 검증: 실제 경로 검증의 성공·연결 불가·API 실패와, 같은 카드로 정리된 결과를 구분한다.
  4. 시간 절약 검사: 기준선보다 5분 이상 빠르지 않은 결과를 제외한다.
  5. 최종 결과와 Pareto 표시를 보여 준다. 웹에서는 Pareto가 탈락 조건이 아니다.

후보를 선택하면 기존 카드 경로와 다른 후보 연결선을 지우고 선택 후보의 전체 이동 순서를 지도에 표시한다. 검증 전에는 생성에 사용한 승하차 지점·기존 구간 좌표, 검증 후에는 확보한 검증 구간 좌표를 사용한다. 택시는 직선으로 연결하며 추가 경로 API 조회는 하지 않는다. 후보 선택을 해제하면 단계별 후보 연결선으로 돌아가고, 일반 카드를 선택하면 개발자 경로 표시는 숨긴다.

단계별 지도는 택시 연결 구간의 직선 모식도다. 최종 후보는 원래 경로 카드·지도로 열 수 있다. 현재 단계에서 살아남은 후보를 약 5행 높이의 스크롤 표로 비교하며, 검증 전에는 추정 점수, 검증 후에는 실제 검증 시간·요금의 점수를 쓴다. 표 위의 시간·요금 그래프는 같은 후보를 점으로 찍는다(검증 전 추정값, 검증 후 실제값). 유지 후보와 기준 대중교통의 Pareto 경계를 계단선으로 긋고, 점수(요금 + 분 × 456)가 가장 낮은 유지 경로를 지나는 기울기 −456원/분의 등점수선을 함께 긋고, 점을 누르면 그 후보를 선택한다. 탐색 기록은 456원/분 기준으로 고정되고 사용자 VOT 슬라이더는 기존처럼 최종 카드 정렬·추천만 바꾼다.

후보가 수만 개일 수 있어 표·지도에는 확인을 시도한 후보 전부와 추정 점수 상위 500개만 보낸다. 단계별 개수에는 전체 후보가 포함된다. 노선 한 번 더 탐색에서 운행시간·시간 상한으로 펼치지 않은 정류소와 짝 짓기에서 거른 짝은 개별 기록 없이 진단 집계(generation)만 보여 준다. 모든 검증이 API 오류로 끝나도 개발자 모드에서는 실패 기록을 볼 수 있도록 빈 결과와 기록을 반환한다.

7. 튜닝 상수

상수 값 근거
TAXI_K_S · TAXI_P · TAXI_BAND 370초 · 0.70 · 출퇴근 1.20 / 평시 1.0 / 심야 0.84 직선 0.3–1 · 1–6 · 6–12 km 자동차 경로 각 150표본의 구간 중앙값에 맞춘 곡선(method.md E31), 배수는 시간대별 편향(E23)을 4.2 km 에서 같은 비율로
BAND_TIME_S · BAND_FARE 5분 · 2,000원 짝 짓기 뒤 추정 Pareto 띠 — 다른 후보가 이만큼 넘게 빠르고 이만큼 이상 싼 후보는 뒤 차례로 미룬다(§3.4). 8 OD 에서 띠 없음과 좋은 경로 수가 같았고, 3분 · 1,000원은 검증할 후보가 모자랐다(method.md E33)
REST_RAIL_KMH · REST_RAIL_CONNECT_S 34 km/h · 120초 남은 길 하한의 전철 쪽 — 도시철도 전체 표정속도 중앙(E29) · 역 연결을 낮게 잡은 값(실측 몇 건 4–5분)
DETOUR 1.42 서울·경기 정류소 쌍 150개(직선 1–6 km)의 자동차 API 도로거리 ÷ 직선거리 중앙값. 공식 고정값이나 실제 택시 주행 실측값은 아니다
TAXI_PICKUP_S · TAXI_BUFFER_S 90초 · 60초 합의한 상수 — 실측 아님 (method.md R27)
TAXI_MIN_M · TAXI_MIN_SAVING_S 약 150 m · 300초 택시가 걷기보다 빨라지는 직선거리 · 합의한 기준
T_MAX_S 1,800초(30분) 후보 탐색용 택시 이동 시간 추정 상한. 호출 대기·연결 버퍼는 별도이며 실제 API 이동 시간의 상한을 보장하지 않는다
WALK_RADIUS_M 400 · 800 m 출발지·목적지에서 걸어서 닿는 정류소를 찾는 반경, 비면 넓힌다. 그보다 멀면 걷지 않고 택시로 간다
LOCAL_RIDES · TRANSFER_M 1회 · 300 m 시작 표지에서 노선을 더 타는 횟수 · 갈아탈 때 걷는 거리 — 환승이 늘면 추정 오차가 커진다(method.md E29)
BUS_KMH · RAIL_KMH_BY_GROUP 버스 마을·순환 10 · 지선 12 · 일반·간선 13 · 심야 15 · 광역·직행 17 · 시외·공항 21 km/h, 도시철도 GTX-A 90 · 1호선 46 · 경의중앙선 44 · 신분당선·8호선 38 · 수인분당선 36 · 3·4호선 34 · 7호선 33 · 2호선 31 · 5·6·9호선 30 · 그 밖 34 km/h 정차 포함 표정속도. 순서표 직선 거리 기준으로 카카오 차내 시간에 맞춘 중앙값(버스 E24, 도시철도 E29 — 표본 3개 미만인 노선군은 전체 중앙). 실시간 첫 대기의 철도 구간 추정(RAIL_KMH)은 32 km/h 그대로다
DEFAULT_WAIT_S · WAIT_UNKNOWN_S 900초 배차를 모르는 승차 구간의 기본 대기(후보 점수·경로 합계·비교 공통) — 버스 평일 배차 중앙 30분의 절반
TRANSFER_WINDOW_S · …_NIGHT_S 30분 · 60분(21–07시) 수도권 통합환승 인정 시간
RAIL_S_PER_STATION · BUS_S_PER_STOP 134초 · 72초 순서표로 거리를 못 읽을 때의 대체값 (method.md E19)
SAME_CARD_M(웹) 200 m 같은 카드로 볼 택시 끝 거리
VOT 시간 456 · 균형 300 · 비용 150 원/분 시간 중시는 업무통행 시간가치 환산값, 균형·비용 중시는 잠정값 (plan.md §12)
SEARCH_BUDGET · SEARCH_PARALLEL(웹, CLI --budget · --parallel) 30회 · 3 2026-10-10 동일 예산 비교의 OD당 16–32회 상단 · 하나씩 보내면 응답 4–9초, 3개씩이면 2.6–5.5초(2026-10-27, 3 OD)

시간 중시 VOT의 근거는 사용자 제공 업무통행 시간가치 22,502원/시(2017년 가격)에 2017~2025년 누적 소비자물가 보정계수 1.2165를 적용한 값이다. 22,502 × 1.2165 ÷ 60 = 456.22805원/분을 반올림하여 456원/분을 사용한다.

8. 아직 없거나 근사인 것

  • U·V 범위: 시작 표지에서 노선을 1번 더 타는 데까지만 U·V 에 넣는다 — 두 번 더 갈아타야 닿는 정류소는 빠진다(늘리면 추정 오차가 커진다, method.md E29)
  • 하나의 규칙의 실측 평가: 이전 서비스와는 11 OD 한 시각만 견줬다(method.md E21) — 시간대·OD 를 늘린 평가가 남았다
  • VOT 균형·비용 중시는 잠정값이다
  • 호출 대기·버퍼는 합의한 상수다. 택시 시간대 배수는 시간대마다 하루 한 번의 표본이고, 주말·공휴일 출퇴근은 따로 재지 않았다(공휴일은 평일로 본다)
  • 노선을 더 탄 부분의 차내 시간은 노선 유형·노선군별 속도 하나다 — 짧은 버스 구간(2 km 미만)은 더 느리고 긴 구간은 더 빠른 경향이 남고(method.md E24), 도시철도 급행은 따로 보지 않는다. 대기는 그 노선 하나의 배차로 잡아, 같은 정류소에서 함께 기다릴 다른 노선의 빈도를 더하지 않는다
  • 택시 계수의 거리 범위: 30분 상한의 직선 반경은 평시 약 9.6 km(출퇴근 7.4 km, 심야 12.3 km)다. 곡선은 직선 0.3–12 km 의 평시 한 시각 표본으로 맞췄다 — 시간대 배수는 곡선 모양이 시간대마다 같다고 본 것이다
  • 대기를 모르는 경로: 구간당 900초로 비교 기준은 맞췄지만, 실제 배차를 반영한 값은 아니다
  • 도시철도 첫·막차 시간대 배차는 3시간 창이 운행 없는 시간을 품어 넓게 나온다. 기대 대기의 상한도 없다
  • 뒷부분과 노선을 더 탄 부분의 첫 대기는 정적 배차다 — 택시로 정류장에 닿는 시각 기준의 실시간 대기가 없다
  • 대중교통 요금은 운임 최단거리표가 아닌 구간 거리로 추정한다. 별도운임·관할·거리 미확인 시 원래 경로 요금 등으로 대체하며 사유를 표시한다. 지원 범위와 전제는 요금 규칙
  • 실시간 근사: 버스 남은 정류소 수(정류소당 72초)는 실측하지 않았고, 전역 출발 열차는 한 구간 전으로 봐 최대 한 구간만큼 늦게 본다. 급행 열차를 가르는 규칙과 오래된 실시간 값을 버리는 규칙은 없다
  • 순차 검증의 CPU 시간은 후보 수에 비례한다 — 장거리 동천 → 서울역(후보 약 8.5만 개, 택시로 시작해 노선을 더 타는 앞부분이 많다)에서 검증 탐색이 약 6초, 그 밖 8 OD 는 1.5–2.6초다(method.md E30)

대상 지역은 서울·인천·경기다. 인천 정류장·철도역과 기존 서울·경기 버스 노선의 인천 구간을 포함한다. 인천 버스 실시간 도착·노선명 조회 코드는 기존 공공데이터 클라이언트에 연결되어 있다. 동일 노선은 서울→경기→인천 순으로 유효 예측을 선택한다. 인천 자체 노선 수집·순서표 병합 코드도 있으나 실제 API의 HTTP_ERROR(04)로 정상 데이터는 아직 확보하지 못했다. 인천 버스 하이브리드 후보 탐색은 보유 순서표에 한정되며, 미수집 배차는 기존 기본 대기 추정을 사용한다. 상세: 인천 확장 현황.