Compare commits

...

18 Commits

Author SHA1 Message Date
Cesa-V
fe2ba12e49 Inclui @vitorcesavc nos autores (README e docs/authors.md)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 18:03:31 -03:00
Cesa-V
b368201d5d Documenta historico v1/v2/v3 no README com fases, limitacoes e nomenclatura
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 11:14:57 -03:00
Cesa-V
e71a0f2518 Atualiza README para v0.10: Set Partitioning, cenario insp grande e tabela de parametros
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 11:11:23 -03:00
Cesa-V
a34ed6b340 Migra C1 de Set Covering para Set Partitioning e documenta limitacoes (v0.10)
- C1 alterado: z[nid] <= 1 -> z[nid] == 1 para todas as missoes; toda missao
  deve ser coberta por exatamente 1 aeronave (sem missoes descobertas).
- n_missoes padrao reduzido de 50 para 20 (n_evam de 5 para 4): garante
  factibilidade do MIP com Set Partitioning no envelope de 4 aeronaves.
- Time limit padrao do solver CBC fixado em 120 s (era ilimitado): evita
  travamento ao tentar provar infeasibility.
- Corrigido display "Not Solved": status de L1 preservado quando PuLP
  redefine mdl.status apos setObjective() em L2.
- f0_cenario_insp_grande.json revisado: apenas 2803 com f0=595h em INSP 2A
  (F_max=600h, downtime=4 dias); demais matriculas com f0=0.
- Gerado planejamento_insp_grande.html: Gantt exibe INSP 2A visivelmente;
  20/20 missoes cobertas; CBC Optimal em < 15 s.
- changelog.md e LOG.md atualizados com limitacoes detalhadas para
  contribuidores: escopo validado, bugs de display, riscos numericos do
  big-M com LRT < 5h e comportamento de 2811 ociosa.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 11:09:24 -03:00
Cesa-V
6433055e9e Atualiza LOG e changelog para sessao 2026-06-16 (v0.8 e v0.9)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:39:30 -03:00
Cesa-V
83a2dac624 Corrige Gantt, adiciona escala de voo real e ajusta solver (v0.9)
- visualizar_resultado: barras Gantt via go.Scatter lines (renderizacao
  garantida), yaxis range fixo para FAB 2800 visivel, largura minima 2h
- oamrp_v3: parser --escala para Escala de Voo Modelo 1 (CSV real),
  coordenadas de 26 bases, time-limit default 5min, imports timedelta/date

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:34:14 -03:00
Cesa-V
3756f961a8 Adiciona visualizacao combinada (Gantt + mapa + tabela) e exporta inspecoes no CSV (v0.8)
oamrp_v3.py: exporta linhas status=inspecao no CSV resultado com datas UTC reais
visualizar_resultado.py: refatorado para gerar um unico HTML (planejamento.html)
com cabecalho, Gantt Plotly (divisoes diarias, FAB labels), mapa Folium em iframe
e tabela de voos com filtros por aeronave/status/prioridade/base e busca livre

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 01:48:36 -03:00
Cesa-V
4d8554df38 Atualiza documentacao para refletir sessao 2026-06-16 (v0.7)
- LOG.md: entradas para bugs corrigidos (C2 inspeção, C6 removida),
  renomeação Al-Thani, OFRAG, visualização e rodada 50/50
- docs/changelog.md: versão v0.7 com correções, adições e resultado validado
- docs/about.md: seção v3 com rede espaço-tempo, nomenclatura Al-Thani,
  restrições C1-C11 e scripts auxiliares; v1 mantido como referência histórica
- README.md: estado atual aponta oamrp_v3 como modelo ativo, com comandos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 01:00:48 -03:00
Cesa-V
f0d72f8189 Corrige modelo OAMRP v3 e alinha nomenclatura com Al-Thani (2016)
Correções ao modelo:
- Adiciona conservação de fluxo em nós de inspeção (bug que permitia
  criação/destruição de unidades de fluxo, inflando cobertura para 50/50
  sem atribuição real de rotas)
- Remove restrição C6 simplificada que somava horas totais sem respeitar
  resets de inspeção, tornando 2809/2811 artificialmente ociosos

Nomenclatura alinhada ao Al-Thani (2016):
- InspecaoParam.lrt_h → f_max  (F do artigo: batente máximo legal)
- Aeronave.horas_iniciais → f0  (f_k: horas acumuladas no início)
- orcamento_h() → lrt_inicial() (LRT = F_max − f0)
- horas_iniciais_aleatorias() → gerar_f0_aleatorio()
- carregar_horas_iniciais() → carregar_f0()
- CLI --horas-iniciais → --f0

Novos arquivos:
- software/gerar_ofrag.py: gera tabela de OFRAGs com prioridades 1-5
- software/visualizar_resultado.py: mapa Folium interativo de rotas
- db/processed/ofrag.csv: 50 OFRAGs sintéticas (seed 42)
- db/processed/mapa_rotas.html: mapa gerado da última rodada

Resultado com --sintetico --aleatorio --ofrag: 50/50 missões cumpridas
(antes: 30/50 sem ferry, 7/50 com bug de fluxo)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 00:50:53 -03:00
Cesa-V
7cd7458565 Adiciona missoes sinteticas, ferry (Fase 3) e consolida versoes anteriores
- Gerador sintetico cross-base (50 missoes, 10 bases, haversine/C-105)
- Fase 3: arcos de ferry em construir_rede(); ferry consome horas de celula
- oamrp_v2.py e resultados CSV anteriores adicionados ao repositorio
- README, docs/about.md e changelog atualizados ate v0.6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:51:31 -03:00
Cesa-V
328a066849 Implementa OAMRP v3 com rede espaco-tempo e restricoes de inspecao (Fases 1-2)
Reescrita do zero em software/oamrp_v3.py: abstracao No/Arco, variavel
unificada y[k,arco], C1-C6 (Fase 1) e C7-C11 com relogio de horas,
downtime e disjuntivas de slot unico (Fase 2). TAT corrigido para 1,5h;
top-10 bases; EVAM obrigatoria; L2 = maximizar horas voadas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:29:44 -03:00
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
Cesa-V
32ad7c9f23 Compare ICA 66-31 with cycle report 2026-06-15 18:34:45 -03:00
Cesa-V
d2bf2883d7 Group cycle report preprocessing artifacts 2026-06-15 18:12:13 -03:00
Cesa-V
33c452b264 Add technical docs structure 2026-06-15 17:58:54 -03:00
46 changed files with 16146 additions and 44 deletions

5
.gitignore vendored
View File

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

View File

@@ -2,13 +2,24 @@
## 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.
## Arquivos de Documentação
- `README.md`: guia inicial para colaboradores e uso básico do repositório.
- `CONTEXTO.md`: orientações permanentes e combinados de trabalho.
- `LOG.md`: diário operacional detalhado, com data, hora, autor, ação, arquivos e observações.
- `docs/about.md`: documentação técnica do projeto, fluxo de dados, limitações e oportunidades de melhoria.
- `docs/authors.md`: autoria, colaboradores e regras de contribuição.
- `docs/changelog.md`: histórico resumido de versões significativas.
## Colaboradores ## Colaboradores
@@ -18,21 +29,43 @@ 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.
- 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.
- Registrar a origem de cada documento, quando conhecida. - Registrar a origem de cada documento, quando conhecida.
- Informar scripts executados e parâmetros importantes. - Informar scripts executados e parâmetros importantes.
- 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.
## 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 |
``` ```

119
HANDOFF_arara_oamrp.md Normal file
View File

@@ -0,0 +1,119 @@
# HANDOFF — ARARA OAMRP (Aircraft Routing C-105 / FAB)
> Documento de transferência para o **Claude Code**.
> Projeto local: `arara_oamrp/`. Cole/abra este arquivo no Claude Code e siga as instruções de leitura antes de validar a formulação e gerar código.
---
## 0. Enquadramento do problema
Modelo de **Aircraft Routing** para o Esquadrão ARARA (C-105), no estilo **OAMRP** (Al-Thani et al., 2016): sequenciamento **compacto** em **rede espaço-tempo**.
**NÃO** é Set Partitioning + Column Generation (modelo civil Bazargan). Desvios deliberados em relação ao caso civil:
1. **Cobertura ≤ 1** (seleção de missões), não particionamento `= 1`.
2. **Objetivo de maximização** de cobertura ponderada por prioridade, não minimização de custo.
3. Possibilidade de subconjunto de missões **obrigatórias (hard)**.
**Stack:** Python + PuLP (CBC para começar) → HiGHS como evolução.
---
## 1. Calibração confirmada (respostas do Vitor)
| Item | Decisão |
|---|---|
| Frota | **4 aeronaves**, homogêneas (mesmo LRT, mesma performance) |
| Horizonte | **1 mês** |
| Discretização | **Estilo OAMRP** — eventos de voo com horários explícitos |
| TAT (turnaround) | **01h30** |
| Base de inspeção | Apenas **SBMN** (somente níveis **Base** e **Orgânico**; **Parque desconsiderado**) |
| Bases de operação | **10 bases da FAB** (derivar/confirmar a partir do CSV) |
| Traslado (ferry) | **Permitido** e **consome horas de célula** |
| Reset de horas | Inspeção **zera apenas aquela inspeção**; inspeções **não simultâneas** na mesma aeronave (podem ser sequenciais se requisitos cumpridos) |
| Prioridade | Escala **1 a 5** |
| Missões por dia | Uma cauda pode encadear **várias missões no mesmo dia** |
| Horas iniciais (f_k) | Criar **campos editáveis**; permitir **default aleatório** |
### Objetivo (lexicográfico)
- **L1 — Primário:** maximizar Σ (prioridade × missões cumpridas).
- **L2 — Secundário:** "entregar aeronave com menor LRT" *(ver Q3 — pendente de definição)*.
- **Restrição dura derivada do 6.1:** **no máximo 1 aeronave em inspeção ao mesmo tempo** → modelada como restrição de recurso (C11), não como objetivo.
---
## 2. Formulação matemática proposta (validar antes de codar)
### Conjuntos
- `K` — aeronaves (caudas), |K| = 4.
- `B` — bases (10). `B_insp ⊆ B = {SBMN}`.
- `M` — missões candidatas. `M_obr ⊆ M` — obrigatórias *(Q2)*.
- `T` — tipos de inspeção válidos *(Q1)*.
### Parâmetros
- `f⁰_{k,i}` — horas acumuladas iniciais da cauda `k` no relógio da inspeção `i` (editável / aleatório).
- `LRT_i` — intervalo (horas de célula) da inspeção `i`.
- `CAL_i` — limite calendárico (dias) da inspeção `i`.
- `DT_i` — tempo indisponível (downtime) da inspeção `i`.
- `h_m` — horas de voo da missão `m` (do CSV, por par O-D).
- `o_m, d_m` — origem/destino de `m`.
- `[e_m, l_m]` — janela de decolagem de `m`.
- `p_m ∈ {1..5}` — prioridade de `m`.
- `hferry_{b,b'}` — horas de traslado entre bases (do CSV/distância).
- `home_k` — base inicial da cauda `k`.
- `TAT = 1.5 h`.
### Variáveis de decisão
- `x_{k,m} ∈ {0,1}` — cauda `k` cumpre missão `m`.
- `y` — arcos de sequenciamento na rede espaço-tempo (missão→missão, ferry, inspeção) por cauda.
- `t_dep_{k,m}`, `t_arr_{k,m}` — tempos (contínuos).
- `z_{k,i}` (+ início `s_{k,i}`) — cauda `k` executa inspeção `i`.
- `F_{k,i}(·)` — estado de horas acumuladas no relógio `i` ao longo da rota (contínuo, zera na inspeção `i`).
### Restrições
- **C1 Cobertura:** Σ_k x_{k,m} ≤ 1 ∀m; **= 1** ∀m ∈ M_obr.
- **C2 Continuidade (fluxo):** cada cauda forma um caminho único ordenado no tempo, iniciando em `home_k`; grau de entrada = grau de saída nos nós.
- **C3 Continuidade espacial:** encadear `m→m'` exige `d_m = o_{m'}`, senão exige arco de **ferry**.
- **C4 Continuidade temporal + TAT:** `t_dep(próx) ≥ t_arr(ant) + TAT` (+ tempo de ferry, se houver reposicionamento).
- **C5 Janela:** `e_m ≤ t_dep_{k,m} ≤ l_m`.
- **C6 Orçamento de horas com reset:** ao longo da rota, horas acumuladas no relógio `i``LRT_i`; voo/ferry somam horas; inspeção `i` zera o relógio `i`.
- **C7 Limite calendárico:** dias desde o último reset de `i``CAL_i`.
- **C8 Local de inspeção:** inspeção `i` só ocorre com a cauda em `B_insp` (SBMN); a cauda precisa rotear até lá.
- **C9 Downtime:** durante `DT_i` a cauda fica indisponível; atividades seguintes começam após.
- **C10 Não-simultaneidade na mesma cauda:** duas inspeções da mesma cauda não se sobrepõem (podem ser back-to-back).
- **C11 Slot único de inspeção (recurso):** em qualquer instante, Σ_k [cauda em inspeção] ≤ 1.
### Função objetivo (lexicográfica)
1. **max** Σ_m p_m · (Σ_k x_{k,m})
2. **então** otimizar L2 *(Q3)*.
---
## 3. Plano incremental de construção
1. **Modelo base** — cobertura ponderada + orçamento de horas (C1C6).
2. **Inspeção com reset** em SBMN — C7C11 + downtime/calendário.
3. **Realismo militar** — prioridade hard (M_obr), voos de posicionamento, janelas.
4. **Replanejamento + visualização** — re-otimização e dashboard.
---
## 4. Instruções para o Claude Code (executar na pasta `arara_oamrp/`)
1. **Ler contexto:** abrir o arquivo de CONTEXTO/LOG e tudo em `docs/` para entender o estado atual.
2. **Auditar e podar:** o ChatGPT inseriu **acréscimos desnecessários** — revisar e remover o que fugir do OAMRP compacto, mas antes pergunte. Confirmar que **não** há resquício de Set Partitioning + Column Generation.
3. **Ler inspeções:** `relatorio_ciclo_inspecoes_c105_2805_2026-06-15` (JSON) → extrair, para cada inspeção válida *(Q1: seq 2,3,4,6,7,18,19,20,22,23)*, os campos `LRT_i` (horas), `CAL_i` (limite calendárico) e `DT_i` (tempo indisponível).
4. **Ler voos:** `db/processed/registro_voo_2025_consolidado.csv` → derivar as **10 bases**, as **horas de voo por par O-D** (`h_m`) e os tempos de **ferry**.
5. **Horas iniciais:** criar campos editáveis para `f⁰_{k,i}`, com opção de **default aleatório**.
6. **Validar a formulação** da Seção 2 com o Vitor antes de gerar PuLP. Construção **incremental**, confirmando cada etapa.
---
## 5. Pendências a resolver (perguntar ao Vitor no Claude Code)
- **Q1 — Inspeções válidas:** confirmar a lista de seq **{2, 3, 4, 6, 7, 18, 19, 20, 22, 23}**.
- **Q2 — Obrigatórias:** SAR/EVAM entram como **hard** (M_obr, cobertura = 1) ou apenas como prioridade 5? *(o dataset hoje só traz prioridade 15)*
- **Q3 — Objetivo secundário "entregar aeronave com menor LRT":** definir o sentido exato. Duas leituras possíveis:
- **(a)** minimizar a **folga de horas ao fim do horizonte** (usar bem as horas disponíveis: não deixar cauda ociosa com muita célula sobrando que poderia ter voado);
- **(b)** **priorizar o emprego** das caudas mais próximas da inspeção primeiro (consumir antes quem está mais perto do limite).
- **Q4 — Bases:** validar as 10 bases que o Claude Code propuser a partir do CSV.

30
LOG.md
View File

@@ -18,3 +18,33 @@ Legenda de autores:
| 2026-06-15 | 16:03 | VTO | Corrigiu interpretação da coluna `Zera TSO` | `pre_process/preprocess_pdf.py`; `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json`; `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv` | `Zera TSO` passou a ser coluna única `zera_tso`; níveis `B`, `P` e `O` mapeados para Base, Parque e Orgânico; `controle_original` mantido para rastreabilidade. | | 2026-06-15 | 16:03 | VTO | Corrigiu interpretação da coluna `Zera TSO` | `pre_process/preprocess_pdf.py`; `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json`; `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv` | `Zera TSO` passou a ser coluna única `zera_tso`; níveis `B`, `P` e `O` mapeados para Base, Parque e Orgânico; `controle_original` mantido para rastreabilidade. |
| 2026-06-15 | 16:08 | VTO | Separou valores numéricos dos intervalos de inspeção | `pre_process/preprocess_pdf.py`; `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json`; `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv` | Criadas colunas discretas para horas de voo, meses contínuos e pousos, mantendo os campos textuais originais. | | 2026-06-15 | 16:08 | VTO | Separou valores numéricos dos intervalos de inspeção | `pre_process/preprocess_pdf.py`; `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json`; `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv` | Criadas colunas discretas para horas de voo, meses contínuos e pousos, mantendo os campos textuais originais. |
| 2026-06-15 | 16:43 | VTO | Renomeou o projeto e trocou o repositório remoto | `README.md`; `CONTEXTO.md`; `.git/config` | Projeto passou a se chamar `arara_oarmp`; remoto atualizado para `https://git.ppgao.ita.br/vitorcesavc/arara_oarmp.git`. | | 2026-06-15 | 16:43 | VTO | Renomeou o projeto e trocou o repositório remoto | `README.md`; `CONTEXTO.md`; `.git/config` | Projeto passou a se chamar `arara_oarmp`; remoto atualizado para `https://git.ppgao.ita.br/vitorcesavc/arara_oarmp.git`. |
| 2026-06-15 | 17:57 | VTO | Criou documentação técnica no padrão `docs/` | `docs/about.md`; `docs/authors.md`; `docs/changelog.md`; `README.md`; `CONTEXTO.md`; `LOG.md` | Estrutura inspirada no módulo `meteorologia_aeroportos`: documentação técnica, autoria e changelog separados do log operacional. |
| 2026-06-15 | 18:11 | VTO | Agrupou artefatos do relatório de ciclo em subpasta própria | `pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/`; `README.md`; `docs/about.md` | Removido `.gitkeep` de `pre_process/` porque a pasta deixou de estar vazia; `.gitkeep` de `processed/` foi mantido. |
| 2026-06-15 | 18:33 | VTO | Comparou ICA 66-31/2023 com relatório de ciclo C-105 2805 | `raw/ICA 66-31 2023.pdf`; `pre_process/comparacao_ica_66_31_c105_2023/` | A comparação usou o Anexo D textual do PDF completo da ICA. |
| 2026-06-15 | 19:01 | VTO | Reorganizou dados em `db/` e adicionou protótipo OAMRP | `db/`; `software/oamrp_v1.py`; `.gitignore`; `README.md`; `CONTEXTO.md`; `docs/about.md`; `docs/authors.md`; `docs/changelog.md` | Commit `1131267`; diretórios `raw/`, `pre_process/` e `processed/` movidos para `db/`; modelo MIP inicial em PuLP adicionado em `software/`. |
| 2026-06-15 | 19:56 | VTO | Pre-processou a Planilha 2025 para registro de voo | `db/raw/Planilha 2025.xlsx`; `db/pre_process/registro_voo_2025/`; `db/processed/registro_voo_2025.csv` | Extraidos 1421 registros; colunas `REAL` ate `COM2` descartadas; 53 linhas cruzam 00Z; 1 registro sem OM e 9 sem pousos preservados para revisao. |
| 2026-06-15 | 19:56 | VTO | Consolidou transicoes `ROTA` do registro de voo 2025 | `db/pre_process/registro_voo_2025/preprocess_planilha_2025.py`; `db/processed/registro_voo_2025_consolidado.csv` | `ROTA` tratado como marcador de transicao; 1421 linhas originais viraram 1268 trechos consolidados; 135 trechos com `ROTA` foram convertidos para origem/destino reais. |
| 2026-06-15 | 19:56 | VTO | Validou o registro de voo 2025 consolidado | `db/pre_process/registro_voo_2025/validacao_registro_voo_2025.md`; `db/processed/registro_voo_2025_consolidado.csv` | Sem duplicidades, sem divergencias de duracao e sem sobreposicoes temporais; pendentes 1 registro sem OM, 1 trecho com pousos zero e 3 quebras de continuidade espacial. |
| 2026-06-15 | 19:56 | VTO | Removeu registro isolado da aeronave 2806 | `db/pre_process/registro_voo_2025/preprocess_planilha_2025.py`; `db/processed/registro_voo_2025.csv`; `db/processed/registro_voo_2025_consolidado.csv` | Linha original 793 excluida dos artefatos processados; registro final passou para 1420 linhas e consolidado para 1267 trechos; nao ha mais registro sem OM. |
| 2026-06-15 | 19:56 | VTO | Criou modelo OAMRP v2 com dados reais de 2025 | `software/oamrp_v2.py`; `db/processed/resultado_oamrp_jan_2025.csv`; `README.md`; `docs/about.md`; `docs/changelog.md` | Rodada inicial de janeiro/2025 resolvida como otima pelo CBC; prioridade uniforme 1; base de manutencao SBMN; 50 de 61 missoes cumpridas. |
| 2026-06-15 | 21:00 | VTO | Criou modelo OAMRP v3 — Fase 1: rede espaço-tempo | `software/oamrp_v3.py`; `db/processed/resultado_oamrp_v3.csv` | Reescrita do zero com abstrações No/Arco e variável unificada y[k,arco]; C1C6; TAT=1,5h; f0_{k,i} editável; top-10 bases por frequência; missões EVAM hard; 50/50 missões cobertas em jan/2025. |
| 2026-06-15 | 22:00 | VTO | Calibrou inspeções válidas (Q1) e respondeu Q2Q4 | `software/oamrp_v3.py` | Removidas seq 18 e 23 do catálogo; EVAM marcado como M_obr (cobertura=1); L2 definido como minimizar folga de horas ao fim do horizonte; 10 bases por frequência. |
| 2026-06-15 | 22:30 | VTO | Implementou Fase 2: inspeções na rede espaço-tempo | `software/oamrp_v3.py` | Adicionados nós INSP_{k}_{seq} e arcos missao_insp/insp_missao; variáveis H_{k,i,nid} para rastreamento de horas com reset; C9 downtime; C11 slot único via disjuntivas. |
| 2026-06-15 | 23:00 | VTO | Substituiu dados reais por missões sintéticas cross-base | `software/oamrp_v3.py`; `db/processed/resultado_oamrp_sintetico.csv` | Dados reais de jan/2025 eram todos SBMN→SBMN (voos locais), não exercitando roteamento; gerador sintético cria 50 missões cross-base entre 10 bases com distâncias reais (haversine) e velocidade C-105; flag --sintetico na CLI. |
| 2026-06-15 | 23:30 | VTO | Implementou Fase 3: arcos de ferry (C3 relaxado) | `software/oamrp_v3.py` | Arcos inicio/missao_missao/missao_insp/insp_missao agora permitem bases diferentes com tempo de ferry (haversine/430 km/h); ferry consome horas de célula no relógio de inspeção (C6); relatório imprime segmentos de ferry na linha do tempo. |
| 2026-06-16 | 10:00 | VTO | Corrigiu bug de conservação de fluxo em nós de inspeção | `software/oamrp_v3.py` | Nós INSP_{k}_{seq} não tinham restrição entrada_k==saida_k; solver criava/destruía unidades de fluxo nesses nós, inflando cobertura para 50/50 no relatório com apenas 16 missões reais nas rotas; corrigido adicionando C2 para nós de inspeção. |
| 2026-06-16 | 10:05 | VTO | Removeu restrição C6 Fase 1 simplificada | `software/oamrp_v3.py` | Restrição somava horas totais sem respeitar resets de inspeção, tornando EVAMs 2809/2811 artificialmente ociosas; variáveis H[k,seq,nid] já cobrem isso corretamente com reset nos nós de inspeção. |
| 2026-06-16 | 10:10 | VTO | Adicionou horas iniciais aleatórias e prioridades via OFRAG | `software/oamrp_v3.py`; `software/gerar_ofrag.py`; `db/processed/ofrag.csv` | Flag --aleatorio sorteia f0 entre 10%90% de F_max por aeronave; gerar_ofrag.py cria tabela com id_ofrag, missao_id, prioridade (15), tipo_missao; --ofrag na CLI carrega prioridades ao gerar missões sintéticas. |
| 2026-06-16 | 10:20 | VTO | Alinhou nomenclatura com Al-Thani (2016) | `software/oamrp_v3.py` | lrt_h→f_max (batente fixo F); horas_iniciais→f0 (acumulado inicial f_k); orcamento_h()→lrt_inicial() (LRT=F_maxf0); horas_iniciais_aleatorias()→gerar_f0_aleatorio(); carregar_horas_iniciais()→carregar_f0(); CLI --horas-iniciais→--f0. |
| 2026-06-16 | 10:30 | VTO | Criou visualização interativa de rotas | `software/visualizar_resultado.py`; `db/processed/mapa_rotas.html` | Mapa Folium com rotas por aeronave (cor distinta), missões não cumpridas tracejadas em cinza, popup com detalhes, legenda de prioridades. |
| 2026-06-16 | 10:35 | VTO | Rodada sintética validada: 50/50 missões cumpridas | `db/processed/resultado_oamrp_sintetico.csv` | Comando: --sintetico --seed 42 --aleatorio --ofrag; solver CBC retornou Optimal; todas as 4 EVAMs voaram com ferry; commit f0d72f8 enviado ao remote. |
| 2026-06-16 | 14:00 | VTO | Criou visualização combinada (Gantt + mapa + tabela) | `software/visualizar_resultado.py`; `db/processed/planejamento_cenario_esquadrao.html` | HTML único com título "Planejamento de Diagonal de Manutenção"; Gantt via go.Scatter lines (renderização garantida); mapa Folium em iframe; tabela de voos com filtros JS; commit 3756f96. |
| 2026-06-16 | 15:00 | VTO | Criou cenários JSON de f0 para testes com inspeções | `db/processed/f0_cenario_esquadrao.json`; `db/processed/f0_cenario_insp_grande.json` | f0_cenario_esquadrao simula dados reais do esquadrão (LRT 32h/215h/12h/28h); f0_cenario_insp_grande força aeronaves próximas ao limite. |
| 2026-06-16 | 20:00 | VTO | Migrou modelo de Set Covering para Set Partitioning (C1) | `software/oamrp_v3.py` | Restrição C1 alterada de z<=1 para z==1 para todas as missões; eliminada distinção entre missões hard/opcionais; toda missão deve ser coberta por exatamente 1 aeronave. |
| 2026-06-16 | 20:05 | VTO | Reduziu missões sintéticas padrão de 50 para 20 | `software/oamrp_v3.py` | Set Partitioning com 50 missões tornava o MIP inviável ou muito lento (travamento do solver); 20 missões garantem factibilidade no envelope de 4 aeronaves em janeiro; n_evam reduzido de 5 para 4. |
| 2026-06-16 | 20:10 | VTO | Adicionou time limit padrão de 120 s ao solver CBC | `software/oamrp_v3.py` | Solver travava indefinidamente sem time limit ao tentar provar infeasibility; limite de 120 s garante retorno com melhor solução encontrada. |
| 2026-06-16 | 20:15 | VTO | Corrigiu display do status do solver (Not Solved) | `software/oamrp_v3.py` | PuLP redefine mdl.status para 0 ("Not Solved") ao chamar setObjective() antes de L2; status de L1 agora preservado quando L2 reporta 0. |
| 2026-06-16 | 20:20 | VTO | Revisou f0_cenario_insp_grande.json | `db/processed/f0_cenario_insp_grande.json` | Apenas 2803 com f0=595h em INSP 2A (F_max=600h, downtime=4 dias, LRT_ini=5h); demais matrículas com f0=0; configuração anterior com 4 aeronaves apertadas simultaneamente tornava o MIP inviável. |
| 2026-06-16 | 20:30 | VTO | Gerou visualização do cenário inspeção grande (v0.10) | `db/processed/planejamento_insp_grande.html`; `db/processed/resultado_oamrp_v3.csv` | 20/20 missões cobertas; Gantt exibe INSP 2A (4 dias) visivelmente; 2 inspeções no horizonte de janeiro; solver CBC retornou Optimal em < 15 s. |
| 2026-06-16 | 16:00 | VTO | Corrigiu Gantt: FAB 2800 invisível e barras não renderizavam | `software/visualizar_resultado.py` | yaxis range=[-0.5, n-0.5] para FAB 2800 (y=0) visível; shapes substituídas por go.Scatter mode="lines" width=64 (renderização garantida); largura mínima de 2h por barra. |
| 2026-06-16 | 16:30 | VTO | Adicionou parser de Escala de Voo Modelo 1 e bases extras | `software/oamrp_v3.py`; `db/raw/ESCALA DE VOO MODELO 1.csv` | Flag --escala carrega missões reais do CSV com separador ";"; coordenadas de 26 bases (era 10); time-limit default alterado para 300s; imports timedelta/date consolidados; commit 83a2dac. |

