/board 를 A18 에 맞추면
백엔드는 무엇이 달라지나
새 예보 모델 A18(차량 상태 조건부 좌석 분포)을 채택하면
/board 는 선택 필드가 붙는 것이 아니라 다시 만들어진다.
변경을 스키마 · 도메인 · 백엔드 API 셋으로 갈라 적었다.
확정 계약은 여전히 v3 이고 이 문서는 검토 요청안이다.
2026-08-19 작성 · v3 확정 계약(2026-08-17)과 객체 구조 v8 기준 · v4-fe 확정본(2.0.0)의 /board 계약에 맞췄다
한 장 요약 30초
규모부터 적는다. 표와 열의 정본은 파이프라인 계약 v4 에 있다.
| 재는 것 | 값 | 따라붙는 말 |
|---|---|---|
| 신설 표 | 2개 | vehicle_stop_prediction 열 14 · demand_profile 열 11 |
| 기존 표에 붙는 열 | +6 | route_reference_version +4 · vehicle_observation +1 · location_poll +1 |
| 계약에서 빠지는 표 | 2개 | forecast_publication 열 16 · stop_prediction 열 5. 발행 계층이 통째로 없어진다 |
| 남는 표의 열 삭제 · 타입 변경 · NOT NULL 변경 | 0건 | 더하는 여섯 열이 전부 NULL 허용이라 백필도 0건이다 |
| 인덱스 | +3 | 직전 도착 차량 · 같은 여정 · 예보까지 끝난 최신 poll |
| 발행 계층 | 제거 | 두 표와 봉인·승격·시간대 경계 재발행이 함께 빠진다. 쓰기만 멈추고 표를 남기는 안이 앞서 있었고 그 서술은 걷어냈다. v3 의 기록은 v3 에 그대로 있다 |
| 후보 모델 7개 · 평가 파이프라인 | 변경 없음 | 이 문서는 서빙 설계지 평가 설계가 아니다. 일곱 모델은 후보 모델 현황판과 일일 평가 API 에 기록으로 남는다. 일일 채점 자체는 같은 2026-08-19 검토에서 그날 평가일까지로 끊었고, 그것은 평가 규약(evaluation-v3) 쪽 변경이라 이 설계와 무관하다 — 0절 |
/vehicles·/routes | 변경 없음 | 스키마가 한 필드도 안 바뀐다. 확정본이 손댄 것은 설명과 예시뿐이다 |
| 상류 수집 | poll 그대로 | A18 은 이미 받는 필드만 쓴다. 노선정보 호출만 노선당 하루 1회 는다 |
| 패키지 경계 | 셋 그대로 | collector · processor · api. 각 패키지 안에 칸이 는다 |
| 작업량 | 약 24~41 사람·일 | 서버 한 사람 기준. 구현 관점으로 항목을 갈라 세면 23.5~36.5일이고 4절에 항목별로 있다. 계약 관점 27~41 은 파이프라인 계약이 따로 센다. 항목 묶음이 다를 뿐 겹치는 일을 두 번 세지 않았다 — 계획에는 넉넉한 쪽을 써라 |
| 되돌릴 수 있나 | 없다 | 되돌릴 발행 경로가 이 설계에 없고 /board 응답 구조가 함께 바뀐다. 안전장치는 아직 정하지 않았다 — 5절 ② |
팀이 이미 만든 것 중 무엇이 그대로 사는지를 0절에 표로 적었다. 거기부터 읽으면 범위가 좁아진다.
| 달라지는 것 | 지금 (v3) | A18 채택 시 |
|---|---|---|
/board 가 답하는 것 | 이 정류장·이 시간대를 지나는 버스라면 | 지금 오는 이 버스가 이 정류장에 도착할 때 |
| 원천 | 봉인된 forecast_publication | 차량 관측에서 파생된 차량별 예보 |
| 가용성 | 수집이 끊겨도 유효하다 | 최근 관측이 없으면 NO_RECENT_OBSERVATION 503 |
| 정원 상수 | 47석 고정 | 그 차량이 보여 준 최대 잔여석 그대로 |
정원이 A18 에만 걸리는 이유는 기존 후보 모델의 입력이 노선·정류장 순번·시간대 셋뿐이어서다. A18 은 현재 잔여석 ÷ 정원을 점유율로, 좌석 감소 ÷ 정원을 순수요로 쓰므로 정원이 틀리면 두 값이 함께 어긋난다. v6 말뭉치 60,158행에서 정원은 열 갈래로 관측된다 — 44석 40,612 · 45석 7,448 · 40석 6,055 · 68석 5,757 · 49석 192 · 67석 54 · 42석 16 · 41석 12 · 36석 11 · 43석 1이고 최빈값은 44다. 종전 47 은 어느 근거에도 없던 수였다. 모델이 쓰는 정원은 형식승인 좌석수와 다른 양이라 합치지 않고, DB 열이 아니라 번들의 특징 계약이 소유한다.
0. 하지 않아도 되는 것 범위를 먼저 좁힌다
/board 를 다시 만든다고 8/17 확정본이 뒤집히지 않는다.
그대로 쓰이는 것
| 팀이 이미 만든 것 | 어떻게 되나 | |
|---|---|---|
| collector 객체 20개 | 유지 + 판정 1 | GBIS 호출·정규화·호출 장부·노선 판본이 그대로다. 적재 시점 회차 키 판정 하나와 첫차·막차 갱신이 붙는다 |
call_ledger 쿼터 4단계 | 전부 유지 | 예약 차감 규칙과 resultCode=4 처리가 그대로다. 첫차·막차 호출은 같은 장부에서 예약하는데 하루 2회라 한도 10,000회 옆에서 무시할 수 있다 |
| 노선 판본 유효기간 관리 | 전부 유지 | valid_from/valid_to 로 관리하는 방식이 오히려 demand_profile 의 키가 된다 |
| api 실시간 차량 객체 8개 | 스키마 그대로 | /vehicles 는 조회 구조도 응답 필드도 그대로다 |
/routes 계약 | 스키마 그대로 | 필드는 이 변경과 무관하다. 다만 RouteStatus 의 뜻이 "봉인 발행본이 있다"에서 "활성 계수 묶음이 있다"로 옮겨가 판정 근거를 읽는 자리가 번들 쪽으로 바뀐다 |
| 후보 모델 7개 | 전부 유지 | 평가 파이프라인은 이 설계와 별개다 — 일곱 모델은 외부 ML 환경이 채점해 왔고 그 기록이 후보 모델 현황판과 일일 평가 API 에 남는다. 일일 채점은 2026-08-19 평가일까지이며 그 뒤로는 기록만 남는다. 어느 쪽이든 서빙 경로에는 들어오지 않는다 |
| 학습 라벨 조건 | 유지 + 재사용 | A18 의 여정 판정이 이 정의를 그대로 쓴다. 새 규칙을 만들지 않는다 |
supportedScopeDigest | 유지 + 재사용 | 지원 밖을 절편만으로 점수화하지 않으려고 둔 필드가 지평 축을 하나 더 받는다. 열은 그대로다 |
| 만석 확률 저장 · 빈자리 확률 응답 | 규약 유지 | p_full 을 저장하고 서비스 계층에서 뒤집으며 반올림하지 않는 규약이 A18 에도 그대로다 |
노선 ID 넷 분리 · RouteStatus 2값 · 패키지 셋 | 전부 유지 | 공개 ID·공급자 ID·표시명·모델 노선 키 구분이 A18 에서도 같다. 상태를 늘리지 않고 새 패키지도 만들지 않는다 |
실제로 움직이는 것
/board 응답 구조가 이번 안에서 가장 큰 변경이다 — 정류장당 값 하나에서 정류장별 도착 예정
차량 목록으로 바뀌고, 원천이 발행본에서 관측으로 옮겨가며, api 예보 객체 7개의 안이 다시 쓰인다(3절).
표 둘이 신설되고 발행 표 둘이 빠지며, 기존 표에 열 여섯이 붙고 정원 47석 고정이 교정된다(1절).
processor 가 하는 일이 시간대 경계 발행에서 관측 적재마다 도는 추론으로 바뀌고, 예보 저장량이 약 2,000배가 되어
보존과 파티션이 설계 항목으로 올라온다(2절).
v8 의 "서버는 통계를 집계하지 않는다"는 문장도 개정된다 — demand_profile 은 채점 대상이 아니라
모델이 읽는 입력이라 성격이 다르지만, 서버가 경험 통계를 집계하는 것은 맞다.
v2 초안이 버렸던 demand_profile 만 모습을 바꿔 돌아온다 —
시간 축을 열에 박지 않고 셀 식별자 하나로 두어 셀 세분화 실험이 마이그레이션을 부르지 않게 했고,
적률 합 대신 1차 적률만 담으며 폴백 지시자는 열이 아니라 그 셀의 행이 있는지로 정해진다.
stop_arrival_rate 와 4계층 12테이블은 돌아오지 않는다.
v3 가 두 표를 버린 근거는 "읽는 코드가 없다"였고, 바뀐 것은 그 판단이 아니라 전제다 — A18 의 L_cell 이 그 값을 직접 읽는다.
1. 스키마 변경 표와 열
열 정의와 제약의 정본은 파이프라인 계약 v4 다.
상세 → DB 스키마 마이그레이션이 절이 세는 표와 열을 실제로 만드는 SQL 전문이 그쪽에 있다 — 마이그레이션 한 장의 절별 적용 순서, 신설 표 둘의 열 정의, PostgreSQL 16 적용 검증까지.| 종류 | 대상 | 건수 | 왜 |
|---|---|---|---|
| 표 신설 | vehicle_stop_prediction | 표 1 · 열 14 | /board 의 답이 (차량, 정류장) 단위가 됐다. 담을 곳이 없다 |
| 표 신설 | demand_profile | 표 1 · 열 11 | A18 의 L_cell 이 읽는 셀 통계를 담을 곳이 없다 |
| 열 추가 | route_reference_version 방향별 첫차·막차 | 열 4 | v4-fe 가 DirectionInfo 에 필수로 올렸다 |
| 열 추가 | vehicle_observation.vehicleTripKey | 열 1 | 상류 좌석 기울기는 같은 여정 안에서만 뜻이 있다 |
| 열 추가 | location_poll.forecastCompletedAt | 열 1 | 예보가 안 붙은 판을 스냅샷으로 고르면 차량이 통째로 사라진다 |
| 인덱스 | vehicle_observation 2 · location_poll 1 | 3건 | 직전 도착 차량 · 같은 여정 · 예보까지 끝난 최신 poll |
| 표 제거 | forecast_publication · stop_prediction | 표 −2 · 열 −21 | /board 가 발행본을 읽지 않고, 그 발행 경로는 한 번도 뜬 적이 없다. 물리 표를 언제 DROP 할지는 파이프라인 계약 v4 의 미결 14 다 |
| 무변경 | call_ledger · route_stop · model_deployment | 표 3 · 열 0 | call_ledger 는 설명만 고쳤다 |
| 초안 철회 | model_deployment.servingRole | 열 −1 | 서빙 경로가 하나라 배포에 역할을 표시할 자리가 없다 |
| 초안 철회 | seatP10·seatP50·seatP90 | 열 −3 | v4-fe 가 seatQuantiles 를 두지 않기로 했다 |
기존 8개 표 중 둘이 빠지고, 계약에 남는 여섯 중 열이 느는 것은 셋이며 넷 이상 느는 것은 route_reference_version 하나다.
남는 표에서 열의 삭제·타입 변경·NOT NULL 변경은 0건이다.
나머지 셋(call_ledger · route_stop · model_deployment)은 열 정의가 한 글자도 안 바뀐다.
새 열에 딸린 규칙 셋
- 방향별 첫차·막차는
contentDigest대상이 아니다. 시간표만 바뀌면 판본을 새로 끊지 않고 같은 행을 UPDATE 해서 판본이 "정류장 목록의 판본"이라는 뜻을 지킨다. 갱신은 노선당 하루 1회와 판본 신설 시점이다. 방향마다 다른 것이 이 열의 존재 이유다 — 1650 은 상행 04:10~22:35 · 하행 05:25~23:55 로 막차가 80분 어긋난다. - 회차 순번 열은 v3 부터 있다. 실측은 1650 이 44 · 3330 이 43 이고 회차 정류장은 둘 다 안양역이다.
그
station_id(208000069)는 저장하지 않는다 — 순번이면 판정이 되고 그 순번의station_id는route_stop에 있다. (DB 열은station_id이고 응답 필드만stopId다.) vehiclesInService는 새 열이 아니다. 스냅샷 poll 의storedRows가 그 값이다 — 같은 poll 안 차량 중복을 부분 unique index 가 막으므로 저장 행 수가 곧 distinct 차량 수다.
2. 도메인 변경 모듈 경계와 데이터 흐름
A18 은 지금 파이프라인에 없는 넷을 요구한다 — 회차 키 판정 · 셀 통계 집계 · 차량별 추론 · 정류장 조립이다. 넷은 도는 주기가 서로 달라서 같은 자리에 놓으면 빠른 쪽이 느린 쪽에 끌려간다. 계약이 다섯째를 얹는다 — 방향별 첫차·막차는 A18 이 재료로 쓰지 않는데 응답에는 실린다. 빠지는 자리도 하나다 — processor 의 시간대 경계 발행은 이 설계에 없다.
상세 → 객체 구조 v3 → v4여기서 옮기는 경계가 객체 하나하나로 어떻게 내려앉는지는 그쪽에 있다 — 패키지별 v3 → v4 대조표와 Spring Boot 4.1 · Java 21 골격 코드.| 회차 키 판정 | 셀 통계 집계 | 차량별 추론 | 정류장 조립 | |
|---|---|---|---|---|
| 하는 일 | 같은 차량의 연속 관측을 한 운행으로 묶는다 | (판본, 정류장, 시간대) 셀마다 평균 점유율·순수요와 표본 수·날짜 수를 센다 | 차량 하나에 지평 1~12 의 도착 시 좌석 분포를 낸다 | 차량별 예보를 정류장별 approachingVehicles 로 뒤집는다 |
| 주기 | 관측 적재마다 | 자료가 쌓이면 다시 | 관측 적재마다 | 요청마다 |
| 상태 | 있다. 직전 순번을 알아야 경계를 잡는다 | 없다. 스캔 한 번의 순수 집계다 | 있다. 같은 여정의 앞선 관측을 봐야 기울기가 나온다 | 없다. 읽어서 정렬하고 담는다 |
| 지연 예산 | 초 | 분~시간 — 아무도 기다리지 않는다 | 초 | 밀리초 — 사람이 기다린다 |
| 놓을 자리 | collector | processor 주기 배치 | processor | api |
결정과 근거
| 결정 | 근거 한 줄 | 버린 대안 |
|---|---|---|
| 회차 키를 적재 시점에 정해 열에 남긴다 | 조회 때 판정하면 분할 로직이 학습 쪽과 서빙 쪽에 두 벌 생기고, 어긋나는 순간을 아무도 못 잡는다 | 조회 때 관측을 거슬러 훑기 |
| 계산 단위는 차량, 조립 단위는 정류장 | 순번 v 의 차량이 내는 지평 1~12 예보가 곧 순번 v+1…v+12 정류장의 항목이다. 행 하나가 두 축을 같이 들면 피벗이 인덱스 조회 하나로 끝난다 | 정류장마다 차량 전부를 훑기 |
| 낸 예보를 표에 남긴다 | cell_risk 를 버릴 때와 같은 자리에 선다 — 예측값이 없으면 브라이어·로그손실·보정오차를 못 낸다 | 조회 때 계산하고 버리기 |
| 추론은 processor, 조립은 api | api 가 추론하면 요청 수만큼 모델이 돌고 내놓은 값이 남지 않는다. 쓰기가 관측 commit 뒤 별도 transaction 이라 수집 사다리에 추론 시간이 섞이지도 않는다 | api 에서 추론 |
| 차량 예보에 봉인·승격을 걸지 않는다 | 발행 원자성은 한 판이 전부 유효하거나 전부 무효임을 보장하는데 차량 예보에는 판이라는 단위가 없다. 하나를 못 냈다고 나머지를 막으면 오히려 계약을 어긴다 | v3 발행 원자성 재사용 |
approachingVehicles 범위를 서버가 고르지 않는다 | 확정 계약이 지평 창 · 랩어라운드 제외 · maxItems: 3 으로 못박았다(2026-08-19 개정에서 2 → 3). 상한 3 의 이유는 크기가 아니라 답이 바뀌는 자리다 — 앞의 두 대가 모두 빈자리 확률 70% 미만인 경우가 1.80% 인데 그중 42.3% 에서 세 번째가 답이다. 네 번째는 24.8분이라 넣지 않는다 | 담을 대수를 서버가 정하기 |
stop_prediction 을 늘리지 않고 신설하는 근거와 셀 통계를 번들이 아니라 표에 두는 근거는
스키마 쪽 결정이라 파이프라인 계약 v4 에 적었다.
stop_prediction 은 그 계약에서 빠지므로 늘릴 대상 자체가 남지 않는다.
끊어야 할 것은 회차가 아니라 여정이다
회차를 이어서 지난 운행이 1650 94.0% · 3330 72.4% 이고 회차 전후 관측 간격은 중앙 0.3분이다.
차고지에 서지 않고 지나간다는 뜻이라, 회차에서 끊으면 재구성한 운행의 순번 폭 중앙 86/89·81/85 가 절반으로 잘려
상류 좌석 기울기의 재료가 사라진다. 회차 너머 정류장에도 예보를 낸다(approachingVehicles 항목의 10.0%·12.8%).
다만 근거가 바뀐다 — 회차를 넘으면 현재 잔여석이 도착 잔여석을 설명하지 못해 지평 8에서 R² 가 0.178 에서 0.011 로
떨어진다. v6와 같은 5일 표본에서 회차 너머는 지평 8이 5,247건·만석 3건(0.057%), 지평 12가 8,091건·만석 72건(0.890%)이다.
떠받치는 것이 차량 상태보다 정류장 통계에 가까워지므로,
모델은 turnSeq 를 입력으로 받지 않는다.
백엔드에 주는 뜻은 하나다. 회차 횡단 여부를 서버가 저장하지도 계산하지도 않는다.
sequence − horizonStops 와 route.turnSequence 를 견주는 순번 비교 하나이고 두 재료가 모두 응답 안에 있다.
서버가 필드를 더 두면 같은 판정이 두 곳에 생기는데, 그 값이 틀려도 확률은 그대로라 아무도 어긋남을 못 잡는다.
채점을 회차 횡단 여부로 가를 때만 같은 식을 쓰고, 저장이 아니라 조회 시 유도다.
예보를 못 내는 자리는 하나뿐이다
잔여석이 결측인 관측이다. 설계행렬이 잔여석을 세 열로 그대로 쓰고 앵커도 거기서 출발하는데,
그 자리에는 상류 기울기·직전 좌석과 달리 결측 지시자가 없다.
확정 계약에 "예보 없는 approachingVehicles 항목"이라는 상태가 없으므로 항목째로 빠지고 그것이 정상 응답이다 —
상태 코드도 사유 코드도 새로 만들지 않는다. 여정 키가 없다고 거르지는 않는다. 라벨 회수만 못 한다.
규모 — 쓰는 쪽은 크고 내보내는 쪽은 작다
05~24시 poll 기준 동시에 관측되는 차량이 중앙 11대·13대, 최대 28·36대다. 지평 12를 곱하면 수집 한 번에
계산할 예보가 중앙 132~156개 · 최대 336~432개다. 하루로 치면 08-18 실측 기준 노선당 차량 관측이
7.4만~8.2만 건이라 예보 행이 89만~98만이 되고,
v3 의 stop_prediction 이 노선당 하루 340~445행인 것과 견주면 약 2,000배다.
보존 기간과 파티션 축을 먼저 정하지 않으면 이 표가 파이프라인에서 가장 먼저 문제를 낸다.
응답 쪽은 반대로 작다. 지평 12 창만 걸어도 노선당 항목이 중앙 120·150 이고, 상한 없이 담았을 때의 중앙 462~554 와 견주면 4분의 1 아래다. 정류장당 상한(3)은 그 위에서 꼬리만 자른다 — 한 정류장에 붙는 항목이 중앙 3·4 · 최대 9·12 이기 때문이다. 줄이는 일은 지평 창이 하고 상한은 화면을 위해 건다. (중앙 119·133 은 상한 2 로 재던 값이다.) 한 바퀴 돌아 오는 차량이 실제 다음 도착 차량인 경우는 5.1%·15.6% 뿐이라 랩어라운드도 뺐다.
vehiclesInService 와 운행 시간은 서로 다른 시계에 있다
둘 다 예보가 아니라 예보를 읽는 법에 관한 값인데 나오는 자리가 멀다.
vehiclesInService 는 관측 시계에 있고 approachingVehicles 가 빈 이유를 가르려고 둔다 —
0 이면 운행이 끝난 쪽이고 0 보다 크면 이 구간에 지금 차가 없는 쪽이다.
다만 단정은 서버가 하지 않는다. 상류가 빈 응답을 주면 운행 중에도 0 이 되므로(실측 22,010 스냅샷 중 1건, 0.005%)
판별자로 굽지 않고 정수 그대로 내보낸다.
방향별 첫차·막차는 기준정보 시계에 있어 차량위치 poll 과 다른 경로로 들어오고 poll 예산에 얹히지 않는다.
되돌릴 수 없는 변경이다
v3 로 물러설 자리를 남겨 두는 설계가 아니다. 무엇을 안전장치로 둘지는 아직 정하지 않았다.
발행 계층은 남기지 않는다 — 표 둘과 그 경로를 함께 걷어낸다. 봉인 확인과 CAS 승격, 시간대 경계 재발행이
/board 서빙 경로에서 빠지고 그 자리를 관측마다 도는 차량 예보가 대신한다.
앞선 초안은 "표는 남기고 쓰기만 멈춘다"로 적었고 그 서술을 걷어냈다.
물러설 자리로 쓸 수 있는 경로가 아니었기 때문이다 — 그 경로는 한 번도 발행한 적이 없다.
적재할 계수 번들이 없어 processor 가 뜬 적이 없으므로, 한 번도 뜬 적 없는 경로를 안전장치라고 부를 수 없다.
되돌리려 해도 /board 응답 구조가 v3 형태로 함께 돌아간다. 정류장당 값 하나와 정류장별 도착 차량 목록은
같은 필드로 읽히지 않으므로, 프론트가 두 형태를 다 읽지 못하면 되돌림이 곧 중단이다.
무엇을 안전장치로 둘지는 이 문서가 정하지 않았다 — 5절 ② 에 미결로 남겼다.
신규 노선 생애주기
A18 은 노선별로 학습하고 계수를 공유하지 않는다. 그래서 새 노선은 자기 이력이 쌓이기 전까지 예보를 낼 재료가 없다.
RouteStatus enum 을 늘리지 않는다 — 예보를 못 내는 차량은 approachingVehicles 에서 빠질 뿐이다.
조심할 것은 오른쪽 아래다. v3 의 /board 는 발행본이 있으면 늘 값이 찼지만
v4 는 첫차 전·막차 후에 approachingVehicles 가 정상적으로 빈다.
그것을 상태로 승격하면 매일 밤 노선이 PREPARING 으로 떨어진다.
밤과 낮을 가르는 값은 status 가 아니라 vehiclesInService 와 방향별 막차 시각이다.품질 낮은 예보를 내는 중간 단계를 두지 않는다. 새 노선에는 셀 통계가 없어 마스킹 폴백만 도는데 그 값은 그 노선의 평균조차 아니고, 차량 상태만으로는 못 버틴다 — 학습에 쓰지 않은 하루에서 재면 차량 상태의 설명력이 지평 1에서 R² 0.958 인데 지평 8에서 0.179, 지평 12에서 0.002 까지 무너지고 정류장 상태는 0.336 에서 0.310 으로 거의 그대로다. 먼 지평일수록 새 노선이 갖지 못한 쪽에 의존한다.
| 관문 | 무엇을 재나 | A18 이 두 노선에서 낸 값 |
|---|---|---|
| 위조 시험 | 미래 라벨을 뒤집었을 때 결과가 움직이는가 | 변화 정확히 0 (대조군 0.788) |
| 다중비교 생존 | 본페로니 보정 뒤에도 이득이 남는가 | 96개 시험군에서 M1 23/24 생존 |
| 이득 대 잡음 | 관측 이득이 잡음 기대를 넘는가 | 2.068 대 0.209 |
| 노선별 전환 | 노선을 갈아도 기존 기준선을 이기는가 | 36/36 셀 |
| 지평 12 커버율 | 그 노선의 승차 가능 정류장 중 예보가 닿는 비율 | 84.8%(하루 전체) · 운행 시간대(06~23시) 90.4%(두 노선 합산) 분모는 승차 가능 정류장 143곳(1650 65 · 3330 78)이고, 분자는 raw 순번 차이 [S−지평, S−1] 구간에 그 정류장으로 올 버스가 있느냐다. 경유 지점도 구간의 한 칸이다 |
| 자료 조건 | 평일 아침 만석 사건이 관측돼 있는가 | 노선마다 새로 잰다 |
마지막 줄이 실질적으로 가장 센 관문이다 — 학습 자료가 전부 휴일일 때 평일 아침 판별력은 0.518 에 머물고
휴일을 세 배로 늘려도 그대로인데, 평일 반나절이 들어오면 0.958 로 뛴다. 모자란 것은 모델이 아니라 그 노선의 자료다.
다만 위 값들은 A18 이 낸 실측이지 통과 기준이 아니다 — 기준으로 쓰려면 결과를 보기 전에 문턱을 고정해야 한다.
기존 노선의 판본이 개편될 때도 같은 자리에 선다. demand_profile 이 노선 판본에 매여 있어 개편 직후 새 판본에는
셀 행이 없고, 폴백만으로 며칠을 버틸 수 있는지가 위 점선 화살표를 정한다.
아직 갈리는 선택지 둘
① 운행 시간의 요일패턴을 무엇으로 내보내나
오늘 요일의 값을 고른다권고
- 얻는 것
- 토·일 막차가 평일과 다른 노선이 붙어도 화면이 맞는다. 상류에 이미 4종이 다 있다
- 치르는 값
- 응답이 날짜에 의존한다. 캐시 키에 날짜가 들어가고 공휴일 달력이 따라온다
평일 값으로 고정한다
- 얻는 것
- 날짜 의존이 없다. 두 노선은 네 요일패턴의 값이 지금 같아 차이가 0 이다
- 치르는 값
- 주말 막차가 다른 노선을 붙이는 날 조용히 틀리고 화면은 그 오류를 드러내지 못한다
상류 마스터에서 공휴일(we*) 값은 사실상 일요일(sun*)의 복제다 —
양쪽 다 값이 있는 1,927개 노선 중 실제로 다른 것은 10개뿐이다.
② 셀 통계 재계산을 누가 도나
배포와 함께 돈다
- 얻는 것
- 서빙에 쓰는 셀 값과 배포가 항상 짝이 맞아 재현이 쉽다
- 치르는 값
- 통계를 새로 하려면 배포를 해야 한다. 표로 뺀 이유가 반쯤 사라진다
독립 일배치
- 얻는 것
- 자료가 쌓이는 속도로 갱신된다. 표로 뺀 이유가 온전히 산다
- 치르는 값
- 같은 배포가 날짜에 따라 다른 셀 값을 읽는다. 채점 때
trainedThrough로 갈라야 한다
3. 백엔드 API 변경 /board 를 만드는 절차
api 가 하는 일이 계산이 아니라 조립인 것은 그대로다. 무엇을 읽어 무엇을 담는지가 바뀐다.
상세 → /board 조립아래 여섯 단계를 실제 쿼리로 내린 조립 경로와 조립 SQL 이 그쪽에 있다 — 응답 필드와 출처 열의 대조표, api 객체의 v4 형태, 캐시와 오류 계약까지.| 단계 | 무엇을 읽나 | 무엇을 채우나 |
|---|---|---|
| 1 | 활성 노선 판본 | route · turnSequence · referenceVersionId |
| 2 | route_stop 전부 | stops[]. 승차 불가 정류장도 담고 그 approachingVehicles 만 빈 배열이다. station_id 열이 응답에서 stopId 로 나가는 자리도 여기다 |
| 3 | 판본의 방향별 첫차·막차 | route.directions[]. 기·종점 이름은 새 열 없이 route_stop 에서 유도한다 — 회차 지점이 구간을 가르고, DOWN 의 기점은 그 방향 첫 정류장이 아니라 회차 정류장이다 |
| 4 | 스냅샷 poll | observedAt(responseReceivedAt) · vehiclesInService(storedRows) |
| 5 | 그 poll 의 관측에 매달린 예보 행 | approachingVehicles 항목 전부. 항목은 여기서만 나온다 |
| 6 | 그 예보 행을 쓴 배포 | model. 차량 0대라 행이 없는 판에서만 ACTIVE 에서 읽는다 |
| 달라지는 규칙 | 무엇을 하나 | 없으면 무슨 일이 나나 |
|---|---|---|
| 스냅샷 단위 | poll 하나를 통째로 쓴다. 그 판의 모든 approachingVehicles 가 같은 순간을 가리킨다 | 차량별 최신 관측을 긁어모으면 observedAt 하나가 거짓말이 된다 |
| 예보 완결 표시 | forecastCompletedAt 이 빈 poll 은 후보에서 뺀다. 예보 대상이 0건이어도 찍는다 | 쓰기 한 번 실패가 노선 전체의 "곧 오는 버스 없음"으로 나가고 정상과 구별되지 않는다 |
| 배포 필터 | 배포로 거르지 않는다. 스냅샷 poll 에 매달린 행을 그대로 읽는다 | ACTIVE 로 거르면 승격 직후 한 판의 차량이 전부 사라진다 |
| 정렬 | 서버가 고정한다 — horizonStops 오름차순, 같은 지평이면 vehicleId 오름차순 | 결정적이지 않으면 같은 자료에서 응답이 흔들린다 |
| 항목의 출처 | 응답 항목은 예보 행에서만 나온다. 예보가 없으면 항목째 빠진다 | 확정 계약에 "예보 없는 항목"이라는 상태가 없다 |
| 오류 계약 | NO_CURRENT_PUBLICATION 을 빼고 NO_RECENT_OBSERVATION 을 넣는다. 부분 실패를 503 으로 올리지 않는다 | /board 가 발행본을 읽지 않으므로 그 사유가 가리킬 원천이 없다 |
| 캐시 | 하루 4번 캐시에서 수집 주기를 따라가는 캐시로 간다 | 값이 바뀌는 횟수가 세 자릿수로 는다 |
정렬을 지평 오름차순으로 두는 것이 "먼저 도착할 순서"와 어긋나지 않는다.
맨 앞 항목이 실제 첫 도착일 확률이 96.68% 이고 앞의 두 대끼리의 순서는 96.76% 정확하다.
가까울수록 정확해서 1정류장 앞은 96.6% · 10정류장 앞은 88.5% 다.
세 번째 항목은 지평이 멀어 더 흔들린다 — 상한을 3 으로 늘리면서 이 자리의 불확실성이 커졌고,
그래서 확정 계약이 horizonStops 를 화면에서 감추지 말라고 못박는다.
/vehicles 와 /routes 의 서빙은 바뀌지 않는다.
확정본이 손댄 것은 폴링 권고, Observation 주석, RouteStatus 뜻풀이, /routes 예시의 status 값이라 문서 쪽 일이다.
api 의 읽기 전용 경계도 유지된다. v8 이 적은 "모델이 무엇으로 바뀌어도 api 는 고칠 일이 없다"는 문장이 시험대에 오르는데,
고치는 이유는 모델이 아니라 응답 계약이고 모델 계약에 대한 의존은 여전히 없다.
4. 작업량 추정
계약 문서만 보고 낸 추정이라 기존 구현의 결합도에 따라 움직인다. 짝 문서는 같은 일을 계약 관점으로 다시 갈라 센다 — 항목 묶음이 다를 뿐 겹치는 일을 두 번 세지 않았다.
| 항목 | 무엇을 만드나 | 규모 | 그렇게 본 근거 |
|---|---|---|---|
| 정원 상수 교정 | 47석 고정을 차량별 관측 최대 잔여석으로 | 0.5일 | 특징 계약이 소유하므로 DB 도 계약도 안 바뀐다 |
| 노선 기준정보 확장 | 방향별 첫차·막차 적재, 하루 1회 갱신 job, 요일패턴 대조 | 1일 | 상류에 이미 있는 값이라 파싱과 저장뿐이다 |
| 회차 키 | 열 하나, 적재 시점 판정, 정규화 판 연결 | 1~2일 | 판정 규칙이 v3 라벨 조건에 이미 있다. 되돌아가는 순번의 문턱값만 새로 정한다 |
| 궤적 조회 경로 | 상류 좌석 기울기 · 직전 도착 차량 · 직전 연속 만석. 인덱스 2개 | 3~5일 | 규칙이 아니라 결손 처리가 비용이다 — poll 결손 · 순번 건너뜀 · 여정 경계 |
demand_profile | 표 하나, 집계 배치, 마스킹 폴백 사다리 | 2~3일 | 집계 자체는 SQL 하나다. 얇은 셀을 어디로 물리는지가 실제 설계 몫이다 |
| 번들 형식 확장 | 노선별 블록, 열두 지평 계수 전부, 특징 계약 버전, golden vector | 2~3일 | 기존 골격을 늘리는 일이다. 지평 격자가 다 찼으므로 담을 계수 집합이 확정값이다 |
| A18 추론기 | 허들 p0 + 앵커 + 부호 3분 + 크기 구간 골격에 6층 | 4~6일 | 층 구조가 전부 선형이라 수식은 무겁지 않다. 오프라인 산출과 자리를 맞추는 검증이 시간을 쓴다 |
| 차량 예보 저장 | 표 하나, 관측 뒤 별도 transaction, 파티션 축과 보존 정책 | 2~3일 | 쓰기 경로는 단순한데 노선당 하루 89만~98만행이라 파티션·보존이 실제 일이 된다 |
/board 재조립 | 차량 → 정류장 피벗, 지평 창과 랩어라운드 제외, 정렬, vehiclesInService 집계, 승차 불가 정류장 처리 | 3~5일 | 이번 안에서 가장 큰 항목이다. 피벗은 조인 하나지만 응답이 커져 직렬화와 정렬이 비용이 된다 |
/board 오류 계약 | NO_RECENT_OBSERVATION 신설, 부분 실패를 503 으로 올리지 않게 고침 | 1~2일 | 규칙이 줄어드는 쪽이라 시험을 다시 쓰는 것이 대부분이다 |
| 캐시·부하 | 수명 재설정, 응답 크기 대응, 동시 조회 경로 점검 | 1~2일 | 하루 4번 캐시에서 수집 주기를 따라가는 캐시로 간다 |
| 검증 | golden vector · 결정성 · 계약 시험 신규 작성 · 마이그레이션 리허설 | 3~4일 | 회귀가 아니라 교체다. 기존 /board 시험은 대부분 못 쓴다 |
| 합 | 23.5~36.5일 | 한 사람 기준. 추론기 · 궤적 조회 · 재조립 셋이 절반 가까이 된다 |
여기 들어 있지 않은 것 셋 — 계수 번들 생산(외부 ML 환경 소유. 노선별이라 노선마다 블록이 필요하다),
프론트엔드 작업(v4-fe 가 범위를 적는다),
과거 관측의 여정 소급 계산(여정 키가 NULL 허용이라 백필은 없지만, 이전 관측은 여정이 비어 라벨 대상에서 빠진다).
A18 채점은 evaluation-v6 · composite-score-v2 사설 일일 원장에 편입됐다. 지평 1~12 채점행 합은 608,512건이고, 서빙에는 그 12개 지평만 담는다.
5. 먼저 재야 할 것 코드보다 앞선다
작업량 추정보다 앞에 놓이는 확인이 있다. 결과에 따라 4절의 표가 흔들린다.
순번 12 이하 정류장은 뒤로 볼 칸이 sequence − 1 개뿐이라 지평 창이 짧다.
커버율이 24.47%(1650)·41.85%(3330) 로 떨어지고 그 밖 구간은 98.92%·99.05% 이며,
해당 정류장이 두 노선 합쳐 승차 가능 정류장의 14.0%(20/143) 다.
문제는 커버율이 아니라 그 빈칸을 "곧 오는 버스 없음"으로 읽으면 틀린다는 데 있다.
실제 다음 차량이 차고지에서 대기 중이면 차량위치 API 에 아직 없다.
메우려면 차고지 발차 시각이나 도착정보 API 가 필요하고 둘 다 지금 수집 대상이 아니다.
서버가 할 수 있는 것은 sequence 와 vehiclesInService 로 두 경우를 가를 재료를 주는 것까지다.
이 안에는 물러설 경로가 없다. 발행 계층을 설계에서 뺐고, 그 경로는 애초에 한 번도 발행한 적이 없다.
/board 를 v3 로 되돌리려면 응답 구조가 함께 돌아가므로 프론트도 같이 되돌아가야 한다.
그래서 A18 이 새 자료에서 무너질 때 무엇을 내보낼지가 미결로 남는다.
무너졌다고 판정하는 기준도, 그때 /board 가 내보낼 응답도 정해진 것이 없다.
이것은 코드보다 앞에 놓이는 결정이고 이 문서가 답하지 않는다.
v3 계약 문서의 "60초 고정 · 3노선 하루 4,320회 · 한도 10,000의 43%"는 적응형 확정 전의 값이다.
실제 수집기는 적응형이고(adaptive-kst-v1.0.1 · 15 / 20 / 600초) 현행
expected_daily_calls 는 두 노선 합 8,556 이다 — 08-18 원자료가 정확히 8,556 판이라
설정과 실측이 맞는다.
계약 문서를 실물에 맞추고 남은 여유를 다시 세야 하며, 신규 노선 관문이 여기에 걸려 있다.
학습 밀도와 서빙 밀도가 갈릴 걱정은 없다 — A18 학습 자료를 그 적응형 수집기가 실제로 만들었다.
설계에서 아직 안 정한 것
- 운행 시간의 요일패턴과
demand_profile재계산 주기·주체. 2절 선택지 ①·②다. - 좌석이 결측인 관측의 서빙 처리. 전수 22,010 스냅샷에서 차량행 314,688 중 237행(0.075%)이고
판의 1.07% 가 그런 차량을 하나 이상 담으며 한 차량에 연속 18회까지 이어진 사례가 있다.
/vehicles는 같은 차량을kind: UNKNOWN으로 계속 보여 주므로 두 화면이 어긋난다. - 차량 예보 보존 기간과 파티션 축. 채점에 필요한 창과 저장 비용의 절충이 필요하다.
- 노선 판본 개편 시 상태 전이. 폴백만으로 며칠을 버틸 수 있는지에 답이 달렸다.
- 노선 관문의 문턱값. 채택 규칙은 모델 채택용이라, 노선 관문으로 옮기려면 문턱을 결과 보기 전에 고정해야 한다.
- 여정 분할 문턱. 순번이 줄어드는 것을 새 여정의 신호로 쓰는데 상류 지터로 잠깐 되돌아가는 경우와 구별할 값이 없다. 3330 은 회차 순번 언저리(42~43)에서 끝나고 다시 시작하는 여정이 있어 순번만으로는 갈리지 않는다. 밤 공백의 중앙이 1650 487분 · 3330 462분이라 시간 문턱으로 가를 여지는 있는데, 회차 대기 자체가 시간대에 따라 변한다 — 1650 기준 중앙이 07시 106.7분에서 21시 46.0분까지 내려간다(전체 중앙 75.3분). 문턱 하나로 고정하면 아침에는 한 여정을 둘로 쪼개고 밤에는 둘을 하나로 붙인다.
v3 의 미결 셋(계수 번들 부재 · 학습 사건의 노선 판본 부재 · 제품 공개 노선 ID)은 그대로 남아 있다. 스키마 쪽 미결 전량은 파이프라인 계약 v4 끝에 있다.
이 문서가 새로 잰 값과 그 근거
확정 사실에 없어서 원자료를 다시 센 값들이다. 자료는 2026-08-14~18 수집 기록 22,010건(두 노선)이고, 정류장 수는 1650 89개 · 3330 85개를 썼다. 본문에 적지 않은 값만 아래에 남긴다. 측정 스크립트와 출력은 작업 기록과 함께 남겼다.
동시 관측 차량 수(05~24시 poll) — 1650 중앙 11 · 평균 14.7 · 최대 28, 3330 중앙 13 · 평균 15.2 · 최대 36. 예보 행 수(08-18)는 차량 관측 81,864(1650)·73,936(3330) 에 지평 12를 곱해 982,368행 · 887,232행이다. 견줌에 쓴 v3 의 노선당 하루 340~445행은 v3 계약의 "노선당 하루 4~5판"에 정류장 수를 곱한 값이다.
approachingVehicles 항목 수(05~24시 poll) — 지평 12 창으로만 자르면 중앙 120·150 이고 한 정류장에 붙는 항목이
중앙 3·4 · 최대 9·12 다. 자르지 않으면 중앙 462·554, p90 1,020·1,039, 최대 1,223·1,420 이고
그중 지평 12 안에 드는 것이 26.5%·27.5% 뿐이다. 한 바퀴 돌아 오는 차량 앞에는 중앙 4~7대가 먼저 지나간다.
여정 경계 — 회차 대기 중앙 75.3분(1650)·94.0분(3330). 1650 을 여정 종료 시각대로 가르면 07시 106.7분 · 12시 84.0분 · 17시 59.6분 · 21시 46.0분으로 내려간다(중간에 되오르는 시간대도 있다). 여정 종료 순번은 1650 이 88 에 299건으로 몰리고 3330 은 84 에 317건 · 83 에 73건인데, 회차 순번 언저리(43)에서 끝나거나 42 에서 시작하는 여정이 각각 1건씩 있다.
poll 수(08-18) — 노선당 4,097. 심야 6회/시(600초), 낮 180회/시(20초), 아침·저녁 240회/시(15초)다. 23시대 기록이 없어 23시간분이다. 지평 격자는 1~12 를 전부 적합·채점했고(608,512행) 채점 표본이 j1 56,279 에서 j12 46,854 까지 줄어든다.
이 문서가 전제로 삼은 A18 의 확정 사실
지금 오고 있는 그 버스가 목표 정류장에 도착할 때 잔여좌석이 몇 석일지를 분포로 예측한다.
만석 확률은 그 분포의 0 지점이다. 학습 단위는 노선별이고 계수 공유가 없다.
2026-08-20 v6 산출값이다. 말뭉치는 통과 사건 60,158건·만석 759건이며,
지평 1~12로 펼친 채점행은 608,512건·만석 8,134건이다.
종합 점수는 j1 91.2 · j2 87.3 · j4 81.0 · j8 74.1 · j12 67.8 이다.
기각한 j16은 서빙 지평 목록에 넣지 않는다. 브라이어(.00306 → .00992)와 만석 구분(.9092 → .4291)은
멀수록 한 방향으로 나빠진다. 지평 12 로 확정했다 — 하루 전체 8→12 는 커버율
78.0%→84.8%(+6.73%p)에 만석 구분 .558→.429(−25.5%)인 교환이고,
12→16은 커버율이 85.7%로 0.94%p 느는 데 만석 구분이 다시 22.4% 깎여 기각했다.
일일 평가 원장의 사설 horizon-latest에 있으며 공개 API에는 내지 않는다.
관측 한계 — 입석 없음이 확정이라 잔여석 0 = 승차 불가다. 승하차 분리 관측은 불가능하고 만석 포화 시 대기열은 관측되지 않는다. 도착 예정 API 를 수집하지 않으므로 "몇 분 후"를 낼 수 없다 — v3 도 못 준다. 6층 골격에서 L_support 는 제거했다 — 가용 질량이 16개 셀 전부에서 0.000000 이었다. 나머지 상세는 A18 모델 문서에 있다.
관련 문서
이 문서는 검토 요청안이다. 확정 계약은 v3 이고, 여기서 제안한 어느 항목도 팀 합의 전에는 적용되지 않는다.