This guide explains how automation agents and human contributors should work with the StationAPI repository so releases stay predictable, auditable, and safe. Update this file whenever you change the workflow or behavior it documents.
stationapi/src/domain/– Entity definitions and repository abstractions. Theentity/module mirrors the gRPC schema,repository/providesasync_trait-based interfaces, andnormalize.rscontains text normalization for search.stationapi/src/use_case/– Application logic.interactor/query.rsimplements theQueryUseCasecontract defined intraits/query.rs.stationapi/src/infrastructure/– SQLx repositories for PostgreSQL.My*Repositorytypes share anArc<PgPool)and integration tests live behind#[cfg_attr(not(feature = "integration-tests"), ignore)].stationapi/src/presentation/– gRPC presentation layer.presentation/controller/grpc.rswiresMyApito the generated server types. Health checks and reflection are enabled throughtonic-healthandtonic-reflection.stationapi/proto/stationapi.proto– The gRPC contract.build.rsusestonic-buildto generate server code andFILE_DESCRIPTOR_SET.data/– Canonical CSV datasets and schema definition (create_table.sql). Files follow theN!table.csvnaming scheme to control import order. Detailed instructions are indata/README.md.data_validator/– CLI that verifies cross-file constraints (cargo run -p data_validator).docker/&compose.yml– Container definitions for the API and PostgreSQL 18.docker/postgres/00_extensions.sqlensurespg_trgmandbtree_gistare installed.Makefile– Convenience targets for running tests (make test-unit,make test-integration,make test-all).
- Rust: Use the stable toolchain (
rustup default stable). Docker images build withrust:1. - Protobuf:
protocmust exist when building locally (the Docker image installs it automatically). - PostgreSQL: Version 15+ with
pg_trgmandbtree_gistextensions. Compose spins up PostgreSQL 18 with these extensions preloaded. - Environment variables:
DATABASE_URL– SQLx connection string (e.g.,postgres://stationapi:stationapi@localhost/stationapi).DISABLE_GRPC_WEB–falseenables gRPC-Web; set totruefor pure gRPC/HTTP2.ODPT_ACCESS_TOKEN– ODPT consumer key used to download authenticated data such as Seibu Bus GTFS, Keio Bus GTFS, Tokyu Bus JSON, and the Tokyu-operated Ota, Shinagawa, and Meguro community bus GTFS feeds.HOSTandPORT– listen address (defaults to[::1]:50051; Docker uses0.0.0.0:50051)..env.testexportsTEST_DATABASE_URL,RUST_LOG,RUST_BACKTRACE, andRUST_TEST_THREADS=1for integration tests.
- Recommended: keep shared defaults in
.env, copy overrides to.env.local, and rely on startup loading both files.
- Local development
- Prepare PostgreSQL and set
DATABASE_URL. cargo run -p stationapirebuilds the schema fromdata/create_table.sql, imports everydata/*.csv, and then boots the gRPC server. If extension creation fails, grant the needed privileges manually.- Health checks respond to
grpc.health.v1.Health/Check; gRPC Reflection is available for tooling such asgrpcurl.
- Prepare PostgreSQL and set
- Docker / Compose
docker compose up --buildlaunches both the API and PostgreSQL containers. Compose passes.env.localto the API container, so put local runtime secrets such asODPT_ACCESS_TOKENthere before starting the stack. Source code mounts into/app, but hot reload is not provided; rebuild after code changes.- Production images rely on
docker/api/Dockerfile, which runscargo build -p stationapi --releaseand copiesdata/.
- gRPC-Web
- The server accepts HTTP/1.1 and wraps handlers via
tonic_web::enable. Disable this behavior by exportingDISABLE_GRPC_WEB=trueto require HTTP/2 clients only.
- The server accepts HTTP/1.1 and wraps handlers via
- CSV import order depends on the numeric prefix (
1!,2!, ...). When adding datasets, choose a prefix that preserves foreign-key dependencies. data/create_table.sqldrops and recreates tables, indexes, and foreign keys. Update this script alongside any schema or CSV column changes.data_validatorcurrently verifies that5!station_station_types.csvreferences valid station and type IDs, and that order-sensitive station sequences in3!stations.csvstay intact underORDER BY e_sort, station_cd(e.g. the Toei Oedo Line's Tochomae rows, whose misordering silently drops the station from ETA estimation). Extend the validator when new cross-references or order-sensitive spots are introduced and keep the process fail-fast (panic on invalid data).
- Unit tests –
cargo test --lib --package stationapiormake test-unit; focus on entities and repository mocks without a database. - Integration tests –
source .env.test && cargo test --lib --package stationapi --features integration-testsormake test-integration. Use a dedicated schema behindTEST_DATABASE_URLand clean it up after runs. - Full suite –
make test-allruns unit then integration tests sequentially. SetRUST_LOG=debugto inspect SQL queries during debugging. - Linting and formatting – Run
cargo fmtandcargo clippy --all-targets --all-featuresbefore committing. Resolve new Clippy warnings unless an existing#![allow]covers the case. - Data verification – Execute
cargo run -p data_validatorwhenever CSVs change and record results in pull requests. - IPA coverage audit – Execute
make ipa-auditwhen English or romanized CSV names change. This is a read-only report fordata/2!lines.csv,data/3!stations.csv, anddata/4!types.csv; it does not fail validation, but highlights unresolved tokens and example names so the IPA dictionary can be extended deliberately.
- Stations –
GetStationById,GetStationByIdList,GetStationsByGroupId,GetStationsByCoordinates,GetStationsByLineId,GetStationsByName,GetStationsByLineGroupId.QueryInteractorenriches stations with lines, companies, station numbers, and train types. - Lines –
GetLineById,GetLinesByIdList,GetLinesByName. Results include company data and computed line symbols based on repository helpers. - Routes –
GetRoutes,GetRoutesMinimal. The minimal variant returnsRouteMinimalResponsewith deduplicatedLineMinimaldata; paging tokens are currently empty (pagination not implemented). - Train types –
GetTrainTypesByStationId,GetRouteTypes. Train types aggregate by line group and include related lines plus optional train type metadata. Rail variants useTrainTypeKind::{Default, Branch, Rapid, Express, LimitedExpress, HighSpeedRapid, CommuterRapid}(0-6); bus variants useBusRoute(7), which represents a(route_id, shape_id)operation pattern (e.g. 循環 / 短ターン / 支線) generated automatically from the configured GTFS bus feeds (Toei Bus, Seibu Bus, Keio Bus) and the converted Tokyu Bus JSON. - GTFS bus integration – At startup,
src/import.rs::integrate_gtfs_to_stations()ingests GTFS feeds intogtfs_*tables and then projects them onto the sharedstations/lines/types/station_station_typestables. Every configured GTFS feed is imported, including Seibu Bus and Keio Bus (both downloaded from ODPT withODPT_ACCESS_TOKEN). Tokyu Bus ordinary-routeBusroutePattern,BusstopPole, andBusTimetableJSON are converted into the samegtfs_*representation; pattern IDs becomeshape_idvalues so route variants remain queryable as bus TrainTypes. The Tokyu-operated Ota, Shinagawa, and Meguro community buses use their official GTFS feeds and matching JSON routes are excluded to prevent duplicates.ODPT_ACCESS_TOKENis required for authenticated sources. Stops whose Tokyu JSON records omit coordinates remain available to name and route queries but not coordinate searches.transport_type(0: rail, 1: bus) on bothstationsandlineskeeps rail and bus records queryable side by side. GTFS IDs are namespaced per feed before import to avoid cross-operator collisions.line_cd(100,000,000+),station_cd/station_g_cd(200,000,000+), and bustype_cd/line_group_cd(100,000,000+) are all deterministic fnv1a hashes that stay clear of the rail data ranges. Disable the entire bus pipeline withDISABLE_BUS_FEATURE=true. - Bus stop translations (readings & English) – GTFS-JP
translations.txtlayouts differ per feed, soload_gtfs_translationsresolves columns by header name (Seibu ships 6 columns withoutrecord_sub_id; Keio and the Tokyu community feeds ship 7) and indexes eachstop_nametranslation under both keys it may use:record_id(== the stop_id, Seibu — with the "-NN" pole suffix also mapped to the parent stop_id) andfield_value(== the Japanese stop_name, Keio / Tokyu community, whererecord_idis left empty).import_gtfs_stopsthen looks a stop's translation up by stop_id first, then by name. Keying only byrecord_id(the previous behavior) silently dropped every field_value-keyed feed, leavingstation_name_kfilled with the kanji stop_name andstation_name_rempty. Readings arriving as half-width katakana (ニシハチオウジ, Keio / Tokyu community) are folded to full-width viaromaji::to_fullwidth_katakana()before storage. - Bus English-name fallback – When a feed provides no English (
en) translation for a stop — e.g. Tokyu Bus ordinary-route JSON, which carries onlydc:titleandodpt:kana—src/domain/romaji.rs::romaji_display_name()derives a modified-Hepburn romanization (with macrons for long vowels, matching the curated rail style: Tōkyō / Kyōto / Shin-Ōsaka) from the kana reading, andimport.rsfillsstop_name_rwith it. The fallback never overwrites a realenvalue, and a reading with no convertible kana staysNULLrather than emitting a partial transcription. Becausestop_name_ris the single upstream source that fans out into thestationsprojection,search_by_name, and the romanized bus route/headsign names, this supplements every English-facing surface at once. When projecting intostations,station_name_rnis filled with the plain-ASCII spelling viaromaji::strip_macrons()(Tōkyō → Tokyo), mirroring the rail dataset's_r(macron) /_rn(macron-free) column pair. - TTS metadata –
Station,StationMinimal,Line, andTrainTypeexposename_ipa/name_roman_ipaplusname_tts_segmentsfor multi-segment pronunciation output. Usename_tts_segmentswhen clients need per-token SSML construction for mixed-language names such asKasai-Rinkai Park. - Connected routes –
GetConnectedRoutes.QueryInteractor::get_connected_stationsis not implemented yet and returns an empty vector; update the use-case and infrastructure layers together when adding real logic. - Changes to the service contract require coordinated updates to
proto/stationapi.proto, regenerated code viatonic-build, and corresponding adjustments in both presentation and use-case layers.
- Prioritize quality and performance over implementation speed – Always favor code quality and runtime performance over velocity. Be mindful of algorithmic complexity and look for opportunities to replace O(n×m) linear scans with O(n+m) indexed lookups (e.g., HashMaps). Avoid unnecessary JOINs and redundant queries at the SQL level. When a change affects performance, document the before/after complexity and query plan impact in the pull request.
- Document the commands you executed (for example,
cargo fmt && cargo clippy --all-targets --all-features && make test-unit) and their outcomes in every pull request. - For database, gRPC, or schema updates, add architectural notes under
docs/and synchronize README references so onboarding materials stay accurate. - When modifying
QueryInteractor, ensure the enrichment steps (companies, train types, line symbols) still behave as expected. Double-check helper methods such asupdate_station_vec_with_attributesandbuild_route_tree_map. - Introducing new tables, endpoints, or feature flags must come with matching updates to this document and any other affected guidance.
Keep this guide aligned with the repository. If a workflow, environment requirement, or endpoint changes, update AGENTS.md in the same pull request so automation agents and contributors work from current instructions.