Core domain

다섯 객체. 각자 한 문장만 책임집니다.

그림 세 장이 설계의 전부다. 누가 무엇을 아는지(책임과 관계), 질문 하나가 어떻게 답이 되는지(흐름), 이 그림을 진짜 API에 통과시킨 결과(실측 대조). 카드를 누르면 상세가 열리고, 🎯 판정 카드는 확대 페이지로 이어진다.

그림 1 — 책임과 관계

누가 무엇을 아는가

변하지 않는 지식
쌓이는 사실
계산되는 답
카드 안의 칸품고 있다 (같이 저장·같이 삭제) 점선 이름(ID)만 안다

그림 2 — 흐름

질문 하나가 답이 되기까지

실측 값(8/13 저녁, 3330 농수산물시장) 그대로다.

🙋 사용자 "농수산물시장인데,
다음 버스 탈 수 있어?"
📸 관측 방금 찍은 노선 사진을
가져온다
차량 20대 · 만석 5대
🚌 관측에게 묻는다 "나를 방금 지나간
버스는?"
v411 · 잔여 0석
🪑 만석 정책 잔여 0 = 만석 —
미리 붙여둔 라벨을 읽는다
FULL
🎯 판정 "앞차가 만석이면
탈 확률 46.6%"
근거·시각과 함께 기록

핵심은 두 가지다 — 예측이 아니라 방금 지나간 버스의 관측으로 답하고, 답은 항상 근거(어느 관측, 어느 모델)와 함께 남아서 나중에 그대로 재현할 수 있다.

그리지 않는 화살표

한 번쯤 긋고 싶어지는 관계 네 개

🚫
버스.위치

위치는 버스의 속성이 아니라 그 순간의 사실. 필드로 넣으면 과거가 사라져 백테스트가 불가능해진다.

🚫
노선.차량목록

"지금 이 노선을 달리는 차"는 관측이 말해준다. 배속 명단은 실측에서 불완전했다(16대 중 3대 누락).

🚫
정류장.노선목록

"여기 어떤 노선이 서나"는 검색(질의)이지 필드가 아니다. 필드로 두면 노선 개편 때마다 전 정류장을 고쳐야 한다.

🚫
관측.차량객체

관측은 차량의 이름(ID)만 안다. 객체를 물면 나중에 정원이 바뀔 때 과거 관측의 의미까지 따라 변한다.

전체 필드표 펼쳐 보기 (스키마의 원형)
객체필드관계근거
Route 🗺️id · displayName"3330". 외부 ID는 어댑터 매핑표 뒤에
plan요일 4패턴 첫막차·배차 — 「몇 분」 환산 상수
stops (경로)품는다순번(1-based) + 정류장 ID의 순열 + 회차 순번(turnSeq). 상·하행이 한 축(3330은 1..85, 회차 43) — 방면은 「순번 < 회차」→상행으로 유도(실측 197,240행 전건 성립)
Stop 🚏id · name · at(좌표)정류장은 안 움직이니 좌표가 속성. 노선 역참조 금지
Vehicle 🚌id · capacity정원 3형태(등재 45·46·69·71 / 관측하한 41+ / 미상). 위치·잔여석 없음
RouteSnapshot 📸route(ID)이름만마스터 갱신이 과거 관측을 오염시키지 않도록
observedAt시각은 사진 한 장 단위(GBIS가 차량별 시각을 주지 않음)
observations[]품는다차량ID(이름만) + 마지막 통과 정류장 + 잔여석(모름≠0). 0대도 사실
Assessment 🎯query · judgedAt이름만노선ID + 정류장 순번 + 주입된 판정 시각
확률·몇대·몇분품는다미추정은 0이 아니라 사유 있는 "안 함"
lineage품는다 + 이름만모델 버전(품음) + 근거 관측 범위(ID) — 재현·비교의 좌표

그림 3 — 실측 대조

이 그림을 진짜 API에 통과시켰다

위치 응답 201건, 마스터 262,984행, 20일 관측 102,197건 전수 대조. 다섯 카드와 화살표(경계)는 전건 통과했다 — 고칠 것은 경계가 아니라 그 위의 규칙 여섯 개였다.

📡 위치 API → 번역 → 📸 RouteSnapshot 201/201 무손실

폴링 1번 = 사진 1장·시각 하나 — 응답도 배치 시각 하나만 주니 도메인과 같은 모양이다. 규칙 밖 상태코드 0건, 스냅샷 안 차량 중복 0건.

🗄️ 마스터 6종 → 적재 → 🗺️ Route · 🚏 Stop · 🚌 Vehicle 262,984행 전수 매핑

순번 1..N 완전연속이 3,139개 전 노선에서 성립. 중복 컬럼(정류장 이름·좌표 사본)은 원본과 불일치 0이라 버려도 안전.

⏱️ 도착 API → 🚪 코어 밖 → 화면 ETA 표시 전용 사진으로 번역 금지

