/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 | 결합 신설 |
| 상류 API | GBIS 차량 위치 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 열이 아니라 번들의 특징 계약이 소유한다.
이전 안은 "A18 을 /vehicles 에 선택 필드로 붙인다"였다.
/board 를 손대지 않는 것이 그 안의 핵심이자 안전장치였고, 그래서 되돌리기가 쉬웠다.
지금 안은 반대다. /board 를 A18 에 맞춰 재구성한다.
뒤집은 근거는 화면 쪽에 있다 — 시간대 평균은 A18 이 이미 재료로 쓰는 값이라
따로 내보내면 재료를 덜 가공해서 내보내는 것이 된다(v4-fe).
대신 값을 하나 치른다. 프론트엔드 수정이 필요하고, 되돌림이 조용한 폴백이 아니라 계약 되돌림이 된다 — 7절.
2. 모듈 경계 주기 · 상태 · 지연 예산으로 가른다
A18 은 지금 파이프라인에 없는 네 가지를 요구한다 — 회차 키 판정 · 셀 통계 집계 · 차량별 추론 · 정류장 조립이다. 넷은 도는 주기가 서로 다르고, 같은 자리에 놓으면 빠른 쪽이 느린 쪽에 끌려간다. 여기에 계약이 하나를 더 얹는다 — 방향별 첫차·막차는 A18 이 재료로 쓰지 않는데 응답에는 실린다.
| 회차 키 판정 | 셀 통계 집계 | 차량별 추론 | 정류장 조립 | |
|---|---|---|---|---|
| 하는 일 | 같은 차량의 연속 관측을 한 운행으로 묶는다 | (노선 판본, 정류장, 시간대) 셀마다 평균 점유율·순수요와 그것을 만든 표본 수·날짜 수를 센다 | 차량 하나에 지평 1~12 의 도착 시 좌석 분포를 낸다 | 차량별 예보를 정류장별 arrivals 로 뒤집고, 그 poll 의 storedRows 를 vehiclesInService 로 붙인다 |
| 주기 | 관측 적재마다 | 자료가 쌓이면 다시 | 관측 적재마다 | 요청마다 |
| 입력 범위 | 직전 관측 하나 — 좁다 | 학습 기간 전량 — 넓다 | 그 차량의 궤적 + 셀 통계 | 그 노선의 최신 예보 행들 |
| 상태 | 있다. 직전 순번을 알아야 경계를 잡는다 | 없다. 스캔 한 번의 순수 집계다 | 있다. 같은 여정의 앞선 관측을 봐야 기울기가 나온다 | 없다. 읽어서 정렬하고 담는다 |
| 지연 예산 | 초 — 다음 poll 전에 끝나야 한다 | 분~시간 — 아무도 기다리지 않는다 | 초 — 다음 poll 전에 끝나야 한다 | 밀리초 — 사람이 기다린다 |
| 놓을 자리 | collector | processor 주기 배치 | processor | api |
/board 가 방향별 첫차·막차를 함께 내보내는데,
그 값은 관측이 아니라 노선 기준정보라 노선 판본 쪽 속도로 돈다.회차 키를 적재 시점에 정하는 이유
A18 은 "j정류장 전 그 버스의 좌석"과 "앞선 정류장들에서 좌석이 줄던 속도"를 쓴다. 둘 다 같은 차량의 같은 운행 안에서만 뜻이 있다. 한 차량이 하루에 노선을 여러 번 돌면 같은 순번을 여러 번 지나므로, 운행을 나누지 않으면 이번 운행의 9번 정류장과 다음 운행의 17번 정류장이 짝지어진다.
조회 때마다 관측을 거슬러 훑는 방법도 있다. 그렇게 하지 않는 이유는
학습 쪽과 서빙 쪽에 같은 분할 로직이 두 벌 생기기 때문이다.
둘이 어긋나는 순간을 아무도 못 잡고, 오프라인 성적이 온라인에서 재현되지 않는다.
적재할 때 한 번 정해 열에 남기면 그 위험이 사라진다.
분할 규칙은 location_poll.normalizationVersion 에 속한다 — 새 규칙은 새 정규화 판이다.
판정 규칙 자체는 새로 정하지 않아도 된다. v3 의 학습 라벨 조건에 "같은 차량 · 회차"가 이미 들어 있다.
다만 그 판정을 연구 export 가 수행했을 뿐 저장하지 않았고, A18 은 서빙 시점에도 같은 판정을 해야 한다.
poll 이 끊겨 직전 관측을 잃으면 경계를 놓칠 수 있는데, 그때는 보수적으로 끊는다.
여정을 끊어도 예보는 낸다. 설계행렬이 상류 좌석 기울기와 직전 좌석에 각각 결측 지시자 열을 갖고 있어,
여정의 첫 관측이든 vehicleId 가 없는 관측이든 같은 방식으로 점수화된다.
여정 키가 없으면 라벨 회수만 못 한다.
예보를 못 내는 자리는 따로 있다 — 잔여석이 결측인 관측이다.
설계행렬이 잔여석을 세 열로 그대로 쓰고 앵커도 거기서 출발하는데,
그 자리에는 기울기·직전 좌석과 달리 결측 지시자가 없다.
예보를 내지 않는다는 것은 그 항목을 담지 않는다는 뜻이다.
v4-fe 확정본에서 seatAvailableProbability 는 StopArrival 의 필수 필드라
"예보 없는 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 를 없애며 세운
"서버는 통계를 집계하지 않고 승인된 번들 하나만 실행한다"가 그대로 지켜진다.
그런데 셋을 따져 보면 담기지 않는다.
- 갱신 주기가 다르다. 계수는 배포할 때만 바뀌고 셀 통계는 자료가 쌓이면 다시 계산된다. 번들에 넣으면 통계 재계산 한 번마다 배포 episode 가 통째로 한 판씩 돈다. 위 그림에서 자료 시계에 있는 것을 발행 시계로 옮기는 셈이다.
- 마스킹 폴백에 표본 수가 필요하다. 셀이 얇으면 더 거친 셀로 물러나는데, 그 판정 재료인 표본 수가 관측과 함께 계속 늘어난다. 배포 시점에 굳힐 수 있는 값이 아니다.
- 학습값과 서빙값이 애초에 다르다. 학습은 LODO 교차적합이라 폴드마다 값이 다르고, 서빙은 전체 적합값 하나를 쓴다. 폴드별 값은 외부 ML 환경의 것이고 제품 DB 에 들어올 것이 아니다.
그래서 demand_profile 표를 둔다. 다만 이 결정에는 값이 따른다 —
v8 도메인 문서의 "서버가 통계를 집계하지 않는다"는 문장이 실제로 움직인다.
그 문장을 세운 이유는 cell_risk 가 관측률만 있고 모델 예측값이 없어 채점이 불가능한 표였기 때문인데,
demand_profile 은 채점 대상이 아니라 모델이 읽는 입력이라 성격이 다르다.
그래도 문장이 하나 바뀌는 것은 사실이므로 6절에 그대로 적어 두었다.
추론이 processor 에 있고 조립이 api 에 있는 이유
모델을 실행하는 곳은 processor 다. 그 정체성은 그대로 두는 편이 낫다 — api 가 추론하기 시작하면 요청 수만큼 모델이 돌고, 내놓은 값이 남지 않아 사후 채점이 불가능해진다. 다만 processor 의 트리거가 바뀐다: 시간대 경계(레인 A)가 멈추고 관측 적재(레인 B)가 들어온다. 레인 B 의 쓰기는 관측 INSERT 가 commit 된 뒤 별도 transaction 으로 한다 — 수집 경로의 4단계 차감 사다리는 그대로 두고, 모델 추론 시간이 수집 지연에 섞이지 않게 한다.
api 가 하는 일은 계산이 아니라 조립이다 — 최신 예보 행을 목표 순번으로 읽어
horizonStops 오름차순으로 담고, 승차 불가 정류장은 빈 배열로 두고,
스냅샷 poll 의 storedRows 를 vehiclesInService 로 붙인다.
읽기 전용이라는 경계는 유지된다. 다만 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 와 달라진 점이 여기다. 그래서 공존 구조의 요점은 "둘이 나란히 서빙한다"가 아니라 "한쪽만 서빙하고 다른 쪽은 멈춘 채 남는다"이다.
/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 은 노선별로 학습하고 계수를 공유하지 않는다. 그래서 새 노선은 자기 이력이 쌓이기 전까지 예보를 낼 재료가 없다.
RouteStatus enum 을 늘리지 않은 것과 같은 결정이다 —
예보를 못 내는 차량은 arrivals 에서 빠질 뿐이고, 상태를 새로 만들지 않는다.
새 설계에서 특히 조심할 것이 오른쪽 아래다. v3 의 /board 는 발행본이 있으면 늘 값이 찼지만,
v4 의 /board 는 첫차 전·막차 후에 arrivals 가 정상적으로 빈다.
그것을 상태로 승격하면 매일 밤 노선이 PREPARING 으로 떨어진다.
밤과 낮을 가르는 값은 status 가 아니라 vehiclesInService 와 방향별 막차 시각이다.중간 단계를 두지 않는 이유
- 절편만 남은 예측은 예보가 아니다. 노선별 학습이므로 새 노선에는 셀 통계가 없고,
그러면 결측 지시자가 켜진 채로 마스킹 폴백만 돈다. 그때 나오는 값은 "그 노선의 평균"조차 아니다.
v3 도 같은 판단을 이미 적었다 — 세 번째 노선은 수집만 시작하고 예보는 내지 않으며,
미지원 노선을 절편만으로 점수화하면 근거 없는 확률이 된다.
supportedScopeDigest가 그 경계를 이미 봉인하고 있다. - 차량 상태만으로는 못 버틴다. A18 이 차량 상태를 쓴다고 해서 셀 통계 없이 도는 것은 아니다. 정보원이 지평에 따라 옮겨 간다 — 학습에 쓰지 않은 하루(8/18)에서 재면 차량 상태의 설명력이 지평 1에서 R² 0.958 인데 지평 8에서 0.179, 지평 12에서 0.002 까지 무너지고, 정류장 상태는 0.336 에서 0.310 으로 거의 그대로다. 결합하면 지평 8에서 0.442, 지평 12에서 0.337 이다. 먼 지평일수록 새 노선이 갖지 못한 쪽에 의존한다.
- 채점 원장이 오염된다. 배포 ID 가 바뀌면 전후 데이터를 갈라 채점하는 것이 계약이다. 품질 단계를 중간에 끼우면 같은 배포 안에 성격이 다른 구간이 섞여 어느 구간을 잰 것인지 알 수 없게 된다.
- 한 번 잃은 신뢰는 좋은 예보로도 잘 돌아오지 않는다. 이 서비스가 파는 것은 만석 경보 하나이고, 그것이 틀리면 다음 예보도 안 본다.
관문 — 무엇을 통과해야 하나
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 vector | 2~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개 | 유지 + 판정 1 | GBIS 호출·정규화·호출 장부·노선 판본이 그대로다. 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 이하 정류장은 뒤로 볼 수 있는 칸이 sequence − 1 개뿐이라 지평 창이 짧다.
실측 커버율이 37~44% 로 떨어진다. 그 밖 구간은 98.7~98.9% 다.
해당 정류장이 두 노선 합쳐 전체의 13.8% 다.
문제는 커버율 자체가 아니라 그 빈칸을 "곧 오는 버스 없음"으로 읽으면 틀린다는 데 있다. 재어 보면 실제 다음 버스는 중앙 8.7~12.0분 뒤에 오는데, 그 차의 96.2~96.8% 가 차고지에서 대기 중이라 차량위치 API 에 아직 나타나지 않는다. 차량 관측만으로는 메울 수 없는 구멍이고, 메우려면 차고지 발차 시각이나 도착정보 API 같은 다른 원천이 필요하다. 둘 다 지금 수집 대상이 아니다.
서버가 지금 할 수 있는 것은 그 구간을 구분해서 내보내는 것까지다 —
sequence 와 vehiclesInService 만으로 클라이언트가 "곧 오는 버스 없음"과
"여기는 원래 안 보인다"를 가를 수 있다. 배차 간격을 함께 보여줄지는 화면 쪽 결정이다.
레인 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 학습 자료는 제품 수집기가 그 적응형 전략으로 실제로 만든 것이라 학습 밀도와 서빙 밀도가 갈리지 않는다. 남은 것은 전략판 사이의 차이 확인뿐이다.
설계에서 아직 안 정한 것
- 운행 시간의 요일패턴. 2절 선택지 ①이다. 상류에 평일·토·일·공휴일 4종이 있는데 계약은 방향당 첫차·막차 하나씩만 받는다. 두 노선은 현재 네 값이 같아 차이가 드러나지 않는다.
- 좌석이 결측인 관측의 서빙 처리.
remainSeatCnt = −1이면 설계행렬을 채울 수 없어 그 차량이 항목째 빠진다. 전수 22,010 스냅샷에서 차량행 314,688 중 237행(0.075%)이고 판의 1.10% 가 그런 차량을 하나 이상 담으며, 한 차량에 연속 18회까지 이어진 사례가 있다./vehicles는 같은 차량을kind: UNKNOWN으로 계속 보여 주므로 두 화면이 어긋난다. 직전 관측의 좌석으로 대신할지,crowdedCode로 거친 추정을 할지, 지금처럼 빼 둘지 정해야 한다. - 차량 예보 보존 기간과 파티션 축. 노선당 하루 89만~98만행이라 이 표가 파이프라인에서 가장 빨리 자란다. 채점에 필요한 창과 저장 비용의 절충이 필요하다.
demand_profile재계산 주기와 주체. 2절 선택지 ②다. 배포와 함께 돌지 독립 일배치일지 미결이다.- 노선 판본 개편 시 상태 전이. 4절 상태 기계의 점선이다. 폴백만으로 며칠을 버틸 수 있는지에 답이 달렸다.
- 노선 관문의 문턱값. 4절 표의 값들은 A18 이 낸 실측이지 통과 기준이 아니다. 채택 규칙은 모델 채택용으로 만든 것이라 노선 관문으로 옮기려면 문턱을 결과 보기 전에 고정해야 한다.
- 여정 분할 문턱. 순번이 줄어드는 것을 새 여정의 신호로 쓰는데, 상류 지터로 순번이 잠깐 되돌아가는 경우와 구별할 문턱값이 정해지지 않았다. 가르는 것은 여정이지 회차가 아니다 — 회차는 여정 안의 한 점이다.
- 회차지 부근에서 밤을 넘기는 차량. 3330 은 재구성한 여정 중 회차 순번 언저리(42~43)에서 끝나고 다시 시작하는 것이 있다. 순번이 차고지로 돌아가지 않은 채 밤 공백이 들어오므로 여정 경계와 운행 시작 판정이 순번만으로는 갈리지 않는다. 밤 공백의 중앙이 1650 487분 · 3330 462분이라 시간 문턱으로 가를 여지는 있는데, 그 값이 아직 없다.
- 회차 대기가 시간대에 따라 변한다. 1650 기준 회차 대기 중앙이 07시 106.7분에서 21시 46.0분까지 내려간다(전체 중앙 75.3분). 여정 경계를 시간 문턱 하나로 고정하면 아침에는 한 여정을 둘로 쪼개고 밤에는 둘을 하나로 붙인다. 문턱을 시간대별로 둘지, 아예 시간이 아닌 다른 신호로 가를지가 열려 있다.
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 모델 문서에 있다.
관련 문서
이 문서는 검토 요청안이다. 확정 계약은 v3 이고, 여기서 제안한 어느 항목도 팀 합의 전에는 적용되지 않는다.