8. Cross-Cutting Concepts (MVP)¶
This section describes architectural concepts and patterns that apply across multiple building blocks. Code examples illustrate these concepts but are not production code.
8.1 Layered Architecture¶
Note: Each bounded context follows a layered architecture pattern (ADR MVP-005).
Layer Structure (Applied to Each Context)¶
graph TB
subgraph "MVP Layers"
subgraph "Presentation"
CLI[CLI Interface]
end
subgraph "Business Logic"
ConfigService[Configuration Service]
DomainService[Workshop Operations Service]
SimulationService[Analysis & Reporting Service]
end
subgraph "Data Access"
JSONReader[JSON Reader]
CSVWriter[CSV Writer]
end
subgraph "Infrastructure"
SimPy[SimPy Framework]
Matplotlib[Matplotlib]
end
end
CLI --> ConfigService
CLI --> SimulationService
ConfigService --> JSONReader
DomainService --> SimPy
SimulationService --> CSVWriter
SimulationService --> Matplotlib
classDef presentation fill:#4caf50,stroke:#2e7d32,stroke-width:2px,color:#fff
classDef business fill:#2196f3,stroke:#1565c0,stroke-width:2px,color:#fff
classDef data fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
classDef infrastructure fill:#9e9e9e,stroke:#616161,stroke-width:2px,color:#fff
class CLI presentation
class ConfigService,DomainService,SimulationService business
class JSONReader,CSVWriter data
class SimPy,Matplotlib infrastructure
| Layer | Responsibility | Components |
|---|---|---|
| Application | Coordinators, context orchestration | ArrivalCoordinator, CollectionCoordinator, WorkshopCoordinator, ParkingCoordinator |
| Domain | Business logic, domain services | BatchFormationService, RakeFormationService, TrainFormationService, WorkshopSchedulingService |
| Infrastructure | External frameworks, resource management | SimPy, LocomotiveResourceManager, TrackCapacityManager, WorkshopResourceManager |
Rationale: Layered architecture provides clear separation of concerns within each bounded context, enabling rapid MVP development while maintaining code organization. See Section 4.3 for architectural pattern decision.
8.2 Domain Model with Standardized Field Names¶
Field Name Standardization: All entity IDs use consistent naming: id (not prefix_id) and track (not track_id) for track references.
WARNING: Code examples in this section are simplified illustrations for architecture documentation only. They are NOT production-ready and lack: - Complete validation logic - Full type hints and error handling - Business rules and edge cases - Comprehensive docstrings
DO NOT copy-paste these examples into production code.
MVP Core Entities¶
classDiagram
class Workshop {
+id: str
+track: str
+retrofit_stations: int
+get_available_capacity()
+calculate_throughput()
}
class Locomotive {
+id: str
+track: str
+status: LocomotiveStatus
+is_available()
+assign_to_job()
}
class Wagon {
+id: str
+track: str
+length: float
+needs_retrofit: bool
+status: WagonStatus
}
class Train {
+id: str
+locomotive_id: str
+route_id: str
+wagons: List[Wagon]
+arrival_time: datetime
}
Workshop --o Station
Station --o Worker
Station --o Wagon
MVP Configuration Models¶
Actual implementation: popupsim/backend/src/configuration/model_*.py
# SIMPLIFIED EXAMPLE - See actual files for complete implementation
from datetime import date
from pydantic import BaseModel, Field
class ScenarioConfig(BaseModel):
"""Configuration model for simulation scenarios."""
scenario_id: str = Field(pattern=r'^[a-zA-Z0-9_-]+$', min_length=1, max_length=50)
start_date: date
end_date: date
workshop: Workshop | None = None
train_schedule_file: str
routes_file: str | None = None
workshop_tracks_file: str | None = None
# ... additional fields and validators in actual implementation
class Workshop(BaseModel):
"""Workshop models with available tracks."""
tracks: list[WorkshopTrack] = Field(min_length=1)
# ... additional validators in actual implementation
class WorkshopTrack(BaseModel):
"""Individual track within workshop."""
id: str
function: TrackFunction
capacity: int = Field(ge=1)
retrofit_time_min: int = Field(ge=0)
# ... additional fields in actual implementation
MVP Result Models¶
# SIMPLIFIED EXAMPLE - Actual structure to be defined during implementation
class SimulationResults(BaseModel):
total_wagons_processed: int
simulation_duration_hours: float
throughput_per_hour: float
average_waiting_time: float
station_utilization: float
# ... additional KPIs to be determined
class KPIData(BaseModel):
timestamp: str
throughput: float
utilization: float
queue_length: int
waiting_time: float
# ... additional metrics to be determined
8.3 Error Handling¶
Error handling strategy supports the quality goals defined in Section 1.2, particularly Simulation Accuracy & Reliability (Priority 2). See Section 6 for error scenarios in runtime view.
MVP Exception Hierarchy¶
# CONCEPTUAL EXAMPLE - Illustrates exception hierarchy pattern
class PopUpSimError(Exception):
"""Base exception for PopUpSim MVP"""
pass
class ConfigurationError(PopUpSimError):
"""Configuration loading/validation errors"""
pass
class SimulationError(PopUpSimError):
"""Simulation runtime errors"""
pass
class OutputError(PopUpSimError):
"""Output generation errors"""
pass
MVP Error Handling Strategy¶
graph TB
subgraph "MVP Error Handling"
Try[Try Operation]
Catch[Catch Exception]
Log[Log Error]
Recover[Attempt Recovery]
Fail[Graceful Failure]
end
Try --> Catch
Catch --> Log
Log --> Recover
Recover --> Fail
classDef error fill:#ffebee,stroke:#d32f2f
classDef recovery fill:#e8f5e8,stroke:#388e3c
class Try,Catch,Log error
class Recover,Fail recovery
8.4 Logging Concept¶
Logging configuration is defined in Section 7.7.
MVP Logging Levels¶
| Level | MVP Usage | Example |
|---|---|---|
| DEBUG | Detailed development info | SimPy event details |
| INFO | Normal program execution | "Simulation started" |
| WARNING | Potential problems | "High queue length detected" |
| ERROR | Errors with recovery | "Config file not found, using defaults" |
| CRITICAL | Severe errors | "Simulation failed completely" |
MVP Log Format¶
# CONCEPTUAL EXAMPLE - Illustrates logging pattern
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
# Example log output:
# 2025-10-15 10:00:00,123 - ConfigService - INFO - Loading scenario.json
# 2025-10-15 10:00:01,456 - WorkshopService - INFO - Created 4 stations
# 2025-10-15 10:00:02,789 - SimulationService - INFO - Starting simulation
# 2025-10-15 10:05:30,012 - SimulationService - WARNING - Queue length: 25
# 2025-10-15 10:10:45,345 - OutputService - INFO - Generated results.csv
8.5 Configuration Management¶
Configuration management supports Usability & Accessibility (Priority 3) through file-based configuration. See Section 5.2 for Configuration Context details.
MVP Configuration Loading¶
sequenceDiagram
participant Main as main.py
participant CS as ConfigService
participant Validator as Validator
participant Files as File System
Main->>CS: load_configuration()
CS->>Files: read scenario.json
Files->>CS: json_data
CS->>Files: read workshop_config.csv
Files->>CS: csv_data
CS->>Validator: validate_config(data)
Validator->>CS: validation_result
CS->>Main: merged_config
MVP Configuration Validation¶
# CONCEPTUAL EXAMPLE - Illustrates validation pattern
def validate_scenario_config(config: dict) -> List[str]:
"""MVP Configuration Validation"""
errors = []
# Basic validation rules
if config.get('duration_hours', 0) <= 0:
errors.append("Duration must be positive")
if config.get('stations', 0) < 1:
errors.append("At least one station required")
if config.get('workers_per_station', 0) < 1:
errors.append("At least one worker per station required")
return errors
8.6 Testing Concept¶
Testing strategy supports Testability (Priority 5) quality goal. See Section 7.10 for testing tools (Pytest, MyPy, Ruff).
MVP Test Strategy¶
graph TB
subgraph "MVP Testing Pyramid"
subgraph "Unit Tests"
ConfigTests[Configuration Tests]
ModelTests[Domain Model Tests]
ServiceTests[Service Logic Tests]
end
subgraph "Integration Tests"
FileTests[File I/O Tests]
SimPyTests[SimPy Integration Tests]
end
subgraph "System Tests"
E2ETests[End-to-End Simulation Tests]
end
end
ConfigTests --> FileTests
ModelTests --> SimPyTests
ServiceTests --> E2ETests
classDef unit fill:#4caf50,stroke:#2e7d32
classDef integration fill:#ff9800,stroke:#e65100
classDef system fill:#2196f3,stroke:#1565c0
class ConfigTests,ModelTests,ServiceTests unit
class FileTests,SimPyTests integration
class E2ETests system
MVP Test Examples¶
# CONCEPTUAL EXAMPLES - Illustrate testing patterns
# Unit Test Example
def test_workshop_station_availability():
station = Station(id="WS001", capacity=2, workers=[], current_wagons=1)
assert station.is_available() == True
station.current_wagons = 2
assert station.is_available() == False
# Integration Test Example
def test_configuration_loading():
config_service = ConfigurationService()
config = config_service.load_scenario("test_data/")
assert config.duration_hours > 0
assert len(config.workshop.stations) > 0
8.7 Performance Concept¶
Performance monitoring will measure actual resource usage during MVP implementation. See Section 7.8 for performance metrics.
MVP Performance Monitoring¶
# CONCEPTUAL EXAMPLE - Illustrates performance monitoring pattern
import time
from functools import wraps
from typing import Any, Callable, TypeVar
T = TypeVar('T')
def measure_time(func: Callable[..., T]) -> Callable[..., T]:
"""MVP Performance Decorator"""
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> T:
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
duration = end_time - start_time
logging.info(f"{func.__name__} took {duration:.2f} seconds")
return result
return wrapper
# Usage
@measure_time
def run_simulation(duration_hours: int) -> None:
# Simulation logic
pass
MVP Memory Management¶
# CONCEPTUAL EXAMPLE - Illustrates memory monitoring pattern
import gc
import psutil
import os
def log_memory_usage(phase: str) -> None:
"""MVP Memory Monitoring"""
process = psutil.Process(os.getpid())
memory_mb = process.memory_info().rss / 1024 / 1024
logging.info(f"Memory usage in {phase}: {memory_mb:.1f} MB")
# Force garbage collection for MVP
gc.collect()
8.8 4-Layer Validation Framework¶
Enterprise-grade validation with comprehensive error stacking ensures Simulation Accuracy & Reliability (Priority 2). See validation framework documentation in the codebase for complete details.
4-Layer Validation Architecture¶
graph TB
subgraph "Validation Pipeline"
Input["Scenario Data"]
subgraph "Layer 1: SYNTAX"
Syntax["Field format validation<br/>Type checking<br/>Required fields"]
end
subgraph "Layer 2: SEMANTIC"
Semantic["Business rules<br/>Date logic<br/>Strategy validation"]
end
subgraph "Layer 3: INTEGRITY"
Integrity["Cross-references<br/>Data consistency<br/>Duplicate detection"]
end
subgraph "Layer 4: FEASIBILITY"
Feasibility["Capacity constraints<br/>Resource allocation<br/>Simulation readiness"]
end
Result["ValidationResult<br/>All issues stacked"]
end
Input --> Syntax
Syntax --> Semantic
Semantic --> Integrity
Integrity --> Feasibility
Feasibility --> Result
classDef layer fill:#e1f5fe,stroke:#01579b,stroke-width:2px
classDef validation fill:#e8f5e8,stroke:#388e3c,stroke-width:2px
classDef external fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
class Syntax,Semantic,Integrity,Feasibility layer
class Result validation
class Input external
Error Stacking vs Fail-Fast Comparison¶
| Approach | User Experience | Development Efficiency | Error Quality |
|---|---|---|---|
| Fail-Fast (Traditional) | ❌ Fix 1 error → Run again → Fix 1 error | ⭐⭐ Slow iteration | ⭐⭐ Limited context |
| Error Stacking (PopUpSim) | ✅ See ALL issues at once → Fix all | ⭐⭐⭐⭐⭐ Fast iteration | ⭐⭐⭐⭐⭐ Complete context |
Validation Layer Examples¶
# ACTUAL IMPLEMENTATION - Layer 3: Integrity Validator
class IntegrityValidator:
"""Validates cross-references and data consistency."""
def validate(self, scenario: Scenario) -> ValidationResult:
result = ValidationResult(is_valid=True)
# Collect all available IDs
locomotive_ids = {loco.id for loco in scenario.locomotives or []}
route_ids = {route.id for route in scenario.routes or []}
# Validate train references
for i, train in enumerate(scenario.trains or []):
if train.locomotive_id not in locomotive_ids:
result.add_error(
f"Train {train.id} references non-existent locomotive '{train.locomotive_id}'",
field=f"trains[{i}].locomotive_id",
category=ValidationCategory.INTEGRITY,
suggestion=f"Use one of: {', '.join(locomotive_ids)}"
)
return result
# ACTUAL IMPLEMENTATION - Validation Pipeline Usage
from shared.validation.pipeline import ValidationPipeline
pipeline = ValidationPipeline()
result = pipeline.validate(scenario)
if not result.is_valid:
result.print_summary() # Shows categorized issues
print(f"Found {len(result.get_errors())} errors, {len(result.get_warnings())} warnings")
Validation Result Output Example¶
📋 Validation Summary: 5 errors, 2 warnings
SYNTAX ERRORS:
- Scenario ID too long (Field: id)
→ Suggestion: Keep scenario ID under 50 characters
INTEGRITY ERRORS:
- Train T1 references non-existent locomotive 'L99' (Field: trains[0].locomotive_id)
→ Suggestion: Use one of: L1, L2, L3
- Route R1 references non-existent track 'T99' (Field: routes[0].track_sequence[2])
→ Suggestion: Use existing track ID from: T1, T2, T3
- No locomotives configured (Field: locomotives)
→ Suggestion: Add at least one locomotive for wagon transport
FEASIBILITY WARNINGS:
- High wagon-to-station ratio (150.0:1) may cause bottlenecks (Field: workshops)
→ Suggestion: Consider adding more retrofit stations
- Long simulation duration (400 days) may impact performance (Field: end_date)
→ Suggestion: Consider shorter simulation periods
8.9 Security Concept¶
Security measures for MVP desktop application focus on input validation and safe file handling.
MVP Security Considerations¶
| Area | MVP Measure | Rationale |
|---|---|---|
| Input Validation | Pydantic Models | Prevents invalid data |
| File Access | Relative Paths Only | Prevents directory traversal |
| Error Messages | No system paths | Prevents information disclosure |
| Logging | No credentials | Prevents credential leakage |
MVP File Security¶
# CONCEPTUAL EXAMPLE - Illustrates path traversal prevention pattern
import os
from pathlib import Path
def safe_file_path(base_dir: str, filename: str) -> Path:
"""MVP Safe File Path Resolution"""
base_path = Path(base_dir).resolve()
file_path = (base_path / filename).resolve()
# Ensure file is within base directory
if not str(file_path).startswith(str(base_path)):
raise ValueError(f"Invalid file path: {filename}")
return file_path