/board 를 A18 에 맞추면
백엔드는 무엇이 달라지나

새 예보 모델 A18(차량 상태 조건부 좌석 분포)을 채택하면 /board 는 선택 필드가 붙는 것이 아니라 다시 만들어진다. 무엇이 늘고 무엇이 그대로인지, 얼마나 일인지, 무엇을 먼저 재야 하는지를 적었다. 확정 계약은 여전히 v3 이고 이 문서는 검토 요청안이다. 표와 열의 정본은 파이프라인 계약 v4 에 있고, 이 문서는 그것을 도메인 쪽에서 읽는다.

2026-08-19 작성 · v3 확정 계약(2026-08-17)과 객체 구조 v8 기준 · v4-fe 확정본(2.0.0)의 /board 계약에 맞췄다

먼저 결론 — 아래쪽은 그대로, 위쪽이 바뀐다

차량위치 poll 도, 수집 필드도, 쿼터 계산도, /vehicles 도, /routes 도 그대로다. A18 은 이미 수집하고 있는 값만 쓰고 예보를 내려고 상류를 더 부르지 않는다. collector·processor·api 세 패키지 경계도 그대로다. 새 상류가 하나 붙는데 예보 경로가 아니다 — 방향별 첫차·막차이고 기준정보 갱신 경로에 노선당 하루 1회 얹힌다.

바뀌는 것은 /board 한 곳이다. 지금 /board봉인된 발행본을 읽는데, 새 설계에서는 차량 관측을 읽는다. 그래서 원천·갱신 주기·응답 구조·가용성이 함께 움직인다. 이전 v4-be 안은 A18 을 /vehicles 의 선택 필드로 붙이는 증분안이었다. 그 안은 폐기됐고, 이 문서가 대체한다.

기존 모델(상호작용 로지스틱 회귀)은 폐기되지 않는다. 발행 계층의 표도 지워지지 않는다. 다만 쓰기가 멈춘다 — 둘은 레인 A 로 남아 되돌림 대상이 된다. 3절.

1. 한 장 요약 A18 채택 시 백엔드가 새로 하는 일

DB 쪽 수치의 정본은 파이프라인 계약 v4 에 있다. 실측 열의 근거는 7절 끝의 접힌 목록에 있다.

항목지금 (v3)A18 채택 시달라지는 폭
/board 가 답하는 것이 정류장·이 시간대를 지나는 버스라면지금 오는 이 버스가 이 정류장에 도착할 때재구성
/board 의 원천봉인된 forecast_publication차량 관측에서 파생된 차량별 예보원천 교체
/board 값이 바뀌는 횟수시간대 경계 · 하루 4번수집마다. 08-18 실측 노선당 4,097회약 1,000배
/board 응답 크기정류장당 값 1개 · 노선당 85~89개거기에 정류장별 도착 예정 차량이 붙는다. 지평 12 창 · 정류장당 2대 상한 기준 arrivals 항목 중앙 119~133항목 +119~133
/board 가용성수집이 끊겨도 유효하다최근 관측이 없으면 NO_RECENT_OBSERVATION 503결합 신설
상류 APIGBIS 차량 위치 1종예보 경로는 그대로. A18 은 이미 받는 필드만 쓴다. 첫차·막차만 노선 기준정보에서 새로 받는다 — poll 예산과 다른 경로다poll 은 그대로
수집 필드remainingSeats·crowdedCode·lowPlateCode·stopOrder·phase·vehicleId그대로. 새 필드를 요구하지 않는다변경 없음
/vehicles·/routes실시간 차량 · 노선 목록스키마가 한 필드도 바뀌지 않는다. 다만 확정본이 설명과 예시는 손댔다 — /vehicles 의 폴링 권고, Observation 주석, RouteStatus 뜻풀이, /routes 예시의 status스키마 변경 없음
패키지 경계collector · processor · api셋 그대로. 각 패키지 안에 칸이 는다경계 유지
발행 계층/board 의 서빙 경로표와 열은 지워지지 않는다. 다만 쓰기가 멈추고 /board 도 읽지 않는다쓰기 동결
기존 7개 후보 모델비교 대상 · 1차 선택은 상호작용 로지스틱레인 A 로 계속 산다. 폐기가 아니다유지
예보 저장량노선당 하루 340~445행노선당 하루 89만~98만행 (08-18 실측 관측 × 지평 12)약 2,000배
모델 학습 단위노선 범주 계수를 한 모델이 공유노선별. 계수 공유 없음번들 구조
지평개념이 없다 — 시간대만 있다12정류장 고정. 지평 1~12 를 전부 적합·채점했다. 정류장 수와 시간은 서로 다른 축이다축 신설
운행 차량 수없다Board.vehiclesInService 정수 신설. 그 poll 의 distinct 차량 수다필드 신설
방향별 운행 시간없다DirectionInfo 에 첫차·막차 시각. 노선 기준정보에서 오고 갱신 주기가 다르다기준정보 확장
정원 상수47석 고정차량별 관측 최대 잔여석 그대로. 계열 판정을 하지 않는다오류 교정
DB 표·열표 8개 (스키마 v8)표 신설과 열 추가가 있고 기존 열의 삭제·타입 변경은 없다정본은 v4 계약

정원이 A18 에만 걸리는 이유는 레인 A 모델의 입력이 노선·정류장 순번·시간대 셋뿐이어서다. A18 은 현재 잔여석 ÷ 정원을 점유율로, 좌석 감소 ÷ 정원을 순수요로 쓰므로 정원이 틀리면 두 값이 함께 어긋난다. 정원은 차량마다 그 차가 보여 준 최대 잔여석이다. 전수 22,010 스냅샷에서 말뭉치가 일곱 갈래로 갈리고(44석 35,615행 · 45석 5,719 · 40석 5,134 · 68석 4,479 · 49석 1,232 · 36석 17 · 41석 15) 최빈값은 45가 아니라 44다. 종전 47 은 어느 근거에도 없던 수였다. 이것은 모델이 쓰는 정원이고 차량의 형식승인 좌석수와는 다른 양이다 — 저상 신차는 형식승인이 41 로 유력하고, 잔여석이 형식승인 좌석수를 넘는 차량도 49대 중 6대 있다. 사이트 원칙대로 둘을 하나로 합치지 않고 출처별로 남긴다. 정원은 DB 열이 아니라 번들의 특징 계약이 소유한다.

이전 v4-be 안과 무엇이 다른가

이전 안은 "A18 을 /vehicles 에 선택 필드로 붙인다"였다. /board 를 손대지 않는 것이 그 안의 핵심이자 안전장치였고, 그래서 되돌리기가 쉬웠다.

지금 안은 반대다. /board 를 A18 에 맞춰 재구성한다. 뒤집은 근거는 화면 쪽에 있다 — 시간대 평균은 A18 이 이미 재료로 쓰는 값이라 따로 내보내면 재료를 덜 가공해서 내보내는 것이 된다(v4-fe). 대신 값을 하나 치른다. 프론트엔드 수정이 필요하고, 되돌림이 조용한 폴백이 아니라 계약 되돌림이 된다 — 7절.

2. 모듈 경계 주기 · 상태 · 지연 예산으로 가른다

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

