변경 이력 — v1 → v2

2026-08-12 · 명세 검토와 FE 피드백 반영. 큰 변경 6개, 나머지는 표.

1보드를 운행 중 / 운행 종료로 나눔

V1 — 보드는 한 모양
{
  "route": { … },
  "freshness": { "state": "OUT_OF_SERVICE", … },
  "vehicles": [],
  "stops": [ /* 전부 '차량 없음'으로 채움 */ ]
}
V2 — kind로 갈림
{ "kind": "OPERATING",
  "route": …, "freshness": …,
  "activeVehicles": […], "stops": […] }

{ "kind": "OUT_OF_SERVICE",
  "route": …,
  "lastObservedAt": "2026-08-10T13:01:44Z" }

운행 종료 보드의 정류장 상태는 전부 같은 채움값 — 정보가 없음. 상태가 다른 게 아니라 응답의 종류가 다른 것이라 타입으로 분리.

명세에서 보기 →

2freshness 상태 enum 제거

V1 — 서버가 상태를 적음
"freshness": {
  "state": "FRESH | STALE | OUT_OF_SERVICE | UNKNOWN",
  "observedAt": "… | null",
  "staleAt":    "… | null"
}
V2 — 시각만 주고 판정은 클라이언트
"freshness": {
  "observedAt": "2026-08-10T22:30:12Z",
  "staleAt":    "2026-08-10T22:33:12Z"
}
// 낡음 = 지금 > staleAt
// 지금 = Date 헤더 + 경과 (+ Age 보정)

신선도와 운행 여부는 독립 정보인데 한 enum에 묶여 있었음. 본문의 STALE은 ETag가 같으면 304만 오가 갱신되지 않는, 지켜질 수 없는 값. UNKNOWN은 생산 조건 없음 — 보드가 없으면 404.

명세에서 보기 →

3features 2개로 축소

V1 — 4종, 조합이 응답과 불일치
"features": [
  "SEAT_FORECAST",
  "BOARDING_VERDICT",
  "QUEUE_ESTIMATE",
  "UPSTREAM_RECOMMENDATION"
]
V2 — 2종
"features": [
  "SEAT_FORECAST",   // 판정 포함
  "QUEUE_ESTIMATE"
]

판정은 예보와 한 계산 — 따로 켜고 끌 수 없음. v1 예시(1650, 예보만 켜짐)는 스키마로 표현 불가한 조합. UPSTREAM_RECOMMENDATION은 v1 범위 밖.

명세에서 보기 →

4방면 이름 교정

V1 — 정의와 반대
{ "id": "UP",   "name": "도촌동 방면" }
{ "id": "DOWN", "name": "안양역 방면" }
V2 — UP = 기점→회차
{ "id": "UP",   "name": "안양역 방면" }
{ "id": "DOWN", "name": "도촌동 방면" }

그대로 구현하면 사용자에게 반대 방면 표시. Direction enum에 의미 서술 추가.

명세에서 보기 →

5필드명 변경

V1
"vehicles": [ … ]

"queueEstimate": {
  "estimatedPeople": 13.7, … }
V2 — 이름이 내용을 말함
"activeVehicles": [ … ]

"queueEstimate": {
  "pointEstimate": 13.7, … }

vehicles는 '운행 중인 차량만 담긴다'는 범위가 이름에 없음. estimatedPeople은 점추정(보조값)임이 드러나지 않아 주 표시로 오용될 여지 — 주 표시는 display.

명세에서 보기 →

6발행을 행 저장으로 전환 (내부 계약)

V1 — 보드 JSON 통짜
published_boards
  └ payload  "{ …보드 전체… }"
V2 — 행으로 발행, api는 조립만
board_publication   발행 메타
board_vehicle       차량
prediction_log      예보·판정 = 채점 로그
board_queue         대기열
board_gap           없는 자리의 사유

화면에 나간 값 = 채점 기록(같은 행). 확정된 DB 설계(v3) 반영.

pipeline 명세 보기 →

그 외 변경

항목v1v2이유
BoardRoutestatus·turnSequence 추가보드만으로 학습 중·중단 구분, 회차 지점 표시
InsufficientProfilesamples(integer)만vehicleRef 추가, samples는 가중 표본량(number)접근 차량 선택을 FE가 다시 하지 않게
SeatDataUnavailable.vehicleRef연결 설명 없음activeVehicles 연결·존재 보장 명시참조 규칙 명문화
vehicleRef 형식미정의pattern vehicle-[0-9]+, 위치 순 부여, 응답 간 비교 금지"보드 안에서만 유효"를 지키는 규칙
elapsedMinutesnumberinteger발행 시 반올림. 실제 출력과 일치
freshness.observedAt·staleAtnullablenon-nullnull은 운행 종료뿐 → 보드 분리로 소멸. 생성 타입 변경(파괴적)
대기열 UNAVAILABLE 사유6종5종OUT_OF_SERVICE는 보드 종류가 표현
Verdict·lowConfidence설명 부족값별 정의 보강TIGHT = 줄 위치에 따라 갈림
/routes ETag규칙 없음본문 콘텐츠 해시재검증 구현을 하나로
board 429미선언선언목록과 같은 제한 정책
routeId pattern최대 51자40자 이내저장 한도와 일치
예시계산상 불가능한 조합 3건재작성 + LEARNING 예시 추가판정 규칙·최근접 차량 규칙에 맞게
upstream 명세실측 의미론 추가좌석값은 도착(승차 전) 상태, crowded 0은 센서 없음
operations 명세변경 없음

계약 반영 완료. DB 실물(v3 다이어그램·Flyway DDL) 후속 4건: route_feature enum 축소, gap_reason에서 OUT_OF_SERVICE 제거, gap vehicle_ref의 INSUFFICIENT_PROFILE 허용, gap samples를 number로.