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
|
# Local editor folders
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Python cache
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|||||||
37
CONTEXTO.md
37
CONTEXTO.md
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
## Objetivo
|
## 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
|
## Estrutura de Pastas
|
||||||
|
|
||||||
- `raw/`: arquivos originais, sem alteração.
|
- `db/raw/`: arquivos originais, sem alteração.
|
||||||
- `pre_process/`: scripts e saídas intermediárias de pré-processamento.
|
- `db/pre_process/`: scripts e saídas intermediárias de pré-processamento.
|
||||||
- `processed/`: dados finais limpos, consolidados ou prontos para análise.
|
- `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.
|
- `docs/`: documentação técnica, autoria e histórico de versões.
|
||||||
|
|
||||||
## Arquivos de Documentação
|
## Arquivos de Documentação
|
||||||
@@ -28,8 +29,9 @@ Organizar, preservar e processar documentos relacionados ao projeto `arara_oarmp
|
|||||||
|
|
||||||
## Orientações de Trabalho
|
## Orientações de Trabalho
|
||||||
|
|
||||||
- Manter documentos originais sempre em `raw/`.
|
- Manter documentos originais sempre em `db/raw/`.
|
||||||
- Não alterar arquivos dentro de `raw/`; quando necessário, gerar cópias ou saídas em `pre_process/`.
|
- 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 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.
|
- 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.
|
- 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 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 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.
|
- 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.
|
- 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
|
## Formato Recomendado do Log
|
||||||
|
|
||||||
```text
|
```text
|
||||||
| Data | Hora | Autor | Ação | Arquivos | Observações |
|
| 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 | 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: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 | 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. |
|
||||||
|
|||||||
60
README.md
60
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.
|
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
|
```text
|
||||||
arara_oarmp/
|
arara_oarmp/
|
||||||
|
db/
|
||||||
raw/
|
raw/
|
||||||
pre_process/
|
pre_process/
|
||||||
processed/
|
processed/
|
||||||
|
software/
|
||||||
docs/
|
docs/
|
||||||
CONTEXTO.md
|
CONTEXTO.md
|
||||||
LOG.md
|
LOG.md
|
||||||
README.md
|
README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
- `raw/`: documentos originais. Não editar esses arquivos diretamente.
|
- `db/raw/`: documentos originais. Não editar esses arquivos diretamente.
|
||||||
- `pre_process/`: scripts e arquivos intermediários gerados a partir dos documentos originais.
|
- `db/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/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.
|
- `docs/`: documentação técnica, autoria e histórico de versões.
|
||||||
- `CONTEXTO.md`: regras, combinados e orientações permanentes do projeto.
|
- `CONTEXTO.md`: regras, combinados e orientações permanentes do projeto.
|
||||||
- `LOG.md`: histórico rastreável das ações feitas no 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.
|
1. Abra o repositório no Gitea do PPGAO/ITA.
|
||||||
2. Entre nas pastas para ver os arquivos.
|
2. Entre nas pastas para ver os arquivos.
|
||||||
3. Use `raw/` para consultar documentos originais.
|
3. Use `db/raw/` para consultar documentos originais.
|
||||||
4. Use `pre_process/` para consultar arquivos intermediários.
|
4. Use `db/pre_process/` para consultar arquivos intermediários.
|
||||||
5. Use `processed/` para consultar dados finais quando existirem.
|
5. Use `db/processed/` para consultar dados finais quando existirem.
|
||||||
6. Abra o `LOG.md` para entender o que já foi feito.
|
6. Abra o `LOG.md` para entender o que já foi feito.
|
||||||
7. Abra o `CONTEXTO.md` para ver os combinados do projeto.
|
7. Abra o `CONTEXTO.md` para ver os combinados do projeto.
|
||||||
|
|
||||||
@@ -125,16 +128,16 @@ git push
|
|||||||
## Fluxo recomendado de trabalho
|
## Fluxo recomendado de trabalho
|
||||||
|
|
||||||
1. Rode `git pull` antes de começar.
|
1. Rode `git pull` antes de começar.
|
||||||
2. Coloque documentos novos em `raw/`.
|
2. Coloque documentos novos em `db/raw/`.
|
||||||
3. Gere arquivos intermediários em `pre_process/`.
|
3. Gere arquivos intermediários em `db/pre_process/`.
|
||||||
4. Coloque resultados finais em `processed/`.
|
4. Coloque resultados finais em `db/processed/`.
|
||||||
5. Atualize o `LOG.md`.
|
5. Atualize o `LOG.md`.
|
||||||
6. Faça commit.
|
6. Faça commit.
|
||||||
7. Rode `git push`.
|
7. Rode `git push`.
|
||||||
|
|
||||||
## Cuidados importantes
|
## 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 apagar arquivos de outros colaboradores sem combinar.
|
||||||
- Não subir arquivos temporários do Office, como arquivos começando com `~$`.
|
- 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.
|
- 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`.
|
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:
|
Arquivos principais:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
raw/relatorio_ciclo_inspecoes_c105_2805_2026-06-15.pdf
|
db/raw/relatorio_ciclo_inspecoes_c105_2805_2026-06-15.pdf
|
||||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/preprocess_pdf.py
|
db/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
|
db/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
|
db/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_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.
|
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 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.
|
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:
|
Nesta fase inicial, o projeto contém:
|
||||||
|
|
||||||
- documentos originais em `raw/`;
|
- documentos originais em `db/raw/`;
|
||||||
- scripts e saídas intermediárias em `pre_process/`;
|
- scripts e saídas intermediárias em `db/pre_process/`;
|
||||||
- área reservada para dados finais em `processed/`;
|
- á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.
|
- documentação de contexto, autoria, mudanças e rastreabilidade.
|
||||||
|
|
||||||
## Fontes de dados
|
## Fontes de dados
|
||||||
@@ -21,32 +22,44 @@ Fonte inicial processada:
|
|||||||
|
|
||||||
| Arquivo | Conteúdo | Observação |
|
| 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
|
## Como funciona
|
||||||
|
|
||||||
O fluxo atual é:
|
O fluxo atual é:
|
||||||
|
|
||||||
1. Guardar documentos originais em `raw/`.
|
1. Guardar documentos originais em `db/raw/`.
|
||||||
2. Executar scripts de extração e padronização em `pre_process/`.
|
2. Executar scripts de extração e padronização em `db/pre_process/`.
|
||||||
3. Gerar saídas intermediárias em texto, JSON e CSV.
|
3. Gerar saídas intermediárias em texto, JSON e CSV.
|
||||||
4. Manter campos originais para rastreabilidade.
|
4. Manter campos originais para rastreabilidade.
|
||||||
5. Separar campos discretos úteis para cálculo, comparação e planejamento.
|
5. Separar campos discretos úteis para cálculo, comparação e planejamento.
|
||||||
6. Registrar mudanças relevantes em `LOG.md`.
|
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
|
## 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
|
```text
|
||||||
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_texto.txt
|
||||||
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.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_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:
|
Campos extraídos e estruturados:
|
||||||
|
|
||||||
| Campo | Conteúdo |
|
| Campo | Conteúdo |
|
||||||
@@ -97,13 +110,14 @@ Unidades de duração:
|
|||||||
3. Mudanças no formato do relatório podem exigir ajuste no script.
|
3. Mudanças no formato do relatório podem exigir ajuste no script.
|
||||||
4. A grafia original é preservada, inclusive erros existentes no documento fonte.
|
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.
|
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
|
## Oportunidades de melhoria
|
||||||
|
|
||||||
| Prioridade | 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 aeronaves |
|
||||||
| Alta | Criar uma tabela mestre de missões |
|
| Alta | Criar uma tabela mestre de missões |
|
||||||
| Alta | Definir regras para cálculo de vencimento por horas, meses e pousos |
|
| 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 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 o nome do arquivo no script corresponde ao arquivo real;
|
||||||
- se as bibliotecas Python necessárias estão instaladas;
|
- se as bibliotecas Python necessárias estão instaladas;
|
||||||
- se o layout do PDF mudou;
|
- se o layout do PDF mudou;
|
||||||
@@ -125,5 +139,36 @@ Se o pré-processamento falhar, verifique:
|
|||||||
Para reexecutar o pré-processamento:
|
Para reexecutar o pré-processamento:
|
||||||
|
|
||||||
```powershell
|
```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
|
## 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
|
## Como contribuir
|
||||||
|
|
||||||
Ao modificar ou estender este projeto:
|
Ao modificar ou estender este projeto:
|
||||||
|
|
||||||
1. Atualize sua cópia com `git pull` antes de começar.
|
1. Atualize sua cópia com `git pull` antes de começar.
|
||||||
2. Preserve documentos originais em `raw/`.
|
2. Preserve documentos originais em `db/raw/`.
|
||||||
3. Coloque scripts e saídas intermediárias em `pre_process/`.
|
3. Coloque scripts e saídas intermediárias em `db/pre_process/`.
|
||||||
4. Coloque resultados finais e validados em `processed/`.
|
4. Coloque resultados finais e validados em `db/processed/`.
|
||||||
5. Registre mudanças operacionais em `LOG.md`.
|
5. Registre mudanças operacionais em `LOG.md`.
|
||||||
6. Registre mudanças de versão em `docs/changelog.md` quando houver alteração significativa.
|
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`.
|
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 |
|
| 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` |
|
| 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`.
|
Contribuidores futuros devem adicionar uma linha nesta tabela e criar a entrada correspondente em `docs/changelog.md`.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Changelog
|
# 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:
|
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
|
## v0.1 — VTO — 2026-06
|
||||||
|
|
||||||
### Adicionado
|
### Adicionado
|
||||||
@@ -43,3 +59,30 @@ Formato:
|
|||||||
|
|
||||||
- O parser atual foi calibrado para o primeiro PDF processado.
|
- O parser atual foi calibrado para o primeiro PDF processado.
|
||||||
- A grafia original do documento fonte foi preservada, inclusive ocorrências de `INPEÇÃO`.
|
- 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