163
README.md
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.
@@ -8,12 +8,12 @@ A ideia é construir, em conjunto, uma base confiável para encaixar missões, a
Um repositório é uma pasta controlada pelo Git. Ele guarda os arquivos do projeto e também o histórico de alterações. Um repositório é uma pasta controlada pelo Git. Ele guarda os arquivos do projeto e também o histórico de alterações.
O GitHub é o site onde esse repositório fica hospedado de forma privada, para que os colaboradores possam acessar, baixar, atualizar e enviar contribuições. O Gitea do PPGAO/ITA é o site onde esse repositório fica hospedado de forma privada, para que os colaboradores possam acessar, baixar, atualizar e enviar contribuições.
Na prática: Na prática:
- o Git mostra o que mudou; - o Git mostra o que mudou;
- o GitHub permite compartilhar o projeto; - o Gitea permite compartilhar o projeto;
- os commits são registros das alterações feitas; - os commits são registros das alterações feitas;
- o `LOG.md` explica o motivo e o contexto das mudanças importantes. - o `LOG.md` explica o motivo e o contexto das mudanças importantes.
@@ -21,17 +21,22 @@ Na prática:
```text ```text
arara_oarmp/ arara_oarmp/
raw/ db/
pre_process/ raw/
processed/ pre_process/
processed/
software/
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.
- `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.
- `README.md`: este guia inicial. - `README.md`: este guia inicial.
@@ -40,21 +45,21 @@ arara_oarmp/
Usamos tags curtas para identificar quem fez cada alteração relevante: Usamos tags curtas para identificar quem fez cada alteração relevante:
- `VTO`: Vitor Cesa. - `VTO`: Vitor Cesa ([@vitorcesavc](https://git.ppgao.ita.br/vitorcesavc)).
- `GNR`: Generoso. - `GNR`: Generoso.
- `JOM`: João Marcos. - `JOM`: João Marcos.
Sempre que alguém fizer uma mudança importante, deve registrar no `LOG.md` com data, hora, tag do autor, ação, arquivos e observações. Sempre que alguém fizer uma mudança importante, deve registrar no `LOG.md` com data, hora, tag do autor, ação, arquivos e observações.
## Como usar pelo site do GitHub ## Como usar pelo site do Gitea
Este caminho é bom para quem só quer ver arquivos, baixar documentos ou conferir o histórico. Este caminho é bom para quem só quer ver arquivos, baixar documentos ou conferir o histórico.
1. Abra o repositório no GitHub. 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.
@@ -64,9 +69,16 @@ Para baixar um arquivo pelo site:
2. Clique em `Download raw file` ou no botão de download. 2. Clique em `Download raw file` ou no botão de download.
3. Salve no computador. 3. Salve no computador.
## Documentação
- `docs/about.md`: descrição técnica, fluxo de dados, limitações e oportunidades de melhoria.
- `docs/authors.md`: autoria, colaboradores e regras de contribuição.
- `docs/changelog.md`: histórico resumido de versões significativas.
- `LOG.md`: diário operacional detalhado.
## Como usar no computador ## Como usar no computador
Este caminho é para quem vai mexer nos arquivos e devolver alterações ao GitHub. Este caminho é para quem vai mexer nos arquivos e devolver alterações ao Gitea.
Primeiro, instale o Git: Primeiro, instale o Git:
@@ -107,7 +119,7 @@ Crie um commit com uma mensagem curta:
git commit -m "Descreva a alteração feita" git commit -m "Descreva a alteração feita"
``` ```
Envie para o GitHub: Envie para o Gitea:
```powershell ```powershell
git push git push
@@ -116,34 +128,123 @@ 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.
- Preferir nomes de arquivo descritivos, com tipo do documento, aeronave, matrícula e data quando possível. - Preferir nomes de arquivo descritivos, com tipo do documento, aeronave, matrícula e data quando possível.
- Registrar no `LOG.md` qualquer decisão que afete os dados. - Registrar no `LOG.md` qualquer decisão que afete os dados.
## Estado atual ## Estado atual (v0.10)
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`. O modelo ativo é `software/oamrp_v3.py`, que implementa o OAMRP sobre uma rede espaço-tempo com arcos de ferry, múltiplos tipos de inspeção e Set Partitioning para cobertura de missões.
Arquivos principais: **Última rodada validada (2026-06-16, commit `a34ed6b`):**
- 20/20 missões sintéticas cobertas, solver CBC Optimal em < 15 s
- Cenário com INSP 2A (4 dias em solo) visível no Gantt da matrícula 2803
- Set Partitioning: toda missão coberta por exatamente 1 aeronave
### Formulação de cobertura
O modelo usa **Set Partitioning** (`z[m] = 1` para toda missão `m`): cada missão deve ser atribuída a exatamente uma aeronave. Versões anteriores usavam Set Covering (`z[m] ≤ 1`), que permitia missões descobertas. A transição foi documentada em `docs/changelog.md` (v0.10).
> **Atenção para contribuidores:** Set Partitioning foi validado apenas para o cenário sintético padrão (20 missões, seed 42, 4 aeronaves, somente 2803 com LRT apertado). Com mais missões ou múltiplas aeronaves com LRT < 5 h simultâneas, o MIP pode ser infeasível ou ultrapassar o time limit. Consulte as limitações detalhadas em `docs/changelog.md`.
### Arquivos principais
```text ```text
raw/relatorio_ciclo_inspecoes_c105_2805_2026-06-15.pdf software/oamrp_v3.py — modelo MIP principal (Fases 13 + Set Partitioning)
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_texto.txt software/gerar_ofrag.py — gerador da tabela OFRAG
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json software/visualizar_resultado.py — Gantt + mapa + tabela interativa
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv db/processed/ofrag.csv — ordens de missão com prioridades 15
pre_process/preprocess_pdf.py db/processed/f0_cenario_insp_grande.json — f0 calibrado para forçar INSP 2A em 2803
db/processed/resultado_oamrp_v3.csv — resultado da última rodada
db/processed/planejamento_insp_grande.html — Gantt com inspeção de 4 dias visível
``` ```
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. ### Como rodar
Cenário padrão (inspeção grande visível no Gantt):
```powershell
python software/oamrp_v3.py --sintetico --seed 42 --f0 db/processed/f0_cenario_insp_grande.json --time-limit 60
python software/visualizar_resultado.py --resultado db/processed/resultado_oamrp_v3.csv --saida db/processed/planejamento_insp_grande.html
```
Parâmetros relevantes do modelo:
| Parâmetro | Padrão | Descrição |
|---|---|---|
| `--sintetico` | | Usa missões sintéticas cross-base em vez do CSV real |
| `--n-missoes` | 20 | Número de missões sintéticas |
| `--seed` | 42 | Semente aleatória (reprodutibilidade) |
| `--f0` | | JSON com horas acumuladas iniciais por aeronave/inspeção |
| `--time-limit` | 300 | Limite de tempo do solver CBC (segundos) |
| `--aleatorio` | | Sorteia f0 aleatório entre 10%90% de F_max |
---
## Histórico dos modelos
### v1 — Protótipo inicial (`software/oamrp_v1.py`)
Primeiro protótipo do modelo MIP em PuLP. Estrutura simples de atribuição aeronave-missão sem rede espaço-tempo, sem inspeções e sem ferry. Serviu para validar a configuração do ambiente e a integração com o solver CBC.
- **Formulação:** atribuição direta, variáveis binárias por (aeronave, missão)
- **Restrições:** cobertura básica e continuidade de base
- **Sem:** janelas de tempo, inspeções, ferry, prioridades
- **Status:** protótipo histórico, não usar em produção
---
### v2 — Dados reais de 2025 (`software/oamrp_v2.py`)
Primeira rodada com dados reais do registro de voo de janeiro/2025, pré-processados a partir da planilha original. Introduziu janelas de tempo (decolagem e chegada do CSV), TAT entre missões e prioridade uniforme.
- **Dados de entrada:** `db/processed/registro_voo_2025_consolidado.csv` (1 267 trechos, 1 420 linhas originais)
- **Resultado:** 50 de 61 missões cumpridas em janeiro/2025 (`db/processed/resultado_oamrp_jan_2025.csv`)
- **Limitação identificada:** os dados reais de jan/2025 eram predominantemente voos locais SBMNSBMN, não exercitando roteamento cross-base nem inspeções
```powershell
python software/oamrp_v2.py
```
---
### v3 — Rede espaço-tempo completa (`software/oamrp_v3.py`) ← **modelo ativo**
Reescrita completa baseada em Al-Thani, Ben Ahmed & Haouari (2016). Introduziu a formulação por rede espaço-tempo com nós No e arcos Arco, inspeções com relógio de horas acumuladas e reset, arcos de ferry entre bases e missões sintéticas cross-base para validação de roteamento.
**Fases implementadas:**
| Fase | Restrições | Descrição |
|---|---|---|
| 1 | C1C6 | Rede espaço-tempo, cobertura (Set Partitioning), conservação de fluxo, TAT |
| 2 | C7C11 | Nós de inspeção, relógio H[k,i,n] com reset, downtime, slot único |
| 3 | Ferry | Arcos de ferry entre quaisquer bases (haversine ÷ 430 km/h), consome horas de célula |
**Nomenclatura alinhada com Al-Thani (2016):**
- `F_max` batente fixo (intervalo máximo legal entre manutenções, ex: 300 h)
- `f0` horas acumuladas no início do horizonte (estado inicial da aeronave)
- `LRT = F_max f0` tempo legal restante (decresce com o voo)
- `H[k, seq, nid]` acumulado em tempo real, rastreado na rede (equivale a u_j do artigo)
**Missões sintéticas:** como os dados reais de 2025 não exercitavam roteamento, o modelo usa um gerador sintético cross-base (`--sintetico`) com distâncias reais (haversine) e velocidade de cruzeiro do C-105 (430 km/h).
**Pré-processamentos disponíveis:**
```text
db/raw/relatorio_ciclo_inspecoes_c105_2805_2026-06-15.pdf — ciclo de inspeções C-105/2805
db/processed/registro_voo_2025.csv — registro de voo 2025 limpo (1 420 trechos)
db/processed/registro_voo_2025_consolidado.csv — consolidado para roteamento
```

View File

@@ -0,0 +1,24 @@
chave;status;tipo_ica;tipo_relatorio;nivel_ica;nivel_relatorio;intervalo_horas_ica;intervalo_horas_relatorio;intervalo_meses_ica;intervalo_meses_relatorio;duracao_ica;duracao_relatorio;hxh_ica;seq_relatorio;observacoes
PRE VOO;somente_ica;PRÉ VOO;;Base;;;;;;1 H;;1;;Não encontrada no relatório de ciclo
TRANSITO;somente_ica;TRÂNSITO;;Base;;;;;;1 H;;1;;Não encontrada no relatório de ciclo
DIARIA;somente_ica;DIÁRIA;;Base;;;;;;1 H;;1;;Não encontrada no relatório de ciclo
SERV 72H;somente_ica;SERV 72H;;Base;;72;;;;4 H;;16;;Não encontrada no relatório de ciclo
OPERACIONAL 300FH;divergente;OPERACIONAL 300FH;CHECK OPERACIONAL DE 300FH;Base;Orgânico;300;300;;;1 D;6 H;14;6;"nível ICA=Base relatório=Orgânico; duração ICA=1 D relatório=6 H"
OPERACIONAL 400FH;divergente;OPERACIONAL 400FH;CHECK OPERACIONAL DE 400FH;Base;Orgânico;400;400;;;1 D;6 H;14;7;"nível ICA=Base relatório=Orgânico; duração ICA=1 D relatório=6 H"
OPERACIONAL 600FH;somente_ica;OPERACIONAL 600FH;;Base;;600;;;;1 D;;14;;Não encontrada no relatório de ciclo
CHECK 1A;compatível;CHECK 1A;INSPEÇÃO CHECK 1A;Base;Base;300;300;8;8;3 D;3 D;16;2;
CHECK 2A;compatível;CHECK 2A;INSPEÇÃO CHECK 2A;Base;Base;600;600;16;16;4 D;4 D;28;3;
CHECK 3A;compatível;CHECK 3A;INSPEÇÃO CHECK 3A;Base;Base;900;900;24;24;5 D;5 D;76;4;
CHECK 1C;divergente;CHECK 1C;INSPEÇÃO CHECK 1C;Base;Parque;2400;2400;48;48;65 D;65 D;526;15;nível ICA=Base relatório=Parque
CHECK 2C;divergente;CHECK 2C;INSPEÇÃO GERAL CHECK 2C;Base;Parque;4800;4800;96;96;5 D;10 D;17;16;"nível ICA=Base relatório=Parque; duração ICA=5 D relatório=10 D"
CALENDARICA 2Y;compatível;CALENDÁRICA 2Y;INSPEÇÃO CALENDÁRICA DE 2 YEARS;Base;Base;;;24;24;5 D;5 D;30;5;
CALENDARICA 4Y;compatível;CALENDÁRICA 4Y;INSPEÇÃO CALENDÁRICA DE 4 YEARS;Parque;Parque;;;48;48;10 D;10 D;123;17;
CALENDARICA 8Y;compatível;CALENDÁRICA 8Y;INSPEÇÃO CALENDÁRICA DE 8 YEARS;Parque;Parque;;;96;96;20 D;20 D;336;14;
;somente_relatorio;;INSPEÇÃO AOL 295-019;;Base;;;;;;1 D;;8;Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
;somente_relatorio;;INSPEÇÃO AOL 295-019;;Base;;;;;;1 D;;9;Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
;somente_relatorio;;INSPEÇÃO AOL 295-019;;Base;;;;;;1 D;;10;Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
;somente_relatorio;;INSPEÇÃO DE 100 FH;;Orgânico;;100;;;;1 H;;18;Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
;somente_relatorio;;INPEÇÃO 2400FH;;Orgânico;;2400;;;;3 H;;19;Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
;somente_relatorio;;INSPEÇÃO 3000FH;;Base;;3000;;;;1 D;;20;Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
;somente_relatorio;;INPEÇÃO 1000FH;;Base;;1000;;;;1 D;;22;Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
;somente_relatorio;;INSPEÇÃO 50 FH;;Base;;50;;;;1 D;;23;Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
1 chave status tipo_ica tipo_relatorio nivel_ica nivel_relatorio intervalo_horas_ica intervalo_horas_relatorio intervalo_meses_ica intervalo_meses_relatorio duracao_ica duracao_relatorio hxh_ica seq_relatorio observacoes
2 PRE VOO somente_ica PRÉ VOO Base 1 H 1 Não encontrada no relatório de ciclo
3 TRANSITO somente_ica TRÂNSITO Base 1 H 1 Não encontrada no relatório de ciclo
4 DIARIA somente_ica DIÁRIA Base 1 H 1 Não encontrada no relatório de ciclo
5 SERV 72H somente_ica SERV 72H Base 72 4 H 16 Não encontrada no relatório de ciclo
6 OPERACIONAL 300FH divergente OPERACIONAL 300FH CHECK OPERACIONAL DE 300FH Base Orgânico 300 300 1 D 6 H 14 6 nível ICA=Base relatório=Orgânico; duração ICA=1 D relatório=6 H
7 OPERACIONAL 400FH divergente OPERACIONAL 400FH CHECK OPERACIONAL DE 400FH Base Orgânico 400 400 1 D 6 H 14 7 nível ICA=Base relatório=Orgânico; duração ICA=1 D relatório=6 H
8 OPERACIONAL 600FH somente_ica OPERACIONAL 600FH Base 600 1 D 14 Não encontrada no relatório de ciclo
9 CHECK 1A compatível CHECK 1A INSPEÇÃO CHECK 1A Base Base 300 300 8 8 3 D 3 D 16 2
10 CHECK 2A compatível CHECK 2A INSPEÇÃO CHECK 2A Base Base 600 600 16 16 4 D 4 D 28 3
11 CHECK 3A compatível CHECK 3A INSPEÇÃO CHECK 3A Base Base 900 900 24 24 5 D 5 D 76 4
12 CHECK 1C divergente CHECK 1C INSPEÇÃO CHECK 1C Base Parque 2400 2400 48 48 65 D 65 D 526 15 nível ICA=Base relatório=Parque
13 CHECK 2C divergente CHECK 2C INSPEÇÃO GERAL CHECK 2C Base Parque 4800 4800 96 96 5 D 10 D 17 16 nível ICA=Base relatório=Parque; duração ICA=5 D relatório=10 D
14 CALENDARICA 2Y compatível CALENDÁRICA 2Y INSPEÇÃO CALENDÁRICA DE 2 YEARS Base Base 24 24 5 D 5 D 30 5
15 CALENDARICA 4Y compatível CALENDÁRICA 4Y INSPEÇÃO CALENDÁRICA DE 4 YEARS Parque Parque 48 48 10 D 10 D 123 17
16 CALENDARICA 8Y compatível CALENDÁRICA 8Y INSPEÇÃO CALENDÁRICA DE 8 YEARS Parque Parque 96 96 20 D 20 D 336 14
17 somente_relatorio INSPEÇÃO AOL 295-019 Base 1 D 8 Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
18 somente_relatorio INSPEÇÃO AOL 295-019 Base 1 D 9 Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
19 somente_relatorio INSPEÇÃO AOL 295-019 Base 1 D 10 Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
20 somente_relatorio INSPEÇÃO DE 100 FH Orgânico 100 1 H 18 Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
21 somente_relatorio INPEÇÃO 2400FH Orgânico 2400 3 H 19 Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
22 somente_relatorio INSPEÇÃO 3000FH Base 3000 1 D 20 Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
23 somente_relatorio INPEÇÃO 1000FH Base 1000 1 D 22 Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação
24 somente_relatorio INSPEÇÃO 50 FH Base 50 1 D 23 Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação

View File

@@ -0,0 +1,26 @@
# Comparação ICA 66-31/2023 x Relatório de Ciclo C-105 2805
Fonte ICA: `raw/ICA 66-31 2023.pdf`, Anexo D - Projeto C/SC-105.
Observação: a comparação foi feita com o Anexo D textual do PDF completo da ICA.
## Resumo
- `compatível`: 6
- `divergente`: 4
- `somente_ica`: 5
- `somente_relatorio`: 8
## Principais Achados
- `CHECK 1A`, `CHECK 2A`, `CHECK 3A`, `CALENDÁRICA 2Y`, `CALENDÁRICA 4Y` e `CALENDÁRICA 8Y` estão compatíveis nos intervalos e durações.
- `CHECK 1C` bate em intervalo e duração, mas diverge no nível: ICA indica Base na tabela D.2, enquanto o relatório de ciclo indica Parque.
- `CHECK 2C` diverge em nível e duração: ICA indica Base e 5 D; relatório de ciclo indica Parque e 10 D.
- `OPERACIONAL 300FH` e `OPERACIONAL 400FH` batem no intervalo de horas, mas divergem em nível e duração: ICA indica Base e 1 D; relatório de ciclo indica Orgânico e 6 H.
- `OPERACIONAL 600FH`, `PRÉ VOO`, `TRÂNSITO`, `DIÁRIA` e `SERV 72H` aparecem na ICA, mas não apareceram no relatório de ciclo processado.
- Inspeções como AOL, 50FH, 100FH, 1000FH, 2400FH e 3000FH aparecem no relatório de ciclo, mas não na tabela C/SC-105 da ICA usada na comparação.
## Arquivo Detalhado
- `pre_process\comparacao_ica_66_31_c105_2023\comparacao_ica_66_31_c105_2023_vs_relatorio_ciclo.csv`

View File

