| |
| |
|
|
| import streamlit as st |
| import numpy as np |
| import pandas as pd |
| import altair as alt |
|
|
| |
| |
| |
| st.set_page_config(page_title="Collaborative Decision Hub (Python Demo)", layout="wide") |
|
|
| |
| |
| |
| SCENARIO = { |
| "airport": "ZNY", |
| "window_startZ": "14:00Z", |
| "window_endZ": "18:00Z", |
| "hours": 4.0, |
| "arrivals_impacted": 126, |
| "departures_impacted": 40, |
| "runway_config_note": "Runway 22R closed", |
| "airspace_note": "Convective weather causing sector restrictions", |
| "sector_capacity_per_hour": [32, 32, 36, 36], |
| "airport_arr_capacity_per_hour": [38, 38, 38, 38], |
| } |
|
|
| |
| EMISSION_FACTOR_CO2_PER_KG_FUEL = 3.16 |
| FUEL_COST_PER_KG_USD = 0.8 |
| DELAY_COST_PER_MIN_USD = 75 |
| MISCONNECT_DELAY_THRESHOLD_MIN = 20 |
| MISCONNECT_PER_FLIGHT_PER_MIN_FACTOR = 0.002 |
|
|
| |
| |
| |
| def clamp(x, low=0.0, high=1.0): |
| return max(low, min(high, x)) |
|
|
| def normalize(val, ref_max): |
| if ref_max <= 0: |
| return 0.0 |
| return clamp(val / ref_max, 0.0, 1.0) |
|
|
| |
| |
| |
| def compute_metrics( |
| flights_total, |
| arrivals, |
| hours, |
| sector_capacity_per_hour, |
| airport_arr_capacity_per_hour, |
| reroute_pct, |
| fuel_saved_per_rerouted_flight_kg, |
| reroute_delay_delta_min_per_flight, |
| gdp_avg_delay_min, |
| meter_reduction_pct, |
| slot_swap_pct, |
| ): |
| flights_rerouted = int(round(flights_total * reroute_pct / 100.0)) |
| flights_not_rerouted = flights_total - flights_rerouted |
|
|
| avg_delay_min = gdp_avg_delay_min + (reroute_delay_delta_min_per_flight if flights_rerouted > 0 else 0) |
| avg_delay_min = max(0.0, avg_delay_min) |
| total_delay_min = flights_total * avg_delay_min |
|
|
| fuel_saved_kg = flights_rerouted * fuel_saved_per_rerouted_flight_kg |
| co2_saved_t = (fuel_saved_kg * EMISSION_FACTOR_CO2_PER_KG_FUEL) / 1000.0 |
|
|
| cost_usd = total_delay_min * DELAY_COST_PER_MIN_USD - fuel_saved_kg * FUEL_COST_PER_KG_USD |
|
|
| avg_sector_capacity = np.mean(sector_capacity_per_hour) |
| base_arrival_rate = arrivals / hours |
| effective_arrival_rate = base_arrival_rate * (1.0 - meter_reduction_pct / 100.0) |
| sector_relief = 0.2 * (reroute_pct / 100.0) * base_arrival_rate |
| effective_sector_demand = max(0.0, effective_arrival_rate - sector_relief) |
| overload = max(0.0, effective_sector_demand - avg_sector_capacity) |
| safety_score = clamp(100.0 - 120.0 * (overload / max(1.0, avg_sector_capacity)), 0.0, 100.0) |
|
|
| avg_airport_capacity = np.mean(airport_arr_capacity_per_hour) |
| slot_utilization_pct = clamp((effective_arrival_rate / avg_airport_capacity) * 100.0, 0.0, 150.0) |
| delay_over_threshold = max(0.0, avg_delay_min - MISCONNECT_DELAY_THRESHOLD_MIN) |
| misconnects_est = flights_total * delay_over_threshold * MISCONNECT_PER_FLIGHT_PER_MIN_FACTOR * (1.0 - slot_swap_pct / 100.0) |
|
|
| otp_rate = clamp(1.0 / (1.0 + np.exp((avg_delay_min - 15.0) / 3.0)), 0.0, 1.0) |
|
|
| return { |
| "flights_rerouted": flights_rerouted, |
| "avg_delay_min": avg_delay_min, |
| "total_delay_min": total_delay_min, |
| "fuel_saved_kg": fuel_saved_kg, |
| "co2_saved_t": co2_saved_t, |
| "cost_usd": cost_usd, |
| "safety_score": safety_score, |
| "slot_utilization_pct": slot_utilization_pct, |
| "misconnects_est": misconnects_est, |
| "otp_rate": otp_rate, |
| "effective_sector_demand_per_hr": effective_sector_demand, |
| "avg_sector_capacity": avg_sector_capacity, |
| "avg_airport_capacity": avg_airport_capacity, |
| } |
|
|
| |
| |
| |
| def score_solution(metrics, weights): |
| norm_ontime = clamp(1.0 - normalize(metrics["avg_delay_min"], 30.0)) |
| norm_sust = normalize(metrics["fuel_saved_kg"], 6000.0) |
| norm_safety = clamp(metrics["safety_score"] / 100.0) |
| norm_cost = clamp(normalize(-metrics["cost_usd"], 120000.0)) |
|
|
| return ( |
| weights["On-time"] * norm_ontime + |
| weights["Sustainability"] * norm_sust + |
| weights["Safety"] * norm_safety + |
| weights["Cost"] * norm_cost |
| ), { |
| "On-time": norm_ontime, |
| "Sustainability": norm_sust, |
| "Safety": norm_safety, |
| "Cost": norm_cost |
| } |
|
|
| |
| |
| |
| def recommend_best(flights_total, arrivals, hours, sector_cap, airport_cap, weights): |
| candidates = [] |
| for reroute_pct in range(0, 35, 5): |
| for gdp_delay in range(0, 25, 5): |
| for meter_pct in range(0, 35, 10): |
| for slot_swap in range(0, 35, 10): |
| m = compute_metrics( |
| flights_total=flights_total, |
| arrivals=arrivals, |
| hours=hours, |
| sector_capacity_per_hour=sector_cap, |
| airport_arr_capacity_per_hour=airport_cap, |
| reroute_pct=reroute_pct, |
| fuel_saved_per_rerouted_flight_kg=150.0, |
| reroute_delay_delta_min_per_flight=-2.0, |
| gdp_avg_delay_min=gdp_delay, |
| meter_reduction_pct=meter_pct, |
| slot_swap_pct=slot_swap, |
| ) |
| s, norms = score_solution(m, weights) |
| candidates.append({ |
| "reroute_pct": reroute_pct, |
| "gdp_avg_delay_min": gdp_delay, |
| "meter_reduction_pct": meter_pct, |
| "slot_swap_pct": slot_swap, |
| "score": s, |
| "cost_usd": m["cost_usd"], |
| "co2_saved_t": m["co2_saved_t"], |
| "metrics": m, |
| "norms": norms |
| }) |
| best = max(candidates, key=lambda x: x["score"]) if candidates else None |
| return best, candidates |
|
|
| |
| |
| |
| st.sidebar.title("Objective Weights") |
| w_ontime = st.sidebar.slider("On-time performance", 0.0, 1.0, 0.30, 0.05) |
| w_cost = st.sidebar.slider("Cost (savings)", 0.0, 1.0, 0.25, 0.05) |
| w_sust = st.sidebar.slider("Sustainability (CO₂/fuel)", 0.0, 1.0, 0.25, 0.05) |
| w_safety = st.sidebar.slider("ANSP Safety", 0.0, 1.0, 0.20, 0.05) |
|
|
| w_sum = w_ontime + w_cost + w_sust + w_safety |
| weights = { |
| "On-time": w_ontime / w_sum if w_sum else 0.25, |
| "Cost": w_cost / w_sum if w_sum else 0.25, |
| "Sustainability": w_sust / w_sum if w_sum else 0.25, |
| "Safety": w_safety / w_sum if w_sum else 0.25, |
| } |
| st.sidebar.caption(f"Weights normalized: On-time {weights['On-time']:.2f}, Cost {weights['Cost']:.2f}, Sustain {weights['Sustainability']:.2f}, Safety {weights['Safety']:.2f}") |
|
|
| |
| |
| |
| st.title("Collaborative Decision Hub (Python Demo)") |
| st.write(f"{SCENARIO['airport']} | Window {SCENARIO['window_startZ']}–{SCENARIO['window_endZ']} | {SCENARIO['runway_config_note']} | {SCENARIO['airspace_note']}") |
|
|
| |
| |
| |
| left, center, right = st.columns([1.1, 1.2, 1.0]) |
|
|
| |
| with left: |
| st.subheader("Scenario") |
| st.markdown( |
| f"- Arrivals impacted: {SCENARIO['arrivals_impacted']}\n" |
| f"- Departures impacted: {SCENARIO['departures_impacted']}\n" |
| f"- Sector capacity (hr): {SCENARIO['sector_capacity_per_hour']}\n" |
| f"- Airport arrivals capacity (hr): {SCENARIO['airport_arr_capacity_per_hour']}" |
| ) |
|
|
| st.subheader("What-if Controls") |
| reroute_pct = st.slider("Targeted reroutes (% of flights)", 0, 40, 10, 1) |
| fuel_saved_per_rerouted_flight_kg = st.slider("Fuel saved per rerouted flight (kg)", 0, 400, 150, 10) |
| reroute_delay_delta = st.slider("Reroute delay change per rerouted flight (min; negative is good)", -5.0, 5.0, -2.0, 0.5) |
| gdp_avg_delay_min = st.slider("GDP average delay (min)", 0, 30, 15, 1) |
| meter_reduction_pct = st.slider("Departure metering reduction (%)", 0, 40, 10, 1) |
| slot_swap_pct = st.slider("Slot swaps applied (%)", 0, 40, 10, 1) |
|
|
| |
| with center: |
| st.subheader("Agent Collaboration") |
|
|
| flights_total = SCENARIO["arrivals_impacted"] + SCENARIO["departures_impacted"] |
| m = compute_metrics( |
| flights_total=flights_total, |
| arrivals=SCENARIO["arrivals_impacted"], |
| hours=SCENARIO["hours"], |
| sector_capacity_per_hour=SCENARIO["sector_capacity_per_hour"], |
| airport_arr_capacity_per_hour=SCENARIO["airport_arr_capacity_per_hour"], |
| reroute_pct=reroute_pct, |
| fuel_saved_per_rerouted_flight_kg=fuel_saved_per_rerouted_flight_kg, |
| reroute_delay_delta_min_per_flight=reroute_delay_delta, |
| gdp_avg_delay_min=gdp_avg_delay_min, |
| meter_reduction_pct=meter_reduction_pct, |
| slot_swap_pct=slot_swap_pct, |
| ) |
| score, norms = score_solution(m, weights) |
| consensus_pct = int(round(score * 100)) |
|
|
| with st.container(border=True): |
| st.markdown("**Airline AI**") |
| st.write( |
| f"With {reroute_pct}% reroutes (~{m['flights_rerouted']} flights), " |
| f"forecast avg delay {m['avg_delay_min']:.1f} min, fuel saved {m['fuel_saved_kg']:.0f} kg " |
| f"({m['co2_saved_t']:.2f} t CO₂), cost |
| |