5a. Level 3 Implementation Details¶
Overview¶
This document provides Level 3 architectural details for the actual MVP implementation with 4 bounded contexts: Configuration, Retrofit Workflow, Railway Infrastructure, and External Trains.
5a.1 Configuration Context - Level 3¶
Component Diagram¶
graph TB
subgraph "Configuration Context - Level 3"
Builder["ConfigurationBuilder<br/>Load from files"]
Loader["FileLoader<br/>Parse JSON/CSV"]
subgraph "Input DTOs (Pydantic, application/dtos)"
TrainDTO[TrainInputDTO]
WagonDTO[WagonInputDTO]
TrackDTO[TrackInputDTO]
WorkshopDTO[WorkshopInputDTO]
LocoDTO[LocomotiveInputDTO]
RouteDTO[RouteInputDTO]
TopologyDTO[TopologyInputDTO]
end
subgraph "Domain Models (domain/models)"
Scenario[Scenario]
ProcessTimes[ProcessTimes]
Topology[Topology]
end
end
Files[JSON/CSV Files] --> Builder
Builder --> Loader
Loader --> TrainDTO
Loader --> WagonDTO
Loader --> TrackDTO
Loader --> WorkshopDTO
Loader --> LocoDTO
Loader --> RouteDTO
Loader --> TopologyDTO
Loader --> Scenario
Loader --> ProcessTimes
Loader --> Topology
classDef builder fill:#e1f5fe,stroke:#01579b,stroke-width:2px
classDef dto fill:#fff9c4,stroke:#f57f17,stroke-width:2px
classDef model fill:#c5e1a5,stroke:#558b2f,stroke-width:2px
class Builder,Loader builder
class TrainDTO,WagonDTO,TrackDTO,WorkshopDTO,LocoDTO,RouteDTO,TopologyDTO dto
class Scenario,ProcessTimes,Topology model
Components¶
| Component | File | Responsibility |
|---|---|---|
| ConfigurationBuilder | configuration_builder.py |
Load scenario from file path |
| FileLoader | file_loader.py |
Parse JSON/CSV files, handle references |
| Scenario | scenario.py |
Root configuration model with validation |
| ProcessTimes | process_times.py |
Timing configuration for operations |
| DTOs | dtos/ |
Input data transfer objects |
Code Example¶
from pathlib import Path
from contexts.configuration.domain.configuration_builder import ConfigurationBuilder
# Load scenario
scenario = ConfigurationBuilder(Path('scenario_dir')).build()
# Access configuration
print(f'Scenario: {scenario.id}')
print(f'Workshops: {len(scenario.workshops)}')
print(f'Trains: {len(scenario.trains)}')
5a.2 Retrofit Workflow Context - Level 3¶
Component Diagram¶
graph TB
subgraph "Retrofit Workflow Context - Level 3"
Context["RetrofitWorkflowContext<br/>Initialization & orchestration"]
subgraph "Coordinators (Application Layer)"
Arrival["ArrivalCoordinator<br/>Process train arrivals"]
Collection["CollectionCoordinator<br/>Move to retrofit track"]
Workshop["WorkshopCoordinator<br/>Retrofit operations"]
Parking["ParkingCoordinator<br/>Move to parking"]
end
subgraph "Domain Services (No SimPy)"
Batch["BatchFormationService"]
Rake["RakeFormationService"]
TrainForm["TrainFormationService"]
Schedule["WorkshopSchedulingService"]
Coupling["CouplingService"]
Route["RouteService"]
end
subgraph "Resource Managers (Infrastructure)"
LocoMgr["LocomotiveResourceManager"]
TrackMgr["TrackCapacityManager"]
WorkshopMgr["WorkshopResourceManager"]
end
subgraph "Metrics & Export (Application/Infrastructure)"
Collector["EventCollector"]
Aggregator["MetricsAggregator"]
Exporter["CsvEventExporter"]
end
end
Context --> Arrival
Context --> Collection
Context --> Workshop
Context --> Parking
Arrival --> Batch
Collection --> Rake
Workshop --> Schedule
Parking --> TrainForm
Arrival --> LocoMgr
Collection --> TrackMgr
Workshop --> WorkshopMgr
Context --> Collector
Collector --> Aggregator
Collector --> Exporter
classDef coord fill:#e1f5fe,stroke:#01579b,stroke-width:2px
classDef domain fill:#c5e1a5,stroke:#558b2f,stroke-width:2px
classDef resource fill:#fff9c4,stroke:#f57f17,stroke-width:2px
classDef metrics fill:#ffccbc,stroke:#bf360c,stroke-width:2px
class Context,Arrival,Collection,Workshop,Parking coord
class Batch,Rake,TrainForm,Schedule,Coupling,Route domain
class LocoMgr,TrackMgr,WorkshopMgr resource
class Collector,Aggregator,Exporter metrics
Coordinators (Application Layer)¶
| Coordinator | Responsibility | SimPy Process |
|---|---|---|
| ArrivalCoordinator | Receive trains, classify wagons, distribute to collection tracks | Yes |
| CollectionCoordinator | Form batches, allocate locomotive, transport to retrofit track | Yes |
| WorkshopCoordinator | Assign wagons to workshops, execute retrofit, return to retrofitted track | Yes |
| ParkingCoordinator | Transport completed wagons to parking tracks | Yes |
Domain Services (No SimPy Dependencies)¶
| Service | Responsibility |
|---|---|
| BatchFormationService | Create wagon batches based on capacity constraints |
| RakeFormationService | Form and dissolve wagon rakes with coupling logic |
| TrainFormationService | Assemble trains (locomotive + rake) with preparation times |
| WorkshopSchedulingService | Schedule wagon batches to available workshops |
| CouplingService | Calculate coupling/decoupling times |
| RouteService | Provide route durations between tracks |
Resource Managers (Infrastructure Layer)¶
| Manager | Responsibility |
|---|---|
| LocomotiveResourceManager | Allocate and release locomotives (SimPy Resource) |
| TrackCapacityManager | Manage track capacity and wagon placement |
| WorkshopResourceManager | Manage workshop station availability (SimPy Resource) |
Workflow Sequence¶
sequenceDiagram
participant Train
participant Arrival
participant Collection
participant Workshop
participant Parking
Train->>Arrival: TrainArrivedEvent
Arrival->>Arrival: Classify wagons
Arrival->>Collection: Add to collection queue
Collection->>Collection: Form batch
Collection->>Collection: Allocate locomotive
Collection->>Workshop: Transport to retrofit track
Workshop->>Workshop: Select workshop
Workshop->>Workshop: Allocate stations
Workshop->>Workshop: Execute retrofit
Workshop->>Parking: Move to retrofitted track
Parking->>Parking: Form batch
Parking->>Parking: Transport to parking
5a.3 Railway Infrastructure Context - Level 3¶
Component Diagram¶
graph TB
subgraph "Railway Infrastructure Context - Level 3"
Context["RailwayInfrastructureContext<br/>Track building & services"]
subgraph "Aggregates"
Yard["RailwayYard<br/>Yard-level aggregate"]
TrackGroup["TrackGroup<br/>Group tracks by type"]
Occupancy["TrackOccupancy<br/>Wagon placement logic"]
end
subgraph "Entities"
Track["Track<br/>Individual track entity"]
end
subgraph "Domain Services"
Selection["TrackSelectionService<br/>Selection strategies"]
GroupSvc["TrackGroupService"]
OccSvc["TrackOccupancyService"]
Topology["TopologyService"]
end
subgraph "Repositories & Adapters"
Repos["domain/repositories/<br/>RailwayYard, TrackOccupancy"]
DI["infrastructure/di_container.py<br/>+ adapters.py"]
end
end
Context --> Yard
Yard --> TrackGroup
TrackGroup --> Track
Track --> Occupancy
Context --> Selection
Context --> DI
DI --> Repos
Selection --> TrackGroup
classDef component fill:#e1f5fe,stroke:#01579b,stroke-width:2px
class Context,Yard,TrackGroup,Track,Occupancy,Selection,GroupSvc,OccSvc,Topology,Repos,DI component
Components¶
| Component | Responsibility | Pattern |
|---|---|---|
| RailwayInfrastructureContext | Build tracks from scenario, provide services | Context |
| RailwayYard | Yard-level aggregate over track groups | Aggregate |
| TrackGroup | Group tracks by type (collection, retrofit, parking, workshop) | Aggregate |
| Track | Individual track with capacity and fill factor | Entity |
| TrackOccupancy | Manage wagon placement and capacity | Aggregate |
| TrackSelectionService | Select tracks based on strategies | Domain Service |
| TrackGroupService / TrackOccupancyService / TopologyService | Track grouping, occupancy, and topology queries | Domain Services |
Repositories (domain/repositories/) and their adapters/DI wiring
(infrastructure/di_container.py, infrastructure/adapters.py) provide persistence-style
access to yard and occupancy state.
Track Selection Strategies¶
Track selection uses the shared SelectionStrategy value object:
- FIRST_AVAILABLE: Select first track with available capacity
- LEAST_OCCUPIED: Select track with lowest occupancy ratio
- ROUND_ROBIN: Cycle through available tracks
- BEST_FIT: Select the track that best fits the required length
- RANDOM: Random selection from available tracks
5a.4 External Trains Context - Level 3¶
Component Diagram¶
graph TB
subgraph "External Trains Context - Level 3"
Context["ExternalTrainsContext<br/>Train arrival management"]
subgraph "Domain"
Schedule["TrainSchedule<br/>Scheduled arrivals (aggregate)"]
Train["ExternalTrain<br/>Train entity"]
end
subgraph "Events"
TrainEvent["TrainArrivedEvent<br/>Train + wagons"]
end
subgraph "Ports & Adapters"
Port["ExternalTrainsContextPort"]
end
end
Context --> Schedule
Schedule --> Train
Context --> TrainEvent
Context --> Port
classDef component fill:#e1f5fe,stroke:#01579b,stroke-width:2px
class Context,Schedule,Train,TrainEvent,Port component
Components¶
| Component | File | Responsibility |
|---|---|---|
| ExternalTrainsContext | application/external_trains_context.py |
Schedule train arrivals, create wagons, publish events |
| TrainSchedule | domain/aggregates/train_schedule.py |
Manage scheduled train arrivals |
| ExternalTrain | domain/entities/external_train.py |
Train entity with arrival time and wagons |
| Train events | domain/events/train_events.py |
Train arrival/departure domain events |
| ExternalTrainsContextPort | application/ports/external_trains_context_port.py |
Port exposing the context to other contexts |
The context publishes the shared TrainArrivedEvent
(shared/domain/events/wagon_lifecycle_events.py) onto the event bus; wagon entities are
created directly by the context (there is no separate WagonFactory/EventPublisher).
Train Arrival Flow¶
sequenceDiagram
participant Scenario
participant ExternalTrains
participant EventBus
participant RetrofitWorkflow
Scenario->>ExternalTrains: Initialize with trains
ExternalTrains->>ExternalTrains: Schedule arrivals
loop For each train
ExternalTrains->>EventBus: Publish TrainArrivedEvent
EventBus->>RetrofitWorkflow: Deliver event
RetrofitWorkflow->>RetrofitWorkflow: Process wagons
end
5a.5 Integration Patterns¶
Event Bus Communication¶
from infrastructure.event_bus.event_bus import EventBus
from shared.domain.events.wagon_lifecycle_events import TrainArrivedEvent
# External Trains publishes an event onto the shared bus
event_bus.publish(TrainArrivedEvent(...))
# Retrofit Workflow subscribes
event_bus.subscribe(TrainArrivedEvent, handler)
The concrete
EventBuslives ininfrastructure/event_bus/event_bus.py; the shared domain events (includingTrainArrivedEvent) live inshared/domain/events/.
Track Selection¶
from contexts.railway_infrastructure.application.railway_context import RailwayInfrastructureContext
# Build the railway context (via its DI factory in practice — see di_container.py)
railway = RailwayInfrastructureContext(...)
# Track selection is provided by TrackSelectionService using a SelectionStrategy
# (see contexts/retrofit_workflow/domain/services/track_selection_service.py)
Resource Allocation¶
from contexts.retrofit_workflow.infrastructure.resources.locomotive_resource_manager import (
LocomotiveResourceManager,
)
# Locomotive allocation/release is coordinated through the resource manager
# and driven as a SimPy process by the coordinators.
5a.6 File Organization¶
popupsim/backend/src/
├── main.py # CLI entry point
├── application/
│ └── simulation_service.py # Orchestrates all contexts
├── contexts/
│ ├── configuration/ # Configuration Context
│ │ ├── domain/
│ │ │ ├── configuration_builder.py # Load from files
│ │ │ └── models/
│ │ │ ├── scenario.py # Root Scenario model
│ │ │ ├── process_times.py
│ │ │ └── topology.py
│ │ ├── application/
│ │ │ └── dtos/ # Input DTOs (train, wagon, track, workshop, ...)
│ │ └── infrastructure/
│ │ └── file_loader.py # Parse JSON/CSV
│ ├── retrofit_workflow/ # Retrofit Workflow Context
│ │ ├── application/
│ │ │ ├── retrofit_workflow_context.py # Main context
│ │ │ └── coordinators/
│ │ │ ├── arrival_coordinator.py
│ │ │ ├── collection_coordinator.py
│ │ │ ├── workshop_coordinator.py
│ │ │ └── parking_coordinator.py
│ │ ├── domain/
│ │ │ └── services/
│ │ │ ├── batch_formation_service.py
│ │ │ ├── rake_formation_service.py
│ │ │ ├── train_formation_service.py
│ │ │ ├── workshop_scheduling_service.py
│ │ │ ├── coupling_service.py
│ │ │ └── route_service.py
│ │ └── infrastructure/
│ │ ├── resources/
│ │ │ ├── locomotive_resource_manager.py
│ │ │ ├── track_capacity_manager.py
│ │ │ └── workshop_resource_manager.py
│ │ ├── exporters/
│ │ │ ├── csv_event_exporter.py
│ │ │ └── dual_stream_csv_exporter.py
│ │ ├── adapters/
│ │ └── di_container.py
│ ├── railway_infrastructure/ # Railway Infrastructure Context
│ │ ├── application/
│ │ │ ├── railway_context.py # RailwayInfrastructureContext
│ │ │ └── track_occupancy_event_handler.py
│ │ ├── domain/
│ │ │ ├── aggregates/
│ │ │ │ ├── railway_yard.py
│ │ │ │ ├── track_group.py
│ │ │ │ └── track_occupancy.py
│ │ │ ├── entities/
│ │ │ │ └── track.py
│ │ │ ├── repositories/
│ │ │ │ ├── railway_yard_repository.py
│ │ │ │ └── track_occupancy_repository.py
│ │ │ └── services/
│ │ │ ├── track_selection_service.py
│ │ │ ├── track_group_service.py
│ │ │ ├── track_occupancy_service.py
│ │ │ └── topology_service.py
│ │ └── infrastructure/
│ │ ├── di_container.py # RailwayContextFactory
│ │ └── adapters.py
│ ├── external_trains/ # External Trains Context
│ │ ├── application/
│ │ │ ├── external_trains_context.py # Train arrival management
│ │ │ └── ports/
│ │ │ └── external_trains_context_port.py
│ │ ├── domain/
│ │ │ ├── aggregates/
│ │ │ │ └── train_schedule.py
│ │ │ ├── entities/
│ │ │ │ └── external_train.py
│ │ │ ├── events/
│ │ │ │ └── train_events.py
│ │ │ └── value_objects/
│ │ │ ├── train_id.py
│ │ │ └── arrival_metrics.py
│ │ └── infrastructure/
│ │ └── adapters/
│ └── shared/ # Shared Kernel
│ ├── domain/
│ │ ├── events/ # Shared domain events (wagon lifecycle, ...)
│ │ └── value_objects/ # e.g. selection_strategy.py
│ └── infrastructure/
│ └── simulation/
│ └── engines/
│ └── simpy_adapter.py # SimPyEngineAdapter
├── infrastructure/ # Technical infrastructure (outside contexts)
│ ├── event_bus/
│ │ └── event_bus.py # EventBus
│ └── tracking/ # Process/state tracking + export helpers
└── optimizer/ # Two-phase scenario optimization
Tests live under popupsim/backend/tests/ (not src/tests/):
popupsim/backend/tests/
├── unit/
│ ├── contexts/
│ │ ├── configuration/
│ │ ├── retrofit_workflow/
│ │ ├── railway_infrastructure/
│ │ └── optimizer_search/
│ └── ...
├── validation/
└── fixtures/
5a.7 Wagon State Machine¶
stateDiagram-v2
[*] --> ARRIVING: Train arrives
ARRIVING --> SELECTING: At hump
SELECTING --> SELECTED: Needs retrofit + capacity available
SELECTING --> REJECTED: No capacity
SELECTED --> MOVING_TO_COLLECTION: Locomotive pickup
MOVING_TO_COLLECTION --> ON_COLLECTION_TRACK: Delivered
ON_COLLECTION_TRACK --> MOVING_TO_RETROFIT: Batch formed
MOVING_TO_RETROFIT --> ON_RETROFIT_TRACK: At retrofit track
ON_RETROFIT_TRACK --> MOVING_TO_WORKSHOP: Workshop ready
MOVING_TO_WORKSHOP --> RETROFITTING: At station
RETROFITTING --> RETROFITTED: Retrofit complete
RETROFITTED --> MOVING_TO_RETROFITTED: Pickup batch
MOVING_TO_RETROFITTED --> ON_RETROFITTED_TRACK: At retrofitted track
ON_RETROFITTED_TRACK --> MOVING_TO_PARKING: To parking
MOVING_TO_PARKING --> PARKING: At parking track
REJECTED --> [*]
PARKING --> [*]
5a.8 Technology Integration¶
SimPy Integration¶
SimPyEngineAdapter (shared/infrastructure/simulation/engines/simpy_adapter.py)
wraps SimPy behind a port (SimulationEnginePort):
class SimPyEngineAdapter(SimulationEnginePort):
def current_time(self) -> float
def delay(self, duration: float | timedelta) -> Generator
def schedule_process(self, process: Generator | Callable) -> Any
def create_resource(self, capacity: int, name: str | None = None) -> simpy.Resource
def create_store(self, capacity: int | None = None, name: str | None = None) -> Any
def create_event(self) -> Any
def run(self, until: float | None = None) -> None
Pydantic Integration¶
All domain models use Pydantic for: - Type safety - Automatic validation - JSON serialization - Field constraints - Custom validators
Event Bus Integration¶
from infrastructure.event_bus.event_bus import EventBus
from shared.domain.events.wagon_lifecycle_events import TrainArrivedEvent
# Initialize event bus
event_bus = EventBus()
# Subscribe to events
event_bus.subscribe(TrainArrivedEvent, handler)
# Publish events
event_bus.publish(TrainArrivedEvent(...))