Compare commits
18 Commits
c09ce95e62
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe2ba12e49 | ||
|
|
b368201d5d | ||
|
|
e71a0f2518 | ||
|
|
a34ed6b340 | ||
|
|
6433055e9e | ||
|
|
83a2dac624 | ||
|
|
3756f961a8 | ||
|
|
4d8554df38 | ||
|
|
f0d72f8189 | ||
|
|
7cd7458565 | ||
|
|
328a066849 | ||
|
|
11821eaa93 | ||
|
|
8d7dd07b18 | ||
|
|
f570091e19 | ||
|
|
1131267ea2 | ||
|
|
32ad7c9f23 | ||
|
|
d2bf2883d7 | ||
|
|
33c452b264 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -9,3 +9,8 @@ Desktop.ini
|
||||
# Local editor folders
|
||||
.vscode/
|
||||
.idea/
|
||||
.claude/
|
||||
|
||||
# Python cache
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
49
CONTEXTO.md
49
CONTEXTO.md
@@ -2,13 +2,24 @@
|
||||
|
||||
## Objetivo
|
||||
|
||||
Organizar, preservar e processar documentos relacionados ao projeto `arara_oarmp`, mantendo rastreabilidade das alterações e separando arquivos originais, intermediários e processados.
|
||||
Organizar, preservar e processar documentos relacionados ao projeto Arara OAMRP, mantendo rastreabilidade das alterações e separando arquivos originais, intermediários, processados e modelos de software.
|
||||
|
||||
## Estrutura de Pastas
|
||||
|
||||
- `raw/`: arquivos originais, sem alteração.
|
||||
- `pre_process/`: scripts e saídas intermediárias de pré-processamento.
|
||||
- `processed/`: dados finais limpos, consolidados ou prontos para análise.
|
||||
- `db/raw/`: arquivos originais, sem alteração.
|
||||
- `db/pre_process/`: scripts e saídas intermediárias de pré-processamento.
|
||||
- `db/processed/`: dados finais limpos, consolidados ou prontos para análise.
|
||||
- `software/`: modelos, scripts analíticos e protótipos executáveis do OAMRP.
|
||||
- `docs/`: documentação técnica, autoria e histórico de versões.
|
||||
|
||||
## Arquivos de Documentação
|
||||
|
||||
- `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
|
||||
|
||||
@@ -18,21 +29,43 @@ Organizar, preservar e processar documentos relacionados ao projeto `arara_oarmp
|
||||
|
||||
## Orientações de Trabalho
|
||||
|
||||
- Manter documentos originais sempre em `raw/`.
|
||||
- Não alterar arquivos dentro de `raw/`; quando necessário, gerar cópias ou saídas em `pre_process/`.
|
||||
- Manter documentos originais sempre em `db/raw/`.
|
||||
- Não alterar arquivos dentro de `db/raw/`; quando necessário, gerar cópias ou saídas em `db/pre_process/`.
|
||||
- Manter modelos e protótipos executáveis em `software/`.
|
||||
- Registrar no `LOG.md` toda entrada, alteração, processamento ou decisão relevante.
|
||||
- Registrar em `docs/changelog.md` mudanças significativas de versão, como novo parser, novo conjunto de dados, mudança estrutural ou nova funcionalidade.
|
||||
- Usar o formato de log com data, hora, tag do colaborador e descrição objetiva da ação.
|
||||
- Registrar a origem de cada documento, quando conhecida.
|
||||
- Informar scripts executados e parâmetros importantes.
|
||||
- Registrar quantidade de registros extraídos, descartados, corrigidos ou validados.
|
||||
- Registrar problemas encontrados nos arquivos originais, como erros de digitação, páginas ilegíveis, campos ausentes ou tabelas quebradas.
|
||||
- Registrar decisões de padronização, por exemplo nomes de colunas, unidades, formatos de data e tratamento de acentos.
|
||||
- Explicitar os critérios usados para mover dados de `pre_process/` para `processed/`.
|
||||
- Explicitar os critérios usados para mover dados de `db/pre_process/` para `db/processed/`.
|
||||
- Atualizar `docs/about.md` quando o fluxo técnico, campos extraídos ou limitações conhecidas mudarem.
|
||||
|
||||
## Checklist de Atualização Documental
|
||||
|
||||
Antes de fechar uma mudança relevante ou criar um commit, verificar se algum destes arquivos precisa ser atualizado:
|
||||
|
||||
- `LOG.md`: atualizar em toda entrada de documento, alteração de dados, execução de script, processamento, decisão técnica ou mudança estrutural relevante.
|
||||
- `docs/changelog.md`: atualizar quando a mudança representar nova versão ou marco do projeto, como novo parser, novo modelo, novo conjunto de dados, alteração de estrutura, nova funcionalidade ou correção importante.
|
||||
- `README.md`: atualizar quando mudar a forma de usar o projeto, a estrutura de pastas, comandos principais, estado atual, dependências esperadas ou orientação para colaboradores.
|
||||
- `docs/about.md`: atualizar quando mudar o fluxo técnico, arquitetura, campos extraídos, modelo de dados, limitações conhecidas, fontes de dados ou oportunidades de melhoria.
|
||||
- `docs/authors.md`: atualizar quando houver novo colaborador, mudança de papel, nova contribuição atribuída ou nova versão registrada no changelog.
|
||||
- `CONTEXTO.md`: atualizar quando mudar regra permanente de trabalho, convenção de pastas, política de documentação, padrão de nomenclatura ou combinado do projeto.
|
||||
- `.gitignore`: atualizar quando surgirem novos arquivos temporários, caches, saídas locais ou artefatos gerados que não devem ser versionados.
|
||||
|
||||
Critério prático:
|
||||
|
||||
- Mudança operacional ou decisão do dia: registrar no `LOG.md`.
|
||||
- Mudança significativa para quem usa ou entende o projeto: registrar também em `docs/changelog.md`.
|
||||
- Mudança que altera como trabalhar no repositório: atualizar `README.md` e, se for regra permanente, `CONTEXTO.md`.
|
||||
- Mudança técnica interna: atualizar `docs/about.md`.
|
||||
|
||||
## Formato Recomendado do Log
|
||||
|
||||
```text
|
||||
| Data | Hora | Autor | Ação | Arquivos | Observações |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-06-15 | 15:41 | VTO | Pré-processou PDF de inspeções | raw/documento.pdf; pre_process/saida.csv | 18 inspeções extraídas |
|
||||
| 2026-06-15 | 15:41 | VTO | Pré-processou PDF de inspeções | db/raw/documento.pdf; db/pre_process/saida.csv | 18 inspeções extraídas |
|
||||
```
|
||||
|
||||
119
HANDOFF_arara_oamrp.md
Normal file
119
HANDOFF_arara_oamrp.md
Normal 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 (C1–C6).
|
||||
2. **Inspeção com reset** em SBMN — C7–C11 + 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 1–5)*
|
||||
- **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
30
LOG.md
@@ -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: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 | 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]; C1–C6; 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 Q2–Q4 | `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 (1–5), 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_max−f0); 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. |
|
||||
|
||||
157
README.md
157
README.md
@@ -1,6 +1,6 @@
|
||||
# Arara OARMP
|
||||
# Arara OAMRP
|
||||
|
||||
Este repositório organiza os arquivos e processamentos do projeto Arara OARMP do Esquadrão Arara, em Manaus.
|
||||
Este repositório organiza os arquivos, processamentos e protótipos do projeto Arara OAMRP do Esquadrão Arara, em Manaus.
|
||||
|
||||
A ideia é construir, em conjunto, uma base confiável para encaixar missões, aeronaves e manutenções. O repositório serve como lugar único para guardar documentos originais, registrar decisões, transformar relatórios em dados utilizáveis e manter um histórico claro do que foi feito por cada colaborador.
|
||||
|
||||
@@ -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.
|
||||
|
||||
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:
|
||||
|
||||
- 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;
|
||||
- o `LOG.md` explica o motivo e o contexto das mudanças importantes.
|
||||
|
||||
@@ -21,17 +21,22 @@ Na prática:
|
||||
|
||||
```text
|
||||
arara_oarmp/
|
||||
db/
|
||||
raw/
|
||||
pre_process/
|
||||
processed/
|
||||
software/
|
||||
docs/
|
||||
CONTEXTO.md
|
||||
LOG.md
|
||||
README.md
|
||||
```
|
||||
|
||||
- `raw/`: documentos originais. Não editar esses arquivos diretamente.
|
||||
- `pre_process/`: scripts e arquivos intermediários gerados a partir dos documentos originais.
|
||||
- `processed/`: arquivos finais, limpos e prontos para análise ou uso.
|
||||
- `db/raw/`: documentos originais. Não editar esses arquivos diretamente.
|
||||
- `db/pre_process/`: scripts e arquivos intermediários gerados a partir dos documentos originais.
|
||||
- `db/processed/`: arquivos finais, limpos e prontos para análise ou uso.
|
||||
- `software/`: modelos e protótipos executáveis, incluindo o modelo MIP inicial de roteamento.
|
||||
- `docs/`: documentação técnica, autoria e histórico de versões.
|
||||
- `CONTEXTO.md`: regras, combinados e orientações permanentes do projeto.
|
||||
- `LOG.md`: histórico rastreável das ações feitas no projeto.
|
||||
- `README.md`: este guia inicial.
|
||||
@@ -40,21 +45,21 @@ arara_oarmp/
|
||||
|
||||
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.
|
||||
- `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.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
3. Use `raw/` para consultar documentos originais.
|
||||
4. Use `pre_process/` para consultar arquivos intermediários.
|
||||
5. Use `processed/` para consultar dados finais quando existirem.
|
||||
3. Use `db/raw/` para consultar documentos originais.
|
||||
4. Use `db/pre_process/` para consultar arquivos intermediários.
|
||||
5. Use `db/processed/` para consultar dados finais quando existirem.
|
||||
6. Abra o `LOG.md` para entender o que já foi feito.
|
||||
7. Abra o `CONTEXTO.md` para ver os combinados do projeto.
|
||||
|
||||
@@ -64,9 +69,16 @@ Para baixar um arquivo pelo site:
|
||||
2. Clique em `Download raw file` ou no botão de download.
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
@@ -107,7 +119,7 @@ Crie um commit com uma mensagem curta:
|
||||
git commit -m "Descreva a alteração feita"
|
||||
```
|
||||
|
||||
Envie para o GitHub:
|
||||
Envie para o Gitea:
|
||||
|
||||
```powershell
|
||||
git push
|
||||
@@ -116,34 +128,123 @@ git push
|
||||
## Fluxo recomendado de trabalho
|
||||
|
||||
1. Rode `git pull` antes de começar.
|
||||
2. Coloque documentos novos em `raw/`.
|
||||
3. Gere arquivos intermediários em `pre_process/`.
|
||||
4. Coloque resultados finais em `processed/`.
|
||||
2. Coloque documentos novos em `db/raw/`.
|
||||
3. Gere arquivos intermediários em `db/pre_process/`.
|
||||
4. Coloque resultados finais em `db/processed/`.
|
||||
5. Atualize o `LOG.md`.
|
||||
6. Faça commit.
|
||||
7. Rode `git push`.
|
||||
|
||||
## Cuidados importantes
|
||||
|
||||
- Não editar arquivos originais dentro de `raw/`.
|
||||
- Não editar arquivos originais dentro de `db/raw/`.
|
||||
- Não apagar arquivos de outros colaboradores sem combinar.
|
||||
- Não subir arquivos temporários do Office, como arquivos começando com `~$`.
|
||||
- Não colocar senhas, tokens ou informações pessoais desnecessárias no repositório.
|
||||
- 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.
|
||||
|
||||
## 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
|
||||
raw/relatorio_ciclo_inspecoes_c105_2805_2026-06-15.pdf
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_texto.txt
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.json
|
||||
pre_process/relatorio_ciclo_inspecoes_c105_2805_2026-06-15_inspecoes.csv
|
||||
pre_process/preprocess_pdf.py
|
||||
software/oamrp_v3.py — modelo MIP principal (Fases 1–3 + Set Partitioning)
|
||||
software/gerar_ofrag.py — gerador da tabela OFRAG
|
||||
software/visualizar_resultado.py — Gantt + mapa + tabela interativa
|
||||
db/processed/ofrag.csv — ordens de missão com prioridades 1–5
|
||||
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 SBMN→SBMN, 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 | C1–C6 | Rede espaço-tempo, cobertura (Set Partitioning), conservação de fluxo, TAT |
|
||||
| 2 | C7–C11 | 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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
@@ -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`
|
||||
|
||||
@@ -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()
|
||||
1421
db/pre_process/registro_voo_2025/missoes_oamrp_2025.csv
Normal file
1421
db/pre_process/registro_voo_2025/missoes_oamrp_2025.csv
Normal file
File diff suppressed because it is too large
Load Diff
1268
db/pre_process/registro_voo_2025/missoes_oamrp_2025_consolidado.csv
Normal file
1268
db/pre_process/registro_voo_2025/missoes_oamrp_2025_consolidado.csv
Normal file
File diff suppressed because it is too large
Load Diff
509
db/pre_process/registro_voo_2025/preprocess_planilha_2025.py
Normal file
509
db/pre_process/registro_voo_2025/preprocess_planilha_2025.py
Normal file
@@ -0,0 +1,509 @@
|
||||
"""
|
||||
Pre-processa a planilha bruta de voos de 2025.
|
||||
|
||||
Entrada:
|
||||
db/raw/Planilha 2025.xlsx
|
||||
|
||||
Saidas:
|
||||
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.xlsx
|
||||
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.csv
|
||||
db/pre_process/registro_voo_2025/missoes_oamrp_2025.csv
|
||||
db/pre_process/registro_voo_2025/resumo_registro_voo_2025.json
|
||||
db/processed/registro_voo_2025.csv
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import json
|
||||
from collections import Counter
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import openpyxl
|
||||
|
||||
|
||||
ANO = 2025
|
||||
BASE_DIR = Path(__file__).resolve().parents[3]
|
||||
RAW_PATH = BASE_DIR / "db" / "raw" / "Planilha 2025.xlsx"
|
||||
PREPROCESS_DIR = BASE_DIR / "db" / "pre_process" / "registro_voo_2025"
|
||||
PROCESSED_DIR = BASE_DIR / "db" / "processed"
|
||||
|
||||
LIMPO_XLSX = PREPROCESS_DIR / "registro_voo_2025_limpo.xlsx"
|
||||
LIMPO_CSV = PREPROCESS_DIR / "registro_voo_2025_limpo.csv"
|
||||
CONSOLIDADO_CSV = PREPROCESS_DIR / "registro_voo_2025_consolidado.csv"
|
||||
MISSOES_CSV = PREPROCESS_DIR / "missoes_oamrp_2025.csv"
|
||||
MISSOES_CONSOLIDADAS_CSV = PREPROCESS_DIR / "missoes_oamrp_2025_consolidado.csv"
|
||||
RESUMO_JSON = PREPROCESS_DIR / "resumo_registro_voo_2025.json"
|
||||
PROCESSED_CSV = PROCESSED_DIR / "registro_voo_2025.csv"
|
||||
PROCESSED_CONSOLIDADO_CSV = PROCESSED_DIR / "registro_voo_2025_consolidado.csv"
|
||||
|
||||
MES_MAP = {
|
||||
"JAN": 1,
|
||||
"FEV": 2,
|
||||
"MAR": 3,
|
||||
"ABR": 4,
|
||||
"MAI": 5,
|
||||
"JUN": 6,
|
||||
"JUL": 7,
|
||||
"AGO": 8,
|
||||
"SET": 9,
|
||||
"OUT": 10,
|
||||
"NOV": 11,
|
||||
"DEZ": 12,
|
||||
}
|
||||
MES_ABBR = {numero: nome for nome, numero in MES_MAP.items()}
|
||||
LINHAS_ORIGEM_EXCLUIDAS = {793}
|
||||
|
||||
COLUNAS_REGISTRO = [
|
||||
"id_registro",
|
||||
"linha_origem",
|
||||
"data",
|
||||
"data_iso",
|
||||
"mes",
|
||||
"mes_numero",
|
||||
"om",
|
||||
"aeronave",
|
||||
"etapa",
|
||||
"localidade_dep",
|
||||
"localidade_arr",
|
||||
"horario_z_dep",
|
||||
"horario_z_arr",
|
||||
"partida_utc",
|
||||
"chegada_utc",
|
||||
"tempo_voo",
|
||||
"tempo_voo_min",
|
||||
"segmento",
|
||||
"pousos",
|
||||
"missao",
|
||||
"periodo",
|
||||
"cruza_meia_noite",
|
||||
]
|
||||
|
||||
COLUNAS_OAMRP = [
|
||||
"id",
|
||||
"orig",
|
||||
"dest",
|
||||
"partida_utc",
|
||||
"chegada_utc",
|
||||
"prioridade",
|
||||
]
|
||||
|
||||
COLUNAS_CONSOLIDADAS = [
|
||||
"id_registro",
|
||||
"linhas_origem",
|
||||
"data",
|
||||
"data_iso",
|
||||
"mes",
|
||||
"mes_numero",
|
||||
"om",
|
||||
"aeronave",
|
||||
"etapa_inicio",
|
||||
"etapa_fim",
|
||||
"localidade_dep",
|
||||
"localidade_arr",
|
||||
"horario_z_dep",
|
||||
"horario_z_arr",
|
||||
"partida_utc",
|
||||
"chegada_utc",
|
||||
"tempo_voo",
|
||||
"tempo_voo_min",
|
||||
"segmento_inicio",
|
||||
"segmento_fim",
|
||||
"pousos",
|
||||
"missao",
|
||||
"periodo",
|
||||
"cruza_meia_noite",
|
||||
"qtd_linhas_originais",
|
||||
"tem_rota_intermediaria",
|
||||
]
|
||||
|
||||
|
||||
def texto_limpo(valor: Any) -> str:
|
||||
if valor is None:
|
||||
return ""
|
||||
return str(valor).strip()
|
||||
|
||||
|
||||
def normalizar_mes(valor: Any) -> str:
|
||||
texto = texto_limpo(valor).upper().replace(".", "")
|
||||
return texto
|
||||
|
||||
|
||||
def parse_data(valor_data: Any, valor_mes: Any) -> date:
|
||||
if isinstance(valor_data, datetime):
|
||||
return valor_data.date()
|
||||
if isinstance(valor_data, date):
|
||||
return valor_data
|
||||
|
||||
texto = texto_limpo(valor_data).lower().replace(".", "")
|
||||
if "/" not in texto:
|
||||
raise ValueError(f"Data invalida: {valor_data!r}")
|
||||
dia_txt, mes_txt = texto.split("/", 1)
|
||||
mes_key = normalizar_mes(mes_txt or valor_mes)
|
||||
if mes_key not in MES_MAP:
|
||||
raise ValueError(f"Mes invalido: data={valor_data!r}, mes={valor_mes!r}")
|
||||
return date(ANO, MES_MAP[mes_key], int(dia_txt))
|
||||
|
||||
|
||||
def minutos_do_dia(valor: Any) -> tuple[int, int]:
|
||||
"""Retorna (minutos no dia, deslocamento em dias informado pelo Excel)."""
|
||||
if isinstance(valor, datetime):
|
||||
return valor.hour * 60 + valor.minute, 0
|
||||
if isinstance(valor, time):
|
||||
return valor.hour * 60 + valor.minute, 0
|
||||
if isinstance(valor, timedelta):
|
||||
total_min = int(round(valor.total_seconds() / 60))
|
||||
return total_min % (24 * 60), total_min // (24 * 60)
|
||||
if isinstance(valor, (int, float)):
|
||||
total_min = int(round(float(valor) * 24 * 60))
|
||||
return total_min % (24 * 60), total_min // (24 * 60)
|
||||
|
||||
texto = texto_limpo(valor)
|
||||
if ":" in texto:
|
||||
partes = texto.split(":")
|
||||
return int(partes[0]) * 60 + int(partes[1]), 0
|
||||
raise ValueError(f"Horario invalido: {valor!r}")
|
||||
|
||||
|
||||
def minutos_duracao(valor: Any) -> int:
|
||||
if isinstance(valor, datetime):
|
||||
return valor.hour * 60 + valor.minute
|
||||
if isinstance(valor, time):
|
||||
return valor.hour * 60 + valor.minute
|
||||
if isinstance(valor, timedelta):
|
||||
return int(round(valor.total_seconds() / 60))
|
||||
if isinstance(valor, (int, float)):
|
||||
return int(round(float(valor) * 24 * 60))
|
||||
|
||||
texto = texto_limpo(valor)
|
||||
if ":" in texto:
|
||||
horas, minutos = texto.split(":", 1)
|
||||
return int(horas) * 60 + int(minutos)
|
||||
raise ValueError(f"Duracao invalida: {valor!r}")
|
||||
|
||||
|
||||
def hhmm(minutos: int) -> str:
|
||||
minutos = minutos % (24 * 60)
|
||||
return f"{minutos // 60:02d}:{minutos % 60:02d}"
|
||||
|
||||
|
||||
def duracao_hhmm(minutos: int) -> str:
|
||||
return f"{minutos // 60:02d}:{minutos % 60:02d}"
|
||||
|
||||
|
||||
def iso_utc(data_voo: date, minutos: int, deslocamento_dias: int = 0) -> str:
|
||||
data_hora = datetime.combine(data_voo, time()) + timedelta(
|
||||
days=deslocamento_dias,
|
||||
minutes=minutos,
|
||||
)
|
||||
return data_hora.isoformat(timespec="minutes") + ":00Z"
|
||||
|
||||
|
||||
def atualizar_datas_do_registro(
|
||||
registro: dict[str, Any],
|
||||
partida: datetime,
|
||||
chegada: datetime,
|
||||
) -> None:
|
||||
data_voo = partida.date()
|
||||
registro["data"] = data_voo.strftime("%d/%m")
|
||||
registro["data_iso"] = data_voo.isoformat()
|
||||
registro["mes_numero"] = data_voo.month
|
||||
registro["mes"] = MES_ABBR[data_voo.month]
|
||||
registro["partida_utc"] = partida.isoformat(timespec="minutes").replace("+00:00", "") + ":00Z"
|
||||
registro["chegada_utc"] = chegada.isoformat(timespec="minutes").replace("+00:00", "") + ":00Z"
|
||||
registro["cruza_meia_noite"] = chegada.date() > partida.date()
|
||||
|
||||
|
||||
def id_registro(linha: dict[str, Any]) -> str:
|
||||
om = texto_limpo(linha["om"]) or "SEMOM"
|
||||
etapa = texto_limpo(linha["etapa"]) or "SEMETAPA"
|
||||
return f"RV2025_OM{om}_E{etapa}_L{linha['linha_origem']}"
|
||||
|
||||
|
||||
def linha_tem_voo(ws: openpyxl.worksheet.worksheet.Worksheet, row: int) -> bool:
|
||||
return any(ws.cell(row, col).value not in (None, "") for col in range(1, 13))
|
||||
|
||||
|
||||
def extrair_registros() -> list[dict[str, Any]]:
|
||||
wb = openpyxl.load_workbook(RAW_PATH, data_only=True)
|
||||
ws = wb.active
|
||||
|
||||
registros: list[dict[str, Any]] = []
|
||||
for row in range(4, ws.max_row + 1):
|
||||
if row in LINHAS_ORIGEM_EXCLUIDAS:
|
||||
continue
|
||||
if not linha_tem_voo(ws, row):
|
||||
continue
|
||||
|
||||
data_voo = parse_data(ws.cell(row, 1).value, ws.cell(row, 2).value)
|
||||
mes = normalizar_mes(ws.cell(row, 2).value)
|
||||
dep_min, dep_offset = minutos_do_dia(ws.cell(row, 8).value)
|
||||
arr_min, arr_offset_excel = minutos_do_dia(ws.cell(row, 9).value)
|
||||
arr_offset = max(arr_offset_excel, 1 if arr_min <= dep_min else 0)
|
||||
tempo_min = minutos_duracao(ws.cell(row, 10).value)
|
||||
|
||||
registro = {
|
||||
"linha_origem": row,
|
||||
"data": data_voo.strftime("%d/%m"),
|
||||
"data_iso": data_voo.isoformat(),
|
||||
"mes": mes,
|
||||
"mes_numero": MES_MAP.get(mes, ""),
|
||||
"om": ws.cell(row, 3).value,
|
||||
"aeronave": ws.cell(row, 4).value,
|
||||
"etapa": ws.cell(row, 5).value,
|
||||
"localidade_dep": texto_limpo(ws.cell(row, 6).value).upper(),
|
||||
"localidade_arr": texto_limpo(ws.cell(row, 7).value).upper(),
|
||||
"horario_z_dep": hhmm(dep_min),
|
||||
"horario_z_arr": hhmm(arr_min),
|
||||
"partida_utc": iso_utc(data_voo, dep_min, dep_offset),
|
||||
"chegada_utc": iso_utc(data_voo, arr_min, arr_offset),
|
||||
"tempo_voo": duracao_hhmm(tempo_min),
|
||||
"tempo_voo_min": tempo_min,
|
||||
"segmento": ws.cell(row, 11).value,
|
||||
"pousos": ws.cell(row, 12).value,
|
||||
"missao": texto_limpo(ws.cell(row, 35).value).upper(),
|
||||
"periodo": texto_limpo(ws.cell(row, 36).value).upper(),
|
||||
"cruza_meia_noite": arr_offset > dep_offset,
|
||||
}
|
||||
registro["id_registro"] = id_registro(registro)
|
||||
registros.append(registro)
|
||||
|
||||
corrigir_continuidade_temporal(registros)
|
||||
return registros
|
||||
|
||||
|
||||
def corrigir_continuidade_temporal(registros: list[dict[str, Any]]) -> None:
|
||||
grupos: dict[tuple[str, str], list[dict[str, Any]]] = {}
|
||||
for registro in registros:
|
||||
chave = (texto_limpo(registro["om"]), texto_limpo(registro["aeronave"]))
|
||||
grupos.setdefault(chave, []).append(registro)
|
||||
|
||||
for linhas in grupos.values():
|
||||
linhas.sort(key=lambda registro: registro["linha_origem"])
|
||||
chegada_anterior: datetime | None = None
|
||||
for registro in linhas:
|
||||
partida = parse_iso_utc(registro["partida_utc"])
|
||||
chegada = parse_iso_utc(registro["chegada_utc"])
|
||||
while chegada_anterior is not None and partida < chegada_anterior:
|
||||
partida += timedelta(days=1)
|
||||
chegada += timedelta(days=1)
|
||||
atualizar_datas_do_registro(registro, partida, chegada)
|
||||
chegada_anterior = chegada
|
||||
|
||||
|
||||
def escrever_csv(caminho: Path, colunas: list[str], linhas: list[dict[str, Any]]) -> None:
|
||||
with caminho.open("w", newline="", encoding="utf-8-sig") as arquivo:
|
||||
escritor = csv.DictWriter(arquivo, fieldnames=colunas)
|
||||
escritor.writeheader()
|
||||
escritor.writerows(linhas)
|
||||
|
||||
|
||||
def escrever_xlsx(caminho: Path, colunas: list[str], linhas: list[dict[str, Any]]) -> None:
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "registro_voo_2025"
|
||||
ws.append(colunas)
|
||||
for linha in linhas:
|
||||
ws.append([linha[coluna] for coluna in colunas])
|
||||
ws.freeze_panes = "A2"
|
||||
ws.auto_filter.ref = ws.dimensions
|
||||
for column_cells in ws.columns:
|
||||
letter = column_cells[0].column_letter
|
||||
max_len = max(len(texto_limpo(cell.value)) for cell in column_cells)
|
||||
ws.column_dimensions[letter].width = min(max(max_len + 2, 10), 28)
|
||||
wb.save(caminho)
|
||||
|
||||
|
||||
def preparar_oamrp(registros: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
linhas = []
|
||||
for registro in registros:
|
||||
linhas.append(
|
||||
{
|
||||
"id": registro["id_registro"],
|
||||
"orig": registro["localidade_dep"],
|
||||
"dest": registro["localidade_arr"],
|
||||
"partida_utc": registro["partida_utc"],
|
||||
"chegada_utc": registro["chegada_utc"],
|
||||
"prioridade": 1,
|
||||
}
|
||||
)
|
||||
return linhas
|
||||
|
||||
|
||||
def toca_rota(registro: dict[str, Any]) -> bool:
|
||||
return "ROTA" in {registro["localidade_dep"], registro["localidade_arr"]}
|
||||
|
||||
|
||||
def parse_iso_utc(valor: str) -> datetime:
|
||||
return datetime.fromisoformat(valor.replace("Z", "+00:00"))
|
||||
|
||||
|
||||
def consolidar_grupo(grupo: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
primeiro = grupo[0]
|
||||
ultimo = grupo[-1]
|
||||
tempo_total = sum(int(registro["tempo_voo_min"]) for registro in grupo)
|
||||
pousos_total = sum(int(registro["pousos"] or 0) for registro in grupo)
|
||||
missoes = sorted({registro["missao"] for registro in grupo if registro["missao"]})
|
||||
periodos = sorted({registro["periodo"] for registro in grupo if registro["periodo"]})
|
||||
linhas = [str(registro["linha_origem"]) for registro in grupo]
|
||||
cruza_meia_noite = parse_iso_utc(ultimo["chegada_utc"]).date() > parse_iso_utc(
|
||||
primeiro["partida_utc"]
|
||||
).date()
|
||||
|
||||
return {
|
||||
"id_registro": f"{primeiro['id_registro']}_C{ultimo['linha_origem']}",
|
||||
"linhas_origem": ";".join(linhas),
|
||||
"data": primeiro["data"],
|
||||
"data_iso": primeiro["data_iso"],
|
||||
"mes": primeiro["mes"],
|
||||
"mes_numero": primeiro["mes_numero"],
|
||||
"om": primeiro["om"],
|
||||
"aeronave": primeiro["aeronave"],
|
||||
"etapa_inicio": primeiro["etapa"],
|
||||
"etapa_fim": ultimo["etapa"],
|
||||
"localidade_dep": primeiro["localidade_dep"],
|
||||
"localidade_arr": ultimo["localidade_arr"],
|
||||
"horario_z_dep": primeiro["horario_z_dep"],
|
||||
"horario_z_arr": ultimo["horario_z_arr"],
|
||||
"partida_utc": primeiro["partida_utc"],
|
||||
"chegada_utc": ultimo["chegada_utc"],
|
||||
"tempo_voo": duracao_hhmm(tempo_total),
|
||||
"tempo_voo_min": tempo_total,
|
||||
"segmento_inicio": primeiro["segmento"],
|
||||
"segmento_fim": ultimo["segmento"],
|
||||
"pousos": pousos_total,
|
||||
"missao": ";".join(missoes),
|
||||
"periodo": ";".join(periodos),
|
||||
"cruza_meia_noite": cruza_meia_noite,
|
||||
"qtd_linhas_originais": len(grupo),
|
||||
"tem_rota_intermediaria": any(toca_rota(registro) for registro in grupo),
|
||||
}
|
||||
|
||||
|
||||
def consolidar_transicoes_rota(registros: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
grupos: dict[tuple[str, str], list[dict[str, Any]]] = {}
|
||||
for registro in registros:
|
||||
chave = (texto_limpo(registro["om"]), texto_limpo(registro["aeronave"]))
|
||||
grupos.setdefault(chave, []).append(registro)
|
||||
|
||||
consolidados: list[dict[str, Any]] = []
|
||||
for chave in sorted(grupos):
|
||||
linhas = sorted(
|
||||
grupos[chave],
|
||||
key=lambda registro: (parse_iso_utc(registro["partida_utc"]), registro["linha_origem"]),
|
||||
)
|
||||
idx = 0
|
||||
while idx < len(linhas):
|
||||
atual = linhas[idx]
|
||||
if not toca_rota(atual):
|
||||
consolidados.append(consolidar_grupo([atual]))
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
grupo = [atual]
|
||||
idx += 1
|
||||
while (
|
||||
idx < len(linhas)
|
||||
and toca_rota(linhas[idx])
|
||||
and grupo[-1]["localidade_arr"] == linhas[idx]["localidade_dep"]
|
||||
):
|
||||
grupo.append(linhas[idx])
|
||||
idx += 1
|
||||
if grupo[-1]["localidade_arr"] != "ROTA":
|
||||
break
|
||||
consolidados.append(consolidar_grupo(grupo))
|
||||
|
||||
return sorted(consolidados, key=lambda registro: parse_iso_utc(registro["partida_utc"]))
|
||||
|
||||
|
||||
def preparar_oamrp_consolidado(registros: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
linhas = []
|
||||
for registro in registros:
|
||||
linhas.append(
|
||||
{
|
||||
"id": registro["id_registro"],
|
||||
"orig": registro["localidade_dep"],
|
||||
"dest": registro["localidade_arr"],
|
||||
"partida_utc": registro["partida_utc"],
|
||||
"chegada_utc": registro["chegada_utc"],
|
||||
"prioridade": 1,
|
||||
}
|
||||
)
|
||||
return linhas
|
||||
|
||||
|
||||
def gerar_resumo(
|
||||
registros: list[dict[str, Any]],
|
||||
registros_consolidados: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
faltantes = {
|
||||
coluna: sum(registro[coluna] in (None, "") for registro in registros)
|
||||
for coluna in COLUNAS_REGISTRO
|
||||
}
|
||||
return {
|
||||
"arquivo_origem": str(RAW_PATH.relative_to(BASE_DIR)),
|
||||
"ano_assumido": ANO,
|
||||
"linhas_origem_excluidas": sorted(LINHAS_ORIGEM_EXCLUIDAS),
|
||||
"registros_extraidos": len(registros),
|
||||
"registros_consolidados": len(registros_consolidados),
|
||||
"linhas_com_cruzamento_de_meia_noite": sum(
|
||||
1 for registro in registros if registro["cruza_meia_noite"]
|
||||
),
|
||||
"linhas_com_rota": sum(1 for registro in registros if toca_rota(registro)),
|
||||
"trechos_consolidados_com_rota": sum(
|
||||
1 for registro in registros_consolidados if registro["tem_rota_intermediaria"]
|
||||
),
|
||||
"aeronaves": dict(sorted(Counter(r["aeronave"] for r in registros).items())),
|
||||
"periodos": dict(Counter(r["periodo"] for r in registros)),
|
||||
"meses": dict(Counter(r["mes"] for r in registros)),
|
||||
"campos_faltantes": {k: v for k, v in faltantes.items() if v},
|
||||
"observacoes": [
|
||||
"Colunas REAL ate COM2 descartadas conforme criterio informado.",
|
||||
"Horarios tratados como UTC/Z; nao houve conversao de fuso.",
|
||||
"Chegadas iguais ou anteriores a partida foram deslocadas para o dia seguinte.",
|
||||
"SEGMTO foi preservado da planilha original, sem recalculo.",
|
||||
"Transicoes com ROTA tambem foram consolidadas em arquivo separado.",
|
||||
"Prioridade OAMRP definida como 1 por falta de coluna de prioridade na origem.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
PREPROCESS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
PROCESSED_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
registros = extrair_registros()
|
||||
registros_consolidados = consolidar_transicoes_rota(registros)
|
||||
escrever_csv(LIMPO_CSV, COLUNAS_REGISTRO, registros)
|
||||
escrever_csv(PROCESSED_CSV, COLUNAS_REGISTRO, registros)
|
||||
escrever_xlsx(LIMPO_XLSX, COLUNAS_REGISTRO, registros)
|
||||
escrever_csv(CONSOLIDADO_CSV, COLUNAS_CONSOLIDADAS, registros_consolidados)
|
||||
escrever_csv(PROCESSED_CONSOLIDADO_CSV, COLUNAS_CONSOLIDADAS, registros_consolidados)
|
||||
escrever_csv(MISSOES_CSV, COLUNAS_OAMRP, preparar_oamrp(registros))
|
||||
escrever_csv(
|
||||
MISSOES_CONSOLIDADAS_CSV,
|
||||
COLUNAS_OAMRP,
|
||||
preparar_oamrp_consolidado(registros_consolidados),
|
||||
)
|
||||
|
||||
with RESUMO_JSON.open("w", encoding="utf-8") as arquivo:
|
||||
json.dump(
|
||||
gerar_resumo(registros, registros_consolidados),
|
||||
arquivo,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
arquivo.write("\n")
|
||||
|
||||
print(f"Registros extraidos: {len(registros)}")
|
||||
print(f"Registros consolidados: {len(registros_consolidados)}")
|
||||
print(f"CSV final: {PROCESSED_CSV.relative_to(BASE_DIR)}")
|
||||
print(f"CSV consolidado: {PROCESSED_CONSOLIDADO_CSV.relative_to(BASE_DIR)}")
|
||||
print(f"CSV OAMRP: {MISSOES_CSV.relative_to(BASE_DIR)}")
|
||||
print(f"CSV OAMRP consolidado: {MISSOES_CONSOLIDADAS_CSV.relative_to(BASE_DIR)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1268
db/pre_process/registro_voo_2025/registro_voo_2025_consolidado.csv
Normal file
1268
db/pre_process/registro_voo_2025/registro_voo_2025_consolidado.csv
Normal file
File diff suppressed because it is too large
Load Diff
1421
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.csv
Normal file
1421
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.xlsx
Normal file
BIN
db/pre_process/registro_voo_2025/registro_voo_2025_limpo.xlsx
Normal file
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"arquivo_origem": "db\\raw\\Planilha 2025.xlsx",
|
||||
"ano_assumido": 2025,
|
||||
"linhas_origem_excluidas": [
|
||||
793
|
||||
],
|
||||
"registros_extraidos": 1420,
|
||||
"registros_consolidados": 1267,
|
||||
"linhas_com_cruzamento_de_meia_noite": 53,
|
||||
"linhas_com_rota": 288,
|
||||
"trechos_consolidados_com_rota": 135,
|
||||
"aeronaves": {
|
||||
"2800": 329,
|
||||
"2803": 513,
|
||||
"2809": 212,
|
||||
"2811": 366
|
||||
},
|
||||
"periodos": {
|
||||
"NOTURNO": 247,
|
||||
"DIURNO": 1145,
|
||||
"NVG": 28
|
||||
},
|
||||
"meses": {
|
||||
"JAN": 69,
|
||||
"FEV": 179,
|
||||
"MAR": 166,
|
||||
"ABR": 184,
|
||||
"MAI": 218,
|
||||
"JUN": 144,
|
||||
"JUL": 106,
|
||||
"AGO": 36,
|
||||
"SET": 160,
|
||||
"OUT": 29,
|
||||
"NOV": 66,
|
||||
"DEZ": 63
|
||||
},
|
||||
"campos_faltantes": {
|
||||
"pousos": 9
|
||||
},
|
||||
"observacoes": [
|
||||
"Colunas REAL ate COM2 descartadas conforme criterio informado.",
|
||||
"Horarios tratados como UTC/Z; nao houve conversao de fuso.",
|
||||
"Chegadas iguais ou anteriores a partida foram deslocadas para o dia seguinte.",
|
||||
"SEGMTO foi preservado da planilha original, sem recalculo.",
|
||||
"Transicoes com ROTA tambem foram consolidadas em arquivo separado.",
|
||||
"Prioridade OAMRP definida como 1 por falta de coluna de prioridade na origem."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
# Validacao do registro de voo 2025
|
||||
|
||||
Fonte validada: `db/processed/registro_voo_2025_consolidado.csv`
|
||||
|
||||
## Resultado geral
|
||||
|
||||
- Linhas originais limpas: 1420.
|
||||
- Trechos consolidados: 1267.
|
||||
- Linhas originais excluidas: 793.
|
||||
- Trechos com `ROTA` no consolidado: 0.
|
||||
- Transicoes `ROTA` convertidas para origem/destino reais: 135.
|
||||
- Cruzamentos de 00Z preservados: 53.
|
||||
- IDs duplicados: 0.
|
||||
- Divergencias de duracao no registro original: 0.
|
||||
- Trechos consolidados com tempo decorrido menor que tempo de voo: 0.
|
||||
- Sobreposicoes temporais dentro da mesma OM/aeronave: 0.
|
||||
|
||||
## Pendencias encontradas
|
||||
|
||||
### 1. Trecho consolidado com pousos zero
|
||||
|
||||
Trecho consolidado:
|
||||
|
||||
| Campo | Valor |
|
||||
| --- | --- |
|
||||
| ID | RV2025_OM219_E4_L1108_C1109 |
|
||||
| Linhas originais | 1108;1109 |
|
||||
| Data | 01/09 |
|
||||
| OM | 219 |
|
||||
| Aeronave | 2809 |
|
||||
| Trecho | SBCG -> SBCG |
|
||||
| Tempo de voo | 01:50 |
|
||||
| Pousos | 0 |
|
||||
| Missao | 50TT04;50TT05 |
|
||||
| Periodo | NOTURNO |
|
||||
|
||||
Classificacao: verificar se o pouso ficou ausente por estar dividido pela transicao `ROTA` em 00Z.
|
||||
|
||||
### 2. Quebras de continuidade espacial
|
||||
|
||||
Foram encontradas 3 quebras de continuidade espacial dentro da mesma OM/aeronave, sem sobreposicao temporal.
|
||||
|
||||
| OM | Aeronave | Trecho anterior | Trecho seguinte | Hipotese |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 7 | 2809 | SBMN -> SBBV | SBMN -> SBBV | Provavel erro ou duplicidade de origem; nao ha retorno registrado entre as duas etapas. |
|
||||
| 55 | 2803 | SBUA -> SWMK | SWMQ -> SBUA | Provavel divergencia de digitacao entre `SWMK` e `SWMQ`. |
|
||||
| 199 | 2800 | SBBR -> SBBR | SBGL -> SBAN | Lacuna operacional ou etapa ausente entre Brasilia e Galeao. |
|
||||
|
||||
## Decisao de validacao
|
||||
|
||||
O arquivo consolidado esta tecnicamente consistente para um primeiro uso no modelo OAMRP, desde que as 3 quebras espaciais sejam tratadas como excecoes conhecidas ou filtradas em uma rodada inicial.
|
||||
|
||||
Recomendacao para a primeira simulacao:
|
||||
|
||||
- Usar `db/pre_process/registro_voo_2025/missoes_oamrp_2025_consolidado.csv`.
|
||||
- Comecar por janeiro/2025, pois o periodo contem apenas a quebra da OM 7 entre as pendencias de continuidade listadas.
|
||||
- Manter as pendencias acima sem correcao automatica ate confirmacao humana.
|
||||
@@ -6,12 +6,13 @@ from pathlib import Path
|
||||
import pdfplumber
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
BASE_NAME = "relatorio_ciclo_inspecoes_c105_2805_2026-06-15"
|
||||
OUTPUT_DIR = Path(__file__).resolve().parent
|
||||
PDF_PATH = ROOT / "raw" / f"{BASE_NAME}.pdf"
|
||||
TXT_PATH = ROOT / "pre_process" / f"{BASE_NAME}_texto.txt"
|
||||
JSON_PATH = ROOT / "pre_process" / f"{BASE_NAME}_inspecoes.json"
|
||||
CSV_PATH = ROOT / "pre_process" / f"{BASE_NAME}_inspecoes.csv"
|
||||
TXT_PATH = OUTPUT_DIR / f"{BASE_NAME}_texto.txt"
|
||||
JSON_PATH = OUTPUT_DIR / f"{BASE_NAME}_inspecoes.json"
|
||||
CSV_PATH = OUTPUT_DIR / f"{BASE_NAME}_inspecoes.csv"
|
||||
|
||||
ROW_START_RE = re.compile(r"^(?P<seq>\d+)\s+")
|
||||
INTERVAL_RE = re.compile(
|
||||
6
db/processed/f0_cenario_esquadrao.json
Normal file
6
db/processed/f0_cenario_esquadrao.json
Normal 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}
|
||||
}
|
||||
6
db/processed/f0_cenario_insp_grande.json
Normal file
6
db/processed/f0_cenario_insp_grande.json
Normal 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
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
51
db/processed/ofrag.csv
Normal 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
|
||||
|
1120
db/processed/planejamento_insp_grande.html
Normal file
1120
db/processed/planejamento_insp_grande.html
Normal file
File diff suppressed because one or more lines are too long
1421
db/processed/registro_voo_2025.csv
Normal file
1421
db/processed/registro_voo_2025.csv
Normal file
File diff suppressed because it is too large
Load Diff
1268
db/processed/registro_voo_2025_consolidado.csv
Normal file
1268
db/processed/registro_voo_2025_consolidado.csv
Normal file
File diff suppressed because it is too large
Load Diff
62
db/processed/resultado_oamrp_jan_2025.csv
Normal file
62
db/processed/resultado_oamrp_jan_2025.csv
Normal 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
|
||||
|
51
db/processed/resultado_oamrp_sintetico.csv
Normal file
51
db/processed/resultado_oamrp_sintetico.csv
Normal 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,,
|
||||
|
23
db/processed/resultado_oamrp_v3.csv
Normal file
23
db/processed/resultado_oamrp_v3.csv
Normal 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,,
|
||||
|
BIN
db/raw/ICA 66-31 2023.pdf
Normal file
BIN
db/raw/ICA 66-31 2023.pdf
Normal file
Binary file not shown.
BIN
db/raw/Planilha 2025.xlsx
Normal file
BIN
db/raw/Planilha 2025.xlsx
Normal file
Binary file not shown.
257
docs/about.md
Normal file
257
docs/about.md
Normal 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. 29–44) 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 1–5 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
45
docs/authors.md
Normal 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
226
docs/changelog.md
Normal 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 1–5, 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 (C1–C6): fluxo de conservação, cobertura de missões, restrição de voo ativo, orçamento de horas por inspeção.
|
||||
- Fase 2 (C7–C11): 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 (Q1–Q5) 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.
|
||||
102
docs/roteiro_apresentacao_oamrp.doc
Normal file
102
docs/roteiro_apresentacao_oamrp.doc
Normal file
@@ -0,0 +1,102 @@
|
||||
{\rtf1\ansi\deff0
|
||||
{\fonttbl{\f0 Arial;}{\f1 Courier New;}}
|
||||
\fs24
|
||||
\b Roteiro para Apresentacao PowerPoint - OAMRP v1\b0\par
|
||||
\par
|
||||
\b Tema:\b0 Aircraft Routing - Esquadrao Arara (Transporte C-95/C-105)\par
|
||||
\b Objetivo da apresentacao:\b0 apresentar uma primeira ideia de modelo matematico/computacional para apoiar a atribuicao de aeronaves a missoes de transporte, considerando prioridade operacional, continuidade de rota, tempo minimo em solo e limite de horas ate inspecao.\par
|
||||
\par
|
||||
\b Ideia inicial 1 - Modelo MIP compacto para atribuicao de aeronaves\b0\par
|
||||
\par
|
||||
Esta primeira ideia usa um modelo de programacao inteira mista (MIP) para escolher quais missoes serao cumpridas e por qual aeronave. Nesta versao, Manaus (MN) foi definida como base operacional do Esquadrao Arara, e as missoes simuladas partem de MN para outras localidades representativas da malha da FAB. As missoes sao informadas com data e hora UTC, como em uma planilha operacional, e o codigo converte internamente para horas decimais no horizonte de planejamento. O foco nao e minimizar custo, como em modelos civis tradicionais, mas maximizar o cumprimento ponderado de missoes, dando maior peso as missoes de maior prioridade.\par
|
||||
\par
|
||||
O modelo trata cada missao como opcional. Isso significa que uma missao pode ficar de fora quando nao houver aeronave disponivel, quando a conexao temporal/geografica nao for viavel, ou quando o limite de horas de celula ate a proxima inspecao impedir a execucao segura da rota.\par
|
||||
\par
|
||||
\b Estrutura sugerida dos slides\b0\par
|
||||
\par
|
||||
\b Slide 1 - Titulo\b0\par
|
||||
Titulo sugerido: Otimizacao de Rotas e Atribuicao de Aeronaves no Esquadrao Arara.\par
|
||||
Fala sugerida: Esta apresentacao mostra uma primeira proposta de ferramenta de apoio a decisao para distribuir aeronaves C-95/C-105 entre missoes de transporte, respeitando restricoes operacionais e de manutencao.\par
|
||||
\par
|
||||
\b Slide 2 - Problema operacional\b0\par
|
||||
Mensagem central: o esquadrao precisa decidir rapidamente quais aeronaves atendem quais missoes, mantendo coerencia de rota, disponibilidade temporal e margem de horas antes da inspecao.\par
|
||||
Pontos para apresentar:\par
|
||||
- Ha varias missoes com origens, destinos, horarios e prioridades diferentes.\par
|
||||
- Cada aeronave parte de Manaus (MN), base operacional do Arara nesta simulacao, e possui horas acumuladas desde a ultima inspecao.\par
|
||||
- Nem toda combinacao de missoes e viavel para uma mesma aeronave.\par
|
||||
\par
|
||||
\b Slide 3 - Objetivo do modelo\b0\par
|
||||
Mensagem central: maximizar o valor operacional das missoes cumpridas e, em segundo nivel, organizar o uso do LRT para apoiar a manutencao.\par
|
||||
Fala sugerida: Em vez de obrigar que todas as missoes sejam atendidas, o modelo escolhe o melhor conjunto possivel. Missoes mais importantes recebem maior peso. Depois de encontrar a melhor prioridade operacional, o modelo escolhe a distribuicao que reduz o LRT restante ponderado, priorizando aeronaves mais proximas da manutencao.\par
|
||||
\par
|
||||
\b Slide 4 - Entradas do modelo\b0\par
|
||||
Entradas usadas na versao inicial:\par
|
||||
- Base operacional simulada: MN, Manaus.\par
|
||||
- Base habilitada a inspecao: MN, Manaus.\par
|
||||
- Turnaround minimo: 1 hora.\par
|
||||
- Limite de horas ate inspecao obrigatoria: 100 horas.\par
|
||||
- Aeronaves: FAB-2301, FAB-2302, FAB-2303 e FAB-2304.\par
|
||||
- Missoes: 20 missoes simuladas entre Manaus e outras localidades, incluindo BR, BE, PV, BV, GL, CG, RF e NT.\par
|
||||
- Horarios das missoes: entrada em data/hora UTC, com possibilidade de carregar CSV exportado de planilha.\par
|
||||
\par
|
||||
\b Slide 5 - Restricoes consideradas\b0\par
|
||||
Restricoes principais:\par
|
||||
- Cada missao pode ser cumprida por no maximo uma aeronave.\par
|
||||
- A aeronave so pode iniciar uma rota em uma missao que parte de sua base inicial.\par
|
||||
- A continuidade da rota exige que o destino de uma missao seja a origem da proxima.\par
|
||||
- Deve haver turnaround minimo entre chegada e nova partida.\par
|
||||
- A soma das horas voadas por aeronave nao pode ultrapassar o LRT disponivel.\par
|
||||
\par
|
||||
\b Slide 6 - Resultado da execucao inicial\b0\par
|
||||
Resultado obtido ao rodar o arquivo software/oamrp_v1.py:\par
|
||||
\par
|
||||
{\f1
|
||||
STATUS: Optimal\par
|
||||
Missoes cumpridas: 20/20\par
|
||||
Prioridade obtida: 74/74\par
|
||||
Todas as missoes foram atendidas\par
|
||||
Horizonte inicia em UTC: 2026-07-01T06:00:00Z\par
|
||||
LRT restante total: 124h\par
|
||||
Criterio secundario: menor LRT restante ponderado para manutencao\par
|
||||
}\f0
|
||||
\par
|
||||
Fala sugerida: Com os dados de exemplo, o solver encontrou uma solucao otima. As 20 missoes simuladas foram cumpridas, com aproveitamento integral da prioridade operacional planejada. Em seguida, a solucao foi refinada para usar melhor o LRT das aeronaves mais proximas da manutencao.\par
|
||||
\par
|
||||
\b Slide 7 - Distribuicao das aeronaves\b0\par
|
||||
Rotas encontradas:\par
|
||||
\par
|
||||
{\f1
|
||||
FAB-2301: M07 -> M08\par
|
||||
FAB-2302: M01 -> M02 -> M11 -> M12 -> M13 -> M14 -> M19 -> M20\par
|
||||
FAB-2303: M05 -> M06 -> M09 -> M10 -> M15 -> M16 -> M17 -> M18\par
|
||||
FAB-2304: M03 -> M04\par
|
||||
}\f0
|
||||
\par
|
||||
Fala sugerida: O resultado mostra uma sequencia coerente para cada aeronave. Cada rota respeita origem, destino, horarios e limite de horas disponiveis ate a inspecao.\par
|
||||
\par
|
||||
\b Slide 8 - Leitura operacional do resultado\b0\par
|
||||
Mensagem central: a malha simulada mostra que a frota consegue cumprir o pacote completo de missoes, mas algumas aeronaves ficam mais pressionadas pelo limite de horas.\par
|
||||
Pontos para apresentar:\par
|
||||
- A prioridade total possivel era 74.\par
|
||||
- A solucao atingiu 74 pontos de prioridade.\par
|
||||
- Todas as missoes foram cumpridas.\par
|
||||
- A FAB-2302 e a FAB-2304 ficaram com LRT restante igual a 0 hora, mostrando aeronaves prontas para entrar no ciclo de manutencao.\par
|
||||
\par
|
||||
\b Slide 9 - Valor para o planejamento\b0\par
|
||||
Beneficios da abordagem:\par
|
||||
- Apoia decisoes rapidas e rastreaveis.\par
|
||||
- Ajuda a visualizar gargalos de frota, horario e manutencao.\par
|
||||
- Permite testar cenarios com novas missoes, prioridades ou disponibilidade de aeronaves.\par
|
||||
- Reduz a dependencia de avaliacao manual quando ha muitas combinacoes possiveis.\par
|
||||
\par
|
||||
\b Slide 10 - Proximos passos\b0\par
|
||||
Evolucoes sugeridas:\par
|
||||
- Substituir os dados ficticios por dados reais do Esquadrao Arara.\par
|
||||
- Incluir pousos intermediarios, pernoites e janelas de disponibilidade de tripulacao.\par
|
||||
- Modelar inspecao com reset de horas em bases habilitadas.\par
|
||||
- Gerar graficos e tabelas automaticas para uso direto em briefing.\par
|
||||
- Comparar cenarios: frota completa, aeronave indisponivel, aumento de demanda ou restricao de base.\par
|
||||
\par
|
||||
\b Fechamento sugerido\b0\par
|
||||
Esta versao inicial demonstra que e possivel transformar o problema de atribuicao de aeronaves em um modelo de otimizacao explicavel. Mesmo com dados simples, o modelo ja produz uma solucao coerente, priorizada e aderente as restricoes principais de operacao e manutencao.\par
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
108
software/gerar_ofrag.py
Normal file
108
software/gerar_ofrag.py
Normal 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
271
software/oamrp_v1.py
Normal file
@@ -0,0 +1,271 @@
|
||||
"""
|
||||
============================================================================
|
||||
AIRCRAFT ROUTING - ESQUADRAO ARARA (Transporte C-95/C-105)
|
||||
============================================================================
|
||||
Modelo MIP compacto que ATRIBUI aeronaves a missoes de transporte,
|
||||
maximizando o numero (ponderado por prioridade) de missoes cumpridas,
|
||||
respeitando:
|
||||
- rota viavel (continuidade espacial e temporal por aeronave)
|
||||
- turnaround minimo entre missoes
|
||||
- limite de horas de celula ate a proxima inspecao (LRT)
|
||||
- base inicial e horas acumuladas reais de cada aeronave
|
||||
|
||||
Diferenca chave vs. modelo civil (Bazargan): aqui a cobertura e' "<= 1"
|
||||
(missao OPCIONAL, selecionada se compensar) e nao "= 1". O objetivo e'
|
||||
prontidao/cumprimento de missao, nao custo.
|
||||
|
||||
Solver: CBC (vem junto com o PuLP, zero instalacao). Para instancias
|
||||
maiores troque por HiGHS (ver nota no fim do arquivo).
|
||||
|
||||
Autor: gerado como ponto de partida. pt-BR nos comentarios, codigo limpo.
|
||||
============================================================================
|
||||
"""
|
||||
|
||||
import csv
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import pulp
|
||||
|
||||
# ===========================================================================
|
||||
# 1) DADOS DE ENTRADA (substitua pelos dados reais do ARARA)
|
||||
# ===========================================================================
|
||||
# Bases (codigos curtos ficticios). 'S' marca bases HABILITADAS a inspecao.
|
||||
# Na FAB o conjunto de bases habilitadas e' restrito -> isso aperta o routing.
|
||||
# MN representa Manaus, base operacional do Esquadrao Arara nesta simulacao.
|
||||
BASES_INSPECAO = {"MN"} # Manaus
|
||||
|
||||
TURNAROUND_MIN_H = 1.0 # tempo minimo em solo entre 2 missoes (h)
|
||||
LIMITE_HORAS_F = 100.0 # horas de celula ate inspecao obrigatoria
|
||||
# (AJUSTE ao intervalo real da sua frota)
|
||||
|
||||
# ----- Aeronaves (tail number, base inicial, horas acumuladas desde a ult. insp.)
|
||||
# 'horas_acum' = f_k. LRT inicial de cada aeronave = LIMITE_HORAS_F - f_k.
|
||||
AERONAVES = [
|
||||
# id base horas_acum
|
||||
("FAB-2301", "MN", 12.0),
|
||||
("FAB-2302", "MN", 71.0), # ja gastou muito -> pouco LRT disponivel
|
||||
("FAB-2303", "MN", 30.0),
|
||||
("FAB-2304", "MN", 95.0), # quase no limite -> mal consegue voar
|
||||
]
|
||||
|
||||
# ----- Missoes de transporte
|
||||
# Entrada recomendada: data/hora UTC, como viria de uma planilha.
|
||||
# Para usar uma planilha, exporte para CSV com as colunas:
|
||||
# id,orig,dest,partida_utc,chegada_utc,prioridade
|
||||
# e preencha CSV_MISSOES_PATH com o caminho do arquivo.
|
||||
CSV_MISSOES_PATH = None
|
||||
|
||||
MISSOES_UTC = [
|
||||
# id orig dest partida UTC chegada UTC prio
|
||||
("M01", "MN", "BR", "2026-07-01T06:00Z", "2026-07-01T10:00Z", 5),
|
||||
("M02", "BR", "MN", "2026-07-01T12:00Z", "2026-07-01T16:00Z", 5),
|
||||
("M03", "MN", "BE", "2026-07-01T06:30Z", "2026-07-01T09:00Z", 4),
|
||||
("M04", "BE", "MN", "2026-07-01T11:00Z", "2026-07-01T13:30Z", 4),
|
||||
("M05", "MN", "PV", "2026-07-01T07:00Z", "2026-07-01T09:00Z", 3),
|
||||
("M06", "PV", "MN", "2026-07-01T10:30Z", "2026-07-01T12:30Z", 3),
|
||||
("M07", "MN", "BV", "2026-07-01T08:00Z", "2026-07-01T09:30Z", 3),
|
||||
("M08", "BV", "MN", "2026-07-01T11:00Z", "2026-07-01T12:30Z", 3),
|
||||
("M09", "MN", "GL", "2026-07-01T18:00Z", "2026-07-01T23:00Z", 4),
|
||||
("M10", "GL", "MN", "2026-07-02T01:00Z", "2026-07-02T06:00Z", 4),
|
||||
("M11", "MN", "CG", "2026-07-01T20:00Z", "2026-07-01T23:00Z", 2),
|
||||
("M12", "CG", "MN", "2026-07-02T01:00Z", "2026-07-02T04:00Z", 2),
|
||||
("M13", "MN", "RF", "2026-07-02T06:00Z", "2026-07-02T11:00Z", 4),
|
||||
("M14", "RF", "MN", "2026-07-02T13:00Z", "2026-07-02T18:00Z", 4),
|
||||
("M15", "MN", "NT", "2026-07-02T08:00Z", "2026-07-02T12:30Z", 3),
|
||||
("M16", "NT", "MN", "2026-07-02T14:00Z", "2026-07-02T18:30Z", 3),
|
||||
("M17", "MN", "BR", "2026-07-03T00:00Z", "2026-07-03T04:00Z", 5),
|
||||
("M18", "BR", "MN", "2026-07-03T06:00Z", "2026-07-03T10:00Z", 5),
|
||||
("M19", "MN", "BE", "2026-07-03T02:00Z", "2026-07-03T04:30Z", 4),
|
||||
("M20", "BE", "MN", "2026-07-03T06:00Z", "2026-07-03T08:30Z", 4),
|
||||
]
|
||||
|
||||
def parse_utc(valor):
|
||||
"""Converte texto ISO UTC da planilha para datetime com timezone."""
|
||||
texto = str(valor).strip()
|
||||
if texto.endswith("Z"):
|
||||
texto = texto[:-1] + "+00:00"
|
||||
dt = datetime.fromisoformat(texto)
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
return dt.astimezone(timezone.utc)
|
||||
|
||||
def carregar_missoes_csv(caminho):
|
||||
with Path(caminho).open(newline="", encoding="utf-8-sig") as arq:
|
||||
leitor = csv.DictReader(arq)
|
||||
return [
|
||||
(
|
||||
linha["id"],
|
||||
linha["orig"],
|
||||
linha["dest"],
|
||||
linha["partida_utc"],
|
||||
linha["chegada_utc"],
|
||||
int(linha["prioridade"]),
|
||||
)
|
||||
for linha in leitor
|
||||
]
|
||||
|
||||
def converter_missoes_para_horas(missoes_utc):
|
||||
datas_partida = [parse_utc(m[3]) for m in missoes_utc]
|
||||
horizonte_inicio = min(datas_partida)
|
||||
missoes_horas = []
|
||||
for mid, orig, dest, partida_utc, chegada_utc, prio in missoes_utc:
|
||||
partida = parse_utc(partida_utc)
|
||||
chegada = parse_utc(chegada_utc)
|
||||
if chegada <= partida:
|
||||
raise ValueError(f"Missao {mid}: chegada_utc deve ser depois de partida_utc")
|
||||
part_h = (partida - horizonte_inicio).total_seconds() / 3600
|
||||
cheg_h = (chegada - horizonte_inicio).total_seconds() / 3600
|
||||
missoes_horas.append((mid, orig, dest, part_h, cheg_h, prio))
|
||||
return missoes_horas, horizonte_inicio
|
||||
|
||||
MISSOES_FONTE = carregar_missoes_csv(CSV_MISSOES_PATH) if CSV_MISSOES_PATH else MISSOES_UTC
|
||||
MISSOES, HORIZONTE_INICIO_UTC = converter_missoes_para_horas(MISSOES_FONTE)
|
||||
|
||||
# ===========================================================================
|
||||
# 2) PRE-PROCESSAMENTO (gera conexoes viaveis)
|
||||
# ===========================================================================
|
||||
miss = {m[0]: dict(orig=m[1], dest=m[2], part=m[3], cheg=m[4], prio=m[5],
|
||||
dur=m[4] - m[3]) for m in MISSOES}
|
||||
acft = {a[0]: dict(base=a[1], f=a[2]) for a in AERONAVES}
|
||||
IDS = list(miss.keys())
|
||||
KS = list(acft.keys())
|
||||
|
||||
# Conexao i->j valida: destino de i == origem de j e ha turnaround suficiente
|
||||
conex = [(i, j) for i in IDS for j in IDS
|
||||
if i != j
|
||||
and miss[i]["dest"] == miss[j]["orig"]
|
||||
and miss[j]["part"] >= miss[i]["cheg"] + TURNAROUND_MIN_H]
|
||||
|
||||
# Aeronave k pode INICIAR pela missao i se i parte da base inicial de k
|
||||
def pode_iniciar(k, i):
|
||||
return miss[i]["orig"] == acft[k]["base"]
|
||||
|
||||
# ===========================================================================
|
||||
# 3) MODELO MIP
|
||||
# ===========================================================================
|
||||
mdl = pulp.LpProblem("ARARA_Aircraft_Routing", pulp.LpMaximize)
|
||||
|
||||
# --- Variaveis
|
||||
x = {(k, i): pulp.LpVariable(f"x_{k}_{i}", cat="Binary") for k in KS for i in IDS}
|
||||
s = {(k, i): pulp.LpVariable(f"s_{k}_{i}", cat="Binary") # k inicia por i
|
||||
for k in KS for i in IDS if pode_iniciar(k, i)}
|
||||
c = {(k, i, j): pulp.LpVariable(f"c_{k}_{i}_{j}", cat="Binary") # k voa i depois j
|
||||
for k in KS for (i, j) in conex}
|
||||
z = {i: pulp.LpVariable(f"z_{i}", cat="Binary") for i in IDS} # missao coberta
|
||||
|
||||
# --- Objetivo primario: maximizar prioridade total das missoes cumpridas
|
||||
mdl += pulp.lpSum(miss[i]["prio"] * z[i] for i in IDS), "Missoes_cumpridas_ponderadas"
|
||||
|
||||
# --- (R1) Cobertura: cada missao por NO MAXIMO uma aeronave
|
||||
for i in IDS:
|
||||
mdl += z[i] == pulp.lpSum(x[(k, i)] for k in KS), f"def_z_{i}"
|
||||
# z[i] ja e' binaria, entao automaticamente <= 1
|
||||
|
||||
# --- (R2) Fluxo de entrada: se k voa i, ele iniciou por i OU chegou de outra missao
|
||||
for k in KS:
|
||||
for i in IDS:
|
||||
entradas = pulp.lpSum(c[(k, jj, i)] for (jj, ii) in conex if ii == i)
|
||||
ini = s[(k, i)] if (k, i) in s else 0
|
||||
mdl += ini + entradas == x[(k, i)], f"in_{k}_{i}"
|
||||
|
||||
# --- (R3) Fluxo de saida: depois de i, k segue para no max uma proxima missao
|
||||
for k in KS:
|
||||
for i in IDS:
|
||||
saidas = pulp.lpSum(c[(k, i, jj)] for (ii, jj) in conex if ii == i)
|
||||
mdl += saidas <= x[(k, i)], f"out_{k}_{i}"
|
||||
|
||||
# --- (R4) Cada aeronave inicia no maximo uma rota
|
||||
for k in KS:
|
||||
inis = [s[(k, i)] for i in IDS if (k, i) in s]
|
||||
if inis:
|
||||
mdl += pulp.lpSum(inis) <= 1, f"start_{k}"
|
||||
|
||||
# --- (R5) Limite de horas de celula (LRT): total voado por k <= F - f_k
|
||||
# Esta e' a restricao CENTRAL de manutencao (versao "orcamento de horas").
|
||||
# -> A inspecao com RESET de horas em base habilitada e' a extensao da Fase 3.
|
||||
for k in KS:
|
||||
mdl += pulp.lpSum(miss[i]["dur"] * x[(k, i)] for i in IDS) <= \
|
||||
LIMITE_HORAS_F - acft[k]["f"], f"horas_{k}"
|
||||
|
||||
# ===========================================================================
|
||||
# 4) RESOLVER
|
||||
# ===========================================================================
|
||||
solver = pulp.PULP_CBC_CMD(msg=False) # troque por HiGHS se instalado
|
||||
|
||||
# Etapa 1: maximiza o cumprimento ponderado das missoes.
|
||||
mdl.solve(solver)
|
||||
prio_otima = int(pulp.value(mdl.objective))
|
||||
|
||||
# Etapa 2: mantendo a prioridade otima, minimiza o LRT restante ponderado.
|
||||
# Como as horas totais voadas podem ser constantes quando todas as missoes sao
|
||||
# cumpridas, o peso prioriza consumir LRT das aeronaves mais proximas da
|
||||
# manutencao, sem sacrificar nenhuma missao prioritaria da primeira etapa.
|
||||
lrt_restante = {
|
||||
k: (LIMITE_HORAS_F - acft[k]["f"]) -
|
||||
pulp.lpSum(miss[i]["dur"] * x[(k, i)] for i in IDS)
|
||||
for k in KS
|
||||
}
|
||||
pesos_lrt = {k: 1 / (LIMITE_HORAS_F - acft[k]["f"]) for k in KS}
|
||||
mdl += pulp.lpSum(miss[i]["prio"] * z[i] for i in IDS) == prio_otima, "fixa_prioridade_otima"
|
||||
mdl.sense = pulp.LpMinimize
|
||||
mdl.setObjective(pulp.lpSum(pesos_lrt[k] * lrt_restante[k] for k in KS))
|
||||
mdl.solve(solver)
|
||||
|
||||
# ===========================================================================
|
||||
# 5) RELATORIO
|
||||
# ===========================================================================
|
||||
print("=" * 70)
|
||||
print(f" STATUS: {pulp.LpStatus[mdl.status]}")
|
||||
print(f" Horizonte inicia em UTC: {HORIZONTE_INICIO_UTC.isoformat().replace('+00:00', 'Z')}")
|
||||
prio_tot = sum(miss[i]["prio"] for i in IDS)
|
||||
prio_obt = prio_otima
|
||||
cob = [i for i in IDS if z[i].value() and z[i].value() > 0.5]
|
||||
print(f" Missoes cumpridas: {len(cob)}/{len(IDS)} "
|
||||
f"| Prioridade obtida: {prio_obt}/{prio_tot}")
|
||||
lrt_total = sum((LIMITE_HORAS_F - acft[k]["f"]) -
|
||||
sum(miss[i]["dur"] * x[(k, i)].value() for i in IDS)
|
||||
for k in KS)
|
||||
print(f" LRT restante total: {lrt_total:g}h")
|
||||
print(" Criterio secundario: menor LRT restante ponderado p/ manutencao")
|
||||
print("=" * 70)
|
||||
|
||||
def rota_da_aeronave(k):
|
||||
"""Reconstroi a sequencia de missoes da aeronave k seguindo as conexoes."""
|
||||
inicio = [i for i in IDS if (k, i) in s and s[(k, i)].value() and s[(k, i)].value() > 0.5]
|
||||
if not inicio:
|
||||
return []
|
||||
seq = [inicio[0]]
|
||||
while True:
|
||||
atual = seq[-1]
|
||||
prox = [j for (ii, jj) in conex if ii == atual
|
||||
for j in [jj] if c[(k, atual, j)].value() and c[(k, atual, j)].value() > 0.5]
|
||||
if not prox:
|
||||
break
|
||||
seq.append(prox[0])
|
||||
return seq
|
||||
|
||||
for k in KS:
|
||||
seq = rota_da_aeronave(k)
|
||||
horas = sum(miss[i]["dur"] for i in seq)
|
||||
lrt_ini = LIMITE_HORAS_F - acft[k]["f"]
|
||||
if seq:
|
||||
trechos = " -> ".join(
|
||||
f"{i}({miss[i]['orig']}-{miss[i]['dest']} {miss[i]['part']:g}h)" for i in seq)
|
||||
print(f"\n {k} [base {acft[k]['base']}, f={acft[k]['f']:g}h, LRT={lrt_ini:g}h]")
|
||||
print(f" {trechos}")
|
||||
print(f" horas voadas: {horas:g}h | LRT restante: {lrt_ini - horas:g}h")
|
||||
else:
|
||||
print(f"\n {k} [base {acft[k]['base']}, LRT={lrt_ini:g}h] -> sem missoes")
|
||||
|
||||
descobertas = [i for i in IDS if i not in cob]
|
||||
if descobertas:
|
||||
print("\n " + "-" * 60)
|
||||
print(" Missoes NAO cumpridas:",
|
||||
", ".join(f"{i}(prio {miss[i]['prio']})" for i in descobertas))
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# NOTA - usar HiGHS (recomendado p/ instancias maiores, sem licenca):
|
||||
# pip install highspy
|
||||
# solver = pulp.HiGHS(msg=False) # PuLP 3.x expoe HiGHS nativo
|
||||
# ---------------------------------------------------------------------------
|
||||
375
software/oamrp_v2.py
Normal file
375
software/oamrp_v2.py
Normal 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
1574
software/oamrp_v3.py
Normal file
File diff suppressed because it is too large
Load Diff
625
software/visualizar_resultado.py
Normal file
625
software/visualizar_resultado.py
Normal 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 4–5<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()
|
||||
Reference in New Issue
Block a user