Add OARMP routing engine, dashboard and documentation
- Complete routing engine: ingest, optimizer (CG+B&B), maintenance monitor, metrics, pipeline, quality checks - Streamlit dashboard with Input/Output tab structure, editable data editors, interactive Folium map with satellite layer and maintenance base highlights, FH stacked bar chart with TTM availability - CSV data files: AERONAVES, CHECKS, AIRPORTS, ESCALA DE VOO - README, CONTEXTO and CHANGELOG added - Remove legacy pre_process scripts and raw binary files (PDFs/xlsx) - Update .gitignore to exclude outputs/, data/, raw/*.pdf, raw/*.xlsx Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
232
app/dashboard_backup.py
Normal file
232
app/dashboard_backup.py
Normal file
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Aircraft Routing Dashboard – Streamlit application.
|
||||
|
||||
Run with: streamlit run app/dashboard.py (from project root)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Allow imports from src/ when running from project root
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
from datetime import timedelta
|
||||
|
||||
from src.routing_engine import RoutingPipeline, DEFAULT_CONFIG
|
||||
|
||||
# ── Page config ───────────────────────────────────────────────────────────────
|
||||
st.set_page_config(
|
||||
page_title="OARMP – Aircraft Routing",
|
||||
page_icon="✈",
|
||||
layout="wide",
|
||||
)
|
||||
st.title("✈ Aircraft Routing & Maintenance Planning (OARMP)")
|
||||
st.caption("Set Partitioning + Column Generation + Branch & Bound")
|
||||
|
||||
# ── Sidebar – parameters ──────────────────────────────────────────────────────
|
||||
with st.sidebar:
|
||||
st.header("⚙ Parameters")
|
||||
tat = st.number_input("TAT mínimo (min)", min_value=0, max_value=240, value=60, step=10)
|
||||
year = st.number_input("Ano do planejamento", min_value=2024, max_value=2030, value=2026)
|
||||
time_limit = st.number_input("Limite tempo solver (s)", min_value=30, max_value=600, value=120)
|
||||
run_btn = st.button("▶ Executar Otimização", type="primary")
|
||||
|
||||
# ── Session state ─────────────────────────────────────────────────────────────
|
||||
if "result" not in st.session_state:
|
||||
st.session_state["result"] = None
|
||||
|
||||
# ── Run pipeline ──────────────────────────────────────────────────────────────
|
||||
if run_btn:
|
||||
cfg = DEFAULT_CONFIG
|
||||
cfg.tat_minutes = tat
|
||||
cfg.planning_year = int(year)
|
||||
cfg.mip_time_limit_seconds = int(time_limit)
|
||||
|
||||
with st.spinner("Executando otimização…"):
|
||||
try:
|
||||
pipe = RoutingPipeline(cfg)
|
||||
st.session_state["result"] = pipe.run(save_outputs=True)
|
||||
st.success("Otimização concluída!")
|
||||
except Exception as exc:
|
||||
st.error(f"Erro: {exc}")
|
||||
st.exception(exc)
|
||||
|
||||
result = st.session_state.get("result")
|
||||
|
||||
# ── Tabs ──────────────────────────────────────────────────────────────────────
|
||||
tab_summary, tab_gantt, tab_maint, tab_fleet, tab_ofrags = st.tabs(
|
||||
["📊 Resumo", "📅 Gantt", "🔧 Manutenção", "✈ Frota", "📋 OFRAGs"]
|
||||
)
|
||||
|
||||
# ── Tab: Summary ──────────────────────────────────────────────────────────────
|
||||
with tab_summary:
|
||||
if result is None:
|
||||
st.info("Execute a otimização para ver os resultados.")
|
||||
else:
|
||||
s = result["summary"]
|
||||
c1, c2, c3, c4 = st.columns(4)
|
||||
c1.metric("Status", s["status"])
|
||||
c2.metric("Objetivo (FH perdidas)", f"{s['total_ttm_loss_hours']:.2f} h")
|
||||
c3.metric("OFRAGs cobertas", f"{s['covered_ofrags']} / {s['total_ofrags']}")
|
||||
c4.metric("Eventos de manutenção", s["n_maintenance_events"])
|
||||
|
||||
if s["uncovered_ofrags"]:
|
||||
st.warning(f"OFRAGs NÃO cobertas: {s['uncovered_ofrags']}")
|
||||
|
||||
st.subheader("Escala de voo otimizada")
|
||||
sched = result["schedule"]
|
||||
if not sched.empty:
|
||||
st.dataframe(
|
||||
sched.style.apply(
|
||||
lambda r: ["background-color:#fff3cd" if r.get("maintenance_before") else "" for _ in r],
|
||||
axis=1,
|
||||
),
|
||||
use_container_width=True,
|
||||
)
|
||||
|
||||
st.caption(f"Colunas geradas no CG: {s['columns_generated']}")
|
||||
|
||||
# ── Tab: Gantt ────────────────────────────────────────────────────────────────
|
||||
with tab_gantt:
|
||||
if result is None:
|
||||
st.info("Execute a otimização para ver o diagrama de Gantt.")
|
||||
else:
|
||||
sched = result["schedule"]
|
||||
maint = result["maintenance"]
|
||||
|
||||
gantt_rows = []
|
||||
|
||||
if not sched.empty:
|
||||
for _, row in sched.iterrows():
|
||||
if pd.isna(row.get("departure")) or pd.isna(row.get("arrival")):
|
||||
continue
|
||||
gantt_rows.append(
|
||||
dict(
|
||||
Task=row["aircraft"],
|
||||
Start=row["departure"],
|
||||
Finish=row["arrival"],
|
||||
Resource=row["ofrag_id"],
|
||||
Type="OFRAG",
|
||||
Label=row["ofrag_id"],
|
||||
)
|
||||
)
|
||||
|
||||
if not maint.empty:
|
||||
for _, row in maint.iterrows():
|
||||
if pd.isna(row.get("maint_start")) or pd.isna(row.get("maint_end")):
|
||||
continue
|
||||
gantt_rows.append(
|
||||
dict(
|
||||
Task=row["aircraft"],
|
||||
Start=row["maint_start"],
|
||||
Finish=row["maint_end"],
|
||||
Resource="Manutenção",
|
||||
Type="Maintenance",
|
||||
Label=f"CHECK (perde {row['ttm_loss_hours']:.1f}h)",
|
||||
)
|
||||
)
|
||||
|
||||
if gantt_rows:
|
||||
df_gantt = pd.DataFrame(gantt_rows)
|
||||
color_map = {"Manutenção": "#636EFA"}
|
||||
fig = px.timeline(
|
||||
df_gantt,
|
||||
x_start="Start",
|
||||
x_end="Finish",
|
||||
y="Task",
|
||||
color="Type",
|
||||
text="Label",
|
||||
title="Escala de Voo e Manutenção por Aeronave",
|
||||
color_discrete_map={"OFRAG": "#00CC96", "Maintenance": "#EF553B"},
|
||||
)
|
||||
fig.update_yaxes(categoryorder="category ascending")
|
||||
fig.update_traces(textposition="inside")
|
||||
fig.update_layout(height=400 + len(sched["aircraft"].unique()) * 40)
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
cfg_obj = DEFAULT_CONFIG
|
||||
try:
|
||||
fig.write_html(str(cfg_obj.figures_dir / "gantt.html"))
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
st.info("Nenhum dado de Gantt disponível.")
|
||||
|
||||
# ── Tab: Maintenance ──────────────────────────────────────────────────────────
|
||||
with tab_maint:
|
||||
if result is None:
|
||||
st.info("Execute a otimização para ver eventos de manutenção.")
|
||||
else:
|
||||
maint = result["maintenance"]
|
||||
if maint.empty:
|
||||
st.success("Nenhum evento de manutenção forçado.")
|
||||
else:
|
||||
st.dataframe(maint, use_container_width=True)
|
||||
|
||||
fig_loss = px.bar(
|
||||
maint,
|
||||
x="aircraft",
|
||||
y="ttm_loss_hours",
|
||||
color="check_cycle_index",
|
||||
title="Horas de TTM perdidas por aeronave / ciclo de check",
|
||||
labels={"ttm_loss_hours": "TTM perdido (h)", "aircraft": "Aeronave"},
|
||||
text_auto=".2f",
|
||||
)
|
||||
st.plotly_chart(fig_loss, use_container_width=True)
|
||||
|
||||
# ── Tab: Fleet ────────────────────────────────────────────────────────────────
|
||||
with tab_fleet:
|
||||
if result is None:
|
||||
st.info("Execute a otimização.")
|
||||
else:
|
||||
fleet_df = result["fleet"]
|
||||
st.dataframe(fleet_df, use_container_width=True)
|
||||
|
||||
fig_util = px.bar(
|
||||
fleet_df,
|
||||
x="aircraft",
|
||||
y="ttm_utilisation_pct",
|
||||
title="Utilização do TTM por aeronave (%)",
|
||||
labels={"ttm_utilisation_pct": "Utilização TTM (%)", "aircraft": "Aeronave"},
|
||||
text_auto=".1f",
|
||||
color="ttm_utilisation_pct",
|
||||
color_continuous_scale="RdYlGn",
|
||||
range_color=[0, 100],
|
||||
)
|
||||
st.plotly_chart(fig_util, use_container_width=True)
|
||||
|
||||
fig_fh = px.bar(
|
||||
fleet_df,
|
||||
x="aircraft",
|
||||
y=["initial_ttm_h", "flight_hours_scheduled", "total_ttm_loss_h"],
|
||||
barmode="group",
|
||||
title="TTM inicial vs FH planejadas vs FH perdidas",
|
||||
labels={"value": "Horas de voo", "aircraft": "Aeronave"},
|
||||
)
|
||||
st.plotly_chart(fig_fh, use_container_width=True)
|
||||
|
||||
# ── Tab: OFRAGs ───────────────────────────────────────────────────────────────
|
||||
with tab_ofrags:
|
||||
if result is None:
|
||||
st.info("Execute a otimização.")
|
||||
else:
|
||||
ofrags = result["ofrags"]
|
||||
st.write(f"**{len(ofrags)} OFRAGs** elegíveis (iniciam e terminam na base de manutenção).")
|
||||
cols_show = [c for c in ["ofrag_id", "departure", "arrival", "flight_hours", "n_legs", "missions", "origin", "destination"] if c in ofrags.columns]
|
||||
st.dataframe(ofrags[cols_show], use_container_width=True)
|
||||
|
||||
fig_fh = px.bar(
|
||||
ofrags,
|
||||
x="ofrag_id",
|
||||
y="flight_hours",
|
||||
title="Horas de voo por OFRAG",
|
||||
labels={"flight_hours": "FH total", "ofrag_id": "OFRAG"},
|
||||
text_auto=".2f",
|
||||
)
|
||||
st.plotly_chart(fig_fh, use_container_width=True)
|
||||
Reference in New Issue
Block a user