Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ A GraphQL API that provides nearby Japanese train stations and bus stops, runnin
- For system architecture and design decisions, see [docs/architecture.md](docs/architecture.md).
- For the design of the transfer route search (`connectedRoutes`, RAPTOR), see [docs/route-search.md](docs/route-search.md).
- For the source-backed operating constraints used by timetable-free search, see [docs/route-service-audit.md](docs/route-service-audit.md).
- For the Keio stopping-pattern audit and regression checks, see [docs/keio-stopping-pattern-audit.md](docs/keio-stopping-pattern-audit.md).
- For technical debt analysis and architectural concerns, see [docs/technical_debt.md](docs/technical_debt.md).
- For the record of the Cloudflare Workers migration, see [docs/cloudflare-workers-migration.md](docs/cloudflare-workers-migration.md).
- For the published GraphQL schema, see [schema/public.graphql](schema/public.graphql).
Expand Down Expand Up @@ -109,3 +110,4 @@ make deploy-production # production -> stationapi

The data lives inside the WASM binary, so **a data change needs a rebuild and a
redeploy**; it is not picked up at runtime.

12 changes: 6 additions & 6 deletions data/5!station_station_types.csv
Original file line number Diff line number Diff line change
Expand Up @@ -2022,7 +2022,7 @@ DEFAULT,9950213,203,64,0,修善寺
DEFAULT,2400101,400,65,0,新宿
DEFAULT,2400104,400,65,1,笹塚
DEFAULT,2400105,400,65,1,代田橋
DEFAULT,2400106,400,65,1,明大前
DEFAULT,2400106,400,65,0,明大前
DEFAULT,2400107,400,65,1,下高井戸
DEFAULT,2400108,400,65,1,桜上水
DEFAULT,2400109,400,65,1,上北沢
Expand All @@ -2049,12 +2049,12 @@ DEFAULT,2400129,400,65,0,高幡不動
DEFAULT,2400130,400,65,1,南平
DEFAULT,2400131,400,65,1,平山城址公園
DEFAULT,2400132,400,65,1,長沼
DEFAULT,2400133,400,65,1,北野
DEFAULT,2400133,400,65,0,北野
DEFAULT,2400134,400,65,0,京王八王子
DEFAULT,2400101,401,66,0,新宿
DEFAULT,2400104,401,66,1,笹塚
DEFAULT,2400105,401,66,1,代田橋
DEFAULT,2400106,401,66,1,明大前
DEFAULT,2400106,401,66,0,明大前
DEFAULT,2400107,401,66,1,下高井戸
DEFAULT,2400108,401,66,1,桜上水
DEFAULT,2400109,401,66,1,上北沢
Expand Down Expand Up @@ -11375,7 +11375,7 @@ DEFAULT,2400110,382,285,1,八幡山
DEFAULT,2400111,382,285,1,芦花公園
DEFAULT,2400112,382,285,0,千歳烏山
DEFAULT,2400113,382,285,1,仙川
DEFAULT,2400114,382,285,1,つつじヶ丘
DEFAULT,2400114,382,285,0,つつじヶ丘
DEFAULT,2400115,382,285,1,柴崎
DEFAULT,2400116,382,285,1,国領
DEFAULT,2400117,382,285,1,布田
Expand Down Expand Up @@ -28232,7 +28232,7 @@ DEFAULT,2400110,382,757,1,八幡山
DEFAULT,2400111,382,757,1,芦花公園
DEFAULT,2400112,382,757,0,千歳烏山
DEFAULT,2400113,382,757,1,仙川
DEFAULT,2400114,382,757,1,つつじヶ丘
DEFAULT,2400114,382,757,0,つつじヶ丘
DEFAULT,2400115,382,757,1,柴崎
DEFAULT,2400116,382,757,1,国領
DEFAULT,2400117,382,757,1,布田
Expand Down Expand Up @@ -28336,7 +28336,7 @@ DEFAULT,2400110,382,760,1,八幡山
DEFAULT,2400111,382,760,1,芦花公園
DEFAULT,2400112,382,760,0,千歳烏山
DEFAULT,2400113,382,760,1,仙川
DEFAULT,2400114,382,760,1,つつじヶ丘
DEFAULT,2400114,382,760,0,つつじヶ丘
DEFAULT,2400115,382,760,1,柴崎
DEFAULT,2400116,382,760,1,国領
DEFAULT,2400117,382,760,1,布田
Expand Down
54 changes: 54 additions & 0 deletions data_validator/tests/fixtures/keio_stops.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
station_number,stopping_types,source,checked_on
1,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko01_shinjuku/,2026-10-16
2,各駅停車|快速|区間急行|急行,https://www.keio.co.jp/train/station/ko02_hatsudai/,2026-10-16
3,各駅停車|快速|区間急行|急行,https://www.keio.co.jp/train/station/ko03_hatagaya/,2026-10-16
4,各駅停車|快速|区間急行|急行|特急,https://www.keio.co.jp/train/station/ko04_sasazuka/,2026-10-16
5,各駅停車,https://www.keio.co.jp/train/station/ko05_daitabashi/,2026-10-16
6,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko06in08_meidaimae/,2026-10-16
7,各駅停車|快速,https://www.keio.co.jp/train/station/ko07_shimo-takaido/,2026-10-16
8,各駅停車|快速|区間急行|急行,https://www.keio.co.jp/train/station/ko08_sakurajosui/,2026-10-16
9,各駅停車,https://www.keio.co.jp/train/station/ko09_kami-kitazawa/,2026-10-16
10,各駅停車|快速,https://www.keio.co.jp/train/station/ko10_hachiman-yama/,2026-10-16
11,各駅停車,https://www.keio.co.jp/train/station/ko11_roka-koen/,2026-10-16
12,各駅停車|快速|区間急行|急行|特急,https://www.keio.co.jp/train/station/ko12_chitose-karasuyama/,2026-10-16
13,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko13_sengawa/,2026-10-16
14,各駅停車|快速|区間急行|急行,https://www.keio.co.jp/train/station/ko14_tsutsujigaoka/,2026-10-16
15,各駅停車,https://www.keio.co.jp/train/station/ko15_shibasaki/,2026-10-16
16,各駅停車,https://www.keio.co.jp/train/station/ko16_kokuryo/,2026-10-16
17,各駅停車,https://www.keio.co.jp/train/station/ko17_fuda/,2026-10-16
18,各駅停車|快速|区間急行|急行|特急,https://www.keio.co.jp/train/station/ko18_chofu/,2026-10-16
19,各駅停車|快速,https://www.keio.co.jp/train/station/ko19_nishi-chofu/,2026-10-16
20,各駅停車|快速,https://www.keio.co.jp/train/station/ko20_tobitakyu/,2026-10-16
21,各駅停車|快速,https://www.keio.co.jp/train/station/ko21_musashinodai/,2026-10-16
22,各駅停車|快速,https://www.keio.co.jp/train/station/ko22_tama-reien/,2026-10-16
23,各駅停車|快速|区間急行|急行,https://www.keio.co.jp/train/station/ko23_higashi-fuchu/,2026-10-16
24,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko24_fuchu/,2026-10-16
25,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko25_bubaigawara/,2026-10-16
26,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko26_nakagawara/,2026-10-16
27,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko27_seiseki-sakuragaoka/,2026-10-16
28,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko28_mogusaen/,2026-10-16
29,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko29_takahatafudo/,2026-10-16
30,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko30_minamidaira/,2026-10-16
31,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko31_hirayamajoshi-koen/,2026-10-16
32,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko32_naganuma/,2026-10-16
33,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko33_kitano/,2026-10-16
34,各駅停車|快速|区間急行|急行|特急|京王ライナー,https://www.keio.co.jp/train/station/ko34_keio-hachioji/,2026-10-16
35,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko35_keio-tamagawa/,2026-10-16
36,各駅停車|快速|区間急行|急行|特急,https://www.keio.co.jp/train/station/ko36_keio-inadazutsumi/,2026-10-16
37,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko37_keio-yomiuri-land/,2026-10-16
38,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko38_inagi/,2026-10-16
39,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko39_wakabadai/,2026-10-16
40,各駅停車|快速|区間急行|急行|特急|京王ライナー,https://www.keio.co.jp/train/station/ko40_keio-nagayama/,2026-10-16
41,各駅停車|快速|区間急行|急行|特急|京王ライナー,https://www.keio.co.jp/train/station/ko41_keio-tama-center/,2026-10-16
42,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko42_keio-horinouchi/,2026-10-16
43,各駅停車|快速|区間急行|急行|特急|京王ライナー,https://www.keio.co.jp/train/station/ko43_minami-osawa/,2026-10-16
44,各駅停車|快速|区間急行,https://www.keio.co.jp/train/station/ko44_tamasakai/,2026-10-16
45,各駅停車|快速|区間急行|急行|特急|京王ライナー,https://www.keio.co.jp/train/station/ko45_hashimoto/,2026-10-16
46,各駅停車|急行|特急,https://www.keio.co.jp/train/station/ko46_fuchukeiba-seimommae/,2026-10-16
47,各駅停車|急行,https://www.keio.co.jp/train/station/ko47_tama-dobutsukoen/,2026-10-16
48,各駅停車|快速|区間急行|特急,https://www.keio.co.jp/train/station/ko48_keio-katakura/,2026-10-16
49,各駅停車|快速|区間急行|特急,https://www.keio.co.jp/train/station/ko49_yamada/,2026-10-16
50,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko50_mejirodai/,2026-10-16
51,各駅停車|快速|区間急行|特急,https://www.keio.co.jp/train/station/ko51_hazama/,2026-10-16
52,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko52_takao/,2026-10-16
53,各駅停車|快速|区間急行|急行|特急|京王ライナー|Mt.TAKAO号,https://www.keio.co.jp/train/station/ko53_takaosanguchi/,2026-10-16
111 changes: 111 additions & 0 deletions data_validator/tests/keio_stopping_patterns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::path::Path;