회차 키 판정셀 통계 집계차량별 추론정류장 조립
하는 일같은 차량의 연속 관측을 한 운행으로 묶는다(노선 판본, 정류장, 시간대) 셀마다 평균 점유율·순수요와 그것을 만든 표본 수·날짜 수를 센다차량 하나에 지평 1~12 의 도착 시 좌석 분포를 낸다차량별 예보를 정류장별 arrivals 로 뒤집고, 그 poll 의 storedRowsvehiclesInService 로 붙인다
주기관측 적재마다자료가 쌓이면 다시관측 적재마다요청마다
입력 범위직전 관측 하나 — 좁다학습 기간 전량 — 넓다그 차량의 궤적 + 셀 통계그 노선의 최신 예보 행들
상태있다. 직전 순번을 알아야 경계를 잡는다없다. 스캔 한 번의 순수 집계다있다. 같은 여정의 앞선 관측을 봐야 기울기가 나온다없다. 읽어서 정렬하고 담는다
지연 예산초 — 다음 poll 전에 끝나야 한다분~시간 — 아무도 기다리지 않는다초 — 다음 poll 전에 끝나야 한다밀리초 — 사람이 기다린다
놓을 자리collectorprocessor 주기 배치processorapi
새 /board 의 조각을 네 개의 시계로 가른다 관측 시계 수집마다 지연 예산 · 초 관측 적재collector · 그대로 회차 키 판정collector · 신규 차량 추론processor · 신규 정류장 조립api · 신규 발행 시계 하루 4번 · 지금은 멈춤 지연 예산 · 분 레인 A 발행봉인 확인 5종 · CAS 승격 · 쓰기 동결 /board 는 더 이상 읽지 않는다되돌림 대상으로 남는다 자료 시계 자료가 쌓이면 지연 예산 · 분~시간 셀 통계 재계산processor · 신규 · demand_profile 번들 적재외부 ML 환경이 만든다 · 그대로 기준정보 시계 하루 1회 · 판본 신설 지연 예산 · 일 노선 판본 갱신collector · 그대로 방향별 첫차·막차collector · 신규 · 노선 마스터에서 그대로 새로 붙는다 남지만 /board 서빙 경로에서 빠진다
패키지가 늘지 않는다. collector·processor·api 셋의 책임 경계는 그대로이고, 각 패키지 안에 칸이 붙는다. 가르는 축은 패키지가 아니라 시계다 — 같은 processor 안에서도 차량 추론은 초 단위로 돌고 셀 통계 재계산은 시간 단위로 돈다. 둘을 한 배치에 묶으면 셀 통계 한 번 돌 때마다 차량 예보가 멈춘다. 네 번째 시계가 이번에 늘었다 — /board 가 방향별 첫차·막차를 함께 내보내는데, 그 값은 관측이 아니라 노선 기준정보라 노선 판본 쪽 속도로 돈다.

회차 키를 적재 시점에 정하는 이유

A18 은 "j정류장 전 그 버스의 좌석"과 "앞선 정류장들에서 좌석이 줄던 속도"를 쓴다. 둘 다 같은 차량의 같은 운행 안에서만 뜻이 있다. 한 차량이 하루에 노선을 여러 번 돌면 같은 순번을 여러 번 지나므로, 운행을 나누지 않으면 이번 운행의 9번 정류장과 다음 운행의 17번 정류장이 짝지어진다.

조회 때마다 관측을 거슬러 훑는 방법도 있다. 그렇게 하지 않는 이유는 학습 쪽과 서빙 쪽에 같은 분할 로직이 두 벌 생기기 때문이다. 둘이 어긋나는 순간을 아무도 못 잡고, 오프라인 성적이 온라인에서 재현되지 않는다. 적재할 때 한 번 정해 열에 남기면 그 위험이 사라진다. 분할 규칙은 location_poll.normalizationVersion 에 속한다 — 새 규칙은 새 정규화 판이다.

판정 규칙 자체는 새로 정하지 않아도 된다. v3 의 학습 라벨 조건에 "같은 차량 · 회차"가 이미 들어 있다. 다만 그 판정을 연구 export 가 수행했을 뿐 저장하지 않았고, A18 은 서빙 시점에도 같은 판정을 해야 한다. poll 이 끊겨 직전 관측을 잃으면 경계를 놓칠 수 있는데, 그때는 보수적으로 끊는다. 여정을 끊어도 예보는 낸다. 설계행렬이 상류 좌석 기울기와 직전 좌석에 각각 결측 지시자 열을 갖고 있어, 여정의 첫 관측이든 vehicleId 가 없는 관측이든 같은 방식으로 점수화된다. 여정 키가 없으면 라벨 회수만 못 한다.

예보를 못 내는 자리는 따로 있다 — 잔여석이 결측인 관측이다. 설계행렬이 잔여석을 세 열로 그대로 쓰고 앵커도 거기서 출발하는데, 그 자리에는 기울기·직전 좌석과 달리 결측 지시자가 없다. 예보를 내지 않는다는 것은 그 항목을 담지 않는다는 뜻이다. v4-fe 확정본에서 seatAvailableProbabilityStopArrival 의 필수 필드라 "예보 없는 arrivals 항목"이라는 상태가 계약에 존재하지 않는다. 빈 자리를 표시할 필드가 없으므로 항목째로 빠지고, 그것이 정상 응답이다. 상태 코드도 사유 코드도 새로 만들지 않는다.

회차는 운행의 끝이 아니다

두 노선은 왕복이고 중간의 안양역(208000069)이 회차 지점이다 — 1650 은 순번 44, 3330 은 43 이다. 재어 보면 회차를 이어서 지난 운행이 1650 94.0% · 3330 72.4% 이고 회차 전후 관측 간격은 중앙 0.3분이다. 차고지에 서지 않고 지나간다는 뜻이다. 그래서 회차 키는 회차마다 끊는 키가 아니라 운행마다 끊는 키다. 회차에서 끊으면 재구성한 운행의 순번 폭 중앙 86/89(1650)·81/85(3330) 가 절반으로 잘려 상류 좌석 기울기의 재료가 사라진다.

회차 너머 정류장에도 예보를 내야 한다 — arrivals 항목의 10.0%(1650)·12.8%(3330) 가 여기 해당하고, 그 버스들은 진짜로 오고 있는 버스다. 다만 근거가 바뀐다. 회차를 넘는 순간 차량의 현재 잔여석이 도착 잔여석을 설명하지 못한다 — 지평 8에서 R² 가 0.178 에서 0.011 로 떨어지고 상관이 음수로 뒤집힌다. 그런데도 예보는 맞는다. 회차 넘는 표본의 평균 점유율이 0.105 이고 점유율 0.7 이상이 0건이라, 지평 8에서 실제 만석률 0.000% 에 최대 예측 0.020 이다. 회차 너머 예보를 내는 것은 차량 상태가 아니라 정류장 통계다. 모델은 turnSeq 를 입력으로 받지 않는다 — 셀 층이 그 구간을 이미 학습하고 있다.

백엔드에 주는 뜻은 하나다. 회차 횡단 여부는 서버가 저장하지도 계산하지도 않는다. v4-fe 확정본은 그 판정을 클라이언트에 넘겼다 — sequence − horizonStops 로 차량의 현재 순번을 얻고 route.turnSequence 와 견주면 끝나는 순번 비교 하나이고, 두 재료가 모두 응답 안에 있다. 서버가 필드를 하나 더 두면 같은 판정이 두 곳에 생기고, 그 값이 틀려도 확률은 그대로라 아무도 어긋남을 못 잡는다. 계약에서 지운 것이 아니라 애초에 서버의 일이 아니다. 다만 채점을 회차 횡단 여부로 가를 때는 서버 쪽에서도 같은 식을 쓴다 — 저장이 아니라 조회 시 유도다.

계산 단위는 차량이고 조립 단위는 정류장이다

/board 는 정류장 관점의 응답이지만, A18 의 계산 단위는 여전히 차량이다. 순번 v 에 있는 차량 하나가 지평 1~12 의 예보를 내면 그것이 곧 순번 v+1 … v+12 정류장의 arrivals 항목이 된다. 둘은 같은 집합이고 방향만 다르다. 그러므로 저장은 차량 단위로 하고, 조립에서 정류장 단위로 뒤집는다. 행 하나가 지평 축(채점용)과 목표 순번 축(조회용)을 같이 들면 피벗은 인덱스 조회 하나로 끝난다.

