Processa registro de voo 2025

This commit is contained in:
Cesa-V
2026-06-15 20:22:57 -03:00
parent 8d7dd07b18
commit 11821eaa93
17 changed files with 8973 additions and 25 deletions

View File

@@ -21,6 +21,10 @@
============================================================================
"""
import csv
from datetime import datetime, timezone
from pathlib import Path
import pulp
# ===========================================================================
@@ -28,7 +32,8 @@ import pulp
# ===========================================================================
# Bases (codigos curtos ficticios). 'S' marca bases HABILITADAS a inspecao.
# Na FAB o conjunto de bases habilitadas e' restrito -> isso aperta o routing.
BASES_INSPECAO = {"BR", "GL"} # ex.: Brasilia (hub do esq.) e Galeao
# MN representa Manaus, base operacional do Esquadrao Arara nesta simulacao.
BASES_INSPECAO = {"MN"} # Manaus
TURNAROUND_MIN_H = 1.0 # tempo minimo em solo entre 2 missoes (h)
LIMITE_HORAS_F = 100.0 # horas de celula ate inspecao obrigatoria
@@ -38,31 +43,85 @@ LIMITE_HORAS_F = 100.0 # horas de celula ate inspecao obrigatori
# 'horas_acum' = f_k. LRT inicial de cada aeronave = LIMITE_HORAS_F - f_k.
AERONAVES = [
# id base horas_acum
("FAB-2301", "BR", 12.0),
("FAB-2302", "BR", 71.0), # ja gastou muito -> pouco LRT disponivel
("FAB-2303", "GL", 30.0),
("FAB-2304", "BR", 95.0), # quase no limite -> mal consegue voar
("FAB-2301", "MN", 12.0),
("FAB-2302", "MN", 71.0), # ja gastou muito -> pouco LRT disponivel
("FAB-2303", "MN", 30.0),
("FAB-2304", "MN", 95.0), # quase no limite -> mal consegue voar
]
# ----- Missoes de transporte: (id, origem, destino, partida_h, chegada_h, prioridade)
# partida/chegada em horas decimais dentro do horizonte (ex.: 0..72 = 3 dias).
# prioridade: peso da missao (missao critica pesa mais).
MISSOES = [
# id orig dest part cheg prio
("M01", "BR", "MN", 6.0, 10.0, 5), # logistica Amazonia (alta prio)
("M02", "MN", "BR", 12.0, 16.0, 5),
("M03", "BR", "RF", 7.0, 10.0, 3),
("M04", "RF", "BR", 11.5, 14.5, 3),
("M05", "BR", "CG", 8.0, 10.0, 2),
("M06", "CG", "BR", 11.0, 13.0, 2),
("M07", "GL", "BR", 6.5, 8.0, 4),
("M08", "BR", "BE", 9.0, 12.5, 4), # Belem
("M09", "BE", "BR", 14.0, 17.5, 4),
("M10", "BR", "GL", 15.0, 16.5, 1),
("M11", "BR", "MN", 30.0, 34.0, 5), # dia 2
("M12", "MN", "BR", 36.0, 40.0, 5),
# ----- Missoes de transporte
# Entrada recomendada: data/hora UTC, como viria de uma planilha.
# Para usar uma planilha, exporte para CSV com as colunas:
# id,orig,dest,partida_utc,chegada_utc,prioridade
# e preencha CSV_MISSOES_PATH com o caminho do arquivo.
CSV_MISSOES_PATH = None
MISSOES_UTC = [
# id orig dest partida UTC chegada UTC prio
("M01", "MN", "BR", "2026-07-01T06:00Z", "2026-07-01T10:00Z", 5),
("M02", "BR", "MN", "2026-07-01T12:00Z", "2026-07-01T16:00Z", 5),
("M03", "MN", "BE", "2026-07-01T06:30Z", "2026-07-01T09:00Z", 4),
("M04", "BE", "MN", "2026-07-01T11:00Z", "2026-07-01T13:30Z", 4),
("M05", "MN", "PV", "2026-07-01T07:00Z", "2026-07-01T09:00Z", 3),
("M06", "PV", "MN", "2026-07-01T10:30Z", "2026-07-01T12:30Z", 3),
("M07", "MN", "BV", "2026-07-01T08:00Z", "2026-07-01T09:30Z", 3),
("M08", "BV", "MN", "2026-07-01T11:00Z", "2026-07-01T12:30Z", 3),
("M09", "MN", "GL", "2026-07-01T18:00Z", "2026-07-01T23:00Z", 4),
("M10", "GL", "MN", "2026-07-02T01:00Z", "2026-07-02T06:00Z", 4),
("M11", "MN", "CG", "2026-07-01T20:00Z", "2026-07-01T23:00Z", 2),
("M12", "CG", "MN", "2026-07-02T01:00Z", "2026-07-02T04:00Z", 2),
("M13", "MN", "RF", "2026-07-02T06:00Z", "2026-07-02T11:00Z", 4),
("M14", "RF", "MN", "2026-07-02T13:00Z", "2026-07-02T18:00Z", 4),
("M15", "MN", "NT", "2026-07-02T08:00Z", "2026-07-02T12:30Z", 3),
("M16", "NT", "MN", "2026-07-02T14:00Z", "2026-07-02T18:30Z", 3),
("M17", "MN", "BR", "2026-07-03T00:00Z", "2026-07-03T04:00Z", 5),
("M18", "BR", "MN", "2026-07-03T06:00Z", "2026-07-03T10:00Z", 5),
("M19", "MN", "BE", "2026-07-03T02:00Z", "2026-07-03T04:30Z", 4),
("M20", "BE", "MN", "2026-07-03T06:00Z", "2026-07-03T08:30Z", 4),
]
def parse_utc(valor):
"""Converte texto ISO UTC da planilha para datetime com timezone."""
texto = str(valor).strip()
if texto.endswith("Z"):
texto = texto[:-1] + "+00:00"
dt = datetime.fromisoformat(texto)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt.astimezone(timezone.utc)
def carregar_missoes_csv(caminho):
with Path(caminho).open(newline="", encoding="utf-8-sig") as arq:
leitor = csv.DictReader(arq)
return [
(
linha["id"],
linha["orig"],
linha["dest"],
linha["partida_utc"],
linha["chegada_utc"],
int(linha["prioridade"]),
)
for linha in leitor
]
def converter_missoes_para_horas(missoes_utc):
datas_partida = [parse_utc(m[3]) for m in missoes_utc]
horizonte_inicio = min(datas_partida)
missoes_horas = []
for mid, orig, dest, partida_utc, chegada_utc, prio in missoes_utc:
partida = parse_utc(partida_utc)
chegada = parse_utc(chegada_utc)
if chegada <= partida:
raise ValueError(f"Missao {mid}: chegada_utc deve ser depois de partida_utc")
part_h = (partida - horizonte_inicio).total_seconds() / 3600
cheg_h = (chegada - horizonte_inicio).total_seconds() / 3600
missoes_horas.append((mid, orig, dest, part_h, cheg_h, prio))
return missoes_horas, horizonte_inicio
MISSOES_FONTE = carregar_missoes_csv(CSV_MISSOES_PATH) if CSV_MISSOES_PATH else MISSOES_UTC
MISSOES, HORIZONTE_INICIO_UTC = converter_missoes_para_horas(MISSOES_FONTE)
# ===========================================================================
# 2) PRE-PROCESSAMENTO (gera conexoes viaveis)
# ===========================================================================
@@ -95,7 +154,7 @@ c = {(k, i, j): pulp.LpVariable(f"c_{k}_{i}_{j}", cat="Binary") # k voa i de
for k in KS for (i, j) in conex}
z = {i: pulp.LpVariable(f"z_{i}", cat="Binary") for i in IDS} # missao coberta
# --- Objetivo: maximizar prioridade total das missoes cumpridas
# --- Objetivo primario: maximizar prioridade total das missoes cumpridas
mdl += pulp.lpSum(miss[i]["prio"] * z[i] for i in IDS), "Missoes_cumpridas_ponderadas"
# --- (R1) Cobertura: cada missao por NO MAXIMO uma aeronave
@@ -133,6 +192,24 @@ for k in KS:
# 4) RESOLVER
# ===========================================================================
solver = pulp.PULP_CBC_CMD(msg=False) # troque por HiGHS se instalado
# Etapa 1: maximiza o cumprimento ponderado das missoes.
mdl.solve(solver)
prio_otima = int(pulp.value(mdl.objective))
# Etapa 2: mantendo a prioridade otima, minimiza o LRT restante ponderado.
# Como as horas totais voadas podem ser constantes quando todas as missoes sao
# cumpridas, o peso prioriza consumir LRT das aeronaves mais proximas da
# manutencao, sem sacrificar nenhuma missao prioritaria da primeira etapa.
lrt_restante = {
k: (LIMITE_HORAS_F - acft[k]["f"]) -
pulp.lpSum(miss[i]["dur"] * x[(k, i)] for i in IDS)
for k in KS
}
pesos_lrt = {k: 1 / (LIMITE_HORAS_F - acft[k]["f"]) for k in KS}
mdl += pulp.lpSum(miss[i]["prio"] * z[i] for i in IDS) == prio_otima, "fixa_prioridade_otima"
mdl.sense = pulp.LpMinimize
mdl.setObjective(pulp.lpSum(pesos_lrt[k] * lrt_restante[k] for k in KS))
mdl.solve(solver)
# ===========================================================================
@@ -140,11 +217,17 @@ mdl.solve(solver)
# ===========================================================================
print("=" * 70)
print(f" STATUS: {pulp.LpStatus[mdl.status]}")
print(f" Horizonte inicia em UTC: {HORIZONTE_INICIO_UTC.isoformat().replace('+00:00', 'Z')}")
prio_tot = sum(miss[i]["prio"] for i in IDS)
prio_obt = int(pulp.value(mdl.objective))
prio_obt = prio_otima
cob = [i for i in IDS if z[i].value() and z[i].value() > 0.5]
print(f" Missoes cumpridas: {len(cob)}/{len(IDS)} "
f"| Prioridade obtida: {prio_obt}/{prio_tot}")
lrt_total = sum((LIMITE_HORAS_F - acft[k]["f"]) -
sum(miss[i]["dur"] * x[(k, i)].value() for i in IDS)
for k in KS)
print(f" LRT restante total: {lrt_total:g}h")
print(" Criterio secundario: menor LRT restante ponderado p/ manutencao")
print("=" * 70)
def rota_da_aeronave(k):
@@ -185,4 +268,4 @@ if descobertas:
# NOTA - usar HiGHS (recomendado p/ instancias maiores, sem licenca):
# pip install highspy
# solver = pulp.HiGHS(msg=False) # PuLP 3.x expoe HiGHS nativo
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------