Corrige acentuacao dos textos
This commit is contained in:
130
README.md
130
README.md
@@ -1,24 +1,24 @@
|
|||||||
# Planejador Diario de Missoes e Sobreaviso
|
# Planejador Diário de Missões e Sobreaviso
|
||||||
|
|
||||||
Projeto em Python para gerar a escala diaria de missao acionada, missao local e sobreaviso usando Programacao Linear Inteira Mista (MILP).
|
Projeto em Python para gerar a escala diária de missão acionada, missão local e sobreaviso usando Programação Linear Inteira Mista (MILP).
|
||||||
|
|
||||||
## 1. Estrutura do projeto
|
## 1. Estrutura do projeto
|
||||||
|
|
||||||
```text
|
```text
|
||||||
dados/ Arquivos de entrada do planejamento
|
dados/ Arquivos de entrada do planejamento
|
||||||
resultados/ Planilhas geradas pelo planejador
|
resultados/ Planilhas geradas pelo planejador
|
||||||
src/planejador_missao/ Codigo-fonte Python do modelo
|
src/planejador_missao/ Código-fonte Python do modelo
|
||||||
scripts/ Scripts numerados de execucao, validacao e teste
|
scripts/ Scripts numerados de execução, validação e teste
|
||||||
.vscode/ Arquivos de workspace do VS Code
|
.vscode/ Arquivos de workspace do VS Code
|
||||||
web_app.py Interface web HTML para abrir no navegador
|
web_app.py Interface web HTML para abrir no navegador
|
||||||
run_planner.py Ponto de entrada para executar o planejamento
|
run_planner.py Ponto de entrada para executar o planejamento
|
||||||
requirements.txt Dependencias Python
|
requirements.txt Dependências Python
|
||||||
Abrir Planejador.vbs Atalho para abrir a interface sem prompt
|
Abrir Planejador.vbs Atalho para abrir a interface sem prompt
|
||||||
```
|
```
|
||||||
|
|
||||||
A execucao oficial do projeto e feita somente em Python.
|
A execução oficial do projeto é feita somente em Python.
|
||||||
|
|
||||||
## 2. Preparacao do ambiente
|
## 2. Preparação do ambiente
|
||||||
|
|
||||||
No PowerShell, a partir da raiz do projeto:
|
No PowerShell, a partir da raiz do projeto:
|
||||||
|
|
||||||
@@ -29,13 +29,13 @@ python -m venv .venv
|
|||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
A pasta `.venv` nao acompanha a entrega oficial. Ela deve ser recriada pelo usuario com os comandos acima.
|
A pasta `.venv` não acompanha a entrega oficial. Ela deve ser recriada pelo usuário com os comandos acima.
|
||||||
|
|
||||||
## 3. Como executar pelo app no navegador
|
## 3. Como executar pelo app no navegador
|
||||||
|
|
||||||
Forma recomendada:
|
Forma recomendada:
|
||||||
|
|
||||||
1. De dois cliques em `Abrir Planejador.vbs`.
|
1. Dê dois cliques em `Abrir Planejador.vbs`.
|
||||||
2. Aguarde o navegador abrir em `http://127.0.0.1:8050`.
|
2. Aguarde o navegador abrir em `http://127.0.0.1:8050`.
|
||||||
|
|
||||||
Se preferir executar manualmente pelo PowerShell, use:
|
Se preferir executar manualmente pelo PowerShell, use:
|
||||||
@@ -46,19 +46,19 @@ Na raiz do projeto:
|
|||||||
python web_app.py
|
python web_app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
O navegador abre automaticamente em `http://127.0.0.1:8050`. Se nao abrir, copie esse endereco no Google Chrome.
|
O navegador abre automaticamente em `http://127.0.0.1:8050`. Se não abrir, copie esse endereço no Google Chrome.
|
||||||
|
|
||||||
O app permite:
|
O app permite:
|
||||||
|
|
||||||
- editar data e criterios de otimizacao;
|
- editar data e critérios de otimização;
|
||||||
- informar a missao de rota acionada;
|
- informar a missão de rota acionada;
|
||||||
- marcar condicao das aeronaves;
|
- marcar condição das aeronaves;
|
||||||
- selecionar tripulantes disponiveis;
|
- selecionar tripulantes disponíveis;
|
||||||
- consultar a aba `Quadrinhos`, com proxima OI, horas do ano, SBV e falta para 50 horas;
|
- consultar a aba `Quadrinhos`, com próxima OI, horas do ano, SBV e falta para 50 horas;
|
||||||
- salvar os arquivos de entrada em `dados/`;
|
- salvar os arquivos de entrada em `dados/`;
|
||||||
- gerar a escala e abrir a ultima planilha.
|
- gerar a escala e abrir a última planilha.
|
||||||
|
|
||||||
A versao final usa Python e interface web local.
|
A versão final usa Python e interface web local.
|
||||||
|
|
||||||
## 4. Como executar pelo terminal
|
## 4. Como executar pelo terminal
|
||||||
|
|
||||||
@@ -81,17 +81,17 @@ Os arquivos de entrada ficam em `dados/`.
|
|||||||
|
|
||||||
| Arquivo | Finalidade |
|
| Arquivo | Finalidade |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `Modelagem_C98_ETA2_local.xlsx` ou `Modelagem C98 ETA2.xlsx` | Cadastro base de tripulantes, qualificacoes, projetos, soldo e metas de horas. |
|
| `Modelagem_C98_ETA2_local.xlsx` ou `Modelagem C98 ETA2.xlsx` | Cadastro base de tripulantes, qualificações, projetos, soldo e metas de horas. |
|
||||||
| `catalogo_ois.xlsx` | Catalogo de OIs por aeronave, subprograma, ordem e tipo de missao. |
|
| `catalogo_ois.xlsx` | Catálogo de OIs por aeronave, subprograma, ordem e tipo de missão. |
|
||||||
| `indisponibilidades_2026.xlsx` | Periodos em que cada tripulante nao pode ser escalado. |
|
| `indisponibilidades_2026.xlsx` | Períodos em que cada tripulante não pode ser escalado. |
|
||||||
| `parametros_missao.csv` | Data de planejamento e criterios de otimizacao. |
|
| `parametros_missao.csv` | Data de planejamento e critérios de otimização. |
|
||||||
| `aeronaves_disponiveis.csv` | Condicao diaria de cada aeronave. |
|
| `aeronaves_disponiveis.csv` | Condição diária de cada aeronave. |
|
||||||
| `rotas_acionadas.csv` | Missao de rota acionada para atendimento obrigatorio. |
|
| `rotas_acionadas.csv` | Missão de rota acionada para atendimento obrigatório. |
|
||||||
| `tripulantes_disponiveis.csv` | Selecao manual dos tripulantes disponiveis no dia. |
|
| `tripulantes_disponiveis.csv` | Seleção manual dos tripulantes disponíveis no dia. |
|
||||||
| `progresso_ois_2026.xlsx` | OIs ja concluidas manualmente. |
|
| `progresso_ois_2026.xlsx` | OIs já concluídas manualmente. |
|
||||||
| `historico_horas_voadas.csv` | Historico acumulado gerado automaticamente pelo planejador. |
|
| `historico_horas_voadas.csv` | Histórico acumulado gerado automaticamente pelo planejador. |
|
||||||
|
|
||||||
## 6. Saida gerada
|
## 6. Saída gerada
|
||||||
|
|
||||||
O planejador cria uma planilha em:
|
O planejador cria uma planilha em:
|
||||||
|
|
||||||
@@ -99,17 +99,17 @@ O planejador cria uma planilha em:
|
|||||||
resultados/planejamento_diario_YYYY-MM-DD.xlsx
|
resultados/planejamento_diario_YYYY-MM-DD.xlsx
|
||||||
```
|
```
|
||||||
|
|
||||||
Quando ja existe arquivo para a mesma data, o sistema acrescenta o horario ao nome para preservar a saida anterior.
|
Quando já existe arquivo para a mesma data, o sistema acrescenta o horário ao nome para preservar a saída anterior.
|
||||||
|
|
||||||
A aba `ESCALA DIARIA` contem tres blocos:
|
A aba `ESCALA DIARIA` contém três blocos:
|
||||||
|
|
||||||
- `MISSAO ACIONADA`;
|
- `MISSAO ACIONADA`;
|
||||||
- `VOOS LOCAIS`;
|
- `VOOS LOCAIS`;
|
||||||
- `SOBREAVISO`.
|
- `SOBREAVISO`.
|
||||||
|
|
||||||
Apos a solucao, o arquivo `dados/historico_horas_voadas.csv` tambem e atualizado com horas voadas e sobreavisos registrados.
|
Após a solução, o arquivo `dados/historico_horas_voadas.csv` também é atualizado com horas voadas e sobreavisos registrados.
|
||||||
|
|
||||||
## 7. Scripts e validacao com voos de 2025
|
## 7. Scripts e validação com voos de 2025
|
||||||
|
|
||||||
O fluxo pelo VS Code pode ser executado com:
|
O fluxo pelo VS Code pode ser executado com:
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ Para conferir rapidamente o ambiente:
|
|||||||
python scripts\09_testar_instalacao.py
|
python scripts\09_testar_instalacao.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Para validar o modelo com dados historicos, coloque os voos reais de 2025 em:
|
Para validar o modelo com dados históricos, coloque os voos reais de 2025 em:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
dados/validacao/voos_2025.csv
|
dados/validacao/voos_2025.csv
|
||||||
@@ -135,19 +135,19 @@ O arquivo deve ter as colunas:
|
|||||||
data,aeronave,tipo_escala,tripulante,funcao,oi,horas_voadas,sbv
|
data,aeronave,tipo_escala,tripulante,funcao,oi,horas_voadas,sbv
|
||||||
```
|
```
|
||||||
|
|
||||||
Se a planilha `Quadro de Voo 2025 (2).xlsx` estiver em `dados/`, a validacao importa a aba `VOOS` automaticamente:
|
Se a planilha `Quadro de Voo 2025 (2).xlsx` estiver em `dados/`, a validação importa a aba `VOOS` automaticamente:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
python scripts\00_main.py --validacao 2025
|
python scripts\00_main.py --validacao 2025
|
||||||
```
|
```
|
||||||
|
|
||||||
Tambem e possivel chamar diretamente:
|
Também é possível chamar diretamente:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
python scripts\07_validacao_2025.py
|
python scripts\07_validacao_2025.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Os relatorios sao salvos em:
|
Os relatórios são salvos em:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
resultados/validacao/validacao_2025_resumo.xlsx
|
resultados/validacao/validacao_2025_resumo.xlsx
|
||||||
@@ -156,61 +156,61 @@ resultados/validacao/validacao_2025_metricas.csv
|
|||||||
resultados/validacao/validacao_2025_barras.png
|
resultados/validacao/validacao_2025_barras.png
|
||||||
```
|
```
|
||||||
|
|
||||||
Ele contem:
|
Ele contém:
|
||||||
|
|
||||||
- `metricas`: comparacao entre escala real 2025 e escala otimizada;
|
- `metricas`: comparação entre escala real 2025 e escala otimizada;
|
||||||
- `comparativo_trips`: horas reais, horas otimizadas e delta por tripulante;
|
- `comparativo_trips`: horas reais, horas otimizadas e delta por tripulante;
|
||||||
- `voos_2025_slots`: voos historicos agregados em slots;
|
- `voos_2025_slots`: voos históricos agregados em slots;
|
||||||
- `escala_otimizada`: redistribuicao proposta pelo MILP.
|
- `escala_otimizada`: redistribuição proposta pelo MILP.
|
||||||
|
|
||||||
Os scripts antigos de importacao/validacao foram preservados em `scripts/_arquivados/`.
|
Os scripts antigos de importação/validação foram preservados em `scripts/_arquivados/`.
|
||||||
|
|
||||||
## 8. Metodo de otimizacao
|
## 8. Método de otimização
|
||||||
|
|
||||||
O modelo monta previamente todas as colunas candidatas viaveis. Cada coluna representa uma possivel escala de dupla para:
|
O modelo monta previamente todas as colunas candidatas viáveis. Cada coluna representa uma possível escala de dupla para:
|
||||||
|
|
||||||
- rota acionada;
|
- rota acionada;
|
||||||
- missao local;
|
- missão local;
|
||||||
- sobreaviso.
|
- sobreaviso.
|
||||||
|
|
||||||
Cada coluna recebe uma variavel binaria no MILP:
|
Cada coluna recebe uma variável binária no MILP:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
x[i] = 1 se a coluna candidata i for escolhida
|
x[i] = 1 se a coluna candidata i for escolhida
|
||||||
x[i] = 0 caso contrario
|
x[i] = 0 caso contrário
|
||||||
```
|
```
|
||||||
|
|
||||||
A funcao objetivo maximiza o score operacional, com prioridade para:
|
A função objetivo maximiza o score operacional, com prioridade para:
|
||||||
|
|
||||||
1. atender rotas acionadas;
|
1. atender rotas acionadas;
|
||||||
2. aproveitar missoes locais para progressao operacional;
|
2. aproveitar missões locais para progressão operacional;
|
||||||
3. escolher sobreavisos conforme o criterio definido.
|
3. escolher sobreavisos conforme o critério definido.
|
||||||
|
|
||||||
Restricoes principais:
|
Restrições principais:
|
||||||
|
|
||||||
- cada tripulante aparece no maximo uma vez na escala;
|
- cada tripulante aparece no máximo uma vez na escala;
|
||||||
- cada aeronave livre recebe exatamente uma cobertura principal, por rota acionada ou sobreaviso;
|
- cada aeronave livre recebe exatamente uma cobertura principal, por rota acionada ou sobreaviso;
|
||||||
- cada aeronave executa no maximo uma rota acionada;
|
- cada aeronave executa no máximo uma rota acionada;
|
||||||
- cada aeronave executa no maximo uma missao local;
|
- cada aeronave executa no máximo uma missão local;
|
||||||
- rota acionada e missao local nao podem usar a mesma aeronave no mesmo dia;
|
- rota acionada e missão local não podem usar a mesma aeronave no mesmo dia;
|
||||||
- toda rota acionada informada deve ser atendida exatamente uma vez.
|
- toda rota acionada informada deve ser atendida exatamente uma vez.
|
||||||
|
|
||||||
O resolvedor usado e `scipy.optimize.milp`, um resolvedor exato de programacao inteira disponivel diretamente em Python.
|
O resolvedor usado é `scipy.optimize.milp`, um resolvedor exato de programação inteira disponível diretamente em Python.
|
||||||
|
|
||||||
## 9. Organizacao do codigo
|
## 9. Organização do código
|
||||||
|
|
||||||
O codigo em `src/planejador_missao/` esta dividido por blocos funcionais:
|
O código em `src/planejador_missao/` está dividido por blocos funcionais:
|
||||||
|
|
||||||
| Arquivo | Responsabilidade |
|
| Arquivo | Responsabilidade |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `data_io.py` | Leitura das planilhas/CSVs e escrita do historico. |
|
| `data_io.py` | Leitura das planilhas/CSVs e escrita do histórico. |
|
||||||
| `rules.py` | Regras de disponibilidade, qualificacao e progresso operacional. |
|
| `rules.py` | Regras de disponibilidade, qualificação e progresso operacional. |
|
||||||
| `candidates.py` | Geracao das colunas candidatas para o MILP. |
|
| `candidates.py` | Geração das colunas candidatas para o MILP. |
|
||||||
| `optimizer.py` | Formulacao e solucao do modelo MILP. |
|
| `optimizer.py` | Formulação e solução do modelo MILP. |
|
||||||
| `report.py` | Registro de horas e criacao da planilha Excel. |
|
| `report.py` | Registro de horas e criação da planilha Excel. |
|
||||||
| `quadrinhos.py` | Resumo operacional da aba Quadrinhos. |
|
| `quadrinhos.py` | Resumo operacional da aba Quadrinhos. |
|
||||||
| `validacao.py` | Validacao retrospectiva com voos historicos. |
|
| `validacao.py` | Validação retrospectiva com voos históricos. |
|
||||||
| `main.py` | Orquestracao do fluxo completo. |
|
| `main.py` | Orquestração do fluxo completo. |
|
||||||
| `utils.py` | Normalizacao de textos, datas, booleanos e horas. |
|
| `utils.py` | Normalização de textos, datas, booleanos e horas. |
|
||||||
|
|
||||||
Os comentarios no codigo indicam os blocos do fluxo e as restricoes relevantes do modelo, sem repetir conceitos elementares da linguagem.
|
Os comentários no código indicam os blocos do fluxo e as restrições relevantes do modelo, sem repetir conceitos elementares da linguagem.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Ponto de entrada do planejador diario de missoes."""
|
"""Ponto de entrada do planejador diário de missões."""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -9,6 +9,6 @@ if __name__ == "__main__":
|
|||||||
resultado = executar_planejamento(Path(__file__).resolve().parent)
|
resultado = executar_planejamento(Path(__file__).resolve().parent)
|
||||||
print("\n=== Planejamento concluido ===")
|
print("\n=== Planejamento concluido ===")
|
||||||
print(f"Data: {resultado['data_planejamento']}")
|
print(f"Data: {resultado['data_planejamento']}")
|
||||||
print(f"Arquivo de saida: {resultado['arquivo_saida']}")
|
print(f"Arquivo de saída: {resultado['arquivo_saida']}")
|
||||||
print(f"Colunas candidatas: {resultado['total_candidatas']}")
|
print(f"Colunas candidatas: {resultado['total_candidatas']}")
|
||||||
print(f"Escalas selecionadas: {resultado['total_selecionadas']}")
|
print(f"Escalas selecionadas: {resultado['total_selecionadas']}")
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
"""Orquestrador principal do Planejador Missao.
|
"""Orquestrador principal do Planejador Missão.
|
||||||
|
|
||||||
Entradas:
|
Entradas:
|
||||||
Arquivos em dados/, argumentos opcionais de linha de comando e configuracoes
|
Arquivos em dados/, argumentos opcionais de linha de comando e configuracoes
|
||||||
centralizadas em scripts/01_config.py.
|
centralizadas em scripts/01_config.py.
|
||||||
|
|
||||||
Saidas:
|
Saídas:
|
||||||
Planejamento diario em resultados/, historico operacional atualizado,
|
Planejamento diário em resultados/, histórico operacional atualizado,
|
||||||
relatorios de validacao quando solicitado e logs em logs/execucao.log.
|
relatórios de validação quando solicitado e logs em logs/execucao.log.
|
||||||
|
|
||||||
Papel no pipeline:
|
Papel no pipeline:
|
||||||
Executa o fluxo completo: leitura de dados, preparacao, geracao de colunas
|
Executa o fluxo completo: leitura de dados, preparacao, geracao de colunas
|
||||||
candidatas, montagem/solucao do MILP, exportacao de resultados e validacao.
|
candidatas, montagem/solução do MILP, exportação de resultados e validação.
|
||||||
|
|
||||||
Exemplos:
|
Exemplos:
|
||||||
python scripts/00_main.py --data 2026-01-02 --modo diario
|
python scripts/00_main.py --data 2026-01-02 --modo diario
|
||||||
@@ -43,7 +43,7 @@ def atualizar_data_planejamento(data: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def executar_diario(args: argparse.Namespace, logger) -> dict:
|
def executar_diario(args: argparse.Namespace, logger) -> dict:
|
||||||
"""Executa o planejamento diario oficial em Python."""
|
"""Executa o planejamento diário oficial em Python."""
|
||||||
io_utils.configurar_ambiente()
|
io_utils.configurar_ambiente()
|
||||||
if args.data:
|
if args.data:
|
||||||
atualizar_data_planejamento(args.data)
|
atualizar_data_planejamento(args.data)
|
||||||
@@ -60,24 +60,24 @@ def executar_diario(args: argparse.Namespace, logger) -> dict:
|
|||||||
logger.info("Colunas candidatas: %s", resultado["total_candidatas"])
|
logger.info("Colunas candidatas: %s", resultado["total_candidatas"])
|
||||||
logger.info("Escalas selecionadas: %s", resultado["total_selecionadas"])
|
logger.info("Escalas selecionadas: %s", resultado["total_selecionadas"])
|
||||||
logger.info("Arquivo exportado: %s", resultado["arquivo_saida"])
|
logger.info("Arquivo exportado: %s", resultado["arquivo_saida"])
|
||||||
logger.info("Tempo de execucao: %.1f s", duracao)
|
logger.info("Tempo de execução: %.1f s", duracao)
|
||||||
return resultado
|
return resultado
|
||||||
|
|
||||||
|
|
||||||
def executar_validacao(ano: str, logger) -> dict:
|
def executar_validacao(ano: str, logger) -> dict:
|
||||||
"""Executa validacao retrospectiva suportada pelo projeto."""
|
"""Executa validação retrospectiva suportada pelo projeto."""
|
||||||
if str(ano) != "2025":
|
if str(ano) != "2025":
|
||||||
raise ValueError("Erro: no momento a validacao disponivel e --validacao 2025.")
|
raise ValueError("Erro: no momento a validação disponível é --validacao 2025.")
|
||||||
validacao = importlib.import_module("07_validacao_2025")
|
validacao = importlib.import_module("07_validacao_2025")
|
||||||
return validacao.executar_validacao_2025(importar=True, logger=logger)
|
return validacao.executar_validacao_2025(importar=True, logger=logger)
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(description="Executa o Planejador Missao em Python.")
|
parser = argparse.ArgumentParser(description="Executa o Planejador Missão em Python.")
|
||||||
parser.add_argument("--data", help="Data do planejamento diario no formato AAAA-MM-DD.")
|
parser.add_argument("--data", help="Data do planejamento diário no formato AAAA-MM-DD.")
|
||||||
parser.add_argument("--aeronave", choices=config.AERONAVES, help="Aeronave de referencia para diagnostico.")
|
parser.add_argument("--aeronave", choices=config.AERONAVES, help="Aeronave de referência para diagnóstico.")
|
||||||
parser.add_argument("--modo", default="diario", choices=["diario"], help="Modo de execucao do planejamento.")
|
parser.add_argument("--modo", default="diario", choices=["diario"], help="Modo de execução do planejamento.")
|
||||||
parser.add_argument("--validacao", help="Ano da validacao retrospectiva. Exemplo: --validacao 2025.")
|
parser.add_argument("--validacao", help="Ano da validação retrospectiva. Exemplo: --validacao 2025.")
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -86,13 +86,13 @@ def main() -> None:
|
|||||||
logger = io_utils.configurar_logger("main")
|
logger = io_utils.configurar_logger("main")
|
||||||
if args.validacao:
|
if args.validacao:
|
||||||
saida = executar_validacao(args.validacao, logger)
|
saida = executar_validacao(args.validacao, logger)
|
||||||
print("\n=== Validacao concluida ===")
|
print("\n=== Validação concluída ===")
|
||||||
for path in saida["arquivos"]:
|
for path in saida["arquivos"]:
|
||||||
print(f"- {path}")
|
print(f"- {path}")
|
||||||
return
|
return
|
||||||
|
|
||||||
resultado = executar_diario(args, logger)
|
resultado = executar_diario(args, logger)
|
||||||
print("\n=== Planejamento diario concluido ===")
|
print("\n=== Planejamento diário concluído ===")
|
||||||
print(f"Data: {resultado['data_planejamento']}")
|
print(f"Data: {resultado['data_planejamento']}")
|
||||||
print(f"Arquivo: {resultado['arquivo_saida']}")
|
print(f"Arquivo: {resultado['arquivo_saida']}")
|
||||||
print(f"Colunas candidatas: {resultado['total_candidatas']}")
|
print(f"Colunas candidatas: {resultado['total_candidatas']}")
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"""Configuracao central dos scripts do Planejador Missao.
|
"""Configuração central dos scripts do Planejador Missão.
|
||||||
|
|
||||||
Entradas:
|
Entradas:
|
||||||
Estrutura padrao do projeto, com pastas dados/, src/ e resultados/.
|
Estrutura padrão do projeto, com pastas dados/, src/ e resultados/.
|
||||||
|
|
||||||
Saidas:
|
Saídas:
|
||||||
Constantes de caminho, parametros operacionais, nomes de arquivos e pesos
|
Constantes de caminho, parâmetros operacionais, nomes de arquivos e pesos
|
||||||
usados pelos scripts de execucao, validacao e teste.
|
usados pelos scripts de execução, validação e teste.
|
||||||
|
|
||||||
Papel no pipeline:
|
Papel no pipeline:
|
||||||
Evita caminhos absolutos e concentra convencoes para que o projeto rode em
|
Evita caminhos absolutos e concentra convencoes para que o projeto rode em
|
||||||
@@ -67,6 +67,6 @@ ARQUIVOS_ENTRADA_OBRIGATORIOS = [
|
|||||||
|
|
||||||
|
|
||||||
def garantir_diretorios() -> None:
|
def garantir_diretorios() -> None:
|
||||||
"""Cria diretorios de saida e apoio usados pelos scripts."""
|
"""Cria diretórios de saída e apoio usados pelos scripts."""
|
||||||
for path in [RESULTADOS_DIR, VALIDACAO_DIR, LOGS_DIR, TEMP_DIR, ARQUIVO_VOOS_2025.parent]:
|
for path in [RESULTADOS_DIR, VALIDACAO_DIR, LOGS_DIR, TEMP_DIR, ARQUIVO_VOOS_2025.parent]:
|
||||||
path.mkdir(parents=True, exist_ok=True)
|
path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
"""Utilitarios de entrada, saida e log dos scripts.
|
"""Utilitários de entrada, saída e log dos scripts.
|
||||||
|
|
||||||
Entradas:
|
Entradas:
|
||||||
Caminhos definidos em 01_config.py.
|
Caminhos definidos em 01_config.py.
|
||||||
|
|
||||||
Saidas:
|
Saídas:
|
||||||
Validacoes de arquivos, diretorios criados, logs em logs/execucao.log e
|
Validações de arquivos, diretórios criados, logs em logs/execucao.log e
|
||||||
funcoes pequenas para exportacao.
|
funcoes pequenas para exportacao.
|
||||||
|
|
||||||
Papel no pipeline:
|
Papel no pipeline:
|
||||||
Padroniza mensagens de erro e evita repeticao de codigo de filesystem.
|
Padroniza mensagens de erro e evita repetição de código de filesystem.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -52,7 +52,7 @@ def configurar_logger(nome: str = "planejador_missao") -> logging.Logger:
|
|||||||
|
|
||||||
|
|
||||||
def exigir_arquivos(paths: Iterable[Path]) -> None:
|
def exigir_arquivos(paths: Iterable[Path]) -> None:
|
||||||
"""Interrompe a execucao com mensagem clara se algum arquivo faltar."""
|
"""Interrompe a execução com mensagem clara se algum arquivo faltar."""
|
||||||
ausentes = [path for path in paths if not path.exists()]
|
ausentes = [path for path in paths if not path.exists()]
|
||||||
if ausentes:
|
if ausentes:
|
||||||
lista = "\n".join(f"- {path}" for path in ausentes)
|
lista = "\n".join(f"- {path}" for path in ausentes)
|
||||||
@@ -64,7 +64,7 @@ def exigir_colunas(df: pd.DataFrame, colunas: Iterable[str], origem: str) -> Non
|
|||||||
faltantes = [col for col in colunas if col not in df.columns]
|
faltantes = [col for col in colunas if col not in df.columns]
|
||||||
if faltantes:
|
if faltantes:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Erro: coluna obrigatoria '{faltantes[0]}' nao encontrada em {origem}. "
|
f"Erro: coluna obrigatória '{faltantes[0]}' não encontrada em {origem}. "
|
||||||
"Verifique o arquivo de entrada."
|
"Verifique o arquivo de entrada."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
"""Preparacao das bases operacionais do Planejador Missao.
|
"""Preparação das bases operacionais do Planejador Missão.
|
||||||
|
|
||||||
Entradas:
|
Entradas:
|
||||||
Planilhas e CSVs em dados/: cadastro, catalogo de OIs, indisponibilidades,
|
Planilhas e CSVs em dados/: cadastro, catalogo de OIs, indisponibilidades,
|
||||||
progresso, historico, aeronaves, tripulantes disponiveis e rotas acionadas.
|
progresso, histórico, aeronaves, tripulantes disponíveis e rotas acionadas.
|
||||||
|
|
||||||
Saidas:
|
Saidas:
|
||||||
Dicionario com DataFrames normalizados para geracao de colunas candidatas.
|
Dicionario com DataFrames normalizados para geracao de colunas candidatas.
|
||||||
|
|
||||||
Papel no pipeline:
|
Papel no pipeline:
|
||||||
Concentra a leitura e a preparacao antes do MILP, mantendo rastreavel quais
|
Concentra a leitura e a preparacao antes do MILP, mantendo rastreavel quais
|
||||||
arquivos alimentam cada execucao.
|
arquivos alimentam cada execução.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -64,7 +64,7 @@ def main() -> None:
|
|||||||
logger = io_utils.configurar_logger("preparar_dados")
|
logger = io_utils.configurar_logger("preparar_dados")
|
||||||
bases = carregar_bases()
|
bases = carregar_bases()
|
||||||
logger.info("Tripulantes no cadastro: %s", len(bases["cadastro"]))
|
logger.info("Tripulantes no cadastro: %s", len(bases["cadastro"]))
|
||||||
logger.info("Tripulantes disponiveis: %s", len(bases["disponiveis"]))
|
logger.info("Tripulantes disponíveis: %s", len(bases["disponiveis"]))
|
||||||
logger.info("Aeronaves informadas: %s", len(bases["aeronaves"]))
|
logger.info("Aeronaves informadas: %s", len(bases["aeronaves"]))
|
||||||
logger.info("Rotas acionadas: %s", len(bases["rotas"]))
|
logger.info("Rotas acionadas: %s", len(bases["rotas"]))
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"""Geracao das colunas candidatas viaveis.
|
"""Geração das colunas candidatas viáveis.
|
||||||
|
|
||||||
Entradas:
|
Entradas:
|
||||||
Bases preparadas por 03_preparar_dados.py.
|
Bases preparadas por 03_preparar_dados.py.
|
||||||
|
|
||||||
Saidas:
|
Saidas:
|
||||||
DataFrame em que cada linha representa uma escala possivel: rota acionada,
|
DataFrame em que cada linha representa uma escala possível: rota acionada,
|
||||||
missao local ou sobreaviso, com dupla, aeronave, score, custo e metadados.
|
missão local ou sobreaviso, com dupla, aeronave, score, custo e metadados.
|
||||||
|
|
||||||
Papel no pipeline:
|
Papel no pipeline:
|
||||||
Materializa o espaco de decisoes do MILP. A compatibilidade de aeronave,
|
Materializa o espaco de decisoes do MILP. A compatibilidade de aeronave,
|
||||||
@@ -21,13 +21,13 @@ io_utils = importlib.import_module("02_io_utils")
|
|||||||
|
|
||||||
|
|
||||||
def gerar_colunas_do_dia(bases: dict):
|
def gerar_colunas_do_dia(bases: dict):
|
||||||
"""Gera colunas candidatas para a data e parametros informados nas bases."""
|
"""Gera colunas candidatas para a data e parâmetros informados nas bases."""
|
||||||
from src.planejador_missao.candidates import gerar_colunas
|
from src.planejador_missao.candidates import gerar_colunas
|
||||||
|
|
||||||
parametros = bases["parametros"]
|
parametros = bases["parametros"]
|
||||||
# Cada coluna candidata equivale a uma variavel x_j do MILP.
|
# Cada coluna candidata equivale a uma variável x_j do MILP.
|
||||||
# As regras de dupla, qualificacao, instrucao e indisponibilidade sao
|
# As regras de dupla, qualificacao, instrucao e indisponibilidade sao
|
||||||
# aplicadas antes do solver para reduzir o modelo a alternativas viaveis.
|
# aplicadas antes do solver para reduzir o modelo a alternativas viáveis.
|
||||||
return gerar_colunas(
|
return gerar_colunas(
|
||||||
parametros["data_planejamento"],
|
parametros["data_planejamento"],
|
||||||
bases["rotas"],
|
bases["rotas"],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Modelo MILP do Planejador Missao usando scipy.optimize.milp/HiGHS.
|
"""Modelo MILP do Planejador Missão usando scipy.optimize.milp/HiGHS.
|
||||||
|
|
||||||
Entradas:
|
Entradas:
|
||||||
Colunas candidatas viaveis geradas por 04_gerar_colunas_candidatas.py.
|
Colunas candidatas viáveis geradas por 04_gerar_colunas_candidatas.py.
|
||||||
|
|
||||||
Saidas:
|
Saidas:
|
||||||
Subconjunto de colunas selecionadas pelo solver, representando a escala
|
Subconjunto de colunas selecionadas pelo solver, representando a escala
|
||||||
@@ -14,12 +14,12 @@ Metodologia:
|
|||||||
|
|
||||||
Restricoes implementadas no motor src.planejador_missao.optimizer:
|
Restricoes implementadas no motor src.planejador_missao.optimizer:
|
||||||
a) rota acionada obrigatoria coberta exatamente uma vez;
|
a) rota acionada obrigatoria coberta exatamente uma vez;
|
||||||
b) cada tripulante aparece no maximo uma vez no dia;
|
b) cada tripulante aparece no máximo uma vez no dia;
|
||||||
c) cada aeronave recebe cobertura principal por rota acionada ou SBV;
|
c) cada aeronave recebe cobertura principal por rota acionada ou SBV;
|
||||||
d) aeronave nao executa escalas conflitantes de rota/local no mesmo dia;
|
d) aeronave não executa escalas conflitantes de rota/local no mesmo dia;
|
||||||
e) qualificacao, indisponibilidade e regras de instrucao entram no filtro de
|
e) qualificacao, indisponibilidade e regras de instrucao entram no filtro de
|
||||||
colunas candidatas, logo colunas inviaveis nao chegam ao MILP;
|
colunas candidatas, logo colunas inviáveis não chegam ao MILP;
|
||||||
f) regras especificas de rota, missao local e SBV sao preservadas por tipo.
|
f) regras específicas de rota, missão local e SBV são preservadas por tipo.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -36,10 +36,10 @@ def resolver_escala(colunas, rotas, aeronaves):
|
|||||||
from src.planejador_missao.optimizer import resolver_milp
|
from src.planejador_missao.optimizer import resolver_milp
|
||||||
|
|
||||||
if colunas.empty:
|
if colunas.empty:
|
||||||
raise RuntimeError("Erro: nao ha colunas candidatas viaveis para montar o MILP.")
|
raise RuntimeError("Erro: não há colunas candidatas viáveis para montar o MILP.")
|
||||||
|
|
||||||
# O resolvedor cria x_j binario para cada linha de colunas.
|
# O resolvedor cria x_j binario para cada linha de colunas.
|
||||||
# A funcao objetivo usa -score_milp porque o HiGHS via SciPy resolve
|
# A função objetivo usa -score_milp porque o HiGHS via SciPy resolve
|
||||||
# minimizacao; isso preserva a interpretacao de maximizar score.
|
# minimizacao; isso preserva a interpretacao de maximizar score.
|
||||||
return resolver_milp(colunas, rotas, aeronaves)
|
return resolver_milp(colunas, rotas, aeronaves)
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ Entradas:
|
|||||||
|
|
||||||
Saidas:
|
Saidas:
|
||||||
Planilha em resultados/planejamento_diario_YYYY-MM-DD.xlsx e registros de
|
Planilha em resultados/planejamento_diario_YYYY-MM-DD.xlsx e registros de
|
||||||
historico operacional para atualizacao do acumulado.
|
histórico operacional para atualização do acumulado.
|
||||||
|
|
||||||
Papel no pipeline:
|
Papel no pipeline:
|
||||||
Converte a solucao matematica em artefatos utilizaveis pelo esquadrao.
|
Converte a solução matemática em artefatos utilizáveis pelo esquadrão.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ Saidas:
|
|||||||
resultados/validacao/validacao_2025_barras.png
|
resultados/validacao/validacao_2025_barras.png
|
||||||
|
|
||||||
Papel no pipeline:
|
Papel no pipeline:
|
||||||
Mantem a demanda historica de 2025, gera candidatos compativeis para cada
|
Mantém a demanda histórica de 2025, gera candidatos compatíveis para cada
|
||||||
slot historico e usa MILP para redistribuir tripulantes preservando horas.
|
slot histórico e usa MILP para redistribuir tripulantes preservando horas.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -89,7 +89,7 @@ def _tripulantes(valor: object) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def importar_voos_2025(origem: Path = config.ARQUIVO_QUADRO_VOO_2025, destino: Path = config.ARQUIVO_VOOS_2025) -> pd.DataFrame:
|
def importar_voos_2025(origem: Path = config.ARQUIVO_QUADRO_VOO_2025, destino: Path = config.ARQUIVO_VOOS_2025) -> pd.DataFrame:
|
||||||
"""Importa voos realizados de 2025 da aba VOOS para o CSV de validacao."""
|
"""Importa voos realizados de 2025 da aba VOOS para o CSV de validação."""
|
||||||
io_utils.exigir_arquivos([origem])
|
io_utils.exigir_arquivos([origem])
|
||||||
raw = pd.read_excel(origem, sheet_name=config.ABAS["voos_2025"], header=None)
|
raw = pd.read_excel(origem, sheet_name=config.ABAS["voos_2025"], header=None)
|
||||||
linhas = []
|
linhas = []
|
||||||
@@ -176,7 +176,7 @@ def _png_barras(path: Path, metricas: pd.DataFrame) -> Path:
|
|||||||
|
|
||||||
|
|
||||||
def executar_validacao_2025(importar: bool = True, logger=None) -> dict:
|
def executar_validacao_2025(importar: bool = True, logger=None) -> dict:
|
||||||
"""Executa a validacao completa e exporta os artefatos finais."""
|
"""Executa a validação completa e exporta os artefatos finais."""
|
||||||
io_utils.configurar_ambiente()
|
io_utils.configurar_ambiente()
|
||||||
logger = logger or io_utils.configurar_logger("validacao_2025")
|
logger = logger or io_utils.configurar_logger("validacao_2025")
|
||||||
inicio = pd.Timestamp.now()
|
inicio = pd.Timestamp.now()
|
||||||
@@ -189,9 +189,9 @@ def executar_validacao_2025(importar: bool = True, logger=None) -> dict:
|
|||||||
|
|
||||||
voos = carregar_voos_2025(config.ARQUIVO_VOOS_2025)
|
voos = carregar_voos_2025(config.ARQUIVO_VOOS_2025)
|
||||||
if voos.empty:
|
if voos.empty:
|
||||||
raise RuntimeError("Erro: base de validacao 2025 vazia. Verifique dados/validacao/voos_2025.csv.")
|
raise RuntimeError("Erro: base de validação 2025 vazia. Verifique dados/validacao/voos_2025.csv.")
|
||||||
if (voos["horas_voadas"] < 0).any():
|
if (voos["horas_voadas"] < 0).any():
|
||||||
raise RuntimeError("Erro: foram encontradas horas voadas negativas na validacao 2025.")
|
raise RuntimeError("Erro: foram encontradas horas voadas negativas na validação 2025.")
|
||||||
|
|
||||||
resultado = otimizar_equalizacao_50(
|
resultado = otimizar_equalizacao_50(
|
||||||
config.PROJECT_ROOT,
|
config.PROJECT_ROOT,
|
||||||
@@ -204,7 +204,7 @@ def executar_validacao_2025(importar: bool = True, logger=None) -> dict:
|
|||||||
total_real = float(metricas.loc[metricas["cenario"] == "real_2025", "horas_total"].iloc[0])
|
total_real = float(metricas.loc[metricas["cenario"] == "real_2025", "horas_total"].iloc[0])
|
||||||
total_otim = float(metricas.loc[metricas["cenario"] == "otimizado_meta_50", "horas_total"].iloc[0])
|
total_otim = float(metricas.loc[metricas["cenario"] == "otimizado_meta_50", "horas_total"].iloc[0])
|
||||||
if abs(total_real - total_otim) > 0.01:
|
if abs(total_real - total_otim) > 0.01:
|
||||||
logger.warning("Horas nao preservadas: real=%s otimizado=%s", total_real, total_otim)
|
logger.warning("Horas não preservadas: real=%s otimizado=%s", total_real, total_otim)
|
||||||
|
|
||||||
resumo_path = config.VALIDACAO_DIR / "validacao_2025_resumo.xlsx"
|
resumo_path = config.VALIDACAO_DIR / "validacao_2025_resumo.xlsx"
|
||||||
detalhada_path = config.VALIDACAO_DIR / "validacao_2025_detalhada.xlsx"
|
detalhada_path = config.VALIDACAO_DIR / "validacao_2025_detalhada.xlsx"
|
||||||
@@ -231,7 +231,7 @@ def executar_validacao_2025(importar: bool = True, logger=None) -> dict:
|
|||||||
logger.info("Alocacoes otimizadas: %s", len(resultado["otimizados"]))
|
logger.info("Alocacoes otimizadas: %s", len(resultado["otimizados"]))
|
||||||
logger.info("Tripulantes comparados: %s", len(resultado["comparativo"]))
|
logger.info("Tripulantes comparados: %s", len(resultado["comparativo"]))
|
||||||
logger.info("Status solver: %s", resultado["status_solver"]["mensagem"])
|
logger.info("Status solver: %s", resultado["status_solver"]["mensagem"])
|
||||||
logger.info("Tempo de validacao: %.1f s", duracao)
|
logger.info("Tempo de validação: %.1f s", duracao)
|
||||||
logger.info("Arquivos exportados: %s | %s | %s | %s", resumo_path, detalhada_path, metricas_path, grafico_path)
|
logger.info("Arquivos exportados: %s | %s | %s | %s", resumo_path, detalhada_path, metricas_path, grafico_path)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -242,11 +242,11 @@ def executar_validacao_2025(importar: bool = True, logger=None) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Ponto de entrada da validacao 2025."""
|
"""Ponto de entrada da validação 2025."""
|
||||||
logger = io_utils.configurar_logger("validacao_2025")
|
logger = io_utils.configurar_logger("validacao_2025")
|
||||||
saida = executar_validacao_2025(importar=True, logger=logger)
|
saida = executar_validacao_2025(importar=True, logger=logger)
|
||||||
metricas = saida["resultado"]["metricas"]
|
metricas = saida["resultado"]["metricas"]
|
||||||
print("\n=== Validacao 2025 concluida ===")
|
print("\n=== Validação 2025 concluída ===")
|
||||||
print(metricas.to_string(index=False))
|
print(metricas.to_string(index=False))
|
||||||
print("\nArquivos gerados:")
|
print("\nArquivos gerados:")
|
||||||
for path in saida["arquivos"]:
|
for path in saida["arquivos"]:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def comando_python_app() -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def abrir_app() -> None:
|
def abrir_app() -> None:
|
||||||
"""Inicia o app em segundo plano sem janela de console quando possivel."""
|
"""Inicia o app em segundo plano sem janela de console quando possível."""
|
||||||
io_utils.configurar_ambiente()
|
io_utils.configurar_ambiente()
|
||||||
subprocess.Popen(comando_python_app(), cwd=config.PROJECT_ROOT)
|
subprocess.Popen(comando_python_app(), cwd=config.PROJECT_ROOT)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
"""Teste rapido de instalacao do Planejador Missao.
|
"""Teste rápido de instalação do Planejador Missão.
|
||||||
|
|
||||||
Entradas:
|
Entradas:
|
||||||
Ambiente Python atual, dependencias instaladas e arquivos em dados/.
|
Ambiente Python atual, dependencias instaladas e arquivos em dados/.
|
||||||
|
|
||||||
Saidas:
|
Saídas:
|
||||||
Diagnostico amigavel no terminal e log em logs/execucao.log.
|
Diagnóstico amigável no terminal e log em logs/execucao.log.
|
||||||
|
|
||||||
Papel no pipeline:
|
Papel no pipeline:
|
||||||
Permite verificar rapidamente se o computador esta pronto para executar o
|
Permite verificar rapidamente se o computador está pronto para executar o
|
||||||
planejador e o solver HiGHS/scipy.optimize.milp.
|
planejador e o solver HiGHS/scipy.optimize.milp.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ def main() -> None:
|
|||||||
testar_solver_minimo()
|
testar_solver_minimo()
|
||||||
print("Solver scipy.optimize.milp/HiGHS OK")
|
print("Solver scipy.optimize.milp/HiGHS OK")
|
||||||
logger.info("Teste de instalacao concluido com sucesso.")
|
logger.info("Teste de instalacao concluido com sucesso.")
|
||||||
print("Ambiente OK para executar o Planejador Missao.")
|
print("Ambiente OK para executar o Planejador Missão.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Importa a aba VOOS do Quadro de Voo 2025 para a base de validacao."""
|
"""Importa a aba VOOS do Quadro de Voo 2025 para a base de validação."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ def importar(origem: Path, destino: Path) -> pd.DataFrame:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser(description="Importa voos realizados de 2025 para a validacao.")
|
parser = argparse.ArgumentParser(description="Importa voos realizados de 2025 para a validação.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--origem",
|
"--origem",
|
||||||
type=Path,
|
type=Path,
|
||||||
@@ -121,7 +121,7 @@ def main() -> None:
|
|||||||
"--destino",
|
"--destino",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=BASE_DIR / "dados" / "validacao" / "voos_2025.csv",
|
default=BASE_DIR / "dados" / "validacao" / "voos_2025.csv",
|
||||||
help="CSV de validacao gerado.",
|
help="CSV de validação gerado.",
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Executa a validacao retrospectiva com voos de 2025."""
|
"""Executa a validação retrospectiva com voos de 2025."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ def main() -> None:
|
|||||||
|
|
||||||
voos = carregar_voos_2025(entrada)
|
voos = carregar_voos_2025(entrada)
|
||||||
if voos.empty:
|
if voos.empty:
|
||||||
print("Base de validacao ainda vazia.")
|
print("Base de validação ainda vazia.")
|
||||||
print(f"Preencha o arquivo: {entrada}")
|
print(f"Preencha o arquivo: {entrada}")
|
||||||
print("Colunas esperadas: data,aeronave,tipo_escala,tripulante,funcao,oi,horas_voadas,sbv")
|
print("Colunas esperadas: data,aeronave,tipo_escala,tripulante,funcao,oi,horas_voadas,sbv")
|
||||||
return
|
return
|
||||||
@@ -43,13 +43,13 @@ def main() -> None:
|
|||||||
)
|
)
|
||||||
arquivo = salvar_relatorio_validacao(BASE_DIR, resultado, meta_horas=args.meta)
|
arquivo = salvar_relatorio_validacao(BASE_DIR, resultado, meta_horas=args.meta)
|
||||||
|
|
||||||
print("\n=== Validacao 2025 concluida ===")
|
print("\n=== Validação 2025 concluída ===")
|
||||||
print(f"Meta de equalizacao: {args.meta:g} horas")
|
print(f"Meta de equalizacao: {args.meta:g} horas")
|
||||||
print(f"Voos/slots avaliados: {len(resultado['slots'])}")
|
print(f"Voos/slots avaliados: {len(resultado['slots'])}")
|
||||||
print(f"Tripulantes comparados: {len(resultado['comparativo'])}")
|
print(f"Tripulantes comparados: {len(resultado['comparativo'])}")
|
||||||
print(f"Relatorio: {arquivo}")
|
print(f"Relatório: {arquivo}")
|
||||||
print(f"Solver: {resultado['status_solver']['mensagem']}")
|
print(f"Solver: {resultado['status_solver']['mensagem']}")
|
||||||
print("\nMetricas:")
|
print("\nMétricas:")
|
||||||
print(resultado["metricas"].to_string(index=False))
|
print(resultado["metricas"].to_string(index=False))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
"""Planejador de missao e sobreaviso por MILP."""
|
"""Planejador de missão e sobreaviso por MILP."""
|
||||||
|
|
||||||
|
|||||||
@@ -172,13 +172,13 @@ def gerar_colunas(data_planejamento, rotas, aeronaves, cadastro, indisponibilida
|
|||||||
candidatos = filtrar_por_horas_aeronave(candidatos, horas_disp)
|
candidatos = filtrar_por_horas_aeronave(candidatos, horas_disp)
|
||||||
if candidatos.empty:
|
if candidatos.empty:
|
||||||
continue
|
continue
|
||||||
colunas.append(gerar_duplas(aplicar_prioridades(candidatos), criterio_missao, "ROTA_ACIONADA", av, data_planejamento, cadastro, indisponibilidades, progresso, catalogo, disponiveis, rota_id=rota_id + 1, rota=rota, observacao="Missao de rota acionada"))
|
colunas.append(gerar_duplas(aplicar_prioridades(candidatos), criterio_missao, "ROTA_ACIONADA", av, data_planejamento, cadastro, indisponibilidades, progresso, catalogo, disponiveis, rota_id=rota_id + 1, rota=rota, observacao="Missão de rota acionada"))
|
||||||
for av in livres:
|
for av in livres:
|
||||||
horas_disp = horas_disponiveis(aeronaves, av)
|
horas_disp = horas_disponiveis(aeronaves, av)
|
||||||
candidatos_local = candidatos_missao(data_planejamento, av, "LOCAL", cadastro, indisponibilidades, progresso, catalogo, disponiveis)
|
candidatos_local = candidatos_missao(data_planejamento, av, "LOCAL", cadastro, indisponibilidades, progresso, catalogo, disponiveis)
|
||||||
candidatos_local = filtrar_por_horas_aeronave(candidatos_local, horas_disp)
|
candidatos_local = filtrar_por_horas_aeronave(candidatos_local, horas_disp)
|
||||||
if not candidatos_local.empty:
|
if not candidatos_local.empty:
|
||||||
colunas.append(gerar_duplas(aplicar_prioridades(candidatos_local), criterio_missao, "MISSAO_LOCAL", av, data_planejamento, cadastro, indisponibilidades, progresso, catalogo, disponiveis, observacao="Missao local planejada para progressao operacional"))
|
colunas.append(gerar_duplas(aplicar_prioridades(candidatos_local), criterio_missao, "MISSAO_LOCAL", av, data_planejamento, cadastro, indisponibilidades, progresso, catalogo, disponiveis, observacao="Missão local planejada para progressão operacional"))
|
||||||
candidatos_sbv = []
|
candidatos_sbv = []
|
||||||
for _, row in cadastro[cadastro[f"qt_{av.lower()}"]].iterrows():
|
for _, row in cadastro[cadastro[f"qt_{av.lower()}"]].iterrows():
|
||||||
if row["tripulante"] in set(disponiveis["tripulante"]) and piloto_disponivel(row["tripulante"], data_planejamento, indisponibilidades):
|
if row["tripulante"] in set(disponiveis["tripulante"]) and piloto_disponivel(row["tripulante"], data_planejamento, indisponibilidades):
|
||||||
@@ -191,7 +191,7 @@ def gerar_colunas(data_planejamento, rotas, aeronaves, cadastro, indisponibilida
|
|||||||
r["bloqueado"] = False
|
r["bloqueado"] = False
|
||||||
candidatos_sbv.append(r)
|
candidatos_sbv.append(r)
|
||||||
if len(candidatos_sbv) >= 2:
|
if len(candidatos_sbv) >= 2:
|
||||||
colunas.append(gerar_duplas(pd.DataFrame(candidatos_sbv), criterio_sbv, "SBV", av, data_planejamento, cadastro, indisponibilidades, progresso, catalogo, disponiveis, observacao="Sobreaviso para missao de rota"))
|
colunas.append(gerar_duplas(pd.DataFrame(candidatos_sbv), criterio_sbv, "SBV", av, data_planejamento, cadastro, indisponibilidades, progresso, catalogo, disponiveis, observacao="Sobreaviso para missão de rota"))
|
||||||
if not colunas:
|
if not colunas:
|
||||||
return pd.DataFrame()
|
return pd.DataFrame()
|
||||||
df = pd.concat(colunas, ignore_index=True)
|
df = pd.concat(colunas, ignore_index=True)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Leitura das bases de entrada e escrita do historico operacional."""
|
"""Leitura das bases de entrada e escrita do histórico operacional."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ def executar_planejamento(base_dir: Path) -> dict:
|
|||||||
# Bloco 03 - Modelo MILP e selecao final.
|
# Bloco 03 - Modelo MILP e selecao final.
|
||||||
solucao = resolver_milp(colunas, rotas, aeronaves)
|
solucao = resolver_milp(colunas, rotas, aeronaves)
|
||||||
|
|
||||||
# Bloco 04 - Persistencia operacional e relatorio.
|
# Bloco 04 - Persistência operacional e relatório.
|
||||||
registros = gerar_registros(solucao, catalogo)
|
registros = gerar_registros(solucao, catalogo)
|
||||||
salvar_historico(base_dir, registros)
|
salvar_historico(base_dir, registros)
|
||||||
arquivo_saida = gerar_excel(base_dir, parametros["data_planejamento"], solucao)
|
arquivo_saida = gerar_excel(base_dir, parametros["data_planejamento"], solucao)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def resolver_milp(colunas: pd.DataFrame, rotas: pd.DataFrame, aeronaves: pd.Data
|
|||||||
bounds = Bounds(np.zeros(n), np.ones(n))
|
bounds = Bounds(np.zeros(n), np.ones(n))
|
||||||
restricoes = []
|
restricoes = []
|
||||||
|
|
||||||
# R1 - Cada tripulante aparece no maximo uma vez.
|
# R1 - Cada tripulante aparece no máximo uma vez.
|
||||||
for trip in sorted(set(colunas["trip1"]).union(set(colunas["trip2"]))):
|
for trip in sorted(set(colunas["trip1"]).union(set(colunas["trip2"]))):
|
||||||
linha = np.zeros(n)
|
linha = np.zeros(n)
|
||||||
linha[(colunas["trip1"] == trip) | (colunas["trip2"] == trip)] = 1
|
linha[(colunas["trip1"] == trip) | (colunas["trip2"] == trip)] = 1
|
||||||
@@ -28,7 +28,7 @@ def resolver_milp(colunas: pd.DataFrame, rotas: pd.DataFrame, aeronaves: pd.Data
|
|||||||
idx_local = (colunas["aeronave"] == av) & (colunas["tipo_escala"] == "MISSAO_LOCAL")
|
idx_local = (colunas["aeronave"] == av) & (colunas["tipo_escala"] == "MISSAO_LOCAL")
|
||||||
idx_sbv = (colunas["aeronave"] == av) & (colunas["tipo_escala"] == "SBV")
|
idx_sbv = (colunas["aeronave"] == av) & (colunas["tipo_escala"] == "SBV")
|
||||||
if not (idx_rota | idx_sbv).any():
|
if not (idx_rota | idx_sbv).any():
|
||||||
raise RuntimeError(f"Modelo inviavel: nao ha rota nem SBV candidato para a aeronave {av}.")
|
raise RuntimeError(f"Modelo inviável: não há rota nem SBV candidato para a aeronave {av}.")
|
||||||
linha = np.zeros(n)
|
linha = np.zeros(n)
|
||||||
linha[idx_rota | idx_sbv] = 1
|
linha[idx_rota | idx_sbv] = 1
|
||||||
restricoes.append(LinearConstraint(linha, 1, 1))
|
restricoes.append(LinearConstraint(linha, 1, 1))
|
||||||
@@ -48,13 +48,13 @@ def resolver_milp(colunas: pd.DataFrame, rotas: pd.DataFrame, aeronaves: pd.Data
|
|||||||
for rota_id in range(1, len(rotas) + 1):
|
for rota_id in range(1, len(rotas) + 1):
|
||||||
idx = (colunas["tipo_escala"] == "ROTA_ACIONADA") & (colunas["rota_id"] == rota_id)
|
idx = (colunas["tipo_escala"] == "ROTA_ACIONADA") & (colunas["rota_id"] == rota_id)
|
||||||
if not idx.any():
|
if not idx.any():
|
||||||
raise RuntimeError(f"Modelo inviavel: nao ha coluna candidata para a rota acionada {rota_id}.")
|
raise RuntimeError(f"Modelo inviável: não há coluna candidata para a rota acionada {rota_id}.")
|
||||||
linha = np.zeros(n)
|
linha = np.zeros(n)
|
||||||
linha[idx] = 1
|
linha[idx] = 1
|
||||||
restricoes.append(LinearConstraint(linha, 1, 1))
|
restricoes.append(LinearConstraint(linha, 1, 1))
|
||||||
|
|
||||||
resultado = milp(c=-colunas["score_milp"].to_numpy(dtype=float), integrality=integrality, bounds=bounds, constraints=restricoes)
|
resultado = milp(c=-colunas["score_milp"].to_numpy(dtype=float), integrality=integrality, bounds=bounds, constraints=restricoes)
|
||||||
if not resultado.success:
|
if not resultado.success:
|
||||||
raise RuntimeError(f"MILP nao encontrou solucao viavel: {resultado.message}")
|
raise RuntimeError(f"MILP não encontrou solução viável: {resultado.message}")
|
||||||
selecionadas = resultado.x > 0.5
|
selecionadas = resultado.x > 0.5
|
||||||
return colunas.loc[selecionadas].reset_index(drop=True)
|
return colunas.loc[selecionadas].reset_index(drop=True)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Registro de horas e geracao do relatorio Excel final."""
|
"""Registro de horas e geração do relatório Excel final."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ def gerar_excel(base_dir: Path, data_planejamento, solucao: pd.DataFrame) -> Pat
|
|||||||
ws["A2"].font = Font(color="1F4E78", bold=True, size=18)
|
ws["A2"].font = Font(color="1F4E78", bold=True, size=18)
|
||||||
ws["A2"].alignment = Alignment(horizontal="center")
|
ws["A2"].alignment = Alignment(horizontal="center")
|
||||||
|
|
||||||
escrever_tabela(ws, "MISSAO ACIONADA", bloco(solucao, "ROTA_ACIONADA"), 5, 1, "C00000")
|
escrever_tabela(ws, "MISSÃO ACIONADA", bloco(solucao, "ROTA_ACIONADA"), 5, 1, "C00000")
|
||||||
escrever_tabela(ws, "VOOS LOCAIS", bloco(solucao, "MISSAO_LOCAL"), 5, 7, "548235")
|
escrever_tabela(ws, "VOOS LOCAIS", bloco(solucao, "MISSAO_LOCAL"), 5, 7, "548235")
|
||||||
escrever_tabela(ws, "SOBREAVISO", bloco(solucao, "SBV"), 5, 13, "8064A2")
|
escrever_tabela(ws, "SOBREAVISO", bloco(solucao, "SBV"), 5, 13, "8064A2")
|
||||||
for col in range(1, 18):
|
for col in range(1, 18):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Regras operacionais de disponibilidade, qualificacao e progressao."""
|
"""Regras operacionais de disponibilidade, qualificação e progressão."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Utilitarios pequenos de normalizacao usados em todo o planejador."""
|
"""Utilitários pequenos de normalização usados em todo o planejador."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Validacao retrospectiva usando voos historicos."""
|
"""Validação retrospectiva usando voos históricos."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -110,11 +110,11 @@ def otimizar_equalizacao_50(
|
|||||||
|
|
||||||
slots = _montar_slots(voos)
|
slots = _montar_slots(voos)
|
||||||
if slots.empty:
|
if slots.empty:
|
||||||
raise RuntimeError("A base de 2025 nao possui linhas com horas_voadas maiores que zero.")
|
raise RuntimeError("A base de 2025 não possui linhas com horas_voadas maiores que zero.")
|
||||||
|
|
||||||
trips = sorted(set(voos["tripulante"]) & trips_cadastro)
|
trips = sorted(set(voos["tripulante"]) & trips_cadastro)
|
||||||
if not trips:
|
if not trips:
|
||||||
raise RuntimeError("Nao ha tripulantes da base de 2025 presentes no cadastro atual.")
|
raise RuntimeError("Não há tripulantes da base de 2025 presentes no cadastro atual.")
|
||||||
|
|
||||||
pares = []
|
pares = []
|
||||||
for s_idx, slot in slots.iterrows():
|
for s_idx, slot in slots.iterrows():
|
||||||
@@ -124,7 +124,7 @@ def otimizar_equalizacao_50(
|
|||||||
if not linha.empty and bool(linha.iloc[0].get(qt_col, False)):
|
if not linha.empty and bool(linha.iloc[0].get(qt_col, False)):
|
||||||
pares.append((s_idx, t_idx, float(slot["horas_voadas"])))
|
pares.append((s_idx, t_idx, float(slot["horas_voadas"])))
|
||||||
if not pares:
|
if not pares:
|
||||||
raise RuntimeError("Nao ha candidatos compativeis entre aeronaves dos voos 2025 e cadastro.")
|
raise RuntimeError("Não há candidatos compatíveis entre aeronaves dos voos 2025 e cadastro.")
|
||||||
|
|
||||||
n_x = len(pares)
|
n_x = len(pares)
|
||||||
n_t = len(trips)
|
n_t = len(trips)
|
||||||
@@ -188,7 +188,7 @@ def otimizar_equalizacao_50(
|
|||||||
options={"time_limit": tempo_limite_segundos, "mip_rel_gap": gap_relativo},
|
options={"time_limit": tempo_limite_segundos, "mip_rel_gap": gap_relativo},
|
||||||
)
|
)
|
||||||
if result.x is None:
|
if result.x is None:
|
||||||
raise RuntimeError(f"Validacao MILP inviavel: {result.message}")
|
raise RuntimeError(f"Validação MILP inviável: {result.message}")
|
||||||
|
|
||||||
escolhidos = result.x[:n_x] > 0.5
|
escolhidos = result.x[:n_x] > 0.5
|
||||||
linhas = []
|
linhas = []
|
||||||
|
|||||||
32
web_app.py
32
web_app.py
@@ -1,4 +1,4 @@
|
|||||||
"""Interface web local em HTML para o planejador diario."""
|
"""Interface web local em HTML para o planejador diário."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Planejador Diario de Missoes</title>
|
<title>Planejador Diário de Missões</title>
|
||||||
<link rel="icon" href="/assets/favicon.ico">
|
<link rel="icon" href="/assets/favicon.ico">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
@@ -291,7 +291,7 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
<div class="brand">
|
<div class="brand">
|
||||||
<img src="/assets/app_logo.png" alt="">
|
<img src="/assets/app_logo.png" alt="">
|
||||||
<div>
|
<div>
|
||||||
<h1>Planejador Diario de Missoes</h1>
|
<h1>Planejador Diário de Missões</h1>
|
||||||
<div class="subtitle">Escala, sobreaviso e acompanhamento operacional</div>
|
<div class="subtitle">Escala, sobreaviso e acompanhamento operacional</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,13 +310,13 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
<div id="planejamento" class="view active">
|
<div id="planejamento" class="view active">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<section>
|
<section>
|
||||||
<h2>Parametros</h2>
|
<h2>Parâmetros</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div><label>Data</label><input id="data_planejamento" placeholder="hoje ou AAAA-MM-DD"></div>
|
<div><label>Data</label><input id="data_planejamento" placeholder="hoje ou AAAA-MM-DD"></div>
|
||||||
<div><label>Criterio missao</label><select id="criterio_missao"><option>meta_50</option><option>meta_110</option><option>menor_custo</option></select></div>
|
<div><label>Critério missão</label><select id="criterio_missao"><option>meta_50</option><option>meta_110</option><option>menor_custo</option></select></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div><label>Criterio SBV</label><select id="criterio_sbv"><option>equalizar_quadrinhos</option><option>meta_50</option><option>meta_110</option></select></div>
|
<div><label>Critério SBV</label><select id="criterio_sbv"><option>equalizar_quadrinhos</option><option>meta_50</option><option>meta_110</option></select></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -326,14 +326,14 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Missao acionada</h2>
|
<h2>Missão acionada</h2>
|
||||||
<label class="check"><input id="tem_rota" type="checkbox"> Existe missao de rota acionada</label>
|
<label class="check"><input id="tem_rota" type="checkbox"> Existe missão de rota acionada</label>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div><label>Origem</label><input id="origem"></div>
|
<div><label>Origem</label><input id="origem"></div>
|
||||||
<div><label>Destino</label><input id="destino"></div>
|
<div><label>Destino</label><input id="destino"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div><label>Inicio</label><input id="inicio" placeholder="AAAA-MM-DD HH:MM"></div>
|
<div><label>Início</label><input id="inicio" placeholder="AAAA-MM-DD HH:MM"></div>
|
||||||
<div><label>Fim</label><input id="fim" placeholder="AAAA-MM-DD HH:MM"></div>
|
<div><label>Fim</label><input id="fim" placeholder="AAAA-MM-DD HH:MM"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -344,7 +344,7 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Tripulantes disponiveis</h2>
|
<h2>Tripulantes disponíveis</h2>
|
||||||
<div class="crewbar">
|
<div class="crewbar">
|
||||||
<button onclick="setCrew(true)">Marcar todos</button>
|
<button onclick="setCrew(true)">Marcar todos</button>
|
||||||
<button onclick="setCrew(false)">Desmarcar todos</button>
|
<button onclick="setCrew(false)">Desmarcar todos</button>
|
||||||
@@ -365,10 +365,10 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
<div class="summary">
|
<div class="summary">
|
||||||
<div class="metric">Tripulantes<b id="qtd_trip">-</b></div>
|
<div class="metric">Tripulantes<b id="qtd_trip">-</b></div>
|
||||||
<div class="metric">Quadrinhos abaixo de 50h<b id="qtd_abaixo50">-</b></div>
|
<div class="metric">Quadrinhos abaixo de 50h<b id="qtd_abaixo50">-</b></div>
|
||||||
<div class="metric">Media de horas no ano<b id="media_horas">-</b></div>
|
<div class="metric">Média de horas no ano<b id="media_horas">-</b></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<input id="q_busca" placeholder="Filtrar por tripulante, OI ou observacao" oninput="renderQuadrinhos()">
|
<input id="q_busca" placeholder="Filtrar por tripulante, OI ou observação" oninput="renderQuadrinhos()">
|
||||||
<select id="q_aeronave" onchange="renderQuadrinhos()"><option value="">Todas aeronaves</option><option>C98</option><option>C97</option><option>C95</option></select>
|
<select id="q_aeronave" onchange="renderQuadrinhos()"><option value="">Todas aeronaves</option><option>C98</option><option>C97</option><option>C95</option></select>
|
||||||
<select id="q_meta" onchange="renderQuadrinhos()"><option value="">Todos</option><option value="abaixo50">Abaixo de 50h</option></select>
|
<select id="q_meta" onchange="renderQuadrinhos()"><option value="">Todos</option><option value="abaixo50">Abaixo de 50h</option></select>
|
||||||
</div>
|
</div>
|
||||||
@@ -376,8 +376,8 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Trip</th><th>Aeronave</th><th>Qualificacao</th><th>Proxima OI</th>
|
<th>Trip</th><th>Aeronave</th><th>Qualificação</th><th>Próxima OI</th>
|
||||||
<th>Horas base</th><th>Horas ano</th><th>SBV</th><th>Faltam 50h</th><th>Ultimo voo</th><th>Obs</th>
|
<th>Horas base</th><th>Horas ano</th><th>SBV</th><th>Faltam 50h</th><th>Último voo</th><th>Obs</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="quadrinhos_body"><tr><td colspan="10">Carregando...</td></tr></tbody>
|
<tbody id="quadrinhos_body"><tr><td colspan="10">Carregando...</td></tr></tbody>
|
||||||
@@ -465,9 +465,9 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
item.className = 'aircraft';
|
item.className = 'aircraft';
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<strong>${av.aeronave}</strong>
|
<strong>${av.aeronave}</strong>
|
||||||
<label class="check"><input type="checkbox" data-av="${av.aeronave}" data-field="disponivel_sede" ${av.disponivel_sede ? 'checked' : ''}> Disponivel em sede</label>
|
<label class="check"><input type="checkbox" data-av="${av.aeronave}" data-field="disponivel_sede" ${av.disponivel_sede ? 'checked' : ''}> Disponível em sede</label>
|
||||||
<label class="check"><input type="checkbox" data-av="${av.aeronave}" data-field="em_pane" ${av.em_pane ? 'checked' : ''}> Em pane</label>
|
<label class="check"><input type="checkbox" data-av="${av.aeronave}" data-field="em_pane" ${av.em_pane ? 'checked' : ''}> Em pane</label>
|
||||||
<label class="check"><input type="checkbox" data-av="${av.aeronave}" data-field="em_missao_rota" ${av.em_missao_rota ? 'checked' : ''}> Em missao de rota</label>
|
<label class="check"><input type="checkbox" data-av="${av.aeronave}" data-field="em_missao_rota" ${av.em_missao_rota ? 'checked' : ''}> Em missão de rota</label>
|
||||||
<label>Hora livre</label><input value="${av.hora_livre ?? ''}" data-av="${av.aeronave}" data-field="hora_livre">
|
<label>Hora livre</label><input value="${av.hora_livre ?? ''}" data-av="${av.aeronave}" data-field="hora_livre">
|
||||||
`;
|
`;
|
||||||
root.appendChild(item);
|
root.appendChild(item);
|
||||||
|
|||||||
Reference in New Issue
Block a user