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:
Eduardo Carlos
2026-06-17 11:52:34 -03:00
parent 32ad7c9f23
commit 3607965c88
43 changed files with 3419 additions and 1936 deletions

View File

@@ -0,0 +1,50 @@
"""
Script 00 Project setup.
Creates all required directories and validates that the raw input files exist.
Run once after cloning the repository.
"""
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from src.routing_engine.config import DEFAULT_CONFIG
cfg = DEFAULT_CONFIG
DIRS = [
cfg.raw_index_dir,
cfg.processed_dir,
cfg.reference_dir,
cfg.quality_dir,
cfg.schedules_dir,
cfg.figures_dir,
cfg.exports_dir,
ROOT / "outputs" / "dashboards",
]
print("Creating project directories…")
for d in DIRS:
d.mkdir(parents=True, exist_ok=True)
print(f" ok {d.relative_to(ROOT)}")
print("\nChecking raw input files…")
REQUIRED = [cfg.flight_schedule_file, cfg.aircraft_file, cfg.checks_file, cfg.airports_file]
all_ok = True
for fname in REQUIRED:
p = cfg.raw_dir / fname
if p.exists():
size = p.stat().st_size
print(f" ok {fname} ({size:,} bytes)")
else:
print(f"{fname} NOT FOUND")
all_ok = False
if all_ok:
print("\n[OK] Setup complete. Run scripts in order: 01 -> 02 -> 03 -> 04.")
else:
print("\n[!] Some raw files are missing. Place them in the 'raw/' folder and re-run.")
sys.exit(1)

View File

@@ -0,0 +1,41 @@
"""
Script 01 Inspect raw input files.
Prints metadata (encoding, separator, column names, sample rows) for every CSV
in the raw/ folder and saves a JSON report to data/raw_index/.
"""
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from src.routing_engine.config import DEFAULT_CONFIG
from src.routing_engine.inspect_files import inspect_all
cfg = DEFAULT_CONFIG
print(f"Inspecting files in: {cfg.raw_dir}\n")
meta = inspect_all(cfg.raw_dir)
for name, info in meta.items():
print(f"-- {name} -------------------------------------------")
if "error" in info:
print(f" ERROR: {info['error']}")
continue
print(f" Encoding : {info['encoding']}")
print(f" Separator : {repr(info['separator'])}")
print(f" Columns : {info['columns']}")
if info["sample_rows"]:
print(f" First row : {info['sample_rows'][0]}")
print()
# Save report
out = cfg.raw_index_dir / "file_inventory.json"
cfg.raw_index_dir.mkdir(parents=True, exist_ok=True)
with open(out, "w", encoding="utf-8") as f:
json.dump(meta, f, ensure_ascii=False, indent=2, default=str)
print(f"Report saved -> {out}")

View File

@@ -0,0 +1,64 @@
"""
Script 02 Build fleet reference table.
Reads AERONAVES.csv + CHECKS.csv, computes TTM and the full check-cycle
sequence for each aircraft, and saves the reference to data/reference/.
"""
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
import pandas as pd
from datetime import datetime
from src.routing_engine.config import DEFAULT_CONFIG
from src.routing_engine.inspect_files import read_aircraft, read_checks
from src.routing_engine.ingest import build_fleet, _check_cycles
cfg = DEFAULT_CONFIG
aircraft_df = read_aircraft(cfg.raw_dir / cfg.aircraft_file)
checks_df = read_checks(cfg.raw_dir / cfg.checks_file)
print("Aircraft loaded:")
print(aircraft_df.to_string(index=False))
print()
print("Checks loaded:")
print(checks_df.to_string(index=False))
print()
# Build fleet
planning_start = datetime(cfg.planning_year, 3, 18) # first OFRAG departure in the sample data
fleet = build_fleet(aircraft_df, checks_df, planning_start)
print("Fleet reference:")
for _, row in fleet.iterrows():
print(f"\n {row['tail_number']} ({row['model']}) FH total = {row['fh_total']:.0f}")
print(f" TTM before first check: {row['ttm_hours']:.1f} FH")
for i, c in enumerate(row["checks"]):
print(f" Cycle {i}: threshold={c['fh_threshold']:.0f} FH, "
f"TTM={c['ttm']:.0f} FH, duration={c['duration_hours']:.0f} h")
# Save to reference
cfg.reference_dir.mkdir(parents=True, exist_ok=True)
out_path = cfg.reference_dir / "fleet_reference.csv"
flat_rows = []
for _, row in fleet.iterrows():
flat_rows.append(
{
"tail_number": row["tail_number"],
"model": row["model"],
"fh_total": row["fh_total"],
"ttm_hours_cycle0": row["ttm_hours"],
"n_check_cycles": len(row["checks"]),
"cycles_json": str(row["checks"]),
}
)
pd.DataFrame(flat_rows).to_csv(out_path, index=False)
print(f"\nFleet reference saved -> {out_path}")