@@ -0,0 +1,182 @@
import csv
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
RELATORIO_JSON = (
ROOT
/ "pre_process"
/ "relatorio_ciclo_inspecoes_c105_2805_2026-06-15"
/ "relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json"
)
OUTPUT_DIR = Path(__file__).resolve().parent
CSV_PATH = OUTPUT_DIR / "comparacao_ica_66_31_c105_2023_vs_relatorio_ciclo.csv"
MD_PATH = OUTPUT_DIR / "comparacao_ica_66_31_c105_2023_vs_relatorio_ciclo.md"
ICA_C105 = [
{"chave": "PRE VOO", "tipo_ica": "PRÉ VOO", "nivel_ica": "Base", "intervalo_horas_ica": "", "intervalo_meses_ica": "", "duracao_ica": "1 H", "hxh_ica": 1},
{"chave": "TRANSITO", "tipo_ica": "TRÂNSITO", "nivel_ica": "Base", "intervalo_horas_ica": "", "intervalo_meses_ica": "", "duracao_ica": "1 H", "hxh_ica": 1},
{"chave": "DIARIA", "tipo_ica": "DIÁRIA", "nivel_ica": "Base", "intervalo_horas_ica": "", "intervalo_meses_ica": "", "duracao_ica": "1 H", "hxh_ica": 1},
{"chave": "SERV 72H", "tipo_ica": "SERV 72H", "nivel_ica": "Base", "intervalo_horas_ica": 72, "intervalo_meses_ica": "", "duracao_ica": "4 H", "hxh_ica": 16},
{"chave": "OPERACIONAL 300FH", "tipo_ica": "OPERACIONAL 300FH", "nivel_ica": "Base", "intervalo_horas_ica": 300, "intervalo_meses_ica": "", "duracao_ica": "1 D", "hxh_ica": 14},
{"chave": "OPERACIONAL 400FH", "tipo_ica": "OPERACIONAL 400FH", "nivel_ica": "Base", "intervalo_horas_ica": 400, "intervalo_meses_ica": "", "duracao_ica": "1 D", "hxh_ica": 14},
{"chave": "OPERACIONAL 600FH", "tipo_ica": "OPERACIONAL 600FH", "nivel_ica": "Base", "intervalo_horas_ica": 600, "intervalo_meses_ica": "", "duracao_ica": "1 D", "hxh_ica": 14},
{"chave": "CHECK 1A", "tipo_ica": "CHECK 1A", "nivel_ica": "Base", "intervalo_horas_ica": 300, "intervalo_meses_ica": 8, "duracao_ica": "3 D", "hxh_ica": 16},
{"chave": "CHECK 2A", "tipo_ica": "CHECK 2A", "nivel_ica": "Base", "intervalo_horas_ica": 600, "intervalo_meses_ica": 16, "duracao_ica": "4 D", "hxh_ica": 28},
{"chave": "CHECK 3A", "tipo_ica": "CHECK 3A", "nivel_ica": "Base", "intervalo_horas_ica": 900, "intervalo_meses_ica": 24, "duracao_ica": "5 D", "hxh_ica": 76},
{"chave": "CHECK 1C", "tipo_ica": "CHECK 1C", "nivel_ica": "Base", "intervalo_horas_ica": 2400, "intervalo_meses_ica": 48, "duracao_ica": "65 D", "hxh_ica": 526},
{"chave": "CHECK 2C", "tipo_ica": "CHECK 2C", "nivel_ica": "Base", "intervalo_horas_ica": 4800, "intervalo_meses_ica": 96, "duracao_ica": "5 D", "hxh_ica": 17},
{"chave": "CALENDARICA 2Y", "tipo_ica": "CALENDÁRICA 2Y", "nivel_ica": "Base", "intervalo_horas_ica": "", "intervalo_meses_ica": 24, "duracao_ica": "5 D", "hxh_ica": 30},
{"chave": "CALENDARICA 4Y", "tipo_ica": "CALENDÁRICA 4Y", "nivel_ica": "Parque", "intervalo_horas_ica": "", "intervalo_meses_ica": 48, "duracao_ica": "10 D", "hxh_ica": 123},
{"chave": "CALENDARICA 8Y", "tipo_ica": "CALENDÁRICA 8Y", "nivel_ica": "Parque", "intervalo_horas_ica": "", "intervalo_meses_ica": 96, "duracao_ica": "20 D", "hxh_ica": 336},
]
RELATORIO_KEYS = {
"CHECK 1A": "CHECK 1A",
"CHECK 2A": "CHECK 2A",
"CHECK 3A": "CHECK 3A",
"CHECK 1C": "CHECK 1C",
"CHECK 2C": "CHECK 2C",
"CALENDÁRICA DE 2 YEARS": "CALENDARICA 2Y",
"CALENDÁRICA DE 4 YEARS": "CALENDARICA 4Y",
"CALENDÁRICA DE 8 YEARS": "CALENDARICA 8Y",
"OPERACIONAL DE 300FH": "OPERACIONAL 300FH",
"OPERACIONAL DE 400FH": "OPERACIONAL 400FH",
}
def key_from_relatorio(row):
descricao = row["descricao_mnt"].upper()
for needle, key in RELATORIO_KEYS.items():
if needle in descricao:
return key
return ""
def equal_or_blank(left, right):
return left == "" or right == "" or left == right
def compare_row(ica, rel):
if rel is None:
return "somente_ica", ["Não encontrada no relatório de ciclo"]
issues = []
if not equal_or_blank(ica["nivel_ica"], rel["nivel_descricao"]):
issues.append(f"nível ICA={ica['nivel_ica']} relatório={rel['nivel_descricao']}")
if not equal_or_blank(ica["intervalo_horas_ica"], rel["intervalo_horas_voo_valor"]):
issues.append(f"horas ICA={ica['intervalo_horas_ica']} relatório={rel['intervalo_horas_voo_valor']}")
if not equal_or_blank(ica["intervalo_meses_ica"], rel["intervalo_meses_continuos_valor"]):
issues.append(f"meses ICA={ica['intervalo_meses_ica']} relatório={rel['intervalo_meses_continuos_valor']}")
if not equal_or_blank(ica["duracao_ica"], rel["duracao"]):
issues.append(f"duração ICA={ica['duracao_ica']} relatório={rel['duracao']}")
return ("divergente" if issues else "compatível"), issues
def main():
payload = json.loads(RELATORIO_JSON.read_text(encoding="utf-8"))
relatorio_by_key = {}
relatorio_unmatched = []
for row in payload["inspecoes"]:
key = key_from_relatorio(row)
if key:
relatorio_by_key[key] = row
else:
relatorio_unmatched.append(row)
rows = []
for ica in ICA_C105:
rel = relatorio_by_key.get(ica["chave"])
status, issues = compare_row(ica, rel)
rows.append(
{
"chave": ica["chave"],
"status": status,
"tipo_ica": ica["tipo_ica"],
"tipo_relatorio": rel["descricao_mnt"] if rel else "",
"nivel_ica": ica["nivel_ica"],
"nivel_relatorio": rel["nivel_descricao"] if rel else "",
"intervalo_horas_ica": ica["intervalo_horas_ica"],
"intervalo_horas_relatorio": rel["intervalo_horas_voo_valor"] if rel else "",
"intervalo_meses_ica": ica["intervalo_meses_ica"],
"intervalo_meses_relatorio": rel["intervalo_meses_continuos_valor"] if rel else "",
"duracao_ica": ica["duracao_ica"],
"duracao_relatorio": rel["duracao"] if rel else "",
"hxh_ica": ica["hxh_ica"],
"seq_relatorio": rel["seq"] if rel else "",
"observacoes": "; ".join(issues),
}
)
for rel in relatorio_unmatched:
rows.append(
{
"chave": "",
"status": "somente_relatorio",
"tipo_ica": "",
"tipo_relatorio": rel["descricao_mnt"],
"nivel_ica": "",
"nivel_relatorio": rel["nivel_descricao"],
"intervalo_horas_ica": "",
"intervalo_horas_relatorio": rel["intervalo_horas_voo_valor"],
"intervalo_meses_ica": "",
"intervalo_meses_relatorio": rel["intervalo_meses_continuos_valor"],
"duracao_ica": "",
"duracao_relatorio": rel["duracao"],
"hxh_ica": "",
"seq_relatorio": rel["seq"],
"observacoes": "Não encontrada na tabela C/SC-105 da ICA 66-31/2023 usada nesta comparação",
}
)
fields = list(rows[0].keys())
with CSV_PATH.open("w", newline="", encoding="utf-8-sig") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=fields, delimiter=";")
writer.writeheader()
writer.writerows(rows)
counts = {status: sum(1 for row in rows if row["status"] == status) for status in sorted({row["status"] for row in rows})}
lines = [
"# Comparação ICA 66-31/2023 x Relatório de Ciclo C-105 2805",
"",
"Fonte ICA: `raw/ICA 66-31 2023.pdf`, Anexo D - Projeto C/SC-105.",
"",
"Observação: a comparação foi feita com o Anexo D textual do PDF completo da ICA.",
"",
"## Resumo",
"",
]
for status, count in counts.items():
lines.append(f"- `{status}`: {count}")
lines.extend(
[
"",
"## Principais Achados",
"",
"- `CHECK 1A`, `CHECK 2A`, `CHECK 3A`, `CALENDÁRICA 2Y`, `CALENDÁRICA 4Y` e `CALENDÁRICA 8Y` estão compatíveis nos intervalos e durações.",
"- `CHECK 1C` bate em intervalo e duração, mas diverge no nível: ICA indica Base na tabela D.2, enquanto o relatório de ciclo indica Parque.",
"- `CHECK 2C` diverge em nível e duração: ICA indica Base e 5 D; relatório de ciclo indica Parque e 10 D.",
"- `OPERACIONAL 300FH` e `OPERACIONAL 400FH` batem no intervalo de horas, mas divergem em nível e duração: ICA indica Base e 1 D; relatório de ciclo indica Orgânico e 6 H.",
"- `OPERACIONAL 600FH`, `PRÉ VOO`, `TRÂNSITO`, `DIÁRIA` e `SERV 72H` aparecem na ICA, mas não apareceram no relatório de ciclo processado.",
"- Inspeções como AOL, 50FH, 100FH, 1000FH, 2400FH e 3000FH aparecem no relatório de ciclo, mas não na tabela C/SC-105 da ICA usada na comparação.",
"",
"## Arquivo Detalhado",
"",
f"- `{CSV_PATH.relative_to(ROOT)}`",
"",
]
)
MD_PATH.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Gerado: {CSV_PATH.relative_to(ROOT)}")
print(f"Gerado: {MD_PATH.relative_to(ROOT)}")
print(counts)
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,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.

View File