use csv::StringRecord;

fn records(path: &Path) -> Vec<StringRecord> {
csv::Reader::from_path(path)
.unwrap()
.records()
.collect::<Result<_, _>>()
.unwrap()
}

// Line-specific station IDs at junctions must appear on both sides, in order.
fn sections(group: u32) -> Vec<(u32, u32, u32)> {
match group {
65 | 67..=71 => vec![(24001, 1, 34)],
66 | 252 | 416 | 760..=762 => vec![(24001, 1, 18), (24002, 18, 45)],
253 | 412..=415 | 758 | 759 => vec![(24001, 1, 33), (24003, 33, 53)],
254 | 285..=287 | 757 => {
vec![(24007, 1, 4), (24001, 4, 18), (24002, 18, 45)]
}
255 => vec![(24007, 1, 4), (24001, 4, 33), (24003, 33, 53)],
756 => vec![(24007, 1, 4), (24001, 4, 34)],
763 | 764 => vec![(24001, 1, 23), (24004, 23, 46)],
765 => vec![(24004, 23, 46)],
766 => vec![(24001, 1, 29), (24005, 29, 47)],
767 => vec![(24005, 29, 47)],
_ => panic!("unaudited Keio group: {group}"),
}
}

#[test]
fn existing_keio_patterns_match_official_stops_and_complete_station_sequences() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let data = root.join("data");
let stations = records(&data.join("3!stations.csv"));
let station_by_id: HashMap<&str, &StringRecord> = stations.iter().map(|r| (&r[0], r)).collect();
let types = records(&data.join("4!types.csv"));
let type_names: HashMap<&str, &str> = types.iter().map(|r| (&r[1], &r[2])).collect();
let fixture = records(&root.join("data_validator/tests/fixtures/keio_stops.csv"));
let official: HashMap<u32, &str> = fixture
.iter()
.map(|r| (r[0].parse().unwrap(), &r[1]))
.collect();
assert_eq!(official.len(), 53);

