Arpit-Bansal commited on
Commit
51de58f
·
1 Parent(s): ac0363b

update synthetic generation

Browse files
DataService/enhanced_generator.py CHANGED
@@ -288,19 +288,41 @@ class EnhancedMetroDataGenerator:
288
  ])
289
 
290
  def generate_realistic_component_health(self) -> List[Dict]:
291
- """Generate component health data correlated with mileage and age."""
 
 
 
 
292
  health_data = []
293
 
 
 
 
 
294
  for ts_id in self.trainset_ids:
295
  profile = self.trainset_profiles[ts_id]
 
296
 
297
  for comp_name, comp_info in self.components.items():
298
  # Calculate wear based on mileage and service life
299
  wear_ratio = profile["total_mileage_km"] / comp_info["service_life_km"]
300
  base_wear = min(95, wear_ratio * 100)
301
 
302
- # Add random variation
303
- wear_level = max(0, min(100, base_wear + random.randint(-15, 10)))
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
  # Health score inversely related to wear
306
  health_score = max(60, 100 - wear_level + random.randint(-5, 5))
 
288
  ])
289
 
290
  def generate_realistic_component_health(self) -> List[Dict]:
291
+ """Generate component health data correlated with mileage and age.
292
+
293
+ Generates mostly healthy components to reflect a well-maintained metro fleet.
294
+ About 85% of trainsets will have all components in good condition.
295
+ """
296
  health_data = []
297
 
298
+ # Ensure 85% of trainsets have healthy components (realistic for well-maintained fleet)
299
+ healthy_trainset_count = int(self.num_trainsets * 0.85)
300
+ healthy_trainsets = set(random.sample(self.trainset_ids, healthy_trainset_count))
301
+
302
  for ts_id in self.trainset_ids:
303
  profile = self.trainset_profiles[ts_id]
304
+ is_healthy_trainset = ts_id in healthy_trainsets
305
 
306
  for comp_name, comp_info in self.components.items():
307
  # Calculate wear based on mileage and service life
308
  wear_ratio = profile["total_mileage_km"] / comp_info["service_life_km"]
309
  base_wear = min(95, wear_ratio * 100)
310
 
311
+ # For healthy trainsets, keep components well-maintained
312
+ if is_healthy_trainset:
313
+ # Keep wear level safely below threshold (at most 60% of threshold)
314
+ # This represents a well-maintained fleet with regular servicing
315
+ max_healthy_wear = comp_info["wear_threshold"] * 0.60
316
+ wear_level = min(max_healthy_wear, base_wear * 0.4 + random.randint(-3, 3))
317
+ wear_level = max(5, wear_level) # Minimum 5% wear (nothing is brand new)
318
+ else:
319
+ # Even unhealthy trainsets - only some components may exceed threshold
320
+ # 50% chance each component exceeds threshold
321
+ if random.random() < 0.5:
322
+ wear_level = max(0, min(100, base_wear + random.randint(-10, 15)))
323
+ else:
324
+ # Keep this component healthy
325
+ wear_level = min(comp_info["wear_threshold"] * 0.7, base_wear * 0.5)
326
 
327
  # Health score inversely related to wear
328
  health_score = max(60, 100 - wear_level + random.randint(-5, 5))
api/test_greedyoptim_api.py CHANGED
@@ -7,7 +7,7 @@ import requests
7
  import json
8
  from datetime import datetime, timedelta
9
 
10
- BASE_URL = "http://localhost:8001"
11
 
12
 
13
  def test_health():
@@ -47,7 +47,7 @@ def test_generate_synthetic():
47
  print("="*70)
48
 
49
  payload = {
50
- "num_trainsets": 20,
51
  "maintenance_rate": 0.1,
52
  "availability_rate": 0.8
53
  }
@@ -161,16 +161,18 @@ def test_compare(data):
161
  print("Testing Method Comparison")
162
  print("="*70)
163
 
164
- # Create comparison request
165
  request_data = {
166
- "trainset_status": data['trainset_status'][:15], # Use smaller dataset for faster comparison
167
- "fitness_certificates": [fc for fc in data['fitness_certificates'] if fc['trainset_id'] in [ts['trainset_id'] for ts in data['trainset_status'][:15]]],
168
- "job_cards": [jc for jc in data['job_cards'] if jc['trainset_id'] in [ts['trainset_id'] for ts in data['trainset_status'][:15]]],
169
- "component_health": [ch for ch in data['component_health'] if ch['trainset_id'] in [ts['trainset_id'] for ts in data['trainset_status'][:15]]],
170
- "methods": ["ga", "pso"],
171
  "config": {
172
- "population_size": 20,
173
- "generations": 30
 
 
174
  }
175
  }
176
 
@@ -205,11 +207,11 @@ def test_custom_data():
205
  print("Testing with Custom Minimal Data")
206
  print("="*70)
207
 
