Compare commits
4 Commits
32ad7c9f23
...
11821eaa93
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11821eaa93 | ||
|
|
8d7dd07b18 | ||
|
|
f570091e19 | ||
|
|
1131267ea2 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,3 +9,7 @@ Desktop.ini
|
||||
# Local editor folders
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Python cache
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
37
CONTEXTO.md
37
CONTEXTO.md
@@ -2,13 +2,14 @@
|
||||
|
||||
## Objetivo
|
||||
|
||||
Organizar, preservar e processar documentos relacionados ao projeto `arara_oarmp`, mantendo rastreabilidade das alterações e separando arquivos originais, intermediários e processados.
|
||||
Organizar, preservar e processar documentos relacionados ao projeto Arara OAMRP, mantendo rastreabilidade das alterações e separando arquivos originais, intermediários, processados e modelos de software.
|
||||
|
||||
## Estrutura de Pastas
|
||||
|
||||
- `raw/`: arquivos originais, sem alteração.
|
||||
- `pre_process/`: scripts e saídas intermediárias de pré-processamento.
|
||||
- `processed/`: dados finais limpos, consolidados ou prontos para análise.
|
||||
- `db/raw/`: arquivos originais, sem alteração.
|
||||
- `db/pre_process/`: scripts e saídas intermediárias de pré-processamento.
|
||||
- `db/processed/`: dados finais limpos, consolidados ou prontos para análise.
|
||||
- `software/`: modelos, scripts analíticos e protótipos executáveis do OAMRP.
|
||||
- `docs/`: documentação técnica, autoria e histórico de versões.
|
||||
|
||||
## Arquivos de Documentação
|
||||
@@ -28,8 +29,9 @@ Organizar, preservar e processar documentos relacionados ao projeto `arara_oarmp
|
||||
|
||||
## Orientações de Trabalho
|
||||
|
||||
- Manter documentos originais sempre em `raw/`.
|
||||
- Não alterar arquivos dentro de `raw/`; quando necessário, gerar cópias ou saídas em `pre_process/`.
|
||||
- Manter documentos originais sempre em `db/raw/`.
|
||||
- Não alterar arquivos dentro de `db/raw/`; quando necessário, gerar cópias ou saídas em `db/pre_process/`.
|
||||
- Manter modelos e protótipos executáveis em `software/`.
|
||||
- Registrar no `LOG.md` toda entrada, alteração, processamento ou decisão relevante.
|
||||
- Registrar em `docs/changelog.md` mudanças significativas de versão, como novo parser, novo conjunto de dados, mudança estrutural ou nova funcionalidade.
|
||||
- Usar o formato de log com data, hora, tag do colaborador e descrição objetiva da ação.
|
||||
@@ -38,13 +40,32 @@ Organizar, preservar e processar documentos relacionados ao projeto `arara_oarmp
|
||||
- Registrar quantidade de registros extraídos, descartados, corrigidos ou validados.
|
||||
- Registrar problemas encontrados nos arquivos originais, como erros de digitação, páginas ilegíveis, campos ausentes ou tabelas quebradas.
|
||||
- Registrar decisões de padronização, por exemplo nomes de colunas, unidades, formatos de data e tratamento de acentos.
|
||||
- Explicitar os critérios usados para mover dados de `pre_process/` para `processed/`.
|
||||
- Explicitar os critérios usados para mover dados de `db/pre_process/` para `db/processed/`.
|
||||
- Atualizar `docs/about.md` quando o fluxo técnico, campos extraídos ou limitações conhecidas mudarem.
|
||||
|
||||
## Checklist de Atualização Documental
|
||||
|
||||
Antes de fechar uma mudança relevante ou criar um commit, verificar se algum destes arquivos precisa ser atualizado:
|
||||
|
||||
- `LOG.md`: atualizar em toda entrada de documento, alteração de dados, execução de script, processamento, decisão técnica ou mudança estrutural relevante.
|
||||
- `docs/changelog.md`: atualizar quando a mudança representar nova versão ou marco do projeto, como novo parser, novo modelo, novo conjunto de dados, alteração de estrutura, nova funcionalidade ou correção importante.
|
||||
- `README.md`: atualizar quando mudar a forma de usar o projeto, a estrutura de pastas, comandos principais, estado atual, dependências esperadas ou orientação para colaboradores.
|
||||
- `docs/about.md`: atualizar quando mudar o fluxo técnico, arquitetura, campos extraídos, modelo de dados, limitações conhecidas, fontes de dados ou oportunidades de melhoria.
|
||||
- `docs/authors.md`: atualizar quando houver novo colaborador, mudança de papel, nova contribuição atribuída ou nova versão registrada no changelog.
|
||||
- `CONTEXTO.md`: atualizar quando mudar regra permanente de trabalho, convenção de pastas, política de documentação, padrão de nomenclatura ou combinado do projeto.
|
||||
- `.gitignore`: atualizar quando surgirem novos arquivos temporários, caches, saídas locais ou artefatos gerados que não devem ser versionados.
|
||||
|
||||
Critério prático:
|
||||
|
||||
- Mudança operacional ou decisão do dia: registrar no `LOG.md`.
|
||||
- Mudança significativa para quem usa ou entende o projeto: registrar também em `docs/changelog.md`.
|
||||
- Mudança que altera como trabalhar no repositório: atualizar `README.md` e, se for regra permanente, `CONTEXTO.md`.
|
||||
- Mudança técnica interna: atualizar `docs/about.md`.
|
||||
|
||||
## Formato Recomendado do Log
|
||||
|
||||
```text
|
||||
| Data | Hora | Autor | Ação | Arquivos | Observações |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-06-15 | 15:41 | VTO | Pré-processou PDF de inspeções | raw/documento.pdf; pre_process/saida.csv | 18 inspeções extraídas |
|
||||
| 2026-06-15 | 15:41 | VTO | Pré-processou PDF de inspeções | db/raw/documento.pdf; db/pre_process/saida.csv | 18 inspeções extraídas |
|
||||
```
|
||||
|
||||
5
LOG.md
5
LOG.md
@@ -21,3 +21,8 @@ Legenda de autores:
|
||||
| 2026-06-15 | 17:57 | VTO | Criou documentação técnica no padrão `docs/` | `docs/about.md`; `docs/authors.md`; `docs/changelog.md`; `README.md`; `CONTEXTO.md`; `LOG.md` | Estrutura inspirada no módulo `meteorologia_aeroportos`: documentação técnica, autoria e changelog separados do log operacional. |
|
||||
| 2026-06-15 | 18:11 | VTO | Agrupou artefatos do relatório de ciclo em subpasta própria | `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/`; `README.md`; `docs/about.md` | Removido `.gitkeep` de `pre_process/` porque a pasta deixou de estar vazia; `.gitkeep` de `processed/` foi mantido. |
|
||||
| 2026-06-15 | 18:33 | VTO | Comparou ICA 66-31/2023 com relatório de ciclo C-105 2805 | `raw/ICA 66-31 2023.pdf`; `pre_process/comparacao_ica_66_31_c105_2023/` | A comparação usou o Anexo D textual do PDF completo da ICA. |
|
||||
| 2026-06-15 | 19:01 | VTO | Reorganizou dados em `db/` e adicionou protótipo OAMRP | `db/`; `software/oamrp_v1.py`; `.gitignore`; `README.md`; `CONTEXTO.md`; `docs/about.md`; `docs/authors.md`; `docs/changelog.md` | Commit `1131267`; diretórios `raw/`, `pre_process/` e `processed/` movidos para `db/`; modelo MIP inicial em PuLP adicionado em `software/`. |
|
||||
| 2026-06-15 | 19:56 | VTO | Pre-processou a Planilha 2025 para registro de voo | `db/raw/Planilha 2025.xlsx`; `db/pre_process/registro_voo_2025/`; `db/processed/registro_voo_2025.csv` | Extraidos 1421 registros; colunas `REAL` ate `COM2` descartadas; 53 linhas cruzam 00Z; 1 registro sem OM e 9 sem pousos preservados para revisao. |
|
||||
| 2026-06-15 | 19:56 | VTO | Consolidou transicoes `ROTA` do registro de voo 2025 | `db/pre_process/registro_voo_2025/preprocess_planilha_2025.py`; `db/processed/registro_voo_2025_consolidado.csv` | `ROTA` tratado como marcador de transicao; 1421 linhas originais viraram 1268 trechos consolidados; 135 trechos com `ROTA` foram convertidos para origem/destino reais. |
|
||||
| 2026-06-15 | 19:56 | VTO | Validou o registro de voo 2025 consolidado | `db/pre_process/registro_voo_2025/validacao_registro_voo_2025.md`; `db/processed/registro_voo_2025_consolidado.csv` | Sem duplicidades, sem divergencias de duracao e sem sobreposicoes temporais; pendentes 1 registro sem OM, 1 trecho com pousos zero e 3 quebras de continuidade espacial. |
|
||||
| 2026-06-15 | 19:56 | VTO | Removeu registro isolado da aeronave 2806 | `db/pre_process/registro_voo_2025/preprocess_planilha_2025.py`; `db/processed/registro_voo_2025.csv`; `db/processed/registro_voo_2025_consolidado.csv` | Linha original 793 excluida dos artefatos processados; registro final passou para 1420 linhas e consolidado para 1267 trechos; nao ha mais registro sem OM. |
|
||||
|
||||
66
README.md
66
README.md
@@ -1,6 +1,6 @@
|
||||
# Arara OARMP
|
||||
# Arara OAMRP
|
||||
|
||||
Este repositório organiza os arquivos e processamentos do projeto Arara OARMP do Esquadrão Arara, em Manaus.
|
||||
Este repositório organiza os arquivos, processamentos e protótipos do projeto Arara OAMRP do Esquadrão Arara, em Manaus.
|
||||
|
||||
A ideia é construir, em conjunto, uma base confiável para encaixar missões, aeronaves e manutenções. O repositório serve como lugar único para guardar documentos originais, registrar decisões, transformar relatórios em dados utilizáveis e manter um histórico claro do que foi feito por cada colaborador.
|
||||
|
||||
@@ -21,18 +21,21 @@ Na prática:
|
||||
|
||||
```text
|
||||
arara_oarmp/
|
||||
raw/
|
||||
pre_process/
|
||||
processed/
|
||||
db/
|
||||
raw/
|
||||
pre_process/
|
||||
processed/
|
||||
software/
|
||||
docs/
|
||||
CONTEXTO.md
|
||||
LOG.md
|
||||
README.md
|
||||
```
|
||||
|
||||
- `raw/`: documentos originais. Não editar esses arquivos diretamente.
|
||||
- `pre_process/`: scripts e arquivos intermediários gerados a partir dos documentos originais.
|
||||
- `processed/`: arquivos finais, limpos e prontos para análise ou uso.
|
||||
- `db/raw/`: documentos originais. Não editar esses arquivos diretamente.
|
||||
- `db/pre_process/`: scripts e arquivos intermediários gerados a partir dos documentos originais.
|
||||
- `db/processed/`: arquivos finais, limpos e prontos para análise ou uso.
|
||||
- `software/`: modelos e protótipos executáveis, incluindo o modelo MIP inicial de roteamento.
|
||||
- `docs/`: documentação técnica, autoria e histórico de versões.
|
||||
- `CONTEXTO.md`: regras, combinados e orientações permanentes do projeto.
|
||||
- `LOG.md`: histórico rastreável das ações feitas no projeto.
|
||||
@@ -54,9 +57,9 @@ Este caminho é bom para quem só quer ver arquivos, baixar documentos ou confer
|
||||
|
||||
1. Abra o repositório no Gitea do PPGAO/ITA.
|
||||
2. Entre nas pastas para ver os arquivos.
|
||||
3. Use `raw/` para consultar documentos originais.
|
||||
4. Use `pre_process/` para consultar arquivos intermediários.
|
||||
5. Use `processed/` para consultar dados finais quando existirem.
|
||||
3. Use `db/raw/` para consultar documentos originais.
|
||||
4. Use `db/pre_process/` para consultar arquivos intermediários.
|
||||
5. Use `db/processed/` para consultar dados finais quando existirem.
|
||||
6. Abra o `LOG.md` para entender o que já foi feito.
|
||||
7. Abra o `CONTEXTO.md` para ver os combinados do projeto.
|
||||
|
||||
@@ -125,16 +128,16 @@ git push
|
||||
## Fluxo recomendado de trabalho
|
||||
|
||||
1. Rode `git pull` antes de começar.
|
||||
2. Coloque documentos novos em `raw/`.
|
||||
3. Gere arquivos intermediários em `pre_process/`.
|
||||
4. Coloque resultados finais em `processed/`.
|
||||
2. Coloque documentos novos em `db/raw/`.
|
||||
3. Gere arquivos intermediários em `db/pre_process/`.
|
||||
4. Coloque resultados finais em `db/processed/`.
|
||||
5. Atualize o `LOG.md`.
|
||||
6. Faça commit.
|
||||
7. Rode `git push`.
|
||||
|
||||
## Cuidados importantes
|
||||
|
||||
- Não editar arquivos originais dentro de `raw/`.
|
||||
- Não editar arquivos originais dentro de `db/raw/`.
|
||||
- Não apagar arquivos de outros colaboradores sem combinar.
|
||||
- Não subir arquivos temporários do Office, como arquivos começando com `~$`.
|
||||
- Não colocar senhas, tokens ou informações pessoais desnecessárias no repositório.
|
||||
@@ -145,14 +148,37 @@ git push
|
||||
|
||||
Já existe um pré-processamento do relatório de ciclo de inspeções da aeronave C-105 matrícula 2805, com data de relatório `2026-06-15`.
|
||||
|
||||
Também existe um primeiro protótipo em `software/oamrp_v1.py`, que formula um modelo MIP compacto em PuLP para atribuir aeronaves a missões de transporte, respeitando continuidade espacial/temporal, turnaround mínimo e limite de horas até inspeção.
|
||||
|
||||
Arquivos principais:
|
||||
|
||||
```text
|
||||
raw/relatorio_ciclo_inspecoes_c105_2805_2026-06-15.pdf
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/preprocess_pdf.py
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_texto.txt
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv
|
||||
db/raw/relatorio_ciclo_inspecoes_c105_2805_2026-06-15.pdf
|
||||
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/preprocess_pdf.py
|
||||
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_texto.txt
|
||||
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json
|
||||
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv
|
||||
software/oamrp_v1.py
|
||||
```
|
||||
|
||||
Esse processamento extraiu 18 inspeções e separou campos como inspeção, referência, vencimento, TSO, letra, nível, duração, intervalo por horas de voo, meses contínuos e pousos.
|
||||
## Atualizacao - registro de voo 2025
|
||||
|
||||
A `Planilha 2025.xlsx` foi pre-processada a partir de `db/raw/` sem alterar o arquivo original.
|
||||
|
||||
Arquivos gerados:
|
||||
|
||||
```text
|
||||
db/pre_process/registro_voo_2025/preprocess_planilha_2025.py
|
||||
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.csv
|
||||
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.xlsx
|
||||
db/pre_process/registro_voo_2025/registro_voo_2025_consolidado.csv
|
||||
db/pre_process/registro_voo_2025/missoes_oamrp_2025.csv
|
||||
db/pre_process/registro_voo_2025/missoes_oamrp_2025_consolidado.csv
|
||||
db/pre_process/registro_voo_2025/resumo_registro_voo_2025.json
|
||||
db/pre_process/registro_voo_2025/validacao_registro_voo_2025.md
|
||||
db/processed/registro_voo_2025.csv
|
||||
db/processed/registro_voo_2025_consolidado.csv
|
||||
```
|
||||
|
||||
O consolidado trata `ROTA` como marcador de transicao, nao como localidade operacional, e remove a linha original 793 da aeronave 2806 dos artefatos processados.
|
||||
|
||||
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.
@@ -1,16 +1,17 @@
|
||||
# Documentação Técnica — Arara OARMP
|
||||
# Documentação Técnica — Arara OAMRP
|
||||
|
||||
## O que é
|
||||
|
||||
O Arara OARMP é um projeto para organizar dados e documentos usados no planejamento e acompanhamento operacional do Esquadrão Arara, em Manaus.
|
||||
O Arara OAMRP é um projeto para organizar dados, documentos e modelos usados no planejamento e acompanhamento operacional do Esquadrão Arara, em Manaus.
|
||||
|
||||
O objetivo é construir uma base rastreável para encaixar missões, aeronaves e manutenções, começando pela leitura e estruturação de documentos operacionais e de manutenção.
|
||||
|
||||
Nesta fase inicial, o projeto contém:
|
||||
|
||||
- documentos originais em `raw/`;
|
||||
- scripts e saídas intermediárias em `pre_process/`;
|
||||
- área reservada para dados finais em `processed/`;
|
||||
- documentos originais em `db/raw/`;
|
||||
- scripts e saídas intermediárias em `db/pre_process/`;
|
||||
- área reservada para dados finais em `db/processed/`;
|
||||
- protótipos de otimização e análise em `software/`;
|
||||
- documentação de contexto, autoria, mudanças e rastreabilidade.
|
||||
|
||||
## Fontes de dados
|
||||
@@ -21,32 +22,44 @@ Fonte inicial processada:
|
||||
|
||||
| Arquivo | Conteúdo | Observação |
|
||||
| --- | --- | --- |
|
||||
| `raw/relatorio_ciclo_inspecoes_c105_2805_2026-06-15.pdf` | Relatório de ciclo de inspeções do equipamento | Aeronave C-105, matrícula 2805, relatório de 2026-06-15 |
|
||||
| `db/raw/relatorio_ciclo_inspecoes_c105_2805_2026-06-15.pdf` | Relatório de ciclo de inspeções do equipamento | Aeronave C-105, matrícula 2805, relatório de 2026-06-15 |
|
||||
|
||||
Arquivos originais devem permanecer em `raw/` sem edição direta.
|
||||
Arquivos originais devem permanecer em `db/raw/` sem edição direta.
|
||||
|
||||
## Como funciona
|
||||
|
||||
O fluxo atual é:
|
||||
|
||||
1. Guardar documentos originais em `raw/`.
|
||||
2. Executar scripts de extração e padronização em `pre_process/`.
|
||||
1. Guardar documentos originais em `db/raw/`.
|
||||
2. Executar scripts de extração e padronização em `db/pre_process/`.
|
||||
3. Gerar saídas intermediárias em texto, JSON e CSV.
|
||||
4. Manter campos originais para rastreabilidade.
|
||||
5. Separar campos discretos úteis para cálculo, comparação e planejamento.
|
||||
6. Registrar mudanças relevantes em `LOG.md`.
|
||||
7. Consolidar dados finais em `processed/` quando os critérios de qualidade estiverem definidos.
|
||||
7. Consolidar dados finais em `db/processed/` quando os critérios de qualidade estiverem definidos.
|
||||
8. Desenvolver protótipos e modelos em `software/`, mantendo dados e código separados.
|
||||
|
||||
## Pré-processamento atual
|
||||
|
||||
O script `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/preprocess_pdf.py` lê o relatório de ciclo de inspeções em PDF e gera:
|
||||
O script `db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/preprocess_pdf.py` lê o relatório de ciclo de inspeções em PDF e gera:
|
||||
|
||||
```text
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_texto.txt
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv
|
||||
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_texto.txt
|
||||
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json
|
||||
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv
|
||||
```
|
||||
|
||||
## Modelo OAMRP inicial
|
||||
|
||||
O arquivo `software/oamrp_v1.py` contém um protótipo de modelo MIP em PuLP para o Aircraft Routing do Esquadrão Arara. O modelo usa dados sintéticos/substituíveis de aeronaves e missões e busca maximizar o cumprimento ponderado de missões opcionais.
|
||||
|
||||
Restrições representadas no protótipo:
|
||||
|
||||
- continuidade espacial e temporal por aeronave;
|
||||
- turnaround mínimo entre missões;
|
||||
- limite de horas de célula até a próxima inspeção;
|
||||
- base inicial e horas acumuladas reais de cada aeronave como parâmetros de entrada.
|
||||
|
||||
Campos extraídos e estruturados:
|
||||
|
||||
| Campo | Conteúdo |
|
||||
@@ -97,13 +110,14 @@ Unidades de duração:
|
||||
3. Mudanças no formato do relatório podem exigir ajuste no script.
|
||||
4. A grafia original é preservada, inclusive erros existentes no documento fonte.
|
||||
5. Ainda não há validação cruzada com outros sistemas ou fontes oficiais.
|
||||
6. Ainda não há modelo final em `processed/` para integração completa entre missões, aeronaves e manutenções.
|
||||
6. O modelo em `software/oamrp_v1.py` ainda é protótipo e usa dados de exemplo a substituir por bases reais.
|
||||
7. Ainda não há modelo final em `db/processed/` para integração completa entre missões, aeronaves e manutenções.
|
||||
|
||||
## Oportunidades de melhoria
|
||||
|
||||
| Prioridade | Melhoria |
|
||||
| --- | --- |
|
||||
| Alta | Definir o modelo final de dados em `processed/` |
|
||||
| Alta | Definir o modelo final de dados em `db/processed/` |
|
||||
| Alta | Criar uma tabela mestre de aeronaves |
|
||||
| Alta | Criar uma tabela mestre de missões |
|
||||
| Alta | Definir regras para cálculo de vencimento por horas, meses e pousos |
|
||||
@@ -116,7 +130,7 @@ Unidades de duração:
|
||||
|
||||
Se o pré-processamento falhar, verifique:
|
||||
|
||||
- se o PDF existe em `raw/`;
|
||||
- se o PDF existe em `db/raw/`;
|
||||
- se o nome do arquivo no script corresponde ao arquivo real;
|
||||
- se as bibliotecas Python necessárias estão instaladas;
|
||||
- se o layout do PDF mudou;
|
||||
@@ -125,5 +139,36 @@ Se o pré-processamento falhar, verifique:
|
||||
Para reexecutar o pré-processamento:
|
||||
|
||||
```powershell
|
||||
python pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/preprocess_pdf.py
|
||||
python db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/preprocess_pdf.py
|
||||
```
|
||||
## Registro de voo 2025
|
||||
|
||||
O arquivo `db/raw/Planilha 2025.xlsx` foi pre-processado por `db/pre_process/registro_voo_2025/preprocess_planilha_2025.py`.
|
||||
|
||||
O processamento gera duas visoes principais:
|
||||
|
||||
- `db/processed/registro_voo_2025.csv`: registro limpo, preservando a granularidade das linhas originais relevantes.
|
||||
- `db/processed/registro_voo_2025_consolidado.csv`: registro consolidado para roteamento, com `ROTA` tratado como marcador de transicao.
|
||||
|
||||
Tambem sao gerados CSVs no formato esperado pelo prototipo OAMRP:
|
||||
|
||||
```text
|
||||
db/pre_process/registro_voo_2025/missoes_oamrp_2025.csv
|
||||
db/pre_process/registro_voo_2025/missoes_oamrp_2025_consolidado.csv
|
||||
```
|
||||
|
||||
Criterios aplicados:
|
||||
|
||||
- colunas entre `REAL` e `COM2` descartadas;
|
||||
- datas de 2025 normalizadas para formato ISO e `dd/mm`;
|
||||
- horarios tratados como UTC/Z;
|
||||
- chegadas iguais ou anteriores a partida deslocadas para o dia seguinte;
|
||||
- transicoes com `ROTA` consolidadas em origem/destino reais;
|
||||
- linha original 793, aeronave 2806, excluida dos artefatos processados;
|
||||
- `SEGMTO` preservado como campo original, sem recalculo automatico.
|
||||
|
||||
O relatorio de validacao esta em:
|
||||
|
||||
```text
|
||||
db/pre_process/registro_voo_2025/validacao_registro_voo_2025.md
|
||||
```
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
|
||||
## Instituição e contexto
|
||||
|
||||
Projeto desenvolvido para apoiar o OARMP do Esquadrão Arara, em Manaus, com organização colaborativa em repositório Git.
|
||||
Projeto desenvolvido para apoiar o OAMRP do Esquadrão Arara, em Manaus, com organização colaborativa em repositório Git.
|
||||
|
||||
## Como contribuir
|
||||
|
||||
Ao modificar ou estender este projeto:
|
||||
|
||||
1. Atualize sua cópia com `git pull` antes de começar.
|
||||
2. Preserve documentos originais em `raw/`.
|
||||
3. Coloque scripts e saídas intermediárias em `pre_process/`.
|
||||
4. Coloque resultados finais e validados em `processed/`.
|
||||
2. Preserve documentos originais em `db/raw/`.
|
||||
3. Coloque scripts e saídas intermediárias em `db/pre_process/`.
|
||||
4. Coloque resultados finais e validados em `db/processed/`.
|
||||
5. Registre mudanças operacionais em `LOG.md`.
|
||||
6. Registre mudanças de versão em `docs/changelog.md` quando houver alteração significativa.
|
||||
7. Identifique o autor usando a tag do colaborador: `VTO`, `GNR` ou `JOM`.
|
||||
@@ -39,6 +39,7 @@ Ao modificar ou estender este projeto:
|
||||
|
||||
| Versão | Autor | Data | Descrição resumida |
|
||||
| --- | --- | --- | --- |
|
||||
| v0.2 | VTO | 2026-06 | Reorganização dos dados em `db/`, criação de `software/` e inclusão do protótipo MIP `oamrp_v1.py` |
|
||||
| v0.1 | VTO | 2026-06 | Estrutura inicial, documentação, pré-processamento do primeiro relatório de inspeções e migração para o repositório `arara_oarmp` |
|
||||
|
||||
Contribuidores futuros devem adicionar uma linha nesta tabela e criar a entrada correspondente em `docs/changelog.md`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
Todas as versões significativas do Arara OARMP devem ser registradas aqui. O `LOG.md` guarda o diário operacional detalhado; este arquivo resume mudanças por versão.
|
||||
Todas as versões significativas do Arara OAMRP devem ser registradas aqui. O `LOG.md` guarda o diário operacional detalhado; este arquivo resume mudanças por versão.
|
||||
|
||||
Formato:
|
||||
|
||||
@@ -14,6 +14,22 @@ Formato:
|
||||
|
||||
---
|
||||
|
||||
## v0.2 — VTO — 2026-06
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Diretório `software/` com o primeiro protótipo `software/oamrp_v1.py`.
|
||||
- Modelo MIP compacto em PuLP para atribuição de aeronaves a missões opcionais.
|
||||
- Regras de `.gitignore` para caches Python.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Estrutura de dados reorganizada para `db/raw/`, `db/pre_process/` e `db/processed/`.
|
||||
- Documentação atualizada para refletir a separação entre dados (`db/`) e modelos (`software/`).
|
||||
- Nome técnico do projeto padronizado como Arara OAMRP nos documentos principais.
|
||||
|
||||
---
|
||||
|
||||
## v0.1 — VTO — 2026-06
|
||||
|
||||
### Adicionado
|
||||
@@ -43,3 +59,30 @@ Formato:
|
||||
|
||||
- O parser atual foi calibrado para o primeiro PDF processado.
|
||||
- A grafia original do documento fonte foi preservada, inclusive ocorrências de `INPEÇÃO`.
|
||||
## v0.3 - VTO - 2026-06
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Pre-processamento da `Planilha 2025.xlsx` para registro de voo limpo.
|
||||
- Saidas em CSV/XLSX sem mesclas e CSV OAMRP com `id,orig,dest,partida_utc,chegada_utc,prioridade`.
|
||||
- Resumo JSON com contagens, campos faltantes e criterios de conversao.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Criado arquivo final `db/processed/registro_voo_2025.csv`.
|
||||
|
||||
### Observacoes
|
||||
|
||||
- `SEGMTO` foi preservado da planilha original; cruzamento de 00Z foi registrado em campo separado.
|
||||
- Colunas entre `REAL` e `COM2` foram descartadas.
|
||||
## v0.3.1 - VTO - 2026-06
|
||||
|
||||
### Alterado
|
||||
|
||||
- Registro de voo 2025 regenerado removendo a linha original 793, aeronave 2806.
|
||||
- Consolidado passou para 1267 trechos e registro limpo para 1420 linhas.
|
||||
- Documentacao atualizada em `README.md`, `docs/about.md`, `LOG.md` e relatorio de validacao.
|
||||
|
||||
### Observacoes
|
||||
|
||||
- A remocao foi aplicada no script de pre-processamento para manter reprodutibilidade.
|
||||
|
||||
102
docs/roteiro_apresentacao_oamrp.doc
Normal file
102
docs/roteiro_apresentacao_oamrp.doc
Normal file
@@ -0,0 +1,102 @@
|
||||
{\rtf1\ansi\deff0
|
||||
{\fonttbl{\f0 Arial;}{\f1 Courier New;}}
|
||||
\fs24
|
||||
\b Roteiro para Apresentacao PowerPoint - OAMRP v1\b0\par
|
||||
\par
|
||||
\b Tema:\b0 Aircraft Routing - Esquadrao Arara (Transporte C-95/C-105)\par
|
||||
\b Objetivo da apresentacao:\b0 apresentar uma primeira ideia de modelo matematico/computacional para apoiar a atribuicao de aeronaves a missoes de transporte, considerando prioridade operacional, continuidade de rota, tempo minimo em solo e limite de horas ate inspecao.\par
|
||||
\par
|
||||
\b Ideia inicial 1 - Modelo MIP compacto para atribuicao de aeronaves\b0\par
|
||||
\par
|
||||
Esta primeira ideia usa um modelo de programacao inteira mista (MIP) para escolher quais missoes serao cumpridas e por qual aeronave. Nesta versao, Manaus (MN) foi definida como base operacional do Esquadrao Arara, e as missoes simuladas partem de MN para outras localidades representativas da malha da FAB. As missoes sao informadas com data e hora UTC, como em uma planilha operacional, e o codigo converte internamente para horas decimais no horizonte de planejamento. O foco nao e minimizar custo, como em modelos civis tradicionais, mas maximizar o cumprimento ponderado de missoes, dando maior peso as missoes de maior prioridade.\par
|
||||
\par
|
||||
O modelo trata cada missao como opcional. Isso significa que uma missao pode ficar de fora quando nao houver aeronave disponivel, quando a conexao temporal/geografica nao for viavel, ou quando o limite de horas de celula ate a proxima inspecao impedir a execucao segura da rota.\par
|
||||
\par
|
||||
\b Estrutura sugerida dos slides\b0\par
|
||||
\par
|
||||
\b Slide 1 - Titulo\b0\par
|
||||
Titulo sugerido: Otimizacao de Rotas e Atribuicao de Aeronaves no Esquadrao Arara.\par
|
||||
Fala sugerida: Esta apresentacao mostra uma primeira proposta de ferramenta de apoio a decisao para distribuir aeronaves C-95/C-105 entre missoes de transporte, respeitando restricoes operacionais e de manutencao.\par
|
||||
\par
|
||||
\b Slide 2 - Problema operacional\b0\par
|
||||
Mensagem central: o esquadrao precisa decidir rapidamente quais aeronaves atendem quais missoes, mantendo coerencia de rota, disponibilidade temporal e margem de horas antes da inspecao.\par
|
||||
Pontos para apresentar:\par
|
||||
- Ha varias missoes com origens, destinos, horarios e prioridades diferentes.\par
|
||||
- Cada aeronave parte de Manaus (MN), base operacional do Arara nesta simulacao, e possui horas acumuladas desde a ultima inspecao.\par
|
||||
- Nem toda combinacao de missoes e viavel para uma mesma aeronave.\par
|
||||
\par
|
||||
\b Slide 3 - Objetivo do modelo\b0\par
|
||||
Mensagem central: maximizar o valor operacional das missoes cumpridas e, em segundo nivel, organizar o uso do LRT para apoiar a manutencao.\par
|
||||
Fala sugerida: Em vez de obrigar que todas as missoes sejam atendidas, o modelo escolhe o melhor conjunto possivel. Missoes mais importantes recebem maior peso. Depois de encontrar a melhor prioridade operacional, o modelo escolhe a distribuicao que reduz o LRT restante ponderado, priorizando aeronaves mais proximas da manutencao.\par
|
||||
\par
|
||||
\b Slide 4 - Entradas do modelo\b0\par
|
||||
Entradas usadas na versao inicial:\par
|
||||
- Base operacional simulada: MN, Manaus.\par
|
||||
- Base habilitada a inspecao: MN, Manaus.\par
|
||||
- Turnaround minimo: 1 hora.\par
|
||||
- Limite de horas ate inspecao obrigatoria: 100 horas.\par
|
||||
- Aeronaves: FAB-2301, FAB-2302, FAB-2303 e FAB-2304.\par
|
||||
- Missoes: 20 missoes simuladas entre Manaus e outras localidades, incluindo BR, BE, PV, BV, GL, CG, RF e NT.\par
|
||||
- Horarios das missoes: entrada em data/hora UTC, com possibilidade de carregar CSV exportado de planilha.\par
|
||||
\par
|
||||
\b Slide 5 - Restricoes consideradas\b0\par
|
||||
Restricoes principais:\par
|
||||
- Cada missao pode ser cumprida por no maximo uma aeronave.\par
|
||||
- A aeronave so pode iniciar uma rota em uma missao que parte de sua base inicial.\par
|
||||
- A continuidade da rota exige que o destino de uma missao seja a origem da proxima.\par
|
||||
- Deve haver turnaround minimo entre chegada e nova partida.\par
|
||||
- A soma das horas voadas por aeronave nao pode ultrapassar o LRT disponivel.\par
|
||||
\par
|
||||
\b Slide 6 - Resultado da execucao inicial\b0\par
|
||||
Resultado obtido ao rodar o arquivo software/oamrp_v1.py:\par
|
||||
\par
|
||||
{\f1
|
||||
STATUS: Optimal\par
|
||||
Missoes cumpridas: 20/20\par
|
||||
Prioridade obtida: 74/74\par
|
||||
Todas as missoes foram atendidas\par
|
||||
Horizonte inicia em UTC: 2026-07-01T06:00:00Z\par
|
||||
LRT restante total: 124h\par
|
||||
Criterio secundario: menor LRT restante ponderado para manutencao\par
|
||||
}\f0
|
||||
\par
|
||||
Fala sugerida: Com os dados de exemplo, o solver encontrou uma solucao otima. As 20 missoes simuladas foram cumpridas, com aproveitamento integral da prioridade operacional planejada. Em seguida, a solucao foi refinada para usar melhor o LRT das aeronaves mais proximas da manutencao.\par
|
||||
\par
|
||||
\b Slide 7 - Distribuicao das aeronaves\b0\par
|
||||
Rotas encontradas:\par
|
||||
\par
|
||||
{\f1
|
||||
FAB-2301: M07 -> M08\par
|
||||
FAB-2302: M01 -> M02 -> M11 -> M12 -> M13 -> M14 -> M19 -> M20\par
|
||||
FAB-2303: M05 -> M06 -> M09 -> M10 -> M15 -> M16 -> M17 -> M18\par
|
||||
FAB-2304: M03 -> M04\par
|
||||
}\f0
|
||||
\par
|
||||
Fala sugerida: O resultado mostra uma sequencia coerente para cada aeronave. Cada rota respeita origem, destino, horarios e limite de horas disponiveis ate a inspecao.\par
|
||||
\par
|
||||
\b Slide 8 - Leitura operacional do resultado\b0\par
|
||||
Mensagem central: a malha simulada mostra que a frota consegue cumprir o pacote completo de missoes, mas algumas aeronaves ficam mais pressionadas pelo limite de horas.\par
|
||||
Pontos para apresentar:\par
|
||||
- A prioridade total possivel era 74.\par
|
||||
- A solucao atingiu 74 pontos de prioridade.\par
|
||||
- Todas as missoes foram cumpridas.\par
|
||||
- A FAB-2302 e a FAB-2304 ficaram com LRT restante igual a 0 hora, mostrando aeronaves prontas para entrar no ciclo de manutencao.\par
|
||||
\par
|
||||
\b Slide 9 - Valor para o planejamento\b0\par
|
||||
Beneficios da abordagem:\par
|
||||
- Apoia decisoes rapidas e rastreaveis.\par
|
||||
- Ajuda a visualizar gargalos de frota, horario e manutencao.\par
|
||||
- Permite testar cenarios com novas missoes, prioridades ou disponibilidade de aeronaves.\par
|
||||
- Reduz a dependencia de avaliacao manual quando ha muitas combinacoes possiveis.\par
|
||||
\par
|
||||
\b Slide 10 - Proximos passos\b0\par
|
||||
Evolucoes sugeridas:\par
|
||||
- Substituir os dados ficticios por dados reais do Esquadrao Arara.\par
|
||||
- Incluir pousos intermediarios, pernoites e janelas de disponibilidade de tripulacao.\par
|
||||
- Modelar inspecao com reset de horas em bases habilitadas.\par
|
||||
- Gerar graficos e tabelas automaticas para uso direto em briefing.\par
|
||||
- Comparar cenarios: frota completa, aeronave indisponivel, aumento de demanda ou restricao de base.\par
|
||||
\par
|
||||
\b Fechamento sugerido\b0\par
|
||||
Esta versao inicial demonstra que e possivel transformar o problema de atribuicao de aeronaves em um modelo de otimizacao explicavel. Mesmo com dados simples, o modelo ja produz uma solucao coerente, priorizada e aderente as restricoes principais de operacao e manutencao.\par
|
||||
}
|
||||
271
software/oamrp_v1.py
Normal file
271
software/oamrp_v1.py
Normal file
@@ -0,0 +1,271 @@
|
||||
"""
|
||||
============================================================================
|
||||
AIRCRAFT ROUTING - ESQUADRAO ARARA (Transporte C-95/C-105)
|
||||
============================================================================
|
||||
Modelo MIP compacto que ATRIBUI aeronaves a missoes de transporte,
|
||||
maximizando o numero (ponderado por prioridade) de missoes cumpridas,
|
||||
respeitando:
|
||||
- rota viavel (continuidade espacial e temporal por aeronave)
|
||||
- turnaround minimo entre missoes
|
||||
- limite de horas de celula ate a proxima inspecao (LRT)
|
||||
- base inicial e horas acumuladas reais de cada aeronave
|
||||
|
||||
Diferenca chave vs. modelo civil (Bazargan): aqui a cobertura e' "<= 1"
|
||||
(missao OPCIONAL, selecionada se compensar) e nao "= 1". O objetivo e'
|
||||
prontidao/cumprimento de missao, nao custo.
|
||||
|
||||
Solver: CBC (vem junto com o PuLP, zero instalacao). Para instancias
|
||||
maiores troque por HiGHS (ver nota no fim do arquivo).
|
||||
|
||||
Autor: gerado como ponto de partida. pt-BR nos comentarios, codigo limpo.
|
||||
============================================================================
|
||||
"""
|
||||
|
||||
import csv
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import pulp
|
||||
|
||||
# ===========================================================================
|
||||
# 1) DADOS DE ENTRADA (substitua pelos dados reais do ARARA)
|
||||
# ===========================================================================
|
||||
# Bases (codigos curtos ficticios). 'S' marca bases HABILITADAS a inspecao.
|
||||
# Na FAB o conjunto de bases habilitadas e' restrito -> isso aperta o routing.
|
||||
# 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
|
||||
# (AJUSTE ao intervalo real da sua frota)
|
||||
|
||||
# ----- Aeronaves (tail number, base inicial, horas acumuladas desde a ult. insp.)
|
||||
# 'horas_acum' = f_k. LRT inicial de cada aeronave = LIMITE_HORAS_F - f_k.
|
||||
AERONAVES = [
|
||||
# id base horas_acum
|
||||
("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
|
||||
# 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)
|
||||
# ===========================================================================
|
||||
miss = {m[0]: dict(orig=m[1], dest=m[2], part=m[3], cheg=m[4], prio=m[5],
|
||||
dur=m[4] - m[3]) for m in MISSOES}
|
||||
acft = {a[0]: dict(base=a[1], f=a[2]) for a in AERONAVES}
|
||||
IDS = list(miss.keys())
|
||||
KS = list(acft.keys())
|
||||
|
||||
# Conexao i->j valida: destino de i == origem de j e ha turnaround suficiente
|
||||
conex = [(i, j) for i in IDS for j in IDS
|
||||
if i != j
|
||||
and miss[i]["dest"] == miss[j]["orig"]
|
||||
and miss[j]["part"] >= miss[i]["cheg"] + TURNAROUND_MIN_H]
|
||||
|
||||
# Aeronave k pode INICIAR pela missao i se i parte da base inicial de k
|
||||
def pode_iniciar(k, i):
|
||||
return miss[i]["orig"] == acft[k]["base"]
|
||||
|
||||
# ===========================================================================
|
||||
# 3) MODELO MIP
|
||||
# ===========================================================================
|
||||
mdl = pulp.LpProblem("ARARA_Aircraft_Routing", pulp.LpMaximize)
|
||||
|
||||
# --- Variaveis
|
||||
x = {(k, i): pulp.LpVariable(f"x_{k}_{i}", cat="Binary") for k in KS for i in IDS}
|
||||
s = {(k, i): pulp.LpVariable(f"s_{k}_{i}", cat="Binary") # k inicia por i
|
||||
for k in KS for i in IDS if pode_iniciar(k, i)}
|
||||
c = {(k, i, j): pulp.LpVariable(f"c_{k}_{i}_{j}", cat="Binary") # k voa i depois j
|
||||
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 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
|
||||
for i in IDS:
|
||||
mdl += z[i] == pulp.lpSum(x[(k, i)] for k in KS), f"def_z_{i}"
|
||||
# z[i] ja e' binaria, entao automaticamente <= 1
|
||||
|
||||
# --- (R2) Fluxo de entrada: se k voa i, ele iniciou por i OU chegou de outra missao
|
||||
for k in KS:
|
||||
for i in IDS:
|
||||
entradas = pulp.lpSum(c[(k, jj, i)] for (jj, ii) in conex if ii == i)
|
||||
ini = s[(k, i)] if (k, i) in s else 0
|
||||
mdl += ini + entradas == x[(k, i)], f"in_{k}_{i}"
|
||||
|
||||
# --- (R3) Fluxo de saida: depois de i, k segue para no max uma proxima missao
|
||||
for k in KS:
|
||||
for i in IDS:
|
||||
saidas = pulp.lpSum(c[(k, i, jj)] for (ii, jj) in conex if ii == i)
|
||||
mdl += saidas <= x[(k, i)], f"out_{k}_{i}"
|
||||
|
||||
# --- (R4) Cada aeronave inicia no maximo uma rota
|
||||
for k in KS:
|
||||
inis = [s[(k, i)] for i in IDS if (k, i) in s]
|
||||
if inis:
|
||||
mdl += pulp.lpSum(inis) <= 1, f"start_{k}"
|
||||
|
||||
# --- (R5) Limite de horas de celula (LRT): total voado por k <= F - f_k
|
||||
# Esta e' a restricao CENTRAL de manutencao (versao "orcamento de horas").
|
||||
# -> A inspecao com RESET de horas em base habilitada e' a extensao da Fase 3.
|
||||
for k in KS:
|
||||
mdl += pulp.lpSum(miss[i]["dur"] * x[(k, i)] for i in IDS) <= \
|
||||
LIMITE_HORAS_F - acft[k]["f"], f"horas_{k}"
|
||||
|
||||
# ===========================================================================
|
||||
# 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)
|
||||
|
||||
# ===========================================================================
|
||||
# 5) RELATORIO
|
||||
# ===========================================================================
|
||||
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 = 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):
|
||||
"""Reconstroi a sequencia de missoes da aeronave k seguindo as conexoes."""
|
||||
inicio = [i for i in IDS if (k, i) in s and s[(k, i)].value() and s[(k, i)].value() > 0.5]
|
||||
if not inicio:
|
||||
return []
|
||||
seq = [inicio[0]]
|
||||
while True:
|
||||
atual = seq[-1]
|
||||
prox = [j for (ii, jj) in conex if ii == atual
|
||||
for j in [jj] if c[(k, atual, j)].value() and c[(k, atual, j)].value() > 0.5]
|
||||
if not prox:
|
||||
break
|
||||
seq.append(prox[0])
|
||||
return seq
|
||||
|
||||
for k in KS:
|
||||
seq = rota_da_aeronave(k)
|
||||
horas = sum(miss[i]["dur"] for i in seq)
|
||||
lrt_ini = LIMITE_HORAS_F - acft[k]["f"]
|
||||
if seq:
|
||||
trechos = " -> ".join(
|
||||
f"{i}({miss[i]['orig']}-{miss[i]['dest']} {miss[i]['part']:g}h)" for i in seq)
|
||||
print(f"\n {k} [base {acft[k]['base']}, f={acft[k]['f']:g}h, LRT={lrt_ini:g}h]")
|
||||
print(f" {trechos}")
|
||||
print(f" horas voadas: {horas:g}h | LRT restante: {lrt_ini - horas:g}h")
|
||||
else:
|
||||
print(f"\n {k} [base {acft[k]['base']}, LRT={lrt_ini:g}h] -> sem missoes")
|
||||
|
||||
descobertas = [i for i in IDS if i not in cob]
|
||||
if descobertas:
|
||||
print("\n " + "-" * 60)
|
||||
print(" Missoes NAO cumpridas:",
|
||||
", ".join(f"{i}(prio {miss[i]['prio']})" for i in descobertas))
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# NOTA - usar HiGHS (recomendado p/ instancias maiores, sem licenca):
|
||||
# pip install highspy
|
||||
# solver = pulp.HiGHS(msg=False) # PuLP 3.x expoe HiGHS nativo
|
||||
# ---------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user