실측 규모는 이렇다. 05~24시 poll 기준 동시에 관측되는 차량이 중앙 11대(1650)·13대(3330), 최대 28·36대다. 지평 12를 곱하면 수집 한 번에 계산할 예보가 중앙 132~156개, 최대 336~432개다. 하루로 치면 08-18 실측 기준 노선당 차량 관측 7.4만~8.2만 건이므로 예보 행이 89만~98만이 된다. 레인 A 의 stop_prediction 이 노선당 하루 340~445행인 것과 견주면 약 2,000배다. 보존 기간과 파티션 축을 먼저 정하지 않으면 이 표가 파이프라인에서 가장 먼저 문제를 낸다.

arrivals 의 범위는 계약이 닫았다

이전 안은 "다음 몇 대까지 담을 것인가"를 열린 선택지로 뒀다. 그 선택지는 사라졌다. v4-fe 확정본이 범위를 식으로 못박았기 때문이다 — max(1, sequence − 12) ≤ 차량순번 ≤ sequence − 1, 랩어라운드 제외다. 여기에 대수 상한 2가 얹힌다(maxItems: 2) — 사용자가 궁금한 것은 자기가 탈 버스이고, 세 대째는 먼저 오는 버스가 둘이라 중앙 27분을 기다려야 한다. 담을 수 있는 항목에는 예보가 반드시 붙으므로 "오고 있다만 알리는 항목"도 함께 사라졌다.

그 결과 응답 크기가 이전 추정보다 작다. 05~24시 poll 기준 노선당 arrivals 항목이 중앙 119(1650)·133(3330) 이고 정류장당 최대 2개다. 상한 없이 담았을 때의 중앙 462~554 · 최대 1,223~1,420 과 견주면 4분의 1 아래다. 지평 12 만으로도 중앙 120~150 이었으므로 대수 상한이 더 줄이는 몫은 크지 않다 — 상한 2 를 두는 이유는 크기가 아니라 "먼저 도착할 버스"라는 뜻을 지키기 위해서다. 랩어라운드를 뺀 근거도 실측이다 — 한 바퀴 돌아 오는 차량이 실제 다음 도착 차량인 경우가 5.1%(1650)·15.6%(3330)뿐이고 그 앞에 중앙 4~7대가 먼저 지나간다. 조립에서 지평 창과 랩어라운드 제외를 같은 자리에서 적용하면 규칙이 한 곳에만 산다.

차량 예보를 저장하는 이유

조회 때마다 계산하고 버리는 선택지도 있었다. 그렇게 하지 않는 근거는 팀이 cell_risk 를 버릴 때 쓴 기준 그대로다 — 그 표는 실제 관측률만 있고 모델 예측값이 없어 브라이어·로그손실·보정오차를 계산할 수 없었다. 내놓은 예보를 남기지 않으면 A18 도 같은 자리에 선다. 사이트 평가 규약의 지표는 전부 만석 여부 이진 사건에 대한 것이라 만석 확률 하나만 남겨도 사후 채점이 된다 — 분포 전체를 저장할 필요는 없다. expectedSeats 를 내보내려면 기댓값 하나가 더 필요할 뿐이다.

기존 stop_prediction 을 늘리지 않고 신설하는 이유는 단위가 다르기 때문이다. 저쪽은 (발행본, 정류장 순번)이라 한 정류장에 답이 하나인데, A18 의 답은 차량마다 다르고 같은 정류장에 대해 지평마다도 다르다. PK 에 차량과 지평을 더하면 레인 A 의 봉인 확인 3 (route_stop 대상 건수 = header 예상 = 자식 순번 집합)이 성립하지 않는다. 확장이 아니라 신설이 맞다.

셀 통계는 번들이 아니라 표에 둔다

셀 통계를 계수와 함께 불변 번들에 담는 방법을 먼저 검토했다. 그렇게 되면 표가 하나도 늘지 않고, v3 가 cell_risk 를 없애며 세운 "서버는 통계를 집계하지 않고 승인된 번들 하나만 실행한다"가 그대로 지켜진다. 그런데 셋을 따져 보면 담기지 않는다.

그래서 demand_profile 표를 둔다. 다만 이 결정에는 값이 따른다 — v8 도메인 문서의 "서버가 통계를 집계하지 않는다"는 문장이 실제로 움직인다. 그 문장을 세운 이유는 cell_risk 가 관측률만 있고 모델 예측값이 없어 채점이 불가능한 표였기 때문인데, demand_profile 은 채점 대상이 아니라 모델이 읽는 입력이라 성격이 다르다. 그래도 문장이 하나 바뀌는 것은 사실이므로 6절에 그대로 적어 두었다.

추론이 processor 에 있고 조립이 api 에 있는 이유

모델을 실행하는 곳은 processor 다. 그 정체성은 그대로 두는 편이 낫다 — api 가 추론하기 시작하면 요청 수만큼 모델이 돌고, 내놓은 값이 남지 않아 사후 채점이 불가능해진다. 다만 processor 의 트리거가 바뀐다: 시간대 경계(레인 A)가 멈추고 관측 적재(레인 B)가 들어온다. 레인 B 의 쓰기는 관측 INSERT 가 commit 된 별도 transaction 으로 한다 — 수집 경로의 4단계 차감 사다리는 그대로 두고, 모델 추론 시간이 수집 지연에 섞이지 않게 한다.

api 가 하는 일은 계산이 아니라 조립이다 — 최신 예보 행을 목표 순번으로 읽어 horizonStops 오름차순으로 담고, 승차 불가 정류장은 빈 배열로 두고, 스냅샷 poll 의 storedRowsvehiclesInService 로 붙인다. 읽기 전용이라는 경계는 유지된다. 다만 v8 이 적은 "모델이 무엇으로 바뀌어도 api 는 고칠 일이 없다"는 문장은 이번에 시험대에 오른다 — 정확히 말하면 모델이 바뀌어서가 아니라 응답 계약이 바뀌어서 고치는 것이고, 모델 계약(FullnessProbabilityModel)에 대한 의존은 여전히 없다. 그 구분을 흐리면 안 된다.

레인 B 결과에는 봉인·승격을 적용하지 않는다. 발행 원자성은 "한 판의 예보가 전부 유효하거나 전부 무효"를 보장하는 장치인데, 차량 예보에는 판이라는 단위가 없다 — 한 행이 정확히 한 관측에 매달리고 차량마다 독립이다. 차량 하나의 예보를 못 냈다고 나머지 차량의 예보를 막으면 오히려 계약을 어긴다. v3 의 "승차 가능 정류장 하나라도 실패하면 발행 전체 실패"는 레인 A 에만 걸리는 규칙으로 남는다.

vehiclesInService 와 운행 시간은 서로 다른 시계에 있다

확정 계약이 /board 에 값을 둘 더 요구한다. 둘 다 예보가 아니라 예보를 읽는 법에 관한 값이고, 나오는 자리가 서로 멀다.

vehiclesInService 는 관측 시계에 있다. 그 poll 에서 이 노선에 잡힌 서로 다른 차량 수이고, 스냅샷 poll 행의 storedRows 가 이미 그 값이라 새 조회도 새 열도 필요 없다 — 같은 poll 안 차량 중복은 부분 unique index 가 막으므로 저장 행 수가 곧 distinct 차량 수다. 이 값이 필요한 이유는 arrivals 가 빈 이유를 가르기 위해서다 — 0 이면 "운행이 끝났다" 쪽이고 0 보다 크면 "이 구간에 지금 차가 없다" 쪽이다. 다만 단정은 서버가 하지 않는다. 상류가 빈 응답을 주면 운행 중에도 0 이 되고(실측 22,010 스냅샷 중 1건, 0.005%), 심야에 빈 응답이 반복된 뒤 다음 수집에서 차량이 다시 나타난 실측도 있다. 그래서 판별자로 굽지 않고 정수 그대로 내보낸다.

