Repository navigation
[Fix] 자정 이후 시발 열차 시각을 데이터팩 SQLite에 운행일 경계(03:00) 기준 24시 이후 초로 적재 - #921
Conversation
- 인천 1·2호선 공식 FILE과 KRIC 역별 API(GTX-A·의정부)가 00시대로 적은 자정 이후 막차 시각을 전날 운행일의 심야 시각(86400초 이상)으로 적재한다. 스냅샷은 바꾸지 않는다. - 전국 후보 조립 뒤 운행일 경계 앞 정차 시각이 남으면 후보 생성을 실패시킨다. Closes #918
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
정본 팩 JSON의 인천 시간표는 build-datapack이 materializer로 다시 만들어 비교한다(수도권·전국 정본 팩 모두). materializer 출력을 바꾸면 해시가 여러 증거에 결속된 수도권 정본 팩까지 다시 만들어야 하므로, 정본 팩은 원천 시각을 그대로 두고 transit_stop_times를 SQLite에 쓸 때 운행일 초로 바꾼다. - build-datapack: transit_stop_times를 serviceDayStopTimes로 바꿔 쓴다(인천·KRIC 역별 노선 등 원천과 무관). - 바꾼 뒤 trip 안 시각이 줄어들면 validate-datapack의 기존 순서 검사가 거부한다. - materializer·KRIC 역별 adapter·후보 조립 검사 변경은 되돌린다. - 문서 파편(build-datapack.mjs blob)을 갱신한다. Refs #918
AquilaXk
left a comment
There was a problem hiding this comment.
Actionable comments posted: 4
🎯 Linked issue & acceptance criteria audit
- Linked issue: #918 [Fix] Incheon after-midnight trains mix 86400+ and 0-hour values
- Goal summary: After-midnight trains use one service-day representation with the 03:00 boundary.
- Acceptance criteria verified: 2/3
| ID | Acceptance criterion | Status | Evidence |
|---|---|---|---|
| AC-1 | Compare source file notation and compiler conversion and record the result | PASS | PR body Verification table, source comparison row |
| AC-2 | Fix the compiler and add an invariant check for the service-day boundary | FAIL | tools/datapack/build-datapack.mjs:4329 |
| AC-3 | If the representation is correct, record the evidence and close | PASS | Not applicable branch, the representation was wrong and is fixed |
🛡️ Adversarial audit evidence
- Falsifiability verified: Yes (Probe on service-day-seconds.mjs lines 13 and 14 killed 4 of 4 mutants.)
- Hollow assertions detected: 0
- Production backdoors detected: 0
- Ground-truth sources verified:
Backend ServiceDayResolver cutoff 03:00 on origin/mainIssue 918 body Done When list and reproductionMobile station timetable repository on origin/main adding seconds to the datenormalize-kric-timetable cutoff and validate-datapack monotonic check
🤖 Prompt for all review comments with AI agents
Could we verify each finding against the current code and keep only those that
still apply? Could we make the smallest validated fix and briefly note why any
finding no longer applies?
Inline comments:
- `@tools/datapack/build-datapack.mjs:4329-4329`
Could validate-datapack (and the route bundle build) reject any stop time below the
03:00 boundary after compilation, reusing the shared boundary constant, with a test
that feeds a 00:xx row for a non-Incheon trip and expects a failure? That would turn
the rule into an enforced invariant that blocks merge until it exists.
- `@tools/datapack/lib/service-day-seconds.mjs:1-6`
Could the two JSON-reading builders either apply the same conversion or fail when
they meet a stop time below the boundary? Alternatively, could the contract note the
SQLite-only scope so the divergence is a recorded decision rather than prose in the
PR?
- `@tools/datapack/lib/service-day-seconds.mjs:7-7`
Could normalize-kric-timetable import this constant, and could a contract test pin
it to the serviceDayCutoff string in the Journey contract so drift fails CI?
- `@tools/datapack/materialize-incheon-timetable.test.mjs:438-451`
Could one build-level test add a non-Incheon trip with a 00:xx start and expect 86400
plus seconds, and a straddling trip that validate-datapack rejects with the monotonic
message?
---
Outside diff comments:
None.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Aquila review skill, project CLAUDE.md, PR template full.md and issue 918 body
Review profile: ASSERTIVE
Plan: Aquila fallback
Run ID: N/A (Aquila fallback)
Review source: Aquila CLI
Repository visibility: public GitHub repository
Trigger:
Discovery review of the current change set on the draft pull request
Base:
main@53f01fc5c4c5d2b7630c2c6dcdb79608200eaa1c
Head:
fix/incheon-service-day-boundary-918@87d3731064c195a01b90e55c60847a5bbb156dfb
Command:
node post-review.mjs input.json payload-1.json --repo AquilaXk/easysubway-data --pr 921
📥 Commits
Reviewing files changed in this pull request between:
- Base:
53f01fc5c4c5d2b7630c2c6dcdb79608200eaa1c - Head:
87d3731064c195a01b90e55c60847a5bbb156dfb
📒 Files selected for processing (6)
contracts/documentation/documentation-fragment.jsontools/ci/data-test-ownership.jsontools/datapack/build-datapack.mjstools/datapack/lib/service-day-seconds.mjstools/datapack/lib/service-day-seconds.test.mjstools/datapack/materialize-incheon-timetable.test.mjs
📜 Review details
mobile-v19 CI 러너가 자원 한도로 중단(exit 143)되는 빈도를 줄이려고, 전국 RC 산출물을 새로 빌드하던 F1 테스트를 catalog fixture 빌드(4호선, 인천 아님)와 SQLite 직접 변조·fixture 서명 갱신으로 바꾼다. 검증기의 03:00 검사를 끄면 이 테스트가 실패한다. Refs #918
|
|
No description provided. |
refresh-documentation-fragment --worktree로 다시 만들었다(손 편집 없음). Refs AquilaXk/easysubway-backend#480
* [Fix] 서버 번들 환승 동선의 계단 칸을 stair_access_state 기준으로만 유도 - includes_stairs는 확인된 계단(STAIR_ONLY)일 때만 1이다. 0은 계단 없음이 아니다. - 계단 없음은 STEP_FREE로만 나타낸다. 계단 정보가 없으면 UNKNOWN으로 남긴다. - 입력의 includesStairs와 상태가 어긋나거나 상태 값이 계약 밖이면 번들을 만들지 않는다. - SQLite 계약(includes_stairs INTEGER NOT NULL, 모바일 Drift bool)은 바꾸지 않는다. Refs AquilaXk/easysubway-backend#480 * [Fix] 서버 시간표 스냅샷의 strict route 자격을 stairAccessState STEP_FREE에서만 부여 리뷰 F3(#923). includesStairs=false 기본값만으로 ENTRY·EXIT edge가 strict_route_eligible이 되던 유도를 고친다. 계단 상태가 UNKNOWN·STAIR_ONLY면 자격을 주지 않는다. Refs AquilaXk/easysubway-backend#480 * [Test] 서버 번들 network_edges 계단 칸 규칙을 emitter 호출부에서 고정 리뷰 F1(#923). populateNationwideTopologyEdges를 내보내 catalog-schema의 network_edges DDL에 직접 채우고 STAIR_ONLY·STEP_FREE·미확인 행과 어긋난 입력 거부를 단언한다. 호출부를 이전 인자로 되돌리면 이 테스트가 실패한다. Refs AquilaXk/easysubway-backend#480 * [Fix] 모바일 팩 build-datapack도 서버 번들과 같은 계단 칸 헬퍼와 불일치 거부를 공유 리뷰 F2(#923). lib/network-edge-stair-columns.mjs로 규칙을 옮겨 emitter와 build-datapack이 함께 쓴다. 계단 여부와 상태가 어긋나는 edge(STAIR_ONLY+false, STEP_FREE+true)는 이제 두 경로 모두 거부한다. 기존 legacy flag 테스트는 거부와 일치 입력의 유도를 함께 단언하도록 바꿨다. 문서 파편 갱신. Refs AquilaXk/easysubway-backend#480 * [Docs] #921 병합 뒤 rebase에 맞춰 build-datapack 문서 파편을 재생성 refresh-documentation-fragment --worktree로 다시 만들었다(손 편집 없음). Refs AquilaXk/easysubway-backend#480
* [Fix] 부산·대구·대전 시간표 달력에 KASI 공휴일 예외를 싣고 공휴일 달력 불변식 추가 - 지역 시간표 통합기가 공휴일(HOLIDAYS_2026)마다 평일·토요일 달력을 빼고(2) 휴일 달력을 더한다(1). 공휴일 목록이 없거나 형식이 틀리면 실패한다. - 세 기관 원천에 명절 시간표가 없어 설·추석도 휴일 시간표로 운행한다. - HOLIDAYS_2026이 KASI 2026년 특일 정보 원문과 같음을 보관 원문 fixture로 고정한다. - 전국 후보 조립 뒤 공휴일에 평일·토요일 달력이 운행하거나 휴일 달력이 빠진 노선이 있으면 실패한다. Closes #919 * [Chore] #919 공휴일 예외를 반영해 전국 후보(seq126 시계)를 재생성 refresh-nationwide-candidate --evaluated-at 2026-10-03T05:27:51.411Z --release-sequence 126 원천 snapshot과 fan-in head는 그대로다. 부산·대구·대전 공휴일 예외 192행이 정본 팩에 더해진다. Refs #919 * [Refactor] 공휴일 달력 불변식의 정렬 비교 함수 명시와 노선별 판정 분리(SonarCloud) 동작은 같다. 문자열 정렬에 codepointCompare를 쓰고, 노선·날짜 판정을 routeHolidayViolations로 나눠 인지 복잡도를 낮춘다. 후보 조립의 통합기 import를 한 줄로 합친다. Refs #919 * [Chore] #919 리팩터 커밋에 builder SHA를 맞춰 전국 후보 결속을 갱신 refresh-nationwide-candidate --evaluated-at 2026-10-03T05:27:51.411Z --release-sequence 126 정본 팩 바이트는 그대로이고 spec·request·hash·preparation의 builder SHA만 바뀐다. Refs #919 * [Test] 달력 창 밖 공휴일의 예외 행·운행 판정 가드를 변이로 잡는 테스트 추가 리뷰 F1(#922). 2025-12-25·2027-01-01처럼 달력 창 밖 날짜로 holidayExceptionRows(창 가드)와 holidayCalendarViolations(calendarActiveOn 창 가드, 창 밖 날짜 건너뛰기)를 고정한다. 세 가드의 ||→&&·return false→true 변이 4개가 모두 실패로 잡힌다. Refs #919 * [Fix] 공휴일 목록을 보관된 KASI 원문 전체 집합과 정확히 맞추고 불변식은 그 독립 집합과 비교 리뷰 F2(#922). 보관 원문을 release/kasi-public-holiday-2026으로 옮기고, 통합기는 넘겨받은 목록이 원문에서 도출한 공휴일 전체와 같지 않으면(빠진 날·더한 날) 실패한다. 후보 조립 불변식은 통합기 입력이 아니라 원문에서 따로 읽은 집합과 비교해 순환을 끊는다. Refs #919 * [Chore] #919 리뷰 반영 커밋에 builder SHA를 맞춰 전국 후보 결속을 갱신 refresh-nationwide-candidate --evaluated-at 2026-10-03T05:27:51.411Z --release-sequence 126 정본 팩 바이트는 그대로다. Refs #919 * [Chore] #921·#923 병합 뒤 rebase에 맞춰 전국 후보 결속(builder SHA)을 재생성 refresh-nationwide-candidate --evaluated-at 2026-10-03T05:27:51.411Z --release-sequence 126 정본 팩 바이트는 그대로이고 spec·request·hash·preparation의 builder SHA만 바뀐다. Refs #919



