Commit
·
c7ea7db
1
Parent(s):
062c4f5
job cards optional everywhere
Browse files- DataService/enhanced_generator.py +23 -8
- DataService/metro_models.py +1 -1
- DataService/schedule_optimizer.py +4 -2
- DataService/synthetic_base.py +15 -6
- api/greedyoptim_api.py +3 -3
- greedyOptim/error_handling.py +13 -2
- greedyOptim/evaluator.py +2 -2
DataService/enhanced_generator.py
CHANGED
|
@@ -256,7 +256,7 @@ class EnhancedMetroDataGenerator:
|
|
| 256 |
estimated_hours = random.randint(2, 24)
|
| 257 |
|
| 258 |
job = {
|
| 259 |
-
"
|
| 260 |
"trainset_id": ts_id,
|
| 261 |
"work_order_number": f"WO-{random.randint(100000, 999999)}",
|
| 262 |
"job_type": random.choice(job_types),
|
|
@@ -391,8 +391,12 @@ class EnhancedMetroDataGenerator:
|
|
| 391 |
|
| 392 |
return contracts
|
| 393 |
|
| 394 |
-
def generate_complete_enhanced_dataset(self) -> Dict:
|
| 395 |
-
"""Generate complete enhanced dataset with all improvements.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
print("Generating enhanced synthetic data...")
|
| 397 |
|
| 398 |
dataset = {
|
|
@@ -413,7 +417,7 @@ class EnhancedMetroDataGenerator:
|
|
| 413 |
"trainset_profiles": self.trainset_profiles,
|
| 414 |
"trainset_status": self.generate_enhanced_trainset_status(),
|
| 415 |
"fitness_certificates": self.generate_realistic_fitness_certificates(),
|
| 416 |
-
"job_cards": self.generate_correlated_job_cards(),
|
| 417 |
"component_health": self.generate_realistic_component_health(),
|
| 418 |
"branding_contracts": self.generate_optimized_branding_contracts(),
|
| 419 |
# Keep the existing generators for other data
|
|
@@ -444,6 +448,11 @@ class EnhancedMetroDataGenerator:
|
|
| 444 |
"bogie_2": round(random.uniform(0.5, 3.5) / reliability_factor, 2),
|
| 445 |
"unit": "mm/s"
|
| 446 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
"overall_condition": "Good" if reliability_factor > 0.85 else "Fair" if reliability_factor > 0.75 else "Poor"
|
| 448 |
}
|
| 449 |
sensor_data.append(sensors)
|
|
@@ -555,7 +564,8 @@ class EnhancedMetroDataGenerator:
|
|
| 555 |
return {
|
| 556 |
"date": datetime.now().date().isoformat(),
|
| 557 |
"weather": {
|
| 558 |
-
"condition": random.choice(["Clear", "Cloudy", "Rainy"])
|
|
|
|
| 559 |
},
|
| 560 |
"ridership_forecast": {
|
| 561 |
"expected_passengers": random.randint(80000, 150000),
|
|
@@ -563,9 +573,14 @@ class EnhancedMetroDataGenerator:
|
|
| 563 |
}
|
| 564 |
}
|
| 565 |
|
| 566 |
-
def save_to_json(self, filename: str = "metro_enhanced_data.json") -> Dict:
|
| 567 |
-
"""Save enhanced data to JSON file.
|
| 568 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
|
| 570 |
with open(filename, 'w') as f:
|
| 571 |
json.dump(data, f, indent=2)
|
|
|
|
| 256 |
estimated_hours = random.randint(2, 24)
|
| 257 |
|
| 258 |
job = {
|
| 259 |
+
"job_id": f"JC-{random.randint(10000, 99999)}",
|
| 260 |
"trainset_id": ts_id,
|
| 261 |
"work_order_number": f"WO-{random.randint(100000, 999999)}",
|
| 262 |
"job_type": random.choice(job_types),
|
|
|
|
| 391 |
|
| 392 |
return contracts
|
| 393 |
|
| 394 |
+
def generate_complete_enhanced_dataset(self, include_job_cards: bool = False) -> Dict:
|
| 395 |
+
"""Generate complete enhanced dataset with all improvements.
|
| 396 |
+
|
| 397 |
+
Args:
|
| 398 |
+
include_job_cards: Whether to include job cards in the dataset. Default False.
|
| 399 |
+
"""
|
| 400 |
print("Generating enhanced synthetic data...")
|
| 401 |
|
| 402 |
dataset = {
|
|
|
|
| 417 |
"trainset_profiles": self.trainset_profiles,
|
| 418 |
"trainset_status": self.generate_enhanced_trainset_status(),
|
| 419 |
"fitness_certificates": self.generate_realistic_fitness_certificates(),
|
| 420 |
+
"job_cards": self.generate_correlated_job_cards() if include_job_cards else [],
|
| 421 |
"component_health": self.generate_realistic_component_health(),
|
| 422 |
"branding_contracts": self.generate_optimized_branding_contracts(),
|
| 423 |
# Keep the existing generators for other data
|
|
|
|
| 448 |
"bogie_2": round(random.uniform(0.5, 3.5) / reliability_factor, 2),
|
| 449 |
"unit": "mm/s"
|
| 450 |
},
|
| 451 |
+
"temperature": {
|
| 452 |
+
"motor_1": round(random.uniform(45, 85) + (1 - reliability_factor) * 10, 1),
|
| 453 |
+
"motor_2": round(random.uniform(45, 85) + (1 - reliability_factor) * 10, 1),
|
| 454 |
+
"unit": "°C"
|
| 455 |
+
},
|
| 456 |
"overall_condition": "Good" if reliability_factor > 0.85 else "Fair" if reliability_factor > 0.75 else "Poor"
|
| 457 |
}
|
| 458 |
sensor_data.append(sensors)
|
|
|
|
| 564 |
return {
|
| 565 |
"date": datetime.now().date().isoformat(),
|
| 566 |
"weather": {
|
| 567 |
+
"condition": random.choice(["Clear", "Cloudy", "Rainy"]),
|
| 568 |
+
"temperature": round(random.uniform(20, 35), 1)
|
| 569 |
},
|
| 570 |
"ridership_forecast": {
|
| 571 |
"expected_passengers": random.randint(80000, 150000),
|
|
|
|
| 573 |
}
|
| 574 |
}
|
| 575 |
|
| 576 |
+
def save_to_json(self, filename: str = "metro_enhanced_data.json", include_job_cards: bool = False) -> Dict:
|
| 577 |
+
"""Save enhanced data to JSON file.
|
| 578 |
+
|
| 579 |
+
Args:
|
| 580 |
+
filename: Output filename.
|
| 581 |
+
include_job_cards: Whether to include job cards in the dataset. Default False.
|
| 582 |
+
"""
|
| 583 |
+
data = self.generate_complete_enhanced_dataset(include_job_cards=include_job_cards)
|
| 584 |
|
| 585 |
with open(filename, 'w') as f:
|
| 586 |
json.dump(data, f, indent=2)
|
DataService/metro_models.py
CHANGED
|
@@ -103,7 +103,7 @@ class Trainset(BaseModel):
|
|
| 103 |
|
| 104 |
# Compliance and health
|
| 105 |
fitness_certificates: FitnessCertificates
|
| 106 |
-
job_cards: JobCards
|
| 107 |
|
| 108 |
# Branding
|
| 109 |
branding: Optional[Branding] = None
|
|
|
|
| 103 |
|
| 104 |
# Compliance and health
|
| 105 |
fitness_certificates: FitnessCertificates
|
| 106 |
+
job_cards: Optional[JobCards] = Field(default_factory=lambda: JobCards(open=0, blocking=[]))
|
| 107 |
|
| 108 |
# Branding
|
| 109 |
branding: Optional[Branding] = None
|
DataService/schedule_optimizer.py
CHANGED
|
@@ -29,7 +29,8 @@ class MetroScheduleOptimizer:
|
|
| 29 |
num_trains: int,
|
| 30 |
route: Route,
|
| 31 |
train_health: List[TrainHealthStatus],
|
| 32 |
-
depot_name: str = "Muttom_Depot"
|
|
|
|
| 33 |
):
|
| 34 |
self.date = date
|
| 35 |
self.num_trains = num_trains
|
|
@@ -37,6 +38,7 @@ class MetroScheduleOptimizer:
|
|
| 37 |
self.train_health = {t.trainset_id: t for t in train_health}
|
| 38 |
self.depot_name = depot_name
|
| 39 |
self.generator = MetroDataGenerator(num_trains)
|
|
|
|
| 40 |
|
| 41 |
# Operating parameters
|
| 42 |
self.op_hours = OperationalHours()
|
|
@@ -58,7 +60,7 @@ class MetroScheduleOptimizer:
|
|
| 58 |
for i, train_id in enumerate(self.generator.trainset_ids):
|
| 59 |
health = self.train_health[train_id]
|
| 60 |
fitness_certs = self.generator.generate_fitness_certificates(train_id)
|
| 61 |
-
job_cards = self.generator.generate_job_cards(train_id)
|
| 62 |
branding = self.generator.generate_branding()
|
| 63 |
|
| 64 |
readiness = self.generator.calculate_readiness_score(
|
|
|
|
| 29 |
num_trains: int,
|
| 30 |
route: Route,
|
| 31 |
train_health: List[TrainHealthStatus],
|
| 32 |
+
depot_name: str = "Muttom_Depot",
|
| 33 |
+
include_job_cards: bool = False
|
| 34 |
):
|
| 35 |
self.date = date
|
| 36 |
self.num_trains = num_trains
|
|
|
|
| 38 |
self.train_health = {t.trainset_id: t for t in train_health}
|
| 39 |
self.depot_name = depot_name
|
| 40 |
self.generator = MetroDataGenerator(num_trains)
|
| 41 |
+
self.include_job_cards = include_job_cards
|
| 42 |
|
| 43 |
# Operating parameters
|
| 44 |
self.op_hours = OperationalHours()
|
|
|
|
| 60 |
for i, train_id in enumerate(self.generator.trainset_ids):
|
| 61 |
health = self.train_health[train_id]
|
| 62 |
fitness_certs = self.generator.generate_fitness_certificates(train_id)
|
| 63 |
+
job_cards = self.generator.generate_job_cards(train_id) if self.include_job_cards else JobCards(open=0, blocking=[])
|
| 64 |
branding = self.generator.generate_branding()
|
| 65 |
|
| 66 |
readiness = self.generator.calculate_readiness_score(
|
DataService/synthetic_base.py
CHANGED
|
@@ -281,8 +281,12 @@ class MetroSyntheticDataGenerator:
|
|
| 281 |
}
|
| 282 |
}
|
| 283 |
|
| 284 |
-
def generate_complete_dataset(self) -> Dict:
|
| 285 |
-
"""Generate complete synthetic dataset for metro scheduling
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
dataset = {
|
| 287 |
"metadata": {
|
| 288 |
"generated_at": datetime.now().isoformat(),
|
|
@@ -292,7 +296,7 @@ class MetroSyntheticDataGenerator:
|
|
| 292 |
},
|
| 293 |
"trainset_status": self.generate_trainset_status(),
|
| 294 |
"fitness_certificates": self.generate_fitness_certificates(),
|
| 295 |
-
"job_cards": self.generate_job_cards(),
|
| 296 |
"component_health": self.generate_component_health(),
|
| 297 |
"iot_sensors": self.generate_iot_sensors(),
|
| 298 |
"branding_contracts": self.generate_branding_contracts(),
|
|
@@ -304,9 +308,14 @@ class MetroSyntheticDataGenerator:
|
|
| 304 |
}
|
| 305 |
return dataset
|
| 306 |
|
| 307 |
-
def save_to_json(self, filename: str = "metro_synthetic_data.json"):
|
| 308 |
-
"""Save generated data to JSON file
|
| 309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
with open(filename, 'w') as f:
|
| 311 |
json.dump(data, f, indent=2)
|
| 312 |
print(f"Synthetic data generated and saved to {filename}")
|
|
|
|
| 281 |
}
|
| 282 |
}
|
| 283 |
|
| 284 |
+
def generate_complete_dataset(self, include_job_cards: bool = False) -> Dict:
|
| 285 |
+
"""Generate complete synthetic dataset for metro scheduling.
|
| 286 |
+
|
| 287 |
+
Args:
|
| 288 |
+
include_job_cards: Whether to include job cards in the dataset. Default False.
|
| 289 |
+
"""
|
| 290 |
dataset = {
|
| 291 |
"metadata": {
|
| 292 |
"generated_at": datetime.now().isoformat(),
|
|
|
|
| 296 |
},
|
| 297 |
"trainset_status": self.generate_trainset_status(),
|
| 298 |
"fitness_certificates": self.generate_fitness_certificates(),
|
| 299 |
+
"job_cards": self.generate_job_cards() if include_job_cards else [],
|
| 300 |
"component_health": self.generate_component_health(),
|
| 301 |
"iot_sensors": self.generate_iot_sensors(),
|
| 302 |
"branding_contracts": self.generate_branding_contracts(),
|
|
|
|
| 308 |
}
|
| 309 |
return dataset
|
| 310 |
|
| 311 |
+
def save_to_json(self, filename: str = "metro_synthetic_data.json", include_job_cards: bool = False):
|
| 312 |
+
"""Save generated data to JSON file.
|
| 313 |
+
|
| 314 |
+
Args:
|
| 315 |
+
filename: Output filename.
|
| 316 |
+
include_job_cards: Whether to include job cards in the dataset. Default False.
|
| 317 |
+
"""
|
| 318 |
+
data = self.generate_complete_dataset(include_job_cards=include_job_cards)
|
| 319 |
with open(filename, 'w') as f:
|
| 320 |
json.dump(data, f, indent=2)
|
| 321 |
print(f"Synthetic data generated and saved to {filename}")
|
api/greedyoptim_api.py
CHANGED
|
@@ -104,7 +104,7 @@ class ScheduleOptimizationRequest(BaseModel):
|
|
| 104 |
"""Request for schedule optimization"""
|
| 105 |
trainset_status: List[TrainsetStatusInput]
|
| 106 |
fitness_certificates: List[FitnessCertificateInput]
|
| 107 |
-
job_cards: List[JobCardInput]
|
| 108 |
component_health: List[ComponentHealthInput]
|
| 109 |
|
| 110 |
# Optional metadata
|
|
@@ -125,7 +125,7 @@ class CompareMethodsRequest(BaseModel):
|
|
| 125 |
"""Request to compare multiple optimization methods"""
|
| 126 |
trainset_status: List[TrainsetStatusInput]
|
| 127 |
fitness_certificates: List[FitnessCertificateInput]
|
| 128 |
-
job_cards: List[JobCardInput]
|
| 129 |
component_health: List[ComponentHealthInput]
|
| 130 |
|
| 131 |
metadata: Optional[Dict[str, Any]] = None
|
|
@@ -181,7 +181,7 @@ def convert_pydantic_to_dict(request: ScheduleOptimizationRequest) -> Dict[str,
|
|
| 181 |
data = {
|
| 182 |
"trainset_status": [ts.dict() for ts in request.trainset_status],
|
| 183 |
"fitness_certificates": [fc.dict() for fc in request.fitness_certificates],
|
| 184 |
-
"job_cards": [jc.dict() for jc in request.job_cards],
|
| 185 |
"component_health": [ch.dict() for ch in request.component_health],
|
| 186 |
"metadata": request.metadata or {
|
| 187 |
"generated_at": datetime.now().isoformat(),
|
|
|
|
| 104 |
"""Request for schedule optimization"""
|
| 105 |
trainset_status: List[TrainsetStatusInput]
|
| 106 |
fitness_certificates: List[FitnessCertificateInput]
|
| 107 |
+
job_cards: Optional[List[JobCardInput]] = Field(default_factory=list, description="Job cards are optional, defaults to empty list")
|
| 108 |
component_health: List[ComponentHealthInput]
|
| 109 |
|
| 110 |
# Optional metadata
|
|
|
|
| 125 |
"""Request to compare multiple optimization methods"""
|
| 126 |
trainset_status: List[TrainsetStatusInput]
|
| 127 |
fitness_certificates: List[FitnessCertificateInput]
|
| 128 |
+
job_cards: Optional[List[JobCardInput]] = Field(default_factory=list, description="Job cards are optional, defaults to empty list")
|
| 129 |
component_health: List[ComponentHealthInput]
|
| 130 |
|
| 131 |
metadata: Optional[Dict[str, Any]] = None
|
|
|
|
| 181 |
data = {
|
| 182 |
"trainset_status": [ts.dict() for ts in request.trainset_status],
|
| 183 |
"fitness_certificates": [fc.dict() for fc in request.fitness_certificates],
|
| 184 |
+
"job_cards": [jc.dict() for jc in request.job_cards] if request.job_cards else [],
|
| 185 |
"component_health": [ch.dict() for ch in request.component_health],
|
| 186 |
"metadata": request.metadata or {
|
| 187 |
"generated_at": datetime.now().isoformat(),
|
greedyOptim/error_handling.py
CHANGED
|
@@ -54,8 +54,10 @@ class DataValidator:
|
|
| 54 |
errors = []
|
| 55 |
|
| 56 |
try:
|
| 57 |
-
# Check required top-level keys
|
| 58 |
-
required_keys = ['trainset_status', 'fitness_certificates', '
|
|
|
|
|
|
|
| 59 |
for key in required_keys:
|
| 60 |
if key not in data:
|
| 61 |
errors.append(f"Missing required data section: {key}")
|
|
@@ -69,6 +71,15 @@ class DataValidator:
|
|
| 69 |
section_errors = cls._validate_section(data[key], key)
|
| 70 |
errors.extend(section_errors)
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
# Cross-validation
|
| 73 |
if not errors: # Only if basic structure is valid
|
| 74 |
cross_errors = cls._cross_validate(data)
|
|
|
|
| 54 |
errors = []
|
| 55 |
|
| 56 |
try:
|
| 57 |
+
# Check required top-level keys (job_cards is now optional)
|
| 58 |
+
required_keys = ['trainset_status', 'fitness_certificates', 'component_health']
|
| 59 |
+
optional_keys = ['job_cards']
|
| 60 |
+
|
| 61 |
for key in required_keys:
|
| 62 |
if key not in data:
|
| 63 |
errors.append(f"Missing required data section: {key}")
|
|
|
|
| 71 |
section_errors = cls._validate_section(data[key], key)
|
| 72 |
errors.extend(section_errors)
|
| 73 |
|
| 74 |
+
# Validate optional keys if present
|
| 75 |
+
for key in optional_keys:
|
| 76 |
+
if key in data and data[key]:
|
| 77 |
+
if not isinstance(data[key], list):
|
| 78 |
+
errors.append(f"Data section {key} must be a list")
|
| 79 |
+
continue
|
| 80 |
+
section_errors = cls._validate_section(data[key], key)
|
| 81 |
+
errors.extend(section_errors)
|
| 82 |
+
|
| 83 |
# Cross-validation
|
| 84 |
if not errors: # Only if basic structure is valid
|
| 85 |
cross_errors = cls._cross_validate(data)
|
greedyOptim/evaluator.py
CHANGED
|
@@ -33,9 +33,9 @@ class TrainsetSchedulingEvaluator:
|
|
| 33 |
self.fitness_map[ts_id] = {}
|
| 34 |
self.fitness_map[ts_id][cert['department']] = cert
|
| 35 |
|
| 36 |
-
# Job cards by trainset
|
| 37 |
self.job_map = {}
|
| 38 |
-
for job in self.data
|
| 39 |
ts_id = job['trainset_id']
|
| 40 |
if ts_id not in self.job_map:
|
| 41 |
self.job_map[ts_id] = []
|
|
|
|
| 33 |
self.fitness_map[ts_id] = {}
|
| 34 |
self.fitness_map[ts_id][cert['department']] = cert
|
| 35 |
|
| 36 |
+
# Job cards by trainset (optional - may be empty)
|
| 37 |
self.job_map = {}
|
| 38 |
+
for job in self.data.get('job_cards', []):
|
| 39 |
ts_id = job['trainset_id']
|
| 40 |
if ts_id not in self.job_map:
|
| 41 |
self.job_map[ts_id] = []
|