Commit ·
026e398
1
Parent(s): c7ea7db
aligning with backend
Browse files- api/greedyoptim_api.py +3 -3
- greedyOptim/error_handling.py +49 -3
- greedyOptim/evaluator.py +38 -2
api/greedyoptim_api.py
CHANGED
|
@@ -53,7 +53,7 @@ app.add_middleware(
|
|
| 53 |
class TrainsetStatusInput(BaseModel):
|
| 54 |
"""Single trainset operational status"""
|
| 55 |
trainset_id: str
|
| 56 |
-
operational_status: str = Field(..., description="Available, In-Service, Maintenance, Standby, Out-of-Order")
|
| 57 |
last_maintenance_date: Optional[str] = None
|
| 58 |
total_mileage_km: Optional[float] = None
|
| 59 |
age_years: Optional[float] = None
|
|
@@ -63,7 +63,7 @@ class FitnessCertificateInput(BaseModel):
|
|
| 63 |
"""Fitness certificate for a trainset"""
|
| 64 |
trainset_id: str
|
| 65 |
department: str = Field(..., description="Safety, Operations, Technical, Electrical, Mechanical")
|
| 66 |
-
status: str = Field(..., description="Valid, Expired, Expiring-Soon, Suspended")
|
| 67 |
issue_date: Optional[str] = None
|
| 68 |
expiry_date: Optional[str] = None
|
| 69 |
|
|
@@ -82,7 +82,7 @@ class ComponentHealthInput(BaseModel):
|
|
| 82 |
"""Component health status"""
|
| 83 |
trainset_id: str
|
| 84 |
component: str = Field(..., description="Brakes, HVAC, Doors, Propulsion, etc.")
|
| 85 |
-
status: str = Field(..., description="Good, Fair, Warning, Critical")
|
| 86 |
wear_level: Optional[float] = Field(None, ge=0, le=100)
|
| 87 |
last_inspection: Optional[str] = None
|
| 88 |
|
|
|
|
| 53 |
class TrainsetStatusInput(BaseModel):
|
| 54 |
"""Single trainset operational status"""
|
| 55 |
trainset_id: str
|
| 56 |
+
operational_status: str = Field(..., description="IN_SERVICE, STANDBY, MAINTENANCE, OUT_OF_SERVICE, TESTING (or legacy: Available, In-Service, Maintenance, Standby, Out-of-Order)")
|
| 57 |
last_maintenance_date: Optional[str] = None
|
| 58 |
total_mileage_km: Optional[float] = None
|
| 59 |
age_years: Optional[float] = None
|
|
|
|
| 63 |
"""Fitness certificate for a trainset"""
|
| 64 |
trainset_id: str
|
| 65 |
department: str = Field(..., description="Safety, Operations, Technical, Electrical, Mechanical")
|
| 66 |
+
status: str = Field(..., description="ISSUED, EXPIRED, SUSPENDED, PENDING, IN_PROGRESS, REVOKED, RENEWED, CANCELLED (or legacy: Valid, Expired, Expiring-Soon, Suspended)")
|
| 67 |
issue_date: Optional[str] = None
|
| 68 |
expiry_date: Optional[str] = None
|
| 69 |
|
|
|
|
| 82 |
"""Component health status"""
|
| 83 |
trainset_id: str
|
| 84 |
component: str = Field(..., description="Brakes, HVAC, Doors, Propulsion, etc.")
|
| 85 |
+
status: str = Field(..., description="EXCELLENT, GOOD, FAIR, POOR, CRITICAL, FAILED (or legacy: Good, Fair, Warning, Critical)")
|
| 86 |
wear_level: Optional[float] = Field(None, ge=0, le=100)
|
| 87 |
last_inspection: Optional[str] = None
|
| 88 |
|
greedyOptim/error_handling.py
CHANGED
|
@@ -37,11 +37,57 @@ class DataValidator:
|
|
| 37 |
'component_health': ['trainset_id', 'component', 'status']
|
| 38 |
}
|
| 39 |
|
|
|
|
| 40 |
VALID_STATUSES = {
|
| 41 |
-
'operational': [
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
'job': ['Open', 'In-Progress', 'Closed', 'Pending-Parts'],
|
| 44 |
-
'component': [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
|
| 47 |
@classmethod
|
|
|
|
| 37 |
'component_health': ['trainset_id', 'component', 'status']
|
| 38 |
}
|
| 39 |
|
| 40 |
+
# Accept both legacy and new backend formats
|
| 41 |
VALID_STATUSES = {
|
| 42 |
+
'operational': [
|
| 43 |
+
# Legacy format
|
| 44 |
+
'Available', 'In-Service', 'Maintenance', 'Standby', 'Out-of-Order',
|
| 45 |
+
# New backend format
|
| 46 |
+
'IN_SERVICE', 'STANDBY', 'MAINTENANCE', 'OUT_OF_SERVICE', 'TESTING'
|
| 47 |
+
],
|
| 48 |
+
'certificate': [
|
| 49 |
+
# Legacy format
|
| 50 |
+
'Valid', 'Expired', 'Expiring-Soon', 'Suspended',
|
| 51 |
+
# New backend format
|
| 52 |
+
'PENDING', 'IN_PROGRESS', 'ISSUED', 'EXPIRED', 'SUSPENDED',
|
| 53 |
+
'REVOKED', 'RENEWED', 'CANCELLED'
|
| 54 |
+
],
|
| 55 |
'job': ['Open', 'In-Progress', 'Closed', 'Pending-Parts'],
|
| 56 |
+
'component': [
|
| 57 |
+
# Legacy format
|
| 58 |
+
'Good', 'Fair', 'Warning', 'Critical',
|
| 59 |
+
# New backend format
|
| 60 |
+
'EXCELLENT', 'GOOD', 'FAIR', 'POOR', 'CRITICAL', 'FAILED'
|
| 61 |
+
]
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
# Mapping from backend format to internal format for optimization logic
|
| 65 |
+
STATUS_MAPPINGS = {
|
| 66 |
+
'operational': {
|
| 67 |
+
'IN_SERVICE': 'In-Service',
|
| 68 |
+
'STANDBY': 'Standby',
|
| 69 |
+
'MAINTENANCE': 'Maintenance',
|
| 70 |
+
'OUT_OF_SERVICE': 'Out-of-Order',
|
| 71 |
+
'TESTING': 'Maintenance', # Treat testing as maintenance for optimization
|
| 72 |
+
},
|
| 73 |
+
'certificate': {
|
| 74 |
+
'PENDING': 'Expiring-Soon',
|
| 75 |
+
'IN_PROGRESS': 'Expiring-Soon',
|
| 76 |
+
'ISSUED': 'Valid',
|
| 77 |
+
'EXPIRED': 'Expired',
|
| 78 |
+
'SUSPENDED': 'Suspended',
|
| 79 |
+
'REVOKED': 'Expired',
|
| 80 |
+
'RENEWED': 'Valid',
|
| 81 |
+
'CANCELLED': 'Expired',
|
| 82 |
+
},
|
| 83 |
+
'component': {
|
| 84 |
+
'EXCELLENT': 'Good',
|
| 85 |
+
'GOOD': 'Good',
|
| 86 |
+
'FAIR': 'Fair',
|
| 87 |
+
'POOR': 'Warning',
|
| 88 |
+
'CRITICAL': 'Critical',
|
| 89 |
+
'FAILED': 'Critical',
|
| 90 |
+
}
|
| 91 |
}
|
| 92 |
|
| 93 |
@classmethod
|
greedyOptim/evaluator.py
CHANGED
|
@@ -9,6 +9,38 @@ from typing import Dict, List, Tuple, Optional
|
|
| 9 |
from .models import OptimizationConfig, TrainsetConstraints
|
| 10 |
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
class TrainsetSchedulingEvaluator:
|
| 13 |
"""Multi-objective evaluator for trainset scheduling optimization."""
|
| 14 |
|
|
@@ -68,7 +100,9 @@ class TrainsetSchedulingEvaluator:
|
|
| 68 |
has_valid_certs = True
|
| 69 |
if trainset_id in self.fitness_map:
|
| 70 |
for dept, cert in self.fitness_map[trainset_id].items():
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
has_valid_certs = False
|
| 73 |
break
|
| 74 |
try:
|
|
@@ -94,7 +128,9 @@ class TrainsetSchedulingEvaluator:
|
|
| 94 |
component_warnings = []
|
| 95 |
if trainset_id in self.health_map:
|
| 96 |
for health in self.health_map[trainset_id]:
|
| 97 |
-
|
|
|
|
|
|
|
| 98 |
component_warnings.append(health['component'])
|
| 99 |
|
| 100 |
# Check maintenance status
|
|
|
|
| 9 |
from .models import OptimizationConfig, TrainsetConstraints
|
| 10 |
|
| 11 |
|
| 12 |
+
# Status normalization mappings (backend format -> internal format)
|
| 13 |
+
CERTIFICATE_STATUS_MAP = {
|
| 14 |
+
'PENDING': 'Expiring-Soon',
|
| 15 |
+
'IN_PROGRESS': 'Expiring-Soon',
|
| 16 |
+
'ISSUED': 'Valid',
|
| 17 |
+
'EXPIRED': 'Expired',
|
| 18 |
+
'SUSPENDED': 'Suspended',
|
| 19 |
+
'REVOKED': 'Expired',
|
| 20 |
+
'RENEWED': 'Valid',
|
| 21 |
+
'CANCELLED': 'Expired',
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
COMPONENT_STATUS_MAP = {
|
| 25 |
+
'EXCELLENT': 'Good',
|
| 26 |
+
'GOOD': 'Good',
|
| 27 |
+
'FAIR': 'Fair',
|
| 28 |
+
'POOR': 'Warning',
|
| 29 |
+
'CRITICAL': 'Critical',
|
| 30 |
+
'FAILED': 'Critical',
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def normalize_certificate_status(status: str) -> str:
|
| 35 |
+
"""Normalize certificate status to internal format."""
|
| 36 |
+
return CERTIFICATE_STATUS_MAP.get(status, status)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def normalize_component_status(status: str) -> str:
|
| 40 |
+
"""Normalize component status to internal format."""
|
| 41 |
+
return COMPONENT_STATUS_MAP.get(status, status)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
class TrainsetSchedulingEvaluator:
|
| 45 |
"""Multi-objective evaluator for trainset scheduling optimization."""
|
| 46 |
|
|
|
|
| 100 |
has_valid_certs = True
|
| 101 |
if trainset_id in self.fitness_map:
|
| 102 |
for dept, cert in self.fitness_map[trainset_id].items():
|
| 103 |
+
# Normalize status to handle both legacy and backend formats
|
| 104 |
+
status = normalize_certificate_status(cert['status'])
|
| 105 |
+
if status in ['Expired']:
|
| 106 |
has_valid_certs = False
|
| 107 |
break
|
| 108 |
try:
|
|
|
|
| 128 |
component_warnings = []
|
| 129 |
if trainset_id in self.health_map:
|
| 130 |
for health in self.health_map[trainset_id]:
|
| 131 |
+
# Normalize status to handle both legacy and backend formats
|
| 132 |
+
status = normalize_component_status(health['status'])
|
| 133 |
+
if status in ['Warning', 'Critical'] and health.get('wear_level', 0) > 90:
|
| 134 |
component_warnings.append(health['component'])
|
| 135 |
|
| 136 |
# Check maintenance status
|