방향별 첫차·막차는 기준정보 시계에 있다. 원천은 노선 기준정보의 upFirstTime·upLastTime·downFirstTime·downLastTime 이고, 받는 길이 둘이다 — 노선당 getBusRouteInfoItemv2 1콜이거나, 같은 값을 담은 노선 마스터 파일 한 벌이다. 사이트의 상류 판정은 마스터 쪽이지만(3,139개 노선을 1회 다운로드로 덮는다), 파이프라인 계약은 두 노선만 다루므로 API 1콜로 잡았다. 차량위치 poll 과는 다른 경로이므로 poll 예산에 얹히지 않는다. 저장 자리는 노선 판본 표다 — 거기 이미 기점·종점 이름과 회차 순번이 있고, contentDigest 대상에서는 빼서 시간표만 바뀌면 판본을 새로 끊지 않고 같은 행을 UPDATE 한다. 갱신은 노선당 하루 1회(KST 03:30)와 판본 신설 시점이다. 판본 신설 때만 부르는 것으로는 모자란다 — 정류장 목록이 그대로인 채 막차만 바뀌면 판본이 생기지 않아 시간표가 조용히 낡는다. 하루 2회는 위치 수집 옆에서 무시할 수 있고 예보 경로에 부하를 더하지 않는다.

방향마다 다르다는 것이 이 필드의 존재 이유다. 1650 은 상행 04:10~22:35 · 하행 05:25~23:55 로 막차가 80분 어긋나고, 3330 은 상행 04:50~23:30 · 하행 05:00~23:30 이다. 노선 하나를 통째로 "운행 종료"라고 말하면 그 사이 80분 동안 틀린다. 상류에는 평일·토·일·공휴일 4종의 요일패턴이 있는데 두 노선은 현재 네 값이 같아 차이가 드러나지 않는다 — 무엇을 내보낼지는 아래 선택지 ①이다.

갈리는 선택지 둘

위의 다섯(회차 키 저장 · arrivals 범위 · 셀 통계 위치 · 차량 예보 저장 · 조립 위치)은 근거가 한쪽으로 기울었거나 계약이 이미 닫아서 결론까지 갔다. 남은 둘은 아직 열려 있고, 어느 쪽을 골라도 A18 은 동작한다.

① 운행 시간의 요일패턴을 무엇으로 내보내나

오늘 요일의 값을 고른다권고
얻는 것
토·일 막차가 평일과 다른 노선이 붙어도 화면이 그대로 맞는다. 상류에 이미 4종이 다 있다
치르는 값
응답이 날짜에 의존한다. 캐시 키에 날짜가 들어가고, 공휴일 달력을 어디서 받을지가 따라온다
평일 값으로 고정한다
얻는 것
날짜 의존이 없다. 두 노선은 네 요일패턴의 값이 지금 같아서 차이가 0 이다
치르는 값
주말 막차가 다른 노선을 붙이는 날 조용히 틀린다. 화면은 그 오류를 드러내지 못한다

상류 마스터에서 공휴일(we*) 값은 사실상 일요일(sun*)의 복제다 — 양쪽 다 값이 있는 1,927개 노선 중 실제로 다른 것은 10개뿐이다. 요일패턴을 셋(평일·토·일)으로 줄여도 잃는 것이 거의 없다.

② 셀 통계 재계산을 누가 도나

배포와 함께 돈다
얻는 것
서빙에 쓰는 셀 값과 배포가 항상 짝이 맞는다. 재현이 쉽다
치르는 값
통계를 새로 하려면 배포를 해야 한다. 갱신 주기를 계수에 묶는 셈이라 표로 뺀 이유가 반쯤 사라진다
독립 일배치
얻는 것
자료가 쌓이는 속도로 갱신된다. 표로 뺀 이유가 온전히 산다
치르는 값
같은 배포가 날짜에 따라 다른 셀 값을 읽는다. 채점 때 trainedThrough 로 구간을 갈라야 한다

3. 두 레인의 공존 같은 관측에서 갈라져 스위치 앞에서 만난다

기존 7개 후보는 차량 상태를 쓰지 않고 A18 은 쓴다. 둘은 답하는 질문이 다르지만 같은 엔드포인트를 놓고 경쟁한다 — v3 와 달라진 점이 여기다. 그래서 공존 구조의 요점은 "둘이 나란히 서빙한다"가 아니라 "한쪽만 서빙하고 다른 쪽은 멈춘 채 남는다"이다.

두 레인은 공유 관측에서 갈라져 서빙 스위치 앞에서 만난다 GBIS 차량 위치외부 원천 · 1종 collector수집 + 회차 키 공유 관측두 레인이 같이 읽는다 /vehicles스키마는 안 바뀐다 레인 A · 정류장·시간대 예보 기존 7개 후보 · 차량 상태 미사용 processor · 발행상호작용 로지스틱 발행본 2표쓰기 동결 · 표는 남는다 레인 B · 차량 예보 A18 · 차량 상태 사용 · 신규 차량 추론 · A18봉인하지 않는다 차량 예보관측마다 · 채점 대상 demand_profile셀 통계 · 주기 재계산 지금은 끊겨 있다 서빙 선택 레인 A 는 멈춰 있다 실선이 지금 경로 /board관측마다
갈라지는 지점은 그대로 공유 관측이다. 차량위치 상류 호출도 collector 도 하나뿐이고, 두 레인은 같은 관측을 읽어 서로 다른 답을 만든다. v3 와 달라진 것은 오른쪽 끝이다 — 두 레인이 /board 라는 같은 출구를 놓고 만나는데, 지금 이어져 있는 것은 레인 B 하나다. 레인 A 는 발행 스케줄러가 멈춰 새 판을 만들지 않고 /board 도 그것을 읽지 않는다. /vehicles 는 어느 레인도 거치지 않고 관측에서 바로 나가므로 이 스위치와 무관하다.
레인 A — 정류장·시간대 예보레인 B — 차량 예보 (신규)
묻는 것이 정류장·이 시간대에 자리가 있나이 버스가 여기 도착할 때 자리가 있나
모델기존 7개 후보. 1차 선택은 상호작용 로지스틱 회귀A18 · 차량 상태 조건부 좌석 분포
입력노선 · 정류장 순번 · KST 시간대거기에 그 버스의 지금 잔여석과 운행 궤적을 더한다
학습 단위노선 범주 계수를 한 모델이 공유노선별. 계수 공유 없음
출력만석 확률 하나좌석 수별 확률 분포. 만석 확률은 그 분포의 0 지점
갱신시간대 경계 · 하루 4번. 지금은 멈춰 있다관측마다 · 08-18 실측 노선당 4,097회
저장forecast_publication·stop_prediction — 봉인 후 공개. 쓰기 동결이라 새 행이 생기지 않는다관측에 매달린 파생. 봉인 없음
저장량노선당 하루 340~445행노선당 하루 89만~98만행
실패 처리승차 가능 정류장 하나라도 실패하면 발행 전체 실패차량마다 독립. 잔여석이 결측이면 그 차량을 arrivals 에서 뺀다
수집이 끊기면영향 없다. 현재 차량은 추론 입력이 아니다기준 시각을 잃는다. NO_RECENT_OBSERVATION 503
/board 연결지금은 끊겨 있다. 되돌림 대상이어져 있다

왜 레인 A 를 지우지 않나

