Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
774e693
exposing cluster id for stops
sameerzohy Jun 5, 2026
a94a85d
added query param for clusterId exposing
sameerzohy Jun 5, 2026
6dbe5c9
migrate_gtfs_data_from_nandi_to_gims
blaze-198 Jun 8, 2026
0dff2f3
chore: update preprocessed GTFS data from Nandi build (32e0c3)
Jun 8, 2026
0908068
chore: update preprocessed GTFS data from Nandi build (32e0c3)
Jun 8, 2026
39e80d3
chore: update preprocessed GTFS data from Nandi build (8f871e)
Jun 9, 2026
c2468cd
chore: update preprocessed GTFS data from Nandi build (8f871e)
Jun 10, 2026
5d97679
chore: update preprocessed GTFS data from Nandi build (b1ca96)
Jun 10, 2026
1aaf4dd
chore: update preprocessed GTFS data from Nandi build (337064)
Jun 15, 2026
68980aa
chore: update preprocessed GTFS data from Nandi build (c36b9f)
Jun 15, 2026
c7a7ce2
chore: update preprocessed GTFS data from Nandi build (77d2fb)
Jun 19, 2026
b73dd89
chore: update preprocessed GTFS data from Nandi build (4ae3a3)
Jun 19, 2026
9fd45a8
chore: update preprocessed GTFS data from Nandi build (dc65ae)
Jun 19, 2026
607174b
chore: update preprocessed GTFS data from Nandi build (ab5af2)
Jun 23, 2026
81e1d98
chore: update preprocessed GTFS data from Nandi build (ab5af2)
Jun 23, 2026
2cb35a4
chore: update preprocessed GTFS data from Nandi build (712e15)
Jun 26, 2026
05f3835
chore: update preprocessed GTFS data from Nandi build (712e15)
Jul 2, 2026
910c270
chore: update preprocessed GTFS data from Nandi build (712e15)
Jul 2, 2026
1166015
chore: update preprocessed GTFS data from Nandi build (d5ef04)
Jul 6, 2026
63978da
chore: update preprocessed GTFS data from Nandi build (d5ef04)
Jul 6, 2026
b4c6d90
fix: dhall typo
pranavs6 Jul 6, 2026
b512d9c
chore: update preprocessed GTFS data from Nandi build (9cfdf1)
Jul 6, 2026
a3bc284
merge: sync master-gims into main via sync/master-gims-to-main
blaze-198 Jul 15, 2026
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
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,12 @@ CLAUDE.md
.claude/

# Unused files
ignored/
ignored/

# Preprocessed per-trip stop-times shards.
# These are generated by the Nandi preprocessor and copied into assets/ at
# build time so they get baked into the GIMS Docker image. They are NOT
# source — individual shards exceed GitHub's 100 MB push limit, so they must
# never enter git. The Sync GIMS Repo step relies on this rule to keep the
# branch under the limit while the shards still reach every built image.
assets/trip_stoptimes/
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ name = "gtfs-routes-service"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "gtfs-routes-service"
path = "src/main.rs"

