PunitRaveendran/Air_Traffic_Control_System
1
1"""Grader 2: Weighted scoring for delay, fuel-critical handling, and separation compliance."""2from typing import Dict, Any3 4 5def grade(episode_log: Dict[str, Any]) -> float:6 """7 Weighted score: 40% delay, 40% fuel-critical handling, 20% separation compliance.8 9 FIX (Bug 8): fuel_critical_count now uses 'initial_fuel_critical_ids' passed from the10 environment, which records aircraft that were FUEL_CRITICAL at spawn time.11 Previously it used final fuel_remaining_min < 15, which includes any aircraft that12 naturally depleted fuel over a long episode — massively inflating the denominator13 and making fuel_score collapse to near-zero even with perfect play.14 """15 # Updated bounds to guarantee scores are strictly between (0, 1) at 4 decimal places16 MIN_SCORE = 0.00117 MAX_SCORE = 0.99918 19 if not episode_log:20 return MIN_SCORE21 22 aircraft = episode_log.get("aircraft", [])23 total_aircraft = len(aircraft)24 if total_aircraft == 0:25 return MIN_SCORE26 27 # 40%: Landings completed28 landed = sum(1 for ac in aircraft if ac.get("status") == "LANDED")29 delay_score = landed / total_aircraft30 31 # 40%: Fuel-critical aircraft all land32 # Use initial_fuel_critical_ids if available (set at episode start before any fuel depletion).33 # Fall back to priority flag only (not fuel_remaining_min) to avoid counting aircraft34 # that aged into fuel-critical status during a long episode.35 initial_fc_ids = set(episode_log.get("initial_fuel_critical_ids", []))36 if initial_fc_ids:37 fuel_critical_count = len(initial_fc_ids)38 fuel_landed = sum(39 1 for ac in aircraft40 if ac.get("id") in initial_fc_ids and ac.get("status") == "LANDED"41 )42 else:43 # Fallback: count only aircraft whose priority is FUEL_CRITICAL (set at spawn44 # or upgraded during tick — but not using final fuel level which degrades over time)45 fuel_critical_count = sum(46 1 for ac in aircraft if ac.get("priority") == "FUEL_CRITICAL"47 )48 fuel_landed = sum(49 1 for ac in aircraft50 if ac.get("priority") == "FUEL_CRITICAL" and ac.get("status") == "LANDED"51 )52 fuel_score = fuel_landed / fuel_critical_count if fuel_critical_count > 0 else 1.053 54 # 20%: Separation compliance (duplicate sequence positions = violations)55 separation_violations = episode_log.get("separation_violations", 0)56 max_violations = 1057 separation_score = max(0.0, 1.0 - separation_violations / max_violations)58 59 final_score = 0.4 * delay_score + 0.4 * fuel_score + 0.2 * separation_score60 61 # Enforce safe boundaries on the final returned score62 return max(MIN_SCORE, min(MAX_SCORE, final_score))63 