Related issue
Closes #918
Summary
00:09:30처럼 24시 미만으로 적는다. 24시 표기는 없다.trainUseInfo/subwayTimetable)로 만든 GTX-A·의정부경전철 trip도 같다.transit_stop_times를 쓸 때 원천이 00:00~02:59로 적은 시각을 전날 운행일의 심야 시각(86400초 이상)으로 싣는다.validate-datapack의 기존 순서 검사(transit_stop_times must be monotonic)가 팩을 거부한다. 03:00 전후에 걸친 열차가 여기에 해당한다.validate-datapack은 컴파일된 SQLite에 03:00 전 도착·출발이 하나라도 남으면 기관과 상관없이 팩을 거부한다(리뷰 F1).Changes
tools/datapack/lib/service-day-seconds.mjs(신규)serviceDaySeconds: 03:00 이전 시각에 86400을 더한다. 정수 초가 아니면 실패한다.serviceDayStopTimes: stop_time 행에 위 변환을 적용한다.normalize-kric-timetable.mjs와 같은 03:00이다.tools/datapack/build-datapack.mjs:transit_stop_times적재 행을serviceDayStopTimes로 바꿔 쓴다.tools/datapack/validate-datapack.mjs: 03:00 전 정차 시각이 남으면 거부한다(리뷰 F1).tools/datapack/normalize-kric-timetable.mjs: 별도 3시 상수 대신 공유 상수를 쓴다(리뷰 F3).SERVICE_DAY_CUTOFF_LOCAL_TIME = "03:00"을 테스트로 고정한다.serviceDayCutoff, backendServiceDayResolver.CUTOFF_LOCAL_TIME.contracts/documentation/documentation-fragment.json:build-datapack.mjsblob 갱신(refresh-documentation-fragment --worktree→--checkIN SYNC)lib/service-day-seconds.test.mjs(신규,data-test-ownership.json에 data918로 등록)materialize-incheon-timetable.test.mjs: 빌드한 SQLite에 03:00 이전 인천 정차가 0행인지 확인한다. 박촌 1301~1309 출발 시각도 확인한다.datapack-tools.test.mjs(리뷰 F1·F4)build-datapack이 정본 팩의 인천 시간표를 materializer로 다시 만들어 비교하기 때문이다(validateProductionIncheonTimetableFixture). 수도권 정본 팩도 비교 대상이다.production Incheon timetable fixture does not match pinned admission).Scope
Included
transit_stop_times의 운행일 경계 표현(인천 1·2호선, KRIC 역별 노선 GTX-A·의정부 포함 전 원천)Excluded
Ownership / dependencies
emit-artifact-components.mjs만 바꾼다.build-datapack.mjs와 문서 파편은 건드리지 않는다.prepare-nationwide-candidate-run.mjs를 바꾸고, 이 PR은 그 파일을 바꾸지 않는다.Contract & Compatibility
providerRecordHash는 그대로다.Version impact
Product gate impact
Provenance impact
Version decision
Verification
incheon-line1-train-timetable-20261003의raw*Sha256과 같다(예: 1505120472f548f4…). 1호선 평일 하선 원문의 마지막 행 154156은 1305·1307·1309이고 첫 시각은04시 시각은 없다.00:09:30·00:21:30·00:30:30이다. 23시대 시발 행(1301·1303) 뒤에 있다. 8개 파일 모두 00시대 시발 행은 파일 끝에만 있다. 02kric-subway-timetable-station-lines-20261003기준이다. GTX-A는 00시대 시발 20개, 05시대 16개이고 의정부는 00시대 8개, 05시대 24개다. 01~04시 시발 열차는 없다.materialized SQLite와 provenance가 인천 schedule_timetable 2건을 SUPPORTED로 만든다에서 03:00 이전 인천 정차가 340행이었다(기대 0). GREEN: 같은 테스트 1/1,lib/service-day-seconds.test.mjs3/3datapack-tools.test.mjs의데이터팩 검증기는 trip별 stop_time 시간이 역행하면 거부한다가 통과한다. 빌드 단계에 별도 순서 검사를 넣지 않고 기존 validator 검사를 그대로 쓴다.run-affected-tests대상 50개 파일을 로컬에서 돌렸다. 실패는bundle-component-contract.test.mjs의contract mutations fail closed하나이고, origin/main에서도 똑같이 실패하는 로컬 환경 문제다.node tools/repo/refresh-documentation-fragment.mjs --worktree --check: IN SYNC,node tools/ci/check-documentation-fragment-sync.mjs --base origin/main: PASSEDrefresh-nationwide-candidate→build-current-release-candidate-accessibility-input→build-datapack(RC) →validate-datapack --require-production→stage-current-server-route-bundle-candidate이고 전 단계가 통과했다. 같은 절차로 main을 돌리면 seq126 번들의 timetable 표 해시와 같다. 서명은 로컬 임시 키를 썼다.Not run
Risk
build-backend-timetable-seed·build-server-timetable-snapshot에는 운영 소비처가 없다(리뷰 F2 확인).lib/service-day-seconds.mjs에 적었다.validate-datapack이 팩을 거부해 RC가 실패한다.Rollout / Recovery
병합 순서
build-datapack.mjs와 문서 파편을 바꾼다. #923은 이 PR 병합 뒤 rebase하고 파편을 다시 갱신한다.Review focus
04시 시각 부재, KRIC 0104시 시발 부재다.Checklist