Skip to content

Architecture

Priyesh Patel edited this page Oct 17, 2026 · 9 revisions

Architecture

High-level architecture

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
Loading

System layers

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;
Loading

Domain model and class diagrams

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
Loading

Sequence Diagram (Train Arrival and Hold Evaluation)

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)
Loading

Architecture changes

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.

Clone this wiki locally