@@ -6,12 +6,13 @@ from pathlib import Path
import pdfplumber import pdfplumber
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[2]
BASE_NAME = "relatorio_ciclo_inspecoes_c105_2805_2026-06-15" BASE_NAME = "relatorio_ciclo_inspecoes_c105_2805_2026-06-15"
OUTPUT_DIR = Path(__file__).resolve().parent
PDF_PATH = ROOT / "raw" / f"{BASE_NAME}.pdf" PDF_PATH = ROOT / "raw" / f"{BASE_NAME}.pdf"
TXT_PATH = ROOT / "pre_process" / f"{BASE_NAME}_texto.txt" TXT_PATH = OUTPUT_DIR / f"{BASE_NAME}_texto.txt"
JSON_PATH = ROOT / "pre_process" / f"{BASE_NAME}_inspecoes.json" JSON_PATH = OUTPUT_DIR / f"{BASE_NAME}_inspecoes.json"
CSV_PATH = ROOT / "pre_process" / f"{BASE_NAME}_inspecoes.csv" CSV_PATH = OUTPUT_DIR / f"{BASE_NAME}_inspecoes.csv"
ROW_START_RE = re.compile(r"^(?P<seq>\d+)\s+") ROW_START_RE = re.compile(r"^(?P<seq>\d+)\s+")
INTERVAL_RE = re.compile( INTERVAL_RE = re.compile(

View File

@@ -0,0 +1,6 @@
{
"2800": {"2": 268, "3": 268, "4": 568, "6": 142, "7": 142, "19": 1800, "20": 2100, "22": 680},
"2803": {"2": 85, "3": 85, "4": 285, "6": 85, "7": 185, "19": 800, "20": 1200, "22": 350},
"2809": {"2": 155, "3": 455, "4": 155, "6": 288, "7": 288, "19": 1200, "20": 1800, "22": 500},
"2811": {"2": 120, "3": 572, "4": 572, "6": 120, "7": 220, "19": 1500, "20": 2200, "22": 720}
}

View File

@@ -0,0 +1,6 @@
{
"2800": {"2": 0, "3": 0, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0},
"2803": {"2": 0, "3": 595, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0},
"2809": {"2": 0, "3": 0, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0},
"2811": {"2": 0, "3": 0, "4": 0, "6": 0, "7": 0, "19": 0, "20": 0, "22": 0}
}

1999
db/processed/mapa_rotas.html Normal file

File diff suppressed because it is too large Load Diff

51
db/processed/ofrag.csv Normal file
View File

@@ -0,0 +1,51 @@
id_ofrag,missao_id,om,orig,dest,prioridade,tipo_missao,observacao
OFRAG-2025-001,SIM_001_SBMN_SBMY,1,SBMN,SBMY,1,Reabastecimento,
OFRAG-2025-002,SIM_002_SBBE_SBTT,2,SBBE,SBTT,1,Pessoal,
OFRAG-2025-003,SIM_003_SBMN_SBPV,3,SBMN,SBPV,1,Médico,EVAM — execução obrigatória
OFRAG-2025-004,SIM_004_SBMN_SBUA,4,SBMN,SBUA,1,Logística,EVAM — execução obrigatória
OFRAG-2025-005,SIM_005_SBSN_SBTS,5,SBSN,SBTS,2,Reabastecimento,
OFRAG-2025-006,SIM_006_SBSN_SBBE,6,SBSN,SBBE,2,Pessoal,
OFRAG-2025-007,SIM_007_SBTS_SBMN,7,SBTS,SBMN,2,Reconhecimento,
OFRAG-2025-008,SIM_008_SBBE_SBTS,8,SBBE,SBTS,4,Reconhecimento,
OFRAG-2025-009,SIM_009_SBMY_SBTS,9,SBMY,SBTS,2,Logística,
OFRAG-2025-010,SIM_010_SBTT_SBMN,10,SBTT,SBMN,1,Logística,
OFRAG-2025-011,SIM_011_SBBE_SBSN,11,SBBE,SBSN,1,Logística,
OFRAG-2025-012,SIM_012_SWBC_SBSN,12,SWBC,SBSN,3,Logística,
OFRAG-2025-013,SIM_013_SBMN_SBBV,13,SBMN,SBBV,2,Reconhecimento,
OFRAG-2025-014,SIM_014_SBMN_SBTS,14,SBMN,SBTS,1,Logística,
OFRAG-2025-015,SIM_015_SBMN_SBPV,15,SBMN,SBPV,1,Médico,EVAM — execução obrigatória
OFRAG-2025-016,SIM_016_SBPV_SBMN,16,SBPV,SBMN,1,Logística,
OFRAG-2025-017,SIM_017_SBSN_SBMN,17,SBSN,SBMN,4,Reabastecimento,
OFRAG-2025-018,SIM_018_SBSN_SBBV,18,SBSN,SBBV,1,Reconhecimento,
OFRAG-2025-019,SIM_019_SBBV_SBMN,19,SBBV,SBMN,2,Logística,
OFRAG-2025-020,SIM_020_SWBC_SBTS,20,SWBC,SBTS,4,Pessoal,
OFRAG-2025-021,SIM_021_SBMN_SBPV,21,SBMN,SBPV,1,Médico,EVAM — execução obrigatória
OFRAG-2025-022,SIM_022_SBMN_SBBV,22,SBMN,SBBV,1,Reabastecimento,
OFRAG-2025-023,SIM_023_SBBV_SWBC,23,SBBV,SWBC,3,Reconhecimento,
OFRAG-2025-024,SIM_024_SBMY_SBBV,24,SBMY,SBBV,1,Pessoal,
OFRAG-2025-025,SIM_025_SWBC_SBMN,25,SWBC,SBMN,3,Logística,
OFRAG-2025-026,SIM_026_SBUA_SBTT,26,SBUA,SBTT,3,Reabastecimento,
OFRAG-2025-027,SIM_027_SBTS_SBMY,27,SBTS,SBMY,1,Pessoal,
OFRAG-2025-028,SIM_028_SBMN_SBTT,28,SBMN,SBTT,2,Logística,
OFRAG-2025-029,SIM_029_SBTT_SBSN,29,SBTT,SBSN,2,Reconhecimento,
OFRAG-2025-030,SIM_030_SWBC_SBSN,30,SWBC,SBSN,2,Logística,
OFRAG-2025-031,SIM_031_SBMN_SBTT,31,SBMN,SBTT,2,Reabastecimento,
OFRAG-2025-032,SIM_032_SBMN_SBBE,32,SBMN,SBBE,5,Reabastecimento,
OFRAG-2025-033,SIM_033_SBBV_SBSN,33,SBBV,SBSN,1,Reconhecimento,
OFRAG-2025-034,SIM_034_SBTS_SBMN,34,SBTS,SBMN,2,Logística,
OFRAG-2025-035,SIM_035_SBTT_SBMN,35,SBTT,SBMN,1,Logística,
OFRAG-2025-036,SIM_036_SBPV_SWBC,36,SBPV,SWBC,3,Logística,
OFRAG-2025-037,SIM_037_SBBV_SBMN,37,SBBV,SBMN,3,Logística,
OFRAG-2025-038,SIM_038_SBBV_SBBE,38,SBBV,SBBE,2,Pessoal,
OFRAG-2025-039,SIM_039_SBSN_SBMN,39,SBSN,SBMN,1,Logística,
OFRAG-2025-040,SIM_040_SBSN_SBMN,40,SBSN,SBMN,4,Reconhecimento,
OFRAG-2025-041,SIM_041_SBBE_SBTS,41,SBBE,SBTS,1,Reabastecimento,
OFRAG-2025-042,SIM_042_SBBE_SBSN,42,SBBE,SBSN,5,Reconhecimento,
OFRAG-2025-043,SIM_043_SBMN_SBPV,43,SBMN,SBPV,4,Médico,
OFRAG-2025-044,SIM_044_SBBE_SBPV,44,SBBE,SBPV,3,Pessoal,
OFRAG-2025-045,SIM_045_SBTS_SBMN,45,SBTS,SBMN,4,Reabastecimento,
OFRAG-2025-046,SIM_046_SBMN_SBUA,46,SBMN,SBUA,3,Reconhecimento,
OFRAG-2025-047,SIM_047_SWBC_SBTS,47,SWBC,SBTS,2,Reabastecimento,
OFRAG-2025-048,SIM_048_SBSN_SBUA,48,SBSN,SBUA,1,Médico,
OFRAG-2025-049,SIM_049_SBMN_SBMY,49,SBMN,SBMY,4,Pessoal,
OFRAG-2025-050,SIM_050_SBTT_SBPV,50,SBTT,SBPV,1,Logística,EVAM — execução obrigatória
1 id_ofrag missao_id om orig dest prioridade tipo_missao observacao
2 OFRAG-2025-001 SIM_001_SBMN_SBMY 1 SBMN SBMY 1 Reabastecimento
3 OFRAG-2025-002 SIM_002_SBBE_SBTT 2 SBBE SBTT 1 Pessoal
4 OFRAG-2025-003 SIM_003_SBMN_SBPV 3 SBMN SBPV 1 Médico EVAM — execução obrigatória
5 OFRAG-2025-004 SIM_004_SBMN_SBUA 4 SBMN SBUA 1 Logística EVAM — execução obrigatória
6 OFRAG-2025-005 SIM_005_SBSN_SBTS 5 SBSN SBTS 2 Reabastecimento
7 OFRAG-2025-006 SIM_006_SBSN_SBBE 6 SBSN SBBE 2 Pessoal
8 OFRAG-2025-007 SIM_007_SBTS_SBMN 7 SBTS SBMN 2 Reconhecimento
9 OFRAG-2025-008 SIM_008_SBBE_SBTS 8 SBBE SBTS 4 Reconhecimento
10 OFRAG-2025-009 SIM_009_SBMY_SBTS 9 SBMY SBTS 2 Logística
11 OFRAG-2025-010 SIM_010_SBTT_SBMN 10 SBTT SBMN 1 Logística
12 OFRAG-2025-011 SIM_011_SBBE_SBSN 11 SBBE SBSN 1 Logística
13 OFRAG-2025-012 SIM_012_SWBC_SBSN 12 SWBC SBSN 3 Logística
14 OFRAG-2025-013 SIM_013_SBMN_SBBV 13 SBMN SBBV 2 Reconhecimento
15 OFRAG-2025-014 SIM_014_SBMN_SBTS 14 SBMN SBTS 1 Logística
16 OFRAG-2025-015 SIM_015_SBMN_SBPV 15 SBMN SBPV 1 Médico EVAM — execução obrigatória
17 OFRAG-2025-016 SIM_016_SBPV_SBMN 16 SBPV SBMN 1 Logística
18 OFRAG-2025-017 SIM_017_SBSN_SBMN 17 SBSN SBMN 4 Reabastecimento
19 OFRAG-2025-018 SIM_018_SBSN_SBBV 18 SBSN SBBV 1 Reconhecimento
20 OFRAG-2025-019 SIM_019_SBBV_SBMN 19 SBBV SBMN 2 Logística
21 OFRAG-2025-020 SIM_020_SWBC_SBTS 20 SWBC SBTS 4 Pessoal
22 OFRAG-2025-021 SIM_021_SBMN_SBPV 21 SBMN SBPV 1 Médico EVAM — execução obrigatória
23 OFRAG-2025-022 SIM_022_SBMN_SBBV 22 SBMN SBBV 1 Reabastecimento
24 OFRAG-2025-023 SIM_023_SBBV_SWBC 23 SBBV SWBC 3 Reconhecimento
25 OFRAG-2025-024 SIM_024_SBMY_SBBV 24 SBMY SBBV 1 Pessoal
26 OFRAG-2025-025 SIM_025_SWBC_SBMN 25 SWBC SBMN 3 Logística
27 OFRAG-2025-026 SIM_026_SBUA_SBTT 26 SBUA SBTT 3 Reabastecimento
28 OFRAG-2025-027 SIM_027_SBTS_SBMY 27 SBTS SBMY 1 Pessoal
29 OFRAG-2025-028 SIM_028_SBMN_SBTT 28 SBMN SBTT 2 Logística
30 OFRAG-2025-029 SIM_029_SBTT_SBSN 29 SBTT SBSN 2 Reconhecimento
31 OFRAG-2025-030 SIM_030_SWBC_SBSN 30 SWBC SBSN 2 Logística
32 OFRAG-2025-031 SIM_031_SBMN_SBTT 31 SBMN SBTT 2 Reabastecimento
33 OFRAG-2025-032 SIM_032_SBMN_SBBE 32 SBMN SBBE 5 Reabastecimento
34 OFRAG-2025-033 SIM_033_SBBV_SBSN 33 SBBV SBSN 1 Reconhecimento
35 OFRAG-2025-034 SIM_034_SBTS_SBMN 34 SBTS SBMN 2 Logística
36 OFRAG-2025-035 SIM_035_SBTT_SBMN 35 SBTT SBMN 1 Logística
37 OFRAG-2025-036 SIM_036_SBPV_SWBC 36 SBPV SWBC 3 Logística
38 OFRAG-2025-037 SIM_037_SBBV_SBMN 37 SBBV SBMN 3 Logística
39 OFRAG-2025-038 SIM_038_SBBV_SBBE 38 SBBV SBBE 2 Pessoal
40 OFRAG-2025-039 SIM_039_SBSN_SBMN 39 SBSN SBMN 1 Logística
41 OFRAG-2025-040 SIM_040_SBSN_SBMN 40 SBSN SBMN 4 Reconhecimento
42 OFRAG-2025-041 SIM_041_SBBE_SBTS 41 SBBE SBTS 1 Reabastecimento
43 OFRAG-2025-042 SIM_042_SBBE_SBSN 42 SBBE SBSN 5 Reconhecimento
44 OFRAG-2025-043 SIM_043_SBMN_SBPV 43 SBMN SBPV 4 Médico
45 OFRAG-2025-044 SIM_044_SBBE_SBPV 44 SBBE SBPV 3 Pessoal
46 OFRAG-2025-045 SIM_045_SBTS_SBMN 45 SBTS SBMN 4 Reabastecimento
47 OFRAG-2025-046 SIM_046_SBMN_SBUA 46 SBMN SBUA 3 Reconhecimento
48 OFRAG-2025-047 SIM_047_SWBC_SBTS 47 SWBC SBTS 2 Reabastecimento
49 OFRAG-2025-048 SIM_048_SBSN_SBUA 48 SBSN SBUA 1 Médico
50 OFRAG-2025-049 SIM_049_SBMN_SBMY 49 SBMN SBMY 4 Pessoal
51 OFRAG-2025-050 SIM_050_SBTT_SBPV 50 SBTT SBPV 1 Logística EVAM — execução obrigatória

File diff suppressed because one or more lines are too long

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,62 @@
status,aeronave_modelo,ordem,id,om,orig,dest,partida_utc,chegada_utc,dur_h,prioridade,periodo,aeronave_real_2025,linhas_origem
cumprida,2800,1,RV2025_OM1_E1_L4_C4,1,SBMN,SBMY,2025-01-02T11:55:00Z,2025-01-02T13:05:00Z,1.17,1,NOTURNO,2809,4
cumprida,2800,2,RV2025_OM1_E2_L5_C5,1,SBMY,SBMN,2025-01-02T14:30:00Z,2025-01-02T15:35:00Z,1.08,1,NOTURNO,2809,5
cumprida,2800,3,RV2025_OM6_E1_L12_C12,6,SBMN,SBBV,2025-01-06T19:30:00Z,2025-01-06T21:25:00Z,1.92,1,DIURNO,2811,12
cumprida,2800,4,RV2025_OM6_E2_L14_C14,6,SBBV,SBMN,2025-01-07T11:20:00Z,2025-01-07T13:20:00Z,2.00,1,DIURNO,2811,14
cumprida,2800,5,RV2025_OM8_E1_L17_C17,8,SBMN,SBPV,2025-01-08T10:35:00Z,2025-01-08T12:35:00Z,2.00,1,DIURNO,2803,17
cumprida,2800,6,RV2025_OM8_E2_L18_C18,8,SBPV,SBMN,2025-01-08T14:00:00Z,2025-01-08T16:00:00Z,2.00,1,DIURNO,2803,18
cumprida,2800,7,RV2025_OM10_E1_L19_C19,10,SBMN,SBMN,2025-01-08T22:30:00Z,2025-01-08T23:15:00Z,0.75,1,NOTURNO,2803,19
cumprida,2800,8,RV2025_OM11_E1_L23_C23,11,SBMN,SWKO,2025-01-10T12:10:00Z,2025-01-10T13:15:00Z,1.08,1,DIURNO,2803,23
cumprida,2800,9,RV2025_OM11_E2_L24_C24,11,SWKO,SBMN,2025-01-10T14:25:00Z,2025-01-10T15:25:00Z,1.00,1,DIURNO,2803,24
cumprida,2800,10,RV2025_OM13_E1_L30_C30,13,SBMN,SWKO,2025-01-12T12:20:00Z,2025-01-12T13:10:00Z,0.83,1,DIURNO,2809,30
cumprida,2800,11,RV2025_OM13_E2_L31_C31,13,SWKO,SBMN,2025-01-12T14:40:00Z,2025-01-12T15:25:00Z,0.75,1,DIURNO,2809,31
cumprida,2800,12,RV2025_OM14_E1_L32_C32,14,SBMN,SBBE,2025-01-12T16:55:00Z,2025-01-12T20:25:00Z,3.50,1,DIURNO,2809,32
cumprida,2800,13,RV2025_OM14_E2_L33_C37,14,SBBE,SBMN,2025-01-12T22:45:00Z,2025-01-13T02:05:00Z,3.33,1,NOTURNO,2809,33;37
cumprida,2800,14,RV2025_OM16_E1_L38_C38,16,SBMN,SBTT,2025-01-15T12:00:00Z,2025-01-15T15:05:00Z,3.08,1,DIURNO,2803,38
cumprida,2800,15,RV2025_OM16_E2_L39_C39,16,SBTT,SBMN,2025-01-15T17:35:00Z,2025-01-15T20:35:00Z,3.00,1,DIURNO,2803,39
cumprida,2800,16,RV2025_OM17_E3_L42_C45,17,SBMN,SBMN,2025-01-15T22:50:00Z,2025-01-15T23:50:00Z,1.00,1,NOTURNO,2809,42;43;44;45
cumprida,2800,17,RV2025_OM18_E1_L46_C46,18,SBMN,SBSN,2025-01-21T12:05:00Z,2025-01-21T13:40:00Z,1.58,1,DIURNO,2800,46
cumprida,2800,18,RV2025_OM18_E2_L47_C47,18,SBSN,SBTS,2025-01-21T14:45:00Z,2025-01-21T16:15:00Z,1.50,1,DIURNO,2800,47
cumprida,2800,19,RV2025_OM18_E3_L48_C48,18,SBTS,SBMN,2025-01-21T18:40:00Z,2025-01-21T20:40:00Z,2.00,1,DIURNO,2800,48
cumprida,2800,20,RV2025_OM19_E1_L49_C49,19,SBMN,SBMN,2025-01-22T14:55:00Z,2025-01-22T16:00:00Z,1.08,1,DIURNO,2800,49
cumprida,2800,21,RV2025_OM21_E2_L54_C54,21,SBMN,SBMN,2025-01-22T20:50:00Z,2025-01-22T21:25:00Z,0.58,1,DIURNO,2809,54
cumprida,2800,22,RV2025_OM23_E1_L57_C57,23,SBMN,SBCZ,2025-01-24T12:10:00Z,2025-01-24T15:55:00Z,3.75,1,DIURNO,2800,57
cumprida,2800,23,RV2025_OM23_E2_L58_C58,23,SBCZ,SBMN,2025-01-24T17:05:00Z,2025-01-24T20:55:00Z,3.83,1,DIURNO,2800,58
cumprida,2800,24,RV2025_OM25_E1_L64_C64,25,SBMN,SBMN,2025-01-28T13:25:00Z,2025-01-28T14:30:00Z,1.08,1,DIURNO,2809,64
cumprida,2800,25,RV2025_OM26_E1_L65_C70,26,SBMN,SBMN,2025-01-28T23:10:00Z,2025-01-29T00:50:00Z,1.67,1,NOTURNO,2809,65;66;67;70
cumprida,2800,26,RV2025_OM28_E1_L71_C71,28,SBMN,SBMN,2025-01-29T18:15:00Z,2025-01-29T19:25:00Z,1.17,1,DIURNO,2809,71
cumprida,2803,1,RV2025_OM2_E3_L6_C6,2,SBMN,SBMY,2025-01-02T17:00:00Z,2025-01-02T18:05:00Z,1.08,1,NOTURNO,2809,6
cumprida,2803,2,RV2025_OM2_E4_L7_C7,2,SBMY,SBMN,2025-01-02T20:00:00Z,2025-01-02T21:10:00Z,1.17,1,NOTURNO,2809,7
cumprida,2803,3,RV2025_OM5_E1_L11_C11,5,SBMN,SBPV,2025-01-06T19:10:00Z,2025-01-06T21:20:00Z,2.17,1,DIURNO,2803,11
cumprida,2803,4,RV2025_OM5_E2_L13_C13,5,SBPV,SBMN,2025-01-07T14:15:00Z,2025-01-07T16:15:00Z,2.00,1,DIURNO,2803,13
cumprida,2803,5,RV2025_OM9_E1_L20_C20,9,SBMN,SBBE,2025-01-09T12:30:00Z,2025-01-09T15:55:00Z,3.42,1,DIURNO,2803,20
cumprida,2803,6,RV2025_OM9_E2_L21_C21,9,SBBE,SBSN,2025-01-09T18:10:00Z,2025-01-09T20:00:00Z,1.83,1,DIURNO,2803,21
cumprida,2803,7,RV2025_OM15_E2_L35_C35,15,SBSN,SBTS,2025-01-12T14:25:00Z,2025-01-12T16:00:00Z,1.58,1,DIURNO,2803,35
cumprida,2803,8,RV2025_OM15_E3_L36_C36,15,SBTS,SBMN,2025-01-12T18:30:00Z,2025-01-12T20:35:00Z,2.08,1,DIURNO,2803,36
cumprida,2803,9,RV2025_OM20_E1_L51_C51,20,SBMN,SBMN,2025-01-22T14:55:00Z,2025-01-22T16:00:00Z,1.08,1,DIURNO,2803,51
cumprida,2803,10,RV2025_OM20_E2_L52_C52,20,SBMN,SBMN,2025-01-22T20:50:00Z,2025-01-22T21:25:00Z,0.58,1,DIURNO,2803,52
cumprida,2803,11,RV2025_OM29_E1_L72_C72,29,SBMN,SBMN,2025-01-29T22:50:00Z,2025-01-30T00:00:00Z,1.17,1,NOTURNO,2809,72
cumprida,2809,1,RV2025_OM3_E1_L8_C8,3,SBMN,SBMN,2025-01-03T14:45:00Z,2025-01-03T16:10:00Z,1.42,1,DIURNO,2809,8
cumprida,2809,2,RV2025_OM17_E2_L41_C41,17,SBMN,SBMN,2025-01-15T14:45:00Z,2025-01-15T15:40:00Z,0.92,1,DIURNO,2809,41
cumprida,2809,3,RV2025_OM21_E1_L53_C53,21,SBMN,SBMN,2025-01-22T14:55:00Z,2025-01-22T16:00:00Z,1.08,1,DIURNO,2809,53
cumprida,2809,4,RV2025_OM19_E2_L50_C50,19,SBMN,SBMN,2025-01-22T20:50:00Z,2025-01-22T21:25:00Z,0.58,1,DIURNO,2800,50
cumprida,2809,5,RV2025_OM24_E1_L59_C59,24,SBMN,SNRH,2025-01-24T12:30:00Z,2025-01-24T15:35:00Z,3.08,1,DIURNO,2803,59
cumprida,2809,6,RV2025_OM24_E2_L60_C60,24,SNRH,SBTT,2025-01-24T16:45:00Z,2025-01-24T17:40:00Z,0.92,1,DIURNO,2803,60
cumprida,2809,7,RV2025_OM24_E3_L61_C61,24,SBTT,SWEI,2025-01-25T14:05:00Z,2025-01-25T14:50:00Z,0.75,1,DIURNO,2803,61
cumprida,2809,8,RV2025_OM24_E4_L62_C62,24,SWEI,SBTF,2025-01-25T16:45:00Z,2025-01-25T18:40:00Z,1.92,1,DIURNO,2803,62
cumprida,2811,1,RV2025_OM17_E1_L40_C40,17,SBMN,SBMN,2025-01-15T13:30:00Z,2025-01-15T14:25:00Z,0.92,1,DIURNO,2809,40
cumprida,2811,2,RV2025_OM22_E1_L55_C55,22,SBMN,SBMN,2025-01-22T14:55:00Z,2025-01-22T15:55:00Z,1.00,1,DIURNO,2811,55
cumprida,2811,3,RV2025_OM22_E2_L56_C56,22,SBMN,SBMN,2025-01-22T20:50:00Z,2025-01-22T21:25:00Z,0.58,1,DIURNO,2811,56
cumprida,2811,4,RV2025_OM27_E1_L68_C68,27,SBMN,SBMY,2025-01-28T12:05:00Z,2025-01-28T13:05:00Z,1.00,1,DIURNO,2803,68
cumprida,2811,5,RV2025_OM27_E2_L69_C69,27,SBMY,SBMN,2025-01-28T14:35:00Z,2025-01-28T15:30:00Z,0.92,1,DIURNO,2803,69
nao_cumprida,,,RV2025_OM4_E1_L9_C9,4,SBAN,SWBC,2025-01-06T12:40:00Z,2025-01-06T13:50:00Z,1.17,1,DIURNO,2803,9
nao_cumprida,,,RV2025_OM4_E2_L10_C10,4,SWBC,SBMN,2025-01-06T15:40:00Z,2025-01-06T16:45:00Z,1.08,1,DIURNO,2803,10
nao_cumprida,,,RV2025_OM7_E1_L15_C15,7,SBMN,SBBV,2025-01-07T10:40:00Z,2025-01-07T12:30:00Z,1.83,1,DIURNO,2809,15
nao_cumprida,,,RV2025_OM7_E2_L16_C16,7,SBMN,SBBV,2025-01-07T14:00:00Z,2025-01-07T15:45:00Z,1.75,1,DIURNO,2809,16
nao_cumprida,,,RV2025_OM9_E3_L22_C22,9,SBSN,SBMN,2025-01-09T20:40:00Z,2025-01-09T22:15:00Z,1.58,1,DIURNO,2803,22
nao_cumprida,,,RV2025_OM12_E1_L25_C25,12,SBMN,SBUA,2025-01-11T12:50:00Z,2025-01-11T15:00:00Z,2.17,1,DIURNO,2809,25
nao_cumprida,,,RV2025_OM12_E2_L26_C26,12,SBUA,SBYA,2025-01-11T17:20:00Z,2025-01-11T18:10:00Z,0.83,1,DIURNO,2809,26
nao_cumprida,,,RV2025_OM12_E3_L27_C27,12,SBYA,SBTT,2025-01-11T18:45:00Z,2025-01-11T20:10:00Z,1.42,1,DIURNO,2809,27
nao_cumprida,,,RV2025_OM12_E4_L28_C29,12,SBTT,SBTT,2025-01-11T21:05:00Z,2025-01-12T00:00:00Z,2.92,1,DIURNO;NOTURNO,2809,28;29
nao_cumprida,,,RV2025_OM15_E1_L34_C34,15,SBMN,SBSN,2025-01-12T11:50:00Z,2025-01-12T13:30:00Z,1.67,1,DIURNO,2803,34
nao_cumprida,,,RV2025_OM24_E5_L63_C63,24,SBTF,SBMN,2025-01-25T19:20:00Z,2025-01-25T21:00:00Z,1.67,1,DIURNO,2803,63
1 status aeronave_modelo ordem id om orig dest partida_utc chegada_utc dur_h prioridade periodo aeronave_real_2025 linhas_origem
2 cumprida 2800 1 RV2025_OM1_E1_L4_C4 1 SBMN SBMY 2025-01-02T11:55:00Z 2025-01-02T13:05:00Z 1.17 1 NOTURNO 2809 4
3 cumprida 2800 2 RV2025_OM1_E2_L5_C5 1 SBMY SBMN 2025-01-02T14:30:00Z 2025-01-02T15:35:00Z 1.08 1 NOTURNO 2809 5
4 cumprida 2800 3 RV2025_OM6_E1_L12_C12 6 SBMN SBBV 2025-01-06T19:30:00Z 2025-01-06T21:25:00Z 1.92 1 DIURNO 2811 12
5 cumprida 2800 4 RV2025_OM6_E2_L14_C14 6 SBBV SBMN 2025-01-07T11:20:00Z 2025-01-07T13:20:00Z 2.00 1 DIURNO 2811 14
6 cumprida 2800 5 RV2025_OM8_E1_L17_C17 8 SBMN SBPV 2025-01-08T10:35:00Z 2025-01-08T12:35:00Z 2.00 1 DIURNO 2803 17
7 cumprida 2800 6 RV2025_OM8_E2_L18_C18 8 SBPV SBMN 2025-01-08T14:00:00Z 2025-01-08T16:00:00Z 2.00 1 DIURNO 2803 18
8 cumprida 2800 7 RV2025_OM10_E1_L19_C19 10 SBMN SBMN 2025-01-08T22:30:00Z 2025-01-08T23:15:00Z 0.75 1 NOTURNO 2803 19
9 cumprida 2800 8 RV2025_OM11_E1_L23_C23 11 SBMN SWKO 2025-01-10T12:10:00Z 2025-01-10T13:15:00Z 1.08 1 DIURNO 2803 23
10 cumprida 2800 9 RV2025_OM11_E2_L24_C24 11 SWKO SBMN 2025-01-10T14:25:00Z 2025-01-10T15:25:00Z 1.00 1 DIURNO 2803 24
11 cumprida 2800 10 RV2025_OM13_E1_L30_C30 13 SBMN SWKO 2025-01-12T12:20:00Z 2025-01-12T13:10:00Z 0.83 1 DIURNO 2809 30
12 cumprida 2800 11 RV2025_OM13_E2_L31_C31 13 SWKO SBMN 2025-01-12T14:40:00Z 2025-01-12T15:25:00Z 0.75 1 DIURNO 2809 31
13 cumprida 2800 12 RV2025_OM14_E1_L32_C32 14 SBMN SBBE 2025-01-12T16:55:00Z 2025-01-12T20:25:00Z 3.50 1 DIURNO 2809 32
14 cumprida 2800 13 RV2025_OM14_E2_L33_C37 14 SBBE SBMN 2025-01-12T22:45:00Z 2025-01-13T02:05:00Z 3.33 1 NOTURNO 2809 33;37
15 cumprida 2800 14 RV2025_OM16_E1_L38_C38 16 SBMN SBTT 2025-01-15T12:00:00Z 2025-01-15T15:05:00Z 3.08 1 DIURNO 2803 38
16 cumprida 2800 15 RV2025_OM16_E2_L39_C39 16 SBTT SBMN 2025-01-15T17:35:00Z 2025-01-15T20:35:00Z 3.00 1 DIURNO 2803 39
17 cumprida 2800 16 RV2025_OM17_E3_L42_C45 17 SBMN SBMN 2025-01-15T22:50:00Z 2025-01-15T23:50:00Z 1.00 1 NOTURNO 2809 42;43;44;45
18 cumprida 2800 17 RV2025_OM18_E1_L46_C46 18 SBMN SBSN 2025-01-21T12:05:00Z 2025-01-21T13:40:00Z 1.58 1 DIURNO 2800 46
19 cumprida 2800 18 RV2025_OM18_E2_L47_C47 18 SBSN SBTS 2025-01-21T14:45:00Z 2025-01-21T16:15:00Z 1.50 1 DIURNO 2800 47
20 cumprida 2800 19 RV2025_OM18_E3_L48_C48 18 SBTS SBMN 2025-01-21T18:40:00Z 2025-01-21T20:40:00Z 2.00 1 DIURNO 2800 48
21 cumprida 2800 20 RV2025_OM19_E1_L49_C49 19 SBMN SBMN 2025-01-22T14:55:00Z 2025-01-22T16:00:00Z 1.08 1 DIURNO 2800 49
22 cumprida 2800 21 RV2025_OM21_E2_L54_C54 21 SBMN SBMN 2025-01-22T20:50:00Z 2025-01-22T21:25:00Z 0.58 1 DIURNO 2809 54
23 cumprida 2800 22 RV2025_OM23_E1_L57_C57 23 SBMN SBCZ 2025-01-24T12:10:00Z 2025-01-24T15:55:00Z 3.75 1 DIURNO 2800 57
24 cumprida 2800 23 RV2025_OM23_E2_L58_C58 23 SBCZ SBMN 2025-01-24T17:05:00Z 2025-01-24T20:55:00Z 3.83 1 DIURNO 2800 58
25 cumprida 2800 24 RV2025_OM25_E1_L64_C64 25 SBMN SBMN 2025-01-28T13:25:00Z 2025-01-28T14:30:00Z 1.08 1 DIURNO 2809 64
26 cumprida 2800 25 RV2025_OM26_E1_L65_C70 26 SBMN SBMN 2025-01-28T23:10:00Z 2025-01-29T00:50:00Z 1.67 1 NOTURNO 2809 65;66;67;70
27 cumprida 2800 26 RV2025_OM28_E1_L71_C71 28 SBMN SBMN 2025-01-29T18:15:00Z 2025-01-29T19:25:00Z 1.17 1 DIURNO 2809 71
28 cumprida 2803 1 RV2025_OM2_E3_L6_C6 2 SBMN SBMY 2025-01-02T17:00:00Z 2025-01-02T18:05:00Z 1.08 1 NOTURNO 2809 6
29 cumprida 2803 2 RV2025_OM2_E4_L7_C7 2 SBMY SBMN 2025-01-02T20:00:00Z 2025-01-02T21:10:00Z 1.17 1 NOTURNO 2809 7
30 cumprida 2803 3 RV2025_OM5_E1_L11_C11 5 SBMN SBPV 2025-01-06T19:10:00Z 2025-01-06T21:20:00Z 2.17 1 DIURNO 2803 11
31 cumprida 2803 4 RV2025_OM5_E2_L13_C13 5 SBPV SBMN 2025-01-07T14:15:00Z 2025-01-07T16:15:00Z 2.00 1 DIURNO 2803 13
32 cumprida 2803 5 RV2025_OM9_E1_L20_C20 9 SBMN SBBE 2025-01-09T12:30:00Z 2025-01-09T15:55:00Z 3.42 1 DIURNO 2803 20
33 cumprida 2803 6 RV2025_OM9_E2_L21_C21 9 SBBE SBSN 2025-01-09T18:10:00Z 2025-01-09T20:00:00Z 1.83 1 DIURNO 2803 21
34 cumprida 2803 7 RV2025_OM15_E2_L35_C35 15 SBSN SBTS 2025-01-12T14:25:00Z 2025-01-12T16:00:00Z 1.58 1 DIURNO 2803 35
35 cumprida 2803 8 RV2025_OM15_E3_L36_C36 15 SBTS SBMN 2025-01-12T18:30:00Z 2025-01-12T20:35:00Z 2.08 1 DIURNO 2803 36
36 cumprida 2803 9 RV2025_OM20_E1_L51_C51 20 SBMN SBMN 2025-01-22T14:55:00Z 2025-01-22T16:00:00Z 1.08 1 DIURNO 2803 51
37 cumprida 2803 10 RV2025_OM20_E2_L52_C52 20 SBMN SBMN 2025-01-22T20:50:00Z 2025-01-22T21:25:00Z 0.58 1 DIURNO 2803 52
38 cumprida 2803 11 RV2025_OM29_E1_L72_C72 29 SBMN SBMN 2025-01-29T22:50:00Z 2025-01-30T00:00:00Z 1.17 1 NOTURNO 2809 72
39 cumprida 2809 1 RV2025_OM3_E1_L8_C8 3 SBMN SBMN 2025-01-03T14:45:00Z 2025-01-03T16:10:00Z 1.42 1 DIURNO 2809 8
40 cumprida 2809 2 RV2025_OM17_E2_L41_C41 17 SBMN SBMN 2025-01-15T14:45:00Z 2025-01-15T15:40:00Z 0.92 1 DIURNO 2809 41
41 cumprida 2809 3 RV2025_OM21_E1_L53_C53 21 SBMN SBMN 2025-01-22T14:55:00Z 2025-01-22T16:00:00Z 1.08 1 DIURNO 2809 53
42 cumprida 2809 4 RV2025_OM19_E2_L50_C50 19 SBMN SBMN 2025-01-22T20:50:00Z 2025-01-22T21:25:00Z 0.58 1 DIURNO 2800 50
43 cumprida 2809 5 RV2025_OM24_E1_L59_C59 24 SBMN SNRH 2025-01-24T12:30:00Z 2025-01-24T15:35:00Z 3.08 1 DIURNO 2803 59
44 cumprida 2809 6 RV2025_OM24_E2_L60_C60 24 SNRH SBTT 2025-01-24T16:45:00Z 2025-01-24T17:40:00Z 0.92 1 DIURNO 2803 60
45 cumprida 2809 7 RV2025_OM24_E3_L61_C61 24 SBTT SWEI 2025-01-25T14:05:00Z 2025-01-25T14:50:00Z 0.75 1 DIURNO 2803 61
46 cumprida 2809 8 RV2025_OM24_E4_L62_C62 24 SWEI SBTF 2025-01-25T16:45:00Z 2025-01-25T18:40:00Z 1.92 1 DIURNO 2803 62
47 cumprida 2811 1 RV2025_OM17_E1_L40_C40 17 SBMN SBMN 2025-01-15T13:30:00Z 2025-01-15T14:25:00Z 0.92 1 DIURNO 2809 40
48 cumprida 2811 2 RV2025_OM22_E1_L55_C55 22 SBMN SBMN 2025-01-22T14:55:00Z 2025-01-22T15:55:00Z 1.00 1 DIURNO 2811 55
49 cumprida 2811 3 RV2025_OM22_E2_L56_C56 22 SBMN SBMN 2025-01-22T20:50:00Z 2025-01-22T21:25:00Z 0.58 1 DIURNO 2811 56
50 cumprida 2811 4 RV2025_OM27_E1_L68_C68 27 SBMN SBMY 2025-01-28T12:05:00Z 2025-01-28T13:05:00Z 1.00 1 DIURNO 2803 68
51 cumprida 2811 5 RV2025_OM27_E2_L69_C69 27 SBMY SBMN 2025-01-28T14:35:00Z 2025-01-28T15:30:00Z 0.92 1 DIURNO 2803 69
52 nao_cumprida RV2025_OM4_E1_L9_C9 4 SBAN SWBC 2025-01-06T12:40:00Z 2025-01-06T13:50:00Z 1.17 1 DIURNO 2803 9
53 nao_cumprida RV2025_OM4_E2_L10_C10 4 SWBC SBMN 2025-01-06T15:40:00Z 2025-01-06T16:45:00Z 1.08 1 DIURNO 2803 10
54 nao_cumprida RV2025_OM7_E1_L15_C15 7 SBMN SBBV 2025-01-07T10:40:00Z 2025-01-07T12:30:00Z 1.83 1 DIURNO 2809 15
55 nao_cumprida RV2025_OM7_E2_L16_C16 7 SBMN SBBV 2025-01-07T14:00:00Z 2025-01-07T15:45:00Z 1.75 1 DIURNO 2809 16
56 nao_cumprida RV2025_OM9_E3_L22_C22 9 SBSN SBMN 2025-01-09T20:40:00Z 2025-01-09T22:15:00Z 1.58 1 DIURNO 2803 22
57 nao_cumprida RV2025_OM12_E1_L25_C25 12 SBMN SBUA 2025-01-11T12:50:00Z 2025-01-11T15:00:00Z 2.17 1 DIURNO 2809 25
58 nao_cumprida RV2025_OM12_E2_L26_C26 12 SBUA SBYA 2025-01-11T17:20:00Z 2025-01-11T18:10:00Z 0.83 1 DIURNO 2809 26
59 nao_cumprida RV2025_OM12_E3_L27_C27 12 SBYA SBTT 2025-01-11T18:45:00Z 2025-01-11T20:10:00Z 1.42 1 DIURNO 2809 27
60 nao_cumprida RV2025_OM12_E4_L28_C29 12 SBTT SBTT 2025-01-11T21:05:00Z 2025-01-12T00:00:00Z 2.92 1 DIURNO;NOTURNO 2809 28;29
61 nao_cumprida RV2025_OM15_E1_L34_C34 15 SBMN SBSN 2025-01-12T11:50:00Z 2025-01-12T13:30:00Z 1.67 1 DIURNO 2803 34
62 nao_cumprida RV2025_OM24_E5_L63_C63 24 SBTF SBMN 2025-01-25T19:20:00Z 2025-01-25T21:00:00Z 1.67 1 DIURNO 2803 63

View File

@@ -0,0 +1,51 @@
status,aeronave,ordem,id,om,orig,dest,partida_utc,chegada_utc,dur_h,prioridade,periodo,aeronave_real_2025,linhas_origem
cumprida,2800,1,SIM_007_SBTS_SBMN,7,SBTS,SBMN,2025-01-05T08:52:04.800000Z,2025-01-05T11:40:04.800000Z,2.80,2,DIURNO,,
cumprida,2800,2,SIM_008_SBBE_SBTS,8,SBBE,SBTS,2025-01-05T16:11:09.600000Z,2025-01-05T21:53:09.600000Z,5.70,4,DIURNO,,
cumprida,2800,3,SIM_015_SBMN_SBPV,15,SBMN,SBPV,2025-01-12T01:42:18Z,2025-01-12T03:21:54Z,1.66,1,DIURNO,,
cumprida,2800,4,SIM_021_SBMN_SBPV,21,SBMN,SBPV,2025-01-16T06:26:09.600000Z,2025-01-16T08:12:21.600000Z,1.77,1,DIURNO,,
cumprida,2800,5,SIM_037_SBBV_SBMN,37,SBBV,SBMN,2025-01-23T20:18:28.800000Z,2025-01-23T21:50:52.800000Z,1.54,3,DIURNO,,
cumprida,2800,6,SIM_042_SBBE_SBSN,42,SBBE,SBSN,2025-01-25T14:54:14.400000Z,2025-01-25T16:35:02.400000Z,1.68,5,DIURNO,,
cumprida,2800,7,SIM_045_SBTS_SBMN,45,SBTS,SBMN,2025-01-28T01:21:25.200000Z,2025-01-28T03:43:01.200000Z,2.36,4,DIURNO,,
cumprida,2803,1,SIM_006_SBSN_SBBE,6,SBSN,SBBE,2025-01-05T06:29:20.400000Z,2025-01-05T08:16:44.400000Z,1.79,2,DIURNO,,
cumprida,2803,2,SIM_023_SBBV_SWBC,23,SBBV,SWBC,2025-01-17T11:36:32.400000Z,2025-01-17T12:47:20.400000Z,1.18,3,DIURNO,,
cumprida,2803,3,SIM_028_SBMN_SBTT,28,SBMN,SBTT,2025-01-19T05:45:50.400000Z,2025-01-19T07:06:14.400000Z,1.34,2,DIURNO,,
cumprida,2809,1,SIM_036_SBPV_SWBC,36,SBPV,SWBC,2025-01-23T18:49:19.200000Z,2025-01-23T21:00:07.200000Z,2.18,3,DIURNO,,
cumprida,2811,1,SIM_001_SBMN_SBMY,1,SBMN,SBMY,2025-01-01T02:22:55.200000Z,2025-01-01T03:09:07.200000Z,0.77,1,DIURNO,,
cumprida,2811,2,SIM_003_SBMN_SBPV,3,SBMN,SBPV,2025-01-04T06:06:21.600000Z,2025-01-04T07:48:57.600000Z,1.71,1,DIURNO,,
cumprida,2811,3,SIM_005_SBSN_SBTS,5,SBSN,SBTS,2025-01-05T05:32:09.600000Z,2025-01-05T09:50:09.600000Z,4.30,2,DIURNO,,
cumprida,2811,4,SIM_025_SWBC_SBMN,25,SWBC,SBMN,2025-01-18T08:18:36Z,2025-01-18T09:11:24Z,0.88,3,DIURNO,,
cumprida,2811,5,SIM_041_SBBE_SBTS,41,SBBE,SBTS,2025-01-24T12:09:03.600000Z,2025-01-24T17:48:03.600000Z,5.65,1,DIURNO,,
nao_cumprida,,,SIM_002_SBBE_SBTT,2,SBBE,SBTT,2025-01-03T00:34:55.200000Z,2025-01-03T04:33:07.200000Z,3.97,1,DIURNO,,
nao_cumprida,,,SIM_004_SBMN_SBUA,4,SBMN,SBUA,2025-01-04T12:04:51.600000Z,2025-01-04T13:55:51.600000Z,1.85,1,DIURNO,,
nao_cumprida,,,SIM_009_SBMY_SBTS,9,SBMY,SBTS,2025-01-05T20:05:16.800000Z,2025-01-05T22:20:52.800000Z,2.26,2,DIURNO,,
nao_cumprida,,,SIM_010_SBTT_SBMN,10,SBTT,SBMN,2025-01-05T21:25:37.200000Z,2025-01-05T22:33:25.200000Z,1.13,1,DIURNO,,
nao_cumprida,,,SIM_011_SBBE_SBSN,11,SBBE,SBSN,2025-01-07T12:04:04.800000Z,2025-01-07T13:40:40.800000Z,1.61,1,DIURNO,,
nao_cumprida,,,SIM_012_SWBC_SBSN,12,SWBC,SBSN,2025-01-08T16:32:16.800000Z,2025-01-08T18:52:40.800000Z,2.34,3,DIURNO,,
nao_cumprida,,,SIM_013_SBMN_SBBV,13,SBMN,SBBV,2025-01-09T23:25:44.400000Z,2025-01-10T01:05:20.400000Z,1.66,2,DIURNO,,
nao_cumprida,,,SIM_014_SBMN_SBTS,14,SBMN,SBTS,2025-01-11T09:41:20.400000Z,2025-01-11T12:08:20.400000Z,2.45,1,DIURNO,,
nao_cumprida,,,SIM_016_SBPV_SBMN,16,SBPV,SBMN,2025-01-12T18:25:08.400000Z,2025-01-12T20:11:56.400000Z,1.78,1,DIURNO,,
nao_cumprida,,,SIM_017_SBSN_SBMN,17,SBSN,SBMN,2025-01-13T06:27:18Z,2025-01-13T07:41:42Z,1.24,4,DIURNO,,
nao_cumprida,,,SIM_018_SBSN_SBBV,18,SBSN,SBBV,2025-01-14T02:28:12Z,2025-01-14T04:36:36Z,2.14,1,DIURNO,,
nao_cumprida,,,SIM_019_SBBV_SBMN,19,SBBV,SBMN,2025-01-15T07:32:56.400000Z,2025-01-15T09:12:32.400000Z,1.66,2,DIURNO,,
nao_cumprida,,,SIM_020_SWBC_SBTS,20,SWBC,SBTS,2025-01-16T02:14:27.600000Z,2025-01-16T04:25:51.600000Z,2.19,4,DIURNO,,
nao_cumprida,,,SIM_022_SBMN_SBBV,22,SBMN,SBBV,2025-01-16T12:38:16.800000Z,2025-01-16T14:04:40.800000Z,1.44,1,DIURNO,,
nao_cumprida,,,SIM_024_SBMY_SBBV,24,SBMY,SBBV,2025-01-17T22:04:04.800000Z,2025-01-18T00:30:28.800000Z,2.44,1,DIURNO,,
nao_cumprida,,,SIM_026_SBUA_SBTT,26,SBUA,SBTT,2025-01-18T20:18:46.800000Z,2025-01-18T21:21:10.800000Z,1.04,3,DIURNO,,
nao_cumprida,,,SIM_027_SBTS_SBMY,27,SBTS,SBMY,2025-01-19T05:22:55.200000Z,2025-01-19T07:42:07.200000Z,2.32,1,DIURNO,,
nao_cumprida,,,SIM_029_SBTT_SBSN,29,SBTT,SBSN,2025-01-20T02:58:12Z,2025-01-20T05:46:12Z,2.80,2,DIURNO,,
nao_cumprida,,,SIM_030_SWBC_SBSN,30,SWBC,SBSN,2025-01-20T15:51:10.800000Z,2025-01-20T18:05:34.800000Z,2.24,2,DIURNO,,
nao_cumprida,,,SIM_031_SBMN_SBTT,31,SBMN,SBTT,2025-01-21T19:19:40.800000Z,2025-01-21T20:35:52.800000Z,1.27,2,DIURNO,,
nao_cumprida,,,SIM_032_SBMN_SBBE,32,SBMN,SBBE,2025-01-21T19:28:22.800000Z,2025-01-21T22:36:46.800000Z,3.14,5,DIURNO,,
nao_cumprida,,,SIM_033_SBBV_SBSN,33,SBBV,SBSN,2025-01-22T22:56:56.400000Z,2025-01-23T00:56:56.400000Z,2.00,1,DIURNO,,
nao_cumprida,,,SIM_034_SBTS_SBMN,34,SBTS,SBMN,2025-01-23T00:14:45.600000Z,2025-01-23T02:54:57.600000Z,2.67,2,DIURNO,,
nao_cumprida,,,SIM_035_SBTT_SBMN,35,SBTT,SBMN,2025-01-23T02:00:57.600000Z,2025-01-23T03:12:21.600000Z,1.19,1,DIURNO,,
nao_cumprida,,,SIM_038_SBBV_SBBE,38,SBBV,SBBE,2025-01-23T23:08:38.400000Z,2025-01-24T02:18:50.400000Z,3.17,2,DIURNO,,
nao_cumprida,,,SIM_039_SBSN_SBMN,39,SBSN,SBMN,2025-01-24T02:44:38.400000Z,2025-01-24T03:59:02.400000Z,1.24,1,DIURNO,,
nao_cumprida,,,SIM_040_SBSN_SBMN,40,SBSN,SBMN,2025-01-24T07:16:48Z,2025-01-24T08:30:36Z,1.23,4,DIURNO,,
nao_cumprida,,,SIM_043_SBMN_SBPV,43,SBMN,SBPV,2025-01-26T17:47:06Z,2025-01-26T19:21:54Z,1.58,4,DIURNO,,
nao_cumprida,,,SIM_044_SBBE_SBPV,44,SBBE,SBPV,2025-01-27T08:03:00Z,2025-01-27T12:37:48Z,4.58,3,DIURNO,,
nao_cumprida,,,SIM_046_SBMN_SBUA,46,SBMN,SBUA,2025-01-28T01:26:06Z,2025-01-28T03:14:42Z,1.81,3,DIURNO,,
nao_cumprida,,,SIM_047_SWBC_SBTS,47,SWBC,SBTS,2025-01-28T22:14:31.200000Z,2025-01-29T00:03:07.200000Z,1.81,2,DIURNO,,
nao_cumprida,,,SIM_048_SBSN_SBUA,48,SBSN,SBUA,2025-01-29T19:44:38.400000Z,2025-01-29T22:51:14.400000Z,3.11,1,DIURNO,,
nao_cumprida,,,SIM_049_SBMN_SBMY,49,SBMN,SBMY,2025-01-30T17:21:57.600000Z,2025-01-30T18:08:09.600000Z,0.77,4,DIURNO,,
nao_cumprida,,,SIM_050_SBTT_SBPV,50,SBTT,SBPV,2025-01-31T12:02:13.200000Z,2025-01-31T13:22:01.200000Z,1.33,1,DIURNO,,
1 status aeronave ordem id om orig dest partida_utc chegada_utc dur_h prioridade periodo aeronave_real_2025 linhas_origem
2 cumprida 2800 1 SIM_007_SBTS_SBMN 7 SBTS SBMN 2025-01-05T08:52:04.800000Z 2025-01-05T11:40:04.800000Z 2.80 2 DIURNO
3 cumprida 2800 2 SIM_008_SBBE_SBTS 8 SBBE SBTS 2025-01-05T16:11:09.600000Z 2025-01-05T21:53:09.600000Z 5.70 4 DIURNO
4 cumprida 2800 3 SIM_015_SBMN_SBPV 15 SBMN SBPV 2025-01-12T01:42:18Z 2025-01-12T03:21:54Z 1.66 1 DIURNO
5 cumprida 2800 4 SIM_021_SBMN_SBPV 21 SBMN SBPV 2025-01-16T06:26:09.600000Z 2025-01-16T08:12:21.600000Z 1.77 1 DIURNO
6 cumprida 2800 5 SIM_037_SBBV_SBMN 37 SBBV SBMN 2025-01-23T20:18:28.800000Z 2025-01-23T21:50:52.800000Z 1.54 3 DIURNO
7 cumprida 2800 6 SIM_042_SBBE_SBSN 42 SBBE SBSN 2025-01-25T14:54:14.400000Z 2025-01-25T16:35:02.400000Z 1.68 5 DIURNO
8 cumprida 2800 7 SIM_045_SBTS_SBMN 45 SBTS SBMN 2025-01-28T01:21:25.200000Z 2025-01-28T03:43:01.200000Z 2.36 4 DIURNO
9 cumprida 2803 1 SIM_006_SBSN_SBBE 6 SBSN SBBE 2025-01-05T06:29:20.400000Z 2025-01-05T08:16:44.400000Z 1.79 2 DIURNO
10 cumprida 2803 2 SIM_023_SBBV_SWBC 23 SBBV SWBC 2025-01-17T11:36:32.400000Z 2025-01-17T12:47:20.400000Z 1.18 3 DIURNO
11 cumprida 2803 3 SIM_028_SBMN_SBTT 28 SBMN SBTT 2025-01-19T05:45:50.400000Z 2025-01-19T07:06:14.400000Z 1.34 2 DIURNO
12 cumprida 2809 1 SIM_036_SBPV_SWBC 36 SBPV SWBC 2025-01-23T18:49:19.200000Z 2025-01-23T21:00:07.200000Z 2.18 3 DIURNO
13 cumprida 2811 1 SIM_001_SBMN_SBMY 1 SBMN SBMY 2025-01-01T02:22:55.200000Z 2025-01-01T03:09:07.200000Z 0.77 1 DIURNO
14 cumprida 2811 2 SIM_003_SBMN_SBPV 3 SBMN SBPV 2025-01-04T06:06:21.600000Z 2025-01-04T07:48:57.600000Z 1.71 1 DIURNO
15 cumprida 2811 3 SIM_005_SBSN_SBTS 5 SBSN SBTS 2025-01-05T05:32:09.600000Z 2025-01-05T09:50:09.600000Z 4.30 2 DIURNO
16 cumprida 2811 4 SIM_025_SWBC_SBMN 25 SWBC SBMN 2025-01-18T08:18:36Z 2025-01-18T09:11:24Z 0.88 3 DIURNO
17 cumprida 2811 5 SIM_041_SBBE_SBTS 41 SBBE SBTS 2025-01-24T12:09:03.600000Z 2025-01-24T17:48:03.600000Z 5.65 1 DIURNO
18 nao_cumprida SIM_002_SBBE_SBTT 2 SBBE SBTT 2025-01-03T00:34:55.200000Z 2025-01-03T04:33:07.200000Z 3.97 1 DIURNO
19 nao_cumprida SIM_004_SBMN_SBUA 4 SBMN SBUA 2025-01-04T12:04:51.600000Z 2025-01-04T13:55:51.600000Z 1.85 1 DIURNO
20 nao_cumprida SIM_009_SBMY_SBTS 9 SBMY SBTS 2025-01-05T20:05:16.800000Z 2025-01-05T22:20:52.800000Z 2.26 2 DIURNO
21 nao_cumprida SIM_010_SBTT_SBMN 10 SBTT SBMN 2025-01-05T21:25:37.200000Z 2025-01-05T22:33:25.200000Z 1.13 1 DIURNO
22 nao_cumprida SIM_011_SBBE_SBSN 11 SBBE SBSN 2025-01-07T12:04:04.800000Z 2025-01-07T13:40:40.800000Z 1.61 1 DIURNO
23 nao_cumprida SIM_012_SWBC_SBSN 12 SWBC SBSN 2025-01-08T16:32:16.800000Z 2025-01-08T18:52:40.800000Z 2.34 3 DIURNO
24 nao_cumprida SIM_013_SBMN_SBBV 13 SBMN SBBV 2025-01-09T23:25:44.400000Z 2025-01-10T01:05:20.400000Z 1.66 2 DIURNO
25 nao_cumprida SIM_014_SBMN_SBTS 14 SBMN SBTS 2025-01-11T09:41:20.400000Z 2025-01-11T12:08:20.400000Z 2.45 1 DIURNO
26 nao_cumprida SIM_016_SBPV_SBMN 16 SBPV SBMN 2025-01-12T18:25:08.400000Z 2025-01-12T20:11:56.400000Z 1.78 1 DIURNO
27 nao_cumprida SIM_017_SBSN_SBMN 17 SBSN SBMN 2025-01-13T06:27:18Z 2025-01-13T07:41:42Z 1.24 4 DIURNO
28 nao_cumprida SIM_018_SBSN_SBBV 18 SBSN SBBV 2025-01-14T02:28:12Z 2025-01-14T04:36:36Z 2.14 1 DIURNO
29 nao_cumprida SIM_019_SBBV_SBMN 19 SBBV SBMN 2025-01-15T07:32:56.400000Z 2025-01-15T09:12:32.400000Z 1.66 2 DIURNO
30 nao_cumprida SIM_020_SWBC_SBTS 20 SWBC SBTS 2025-01-16T02:14:27.600000Z 2025-01-16T04:25:51.600000Z 2.19 4 DIURNO
31 nao_cumprida SIM_022_SBMN_SBBV 22 SBMN SBBV 2025-01-16T12:38:16.800000Z 2025-01-16T14:04:40.800000Z 1.44 1 DIURNO
32 nao_cumprida SIM_024_SBMY_SBBV 24 SBMY SBBV 2025-01-17T22:04:04.800000Z 2025-01-18T00:30:28.800000Z 2.44 1 DIURNO
33 nao_cumprida SIM_026_SBUA_SBTT 26 SBUA SBTT 2025-01-18T20:18:46.800000Z 2025-01-18T21:21:10.800000Z 1.04 3 DIURNO
34 nao_cumprida SIM_027_SBTS_SBMY 27 SBTS SBMY 2025-01-19T05:22:55.200000Z 2025-01-19T07:42:07.200000Z 2.32 1 DIURNO
35 nao_cumprida SIM_029_SBTT_SBSN 29 SBTT SBSN 2025-01-20T02:58:12Z 2025-01-20T05:46:12Z 2.80 2 DIURNO
36 nao_cumprida SIM_030_SWBC_SBSN 30 SWBC SBSN 2025-01-20T15:51:10.800000Z 2025-01-20T18:05:34.800000Z 2.24 2 DIURNO
37 nao_cumprida SIM_031_SBMN_SBTT 31 SBMN SBTT 2025-01-21T19:19:40.800000Z 2025-01-21T20:35:52.800000Z 1.27 2 DIURNO
38 nao_cumprida SIM_032_SBMN_SBBE 32 SBMN SBBE 2025-01-21T19:28:22.800000Z 2025-01-21T22:36:46.800000Z 3.14 5 DIURNO
39 nao_cumprida SIM_033_SBBV_SBSN 33 SBBV SBSN 2025-01-22T22:56:56.400000Z 2025-01-23T00:56:56.400000Z 2.00 1 DIURNO
40 nao_cumprida SIM_034_SBTS_SBMN 34 SBTS SBMN 2025-01-23T00:14:45.600000Z 2025-01-23T02:54:57.600000Z 2.67 2 DIURNO
41 nao_cumprida SIM_035_SBTT_SBMN 35 SBTT SBMN 2025-01-23T02:00:57.600000Z 2025-01-23T03:12:21.600000Z 1.19 1 DIURNO
42 nao_cumprida SIM_038_SBBV_SBBE 38 SBBV SBBE 2025-01-23T23:08:38.400000Z 2025-01-24T02:18:50.400000Z 3.17 2 DIURNO
43 nao_cumprida SIM_039_SBSN_SBMN 39 SBSN SBMN 2025-01-24T02:44:38.400000Z 2025-01-24T03:59:02.400000Z 1.24 1 DIURNO
44 nao_cumprida SIM_040_SBSN_SBMN 40 SBSN SBMN 2025-01-24T07:16:48Z 2025-01-24T08:30:36Z 1.23 4 DIURNO
45 nao_cumprida SIM_043_SBMN_SBPV 43 SBMN SBPV 2025-01-26T17:47:06Z 2025-01-26T19:21:54Z 1.58 4 DIURNO
46 nao_cumprida SIM_044_SBBE_SBPV 44 SBBE SBPV 2025-01-27T08:03:00Z 2025-01-27T12:37:48Z 4.58 3 DIURNO
47 nao_cumprida SIM_046_SBMN_SBUA 46 SBMN SBUA 2025-01-28T01:26:06Z 2025-01-28T03:14:42Z 1.81 3 DIURNO
48 nao_cumprida SIM_047_SWBC_SBTS 47 SWBC SBTS 2025-01-28T22:14:31.200000Z 2025-01-29T00:03:07.200000Z 1.81 2 DIURNO
49 nao_cumprida SIM_048_SBSN_SBUA 48 SBSN SBUA 2025-01-29T19:44:38.400000Z 2025-01-29T22:51:14.400000Z 3.11 1 DIURNO
50 nao_cumprida SIM_049_SBMN_SBMY 49 SBMN SBMY 2025-01-30T17:21:57.600000Z 2025-01-30T18:08:09.600000Z 0.77 4 DIURNO
51 nao_cumprida SIM_050_SBTT_SBPV 50 SBTT SBPV 2025-01-31T12:02:13.200000Z 2025-01-31T13:22:01.200000Z 1.33 1 DIURNO

View File

@@ -0,0 +1,23 @@
status,aeronave,ordem,id,om,orig,dest,partida_utc,chegada_utc,dur_h,prioridade,periodo,aeronave_real_2025,linhas_origem
cumprida,2800,1,SIM_007_SBMN_SBCO,7,SBMN,SBCO,2025-01-09T02:09:28.800000Z,2025-01-09T07:58:40.800000Z,5.82,1,DIURNO,,
cumprida,2800,2,SIM_010_SBCY_SBMN,10,SBCY,SBMN,2025-01-11T23:29:27.600000Z,2025-01-12T03:09:39.600000Z,3.67,3,DIURNO,,
cumprida,2800,3,SIM_012_SWCA_SBTT,12,SWCA,SBTT,2025-01-12T14:52:01.200000Z,2025-01-12T16:14:49.200000Z,1.38,3,DIURNO,,
cumprida,2800,4,SIM_019_SBMN_SWCA,19,SBMN,SWCA,2025-01-29T04:57:54Z,2025-01-29T06:36:54Z,1.65,1,DIURNO,,
cumprida,2800,5,SIM_020_SWCA_SBCO,20,SWCA,SBCO,2025-01-31T03:09:10.800000Z,2025-01-31T08:39:46.800000Z,5.51,2,DIURNO,,
inspecao,2800,,INSP_2800_3,INSP 2A,SBMN,SBMN,2025-01-12T16:14:49.200000Z,2025-01-16T16:14:49.200000Z,96.00,,,,
cumprida,2803,1,SIM_006_SWBC_SBAN,6,SWBC,SBAN,2025-01-06T21:08:24Z,2025-01-07T00:56:24Z,3.80,2,DIURNO,,
cumprida,2809,1,SIM_001_SBBE_SBMN,1,SBBE,SBMN,2025-01-02T07:53:34.800000Z,2025-01-02T11:07:22.800000Z,3.23,2,DIURNO,,
cumprida,2809,2,SIM_002_SBPV_SBOI,2,SBPV,SBOI,2025-01-03T03:03:10.800000Z,2025-01-03T09:37:58.800000Z,6.58,3,DIURNO,,
cumprida,2809,3,SIM_005_SBMY_SBYS,5,SBMY,SBYS,2025-01-04T19:37:19.200000Z,2025-01-05T01:06:43.200000Z,5.49,1,DIURNO,,
cumprida,2809,4,SIM_008_SBPV_SBMN,8,SBPV,SBMN,2025-01-09T23:11:34.800000Z,2025-01-10T00:51:10.800000Z,1.66,2,DIURNO,,
cumprida,2809,5,SIM_011_SBSN_SBVH,11,SBSN,SBVH,2025-01-12T01:26:24Z,2025-01-12T04:15:36Z,2.82,1,DIURNO,,
cumprida,2809,6,SIM_016_SBTF_SBMN,16,SBTF,SBMN,2025-01-24T15:25:55.200000Z,2025-01-24T16:40:19.200000Z,1.24,3,DIURNO,,
inspecao,2809,,INSP_2809_3,INSP 2A,SBMN,SBMN,2025-01-05T01:06:43.200000Z,2025-01-09T01:06:43.200000Z,96.00,,,,
cumprida,2811,1,SIM_003_SBMN_SBUY,3,SBMN,SBUY,2025-01-03T09:50:20.400000Z,2025-01-03T16:17:56.400000Z,6.46,2,DIURNO,,
cumprida,2811,2,SIM_004_SWCA_SBMN,4,SWCA,SBMN,2025-01-04T03:01:19.200000Z,2025-01-04T04:51:43.200000Z,1.84,3,DIURNO,,
cumprida,2811,3,SIM_009_SBUY_SBCC,9,SBUY,SBCC,2025-01-11T12:27:50.400000Z,2025-01-11T17:17:02.400000Z,4.82,1,DIURNO,,
cumprida,2811,4,SIM_013_SBUY_SBMN,13,SBUY,SBMN,2025-01-15T01:13:04.800000Z,2025-01-15T07:31:40.800000Z,6.31,3,DIURNO,,
cumprida,2811,5,SIM_014_SBBE_SBUA,14,SBBE,SBUA,2025-01-20T05:25:19.200000Z,2025-01-20T10:31:55.200000Z,5.11,2,DIURNO,,
cumprida,2811,6,SIM_015_SBBE_SBTS,15,SBBE,SBTS,2025-01-21T05:22:01.200000Z,2025-01-21T10:28:37.200000Z,5.11,4,DIURNO,,
cumprida,2811,7,SIM_017_SWBC_SBCC,17,SWBC,SBCC,2025-01-27T03:48:10.800000Z,2025-01-27T07:24:46.800000Z,3.61,2,DIURNO,,
cumprida,2811,8,SIM_018_SBMY_SBPV,18,SBMY,SBPV,2025-01-28T01:55:19.200000Z,2025-01-28T03:00:07.200000Z,1.08,2,DIURNO,,
1 status aeronave ordem id om orig dest partida_utc chegada_utc dur_h prioridade periodo aeronave_real_2025 linhas_origem
2 cumprida 2800 1 SIM_007_SBMN_SBCO 7 SBMN SBCO 2025-01-09T02:09:28.800000Z 2025-01-09T07:58:40.800000Z 5.82 1 DIURNO
3 cumprida 2800 2 SIM_010_SBCY_SBMN 10 SBCY SBMN 2025-01-11T23:29:27.600000Z 2025-01-12T03:09:39.600000Z 3.67 3 DIURNO
4 cumprida 2800 3 SIM_012_SWCA_SBTT 12 SWCA SBTT 2025-01-12T14:52:01.200000Z 2025-01-12T16:14:49.200000Z 1.38 3 DIURNO
5 cumprida 2800 4 SIM_019_SBMN_SWCA 19 SBMN SWCA 2025-01-29T04:57:54Z 2025-01-29T06:36:54Z 1.65 1 DIURNO
6 cumprida 2800 5 SIM_020_SWCA_SBCO 20 SWCA SBCO 2025-01-31T03:09:10.800000Z 2025-01-31T08:39:46.800000Z 5.51 2 DIURNO
7 inspecao 2800 INSP_2800_3 INSP 2A SBMN SBMN 2025-01-12T16:14:49.200000Z 2025-01-16T16:14:49.200000Z 96.00
8 cumprida 2803 1 SIM_006_SWBC_SBAN 6 SWBC SBAN 2025-01-06T21:08:24Z 2025-01-07T00:56:24Z 3.80 2 DIURNO
9 cumprida 2809 1 SIM_001_SBBE_SBMN 1 SBBE SBMN 2025-01-02T07:53:34.800000Z 2025-01-02T11:07:22.800000Z 3.23 2 DIURNO
10 cumprida 2809 2 SIM_002_SBPV_SBOI 2 SBPV SBOI 2025-01-03T03:03:10.800000Z 2025-01-03T09:37:58.800000Z 6.58 3 DIURNO
11 cumprida 2809 3 SIM_005_SBMY_SBYS 5 SBMY SBYS 2025-01-04T19:37:19.200000Z 2025-01-05T01:06:43.200000Z 5.49 1 DIURNO
12 cumprida 2809 4 SIM_008_SBPV_SBMN 8 SBPV SBMN 2025-01-09T23:11:34.800000Z 2025-01-10T00:51:10.800000Z 1.66 2 DIURNO
13 cumprida 2809 5 SIM_011_SBSN_SBVH 11 SBSN SBVH 2025-01-12T01:26:24Z 2025-01-12T04:15:36Z 2.82 1 DIURNO
14 cumprida 2809 6 SIM_016_SBTF_SBMN 16 SBTF SBMN 2025-01-24T15:25:55.200000Z 2025-01-24T16:40:19.200000Z 1.24 3 DIURNO
15 inspecao 2809 INSP_2809_3 INSP 2A SBMN SBMN 2025-01-05T01:06:43.200000Z 2025-01-09T01:06:43.200000Z 96.00
16 cumprida 2811 1 SIM_003_SBMN_SBUY 3 SBMN SBUY 2025-01-03T09:50:20.400000Z 2025-01-03T16:17:56.400000Z 6.46 2 DIURNO
17 cumprida 2811 2 SIM_004_SWCA_SBMN 4 SWCA SBMN 2025-01-04T03:01:19.200000Z 2025-01-04T04:51:43.200000Z 1.84 3 DIURNO
18 cumprida 2811 3 SIM_009_SBUY_SBCC 9 SBUY SBCC 2025-01-11T12:27:50.400000Z 2025-01-11T17:17:02.400000Z 4.82 1 DIURNO
19 cumprida 2811 4 SIM_013_SBUY_SBMN 13 SBUY SBMN 2025-01-15T01:13:04.800000Z 2025-01-15T07:31:40.800000Z 6.31 3 DIURNO
20 cumprida 2811 5 SIM_014_SBBE_SBUA 14 SBBE SBUA 2025-01-20T05:25:19.200000Z 2025-01-20T10:31:55.200000Z 5.11 2 DIURNO
21 cumprida 2811 6 SIM_015_SBBE_SBTS 15 SBBE SBTS 2025-01-21T05:22:01.200000Z 2025-01-21T10:28:37.200000Z 5.11 4 DIURNO
22 cumprida 2811 7 SIM_017_SWBC_SBCC 17 SWBC SBCC 2025-01-27T03:48:10.800000Z 2025-01-27T07:24:46.800000Z 3.61 2 DIURNO
23 cumprida 2811 8 SIM_018_SBMY_SBPV 18 SBMY SBPV 2025-01-28T01:55:19.200000Z 2025-01-28T03:00:07.200000Z 1.08 2 DIURNO

BIN
db/raw/ICA 66-31 2023.pdf Normal file

Binary file not shown.

BIN
db/raw/Planilha 2025.xlsx Normal file

Binary file not shown.

257
docs/about.md Normal file
View File

@@ -0,0 +1,257 @@
# Documentação Técnica — Arara OAMRP
## O que é
O Arara OAMRP é um projeto para organizar dados, documentos e modelos usados no planejamento e acompanhamento operacional do Esquadrão Arara, em Manaus.
O objetivo é construir uma base rastreável para encaixar missões, aeronaves e manutenções, começando pela leitura e estruturação de documentos operacionais e de manutenção.
Nesta fase inicial, o projeto contém:
- documentos originais em `db/raw/`;
- scripts e saídas intermediárias em `db/pre_process/`;
- área reservada para dados finais em `db/processed/`;
- protótipos de otimização e análise em `software/`;
- documentação de contexto, autoria, mudanças e rastreabilidade.
## Fontes de dados
As fontes atuais são documentos operacionais e de manutenção adicionados manualmente ao repositório.
Fonte inicial processada:
| Arquivo | Conteúdo | Observação |
| --- | --- | --- |
| `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 `db/raw/` sem edição direta.
## Como funciona
O fluxo atual é:
1. Guardar documentos originais em `db/raw/`.
2. Executar scripts de extração e padronização em `db/pre_process/`.
3. Gerar saídas intermediárias em texto, JSON e CSV.
4. Manter campos originais para rastreabilidade.
5. Separar campos discretos úteis para cálculo, comparação e planejamento.
6. Registrar mudanças relevantes em `LOG.md`.
7. Consolidar dados finais em `db/processed/` quando os critérios de qualidade estiverem definidos.
8. Desenvolver protótipos e modelos em `software/`, mantendo dados e código separados.
## Pré-processamento atual
O script `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
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_texto.txt
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json
db/pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv
```
## Modelo OAMRP v3 (modelo atual)
O arquivo `software/oamrp_v3.py` é o modelo principal. Implementa o *Operational Aircraft Maintenance Routing Problem* (Al-Thani, Ben Ahmed, Haouari, 2016, Transportation Research Part C, vol. 72, pp. 2944) sobre uma rede espaço-tempo com arcos de ferry.
### Rede espaço-tempo
A rede tem quatro tipos de nó:
| Tipo | Descrição |
| --- | --- |
| `origem` | Nó de partida de cada aeronave (base inicial, t=0) |
| `missao` | Uma missão específica; `aeronave_id=None` (qualquer aeronave pode voar) |
| `inspecao` | Nó de manutenção; exclusivo de uma aeronave (`aeronave_id=k`) |
| `sumidouro` | Nó de chegada ao fim do horizonte |
Os arcos são: `inicio`, `missao_missao`, `missao_insp`, `insp_missao`, `missao_fim` e `ferry` (quando bases forem distintas). Ferry usa velocidade 430 km/h e distância haversine.
### Nomenclatura (Al-Thani, 2016)
| Símbolo | Significado | Campo no código |
| --- | --- | --- |
| F | Intervalo máximo legal entre manutenções | `InspecaoParam.f_max` |
| f_k | Horas acumuladas no início | `Aeronave.f0` |
| LRT = F f_k | Tempo legal restante (decresce) | `lrt_inicial()` |
| H[k,i,nid] | Horas acumuladas em tempo real (cresce, reseta na inspeção) | `H[k,seq,nid]` |
### Restrições implementadas
- **C1**: cada missão coberta por no máximo uma aeronave.
- **C2**: conservação de fluxo em nós de missão e inspeção.
- **C3**: continuidade espacial (base de chegada = base de partida do próximo arco; ferry gerado automaticamente).
- **C4**: continuidade temporal (partida ≥ chegada + TAT 1,5h).
- **C5**: missões EVAM (`obrigatoria=True`) devem ser cumpridas.
- **C6**: `H[k,i,nid] ≤ f_max` para cada tipo de inspeção, com reset após nó de inspeção.
- **C9**: downtime de inspeção (aeronave fica indisponível durante o slot de manutenção).
- **C11**: slot único de inspeção por base — no máximo uma aeronave em manutenção a cada instante.
### Objetivos
- **L1**: maximizar cobertura ponderada `Σ prioridade_m × z_m` (missões cobertas).
- **L2**: maximizar horas voadas, dado o L1 fixado.
### Execução típica
```powershell
python software/oamrp_v3.py --sintetico --seed 42 --aleatorio --ofrag db/processed/ofrag.csv
```
Gera missões sintéticas cross-base, sorteia `f0` aleatório e carrega prioridades do OFRAG.
### Scripts auxiliares
| Script | Função |
| --- | --- |
| `software/gerar_ofrag.py` | Gera `db/processed/ofrag.csv` com ordens de missão (OFRAG), prioridades 15 e tipo |
| `software/visualizar_resultado.py` | Lê o CSV de resultado e gera `db/processed/mapa_rotas.html` (mapa Folium interativo) |
### Resultado validado (2026-06-16)
Rodada sintética com 50 missões, seed 42, horas iniciais aleatórias e OFRAG: 50/50 missões cumpridas, solver CBC Optimal, todas as 4 EVAMs operacionais.
---
## Modelo OAMRP v1 (protótipo inicial)
O arquivo `software/oamrp_v1.py` contém o protótipo inicial, substituído pelo v3. Mantido no repositório para referência histórica.
Campos extraídos e estruturados:
| Campo | Conteúdo |
| --- | --- |
| `seq` | Sequência da inspeção no relatório |
| `sigla_mnt` | Sigla da manutenção |
| `descricao_mnt` | Descrição da manutenção |
| `referencia` | Referência textual da inspeção |
| `tipo_vencimento` | Tipo de vencimento |
| `zera_tso` | Coluna original `Zera TSO` |
| `letra` | Letra da inspeção, quando preenchida |
| `nivel` | Código do nível |
| `nivel_descricao` | Descrição do nível |
| `var_media` | Variação média informada no relatório |
| `duracao` | Duração original, por exemplo `3 D` ou `6 H` |
| `duracao_valor` | Valor numérico da duração |
| `duracao_unidade` | Unidade normalizada: `dias` ou `horas` |
| `controle_original` | Trecho original usado para rastreabilidade |
| `intervalo_horas_voo` | Intervalo textual em horas de voo |
| `intervalo_horas_voo_valor` | Valor numérico de horas de voo |
| `intervalo_meses_continuos` | Intervalo textual em meses contínuos |
| `intervalo_meses_continuos_valor` | Valor numérico de meses contínuos |
| `intervalo_pousos` | Intervalo textual em pousos |
| `intervalo_pousos_valor` | Valor numérico de pousos |
| `linha_original` | Linha reconstruída a partir do texto extraído |
## Convenções operacionais
Níveis de manutenção:
| Código | Descrição |
| --- | --- |
| `B` | Base |
| `P` | Parque |
| `O` | Orgânico |
Unidades de duração:
| Código | Descrição |
| --- | --- |
| `D` | Dias |
| `H` | Horas |
## Limitações conhecidas
1. A extração depende da qualidade do PDF e do texto recuperado por `pdfplumber`.
2. O parser foi calibrado para o layout atual do relatório de ciclo de inspeções.
3. Mudanças no formato do relatório podem exigir ajuste no script.
4. A grafia original é preservada, inclusive erros existentes no documento fonte.
5. Ainda não há validação cruzada com outros sistemas ou fontes oficiais.
6. O modelo em `software/oamrp_v1.py` ainda é protótipo e usa dados de exemplo a substituir por bases reais.
7. Ainda não há modelo final em `db/processed/` para integração completa entre missões, aeronaves e manutenções.
## Oportunidades de melhoria
| Prioridade | Melhoria |
| --- | --- |
| Alta | Definir o modelo final de dados em `db/processed/` |
| Alta | Criar uma tabela mestre de aeronaves |
| Alta | Criar uma tabela mestre de missões |
| Alta | Definir regras para cálculo de vencimento por horas, meses e pousos |
| Média | Criar testes automatizados para o parser de PDF |
| Média | Padronizar nomes de colunas entre todos os documentos |
| Média | Criar relatórios de inconsistências e campos ausentes |
| Baixa | Criar interface simples para consulta e atualização |
## Solução de problemas
Se o pré-processamento falhar, verifique:
- se o PDF existe em `db/raw/`;
- se o nome do arquivo no script corresponde ao arquivo real;
- se as bibliotecas Python necessárias estão instaladas;
- se o layout do PDF mudou;
- se o texto extraído em `_texto.txt` contém as linhas esperadas.
Para reexecutar o pré-processamento:
```powershell
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
```
## Modelo OAMRP v2
O arquivo `software/oamrp_v2.py` e a primeira versao do modelo usando o registro de voo 2025 consolidado.
Entradas principais:
- `db/processed/registro_voo_2025_consolidado.csv`;
- janela padrao de `2025-01-01` ate `2025-02-01` exclusivo;
- aeronaves reais `2800`, `2803`, `2809` e `2811`;
- base inicial provisoria `SBMN`;
- base de manutencao `SBMN`;
- prioridade uniforme: todas as missoes recebem peso 1.
Saida principal:
```text
db/processed/resultado_oamrp_jan_2025.csv
```
Resultado da primeira execucao:
- status do solver: `Optimal`;
- missoes cumpridas: 50/61;
- prioridade obtida: 50/61.
Limitacao atual: o limite de horas foi deixado alto para validar primeiro a continuidade espacial/temporal. A calibracao com dados reais de manutencao ainda deve ser feita em etapa posterior.

45
docs/authors.md Normal file
View File

@@ -0,0 +1,45 @@
# Autoria e Contribuições
## Colaboradores
| Tag | Nome | Gitea | Papel inicial |
| --- | --- | --- | --- |
| `VTO` | Vitor Cesa | [@vitorcesavc](https://git.ppgao.ita.br/vitorcesavc) | Organização do repositório, estrutura inicial, pré-processamento e documentação |
| `GNR` | Generoso | — | Colaborador |
| `JOM` | João Marcos | — | Colaborador e origem inicial do relatório de inspeções |
## Instituição e contexto
Projeto desenvolvido para apoiar o OAMRP do Esquadrão Arara, em Manaus, com organização colaborativa em repositório Git.
## Como contribuir
Ao modificar ou estender este projeto:
1. Atualize sua cópia com `git pull` antes de começar.
2. Preserve documentos originais em `db/raw/`.
3. Coloque scripts e saídas intermediárias em `db/pre_process/`.
4. Coloque resultados finais e validados em `db/processed/`.
5. Registre mudanças operacionais em `LOG.md`.
6. Registre mudanças de versão em `docs/changelog.md` quando houver alteração significativa.
7. Identifique o autor usando a tag do colaborador: `VTO`, `GNR` ou `JOM`.
8. Use mensagens de commit curtas e objetivas.
## Formato sugerido para mudanças de versão
```text
## v<versão> — <Autor ou tag> — <YYYY-MM>
### Adicionado / Alterado / Corrigido / Removido
- Descrição objetiva da mudança.
```
## Histórico de contribuições
| Versão | Autor | Data | Descrição resumida |
| --- | --- | --- | --- |
| v0.2 | VTO | 2026-06 | Reorganização dos dados em `db/`, criação de `software/` e inclusão do protótipo MIP `oamrp_v1.py` |
| v0.1 | VTO | 2026-06 | Estrutura inicial, documentação, pré-processamento do primeiro relatório de inspeções e migração para o repositório `arara_oarmp` |
Contribuidores futuros devem adicionar uma linha nesta tabela e criar a entrada correspondente em `docs/changelog.md`.

226
docs/changelog.md Normal file
View File

@@ -0,0 +1,226 @@
# Changelog
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:
```text
## v<versão> — <Autor ou tag> — <YYYY-MM>
### Adicionado / Alterado / Corrigido / Removido
- Descrição objetiva.
```
---
## v0.10 — VTO — 2026-06
### Alterado
- **Set Covering → Set Partitioning (C1):** a restrição de cobertura de missões foi alterada de `Σ_k y_entrada ≤ 1` (Set Covering — missão opcional) para `Σ_k y_entrada = 1` (Set Partitioning — toda missão obrigatória). Isso reflete a premissa operacional de que cada missão deve ser realizada por exatamente uma aeronave, sem possibilidade de missões descobertas. A distinção anterior entre missões "hard" (EVAM) e "opcionais" foi eliminada.
- **Número padrão de missões sintéticas reduzido de 50 para 20** (`--n-missoes`, `n_evam` de 5 para 4): Set Partitioning é mais restritivo e, com janelas de tempo fixas e restrições de inspeção simultâneas, 50 missões tornavam o MIP difícil de resolver ou inviável. Com 20 missões, a factibilidade é garantida na margem operacional de 4 aeronaves.
- **Time limit padrão do solver CBC fixado em 120 s** (era ilimitado). O solver agora nunca trava indefinidamente; retorna a melhor solução encontrada dentro do prazo. O CLI mantém `--time-limit` para ajuste.
- **Correção de display do status do solver:** PuLP redefinía `mdl.status` para "Not Solved" ao reutilizar o modelo em L2 (após `setObjective`). O status de L1 é agora preservado quando L2 reporta 0, evitando exibição enganosa.
- **Cenário `f0_cenario_insp_grande.json` revisado:** apenas a matrícula 2803 recebe `f0=595h` para INSP 2A (F_max=600h, downtime=4 dias), forçando a inspeção aparecer no Gantt. As demais matrículas partem com `f0=0` (plena disponibilidade), evitando que o problema se torne inviável por múltiplos relógios apertados simultaneamente.
### Adicionado
- `db/processed/planejamento_insp_grande.html`: visualização Gantt + mapa + tabela para o cenário com inspeção de 4 dias (INSP 2A — INSP CHECK 2A, 96 h de solo).
### Limitações conhecidas (para contribuidores)
1. **Escopo validado restrito:** Set Partitioning foi validado apenas para o cenário sintético com 20 missões, seed 42, 4 aeronaves e somente uma matrícula (2803) com LRT apertado. Fora desse envelope, o MIP pode ser infeasible ou ultrapassar o time limit sem solução.
2. **Missões sintéticas incluem bases fora da Amazônia** (SBBR, SBGL, SBYS etc.) geradas aleatoriamente pelo seed. Para estudos de caso realistas, o gerador deve ser restrito ao conjunto de bases do Esquadrão Arara.
3. **Folga negativa no relatório:** quando uma aeronave realiza inspeção dentro do horizonte, o campo `folga` exibido (`LRT_ini horas_celula_total`) pode ser negativo porque não desconta o reset do relógio pós-inspeção. Isso é um bug de display; a restrição `H ≤ F_max` está correta no modelo.
4. **LRT muito curto (< 5 h) pode gerar instabilidade numérica no big-M:** o cenário foi configurado com LRT_ini=5 h para 2803, que está próximo do limiar onde o big-M deixa de ser eficaz. Valores menores podem permitir que o solver ignore a restrição por tolerância numérica (ε ≈ 1 e-4 do CBC).
5. **Aeronave 2811 fica ociosa neste cenário:** o solver atribui todas as 20 missões a 3 aeronaves. Isso é matematicamente ótimo, mas pode não ser desejável operacionalmente. Considerar restrição de equilíbrio de carga em versões futuras.
6. **Status "Not Solved" após L2:** a correção atual usa o status de L1 como proxy. Se L1 retornar apenas uma solução feasível (não ótima, por time limit), o modelo pode ser reportado como "Optimal" quando não é. Monitorar o campo `prio_otima` para validar.
---
## v0.2 — VTO — 2026-06
### Adicionado
- Diretório `software/` com o primeiro protótipo `software/oamrp_v1.py`.
- Modelo MIP compacto em PuLP para atribuição de aeronaves a missões opcionais.
- Regras de `.gitignore` para caches Python.
### Alterado
- Estrutura de dados reorganizada para `db/raw/`, `db/pre_process/` e `db/processed/`.
- Documentação atualizada para refletir a separação entre dados (`db/`) e modelos (`software/`).
- Nome técnico do projeto padronizado como Arara OAMRP nos documentos principais.
---
## v0.1 — VTO — 2026-06
### Adicionado
- Repositório Git inicial.
- Estrutura de dados com `raw/`, `pre_process/` e `processed/`.
- Arquivo `README.md` na raiz com guia de uso do repositório.
- Arquivo `CONTEXTO.md` com orientações permanentes do projeto.
- Arquivo `LOG.md` com rastreabilidade operacional por data, hora e tag de autor.
- Pré-processamento inicial do relatório de ciclo de inspeções do C-105 matrícula 2805.
- Saídas intermediárias em texto, JSON e CSV.
- Campos discretos para `zera_tso`, letra, nível, duração, horas de voo, meses contínuos e pousos.
- Documentação em `docs/about.md`, `docs/authors.md` e `docs/changelog.md`.
### Alterado
- Projeto renomeado para `arara_oarmp`.
- Repositório remoto migrado para `https://git.ppgao.ita.br/vitorcesavc/arara_oarmp.git`.
- Documentação reorganizada para separar guia inicial, contexto, autoria, histórico de versão e log operacional.
### Corrigido
- Interpretação da coluna `Zera TSO`, que passou a ser tratada como coluna única.
- Mapeamento dos níveis `B`, `P` e `O` para Base, Parque e Orgânico.
### Observações
- O parser atual foi calibrado para o primeiro PDF processado.
- A grafia original do documento fonte foi preservada, inclusive ocorrências de `INPEÇÃO`.
## v0.3 - VTO - 2026-06
### Adicionado
- Pre-processamento da `Planilha 2025.xlsx` para registro de voo limpo.
- Saidas em CSV/XLSX sem mesclas e CSV OAMRP com `id,orig,dest,partida_utc,chegada_utc,prioridade`.
- Resumo JSON com contagens, campos faltantes e criterios de conversao.
### Alterado
- Criado arquivo final `db/processed/registro_voo_2025.csv`.
### Observacoes
- `SEGMTO` foi preservado da planilha original; cruzamento de 00Z foi registrado em campo separado.
- Colunas entre `REAL` e `COM2` foram descartadas.
## v0.3.1 - VTO - 2026-06
### Alterado
- Registro de voo 2025 regenerado removendo a linha original 793, aeronave 2806.
- Consolidado passou para 1267 trechos e registro limpo para 1420 linhas.
- Documentacao atualizada em `README.md`, `docs/about.md`, `LOG.md` e relatorio de validacao.
### Observacoes
- A remocao foi aplicada no script de pre-processamento para manter reprodutibilidade.
## v0.9 — VTO — 2026-06
### Adicionado
- Flag `--escala` na CLI: carrega missões reais da Escala de Voo Modelo 1 (CSV com separador `;`), com parser de data DD/MMM, horários UTC e prioridade derivada do código de missão (69TV→1, 50TT/05TF→3).
- `BASES_COORDS` expandido de 10 para 26 bases (inclui SBMQ, SBOI, SBBR, SBGL, SBCY, SWEI, SWCA, SWKO, SBUY e outras rotas reais).
- Cenários de f0 em JSON: `f0_cenario_esquadrao.json` (dados realistas do esquadrão) e `f0_cenario_insp_grande.json`.
### Corrigido
- Gantt: FAB 2800 (y=0) estava invisível — corrigido com `yaxis range=[-0.5, n-0.5]`.
- Gantt: barras não renderizavam (`shapes type="rect"`) — substituídas por `go.Scatter mode="lines"` que renderiza garantido no eixo de datas.
- Largura mínima de 2h por barra no Gantt para voos curtos ficarem visíveis.
### Alterado
- `--time-limit` default alterado de 60s para 300s (5 minutos).
- Imports `timedelta` e `date` consolidados no topo do módulo (era importado localmente em funções).
### Resultado validado
Rodada `--sintetico --n-missoes 30 --f0 f0_cenario_esquadrao.json` → 30/30 Optimal; FAB 2809 com 1 inspeção (CHECK 300) visível no Gantt.
---
## v0.8 — VTO — 2026-06
### Adicionado
- Visualização combinada em HTML único: Gantt (Plotly) + mapa (Folium em iframe) + tabela de voos com filtros JS interativos.
- Título "Planejamento de Diagonal de Manutenção" no HTML gerado.
- Exportação de inspeções no CSV de resultado com timestamps UTC reais.
- Filtros na tabela: aeronave, status, prioridade, base e busca livre; ordenação por coluna.
- Flag `--time-limit` na CLI do solver (padrão era 60s).
---
## v0.7 — VTO — 2026-06
### Corrigido
- Bug crítico de conservação de fluxo em nós de inspeção (`oamrp_v3.py`): o solver criava unidades de fluxo fantasma nesses nós, fazendo o relatório exibir 50/50 enquanto o CSV real tinha apenas 16 missões cobertas. Corrigido adicionando C2 para nós `tipo == "inspecao"`.
- Bug de restrição C6 Fase 1 simplificada removida: somava horas totais sem respeitar resets de inspeção, tornando EVAMs 2809 e 2811 artificialmente ociosas. As variáveis `H[k,seq,nid]` com reset já impõem o limite corretamente.
### Adicionado
- Flag `--aleatorio` na CLI: sorteia `f0` entre 10% e 90% de `F_max` por aeronave e tipo de inspeção.
- Tabela OFRAG em `db/processed/ofrag.csv` com 50 ordens sintéticas (prioridades 15, tipo de missão, OM).
- Script `software/gerar_ofrag.py` para gerar ou regen a tabela OFRAG de forma reprodutível.
- Flag `--ofrag` na CLI: carrega prioridades do CSV ao gerar missões sintéticas.
- Visualização interativa `db/processed/mapa_rotas.html` via Folium, com uma camada por aeronave, missões não cumpridas tracejadas e popup com detalhes.
- Script `software/visualizar_resultado.py` para gerar o mapa a partir do CSV de resultado.
### Alterado
- Nomenclatura alinhada com Al-Thani (2016): `lrt_h``f_max`; `horas_iniciais``f0`; `orcamento_h()``lrt_inicial()`; `horas_iniciais_aleatorias()``gerar_f0_aleatorio()`; `carregar_horas_iniciais()``carregar_f0()`; CLI `--horas-iniciais``--f0`.
- Relatório imprime `F_max=`, `f0=` e `LRT_ini=` para cada aeronave, alinhado com a formulação do artigo de referência.
### Resultado validado
Rodada `--sintetico --seed 42 --aleatorio --ofrag db/processed/ofrag.csv` → 50/50 missões cumpridas, solver CBC Optimal, todas as 4 EVAMs com voos e ferry.
---
## v0.6 - VTO - 2026-06
### Adicionado
- Gerador de missões sintéticas cross-base (`gerar_missoes_sinteticas()`): 50 missões entre 10 bases com durações calculadas via haversine ÷ velocidade C-105 (430 km/h); flag `--sintetico`, `--n-missoes`, `--n-evam` na CLI.
- Fase 3: arcos de ferry em `construir_rede()` — arcos `inicio`, `missao_missao`, `missao_insp` e `insp_missao` agora permitem bases distintas com tempo de ferry calculado automaticamente.
- Ferry consome horas de célula no relógio de inspeção (C6: `carga = v.carga_h + a.horas_ferry`).
- Relatório imprime segmentos `[FERRY base1->base2 Xh]` na linha do tempo de cada aeronave.
- Resultado `db/processed/resultado_oamrp_sintetico.csv` para 50 missões sintéticas.
### Alterado
- Dicionário `BASES_COORDS` com coordenadas (lat, lon) das 10 bases operacionais do C-105.
## v0.5 - VTO - 2026-06
### Adicionado
- Modelo `software/oamrp_v3.py` — reescrita do zero com rede espaço-tempo (No/Arco) e variável unificada `y[k,arco]`.
- Fase 1 (C1C6): fluxo de conservação, cobertura de missões, restrição de voo ativo, orçamento de horas por inspeção.
- Fase 2 (C7C11): nós e arcos de inspeção na rede; variáveis `H[k,i,nid]` para relógio de horas com reset; C9 downtime; C11 slot único via variáveis disjuntivas `oo`.
- Documento `HANDOFF_arara_oamrp.md` com formulação matemática, decisões de calibração (Q1Q5) e plano de build incremental.
- Resultado `db/processed/resultado_oamrp_v3.csv` para janeiro/2025.
### Alterado
- TAT corrigido para 1,5h (era 1,0h em v2).
- Top-10 bases por frequência no registro de voo 2025 (Q4).
- Missões EVAM marcadas como obrigatórias (Q2).
- Objetivo L2 definido como maximizar horas voadas (= minimizar folga ao fim do horizonte mensal) (Q3).
### Corrigido
- Bug de escopo de arco: arcos de início/fim agora carregam `aeronave_id` e só entram na variável `y` do aircraft correto.
- Bug `NameError tat_h` em C9b: substituído pela constante global `TAT_H`.
- Bug `AttributeError` em `escrever_resultado`: filtro por `n.tipo == "missao"` antes de acessar `n.missao`.
## v0.4 - VTO - 2026-06
### Adicionado
- Modelo `software/oamrp_v2.py` para rodar o OAMRP com o registro de voo 2025 consolidado.
- Resultado inicial `db/processed/resultado_oamrp_jan_2025.csv`.
### Observacoes
- Rodada padrao usa janeiro/2025, aeronaves reais `2800`, `2803`, `2809` e `2811`, base inicial provisoria `SBMN`, base de manutencao `SBMN` e prioridade uniforme 1.
- Primeira execucao retornou solucao otima com 50 de 61 missoes cumpridas.

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
}

View File

@@ -1 +0,0 @@

108
software/gerar_ofrag.py Normal file
View File

@@ -0,0 +1,108 @@
"""
Gera o arquivo de OFRAG (Ordem de Fragmentação) sintético para o Esquadrão Arara.
Cada OFRAG autoriza uma missão específica e define:
- Prioridade operacional (1=urgente … 5=baixa)
- Tipo de missão (Logística, Médico, Pessoal, Reabastecimento, Reconhecimento)
- Base de origem e destino
Uso:
python software/gerar_ofrag.py --seed 42 --n 50 --saida db/processed/ofrag.csv
"""
import argparse
import csv
import random
from datetime import datetime, timezone
from pathlib import Path
# ---------------------------------------------------------------------------
# Importa constantes e função de geração do modelo principal
# ---------------------------------------------------------------------------
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
from oamrp_v3 import (
BASES_COORDS,
TIPOS_MISSAO,
_PESOS_PRIO,
gerar_missoes_sinteticas,
)
BASE_DIR = Path(__file__).resolve().parents[1]
OFRAG_PADRAO = BASE_DIR / "db" / "processed" / "ofrag.csv"
_TIPOS_EVAM = ["Médico", "Logística"] # EVAM normalmente carrega carga urgente
def gerar_ofrag(
n: int = 50,
seed: int = 42,
saida: Path = OFRAG_PADRAO,
) -> None:
inicio = datetime(2025, 1, 1, tzinfo=timezone.utc)
missoes, _ = gerar_missoes_sinteticas(n=n, seed=seed, inicio=inicio, n_evam=5)
rng = random.Random(seed + 1) # semente diferente para tipos
saida.parent.mkdir(parents=True, exist_ok=True)
colunas = [
"id_ofrag", "missao_id", "om", "orig", "dest",
"prioridade", "tipo_missao", "observacao",
]
with saida.open("w", newline="", encoding="utf-8-sig") as f:
w = csv.DictWriter(f, fieldnames=colunas)
w.writeheader()
for idx, m in enumerate(missoes, start=1):
if m.obrigatoria:
prio = 1
tipo = rng.choice(_TIPOS_EVAM)
obs = "EVAM — execução obrigatória"
else:
prio = rng.choices(range(1, 6), weights=_PESOS_PRIO)[0]
tipo = rng.choices(TIPOS_MISSAO, weights=[20, 15, 25, 25, 15])[0]
obs = ""
w.writerow({
"id_ofrag": f"OFRAG-2025-{idx:03d}",
"missao_id": m.id,
"om": m.om,
"orig": m.orig,
"dest": m.dest,
"prioridade": prio,
"tipo_missao": tipo,
"observacao": obs,
})
print(f" OFRAG gerado: {saida} ({len(missoes)} ordens)")
_resumir(saida)
def _resumir(saida: Path) -> None:
from collections import Counter
prios: Counter = Counter()
tipos: Counter = Counter()
with saida.open(newline="", encoding="utf-8-sig") as f:
for row in csv.DictReader(f):
prios[int(row["prioridade"])] += 1
tipos[row["tipo_missao"]] += 1
print("\n Distribuição de prioridades:")
for p in sorted(prios):
bar = "#" * prios[p]
print(f" Prio {p}: {prios[p]:3d} {bar}")
print("\n Distribuição de tipos:")
for t, c in tipos.most_common():
print(f" {t:<20} {c:3d}")
def main() -> None:
parser = argparse.ArgumentParser(description="Gerador de OFRAG sintético — Arara C-105")
parser.add_argument("--seed", type=int, default=42)
parser.add_argument("--n", type=int, default=50, help="Número de missões")
parser.add_argument("--saida", type=Path, default=OFRAG_PADRAO)
args = parser.parse_args()
gerar_ofrag(n=args.n, seed=args.seed, saida=args.saida)
if __name__ == "__main__":
main()

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

375
software/oamrp_v2.py Normal file
View File

@@ -0,0 +1,375 @@
"""
============================================================================
AIRCRAFT ROUTING - ESQUADRAO ARARA - V2
============================================================================
Primeira versao do modelo OAMRP usando dados reais pre-processados do
registro de voo 2025.
Diferencas em relacao ao v1:
- le o CSV consolidado gerado a partir da Planilha 2025;
- filtra uma janela de datas, com janeiro/2025 como padrao;
- usa aeronaves reais observadas no registro: 2800, 2803, 2809 e 2811;
- gera um CSV de resultado em db/processed/.
Esta versao ainda e uma rodada exploratoria. Todas as missoes recebem
prioridade 1 e a base de manutencao considerada e SBMN.
============================================================================
"""
from __future__ import annotations
import argparse
import csv
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
import pulp
BASE_DIR = Path(__file__).resolve().parents[1]
CSV_REGISTRO_PADRAO = BASE_DIR / "db" / "processed" / "registro_voo_2025_consolidado.csv"
CSV_RESULTADO_PADRAO = BASE_DIR / "db" / "processed" / "resultado_oamrp_jan_2025.csv"
DATA_INICIO_PADRAO = "2025-01-01"
DATA_FIM_PADRAO = "2025-02-01" # exclusivo
BASE_MANUTENCAO = "SBMN"
BASE_INICIAL = BASE_MANUTENCAO
TURNAROUND_MIN_H = 1.0
LIMITE_HORAS_F = 300.0
PRIORIDADE_PADRAO = 1
AERONAVES = [
# id base inicial horas acumuladas desde ultima inspecao
("2800", BASE_INICIAL, 0.0),
("2803", BASE_INICIAL, 0.0),
("2809", BASE_INICIAL, 0.0),
("2811", BASE_INICIAL, 0.0),
]
@dataclass(frozen=True)
class Missao:
id: str
orig: str
dest: str
partida_utc: datetime
chegada_utc: datetime
prioridade: int
periodo: str
linhas_origem: str
aeronave_real: str
om: str
@property
def dur_h(self) -> float:
return (self.chegada_utc - self.partida_utc).total_seconds() / 3600
def parse_utc(valor: str) -> datetime:
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 parse_data_inicio(valor: str) -> datetime:
return datetime.fromisoformat(valor).replace(tzinfo=timezone.utc)
def carregar_missoes(caminho: Path, inicio: datetime, fim: datetime) -> list[Missao]:
missoes: list[Missao] = []
with caminho.open(newline="", encoding="utf-8-sig") as arquivo:
leitor = csv.DictReader(arquivo)
for linha in leitor:
partida = parse_utc(linha["partida_utc"])
chegada = parse_utc(linha["chegada_utc"])
if not (inicio <= partida < fim):
continue
if chegada <= partida:
raise ValueError(f"Missao {linha['id_registro']}: chegada antes da partida")
missoes.append(
Missao(
id=linha["id_registro"],
orig=linha["localidade_dep"],
dest=linha["localidade_arr"],
partida_utc=partida,
chegada_utc=chegada,
prioridade=PRIORIDADE_PADRAO,
periodo=linha.get("periodo", ""),
linhas_origem=linha.get("linhas_origem", ""),
aeronave_real=linha.get("aeronave", ""),
om=linha.get("om", ""),
)
)
return sorted(missoes, key=lambda m: (m.partida_utc, m.id))
def horas_relativas(missoes: list[Missao]) -> tuple[dict[str, dict[str, object]], datetime]:
horizonte_inicio = min(m.partida_utc for m in missoes)
dados = {}
for missao in missoes:
dados[missao.id] = {
"orig": missao.orig,
"dest": missao.dest,
"part": (missao.partida_utc - horizonte_inicio).total_seconds() / 3600,
"cheg": (missao.chegada_utc - horizonte_inicio).total_seconds() / 3600,
"prio": missao.prioridade,
"dur": missao.dur_h,
"obj": missao,
}
return dados, horizonte_inicio
def resolver(missoes: list[Missao]) -> dict[str, object]:
if not missoes:
raise ValueError("Nenhuma missao encontrada na janela informada")
miss, horizonte_inicio = horas_relativas(missoes)
acft = {a[0]: {"base": a[1], "f": a[2]} for a in AERONAVES}
ids = list(miss.keys())
ks = list(acft.keys())
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
]
def pode_iniciar(k: str, i: str) -> bool:
return miss[i]["orig"] == acft[k]["base"]
mdl = pulp.LpProblem("ARARA_OAMRP_v2", pulp.LpMaximize)
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")
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")
for k in ks
for (i, j) in conex
}
z = {i: pulp.LpVariable(f"z_{i}", cat="Binary") for i in ids}
mdl += pulp.lpSum(miss[i]["prio"] * z[i] for i in ids), "missoes_cumpridas_ponderadas"
for i in ids:
mdl += z[i] == pulp.lpSum(x[(k, i)] for k in ks), f"def_z_{i}"
for k in ks:
for i in ids:
entradas = pulp.lpSum(c[(k, jj, i)] for (jj, ii) in conex if ii == i)
inicio = s[(k, i)] if (k, i) in s else 0
mdl += inicio + entradas == x[(k, i)], f"in_{k}_{i}"
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}"
for k in ks:
inicios = [s[(k, i)] for i in ids if (k, i) in s]
if inicios:
mdl += pulp.lpSum(inicios) <= 1, f"start_{k}"
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}"
solver = pulp.PULP_CBC_CMD(msg=False)
mdl.solve(solver)
prio_otima = int(round(pulp.value(mdl.objective) or 0))
# Criterio secundario: entre solucoes com a mesma prioridade, minimiza horas
# totais atribuidas. Neste v2 isso evita voos extras irrelevantes se houver
# empate em prioridade.
mdl += pulp.lpSum(miss[i]["prio"] * z[i] for i in ids) == prio_otima, "fixa_prioridade"
mdl.sense = pulp.LpMinimize
mdl.setObjective(pulp.lpSum(miss[i]["dur"] * x[(k, i)] for k in ks for i in ids))
mdl.solve(solver)
return {
"modelo": mdl,
"miss": miss,
"acft": acft,
"ids": ids,
"ks": ks,
"conex": conex,
"x": x,
"s": s,
"c": c,
"z": z,
"horizonte_inicio": horizonte_inicio,
"prioridade_otima": prio_otima,
}
def rota_da_aeronave(sol: dict[str, object], k: str) -> list[str]:
ids = sol["ids"]
conex = sol["conex"]
s = sol["s"]
c = sol["c"]
inicio = [
i
for i in ids
if (k, i) in s and s[(k, i)].value() is not None and s[(k, i)].value() > 0.5
]
if not inicio:
return []
seq = [inicio[0]]
while True:
atual = seq[-1]
prox = [
j
for (ii, j) in conex
if ii == atual and c[(k, atual, j)].value() is not None and c[(k, atual, j)].value() > 0.5
]
if not prox:
break
seq.append(prox[0])
return seq
def escrever_resultado(sol: dict[str, object], caminho: Path) -> None:
caminho.parent.mkdir(parents=True, exist_ok=True)
miss = sol["miss"]
z = sol["z"]
linhas = []
for k in sol["ks"]:
for ordem, mid in enumerate(rota_da_aeronave(sol, k), start=1):
obj = miss[mid]["obj"]
linhas.append(
{
"aeronave_modelo": k,
"ordem": ordem,
"id": mid,
"om": obj.om,
"orig": obj.orig,
"dest": obj.dest,
"partida_utc": obj.partida_utc.isoformat().replace("+00:00", "Z"),
"chegada_utc": obj.chegada_utc.isoformat().replace("+00:00", "Z"),
"dur_h": f"{obj.dur_h:.2f}",
"prioridade": obj.prioridade,
"periodo": obj.periodo,
"aeronave_real_2025": obj.aeronave_real,
"linhas_origem": obj.linhas_origem,
"status": "cumprida",
}
)
for mid in sol["ids"]:
if not z[mid].value() or z[mid].value() <= 0.5:
obj = miss[mid]["obj"]
linhas.append(
{
"aeronave_modelo": "",
"ordem": "",
"id": mid,
"om": obj.om,
"orig": obj.orig,
"dest": obj.dest,
"partida_utc": obj.partida_utc.isoformat().replace("+00:00", "Z"),
"chegada_utc": obj.chegada_utc.isoformat().replace("+00:00", "Z"),
"dur_h": f"{obj.dur_h:.2f}",
"prioridade": obj.prioridade,
"periodo": obj.periodo,
"aeronave_real_2025": obj.aeronave_real,
"linhas_origem": obj.linhas_origem,
"status": "nao_cumprida",
}
)
colunas = [
"status",
"aeronave_modelo",
"ordem",
"id",
"om",
"orig",
"dest",
"partida_utc",
"chegada_utc",
"dur_h",
"prioridade",
"periodo",
"aeronave_real_2025",
"linhas_origem",
]
with caminho.open("w", newline="", encoding="utf-8-sig") as arquivo:
escritor = csv.DictWriter(arquivo, fieldnames=colunas)
escritor.writeheader()
escritor.writerows(linhas)
def imprimir_relatorio(sol: dict[str, object], resultado_csv: Path) -> None:
miss = sol["miss"]
z = sol["z"]
ids = sol["ids"]
cobertas = [i for i in ids if z[i].value() is not None and z[i].value() > 0.5]
prioridade_total = sum(int(miss[i]["prio"]) for i in ids)
prioridade_obtida = sum(int(miss[i]["prio"]) for i in cobertas)
print("=" * 72)
print(" OAMRP V2 - Registro de voo 2025 consolidado")
print("=" * 72)
print(f" Status solver: {pulp.LpStatus[sol['modelo'].status]}")
print(f" Horizonte inicia em UTC: {sol['horizonte_inicio'].isoformat().replace('+00:00', 'Z')}")
print(f" Missoes cumpridas: {len(cobertas)}/{len(ids)}")
print(f" Prioridade obtida: {prioridade_obtida}/{prioridade_total}")
print(f" Resultado CSV: {resultado_csv}")
for k in sol["ks"]:
seq = rota_da_aeronave(sol, k)
horas = sum(float(miss[i]["dur"]) for i in seq)
print(f"\n Aeronave {k}: {len(seq)} missoes | {horas:.1f}h")
for mid in seq[:12]:
obj = miss[mid]["obj"]
print(
f" {obj.partida_utc:%d/%m %H:%M}Z "
f"{obj.orig}->{obj.dest} OM {obj.om} ({mid})"
)
if len(seq) > 12:
print(f" ... mais {len(seq) - 12} missoes")
nao_cumpridas = [i for i in ids if i not in cobertas]
if nao_cumpridas:
print("\n Missoes nao cumpridas (primeiras 20):")
for mid in nao_cumpridas[:20]:
obj = miss[mid]["obj"]
print(f" {obj.partida_utc:%d/%m %H:%M}Z {obj.orig}->{obj.dest} OM {obj.om} ({mid})")
if len(nao_cumpridas) > 20:
print(f" ... mais {len(nao_cumpridas) - 20} missoes")
def main() -> None:
parser = argparse.ArgumentParser(description="Resolve OAMRP v2 com registro de voo real.")
parser.add_argument("--csv", type=Path, default=CSV_REGISTRO_PADRAO)
parser.add_argument("--inicio", default=DATA_INICIO_PADRAO)
parser.add_argument("--fim", default=DATA_FIM_PADRAO)
parser.add_argument("--resultado", type=Path, default=CSV_RESULTADO_PADRAO)
args = parser.parse_args()
inicio = parse_data_inicio(args.inicio)
fim = parse_data_inicio(args.fim)
missoes = carregar_missoes(args.csv, inicio, fim)
sol = resolver(missoes)
escrever_resultado(sol, args.resultado)
imprimir_relatorio(sol, args.resultado)
if __name__ == "__main__":
main()