되돌릴 수 있는 변경으로 만들기 위해서다. 레인 A 의 발행 계층 — 표 둘, 봉인 확인 5종, CAS 승격, 시간대 경계 재발행 — 은 하나도 지워지지 않는다. 다만 계속 도는 것도 아니다. 표와 열은 그대로 남고 새 행만 만들지 않는다. 동결은 DDL 이 아니라 스케줄러 정지다. A18 이 새 평일 자료에서 무너지거나 운영이 감당되지 않으면 스케줄러를 다시 켜고 /board 가 읽는 자리를 되돌린다.

배포에 서빙 역할 열을 두지 않는다. 앞선 초안은 레인마다 ACTIVE 를 하나씩 두려고 model_deployment.servingRole 을 더하려 했는데, 그 초안은 "레인 둘이 동시에 서빙한다"를 전제했다. /board 가 A18 로 재구성되면서 서빙 레인은 하나가 됐고, 나눌 유일성이 사라졌다. 그래서 ACTIVE 는 부분 unique index 로 전역 1개인 채 그대로다. CAS 승격 로직도 그대로다 — expected_active_deployment_id 를 받아 정확히 한 행이 아니면 전체 rollback 이다. A18 을 철회할 때도 A18 배포를 RETIRE 하고 이전 배포를 다시 ACTIVE 로 올리면 되고, 그동안 유일성은 계속 성립한다.

다만 이 되돌림은 아직 진짜 되돌림이 아니다

두 가지가 걸린다. 첫째, 레인 A 는 한 번도 발행한 적이 없다. v3 가 이미 적었듯 적재할 계수 번들이 없어 processor 가 뜬 적이 없다. 한 번도 뜬 적 없는 경로를 안전장치라고 부를 수는 없다 — 먼저 한 번 떠야 한다.

둘째, 되돌림이 조용한 폴백이 아니다. 레인 A 가 내는 /board 는 v3 형태이고 레인 B 가 내는 것은 v4 형태다. 되돌리면 응답 구조가 함께 되돌아가므로, 프론트가 두 형태를 다 읽지 못하면 무중단이 아니다. 경로를 가를지 프론트가 둘 다 받을지는 아직 정해지지 않았다 — 7절.

나머지 6개 후보 모델은 제품 배포와 무관하게 후보 모델 현황판에서 계속 채점된다. 그쪽은 외부 ML 환경의 일이고 이 문서의 경계 밖이다. A18 도 아직 일일 평가 원장에 들어 있지 않다 — 지금 성적은 오프라인 산출이다.

4. 신규 노선 생애주기 중간 단계를 두지 않는다

A18 은 노선별로 학습하고 계수를 공유하지 않는다. 그래서 새 노선은 자기 이력이 쌓이기 전까지 예보를 낼 재료가 없다.

신규 노선 생애주기 — 상태는 둘뿐이다 노선 등록판본 생성 수집만status = PREPARING 관문노선 단위로 다시 잰다 예보 개시status = FORECAST_READY 미달 — 수집만 계속 노선 판본 개편 — 어디로 가는지 미결 두지 않는 것 품질 낮은 예보를 내는 중간 단계 부분 서빙 · 경고 딱지 · 베타 노출 상태가 아닌 것 밤에 arrivals 가 비는 것 — 정상 응답이다 vehiclesInService 로 가르고 status 는 그대로다
상태는 둘뿐이다. v4-fe 가 RouteStatus enum 을 늘리지 않은 것과 같은 결정이다 — 예보를 못 내는 차량은 arrivals 에서 빠질 뿐이고, 상태를 새로 만들지 않는다. 새 설계에서 특히 조심할 것이 오른쪽 아래다. v3 의 /board 는 발행본이 있으면 늘 값이 찼지만, v4 의 /board 는 첫차 전·막차 후에 arrivals 가 정상적으로 빈다. 그것을 상태로 승격하면 매일 밤 노선이 PREPARING 으로 떨어진다. 밤과 낮을 가르는 값은 status 가 아니라 vehiclesInService 와 방향별 막차 시각이다.

중간 단계를 두지 않는 이유

관문 — 무엇을 통과해야 하나

A18 이 채택될 때 통과한 검증을 노선 단위로 다시 적용하면 새로 정의할 것이 없다. 다만 문턱값은 아직 고정되지 않았다 — 아래 값들은 A18 이 두 노선에서 낸 실측이지 통과 기준이 아니다. 기준으로 쓰려면 결과를 보기 전에 고정해야 한다. 7절에 미결로 남겼다.

관문무엇을 재나A18 이 두 노선에서 낸 값
위조 시험미래 라벨을 뒤집었을 때 결과가 움직이는가변화 정확히 0 (대조군 0.788)
다중비교 생존본페로니 보정 뒤에도 이득이 남는가96개 시험군에서 M1 23/24 생존
이득 대 잡음관측 이득이 잡음 기대를 넘는가2.068 대 0.209
노선별 전환노선을 갈아도 기존 기준선을 이기는가36/36 셀
지평 12 커버율그 노선의 정류장 중 예보가 닿는 비율86.3% (하루 전체) · 운행 시간대 06~23시는 90.4%·91.1%
자료 조건평일 아침 만석 사건이 관측돼 있는가노선마다 새로 잰다

마지막 줄이 실질적으로 가장 센 관문이다. 학습 자료가 전부 휴일일 때 평일 아침 판별력은 0.518 에 머물고 휴일을 세 배로 늘려도 그대로인데, 평일 반나절이 들어오면 0.958 로 뛴다. 모자란 것은 모델이 아니라 그 노선의 자료다.

새 노선을 붙이려면 수집 예산부터 재야 한다

이것은 v3 에는 없던 관문이다. 확정된 수집 전략은 적응형(adaptive-kst-v1.0.1)이고 expected_daily_calls 는 3,816, 일일 한도는 10,000 이다. 그런데 08-18 실측은 노선당 4,097 poll(23시간 관측, 23시대 결측)이라 두 노선이면 8,194 다.

두 값이 어긋난다. expected_daily_calls 가 노선당인지 서비스키 전체인지, 08-18 자료가 어느 전략판으로 수집됐는지를 확인해야 한다. 어느 쪽이든 v3 계약 문서의 "60초 고정 · 3노선 하루 4,320회 · 한도의 43%"는 실물과 다르다. 세 번째 노선을 같은 밀도로 붙일 여지가 실제로 얼마인지가 관문 앞에 놓인다.

기존 노선의 판본이 개편될 때도 같은 자리에 선다

demand_profile 은 노선 판본에 매인다 — 순번의 뜻이 판본마다 다르고, v3 가 경계한 "현재 활성 판본을 과거 사건에 사후 부착"을 하지 않기 위해서다. 그래서 개편 직후에는 새 판본에 셀 행이 없고 그 구간을 마스킹 폴백이 받는다. 폴백만으로 며칠을 버틸 수 있는지는 아직 재지 않았고, 답에 따라 위 상태 기계의 점선 화살표가 정해진다 — PREPARING 으로 되돌릴지, FORECAST_READY 를 유지한 채 폴백으로 버틸지다.

5. 작업량 추정

계약 문서만 보고 낸 추정이다. 코드베이스를 열어 보지 않았으므로 기존 구현의 결합도에 따라 아래 값은 움직인다. 팀이 규모를 가늠하는 용도로만 쓰이길 바란다.