let mut actual: BTreeMap<u32, Vec<String>> = BTreeMap::new();
for row in records(&data.join("5!station_station_types.csv")) {
let station = station_by_id[&row[1]];
let line: u32 = station[13].parse().unwrap();
if !matches!(line, 24001..=24005 | 24007) {
continue;
}
let number: u32 = station[8].parse().unwrap();
let group: u32 = row[3].parse().unwrap();
let name = type_names[&row[2]];
let category = if name.starts_with("京王ライナー") {
"京王ライナー"
} else if name == "Mt.TAKAO" {
"Mt.TAKAO号"
} else {
name
};
// Per-row type is essential: Toei locals become Keio express services.
let mut expected = u32::from(!official[&number].split('|').any(|t| t == category));
if group == 758 {
// Mt.TAKAO down: Shinjuku, Meidaimae, Takaosanguchi only.
expected = u32::from(!matches!(number, 1 | 6 | 53));
}
if number == 23 && category == "特急" {
// Racecourse through service stops; main-line services partly stop.
expected = if group == 764 { 0 } else { 5 };
}
assert_eq!(
row[4].parse::<u32>().unwrap(),
expected,
"group {group}, station {} ({}), type {name}",
&station[2],
&station[0]
);
actual.entry(group).or_default().push(row[1].to_owned());
}

