Compare commits

...

4 Commits

Author SHA1 Message Date
Cesa-V
11821eaa93 Processa registro de voo 2025 2026-06-15 20:22:57 -03:00
Cesa-V
8d7dd07b18 Detalha checklist de atualizacao documental 2026-06-15 19:09:45 -03:00
Cesa-V
f570091e19 Atualiza documentacao da estrutura OAMRP 2026-06-15 19:03:24 -03:00
Cesa-V
1131267ea2 Reorganiza dados e adiciona modelo OAMRP 2026-06-15 19:00:45 -03:00
32 changed files with 9250 additions and 51 deletions

4
.gitignore vendored
View File

@@ -9,3 +9,7 @@ Desktop.ini
# Local editor folders # Local editor folders
.vscode/ .vscode/
.idea/ .idea/
# Python cache
__pycache__/
*.py[cod]

View File

@@ -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
View File

@@ -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. |

View File

@@ -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.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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."
]
}

View File

@@ -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.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
db/raw/Planilha 2025.xlsx Normal file

Binary file not shown.

View File

@@ -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
``` ```

View File

@@ -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`.

View File

@@ -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.

View 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
View 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
# ---------------------------------------------------------------------------