/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
기존 표에 붙는 열+6route_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개유지 + 판정 1GBIS 호출·정규화·호출 장부·노선 판본이 그대로다. 적재 시점 회차 키 판정 하나와 첫차·막차 갱신이 붙는다
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 · 열 11A18 의 L_cell 이 읽는 셀 통계를 담을 곳이 없다
열 추가route_reference_version 방향별 첫차·막차열 4v4-fe 가 DirectionInfo 에 필수로 올렸다
열 추가vehicle_observation.vehicleTripKey열 1상류 좌석 기울기는 같은 여정 안에서만 뜻이 있다
열 추가location_poll.forecastCompletedAt열 1예보가 안 붙은 판을 스냅샷으로 고르면 차량이 통째로 사라진다
인덱스vehicle_observation 2 · location_poll 13건직전 도착 차량 · 같은 여정 · 예보까지 끝난 최신 poll
표 제거forecast_publication · stop_prediction표 −2 · 열 −21/board 가 발행본을 읽지 않고, 그 발행 경로는 한 번도 뜬 적이 없다. 물리 표를 언제 DROP 할지는 파이프라인 계약 v4 의 미결 14 다
무변경call_ledger · route_stop · model_deployment표 3 · 열 0call_ledger 는 설명만 고쳤다
초안 철회model_deployment.servingRole열 −1서빙 경로가 하나라 배포에 역할을 표시할 자리가 없다
초안 철회seatP10·seatP50·seatP90열 −3v4-fe 가 seatQuantiles 를 두지 않기로 했다

기존 8개 표 중 둘이 빠지고, 계약에 남는 여섯 중 열이 느는 것은 셋이며 넷 이상 느는 것은 route_reference_version 하나다. 남는 표에서 열의 삭제·타입 변경·NOT NULL 변경은 0건이다. 나머지 셋(call_ledger · route_stop · model_deployment)은 열 정의가 한 글자도 안 바뀐다.

새 열에 딸린 규칙 셋

2. 도메인 변경 모듈 경계와 데이터 흐름

A18 은 지금 파이프라인에 없는 넷을 요구한다 — 회차 키 판정 · 셀 통계 집계 · 차량별 추론 · 정류장 조립이다. 넷은 도는 주기가 서로 달라서 같은 자리에 놓으면 빠른 쪽이 느린 쪽에 끌려간다. 계약이 다섯째를 얹는다 — 방향별 첫차·막차는 A18 이 재료로 쓰지 않는데 응답에는 실린다. 빠지는 자리도 하나다 — processor 의 시간대 경계 발행은 이 설계에 없다.

상세 → 객체 구조 v3 → v4여기서 옮기는 경계가 객체 하나하나로 어떻게 내려앉는지는 그쪽에 있다 — 패키지별 v3 → v4 대조표와 Spring Boot 4.1 · Java 21 골격 코드.
회차 키 판정셀 통계 집계차량별 추론정류장 조립
하는 일같은 차량의 연속 관측을 한 운행으로 묶는다(판본, 정류장, 시간대) 셀마다 평균 점유율·순수요와 표본 수·날짜 수를 센다차량 하나에 지평 1~12 의 도착 시 좌석 분포를 낸다차량별 예보를 정류장별 approachingVehicles 로 뒤집는다
주기관측 적재마다자료가 쌓이면 다시관측 적재마다요청마다
상태있다. 직전 순번을 알아야 경계를 잡는다없다. 스캔 한 번의 순수 집계다있다. 같은 여정의 앞선 관측을 봐야 기울기가 나온다없다. 읽어서 정렬하고 담는다
지연 예산초분~시간 — 아무도 기다리지 않는다초밀리초 — 사람이 기다린다
놓을 자리collectorprocessor 주기 배치processorapi
새 /board 의 조각을 세 개의 시계로 가른다 관측 시계 수집마다 지연 예산 · 초 관측 적재collector · 그대로 회차 키 판정collector · 신규 차량 추론processor · 신규 정류장 조립api · 신규 자료 시계 자료가 쌓이면 지연 예산 · 분~시간 셀 통계 재계산processor · 신규 · demand_profile 번들 적재외부 ML 환경이 만든다 · 그대로 기준정보 시계 하루 1회 · 판본 신설 지연 예산 · 일 노선 판본 갱신collector · 그대로 방향별 첫차·막차collector · 신규 · 노선 마스터에서 그대로 새로 붙는다
패키지가 늘지 않는다. 셋의 책임 경계는 그대로이고 각 패키지 안에 칸이 붙는다. 가르는 축은 패키지가 아니라 시계다 — 같은 processor 안에서도 차량 추론은 초 단위로 돌고 셀 통계 재계산은 시간 단위로 돈다. 둘을 한 배치에 묶으면 셀 통계 한 번 돌 때마다 차량 예보가 멈춘다. 기준정보 시계가 이번에 늘었다. 방향별 첫차·막차는 관측이 아니라 기준정보라 노선 판본 쪽 속도로 돈다.

결정과 근거

결정근거 한 줄버린 대안
회차 키를 적재 시점에 정해 열에 남긴다조회 때 판정하면 분할 로직이 학습 쪽과 서빙 쪽에 두 벌 생기고, 어긋나는 순간을 아무도 못 잡는다조회 때 관측을 거슬러 훑기
계산 단위는 차량, 조립 단위는 정류장순번 v 의 차량이 내는 지평 1~12 예보가 곧 순번 v+1…v+12 정류장의 항목이다. 행 하나가 두 축을 같이 들면 피벗이 인덱스 조회 하나로 끝난다정류장마다 차량 전부를 훑기
낸 예보를 표에 남긴다cell_risk 를 버릴 때와 같은 자리에 선다 — 예측값이 없으면 브라이어·로그손실·보정오차를 못 낸다조회 때 계산하고 버리기
추론은 processor, 조립은 apiapi 가 추론하면 요청 수만큼 모델이 돌고 내놓은 값이 남지 않는다. 쓰기가 관측 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 은 노선별로 학습하고 계수를 공유하지 않는다. 그래서 새 노선은 자기 이력이 쌓이기 전까지 예보를 낼 재료가 없다.

신규 노선 생애주기 — 상태는 둘뿐이다 노선 등록판본 생성 수집만status = PREPARING 관문노선 단위로 다시 잰다 예보 개시status = FORECAST_READY 미달 — 수집만 계속 노선 판본 개편 — 어디로 가는지 미결 두지 않는 것 품질 낮은 예보를 내는 중간 단계 부분 서빙 · 경고 딱지 · 베타 노출 상태가 아닌 것 밤에 approachingVehicles 가 비는 것 — 정상 응답이다 vehiclesInService 로 가르고 status 는 그대로다
상태는 둘뿐이고 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
2route_stop 전부stops[]. 승차 불가 정류장도 담고 그 approachingVehicles 만 빈 배열이다. station_id 열이 응답에서 stopId 로 나가는 자리도 여기다
3판본의 방향별 첫차·막차route.directions[]. 기·종점 이름은 새 열 없이 route_stop 에서 유도한다 — 회차 지점이 구간을 가르고, DOWN 의 기점은 그 방향 첫 정류장이 아니라 회차 정류장이다
4스냅샷 pollobservedAt(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 vector2~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개 정류장은 차고지 발차 시각 없이는 풀리지 않는다

순번 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 학습 자료를 그 적응형 수집기가 실제로 만들었다.

설계에서 아직 안 정한 것

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 모델 문서에 있다.