let audited: BTreeSet<u32> = [
65, 66, 67, 68, 69, 70, 71, 252, 253, 254, 255, 285, 286, 287, 412, 413, 414, 415, 416,
756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767,
]
.into_iter()
.collect();
assert_eq!(actual.keys().copied().collect::<BTreeSet<_>>(), audited);
for (group, station_ids) in actual {
let mut expected = Vec::new();
for (line, first, last) in sections(group) {
let mut section: Vec<&StringRecord> = stations
.iter()
.filter(|r| {
r[13].parse::<u32>().unwrap() == line
&& &r[21] == "0"
&& (first..=last).contains(&r[8].parse::<u32>().unwrap())
})
.collect();
section.sort_by_key(|r| (r[22].parse::<u32>().unwrap(), r[0].to_owned()));
expected.extend(section.into_iter().map(|r| r[0].to_owned()));
}
assert_eq!(
station_ids, expected,
"incomplete/out-of-order group {group}"
);
}
}
30 changes: 30 additions & 0 deletions docs/keio-stopping-pattern-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 京王線系統の停車駅照合(2026-10-16)

京王線・京王新線・相模原線・高尾線・競馬場線・動物園線の公式各駅案内53駅と、既存31路線グループの936行を照合した。都営新宿線直通の京王区間も含む。駅ごとの参照URLと確認日は `data_validator/tests/fixtures/keio_stops.csv` に収録した。

## 修正

| 路線グループ | 種別 | 駅 | 修正 |
| --- | --- | --- | --- |
| 285 | 急行(都営線内も急行) | つつじヶ丘 | 通過 → 停車 |
| 757 | 急行(都営線内は各駅停車) | つつじヶ丘 | 通過 → 停車 |
| 760 | 急行(京王線新宿~橋本) | つつじヶ丘 | 通過 → 停車 |
| 65 | 京王ライナー(京王八王子系統) | 明大前・北野 | 通過 → 停車 |
| 66 | 京王ライナー(橋本系統) | 明大前 | 通過 → 停車 |

急行のつつじヶ丘は[公式駅案内](https://www.keio.co.jp/train/station/ko14_tsutsujigaoka/)、京王ライナーは[上り](https://www.keio.co.jp/zasekishitei/)・[下り](https://www.keio.co.jp/zasekishitei/index_n.html)の公式停車駅・時刻表で確認した。京王ライナーの系統名に「方面」とあるが、既存の方向設定は0(両方向)であり、上りも同じグループを使用する。

## 回帰検査

`cargo test -p data_validator --test keio_stopping_patterns` で、各行の種別と公式駅案内の停車種別を比較する。都営線内の各駅停車をグループ全体の種別として扱わず、京王区間の各行の `type_cd` を使用する。

既存31グループがすべて存在すること、および各グループの路線区間から求めた駅ID列がCSVの列と完全一致することも検証する。これにより行の削除、重複、並び違い、調布・北野・東府中・高幡不動・笹塚の接続部での片側ID欠落を検出する。井の頭線は別線区のためこの検査の対象外。

次の差異は公式の運行形態に基づくもので、均一化しない。

- [Mt.TAKAO号](https://www.keio.co.jp/zasekishitei/index_mt.html)の758は下り(新宿・明大前・高尾山口)、759は上り(府中以西の所定駅・明大前・新宿)。駅案内の停車種別一覧だけでは方向別の停車駅を表現できないため、グループ別の期待値を使用する。
- 京王八王子・高尾山口系統の特急は東府中を一部停車(`pass=5`)とし、競馬場線直通特急764は東府中の両路線IDで停車する。
- 相模原線の急行・特急、高尾線の急行・特急は各線の公式駅案内に従う。特急が高尾線内で各駅に停車しても急行の通過駅まで変更しない。

本検査は既存の停車パターンと駅列を対象とする。新系統の収録網羅性、時刻指定検索、乗降制限の実装を保証するものではない。公式案内には高尾線の京王ライナーも掲載されているが、現行CSVには専用の路線グループがなく、系統追加は別途対応が必要。

Loading