Repository navigation
Architecture
Priyesh Patel edited this page Oct 17, 2026
·
9 revisions
flowchart TB
%% External Data Source
subgraph Ingestion ["External Data and Ingestion Pipeline"]
GTFS_DATA["Public GTFS Feeds<br/>(STM Metro & CDPQ REM)"]
GTFS_PARSER["GTFS Parser & Schedule Ingestion<br/>(Static Timetables, Headways, Track Topology)"]
GTFS_DATA --> GTFS_PARSER
end
%% Frontend Tier
subgraph Client ["Presentation Tier (React + TypeScript)"]
direction TB
UI["UI & Scenario Controls<br/>(Parameter Sliders, Delays, Presets)"]
ZUSTAND["Global State & Playback Store<br/>(Zustand: Synchronized Dual Clock)"]
PIXI["Station Transfer Canvas<br/>(PixiJS WebGL: Passenger & Train Sprites)"]
D3["Results & Visual Analytics<br/>(D3.js: Paired Dual-Series, Histograms, Spreads)"]
EXPORTER["Client Exporters<br/>(PapaParse CSV & html2canvas Images)"]
UI -->|User Actions & Scrubbing| ZUSTAND
ZUSTAND -->|Frame Clock & Interpolation| PIXI
ZUSTAND -->|State Sync & Filters| D3
D3 -.->|Chart Render Node| EXPORTER
end
%% Backend Gateway
subgraph Gateway ["Application & API Tier (FastAPI)"]
direction TB
ROUTER["REST API Endpoints<br/>(/scenarios, /runs, /sweep, /export)"]
PYDANTIC["Validation & Clamping Layer<br/>(Pydantic: Schema & Range Checks)"]
ORCHESTRATOR["Scenario & Paired Run Orchestrator<br/>(Seed Pairing, Dispatching Holding vs. No-Holding)"]
ROUTER --> PYDANTIC
PYDANTIC --> ORCHESTRATOR
end
%% Simulation & Decision Core
subgraph Core ["Simulation & Decision Engine (Python)"]
direction TB
subgraph DES ["Discrete-Event Engine (heapq)"]
SIM_QUEUE["Priority Event Queue & Clock<br/>(Deterministic Advances)"]
TRAIN_LOGIC["Train Kinematics & Delay Propagation<br/>(Dwell Congestion, Headway Slack, Knock-On Delays)"]
PASSENGER_LOGIC["Passenger Movement & Corridors<br/>(Pulsed Burst Arrivals, Corridor Queueing)"]
SIM_QUEUE --> TRAIN_LOGIC
SIM_QUEUE --> PASSENGER_LOGIC
end
subgraph MathStats ["Stochastic & Statistical Models (NumPy / SciPy)"]
CRN["CRN Stream Manager<br/>(Isolated Random Substreams)"]
DIST["Distributions & Fitting<br/>(Lognormal Walking Times, Gamma Delays)"]
CRN --> DIST
end
subgraph DecisionSystem ["Decision & Safety Subsystem"]
OBS_BUILDER["Observation Builder<br/>(Immutable Snapshot with Horizon Error)"]
CONTROLLER["Multi-Strategy Controller<br/>(Break-Even Calculation, Rolling-Horizon, Adaptive Mode)"]
SAFETY["Constraint Enforcer & Operating Rules<br/>(Max Hold Cap, Safe Headway Gap, One-Hop Rule)"]
OBS_BUILDER --> CONTROLLER
CONTROLLER -->|Requested Hold| SAFETY
end
subgraph Analytics ["Metrics & Aggregation (Pandas)"]
METRIC_CALC["Metric Processor<br/>(Helped vs. Harmed, Bucketed Delays, 95th Percentiles)"]
end
DIST --> PASSENGER_LOGIC
DIST --> TRAIN_LOGIC
TRAIN_LOGIC -->|Departure Event Check| OBS_BUILDER
SAFETY -->|Validated / Clamped Action| SIM_QUEUE
PASSENGER_LOGIC --> METRIC_CALC
TRAIN_LOGIC --> METRIC_CALC
end
%% Persistence Tier
subgraph DB ["Persistence Tier (PostgreSQL)"]
ORM["SQLAlchemy ORM Layer"]
POSTGRES[("PostgreSQL Database<br/>- Saved Scenarios & Presets<br/>- Run Manifests & Seeds<br/>- Decision Audit Logs<br/>- Precomputed Sweeps")]
ORM --- POSTGRES
end
%% Inter-Tier Connections
GTFS_PARSER -->|Seed Initial Timetables| ORM
UI -->|"HTTP / JSON (Run Requests & Config)"| ROUTER
ROUTER -->|"HTTP / JSON (Responses)"| UI
ORCHESTRATOR -->|"CRUD Operations"| ORM
ORM -->|"Manifests & Config"| ORCHESTRATOR
ORCHESTRATOR -->|Initialize Paired Runs with Master Seed| Core
METRIC_CALC -->|Aggregated Results & Decision Logs| ROUTER
ROUTER -->|Payloads: Frame Events, Metrics, Audit Data| ZUSTAND
flowchart TB
%% Presentation Layer
subgraph Presentation ["Presentation Layer (Client-Side)"]
direction LR
VIEWS["React Views & Playback Controls"]
STORE["Zustand Global Store"]
RENDER["PixiJS / D3.js Renderers"]
VIEWS ~~~ STORE ~~~ RENDER
end
%% Application Layer
subgraph Application ["Application / Service Layer (API)"]
direction LR
ROUTER["FastAPI Route Handlers"]
ORCHESTRATOR["Scenario & Run Orchestrator"]
COMPARATOR["Paired Run Comparator"]
ROUTER ~~~ ORCHESTRATOR ~~~ COMPARATOR
end
%% Domain Layer (Pure Python)
subgraph Domain ["Domain Layer (Core Business Logic - Pure Python)"]
direction TB
subgraph Sim ["Simulation Sub-domain"]
DES["Event Loop (heapq)"]
ENTITIES["Train & Passenger Entities"]
end
subgraph Dec ["Decision Sub-domain"]
STRAT["Holding Strategies & Break-Even Logic"]
RULES["Safety Constraints (Min Gap, Hold Caps)"]
end
subgraph Stat ["Statistics Sub-domain"]
CRN["CRN Stream Manager"]
METRICS["Metric Aggregation"]
end
end
%% Infrastructure Layer
subgraph Infrastructure ["Infrastructure & Persistence Layer"]
direction LR
GTFS["GTFS Feed Parsers"]
ORM["SQLAlchemy ORM Repositories"]
DB[("PostgreSQL Database")]
GTFS ~~~ ORM ~~~ DB
end
%% Cross-Layer Dependencies
Presentation -->|"HTTP / REST / JSON"| Application
Application -->|"Instantiates & Executes"| Domain
Application -->|"Data Access & Storage"| Infrastructure
%% Note to illustrate Dependency Inversion / Isolation
classDef pureDomain fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
class Domain pureDomain;
classDiagram
%% Core Transit Network Topology
class TransitNetwork {
+String name
+getStations() List~Station~
+getLines() List~Line~
}
class Line {
+String name
+String operator
+HeadwaySchedule schedule
}
class Station {
+String name
+String type
}
class Platform {
+String lineName
+String direction
+List~Passenger~ waitingQueue
}
class TransferCorridor {
+int throughputLimitPerMin
+int queueCapacity
+Distribution walkingTimeDist
+processMovement(TransferBurst)
}
%% Simulation Entities
class Train {
+String id
+int capacity
+int onboardLoad
+float currentDelay
+float layoverSlack
+board(List~Passenger~)
+alight() List~Passenger~
}
class Passenger {
+String id
+String origin
+String destination
+float walkingSpeed
+String connectionStatus
}
class TransferBurst {
+float arrivalTime
+int groupSize
+generateArrivals()
}
class PassengerEventRecord {
+String passengerId
+String groupId
+String trainId
+String platformId
+String eventType
+float timestamp
}
%% Decision Engine & Strategies
class DecisionController {
<<interface>>
+evaluate(obs: ObservationSnapshot) HoldAction
}
class NoControlBaseline {
+evaluate(obs: ObservationSnapshot) HoldAction
}
class HeadwayRegulator {
+float targetHeadway
+evaluate(obs: ObservationSnapshot) HoldAction
}
class PredictiveConnectionProtector {
+evaluate(obs: ObservationSnapshot) HoldAction
-calculateBreakEven() float
}
class RollingHorizonController {
+evaluate(obs: ObservationSnapshot) HoldAction
-evaluateDownstreamImpact() float
}
class ConstraintEnforcer {
+List~OperatingRule~ rules
+validate(action: HoldAction) HoldAction
}
class OperatingRule {
+String ruleName
+float threshold
+isViolated(action: HoldAction) bool
}
class ObservationSnapshot {
+float simulatedTime
+List~TrainState~ trains
+List~PlatformState~ platforms
+applyHorizonError()
}
%% Analytics & Audit
class RunManifest {
+String seed
+String codeVersion
+ParameterSet parameters
}
class DecisionAuditLog {
+float timestamp
+String reason
+HoldAction requestedAction
+HoldAction appliedAction
}
class PairedComparisonResult {
+computeDifferences()
+getHelpedVsHarmed()
}
%% Structural Relationships (Network)
TransitNetwork "1" *-- "*" Line : contains
TransitNetwork "1" *-- "*" Station : contains
Line "1" *-- "*" Train : operates
Station "1" *-- "*" Platform : contains
Station "1" *-- "1..*" TransferCorridor : contains
Platform "2" <-- "1" TransferCorridor : connects
%% Dynamic Relationships (Simulation)
Train "1" o-- "*" Passenger : carries
Platform "1" o-- "*" Passenger : waits at
TransferBurst "*" --> "1" Train : triggered by
Passenger "1" *-- "*" PassengerEventRecord : generates
%% Strategy Pattern (Decision Engine)
DecisionController <|.. NoControlBaseline
DecisionController <|.. HeadwayRegulator
DecisionController <|.. PredictiveConnectionProtector
DecisionController <|.. RollingHorizonController
DecisionController ..> ObservationSnapshot : reads
DecisionController --> ConstraintEnforcer : submits action to
ConstraintEnforcer "1" *-- "*" OperatingRule : enforces
%% Data & Audit Relationships
RunManifest "1" *-- "*" DecisionAuditLog : records
PairedComparisonResult --> "2" RunManifest : compares
sequenceDiagram
participant EQ as EventQueue
participant TR as Train
participant PL as Transfer Corridor
participant OB as Observation Builder
participant DC as Decision Controller
participant CE as Constraint Enforcer
autonumber
%% 1. Train Arrival and Passenger Generation
EQ->>TR: trigger(TrainArrival)
activate TR
TR->>PL: generateTransferBurst(alightingPassengers)
activate PL
Note right of PL: Applies right-skewed walking time<br/>distribution and corridor throughput limits
PL-->>TR: passengers queued
deactivate PL
%% 2. Pre-Departure Evaluation
Note over EQ,TR: Calculated dwell time elapses
EQ->>TR: trigger(DepartureDue)
TR->>OB: requestState()
activate OB
Note right of OB: Captures immutable state and<br/>applies prediction error<br/>scaled by time horizon
OB-->>DC: pass ObservationSnapshot
deactivate OB
%% 3. Strategy Execution (Break-Even Logic)
activate DC
Note right of DC: Evaluates candidate hold lengths:<br/>Calculates (Time Saved for Connections)<br/>vs (Time Lost by Onboard Passengers)
DC-->>CE: submit proposed HoldAction(duration)
deactivate DC
%% 4. Constraint and Safety Rule Enforcement
activate CE
Note right of CE: Evaluates against Operating Rules:<br/>1. Max Hold Cap<br/>2. Minimum Safe Headway Gap<br/>3. One-Hop Anti-Cascading Rule
alt Action breaks rules but is clampable
CE-->>TR: return Clamped HoldAction (reduced duration)
else Action violates strict constraints
CE-->>TR: return Refused (duration = 0)
else Action within limits
CE-->>TR: return Approved HoldAction
end
deactivate CE
%% 5. Simulation State Update
TR->>EQ: schedule(Hold, currentTime) with requested and applied duration
alt duration > 0
Note left of TR: Train sets internal delayed-by-system flag
TR->>EQ: schedule(TrainDeparture, currentTime + duration)
else duration == 0
TR->>EQ: schedule(TrainDeparture, currentTime)
end
deactivate TR
%% 6. Departure
EQ->>TR: trigger(TrainDeparture)
TR->>EQ: schedule(TrainArrival, nextStation)
| Date | What changed | Why | Related issue or pull request |
|---|---|---|---|
| 2026-10-17 | Event loop changed from SimPy to a custom event queue and clock built on Python's heapq
|
A fixed, documented tie order and byte-identical replays are simpler to guarantee and test with a small queue the team controls | #8 |
| 2026-10-17 | Event schema frozen at version 1: eight event types, one record per transferring passenger, and boarding, alighting and onboard counts for all other riders stored on train departures. PassengerEventRecord gains group, train and platform IDs |
Per-passenger delay is needed for transfer metrics; individual records for every rider would make full-day logs too large | #96 |
| 2026-10-17 | Fixed tie order for events at the same time: arrival, alighting, passenger arrival, boarding, departure due, hold, departure, missed connection | Required for deterministic runs; follows what happens at a platform in one instant | #96, #8 |
| 2026-10-17 | Sequence diagram: ScheduledDepartureEvent is now the DepartureDue event; a hold schedules TrainDeparture later instead of rescheduling it, the decision is recorded as a Hold event, and a departure is always an event, even with no hold |
Matches the implemented event types. The hold must be decided when the dwell ends, not at arrival, because prediction error depends on how far ahead the strategy looks (Mathematical Foundations section 7) and Metro dwell depends on who boards during the dwell (section 4) | #96 |
For existing diagrams, describe only what changed rather than repeating the entire architecture description.
AI acknowledgement: architecture changes for US-1.1 drafted with Claude, reviewed and edited by Priyesh Patel.