1574
software/oamrp_v3.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,625 @@
"""
Visualização interativa do resultado OAMRP — Esquadrão Arara C-105.
Gera um único HTML com título "Planejamento de Diagonal de Manutenção" contendo:
- Gantt (Plotly): calendário aeronave × tempo com voos e inspeções
- Mapa (Folium): rotas por aeronave, missões não cumpridas, bases
Uso:
python software/visualizar_resultado.py \
--resultado db/processed/resultado_oamrp_sintetico.csv \
--saida db/processed/planejamento.html
"""
import argparse
import csv
import io
from datetime import datetime, timedelta, timezone
from html import escape
from pathlib import Path
import folium
BASE_DIR = Path(__file__).resolve().parents[1]
RESULTADO_PADRAO = BASE_DIR / "db" / "processed" / "resultado_oamrp_sintetico.csv"
SAIDA_PADRAO = BASE_DIR / "db" / "processed" / "planejamento.html"
# Coordenadas das bases (lat, lon)
BASES_COORDS: dict[str, tuple[float, float]] = {
"SBMN": (-3.15, -59.99),
"SBBE": (-1.38, -48.48),
"SBBV": ( 2.84, -60.69),
"SBSN": (-2.42, -54.79),
"SBPV": (-8.71, -63.90),
"SBTS": (-4.25, -69.94),
"SBTT": (-3.38, -64.72),
"SBUA": (-0.15, -67.05),
"SWBC": (-0.98, -62.92),
"SBMY": (-5.81, -61.28),
}
BASES_NOMES = {
"SBMN": "Manaus / Ponta Pelada (Hub)",
"SBBE": "Belém",
"SBBV": "Boa Vista",
"SBSN": "Santarém",
"SBPV": "Porto Velho",
"SBTS": "Tabatinga",
"SBTT": "Tefé",
"SBUA": "São Gabriel da Cachoeira",
"SWBC": "Barcelos",
"SBMY": "Manicoré",
}
CORES_AERONAVE = {
"2800": "#1f77b4",
"2803": "#2ca02c",
"2809": "#d62728",
"2811": "#ff7f0e",
}
COR_NAO_CUMPRIDA = "#aaaaaa"
_GANTT_CORES = {
"cumprida": {
"1": "#d62728",
"2": "#ff7f0e",
"3": "#2ca02c",
"4": "#1f77b4",
"5": "#9467bd",
"": "#1f77b4",
},
"inspecao": "#8c564b",
"nao_cumprida": "#aaaaaa",
}
def _cor_prio(prio: str) -> str:
return {"1": "red", "2": "orange", "3": "yellow", "4": "lightblue", "5": "lightgray"}.get(str(prio), "white")
def _cor_gantt(linha: dict) -> str:
status = linha["status"]
if status == "inspecao":
return _GANTT_CORES["inspecao"]
if status == "nao_cumprida":
return _GANTT_CORES["nao_cumprida"]
return _GANTT_CORES["cumprida"].get(linha.get("prioridade", ""), "#1f77b4")
def carregar_resultado(caminho: Path) -> list[dict]:
with caminho.open(newline="", encoding="utf-8-sig") as f:
return list(csv.DictReader(f))
def _resumir_resultado(linhas: list[dict]) -> None:
cumpridas = sum(1 for r in linhas if r["status"] == "cumprida")
total_missoes = sum(1 for r in linhas if r["status"] in ("cumprida", "nao_cumprida"))
print(f" Missões: {cumpridas}/{total_missoes} cumpridas")
aeronaves: dict[str, int] = {}
for r in linhas:
if r["aeronave"] and r["status"] == "cumprida":
aeronaves.setdefault(r["aeronave"], 0)
aeronaves[r["aeronave"]] += 1
for k, c in sorted(aeronaves.items()):
print(f" FAB {k}: {c} missões")
# ---------------------------------------------------------------------------
# Mapa Folium → string HTML
# ---------------------------------------------------------------------------
def _construir_mapa(linhas: list[dict]) -> str:
mapa = folium.Map(location=[-3.5, -62.0], zoom_start=5, tiles="CartoDB positron")
aeronaves_vistas = sorted({r["aeronave"] for r in linhas if r["aeronave"]})
grupos: dict[str, folium.FeatureGroup] = {}
for k in aeronaves_vistas:
grupos[k] = folium.FeatureGroup(name=f"FAB {k}", show=True)
grupos["nao_cumprida"] = folium.FeatureGroup(name="Não cumpridas", show=True)
for r in linhas:
if r["status"] != "cumprida":
continue
orig, dest = r["orig"], r["dest"]
if orig not in BASES_COORDS or dest not in BASES_COORDS:
continue
k = r["aeronave"]
cor = CORES_AERONAVE.get(k, "#333333")
lat_o, lon_o = BASES_COORDS[orig]
lat_d, lon_d = BASES_COORDS[dest]
folium.PolyLine(
locations=[[lat_o, lon_o], [lat_d, lon_d]],
color=cor, weight=2.5, opacity=0.8,
tooltip=f"FAB {k} | {orig}{dest} | OM {r['om']} | prio {r['prioridade']}",
popup=folium.Popup(
f"<b>FAB {k}</b> — Missão #{r['ordem']}<br>"
f"{orig}{dest}<br>"
f"OM: {r['om']} | Prio: {r['prioridade']}<br>"
f"Partida: {r['partida_utc'][:16].replace('T',' ')}Z<br>"
f"Chegada: {r['chegada_utc'][:16].replace('T',' ')}Z<br>"
f"Duração: {r['dur_h']}h",
max_width=250,
),
).add_to(grupos[k])
folium.CircleMarker(
location=[lat_o, lon_o], radius=4, color=cor,
fill=True, fill_color=_cor_prio(r["prioridade"]), fill_opacity=0.9,
tooltip=f"{orig} | prio {r['prioridade']}",
).add_to(grupos[k])
for r in linhas:
if r["status"] == "nao_cumprida":
orig, dest = r["orig"], r["dest"]
if orig not in BASES_COORDS or dest not in BASES_COORDS:
continue
lat_o, lon_o = BASES_COORDS[orig]
lat_d, lon_d = BASES_COORDS[dest]
folium.PolyLine(
locations=[[lat_o, lon_o], [lat_d, lon_d]],
color=COR_NAO_CUMPRIDA, weight=1.5, opacity=0.5, dash_array="6 4",
tooltip=f"NÃO CUMPRIDA | {orig}{dest} | OM {r['om']} | prio {r['prioridade']}",
).add_to(grupos["nao_cumprida"])
for g in grupos.values():
g.add_to(mapa)
for icao, (lat, lon) in BASES_COORDS.items():
folium.Marker(
location=[lat, lon],
tooltip=f"<b>{icao}</b><br>{BASES_NOMES.get(icao, '')}",
popup=folium.Popup(f"<b>{icao}</b><br>{BASES_NOMES.get(icao, '')}", max_width=200),
icon=folium.Icon(color="red" if icao == "SBMN" else "blue", icon="plane", prefix="fa"),
).add_to(mapa)
folium.LayerControl(collapsed=False).add_to(mapa)
legenda_html = """
<div style="position:fixed;bottom:30px;left:30px;z-index:1000;background:white;
padding:12px 16px;border-radius:8px;box-shadow:2px 2px 6px rgba(0,0,0,0.3);
font-family:sans-serif;font-size:12px;">
<b>Esquadrão Arara — OAMRP v3</b><br><br>
<b>Aeronaves:</b><br>
"""
for k, cor in CORES_AERONAVE.items():
legenda_html += (
f' <span style="display:inline-block;width:16px;height:4px;'
f'background:{cor};margin-right:6px;vertical-align:middle;"></span>'
f'FAB {k}<br>'
)
legenda_html += """
<br><b>Prioridade (preenchimento):</b><br>
<span style="display:inline-block;width:12px;height:12px;border-radius:50%;
background:red;margin-right:6px;"></span>Prio 1 (urgente)<br>
<span style="display:inline-block;width:12px;height:12px;border-radius:50%;
background:orange;margin-right:6px;"></span>Prio 2<br>
<span style="display:inline-block;width:12px;height:12px;border-radius:50%;
background:yellow;margin-right:6px;"></span>Prio 3<br>
<span style="display:inline-block;width:12px;height:12px;border-radius:50%;
background:lightblue;margin-right:6px;"></span>Prio 45<br>
<br>
<span style="border-bottom:2px dashed #aaa;display:inline-block;width:20px;
margin-right:6px;"></span>Não cumprida
</div>
"""
mapa.get_root().html.add_child(folium.Element(legenda_html))
return mapa.get_root().render()
# ---------------------------------------------------------------------------
# Gantt Plotly → string HTML (só o div, sem <html>)
# ---------------------------------------------------------------------------
def _construir_gantt(linhas: list[dict]) -> str:
try:
import plotly.graph_objects as go
except ImportError:
return "<p><em>plotly não instalado — instale com: pip install plotly</em></p>"
aeronaves_ord = sorted(
{r["aeronave"] for r in linhas if r["aeronave"]},
key=lambda k: int(k) if k.isdigit() else 0,
)
y_pos = {k: i for i, k in enumerate(aeronaves_ord)}
fig = go.Figure()
for i, k in enumerate(aeronaves_ord):
fig.add_hrect(
y0=i - 0.45, y1=i + 0.45,
fillcolor="#f0f0f0" if i % 2 == 0 else "white",
layer="below", line_width=0,
)
shapes = []
bar_traces = []
for r in linhas:
k = r["aeronave"]
if not k or k not in y_pos:
continue
try:
ini = r["partida_utc"].replace("Z", "+00:00")
fim = r["chegada_utc"].replace("Z", "+00:00")
except Exception:
continue
ini_dt = datetime.fromisoformat(ini)
fim_dt = datetime.fromisoformat(fim)
if (fim_dt - ini_dt).total_seconds() < 2 * 3600:
fim_dt = ini_dt + timedelta(hours=2)
yi = y_pos[k]
cor = _cor_gantt(r)
status = r["status"]
if status == "inspecao":
txt = (
f"<b>FAB {k}</b> — Inspeção {r['om']}<br>"
f"Início: {ini[:16].replace('T',' ')}Z<br>"
f"Fim: {fim[:16].replace('T',' ')}Z<br>"
f"Duração: {r['dur_h']}h"
)
else:
txt = (
f"<b>FAB {k}</b> — Missão #{r['ordem']}<br>"
f"{r['orig']}{r['dest']}<br>"
f"OM: {r['om']} | Prio: {r['prioridade']}<br>"
f"Partida: {ini[:16].replace('T',' ')}Z<br>"
f"Chegada: {fim[:16].replace('T',' ')}Z<br>"
f"Duração: {r['dur_h']}h"
)
# Barra como linha horizontal grossa — renderiza garantido no eixo de datas
bar_traces.append(go.Scatter(
x=[ini_dt, fim_dt],
y=[yi, yi],
mode="lines",
line=dict(color=cor, width=64),
hovertext=[txt, txt],
hoverinfo="text",
showlegend=False,
))
# Divisões diárias
datas_utc = [
r[campo].replace("Z", "+00:00")
for r in linhas
for campo in ("partida_utc", "chegada_utc")
if r.get(campo)
]
if datas_utc:
t_min = datetime.fromisoformat(min(datas_utc))
t_max = datetime.fromisoformat(max(datas_utc))
dia = datetime(t_min.year, t_min.month, t_min.day, tzinfo=timezone.utc)
while dia <= t_max + timedelta(days=1):
shapes.append(dict(
type="line", xref="x", yref="paper",
x0=dia.isoformat(), x1=dia.isoformat(),
y0=0, y1=1,
line=dict(color="#bbbbbb", width=1, dash="dot"),
layer="above",
))
dia += timedelta(days=1)
# Legenda
for nome, cor in [
("Prio 1", _GANTT_CORES["cumprida"]["1"]),
("Prio 2", _GANTT_CORES["cumprida"]["2"]),
("Prio 3", _GANTT_CORES["cumprida"]["3"]),
("Prio 4", _GANTT_CORES["cumprida"]["4"]),
("Prio 5", _GANTT_CORES["cumprida"]["5"]),
("Inspeção", _GANTT_CORES["inspecao"]),
]:
fig.add_trace(go.Scatter(
x=[None], y=[None], mode="markers",
marker=dict(size=12, color=cor, symbol="square"),
name=nome, showlegend=True,
))
for t in bar_traces:
fig.add_trace(t)
fig.update_layout(
shapes=shapes,
xaxis=dict(
title="Data / Hora (UTC)", type="date",
tickformat="%d/%m", dtick=86400000,
showgrid=True, gridcolor="#dddddd",
),
yaxis=dict(
title="Aeronave",
tickvals=list(range(len(aeronaves_ord))),
ticktext=[f"FAB {k}" for k in aeronaves_ord],
showgrid=False,
range=[-0.5, len(aeronaves_ord) - 0.5],
),
height=max(300, 120 + 80 * len(aeronaves_ord)),
plot_bgcolor="white",
paper_bgcolor="white",
legend=dict(title="Legenda", orientation="v", x=1.01, y=1, xanchor="left"),
margin=dict(l=100, r=160, t=10, b=60),
hovermode="closest",
)
return fig.to_html(full_html=False, include_plotlyjs="cdn")
# ---------------------------------------------------------------------------
# Tabela de voos com filtros
# ---------------------------------------------------------------------------
def _construir_tabela(linhas: list[dict]) -> str:
import json
# Monta lista de registros para a tabela (missões + inspeções, sem nao_cumprida sem aeronave)
registros = []
for r in linhas:
status = r["status"]
k = r["aeronave"]
if status == "nao_cumprida" and not k:
# mantém como linha sem aeronave
k = ""
registros.append({
"status": status,
"aeronave": f"FAB {k}" if k and k != "" else "",
"ordem": r.get("ordem", ""),
"om": r.get("om", ""),
"orig": r.get("orig", ""),
"dest": r.get("dest", ""),
"partida": r.get("partida_utc", "")[:16].replace("T", " ") + "Z" if r.get("partida_utc") else "",
"chegada": r.get("chegada_utc", "")[:16].replace("T", " ") + "Z" if r.get("chegada_utc") else "",
"dur_h": r.get("dur_h", ""),
"prioridade": r.get("prioridade", ""),
})
dados_json = json.dumps(registros, ensure_ascii=False)
opcoes_aeronave = sorted({r["aeronave"] for r in registros if r["aeronave"] != ""})
opcoes_status = ["cumprida", "nao_cumprida", "inspecao"]
opcoes_prio = sorted({r["prioridade"] for r in registros if r["prioridade"]})
opcoes_base = sorted({b for r in registros for b in (r["orig"], r["dest"]) if b})
def opts(valores: list[str], label: str) -> str:
html = f'<option value="">— {label} —</option>'
for v in valores:
html += f'<option value="{escape(v)}">{escape(v)}</option>'
return html
return f"""
<style>
.filtros {{
display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; align-items: flex-end;
}}
.filtros label {{ font-size: 0.78rem; color: #555; display: flex; flex-direction: column; gap: 3px; }}
.filtros select, .filtros input {{
border: 1px solid #ccc; border-radius: 4px; padding: 5px 8px;
font-size: 0.85rem; min-width: 130px; background: white;
}}
.filtros button {{
padding: 6px 14px; border: none; border-radius: 4px; cursor: pointer;
font-size: 0.85rem; background: #1a3a5c; color: white;
align-self: flex-end;
}}
.filtros button:hover {{ background: #254f80; }}
#contador {{ font-size: 0.82rem; color: #666; margin-bottom: 10px; }}
#tabela-voos {{ width: 100%; border-collapse: collapse; font-size: 0.85rem; }}
#tabela-voos thead th {{
background: #1a3a5c; color: white; padding: 8px 10px;
text-align: left; white-space: nowrap; cursor: pointer; user-select: none;
}}
#tabela-voos thead th:hover {{ background: #254f80; }}
#tabela-voos thead th .sort-icon {{ margin-left: 4px; opacity: 0.5; }}
#tabela-voos tbody tr:nth-child(even) {{ background: #f7f8fa; }}
#tabela-voos tbody tr:hover {{ background: #e8f0fb; }}
#tabela-voos td {{ padding: 7px 10px; border-bottom: 1px solid #eee; white-space: nowrap; }}
.badge {{
display: inline-block; padding: 2px 8px; border-radius: 10px;
font-size: 0.75rem; font-weight: 600; text-transform: uppercase;
}}
.badge-cumprida {{ background: #d4edda; color: #155724; }}
.badge-nao_cumprida {{ background: #f8d7da; color: #721c24; }}
.badge-inspecao {{ background: #e2d9f3; color: #4a235a; }}
.prio-1 {{ color: #d62728; font-weight: 700; }}
.prio-2 {{ color: #ff7f0e; font-weight: 700; }}
.prio-3 {{ color: #2ca02c; font-weight: 700; }}
</style>
<div class="filtros">
<label>Aeronave
<select id="f-aeronave">{opts(opcoes_aeronave, "Todas")}</select>
</label>
<label>Status
<select id="f-status">{opts(opcoes_status, "Todos")}</select>
</label>
<label>Prioridade
<select id="f-prio">{opts(opcoes_prio, "Todas")}</select>
</label>
<label>Base (orig/dest)
<select id="f-base">{opts(opcoes_base, "Todas")}</select>
</label>
<label>Busca (OM, rota…)
<input id="f-texto" type="text" placeholder="ex: SBMN, EVAM…">
</label>
<button onclick="limparFiltros()">Limpar</button>
</div>
<div id="contador"></div>
<table id="tabela-voos">
<thead>
<tr>
<th onclick="ordenar(0)">Aeronave <span class="sort-icon">↕</span></th>
<th onclick="ordenar(1)">Status <span class="sort-icon">↕</span></th>
<th onclick="ordenar(2)">#Missão <span class="sort-icon">↕</span></th>
<th onclick="ordenar(3)">OM <span class="sort-icon">↕</span></th>
<th onclick="ordenar(4)">Origem <span class="sort-icon">↕</span></th>
<th onclick="ordenar(5)">Destino <span class="sort-icon">↕</span></th>
<th onclick="ordenar(6)">Partida (UTC) <span class="sort-icon">↕</span></th>
<th onclick="ordenar(7)">Chegada (UTC) <span class="sort-icon">↕</span></th>
<th onclick="ordenar(8)">Duração (h) <span class="sort-icon">↕</span></th>
<th onclick="ordenar(9)">Prio <span class="sort-icon">↕</span></th>
</tr>
</thead>
<tbody id="corpo-tabela"></tbody>
</table>
<script>
const DADOS = {dados_json};
let _colOrdem = -1, _colAsc = true;
function badgeStatus(s) {{
const label = {{cumprida:'Cumprida', nao_cumprida:'Não cumprida', inspecao:'Inspeção'}}[s] || s;
return `<span class="badge badge-${{s}}">${{label}}</span>`;
}}
function prio(p) {{
if (!p) return '';
const cls = ['1','2','3'].includes(p) ? `prio-${{p}}` : '';
return `<span class="${{cls}}">${{p}}</span>`;
}}
function linhasFiltradas() {{
const fa = document.getElementById('f-aeronave').value;
const fs = document.getElementById('f-status').value;
const fp = document.getElementById('f-prio').value;
const fb = document.getElementById('f-base').value;
const ft = document.getElementById('f-texto').value.toLowerCase();
return DADOS.filter(r => {{
if (fa && r.aeronave !== fa) return false;
if (fs && r.status !== fs) return false;
if (fp && r.prioridade !== fp) return false;
if (fb && r.orig !== fb && r.dest !== fb) return false;
if (ft && !JSON.stringify(r).toLowerCase().includes(ft)) return false;
return true;
}});
}}
function renderizar() {{
let rows = linhasFiltradas();
if (_colOrdem >= 0) {{
const chaves = ['aeronave','status','ordem','om','orig','dest','partida','chegada','dur_h','prioridade'];
const c = chaves[_colOrdem];
rows = [...rows].sort((a,b) => {{
const va = a[c] ?? '', vb = b[c] ?? '';
return _colAsc ? String(va).localeCompare(String(vb), 'pt', {{numeric:true}})
: String(vb).localeCompare(String(va), 'pt', {{numeric:true}});
}});
}}
document.getElementById('contador').textContent =
`${{rows.length}} registro${{rows.length !== 1 ? 's' : ''}} exibido${{rows.length !== 1 ? 's' : ''}} de ${{DADOS.length}}`;
document.getElementById('corpo-tabela').innerHTML = rows.map(r => `
<tr>
<td>${{r.aeronave}}</td>
<td>${{badgeStatus(r.status)}}</td>
<td>${{r.ordem}}</td>
<td>${{r.om}}</td>
<td>${{r.orig}}</td>
<td>${{r.dest}}</td>
<td>${{r.partida}}</td>
<td>${{r.chegada}}</td>
<td>${{r.dur_h}}</td>
<td>${{prio(r.prioridade)}}</td>
</tr>`).join('');
}}
function ordenar(col) {{
if (_colOrdem === col) _colAsc = !_colAsc;
else {{ _colOrdem = col; _colAsc = true; }}
renderizar();
}}
function limparFiltros() {{
['f-aeronave','f-status','f-prio','f-base'].forEach(id => document.getElementById(id).value = '');
document.getElementById('f-texto').value = '';
renderizar();
}}
['f-aeronave','f-status','f-prio','f-base'].forEach(id =>
document.getElementById(id).addEventListener('change', renderizar));
document.getElementById('f-texto').addEventListener('input', renderizar);
renderizar();
</script>"""
# ---------------------------------------------------------------------------
# HTML combinado
# ---------------------------------------------------------------------------
def gerar_planejamento(resultado: Path, saida: Path) -> None:
linhas = carregar_resultado(resultado)
gantt_div = _construir_gantt(linhas)
mapa_html = _construir_mapa(linhas)
tabela_div = _construir_tabela(linhas)
html = f"""<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Planejamento de Diagonal de Manutenção</title>
<style>
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
body {{ font-family: 'Segoe UI', Arial, sans-serif; background: #f0f2f5; color: #222; }}
header {{
background: #1a3a5c; color: white;
padding: 18px 32px;
border-bottom: 4px solid #c8a400;
}}
header h1 {{ font-size: 1.5rem; font-weight: 700; letter-spacing: 0.03em; }}
header p {{ font-size: 0.85rem; opacity: 0.75; margin-top: 4px; }}
.container {{ padding: 24px 32px; max-width: 1600px; margin: 0 auto; }}
.section {{
background: white; border-radius: 8px;
box-shadow: 0 1px 4px rgba(0,0,0,0.12);
margin-bottom: 28px; overflow: hidden;
}}
.section-title {{
background: #f7f8fa; border-bottom: 1px solid #e0e0e0;
padding: 12px 20px; font-size: 0.95rem; font-weight: 600;
color: #1a3a5c; text-transform: uppercase; letter-spacing: 0.05em;
}}
.section-body {{ padding: 16px; }}
iframe.mapa {{
width: 100%; height: 560px; border: none; border-radius: 4px; display: block;
}}
</style>
</head>
<body>
<header>
<h1>Planejamento de Diagonal de Manutenção</h1>
<p>Esquadrão Arara · C-105 · OAMRP v3</p>
</header>
<div class="container">
<div class="section">
<div class="section-title">Calendário de Emprego das Aeronaves</div>
<div class="section-body">{gantt_div}</div>
</div>
<div class="section">
<div class="section-title">Rotas Operacionais</div>
<iframe class="mapa" srcdoc="{escape(mapa_html)}"></iframe>
</div>
<div class="section">
<div class="section-title">Planejamento de Voos</div>
<div class="section-body">{tabela_div}</div>
</div>
</div>
</body>
</html>"""
saida.parent.mkdir(parents=True, exist_ok=True)
saida.write_text(html, encoding="utf-8")
print(f" Planejamento salvo: {saida}")
_resumir_resultado(linhas)
def main() -> None:
parser = argparse.ArgumentParser(description="Visualização OAMRP — Arara C-105")
parser.add_argument("--resultado", type=Path, default=RESULTADO_PADRAO)
parser.add_argument("--saida", type=Path, default=SAIDA_PADRAO)
args = parser.parse_args()
gerar_planejamento(resultado=args.resultado, saida=args.saida)
if __name__ == "__main__":
main()