208
- # Create minimal valid data with at least 15 available trainsets
209
  custom_data = {
210
  "trainset_status": [
211
  {"trainset_id": f"KMRL-{i:02d}", "operational_status": "Available", "total_mileage_km": 50000.0}
212
- for i in range(1, 21)
213
  ],
214
  "fitness_certificates": [
215
  {
@@ -218,7 +220,7 @@ def test_custom_data():
218
  "status": "Valid",
219
  "expiry_date": (datetime.now() + timedelta(days=365)).isoformat()
220
  }
221
- for i in range(1, 21)
222
  ],
223
  "job_cards": [], # No job cards
224
  "component_health": [
@@ -228,14 +230,14 @@ def test_custom_data():
228
  "status": "Good",
229
  "wear_level": 20.0
230
  }
231
- for i in range(1, 21)
232
  ],
233
  "method": "ga",
234
  "config": {
235
  "required_service_trains": 15,
236
  "min_standby": 2,
237
- "population_size": 20,
238
- "generations": 30
239
  }
240
  }
241
 
@@ -256,6 +258,130 @@ def test_custom_data():
256
  return response.status_code == 200
257
 
258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  def main():
260
  """Run all tests"""
261
  print("=" * 70)
@@ -276,6 +402,8 @@ def main():
276
  if synthetic_data:
277
  results['validate'] = test_validate(synthetic_data)
278
  results['optimize'] = test_optimize(synthetic_data)
 
 
279
  results['compare'] = test_compare(synthetic_data)
280
 
281
  results['custom'] = test_custom_data()
@@ -304,6 +432,6 @@ if __name__ == "__main__":
304
  except requests.exceptions.ConnectionError:
305
  print("\n✗ ERROR: Could not connect to API")
306
  print(" Make sure the API is running:")
307
- print(" python api/run_greedyoptim_api.py")
308
  except Exception as e:
309
  print(f"\n✗ ERROR: {str(e)}")
 
7
  import json
8
  from datetime import datetime, timedelta
9
 
10
+ BASE_URL = "http://localhost:7860"
11
 
12
 
13
  def test_health():
 
47
  print("="*70)
48
 
49
  payload = {
50
+ "num_trainsets": 25,
51
  "maintenance_rate": 0.1,
52
  "availability_rate": 0.8
53
  }
 
161
  print("Testing Method Comparison")
162
  print("="*70)
163
 
164
+ # Create comparison request with all trainsets and all methods
165
  request_data = {
166
+ "trainset_status": data['trainset_status'],
167
+ "fitness_certificates": data['fitness_certificates'],
168
+ "job_cards": data.get('job_cards', []),
169
+ "component_health": data['component_health'],
170
+ "methods": ["ga", "pso", "sa", "cmaes", "nsga2"],
171
  "config": {
172
+ "required_service_trains": 15,
173
+ "min_standby": 2,
174
+ "population_size": 30,
175
+ "generations": 50
176
  }
177
  }
178
 
 
207
  print("Testing with Custom Minimal Data")
208
  print("="*70)
209
 
210
+ # Create minimal valid data with 25 trainsets
211
  custom_data = {
212
  "trainset_status": [
213
  {"trainset_id": f"KMRL-{i:02d}", "operational_status": "Available", "total_mileage_km": 50000.0}
214
+ for i in range(1, 26)
215
  ],
216
  "fitness_certificates": [
217
  {
 
220
  "status": "Valid",
221
  "expiry_date": (datetime.now() + timedelta(days=365)).isoformat()
222
  }
223
+ for i in range(1, 26)
224
  ],
225
  "job_cards": [], # No job cards
226
  "component_health": [
 
230
  "status": "Good",
231
  "wear_level": 20.0
232
  }
233
+ for i in range(1, 26)
234
  ],
235
  "method": "ga",
236
  "config": {
237
  "required_service_trains": 15,
238
  "min_standby": 2,
239
+ "population_size": 30,
240
+ "generations": 50
241
  }
242
  }
243
 
 
258
  return response.status_code == 200
259
 
260
 