항목무엇을 만드나규모그렇게 본 근거
정원 상수 교정47석 고정을 차량별 관측 최대 잔여석으로. 계열 판정 없음0.5일특징 계약이 소유하므로 DB 도 계약도 안 바뀐다
노선 기준정보 확장방향별 첫차·막차 적재, 하루 1회 갱신 job, 요일패턴 대조1일상류에 이미 있는 값이라 파싱과 저장뿐이다. 노선 판본 갱신 경로가 이미 있고 호출 하나와 열 넷이 는다
회차 키열 하나, 적재 시점 판정, 정규화 판 연결1~2일판정 규칙이 v3 라벨 조건에 이미 있다. 되돌아가는 순번의 문턱값만 새로 정한다
궤적 조회 경로상류 좌석 기울기 · 직전 도착 차량 · 직전 연속 만석. 인덱스 2개3~5일규칙이 아니라 결손 처리가 비용이다 — poll 결손 · 순번 건너뜀 · 운행 경계
demand_profile표 하나, 집계 배치, 마스킹 폴백 사다리2~3일집계 자체는 SQL 하나다. 얇은 셀을 어디로 물리는지가 실제 설계 몫이다
번들 형식 확장노선별 블록, 열두 지평 계수 전부, 특징 계약 버전, golden vector2~3일기존 CoefficientBundle·TermKey·supportedScopeDigest 골격을 늘리는 일이다. 지평 격자가 다 찼으므로 담을 계수 집합이 확정값이다
A18 추론기허들 p0 + 앵커 + 부호 3분 + 크기 구간 골격에 6층4~6일층 구조가 전부 선형이라 수식은 무겁지 않다. 오프라인 산출과 자리를 맞추는 검증이 시간을 쓴다
차량 예보 저장표 하나, 관측 뒤 별도 transaction, 파티션 축과 보존 정책2~3일쓰기 경로는 단순한데 노선당 하루 89만~98만행이라 파티션·보존이 실제 일이 된다
발행 경로 동결발행 스케줄러 정지, /board 가 읽던 자리 제거, 죽은 경로 표시1일열도 DDL 도 바뀌지 않는다. 초안의 servingRole 열과 백필이 철회돼 이 항목이 그 자리를 대신한다
/board 재조립차량 → 정류장 피벗, 지평 창과 랩어라운드 제외, arrivals 정렬, vehiclesInService 집계, 승차 불가 정류장 처리, 모든 정류장 담기3~5일이번 안에서 새로 생긴 가장 큰 항목이다. 피벗 자체는 조인 하나지만 응답이 커져 직렬화와 정렬이 비용이 된다
/board 오류 계약NO_CURRENT_PUBLICATION 제거, NO_RECENT_OBSERVATION 신설, 부분 실패를 503 으로 올리지 않게 고침1~2일규칙이 줄어드는 쪽이라 구현은 가볍다. 시험을 다시 쓰는 것이 대부분이다
캐시·부하Cache-Control 수명 재설정, 응답 크기 대응, 동시 조회 경로 점검1~2일하루 4번 캐시에서 15초 캐시로 간다. 값이 바뀌는 횟수가 세 자릿수로 는다
검증golden vector · 결정성 · /board 계약 시험 신규 작성 · 마이그레이션 리허설3~4일회귀가 아니라 교체다. 기존 /board 시험은 대부분 못 쓴다
25~38일한 사람 기준. 추론기 · 궤적 조회 · /board 재조립 셋이 절반 가까이 된다

이전 v4-be 안의 추정은 18~29일이었다. 늘어난 몫이 /board 를 늘리는 대신 바꾸기로 한 값이다 — 재조립 3~5일 · 오류 계약 1~2일 · 캐시와 부하 1~2일 · 검증 1일 · 저장 정책 1일 · 노선 기준정보 1일이 더해지고, /vehicles 응답 조립 1~2일이 빠졌다.

가정 하나가 풀렸다 — 지평 격자

이전 판은 지평 5·7·9·10·11 에 자료도 성적도 없어서, 위 표가 "그 작업이 끝났다고 가정한다"고 적었다. 그 자료를 다시 만들어 지평 1~12 를 전부 적합·채점했다(569,260행). 브라이어와 만석 구분은 열두 지평 모두 단조이고, 종합 점수만 j8 에서 바닥을 친 뒤 다시 오르는데 보정 항목 몫이다. 그래서 번들 형식과 추론기 추정이 가정이 아니라 확정값 위에 서게 됐고, 남은 것은 실무 하나다 — 열두 지평의 계수를 모두 번들에 담는 것이며 위 표의 번들 형식 행이 그것을 센다.

이 추정에 들어 있지 않은 것

계수 번들 생산. 학습·평가·선정은 외부 ML 환경 소유이고, v3 도 "적재할 계수 번들이 아직 없다"고 적었다. A18 은 노선별이라 노선마다 블록이 필요하다. 이 작업은 서버 일정과 별개로 잡힌다.

프론트엔드 작업. /board 응답 구조가 바뀌므로 화면 쪽 수정이 함께 필요하다. v4-fe 가 그 범위를 적는다. 위 표는 서버만 센 것이다.

일일 평가 편입. A18 의 현재 성적은 오프라인 산출이다. 평가기에 들어가면 다른 후보들과 같은 원장을 쓰게 되는데, 그 작업은 평가 파이프라인 쪽 일이다.

과거 관측의 회차 소급 계산. 회차 키는 NULL 을 허용하므로 백필이 필요 없지만, 마이그레이션 이전 관측은 회차가 비어 A18 의 대상에서 빠진다. 소급은 정규화 판을 새로 매기는 일이라 별도 작업이다.

6. 하지 않아도 되는 것 팀이 이미 만든 것은 대부분 그대로 산다

/board 를 다시 만든다고 8/17 확정본이 뒤집히지 않는다. 무엇이 그대로이고 무엇이 실제로 움직이는지 나눠 적는다.

그대로 쓰이는 것

팀이 이미 만든 것어떻게 되나
collector 객체 20개유지 + 판정 1GBIS 호출·정규화·호출 장부·노선 판본이 그대로다. A18 은 예보를 내려고 새 상류를 부르지 않고 새 수집 필드를 요구하지 않는다. 적재 시점 회차 키 판정 하나와 판본 갱신 쪽 첫차·막차가 붙는다
call_ledger 쿼터 4단계전부 유지예약 차감 규칙과 resultCode=4 처리가 그대로다. 차량위치 poll 이 늘지 않으므로 예보 경로의 쿼터 계산은 v3 그대로다. 첫차·막차 호출이 노선당 하루 1회 더해지는데 엔드포인트는 달라도 서비스키가 같아 같은 장부에서 예약한다 — 하루 2회이고 한도 10,000회 옆에서 무시할 수 있다
노선 판본 유효기간 관리전부 유지활성 플래그가 아니라 valid_from/valid_to 로 관리하는 방식이 오히려 demand_profile 의 키가 된다
api 실시간 차량 객체 8개스키마 그대로/vehicles 는 조회 구조도 응답 필드도 그대로다. 이전 v4-be 안이 여기 선택 필드를 붙이려 했는데 그 계획이 사라졌다. 확정본이 손댄 것은 폴링 권고와 Observation 주석뿐이라 코드가 아니라 문서 쪽 일이다
/routes 계약스키마 그대로노선 목록의 필드는 이 변경과 무관하다. 다만 RouteStatus 의 뜻이 "유효한 봉인 발행본이 있다"에서 "활성 계수 묶음이 있고 관측이 들어오면 예보를 낸다"로 옮겨갔다 — enum 값 2종은 그대로이고 판정 근거를 읽는 자리가 발행본 표에서 번들 쪽으로 바뀐다
발행 계층 전체지우지 않는다forecast_publication·stop_prediction·봉인 확인 5종·CAS 승격·재발행 시점 셋이 열 하나 안 바뀌고 그대로 남는다. 다만 새 행을 만들지 않는다 — 동결은 DDL 이 아니라 스케줄러 정지라 되돌리려면 다시 켜면 된다
상호작용 로지스틱 회귀계속 산다폐기가 아니다. 레인 A 를 계속 담당하고 되돌림 대상이 된다. A18 과 답하는 질문이 다르다
나머지 6개 후보 모델전부 유지외부 ML 환경에서 같은 규약으로 계속 채점된다. 제품 배포와 무관하다
학습 라벨 조건유지 + 재사용오히려 A18 의 회차 판정이 이 정의를 그대로 쓴다. 새 규칙을 만들지 않는다
supportedScopeDigest유지 + 재사용지원 밖을 절편만으로 점수화하지 않기 위해 넣어 둔 필드가 지평 축을 하나 더 받는다. 열은 그대로다
만석 확률 저장 · 빈자리 확률 응답규약 유지DB 에 p_full 을 저장하고 서비스 계층에서 1 − p_full 로 뒤집으며 반올림하지 않는 규약이 레인 B 에도 그대로 적용된다
노선 ID 넷 분리전부 유지공개 ID·공급자 ID·표시명·모델 노선 키 구분이 A18 에서도 같다
RouteStatus enum 2값전부 유지4절의 결정에 따라 상태를 늘리지 않는다. 정의 문구는 v4-fe 확정본에서 이미 고쳐졌다 — "활성 계수 묶음이 있고 관측이 들어오면 예보를 낸다"로, 봉인 발행본을 가리키지 않는다
패키지 셋경계 유지collector·processor·api 의 책임 구분이 그대로다. 새 패키지를 만들지 않는다