[dependencies]
# Web framework
actix-web = "4.3.1"
Expand Down
203 changes: 203 additions & 0 deletions assets/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
{
"version": "aa171d6f164e",
"build_commit": "9cfdf1",
"generated_at": "2026-07-06T13:28:21Z",
"gtfs_feeds": [
"bangalore_metro",
"bhubaneshwar_bus",
"chennai_bus",
"chennai_metro",
"chennai_suburban",
"delhi_bus",
"delhi_bus_nammayatri_application_mock",
"delhi_metro",
"kochi_metro",
"kolkata_bus",
"kolkata_metro",
"mumbai_MMMOPL_metro",
"mumbai_MMOCL_metro",
"mumbai_MMRCL_metro",
"mumbai_suburban",
"sambalpur_bus"
],
"files": {
"routes.json": {
"sha256": "9818570dfeefcb3612c8da654d0bb5e380771005f50cf60c0f3b24b08f088af9",
"count": 8263
},
"stops.json": {
"sha256": "48dc038ffc4494a9cd1ea9e913049ccd7b7adfd02bbfaa3e39068b6e55c492b1",
"count": 31859
},
"patterns.json": {
"sha256": "5184626d7659a3d2862d84135f5068b085b6b5ee7a9078be2c81bcf3b74800d3",
"count": 8375
},
"route_stops.json": {
"sha256": "59831f81bbc573bc3d856710f695a96d9fdb33e00ce266624de09b8b7665c487",
"count": 208815
},
"trip_stoptimes/bangalore_metro.json": {
"sha256": "e639d9a639c089d51cfd85efadcda52c2b5a277e8c6a77b159d5b4c4ea8a17f4",
"count": 2722
},
"trip_stoptimes/bhubaneshwar_bus.json": {
"sha256": "70816626318d547ab9cd801f62369af29c8c878d6a1dc3409dd4d6bb194ff47e",
"count": 4199
},
"trip_stoptimes/chennai_bus.json": {
"sha256": "d92fe6c6b65df5c0d8b83201024c3cd33a497220c6dd83c10288569c0074853a",
"count": 51283
},
"trip_stoptimes/chennai_metro.json": {
"sha256": "19433454474303f58dc3428ccd33aa8c32239d9b2956a4299a539dc28e34a9a1",
"count": 3738
},
"trip_stoptimes/chennai_suburban.json": {
"sha256": "981ea7728f23fdf991e83329fd6988090f63a56f3955ef87ce1f11bd5f20ef9a",
"count": 807
},
"trip_stoptimes/delhi_bus.json": {
"sha256": "3c9a13d868ccb5cf8074e55c36580902c9331afe3db494548af7f7e3aee47241",
"count": 211851
},
"trip_stoptimes/delhi_bus_nammayatri_application_mock.json": {
"sha256": "da9b10ea10dd6dcd3c9ca2bf9cc33d26c874c03ba0f78dda9cbb89bf4b320b60",
"count": 24
},
"trip_stoptimes/delhi_metro.json": {
"sha256": "b2f65a0b5280ed64ea8e5fdf1c715ef62b096f7b79c74fd137de3db3bd181998",
"count": 21825
},
"trip_stoptimes/kochi_metro.json": {
"sha256": "13c6cb2d264d3f10e50351801f7d3cebd0b3fd68e7460d5797b2e1223fcbb928",
"count": 450
},
"trip_stoptimes/kolkata_bus.json": {
"sha256": "985e38be3a87d41be9d803d68b5ad7dfd73a59bb93aac45f205aadd213f039cc",
"count": 17227
},
"trip_stoptimes/kolkata_metro.json": {
"sha256": "d84a6561c5b6415fb9d63ab1738ba502ff0fed7fcaf75572f38c6d2fa3ec0d19",
"count": 1652
},
"trip_stoptimes/mumbai_MMMOPL_metro.json": {
"sha256": "76fe2c88a106a372b19d032f2d2a8df9faa744db113b64820164e88da3042ca2",
"count": 1
},
"trip_stoptimes/mumbai_MMOCL_metro.json": {
"sha256": "7d7a99bde7d9b1720aec64d98efb1ffed791d8bf1d9d08bb6b50ddd84edf8244",
"count": 4
},
"trip_stoptimes/mumbai_MMRCL_metro.json": {
"sha256": "ea866668bf79e7137ed1f14332854cd167940fede157051436514325aefba602",
"count": 1
},
"trip_stoptimes/mumbai_suburban.json": {
"sha256": "8a374cba7e8b57f9cb8e9c43300984dd7014e2a51e3cc210c7a41fdfa928d2d7",
"count": 134
},
"trip_stoptimes/sambalpur_bus.json": {
"sha256": "11196eed2c41b0aa60e8f4268aa5eeb7aa04de108bcb046ade0800fa8f22f496",
"count": 405
}
},
"stats": {
"bangalore_metro": {
"routes": 6,
"stops": 472,
"patterns": 58,
"route_stop_mappings": 170
},
"bhubaneshwar_bus": {
"routes": 493,
"stops": 2916,
"patterns": 493,
"route_stop_mappings": 16875
},
"chennai_bus": {
"routes": 4133,
"stops": 18008,
"patterns": 4133,
"route_stop_mappings": 71803
},
"chennai_metro": {
"routes": 44,
"stops": 313,
"patterns": 50,
"route_stop_mappings": 593
},
"chennai_suburban": {
"routes": 804,
"stops": 722,
"patterns": 807,
"route_stop_mappings": 16814
},
"delhi_bus": {
"routes": 2621,
"stops": 6676,
"patterns": 2621,
"route_stop_mappings": 98947
},
"delhi_bus_nammayatri_application_mock": {
"routes": 1,
"stops": 11,
"patterns": 1,
"route_stop_mappings": 11
},
"delhi_metro": {
"routes": 22,
"stops": 1126,
"patterns": 57,
"route_stop_mappings": 561
},
"kochi_metro": {
"routes": 2,
"stops": 25,
"patterns": 8,
"route_stop_mappings": 50
},
"kolkata_bus": {
"routes": 88,
"stops": 714,
"patterns": 88,
"route_stop_mappings": 1772
},
"kolkata_metro": {
"routes": 10,
"stops": 182,
"patterns": 20,
"route_stop_mappings": 114
},
"mumbai_MMMOPL_metro": {
"routes": 1,
"stops": 12,
"patterns": 1,
"route_stop_mappings": 12
},
"mumbai_MMOCL_metro": {
"routes": 4,
"stops": 38,
"patterns": 4,
"route_stop_mappings": 40
},
"mumbai_MMRCL_metro": {
"routes": 1,
"stops": 27,
"patterns": 1,
"route_stop_mappings": 27
},
"mumbai_suburban": {
"routes": 2,
"stops": 37,
"patterns": 2,
"route_stop_mappings": 74
},
"sambalpur_bus": {
"routes": 31,
"stops": 580,
"patterns": 31,
"route_stop_mappings": 952
}
}
}
1 change: 1 addition & 0 deletions assets/patterns.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/route_stops.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/routes.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/stops.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion dhall-configs/dev/gtfs_in_memory_server_rust.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ in {
osrtc_username = secrets.osrtc_username,
osrtc_secret_key = secrets.osrtc_secret_key,
osrtc_station_refresh_interval_hours = 1,
osrtc_feed_key = Some "odisha_osrtc"
osrtc_feed_key = Some "odisha_osrtc",

-- Preprocessed data configuration
Expand Down
73 changes: 73 additions & 0 deletions src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::services::{
},
fleet_operator::{DBFleetOperatorService, FleetOperatorService, MockFleetOperatorService},
gtfs_service::GTFSService,
metro_graph::MetroGraph,
operator::{DBOperatorService, MockOperatorService, OperatorService},
osrtc_station_cache::OsrtcStationCache,
trip_service::TripService,
Expand Down Expand Up @@ -70,6 +71,15 @@ pub struct AppConfig {
pub ignored_trip_ids: Vec<String>,
pub bhubaneswar_cache_update_interval: u64,
pub bhubaneswar_external_auth: Option<String>,
/// When true, load GTFS data from preprocessed JSON files (produced by
/// Nandi preprocessor) instead of calling Nandi/OTP APIs.
#[serde(default)]
pub use_preprocessed_data: bool,
/// Directory containing preprocessed JSON files (routes.json, stops.json,
/// patterns.json, route_stops.json, metadata.json).
/// Defaults to "./assets" if not set.
#[serde(default = "default_preprocessed_data_dir")]
pub preprocessed_data_dir: String,
pub phone_number_hash_key: String,
/// Enable schedule-based active trip reconciliation (default: false)
pub enable_schedule_reconciliation: bool,
Expand All @@ -90,6 +100,10 @@ pub struct AppConfig {
pub gen_int_for_id: Option<bool>,
}

fn default_preprocessed_data_dir() -> String {
"./assets".to_string()
}

impl OtpConfig {
pub fn find_instance_by_gtfs_id(&self, gtfs_id: &str) -> Option<&OtpInstance> {
self.gtfs_id_based_instances
Expand Down Expand Up @@ -158,6 +172,8 @@ pub struct AppState {
pub bus_registration_mapping: Arc<HashMap<String, HashMap<String, String>>>,
pub fleet_list: Arc<HashMap<String, HashMap<String, Vec<String>>>>,
pub vehicle_service_sub_types: Arc<HashMap<String, HashMap<String, Vec<String>>>>,
/// Metro transit graphs loaded from preprocessed JSON (gtfs_id -> MetroGraph)
pub metro_graphs: Arc<HashMap<String, MetroGraph>>,
pub depot_manager_details: Arc<HashMap<String, crate::models::DepotManagerDetails>>,
pub fleet_tag_list: Arc<HashMap<String, HashMap<String, String>>>,
pub chennai_service_type_cache: Arc<RwLock<HashMap<String, (Instant, Option<String>)>>>,
Expand Down Expand Up @@ -367,6 +383,7 @@ impl AppState {

// Load bus registration mapping from CSV
let bus_registration_mapping = Arc::new(Self::load_bus_registration_mapping().await?);
let metro_graphs = Arc::new(Self::load_metro_graphs(&app_config));

// Load depot manager details from CSV
let depot_manager_details = Arc::new(Self::load_depot_manager_details().await?);
Expand All @@ -385,6 +402,7 @@ impl AppState {
bus_registration_mapping,
fleet_list: Arc::new(Self::load_fleet_list().await?),
vehicle_service_sub_types: Arc::new(Self::load_vehicle_service_sub_types().await?),
metro_graphs,
depot_manager_details,
fleet_tag_list: Arc::new(Self::load_fleet_tag_list().await?),
chennai_service_type_cache: Arc::new(RwLock::new(HashMap::new())),
Expand Down Expand Up @@ -617,6 +635,61 @@ impl AppState {
Ok(sub_types_map)
}

/// Load metro transit graphs from preprocessed JSON files in the
/// preprocessed data directory.
///
/// Looks for `metro_graph.json` which contains a map of gtfs_id -> MetroGraph.
/// If the file doesn't exist, returns an empty map (metro routing won't
/// be available but all other APIs work fine).
fn load_metro_graphs(config: &AppConfig) -> HashMap<String, MetroGraph> {
let graph_file = std::path::Path::new(&config.preprocessed_data_dir)
.join("metro_graph.json");

if !graph_file.exists() {
info!(
"No metro_graph.json found in {}, metro routing will not be available",
config.preprocessed_data_dir
);
return HashMap::new();
}

info!(
"Loading metro graphs from {}",
graph_file.display()
);

match std::fs::read_to_string(&graph_file) {
Ok(json) => match serde_json::from_str::<HashMap<String, MetroGraph>>(&json) {
Ok(graphs) => {
for (gtfs_id, graph) in &graphs {
info!(
"Loaded metro graph for {}: {} nodes, {} routes",
gtfs_id,
graph.nodes.len(),
graph.route_stop_sequences.len()
);
}
info!("Loaded {} metro transit graphs", graphs.len());
graphs
}
Err(e) => {
error!(
"Failed to deserialize metro_graph.json: {}. Metro routing will not be available.",
e
);
HashMap::new()
}
},
Err(e) => {
error!(
"Failed to read metro_graph.json: {}. Metro routing will not be available.",
e
);
HashMap::new()
}
}
}

async fn load_depot_manager_details(
) -> Result<HashMap<String, crate::models::DepotManagerDetails>> {
use crate::models::DepotManagerDetails;
Expand Down
10 changes: 9 additions & 1 deletion src/graphql/responses/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::models::serialize_option_as_empty_string;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TripApiResponse {
#[serde(rename = "tripId")]
pub trip_id: String,
#[serde(rename = "routeId")]
pub route_id: String,
#[serde(rename = "routeName")]
// Backend's `TripInfoResponse.routeName :: Text` is non-Maybe — emit "" not
// null when the route lookup misses (e.g. the trip's route is missing from
// the routes feed) so the Aeson decoder doesn't fail.
#[serde(
rename = "routeName",
serialize_with = "serialize_option_as_empty_string"
)]
pub route_name: Option<String>,
#[serde(rename = "direction")]
pub direction: Option<i32>,
Expand Down
Loading