모양은 완벽한 사진으로 번역되지만(축이 같음을 실측) 내용은 접근 중 2대뿐 — 핵심 입력인 "막 지나간 버스"가 27초 만에 응답에서 사라지는 것을 실측했다. 넣는 순간 "노선 전량" 약속이 조용히 깨진다.

실물이 지지한 결정 다섯

방면은 순번에서 유도

「순번 < 회차」→상행이 197,240행 전건 성립. 원본의 방면 컬럼은 검증 후 버려도 된다.

사진 한 장에 시각 하나

GBIS도 차량별 시각을 주지 않는다. 도메인이 응답 구조를 정확히 모사한다.

차량 ID 하나로 두 원천이 닫힘

GBIS vehId ≡ 데이터드림 AUTOMB_ID, 103대 전량 동일 — 매핑표 자체가 불필요.

정원 3형태 (등재·관측하한·미상)

신차 2대가 실제로 미상→관측하한으로 승격됐고, 관측하한 ≤ 등재가 41쌍 전건 성립.

상·하행 한 축

회차점을 지나도 만석이 안 풀린다(통과 243쌍, 좌석 증감 중앙값 0). 축을 끊을 이유가 없다.

ID 참조 · append-only

마스터에서 32대가 사라지고 정류장 이름이 75.8% 겹쳐도 — ID만 참조한 덕에 과거 사실이 안 흔들린다.

고칠 것 여섯 — 전부 카드가 아니라 규칙·필드

셋 다 같은 결론: 애그리거트 경계는 그대로 두고, 필드 추가·타입 교체로 해소된다.

01직전버스 규칙을 한 벌로

문서 3곳의 선택식이 서로 달랐다(하나는 "가장 멀리 지나간 차"를 고르는 오기). argmin{마지막 통과 ≥ 내 순번} + 동률 타이브레이커로 확정 — 동률이 질의의 약 1%에서 확률을 94.7%↔46.6%로 뒤집는다.

02같은 차의 두 바퀴 구분

30분 관측창이면 21%의 창에서 같은 차량이 두 바퀴째로 다시 온다. 창 안에서 바퀴(TripSegment)를 나눠 차량마다 최신 바퀴만 직전버스 후보로.

03경로에 회차 순번 승격

"방면은 순번에서 유도"가 회차 순번 없이는 계산 불가였다. Route 경로에 turnSeq 한 칸 추가(회차 = 첫 하행 정류장).

04못 찍은 시간도 1급 사실

실측 20일의 92.7%가 수집 공백. 공백 기록(CollectionGap)이 없으면 재생기가 갭을 정상 구간으로 착각해 없는 통과를 만들어낸다.

05지식에 시점 버전 축

노선 개편·정류장 폐지 때 순번의 의미가 버전에 묶인다. Route에 발효일 버전, 사진에 그 버전 각인, Stop은 삭제 대신 은퇴 표시. 시간표 빈칸은 0이 아니라 빈칸(마스터 결손 40%).

06타입 보강 셋

통과 표식은 정수 0이 아닌 별도 타입(순번과 실수로 비교 못 하게), 판정 근거는 저장됨|즉석 두 형태(무저장 프로토타입이 거짓 ID를 안 만들게), 사진에 수신 시각 복원(소스 시계 멈춤 감지).

번역층(어댑터)이 흡수하는 마찰 열 가지
마찰 (전부 실측)번역층의 처리
차량이 1대면 배열이 아니라 객체로 온다 — 하필 첫차·막차 시간대"객체면 1원소 배열로 승격"을 무조건 선행
resultCode 4 = 데이터 없음실패가 아니라 0대 사진(성공)으로 접수
stateCd 0/1/2 (교차로/도착/출발)통과 표식·정차 여부로 정규화 — 201건 전건 통과
응답의 stationId와 순번 축이 어긋나면 조용한 오배치순번↔정류장ID 대조 검증 후 stationId 폐기
crowded — 2층 표식(lowPlate=2 ⟺ 0, 44/44)과 동치폐기. 좌석형에선 잔여석의 파생값이라 추가 정보 없음
plateNo · taglessCd · routeTypeCd어댑터 잔류, 코어 미전달 — routeTypeCd는 수집 분기 결정자
도착 API locationNo = staOrder − stationSeq (같은 축, 2/2 일치)변환이 가능해서 더 위험 — 타입 시그니처로 코어 진입 차단
순번 축에 미정차 지점 7개(3330) — "몇 정거장 전"이 부풀려짐표시 단계에서 정차 정류장만 세는 변환 제공
route_info의 종점은 축의 끝이 아니라 회차점(안양역, seq 43)축 구성은 routestation 마스터로만. 기·종점은 표시용
단발 위치 스파이크 — 한 관측만 물리적으로 불가능한 순번(20일 35건), 유령 직전버스를 만듦원본 사진은 그대로, 증거 조립 때 그 1건만 후보 제외 + 제외 수 기록