실제로 움직이는 것

무엇
/board 응답 구조재구성이 문서에서 가장 큰 변경이다. 정류장당 값 하나에서 정류장별 도착 예정 차량 목록으로 바뀌고, 노선 수준에 vehiclesInService 와 방향별 첫차·막차가 붙는다. 프론트엔드 수정이 함께 필요하다
노선 판본 기준정보방향별 시각방향별 첫차·막차가 노선 판본 쪽에 새로 들어온다. 관측이 아니라 기준정보라 갱신 주기가 다르다 — 차량위치 poll 예산은 그대로이고 상류 호출만 노선당 하루 1회 는다. 2절
/board 의 원천과 가용성결합 신설발행본이 아니라 관측을 읽으므로 수집 신선도에 매인다. v3 의 "수집이 끊겨도 예보는 유효하다"가 /board 에서는 성립하지 않는다. /vehicles 와 서로 죽지 않는 성질은 유지된다
api 예보 객체 7개안을 다시 쓴다BoardController부터 StopView까지 이름은 남지만 읽는 표와 조립 구조가 바뀐다. 읽기 전용 경계는 유지된다 — 모델 계약에 대한 의존은 여전히 없고, 고치는 이유는 모델이 아니라 응답 계약이다
"서버는 통계를 집계하지 않는다"문장 개정v8 의 processor 절이 cell_risk 를 없애며 세운 문장인데, demand_profile 이 들어오면 서버가 경험 통계를 집계하게 된다. 다만 그 표는 채점 대상이 아니라 모델이 읽는 입력이라 cell_risk 와 성격이 다르다 — 2절에 근거 셋을 적었다
정원 47석 고정교정모델이 쓰는 정원은 그 차량이 보여 준 최대 잔여석 그대로다(2026-08-19 평가기 교정). 45/68 구간화를 없앴으므로 계열 판정이 없고 오판도 없다. 47은 어느 근거에도 없었다. 차량의 형식승인 좌석수는 이것과 다른 양이라 합치지 않는다. 셀 순수요가 이 값으로 나뉘므로 특징 계약 버전이 셀 키에 들어간다 — 값이 바뀌면 이전 행과 섞이지 않는다
발행 스케줄러정지forecast_publication·stop_prediction 에 새 행을 만들지 않는다. 표도 열도 그대로라 DDL 이 아니고 되돌리려면 다시 켠다. 초안이 더하려던 servingRole 열은 철회했다 — 서빙 레인이 하나라 ACTIVE 유일성을 나눌 이유가 없다
processor 트리거교체시간대 경계 트리거가 멈추고 관측 적재 트리거가 들어온다. ForecastJob 코드는 지우지 않는다
vehicle_observation열 +1 · 인덱스회차 키와 조회 경로. INSERT-only 는 유지된다
예보 저장량약 2,000배노선당 하루 340~445행에서 89만~98만행이 된다. 보존과 파티션이 설계 항목으로 올라온다

v2 초안에서 버린 것 — 어디까지 돌아오나

v3 가 v2 초안의 두 표를 버렸다. A18 은 그중 하나만, 그것도 다른 모습으로 필요로 한다.

v2 초안A18 에서는
demand_profile
정류장 × 30분 버킷 순수요
모습 바꿔 부활같은 이름이지만 같은 표는 아니다. 시간 축을 열에 박지 않고 셀 식별자 하나로 두어 셀 세분화 실험이 마이그레이션을 부르지 않게 했고, 적률 합 대신 1차 적률만 담는다 — A18 의 L_cell 이 분산을 읽지 않기 때문이다. 셋째 값인 폴백 지시자도 열이 아니다. 그 셀의 행이 있는지 없는지로 정해진다
stop_arrival_rate
대기열 유입률
안 돌아온다A18 은 대기열을 재료로 쓰지 않는다. L_segsum(순수요 구간합)이 그 값이 들어올 자리로 비어 있을 뿐이고, 지금은 장기 평균이 들어가 있다. 실시간 추정치가 생기면 구조를 바꾸지 않고 갈아 끼운다
4계층 12테이블안 돌아온다표는 몇 개 늘 뿐이고 계층 구조는 그대로다

v3 가 두 표를 버린 근거는 "모델이 상호작용 로지스틱 회귀라서 읽는 코드가 없다"였다. 읽는 코드가 없는 표를 계약에 남기지 않은 것은 맞는 판단이었고, 지금 바뀐 것은 그 판단이 아니라 전제다 — A18 의 L_cell 이 그 값을 직접 읽는다. 레인 A 에는 원래 근거가 여전히 그대로 유효하다.

7. 먼저 재야 할 것 코드보다 앞선다

작업량 추정보다 앞에 놓이는 확인이 있다. 결과에 따라 5절의 표가 흔들린다.

이전 판의 미결 셋이 해소됐다

지평 격자 구멍. 지평 5·7·9·10·11 에 표본이 없다는 것이 이전 판의 첫 미결이었다. 자료를 다시 만들어 지평 1~12 를 전부 적합·채점했다. 5절의 번들 형식과 추론기 추정이 이제 가정 위에 서 있지 않다.

RouteStatus·trainedThrough 문구. v4-fe 확정본에서 고쳐졌다. FORECAST_READY 는 봉인 발행본이 아니라 "활성 계수 묶음이 있고 관측이 들어오면 예보를 낸다"를 뜻한다.

짝 문서와 응답 모양의 어긋남. 이 개정이 v4-fe 2.0.0 쪽으로 맞췄다 — seatForecast 중첩과 viaTurnaround 를 전제한 서술을 걷어내고, vehiclesInService 와 방향별 첫차·막차를 도메인에 들였다. 확정된 쪽은 v4-fe 이고 이 문서가 따라간 것이다.

① 노선 앞머리 12개 정류장은 차고지 발차 시각 없이는 풀리지 않는다

순번 12 이하 정류장은 뒤로 볼 수 있는 칸이 sequence − 1 개뿐이라 지평 창이 짧다. 실측 커버율이 37~44% 로 떨어진다. 그 밖 구간은 98.7~98.9% 다. 해당 정류장이 두 노선 합쳐 전체의 13.8% 다.

문제는 커버율 자체가 아니라 그 빈칸을 "곧 오는 버스 없음"으로 읽으면 틀린다는 데 있다. 재어 보면 실제 다음 버스는 중앙 8.7~12.0분 뒤에 오는데, 그 차의 96.2~96.8% 가 차고지에서 대기 중이라 차량위치 API 에 아직 나타나지 않는다. 차량 관측만으로는 메울 수 없는 구멍이고, 메우려면 차고지 발차 시각이나 도착정보 API 같은 다른 원천이 필요하다. 둘 다 지금 수집 대상이 아니다.

