지하철 칸별 혼잡도 미니앱
칸칸
요약
경로는 이미 안다. 남은 결정은 어느 칸에 타는가다. 출발역과 도착역만 넣으면 시간대별로 덜 붐비는 칸과 빠른환승 칸을 한 화면에 보여주는 토스 미니앱이다.
- 통과 · 출시
- 77건
- 456건
개요
경로는 이미 안다. 남은 결정은 어느 칸에 타는가다. 지도 앱은 최단 경로까지만 알려준다. 앉아 갈 수 있는 칸은 사용자가 매번 감으로 고른다. 매일 지하철을 타며 느낀 불편에서 시작했다.
칸칸은 토스 안에서 도는 미니앱(앱인토스)이다. 기획, 디자인, 개발, 심사 대응, 운영을 1인으로 맡았다. 심사를 통과해 출시했다.
문제
문제는 한 문장이다. 어느 칸에 타야 앉아 가는가.
- 자리는 매번 감으로 찾는다 – 매일 지하철을 타는 사람도 어디에 서야 앉는지는 감으로 정한다.
- 지도 앱은 칸을 보지 않는다 – 네이버·카카오 지도는 최단·최적 경로만 보여준다. 칸별 혼잡도는 없다.
- 타깃 – 출퇴근·등하교 때 자리를 원하지만 정보가 없어 오늘도 서서 가는 지하철 이용자.
핵심 결정
열차가 오기 전에 답을 준다. 입력 한 번, 결과 한 번
탑승 직전 몇 초 안에 판단하는 사용자에게 탭, 설정, 온보딩은 모두 늦다.
출발역과 도착역만 넣으면 바로 결과가 나오는 단일 화면 흐름이다. 역 검색은 초성으로도 된다.
환승까지 한 화면에서 끝낸다
앉아 가는 칸과 환승하기 좋은 칸이 다르면 사용자는 결국 다른 앱을 다시 연다.
구간마다 칸 혼잡도를 보여준다. 환승 구간에는 빠른환승 칸 번호를 함께 적었다.
숫자보다 색으로 읽게 한다
서서 기다리는 사람은 글자를 읽을 시간이 없다. 화면을 의심할 틈도 없다.
혼잡도를 초록 · 노랑 · 빨강으로 구분했다. 퍼센트는 보조로 뒀다. 추천 칸에는 표시를 붙였다.
공공데이터를 그대로 믿지 않는다
빠른환승 칸 정보를 기존 길찾기 앱과 역별로 맞춰 보자 누락과 오기가 나왔다. 잘못된 칸 번호는 곧 잘못된 행동이 된다.
국토교통부 데이터와 서울교통공사 API를 교차검증했다. 어긋난 77건은 손으로 확인해 고쳤다(실측).
틀린 실시간 값보다 검증된 정적 데이터를 택했다
라이브 API가 불안정한 구간이 있었다. 최신값을 보여주는 것보다 틀린 칸 번호를 내보내지 않는 쪽이 중요했다.
검증을 마친 빠른환승 456건을 앱에 정적 데이터로 번들했다(산출물). 실시간 호출은 여기에 쓰지 않는다.
과정
제약 속 설계
플랫폼, 인프라, API의 제약을 기획 초기에 파악했다. 제약마다 디자인과 개발 결정에 반영했다.
- 앱인토스 배포 정책 – 진입 즉시 팝업 금지, 외부 앱 유도 금지, 뒤로가기 차단 금지를 모두 지켰다. 뒤로가기 버튼을 없애고 네이티브 제스처에 맡겼다. 이 조건을 맞춘 뒤 심사를 통과했다.
- WebView 환경 – 앱인토스 SDK와 웹 프레임워크 가이드를 따랐다. 네이티브 코드 없이 WebView 안에서 끝냈다.
- API 무료 한도 – 혼잡도는 자주 바뀌지 않는다. 배치로 모아 Supabase에 캐싱하고 실시간 호출을 줄였다. 하루 1,000건 한도 안에서 운영한다. 지속 운영 비용은 0원이다.
디자인
토스 디자인 시스템(TDS) 컴포넌트를 그대로 썼다. 토스 앱 안에서 이질감 없이 보인다.
쓴 TDS 컴포넌트
- SearchBar – 역 검색 입력.
- Badge – 혼잡도 표시(색상 코딩).
- CTAButton – "칸 추천받기", 해야 할 행동 하나.
- NavigationBar – 브랜드 로고와 가운데 정렬 타이틀.
데이터
데이터 출처와 처리 방식
- 칸별 혼잡도 · 하차 비율 – 퍼즐 API(지오비전 SK), 30분 단위, 1~8호선. 배치로 수집해 Supabase에 캐싱하고 시간대별로 조회한다.
- 빠른환승 칸 번호 – 국토교통부 CSV와 서울교통공사 API. 두 출처를 교차검증해 오류 77건을 고치고 456건을 정적 데이터로 번들했다.
- 유지보수 – 혼잡도는 배치 스크립트로 Supabase를 갱신한다. 역 정보가 바뀌면 빠른환승 데이터를 다시 검증해 교체한다. API 한도는 Vercel 로그로 확인한다.
결과
출시한 것이 결과다. 심사를 통과해 토스 안에서 돌고 있다. 사용 지표는 아직 재지 않았다.
- 통과 · 출시
- 77건
- 0원
사람이 개입하는 지점
- 자동 – 공공데이터를 두 출처로 교차검증해 미리 캐시에 쌓는다.
- 사람 – 교차검증에서 어긋난 77건은 사람이 확인해 고쳤다. 라이브 API가 불안정한 구간은 검증된 456건을 정적 데이터로 번들했다. 틀린 값을 실시간으로 내보내지 않는다.
회고
맞았던 것
- 같은 데이터도 맥락이 바뀌면 다른 서비스가 된다. 혼잡도 데이터는 이미 있었다. 탑승 직전이라는 맥락에 맞추자 전혀 다른 가치가 됐다.
- 틀린 값을 내보내지 않기로 한 결정. 최신값 대신 검증된 456건을 택했다. 커버리지는 1~8호선에서 멈췄지만 화면에 잘못된 칸 번호는 올리지 않는다.
- 비용을 없앤 것도 디자인 결정이었다. 혼자 만든 서비스를 오래 열어 두려면 운영비가 0이어야 했다.
틀렸던 것
- 공공데이터가 서로 맞을 거라고 봤다. 두 출처를 맞춰 보니 77건이 어긋났다. 한 출처만 믿었다면 틀린 칸 번호가 그대로 나갔을 것이다.
- 실시간 값이 항상 낫다고 봤다. 라이브 API의 빠른환승 정보는 실제와 달랐다. 최신인지보다 맞는지가 먼저였다.
다음에 확인할 것
- 추천이 맞았는지 재지 않았다. 추천한 칸에서 실제로 앉았는지 확인할 방법이 아직 없다.
- 혼잡도는 30분 단위 시간대 값이다. 그날의 실제 혼잡도와 얼마나 벌어지는지 모른다.
- 호선 확대 – 1~8호선 밖에서도 같은 품질의 칸별 데이터가 나오는지 확인해야 한다. 9호선과 수도권 광역철도(GTX)가 다음 후보다.
- 알림 – 출발 전 푸시로 칸을 알려주는 방식이 탑승 직전 판단보다 나은지 아직 모른다.