261
+ def test_schedule(data):
262
+ """Test full schedule generation endpoint"""
263
+ print("\n" + "="*70)
264
+ print("Testing Full Schedule Generation (/schedule)")
265
+ print("="*70)
266
+
267
+ # Create schedule request
268
+ request_data = {
269
+ "trainset_status": data['trainset_status'],
270
+ "fitness_certificates": data['fitness_certificates'],
271
+ "job_cards": data.get('job_cards', []),
272
+ "component_health": data['component_health'],
273
+ "method": "ga",
274
+ "config": {
275
+ "required_service_trains": 6,
276
+ "min_standby": 2,
277
+ "population_size": 30,
278
+ "generations": 50
279
+ }
280
+ }
281
+
282
+ print(f"Generating schedule with method: {request_data['method']}")
283
+ print(f"Trainsets: {len(request_data['trainset_status'])}")
284
+
285
+ response = requests.post(f"{BASE_URL}/schedule", json=request_data)
286
+ print(f"Status: {response.status_code}")
287
+
288
+ if response.status_code == 200:
289
+ result = response.json()
290
+ print(f"\nSchedule Generated:")
291
+ print(f" Schedule ID: {result['schedule_id']}")
292
+ print(f" Valid From: {result['valid_from']}")
293
+ print(f" Valid Until: {result['valid_until']}")
294
+ print(f" Depot: {result['depot']}")
295
+
296
+ print(f"\n Fleet Summary:")
297
+ fleet = result['fleet_summary']
298
+ print(f" Total Trainsets: {fleet['total_trainsets']}")
299
+ print(f" Revenue Service: {fleet['revenue_service']}")
300
+ print(f" Standby: {fleet['standby']}")
301
+ print(f" Maintenance: {fleet['maintenance']}")
302
+ print(f" Availability: {fleet['availability_percent']}%")
303
+
304
+ print(f"\n Optimization Metrics:")
305
+ metrics = result['optimization_metrics']
306
+ print(f" Fitness Score: {metrics['fitness_score']:.4f}")
307
+ print(f" Method: {metrics['method']}")
308
+ print(f" Total Planned KM: {metrics['total_planned_km']}")
309
+ print(f" Runtime: {metrics['optimization_runtime_ms']}ms")
310
+
311
+ # Show service trainsets with blocks
312
+ print(f"\n Service Trainsets with Blocks:")
313
+ service_count = 0
314
+ for ts in result['trainsets']:
315
+ if ts['status'] == 'REVENUE_SERVICE':
316
+ service_count += 1
317
+ blocks = ts.get('service_blocks', [])
318
+ print(f" {ts['trainset_id']}: {len(blocks)} blocks, {ts['daily_km_allocation']} km")
319
+ if blocks and service_count <= 2: # Show blocks for first 2 service trains
320
+ for block in blocks[:3]:
321
+ print(f" - {block['block_id']}: {block['departure_time']} {block['origin']} → {block['destination']}")
322
+ if len(blocks) > 3:
323
+ print(f" ... and {len(blocks) - 3} more blocks")
324
+
325
+ # Show alerts
326
+ if result.get('alerts'):
327
+ print(f"\n Alerts: {len(result['alerts'])}")
328
+ for alert in result['alerts'][:3]:
329
+ print(f" [{alert['severity']}] {alert['trainset_id']}: {alert['message']}")
330
+ else:
331
+ print(f"Error: {response.text}")
332
+
333
+ return response.status_code == 200
334
+
335
+
336
+ def test_schedule_methods(data):
337
+ """Test schedule generation with different optimization methods"""
338
+ print("\n" + "="*70)
339
+ print("Testing Schedule with Different Methods")
340
+ print("="*70)
341
+
342
+ methods = ['ga', 'pso', 'sa', 'nsga2']
343
+ results = {}
344
+
345
+ for method in methods:
346
+ request_data = {
347
+ "trainset_status": data['trainset_status'][:15],
348
+ "fitness_certificates": [fc for fc in data['fitness_certificates']
349
+ if fc['trainset_id'] in [ts['trainset_id'] for ts in data['trainset_status'][:15]]],
350
+ "job_cards": [],
351
+ "component_health": [ch for ch in data['component_health']
352
+ if ch['trainset_id'] in [ts['trainset_id'] for ts in data['trainset_status'][:15]]],
353
+ "method": method,
354
+ "config": {
355
+ "required_service_trains": 6,
356
+ "min_standby": 2,
357
+ "population_size": 20,
358
+ "generations": 30
359
+ }
360
+ }
361
+
362
+ response = requests.post(f"{BASE_URL}/schedule", json=request_data)
363
+
364
+ if response.status_code == 200:
365
+ result = response.json()
366
+ total_blocks = sum(
367
+ len(ts.get('service_blocks', []))
368
+ for ts in result['trainsets']
369
+ if ts['status'] == 'REVENUE_SERVICE'
370
+ )
371
+ results[method] = {
372
+ 'success': True,
373
+ 'blocks': total_blocks,
374
+ 'fitness': result['optimization_metrics']['fitness_score'],
375
+ 'service': result['fleet_summary']['revenue_service']
376
+ }
377
+ print(f" {method.upper()}: ✓ {total_blocks} blocks, {results[method]['service']} service trains")
378
+ else:
379
+ results[method] = {'success': False}
380
+ print(f" {method.upper()}: ✗ Failed")
381
+
382
+ return all(r['success'] for r in results.values())
383
+
384
+
385
  def main():
386
  """Run all tests"""
387
  print("=" * 70)
 
402
  if synthetic_data:
403
  results['validate'] = test_validate(synthetic_data)
404
  results['optimize'] = test_optimize(synthetic_data)
405
+ results['schedule'] = test_schedule(synthetic_data)
406
+ results['schedule_methods'] = test_schedule_methods(synthetic_data)
407
  results['compare'] = test_compare(synthetic_data)
408
 
409
  results['custom'] = test_custom_data()
 
432
  except requests.exceptions.ConnectionError:
433
  print("\n✗ ERROR: Could not connect to API")
434
  print(" Make sure the API is running:")
435
+ print(" python api/greedyoptim_api.py")
436
  except Exception as e:
437
  print(f"\n✗ ERROR: {str(e)}")