서버가 지금 할 수 있는 것은 그 구간을 구분해서 내보내는 것까지다 — sequencevehiclesInService 만으로 클라이언트가 "곧 오는 버스 없음"과 "여기는 원래 안 보인다"를 가를 수 있다. 배차 간격을 함께 보여줄지는 화면 쪽 결정이다.

② 되돌림이 아직 되돌림이 아니다

레인 A 는 한 번도 발행한 적이 없다 — 적재할 계수 번들이 없어 processor 가 뜬 적이 없다. 안전장치라고 부르려면 먼저 한 번은 떠야 하고, 그것은 레인 B 보다 앞에 놓이는 일이다.

그리고 되돌림이 응답 형태를 함께 되돌린다. 셋 중 하나를 골라야 한다 — 프론트가 두 형태를 다 읽게 하거나, 경로를 갈라 둘을 동시에 서빙하거나, 되돌림을 계획된 중단으로 받아들이거나다. 아직 정해지지 않았다.

③ 수집 예산 서술이 실물과 다르다

v3 계약 문서는 "60초 고정 · 3노선 하루 4,320회 · 한도 10,000의 43%"라고 적었다. 실제 수집기는 적응형(adaptive-kst-v1.0.1 · 15초 / 20초 / 60초 / 600초)이고 설정값 expected_daily_calls 는 3,816 인데, 08-18 실측은 노선당 4,097 poll 이다.

세 값이 서로 맞지 않는다. 계약 문서를 실물에 맞추고, 남은 여유를 다시 세야 한다. 4절의 신규 노선 관문이 여기에 걸려 있다.

다만 이전 안의 걱정 하나는 여기서 해소됐다. 이전 v4-be 문서는 "A18 은 15초 자료로 배웠는데 제품은 60초라 성적이 유지되는지 재야 한다"를 첫 미결로 걸었다. A18 학습 자료는 제품 수집기가 그 적응형 전략으로 실제로 만든 것이라 학습 밀도와 서빙 밀도가 갈리지 않는다. 남은 것은 전략판 사이의 차이 확인뿐이다.

설계에서 아직 안 정한 것

v3 의 미결 셋(계수 번들 부재 · 학습 사건의 노선 판본 부재 · 제품 공개 노선 ID)은 그대로 남아 있다.

이 문서가 새로 잰 값과 그 근거

확정 사실에 없어서 원자료를 다시 센 값들이다. 자료는 2026-08-14~18 수집 기록 22,010건(두 노선)이고, 정류장 수는 1650 89개 · 3330 85개를 썼다. 측정 스크립트와 출력은 작업 기록과 함께 남겼다.

동시 관측 차량 수(05~24시 poll) — 1650 중앙 11 · 평균 14.7 · 최대 28, 3330 중앙 13 · 평균 15.2 · 최대 36. 여기에 지평 12를 곱한 것이 수집 한 번의 예보 계산량 상한이다.

예보 행 수(2026-08-18) — 차량 관측 81,864(1650)·73,936(3330) 에 지평 12를 곱해 982,368행 · 887,232행이다. 레인 A 의 노선당 하루 340~445행은 v3 계약의 "노선당 하루 4~5판"에 정류장 수를 곱한 값이다.

arrivals 항목 수(05~24시 poll) — 계약대로 지평 12 창으로 자르면 중앙 120(1650)·150(3330) 이고 한 정류장에 붙는 항목이 중앙 3·4 · 최대 9·12 다. 자르지 않으면 중앙 462·554, p90 1,020·1,039, 최대 1,223·1,420 이고 그중 지평 12 안에 드는 것이 26.5%·27.5% 뿐이다.

지평 창의 커버율 — 운행 시간대(06~23시) 전체 90.4%(1650)·91.1%(3330). 순번 12 이하 정류장에서 37~44% 로 떨어지고 그 밖은 98.7~98.9% 다. 해당 정류장은 두 노선 합쳐 전체의 13.8%(24/174)다. 그 구간에서 실제 다음 버스는 중앙 8.7~12.0분 뒤에 오는데 96.2~96.8% 가 아직 차량위치 API 에 없다. 한 바퀴 돌아 오는 차량이 실제 다음 도착 차량인 경우는 5.1%·15.6% 이고 그 앞에 중앙 4~7대가 먼저 지나간다.

회차 대기(재구성한 여정 경계) — 중앙 75.3분(1650)·94.0분(3330). 1650 을 여정 종료 시각대로 가르면 07시 106.7분 · 12시 84.0분 · 17시 59.6분 · 21시 46.0분으로 절반 아래까지 내려간다(중간에 되오르는 시간대도 있다). 밤 공백은 중앙 487분(1650)·462분(3330)이다. 여정 종료 순번은 1650 이 88 에 299건으로 몰리고, 3330 은 84 에 317건 · 83 에 73건인데 회차 순번 언저리(43)에서 끝나거나 42 에서 시작하는 여정이 각각 1건씩 있다.

poll 수(2026-08-18) — 노선당 4,097. 시간대별로 심야 6회/시(600초), 낮 180회/시(20초), 아침·저녁 240회/시(15초)다. 23시대 기록이 없어 23시간분이다.

지평 격자 — 지평 1~12 를 전부 적합·채점했다(569,260행). 채점 표본은 j1 47,384 에서 j12 39,840 까지 지평이 멀어질수록 줄어든다. 이전 판의 자료는 1·2·3·4·6·8·12·16 여덟 개뿐이었다(379,455행).

이 문서가 전제로 삼은 A18 의 확정 사실

정체 — 지금 오고 있는 그 버스가 목표 정류장에 도착할 때 잔여좌석이 몇 석일지를 분포로 예측한다. 만석 확률은 그 분포의 0 지점이다.

구조 — 허들 p0 + 앵커 + 3방향 부호 + 크기 구간 골격에 6층 (L_cell 셀 점유율·순수요 z점수 / L_spline 정류장 위치 8매듭 / L_segsum 구간합 수요 / L_p0shift 인과 사전확률 이동 / L_anchor 지평 적응 기울기 / L_relbins 상대 구간). L_support 는 제거했다 — 가용 질량이 16개 셀 전부에서 0.000000 이었다.

학습 단위 — 노선별. 계수 공유 없음.

성적 — 사이트 평가 규약·같은 지표·같은 점수 정책으로 오프라인 산출. 지평 1~12 를 전부 적합·채점했다. 종합 점수는 j1 90.3 · j2 83.2 · j4 77.5 · j8 69.3 · j12 71.6 이고 j16 은 67.5 다. 브라이어(.00285 → .01242)와 만석 구분(.929 → .420)은 열두 지평 모두 단조로 나빠지며, 종합 점수만 j8 에서 바닥을 치고 다시 오르는데 그것은 보정 항목 몫이다. 지평 12 로 확정했다 — 8→12 는 커버율이 79.2%에서 86.3%로 오르고 향상배수가 31에서 24로 내리는 교환인데, 12→16 은 커버율이 1.1%p 느는 데 향상배수가 17까지 떨어져 기각했다. 지평 12 의 종합 점수가 8보다 높은 것은 확률맞춤이 60.6에서 89.1로 뛴 탓이고 만석구분은 계속 떨어진다. 일일 평가 원장에는 아직 들어 있지 않다.

관측 한계 — 입석 없음이 확정이라 잔여석 0 = 승차 불가다. 승하차 분리 관측은 불가능하고 만석 포화 시 대기열은 관측되지 않는다.

쓰지 않는 재료 — 실제 경과 시간(누출) · 배차 간격(0.41석 해로움) · 도착 예정 API(오라클 이득 0.045석). 도착 예정 API 를 수집하지 않으므로 "몇 분 후"를 낼 수 없다 — v3 도 못 준다.

상세는 A18 모델 문서에 있다.