View File

@@ -0,0 +1,44 @@
"""
Script 03 Run the full optimisation pipeline.
Loads data, builds the network, runs Column Generation + B&B, and prints
the solution. All outputs are saved to outputs/ automatically.
"""
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from src.routing_engine import RoutingPipeline, DEFAULT_CONFIG
cfg = DEFAULT_CONFIG
# Adjust parameters here if needed:
# cfg.tat_minutes = 60
# cfg.mip_time_limit_seconds = 120
pipe = RoutingPipeline(cfg)
result = pipe.run(save_outputs=True)
print("\n" + "=" * 60)
print("SOLUTION SUMMARY")
print("=" * 60)
s = result["summary"]
for k, v in s.items():
print(f" {k:<35} {v}")
print("\n-- Schedule ----------------------------------------------")
sched = result["schedule"]
if not sched.empty:
print(sched[["aircraft", "ofrag_id", "departure", "arrival", "flight_hours", "maintenance_before"]].to_string(index=False))
print("\n-- Maintenance events -------------------------------------")
maint = result["maintenance"]
if maint.empty:
print(" No forced maintenance events.")
else:
print(maint.to_string(index=False))
print("\n-- Fleet utilisation --------------------------------------")
print(result["fleet"].to_string(index=False))

View File

@@ -0,0 +1,75 @@
"""
Script 04 Generate and export flight strings.
Reads the saved solution CSVs and generates a per-aircraft "flight string"
report in human-readable format (console + Excel export).
"""
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
import pandas as pd
from src.routing_engine.config import DEFAULT_CONFIG
cfg = DEFAULT_CONFIG
sched_path = cfg.schedules_dir / "flight_strings.csv"
maint_path = cfg.schedules_dir / "maintenance_events.csv"
if not sched_path.exists():
print("No schedule found. Run script 03 first.")
sys.exit(1)
sched = pd.read_csv(sched_path, parse_dates=["departure", "arrival"])
maint = pd.read_csv(maint_path, parse_dates=["maint_start", "maint_end"]) if maint_path.exists() else pd.DataFrame()
print("=" * 70)
print("FLIGHT STRINGS PER AIRCRAFT")
print("=" * 70)
for aircraft, group in sched.groupby("aircraft"):
print(f"\n{'-'*70}")
print(f" Aeronave: {aircraft}")
print(f"{'-'*70}")
total_fh = 0.0
for _, row in group.sort_values("departure").iterrows():
maint_flag = " <- MANUTENCAO ANTES" if row.get("maintenance_before") else ""
dep = pd.to_datetime(row["departure"])
arr = pd.to_datetime(row["arrival"])
fh = float(row.get("flight_hours", 0))
total_fh += fh
print(
f" {row['ofrag_id']:12s} "
f"{dep.strftime('%d/%m %H:%M')} -> {arr.strftime('%d/%m %H:%M')} "
f"({fh:.2f} FH){maint_flag}"
)
print(f" {'TOTAL':12s} {total_fh:.2f} FH")
if not maint.empty:
ac_maint = maint[maint["aircraft"] == aircraft]
if not ac_maint.empty:
print(f"\n Eventos de manutencao:")
for _, ev in ac_maint.iterrows():
print(
f" CHECK (ciclo {int(ev['check_cycle_index'])}) "
f"{pd.to_datetime(ev['maint_start']).strftime('%d/%m %H:%M')} -> "
f"{pd.to_datetime(ev['maint_end']).strftime('%d/%m %H:%M')} "
f"TTM perdido: {ev['ttm_loss_hours']:.2f} FH"
)
# Export to Excel
out_xlsx = cfg.exports_dir / "flight_strings.xlsx"
try:
with pd.ExcelWriter(out_xlsx, engine="openpyxl") as writer:
sched.to_excel(writer, sheet_name="Escala", index=False)
if not maint.empty:
maint.to_excel(writer, sheet_name="Manutencao", index=False)
print(f"\n\nExportado para Excel -> {out_xlsx}")
except ImportError:
print("\n(openpyxl not installed Excel export skipped)")

View File

@@ -0,0 +1,5 @@
@echo off
echo Iniciando dashboard OARMP...
cd /d "%~dp0.."
.venv\Scripts\streamlit.exe run app\dashboard.py --server.port 8501
pause