Processa registro de voo 2025
This commit is contained in:
1421
db/pre_process/registro_voo_2025/missoes_oamrp_2025.csv
Normal file
1421
db/pre_process/registro_voo_2025/missoes_oamrp_2025.csv
Normal file
File diff suppressed because it is too large
Load Diff
1268
db/pre_process/registro_voo_2025/missoes_oamrp_2025_consolidado.csv
Normal file
1268
db/pre_process/registro_voo_2025/missoes_oamrp_2025_consolidado.csv
Normal file
File diff suppressed because it is too large
Load Diff
509
db/pre_process/registro_voo_2025/preprocess_planilha_2025.py
Normal file
509
db/pre_process/registro_voo_2025/preprocess_planilha_2025.py
Normal file
@@ -0,0 +1,509 @@
|
||||
"""
|
||||
Pre-processa a planilha bruta de voos de 2025.
|
||||
|
||||
Entrada:
|
||||
db/raw/Planilha 2025.xlsx
|
||||
|
||||
Saidas:
|
||||
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.xlsx
|
||||
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.csv
|
||||
db/pre_process/registro_voo_2025/missoes_oamrp_2025.csv
|
||||
db/pre_process/registro_voo_2025/resumo_registro_voo_2025.json
|
||||
db/processed/registro_voo_2025.csv
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import json
|
||||
from collections import Counter
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import openpyxl
|
||||
|
||||
|
||||
ANO = 2025
|
||||
BASE_DIR = Path(__file__).resolve().parents[3]
|
||||
RAW_PATH = BASE_DIR / "db" / "raw" / "Planilha 2025.xlsx"
|
||||
PREPROCESS_DIR = BASE_DIR / "db" / "pre_process" / "registro_voo_2025"
|
||||
PROCESSED_DIR = BASE_DIR / "db" / "processed"
|
||||
|
||||
LIMPO_XLSX = PREPROCESS_DIR / "registro_voo_2025_limpo.xlsx"
|
||||
LIMPO_CSV = PREPROCESS_DIR / "registro_voo_2025_limpo.csv"
|
||||
CONSOLIDADO_CSV = PREPROCESS_DIR / "registro_voo_2025_consolidado.csv"
|
||||
MISSOES_CSV = PREPROCESS_DIR / "missoes_oamrp_2025.csv"
|
||||
MISSOES_CONSOLIDADAS_CSV = PREPROCESS_DIR / "missoes_oamrp_2025_consolidado.csv"
|
||||
RESUMO_JSON = PREPROCESS_DIR / "resumo_registro_voo_2025.json"
|
||||
PROCESSED_CSV = PROCESSED_DIR / "registro_voo_2025.csv"
|
||||
PROCESSED_CONSOLIDADO_CSV = PROCESSED_DIR / "registro_voo_2025_consolidado.csv"
|
||||
|
||||
MES_MAP = {
|
||||
"JAN": 1,
|
||||
"FEV": 2,
|
||||
"MAR": 3,
|
||||
"ABR": 4,
|
||||
"MAI": 5,
|
||||
"JUN": 6,
|
||||
"JUL": 7,
|
||||
"AGO": 8,
|
||||
"SET": 9,
|
||||
"OUT": 10,
|
||||
"NOV": 11,
|
||||
"DEZ": 12,
|
||||
}
|
||||
MES_ABBR = {numero: nome for nome, numero in MES_MAP.items()}
|
||||
LINHAS_ORIGEM_EXCLUIDAS = {793}
|
||||
|
||||
COLUNAS_REGISTRO = [
|
||||
"id_registro",
|
||||
"linha_origem",
|
||||
"data",
|
||||
"data_iso",
|
||||
"mes",
|
||||
"mes_numero",
|
||||
"om",
|
||||
"aeronave",
|
||||
"etapa",
|
||||
"localidade_dep",
|
||||
"localidade_arr",
|
||||
"horario_z_dep",
|
||||
"horario_z_arr",
|
||||
"partida_utc",
|
||||
"chegada_utc",
|
||||
"tempo_voo",
|
||||
"tempo_voo_min",
|
||||
"segmento",
|
||||
"pousos",
|
||||
"missao",
|
||||
"periodo",
|
||||
"cruza_meia_noite",
|
||||
]
|
||||
|
||||
COLUNAS_OAMRP = [
|
||||
"id",
|
||||
"orig",
|
||||
"dest",
|
||||
"partida_utc",
|
||||
"chegada_utc",
|
||||
"prioridade",
|
||||
]
|
||||
|
||||
COLUNAS_CONSOLIDADAS = [
|
||||
"id_registro",
|
||||
"linhas_origem",
|
||||
"data",
|
||||
"data_iso",
|
||||
"mes",
|
||||
"mes_numero",
|
||||
"om",
|
||||
"aeronave",
|
||||
"etapa_inicio",
|
||||
"etapa_fim",
|
||||
"localidade_dep",
|
||||
"localidade_arr",
|
||||
"horario_z_dep",
|
||||
"horario_z_arr",
|
||||
"partida_utc",
|
||||
"chegada_utc",
|
||||
"tempo_voo",
|
||||
"tempo_voo_min",
|
||||
"segmento_inicio",
|
||||
"segmento_fim",
|
||||
"pousos",
|
||||
"missao",
|
||||
"periodo",
|
||||
"cruza_meia_noite",
|
||||
"qtd_linhas_originais",
|
||||
"tem_rota_intermediaria",
|
||||
]
|
||||
|
||||
|
||||
def texto_limpo(valor: Any) -> str:
|
||||
if valor is None:
|
||||
return ""
|
||||
return str(valor).strip()
|
||||
|
||||
|
||||
def normalizar_mes(valor: Any) -> str:
|
||||
texto = texto_limpo(valor).upper().replace(".", "")
|
||||
return texto
|
||||
|
||||
|
||||
def parse_data(valor_data: Any, valor_mes: Any) -> date:
|
||||
if isinstance(valor_data, datetime):
|
||||
return valor_data.date()
|
||||
if isinstance(valor_data, date):
|
||||
return valor_data
|
||||
|
||||
texto = texto_limpo(valor_data).lower().replace(".", "")
|
||||
if "/" not in texto:
|
||||
raise ValueError(f"Data invalida: {valor_data!r}")
|
||||
dia_txt, mes_txt = texto.split("/", 1)
|
||||
mes_key = normalizar_mes(mes_txt or valor_mes)
|
||||
if mes_key not in MES_MAP:
|
||||
raise ValueError(f"Mes invalido: data={valor_data!r}, mes={valor_mes!r}")
|
||||
return date(ANO, MES_MAP[mes_key], int(dia_txt))
|
||||
|
||||
|
||||
def minutos_do_dia(valor: Any) -> tuple[int, int]:
|
||||
"""Retorna (minutos no dia, deslocamento em dias informado pelo Excel)."""
|
||||
if isinstance(valor, datetime):
|
||||
return valor.hour * 60 + valor.minute, 0
|
||||
if isinstance(valor, time):
|
||||
return valor.hour * 60 + valor.minute, 0
|
||||
if isinstance(valor, timedelta):
|
||||
total_min = int(round(valor.total_seconds() / 60))
|
||||
return total_min % (24 * 60), total_min // (24 * 60)
|
||||
if isinstance(valor, (int, float)):
|
||||
total_min = int(round(float(valor) * 24 * 60))
|
||||
return total_min % (24 * 60), total_min // (24 * 60)
|
||||
|
||||
texto = texto_limpo(valor)
|
||||
if ":" in texto:
|
||||
partes = texto.split(":")
|
||||
return int(partes[0]) * 60 + int(partes[1]), 0
|
||||
raise ValueError(f"Horario invalido: {valor!r}")
|
||||
|
||||
|
||||
def minutos_duracao(valor: Any) -> int:
|
||||
if isinstance(valor, datetime):
|
||||
return valor.hour * 60 + valor.minute
|
||||
if isinstance(valor, time):
|
||||
return valor.hour * 60 + valor.minute
|
||||
if isinstance(valor, timedelta):
|
||||
return int(round(valor.total_seconds() / 60))
|
||||
if isinstance(valor, (int, float)):
|
||||
return int(round(float(valor) * 24 * 60))
|
||||
|
||||
texto = texto_limpo(valor)
|
||||
if ":" in texto:
|
||||
horas, minutos = texto.split(":", 1)
|
||||
return int(horas) * 60 + int(minutos)
|
||||
raise ValueError(f"Duracao invalida: {valor!r}")
|
||||
|
||||
|
||||
def hhmm(minutos: int) -> str:
|
||||
minutos = minutos % (24 * 60)
|
||||
return f"{minutos // 60:02d}:{minutos % 60:02d}"
|
||||
|
||||
|
||||
def duracao_hhmm(minutos: int) -> str:
|
||||
return f"{minutos // 60:02d}:{minutos % 60:02d}"
|
||||
|
||||
|
||||
def iso_utc(data_voo: date, minutos: int, deslocamento_dias: int = 0) -> str:
|
||||
data_hora = datetime.combine(data_voo, time()) + timedelta(
|
||||
days=deslocamento_dias,
|
||||
minutes=minutos,
|
||||
)
|
||||
return data_hora.isoformat(timespec="minutes") + ":00Z"
|
||||
|
||||
|
||||
def atualizar_datas_do_registro(
|
||||
registro: dict[str, Any],
|
||||
partida: datetime,
|
||||
chegada: datetime,
|
||||
) -> None:
|
||||
data_voo = partida.date()
|
||||
registro["data"] = data_voo.strftime("%d/%m")
|
||||
registro["data_iso"] = data_voo.isoformat()
|
||||
registro["mes_numero"] = data_voo.month
|
||||
registro["mes"] = MES_ABBR[data_voo.month]
|
||||
registro["partida_utc"] = partida.isoformat(timespec="minutes").replace("+00:00", "") + ":00Z"
|
||||
registro["chegada_utc"] = chegada.isoformat(timespec="minutes").replace("+00:00", "") + ":00Z"
|
||||
registro["cruza_meia_noite"] = chegada.date() > partida.date()
|
||||
|
||||
|
||||
def id_registro(linha: dict[str, Any]) -> str:
|
||||
om = texto_limpo(linha["om"]) or "SEMOM"
|
||||
etapa = texto_limpo(linha["etapa"]) or "SEMETAPA"
|
||||
return f"RV2025_OM{om}_E{etapa}_L{linha['linha_origem']}"
|
||||
|
||||
|
||||
def linha_tem_voo(ws: openpyxl.worksheet.worksheet.Worksheet, row: int) -> bool:
|
||||
return any(ws.cell(row, col).value not in (None, "") for col in range(1, 13))
|
||||
|
||||
|
||||
def extrair_registros() -> list[dict[str, Any]]:
|
||||
wb = openpyxl.load_workbook(RAW_PATH, data_only=True)
|
||||
ws = wb.active
|
||||
|
||||
registros: list[dict[str, Any]] = []
|
||||
for row in range(4, ws.max_row + 1):
|
||||
if row in LINHAS_ORIGEM_EXCLUIDAS:
|
||||
continue
|
||||
if not linha_tem_voo(ws, row):
|
||||
continue
|
||||
|
||||
data_voo = parse_data(ws.cell(row, 1).value, ws.cell(row, 2).value)
|
||||
mes = normalizar_mes(ws.cell(row, 2).value)
|
||||
dep_min, dep_offset = minutos_do_dia(ws.cell(row, 8).value)
|
||||
arr_min, arr_offset_excel = minutos_do_dia(ws.cell(row, 9).value)
|
||||
arr_offset = max(arr_offset_excel, 1 if arr_min <= dep_min else 0)
|
||||
tempo_min = minutos_duracao(ws.cell(row, 10).value)
|
||||
|
||||
registro = {
|
||||
"linha_origem": row,
|
||||
"data": data_voo.strftime("%d/%m"),
|
||||
"data_iso": data_voo.isoformat(),
|
||||
"mes": mes,
|
||||
"mes_numero": MES_MAP.get(mes, ""),
|
||||
"om": ws.cell(row, 3).value,
|
||||
"aeronave": ws.cell(row, 4).value,
|
||||
"etapa": ws.cell(row, 5).value,
|
||||
"localidade_dep": texto_limpo(ws.cell(row, 6).value).upper(),
|
||||
"localidade_arr": texto_limpo(ws.cell(row, 7).value).upper(),
|
||||
"horario_z_dep": hhmm(dep_min),
|
||||
"horario_z_arr": hhmm(arr_min),
|
||||
"partida_utc": iso_utc(data_voo, dep_min, dep_offset),
|
||||
"chegada_utc": iso_utc(data_voo, arr_min, arr_offset),
|
||||
"tempo_voo": duracao_hhmm(tempo_min),
|
||||
"tempo_voo_min": tempo_min,
|
||||
"segmento": ws.cell(row, 11).value,
|
||||
"pousos": ws.cell(row, 12).value,
|
||||
"missao": texto_limpo(ws.cell(row, 35).value).upper(),
|
||||
"periodo": texto_limpo(ws.cell(row, 36).value).upper(),
|
||||
"cruza_meia_noite": arr_offset > dep_offset,
|
||||
}
|
||||
registro["id_registro"] = id_registro(registro)
|
||||
registros.append(registro)
|
||||
|
||||
corrigir_continuidade_temporal(registros)
|
||||
return registros
|
||||
|
||||
|
||||
def corrigir_continuidade_temporal(registros: list[dict[str, Any]]) -> None:
|
||||
grupos: dict[tuple[str, str], list[dict[str, Any]]] = {}
|
||||
for registro in registros:
|
||||
chave = (texto_limpo(registro["om"]), texto_limpo(registro["aeronave"]))
|
||||
grupos.setdefault(chave, []).append(registro)
|
||||
|
||||
for linhas in grupos.values():
|
||||
linhas.sort(key=lambda registro: registro["linha_origem"])
|
||||
chegada_anterior: datetime | None = None
|
||||
for registro in linhas:
|
||||
partida = parse_iso_utc(registro["partida_utc"])
|
||||
chegada = parse_iso_utc(registro["chegada_utc"])
|
||||
while chegada_anterior is not None and partida < chegada_anterior:
|
||||
partida += timedelta(days=1)
|
||||
chegada += timedelta(days=1)
|
||||
atualizar_datas_do_registro(registro, partida, chegada)
|
||||
chegada_anterior = chegada
|
||||
|
||||
|
||||
def escrever_csv(caminho: Path, colunas: list[str], linhas: list[dict[str, Any]]) -> None:
|
||||
with caminho.open("w", newline="", encoding="utf-8-sig") as arquivo:
|
||||
escritor = csv.DictWriter(arquivo, fieldnames=colunas)
|
||||
escritor.writeheader()
|
||||
escritor.writerows(linhas)
|
||||
|
||||
|
||||
def escrever_xlsx(caminho: Path, colunas: list[str], linhas: list[dict[str, Any]]) -> None:
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "registro_voo_2025"
|
||||
ws.append(colunas)
|
||||
for linha in linhas:
|
||||
ws.append([linha[coluna] for coluna in colunas])
|
||||
ws.freeze_panes = "A2"
|
||||
ws.auto_filter.ref = ws.dimensions
|
||||
for column_cells in ws.columns:
|
||||
letter = column_cells[0].column_letter
|
||||
max_len = max(len(texto_limpo(cell.value)) for cell in column_cells)
|
||||
ws.column_dimensions[letter].width = min(max(max_len + 2, 10), 28)
|
||||
wb.save(caminho)
|
||||
|
||||
|
||||
def preparar_oamrp(registros: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
linhas = []
|
||||
for registro in registros:
|
||||
linhas.append(
|
||||
{
|
||||
"id": registro["id_registro"],
|
||||
"orig": registro["localidade_dep"],
|
||||
"dest": registro["localidade_arr"],
|
||||
"partida_utc": registro["partida_utc"],
|
||||
"chegada_utc": registro["chegada_utc"],
|
||||
"prioridade": 1,
|
||||
}
|
||||
)
|
||||
return linhas
|
||||
|
||||
|
||||
def toca_rota(registro: dict[str, Any]) -> bool:
|
||||
return "ROTA" in {registro["localidade_dep"], registro["localidade_arr"]}
|
||||
|
||||
|
||||
def parse_iso_utc(valor: str) -> datetime:
|
||||
return datetime.fromisoformat(valor.replace("Z", "+00:00"))
|
||||
|
||||
|
||||
def consolidar_grupo(grupo: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
primeiro = grupo[0]
|
||||
ultimo = grupo[-1]
|
||||
tempo_total = sum(int(registro["tempo_voo_min"]) for registro in grupo)
|
||||
pousos_total = sum(int(registro["pousos"] or 0) for registro in grupo)
|
||||
missoes = sorted({registro["missao"] for registro in grupo if registro["missao"]})
|
||||
periodos = sorted({registro["periodo"] for registro in grupo if registro["periodo"]})
|
||||
linhas = [str(registro["linha_origem"]) for registro in grupo]
|
||||
cruza_meia_noite = parse_iso_utc(ultimo["chegada_utc"]).date() > parse_iso_utc(
|
||||
primeiro["partida_utc"]
|
||||
).date()
|
||||
|
||||
return {
|
||||
"id_registro": f"{primeiro['id_registro']}_C{ultimo['linha_origem']}",
|
||||
"linhas_origem": ";".join(linhas),
|
||||
"data": primeiro["data"],
|
||||
"data_iso": primeiro["data_iso"],
|
||||
"mes": primeiro["mes"],
|
||||
"mes_numero": primeiro["mes_numero"],
|
||||
"om": primeiro["om"],
|
||||
"aeronave": primeiro["aeronave"],
|
||||
"etapa_inicio": primeiro["etapa"],
|
||||
"etapa_fim": ultimo["etapa"],
|
||||
"localidade_dep": primeiro["localidade_dep"],
|
||||
"localidade_arr": ultimo["localidade_arr"],
|
||||
"horario_z_dep": primeiro["horario_z_dep"],
|
||||
"horario_z_arr": ultimo["horario_z_arr"],
|
||||
"partida_utc": primeiro["partida_utc"],
|
||||
"chegada_utc": ultimo["chegada_utc"],
|
||||
"tempo_voo": duracao_hhmm(tempo_total),
|
||||
"tempo_voo_min": tempo_total,
|
||||
"segmento_inicio": primeiro["segmento"],
|
||||
"segmento_fim": ultimo["segmento"],
|
||||
"pousos": pousos_total,
|
||||
"missao": ";".join(missoes),
|
||||
"periodo": ";".join(periodos),
|
||||
"cruza_meia_noite": cruza_meia_noite,
|
||||
"qtd_linhas_originais": len(grupo),
|
||||
"tem_rota_intermediaria": any(toca_rota(registro) for registro in grupo),
|
||||
}
|
||||
|
||||
|
||||
def consolidar_transicoes_rota(registros: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
grupos: dict[tuple[str, str], list[dict[str, Any]]] = {}
|
||||
for registro in registros:
|
||||
chave = (texto_limpo(registro["om"]), texto_limpo(registro["aeronave"]))
|
||||
grupos.setdefault(chave, []).append(registro)
|
||||
|
||||
consolidados: list[dict[str, Any]] = []
|
||||
for chave in sorted(grupos):
|
||||
linhas = sorted(
|
||||
grupos[chave],
|
||||
key=lambda registro: (parse_iso_utc(registro["partida_utc"]), registro["linha_origem"]),
|
||||
)
|
||||
idx = 0
|
||||
while idx < len(linhas):
|
||||
atual = linhas[idx]
|
||||
if not toca_rota(atual):
|
||||
consolidados.append(consolidar_grupo([atual]))
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
grupo = [atual]
|
||||
idx += 1
|
||||
while (
|
||||
idx < len(linhas)
|
||||
and toca_rota(linhas[idx])
|
||||
and grupo[-1]["localidade_arr"] == linhas[idx]["localidade_dep"]
|
||||
):
|
||||
grupo.append(linhas[idx])
|
||||
idx += 1
|
||||
if grupo[-1]["localidade_arr"] != "ROTA":
|
||||
break
|
||||
consolidados.append(consolidar_grupo(grupo))
|
||||
|
||||
return sorted(consolidados, key=lambda registro: parse_iso_utc(registro["partida_utc"]))
|
||||
|
||||
|
||||
def preparar_oamrp_consolidado(registros: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
linhas = []
|
||||
for registro in registros:
|
||||
linhas.append(
|
||||
{
|
||||
"id": registro["id_registro"],
|
||||
"orig": registro["localidade_dep"],
|
||||
"dest": registro["localidade_arr"],
|
||||
"partida_utc": registro["partida_utc"],
|
||||
"chegada_utc": registro["chegada_utc"],
|
||||
"prioridade": 1,
|
||||
}
|
||||
)
|
||||
return linhas
|
||||
|
||||
|
||||
def gerar_resumo(
|
||||
registros: list[dict[str, Any]],
|
||||
registros_consolidados: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
faltantes = {
|
||||
coluna: sum(registro[coluna] in (None, "") for registro in registros)
|
||||
for coluna in COLUNAS_REGISTRO
|
||||
}
|
||||
return {
|
||||
"arquivo_origem": str(RAW_PATH.relative_to(BASE_DIR)),
|
||||
"ano_assumido": ANO,
|
||||
"linhas_origem_excluidas": sorted(LINHAS_ORIGEM_EXCLUIDAS),
|
||||
"registros_extraidos": len(registros),
|
||||
"registros_consolidados": len(registros_consolidados),
|
||||
"linhas_com_cruzamento_de_meia_noite": sum(
|
||||
1 for registro in registros if registro["cruza_meia_noite"]
|
||||
),
|
||||
"linhas_com_rota": sum(1 for registro in registros if toca_rota(registro)),
|
||||
"trechos_consolidados_com_rota": sum(
|
||||
1 for registro in registros_consolidados if registro["tem_rota_intermediaria"]
|
||||
),
|
||||
"aeronaves": dict(sorted(Counter(r["aeronave"] for r in registros).items())),
|
||||
"periodos": dict(Counter(r["periodo"] for r in registros)),
|
||||
"meses": dict(Counter(r["mes"] for r in registros)),
|
||||
"campos_faltantes": {k: v for k, v in faltantes.items() if v},
|
||||
"observacoes": [
|
||||
"Colunas REAL ate COM2 descartadas conforme criterio informado.",
|
||||
"Horarios tratados como UTC/Z; nao houve conversao de fuso.",
|
||||
"Chegadas iguais ou anteriores a partida foram deslocadas para o dia seguinte.",
|
||||
"SEGMTO foi preservado da planilha original, sem recalculo.",
|
||||
"Transicoes com ROTA tambem foram consolidadas em arquivo separado.",
|
||||
"Prioridade OAMRP definida como 1 por falta de coluna de prioridade na origem.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
PREPROCESS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
PROCESSED_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
registros = extrair_registros()
|
||||
registros_consolidados = consolidar_transicoes_rota(registros)
|
||||
escrever_csv(LIMPO_CSV, COLUNAS_REGISTRO, registros)
|
||||
escrever_csv(PROCESSED_CSV, COLUNAS_REGISTRO, registros)
|
||||
escrever_xlsx(LIMPO_XLSX, COLUNAS_REGISTRO, registros)
|
||||
escrever_csv(CONSOLIDADO_CSV, COLUNAS_CONSOLIDADAS, registros_consolidados)
|
||||
escrever_csv(PROCESSED_CONSOLIDADO_CSV, COLUNAS_CONSOLIDADAS, registros_consolidados)
|
||||
escrever_csv(MISSOES_CSV, COLUNAS_OAMRP, preparar_oamrp(registros))
|
||||
escrever_csv(
|
||||
MISSOES_CONSOLIDADAS_CSV,
|
||||
COLUNAS_OAMRP,
|
||||
preparar_oamrp_consolidado(registros_consolidados),
|
||||
)
|
||||
|
||||
with RESUMO_JSON.open("w", encoding="utf-8") as arquivo:
|
||||
json.dump(
|
||||
gerar_resumo(registros, registros_consolidados),
|
||||
arquivo,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
arquivo.write("\n")
|
||||
|
||||
print(f"Registros extraidos: {len(registros)}")
|
||||
print(f"Registros consolidados: {len(registros_consolidados)}")
|
||||
print(f"CSV final: {PROCESSED_CSV.relative_to(BASE_DIR)}")
|
||||
print(f"CSV consolidado: {PROCESSED_CONSOLIDADO_CSV.relative_to(BASE_DIR)}")
|
||||
print(f"CSV OAMRP: {MISSOES_CSV.relative_to(BASE_DIR)}")
|
||||
print(f"CSV OAMRP consolidado: {MISSOES_CONSOLIDADAS_CSV.relative_to(BASE_DIR)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1268
db/pre_process/registro_voo_2025/registro_voo_2025_consolidado.csv
Normal file
1268
db/pre_process/registro_voo_2025/registro_voo_2025_consolidado.csv
Normal file
File diff suppressed because it is too large
Load Diff
1421
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.csv
Normal file
1421
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.xlsx
Normal file
BIN
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.xlsx
Normal file
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"arquivo_origem": "db\\raw\\Planilha 2025.xlsx",
|
||||
"ano_assumido": 2025,
|
||||
"linhas_origem_excluidas": [
|
||||
793
|
||||
],
|
||||
"registros_extraidos": 1420,
|
||||
"registros_consolidados": 1267,
|
||||
"linhas_com_cruzamento_de_meia_noite": 53,
|
||||
"linhas_com_rota": 288,
|
||||
"trechos_consolidados_com_rota": 135,
|
||||
"aeronaves": {
|
||||
"2800": 329,
|
||||
"2803": 513,
|
||||
"2809": 212,
|
||||
"2811": 366
|
||||
},
|
||||
"periodos": {
|
||||
"NOTURNO": 247,
|
||||
"DIURNO": 1145,
|
||||
"NVG": 28
|
||||
},
|
||||
"meses": {
|
||||
"JAN": 69,
|
||||
"FEV": 179,
|
||||
"MAR": 166,
|
||||
"ABR": 184,
|
||||
"MAI": 218,
|
||||
"JUN": 144,
|
||||
"JUL": 106,
|
||||
"AGO": 36,
|
||||
"SET": 160,
|
||||
"OUT": 29,
|
||||
"NOV": 66,
|
||||
"DEZ": 63
|
||||
},
|
||||
"campos_faltantes": {
|
||||
"pousos": 9
|
||||
},
|
||||
"observacoes": [
|
||||
"Colunas REAL ate COM2 descartadas conforme criterio informado.",
|
||||
"Horarios tratados como UTC/Z; nao houve conversao de fuso.",
|
||||
"Chegadas iguais ou anteriores a partida foram deslocadas para o dia seguinte.",
|
||||
"SEGMTO foi preservado da planilha original, sem recalculo.",
|
||||
"Transicoes com ROTA tambem foram consolidadas em arquivo separado.",
|
||||
"Prioridade OAMRP definida como 1 por falta de coluna de prioridade na origem."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
# Validacao do registro de voo 2025
|
||||
|
||||
Fonte validada: `db/processed/registro_voo_2025_consolidado.csv`
|
||||
|
||||
## Resultado geral
|
||||
|
||||
- Linhas originais limpas: 1420.
|
||||
- Trechos consolidados: 1267.
|
||||
- Linhas originais excluidas: 793.
|
||||
- Trechos com `ROTA` no consolidado: 0.
|
||||
- Transicoes `ROTA` convertidas para origem/destino reais: 135.
|
||||
- Cruzamentos de 00Z preservados: 53.
|
||||
- IDs duplicados: 0.
|
||||
- Divergencias de duracao no registro original: 0.
|
||||
- Trechos consolidados com tempo decorrido menor que tempo de voo: 0.
|
||||
- Sobreposicoes temporais dentro da mesma OM/aeronave: 0.
|
||||
|
||||
## Pendencias encontradas
|
||||
|
||||
### 1. Trecho consolidado com pousos zero
|
||||
|
||||
Trecho consolidado:
|
||||
|
||||
| Campo | Valor |
|
||||
| --- | --- |
|
||||
| ID | RV2025_OM219_E4_L1108_C1109 |
|
||||
| Linhas originais | 1108;1109 |
|
||||
| Data | 01/09 |
|
||||
| OM | 219 |
|
||||
| Aeronave | 2809 |
|
||||
| Trecho | SBCG -> SBCG |
|
||||
| Tempo de voo | 01:50 |
|
||||
| Pousos | 0 |
|
||||
| Missao | 50TT04;50TT05 |
|
||||
| Periodo | NOTURNO |
|
||||
|
||||
Classificacao: verificar se o pouso ficou ausente por estar dividido pela transicao `ROTA` em 00Z.
|
||||
|
||||
### 2. Quebras de continuidade espacial
|
||||
|
||||
Foram encontradas 3 quebras de continuidade espacial dentro da mesma OM/aeronave, sem sobreposicao temporal.
|
||||
|
||||
| OM | Aeronave | Trecho anterior | Trecho seguinte | Hipotese |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 7 | 2809 | SBMN -> SBBV | SBMN -> SBBV | Provavel erro ou duplicidade de origem; nao ha retorno registrado entre as duas etapas. |
|
||||
| 55 | 2803 | SBUA -> SWMK | SWMQ -> SBUA | Provavel divergencia de digitacao entre `SWMK` e `SWMQ`. |
|
||||
| 199 | 2800 | SBBR -> SBBR | SBGL -> SBAN | Lacuna operacional ou etapa ausente entre Brasilia e Galeao. |
|
||||
|
||||
## Decisao de validacao
|
||||
|
||||
O arquivo consolidado esta tecnicamente consistente para um primeiro uso no modelo OAMRP, desde que as 3 quebras espaciais sejam tratadas como excecoes conhecidas ou filtradas em uma rodada inicial.
|
||||
|
||||
Recomendacao para a primeira simulacao:
|
||||
|
||||
- Usar `db/pre_process/registro_voo_2025/missoes_oamrp_2025_consolidado.csv`.
|
||||
- Comecar por janeiro/2025, pois o periodo contem apenas a quebra da OM 7 entre as pendencias de continuidade listadas.
|
||||
- Manter as pendencias acima sem correcao automatica ate confirmacao humana.
|
||||
1421
db/processed/registro_voo_2025.csv
Normal file
1421
db/processed/registro_voo_2025.csv
Normal file
File diff suppressed because it is too large
Load Diff
1268
db/processed/registro_voo_2025_consolidado.csv
Normal file
1268
db/processed/registro_voo_2025_consolidado.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
db/raw/Planilha 2025.xlsx
Normal file
BIN
db/raw/Planilha 2025.xlsx
Normal file
Binary file not shown.
Reference in New Issue
Block a user