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
|
||||
|
||||
```text
|
||||
dados/ Arquivos de entrada do planejamento
|
||||
resultados/ Planilhas geradas pelo planejador
|
||||
src/planejador_missao/ Codigo-fonte Python do modelo
|
||||
scripts/ Scripts numerados de execucao, validacao e teste
|
||||
src/planejador_missao/ Código-fonte Python do modelo
|
||||
scripts/ Scripts numerados de execução, validação e teste
|
||||
.vscode/ Arquivos de workspace do VS Code
|
||||
web_app.py Interface web HTML para abrir no navegador
|
||||
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
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
@@ -29,13 +29,13 @@ python -m venv .venv
|
||||
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
|
||||
|
||||
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`.
|
||||
|
||||
Se preferir executar manualmente pelo PowerShell, use:
|
||||
@@ -46,19 +46,19 @@ Na raiz do projeto:
|
||||
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:
|
||||
|
||||
- editar data e criterios de otimizacao;
|
||||
- informar a missao de rota acionada;
|
||||
- marcar condicao das aeronaves;
|
||||
- selecionar tripulantes disponiveis;
|
||||
- consultar a aba `Quadrinhos`, com proxima OI, horas do ano, SBV e falta para 50 horas;
|
||||
- editar data e critérios de otimização;
|
||||
- informar a missão de rota acionada;
|
||||
- marcar condição das aeronaves;
|
||||
- selecionar tripulantes disponíveis;
|
||||
- consultar a aba `Quadrinhos`, com próxima OI, horas do ano, SBV e falta para 50 horas;
|
||||
- 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
|
||||
|
||||
@@ -81,17 +81,17 @@ Os arquivos de entrada ficam em `dados/`.
|
||||
|
||||
| Arquivo | Finalidade |
|
||||
| --- | --- |
|
||||
| `Modelagem_C98_ETA2_local.xlsx` ou `Modelagem C98 ETA2.xlsx` | Cadastro base de tripulantes, qualificacoes, projetos, soldo e metas de horas. |
|
||||
| `catalogo_ois.xlsx` | Catalogo de OIs por aeronave, subprograma, ordem e tipo de missao. |
|
||||
| `indisponibilidades_2026.xlsx` | Periodos em que cada tripulante nao pode ser escalado. |
|
||||
| `parametros_missao.csv` | Data de planejamento e criterios de otimizacao. |
|
||||
| `aeronaves_disponiveis.csv` | Condicao diaria de cada aeronave. |
|
||||
| `rotas_acionadas.csv` | Missao de rota acionada para atendimento obrigatorio. |
|
||||
| `tripulantes_disponiveis.csv` | Selecao manual dos tripulantes disponiveis no dia. |
|
||||
| `progresso_ois_2026.xlsx` | OIs ja concluidas manualmente. |
|
||||
| `historico_horas_voadas.csv` | Historico acumulado gerado automaticamente pelo planejador. |
|
||||
| `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` | Catálogo de OIs por aeronave, subprograma, ordem e tipo de missão. |
|
||||
| `indisponibilidades_2026.xlsx` | Períodos em que cada tripulante não pode ser escalado. |
|
||||
| `parametros_missao.csv` | Data de planejamento e critérios de otimização. |
|
||||
| `aeronaves_disponiveis.csv` | Condição diária de cada aeronave. |
|
||||
| `rotas_acionadas.csv` | Missão de rota acionada para atendimento obrigatório. |
|
||||
| `tripulantes_disponiveis.csv` | Seleção manual dos tripulantes disponíveis no dia. |
|
||||
| `progresso_ois_2026.xlsx` | OIs já concluídas manualmente. |
|
||||
| `historico_horas_voadas.csv` | Histórico acumulado gerado automaticamente pelo planejador. |
|
||||
|
||||
## 6. Saida gerada
|
||||
## 6. Saída gerada
|
||||
|
||||
O planejador cria uma planilha em:
|
||||
|
||||
@@ -99,17 +99,17 @@ O planejador cria uma planilha em:
|
||||
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`;
|
||||
- `VOOS LOCAIS`;
|
||||
- `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:
|
||||
|
||||
@@ -123,7 +123,7 @@ Para conferir rapidamente o ambiente:
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
python scripts\00_main.py --validacao 2025
|
||||
```
|
||||
|
||||
Tambem e possivel chamar diretamente:
|
||||
Também é possível chamar diretamente:
|
||||
|
||||
```powershell
|
||||
python scripts\07_validacao_2025.py
|
||||
```
|
||||
|
||||
Os relatorios sao salvos em:
|
||||
Os relatórios são salvos em:
|
||||
|
||||
```text
|
||||
resultados/validacao/validacao_2025_resumo.xlsx
|
||||
@@ -156,61 +156,61 @@ resultados/validacao/validacao_2025_metricas.csv
|
||||
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;
|
||||
- `voos_2025_slots`: voos historicos agregados em slots;
|
||||
- `escala_otimizada`: redistribuicao proposta pelo MILP.
|
||||
- `voos_2025_slots`: voos históricos agregados em slots;
|
||||
- `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;
|
||||
- missao local;
|
||||
- missão local;
|
||||
- sobreaviso.
|
||||
|
||||
Cada coluna recebe uma variavel binaria no MILP:
|
||||
Cada coluna recebe uma variável binária no MILP:
|
||||
|
||||
```text
|
||||
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;
|
||||
2. aproveitar missoes locais para progressao operacional;
|
||||
3. escolher sobreavisos conforme o criterio definido.
|
||||
2. aproveitar missões locais para progressão operacional;
|
||||
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 executa no maximo uma rota acionada;
|
||||
- cada aeronave executa no maximo uma missao local;
|
||||
- rota acionada e missao local nao podem usar a mesma aeronave no mesmo dia;
|
||||
- cada aeronave executa no máximo uma rota acionada;
|
||||
- cada aeronave executa no máximo uma missão local;
|
||||
- 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.
|
||||
|
||||
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 |
|
||||
| --- | --- |
|
||||
| `data_io.py` | Leitura das planilhas/CSVs e escrita do historico. |
|
||||
| `rules.py` | Regras de disponibilidade, qualificacao e progresso operacional. |
|
||||
| `candidates.py` | Geracao das colunas candidatas para o MILP. |
|
||||
| `optimizer.py` | Formulacao e solucao do modelo MILP. |
|
||||
| `report.py` | Registro de horas e criacao da planilha Excel. |
|
||||
| `data_io.py` | Leitura das planilhas/CSVs e escrita do histórico. |
|
||||
| `rules.py` | Regras de disponibilidade, qualificação e progresso operacional. |
|
||||
| `candidates.py` | Geração das colunas candidatas para o MILP. |
|
||||
| `optimizer.py` | Formulação e solução do modelo MILP. |
|
||||
| `report.py` | Registro de horas e criação da planilha Excel. |
|
||||
| `quadrinhos.py` | Resumo operacional da aba Quadrinhos. |
|
||||
| `validacao.py` | Validacao retrospectiva com voos historicos. |
|
||||
| `main.py` | Orquestracao do fluxo completo. |
|
||||
| `utils.py` | Normalizacao de textos, datas, booleanos e horas. |
|
||||
| `validacao.py` | Validação retrospectiva com voos históricos. |
|
||||
| `main.py` | Orquestração do fluxo completo. |
|
||||
| `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
|
||||
|
||||
@@ -9,6 +9,6 @@ if __name__ == "__main__":
|
||||
resultado = executar_planejamento(Path(__file__).resolve().parent)
|
||||
print("\n=== Planejamento concluido ===")
|
||||
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"Escalas selecionadas: {resultado['total_selecionadas']}")
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
"""Orquestrador principal do Planejador Missao.
|
||||
"""Orquestrador principal do Planejador Missão.
|
||||
|
||||
Entradas:
|
||||
Arquivos em dados/, argumentos opcionais de linha de comando e configuracoes
|
||||
centralizadas em scripts/01_config.py.
|
||||
|
||||
Saidas:
|
||||
Planejamento diario em resultados/, historico operacional atualizado,
|
||||
relatorios de validacao quando solicitado e logs em logs/execucao.log.
|
||||
Saídas:
|
||||
Planejamento diário em resultados/, histórico operacional atualizado,
|
||||
relatórios de validação quando solicitado e logs em logs/execucao.log.
|
||||
|
||||
Papel no pipeline:
|
||||
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:
|
||||
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:
|
||||
"""Executa o planejamento diario oficial em Python."""
|
||||
"""Executa o planejamento diário oficial em Python."""
|
||||
io_utils.configurar_ambiente()
|
||||
if 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("Escalas selecionadas: %s", resultado["total_selecionadas"])
|
||||
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
|
||||
|
||||
|
||||
def executar_validacao(ano: str, logger) -> dict:
|
||||
"""Executa validacao retrospectiva suportada pelo projeto."""
|
||||
"""Executa validação retrospectiva suportada pelo projeto."""
|
||||
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")
|
||||
return validacao.executar_validacao_2025(importar=True, logger=logger)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Executa o Planejador Missao em Python.")
|
||||
parser.add_argument("--data", help="Data do planejamento diario no formato AAAA-MM-DD.")
|
||||
parser.add_argument("--aeronave", choices=config.AERONAVES, help="Aeronave de referencia para diagnostico.")
|
||||
parser.add_argument("--modo", default="diario", choices=["diario"], help="Modo de execucao do planejamento.")
|
||||
parser.add_argument("--validacao", help="Ano da validacao retrospectiva. Exemplo: --validacao 2025.")
|
||||
parser = argparse.ArgumentParser(description="Executa o Planejador Missão em Python.")
|
||||
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 referência para diagnóstico.")
|
||||
parser.add_argument("--modo", default="diario", choices=["diario"], help="Modo de execução do planejamento.")
|
||||
parser.add_argument("--validacao", help="Ano da validação retrospectiva. Exemplo: --validacao 2025.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -86,13 +86,13 @@ def main() -> None:
|
||||
logger = io_utils.configurar_logger("main")
|
||||
if args.validacao:
|
||||
saida = executar_validacao(args.validacao, logger)
|
||||
print("\n=== Validacao concluida ===")
|
||||
print("\n=== Validação concluída ===")
|
||||
for path in saida["arquivos"]:
|
||||
print(f"- {path}")
|
||||
return
|
||||
|
||||
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"Arquivo: {resultado['arquivo_saida']}")
|
||||
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:
|
||||
Estrutura padrao do projeto, com pastas dados/, src/ e resultados/.
|
||||
Estrutura padrão do projeto, com pastas dados/, src/ e resultados/.
|
||||
|
||||
Saidas:
|
||||
Constantes de caminho, parametros operacionais, nomes de arquivos e pesos
|
||||
usados pelos scripts de execucao, validacao e teste.
|
||||
Saídas:
|
||||
Constantes de caminho, parâmetros operacionais, nomes de arquivos e pesos
|
||||
usados pelos scripts de execução, validação e teste.
|
||||
|
||||
Papel no pipeline:
|
||||
Evita caminhos absolutos e concentra convencoes para que o projeto rode em
|
||||
@@ -67,6 +67,6 @@ ARQUIVOS_ENTRADA_OBRIGATORIOS = [
|
||||
|
||||
|
||||
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]:
|
||||
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:
|
||||
Caminhos definidos em 01_config.py.
|
||||
|
||||
Saidas:
|
||||
Validacoes de arquivos, diretorios criados, logs em logs/execucao.log e
|
||||
Saídas:
|
||||
Validações de arquivos, diretórios criados, logs em logs/execucao.log e
|
||||
funcoes pequenas para exportacao.
|
||||
|
||||
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
|
||||
@@ -52,7 +52,7 @@ def configurar_logger(nome: str = "planejador_missao") -> logging.Logger:
|
||||
|
||||
|
||||
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()]
|
||||
if 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]
|
||||
if faltantes:
|
||||
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."
|
||||
)
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"""Preparacao das bases operacionais do Planejador Missao.
|
||||
"""Preparação das bases operacionais do Planejador Missão.
|
||||
|
||||
Entradas:
|
||||
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:
|
||||
Dicionario com DataFrames normalizados para geracao de colunas candidatas.
|
||||
|
||||
Papel no pipeline:
|
||||
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
|
||||
@@ -64,7 +64,7 @@ def main() -> None:
|
||||
logger = io_utils.configurar_logger("preparar_dados")
|
||||
bases = carregar_bases()
|
||||
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("Rotas acionadas: %s", len(bases["rotas"]))
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""Geracao das colunas candidatas viaveis.
|
||||
"""Geração das colunas candidatas viáveis.
|
||||
|
||||
Entradas:
|
||||
Bases preparadas por 03_preparar_dados.py.
|
||||
|
||||
Saidas:
|
||||
DataFrame em que cada linha representa uma escala possivel: rota acionada,
|
||||
missao local ou sobreaviso, com dupla, aeronave, score, custo e metadados.
|
||||
DataFrame em que cada linha representa uma escala possível: rota acionada,
|
||||
missão local ou sobreaviso, com dupla, aeronave, score, custo e metadados.
|
||||
|
||||
Papel no pipeline:
|
||||
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):
|
||||
"""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
|
||||
|
||||
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
|
||||
# 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(
|
||||
parametros["data_planejamento"],
|
||||
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:
|
||||
Colunas candidatas viaveis geradas por 04_gerar_colunas_candidatas.py.
|
||||
Colunas candidatas viáveis geradas por 04_gerar_colunas_candidatas.py.
|
||||
|
||||
Saidas:
|
||||
Subconjunto de colunas selecionadas pelo solver, representando a escala
|
||||
@@ -14,12 +14,12 @@ Metodologia:
|
||||
|
||||
Restricoes implementadas no motor src.planejador_missao.optimizer:
|
||||
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;
|
||||
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
|
||||
colunas candidatas, logo colunas inviaveis nao chegam ao MILP;
|
||||
f) regras especificas de rota, missao local e SBV sao preservadas por tipo.
|
||||
colunas candidatas, logo colunas inviáveis não chegam ao MILP;
|
||||
f) regras específicas de rota, missão local e SBV são preservadas por tipo.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -36,10 +36,10 @@ def resolver_escala(colunas, rotas, aeronaves):
|
||||
from src.planejador_missao.optimizer import resolver_milp
|
||||
|
||||
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.
|
||||
# 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.
|
||||
return resolver_milp(colunas, rotas, aeronaves)
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ Entradas:
|
||||
|
||||
Saidas:
|
||||
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:
|
||||
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
|
||||
|
||||
@@ -11,8 +11,8 @@ Saidas:
|
||||
resultados/validacao/validacao_2025_barras.png
|
||||
|
||||
Papel no pipeline:
|
||||
Mantem a demanda historica de 2025, gera candidatos compativeis para cada
|
||||
slot historico e usa MILP para redistribuir tripulantes preservando horas.
|
||||
Mantém a demanda histórica de 2025, gera candidatos compatíveis para cada
|
||||
slot histórico e usa MILP para redistribuir tripulantes preservando horas.
|
||||
"""
|
||||
|
||||
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:
|
||||
"""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])
|
||||
raw = pd.read_excel(origem, sheet_name=config.ABAS["voos_2025"], header=None)
|
||||
linhas = []
|
||||
@@ -176,7 +176,7 @@ def _png_barras(path: Path, metricas: pd.DataFrame) -> Path:
|
||||
|
||||
|
||||
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()
|
||||
logger = logger or io_utils.configurar_logger("validacao_2025")
|
||||
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)
|
||||
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():
|
||||
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(
|
||||
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_otim = float(metricas.loc[metricas["cenario"] == "otimizado_meta_50", "horas_total"].iloc[0])
|
||||
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"
|
||||
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("Tripulantes comparados: %s", len(resultado["comparativo"]))
|
||||
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)
|
||||
|
||||
return {
|
||||
@@ -242,11 +242,11 @@ def executar_validacao_2025(importar: bool = True, logger=None) -> dict:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Ponto de entrada da validacao 2025."""
|
||||
"""Ponto de entrada da validação 2025."""
|
||||
logger = io_utils.configurar_logger("validacao_2025")
|
||||
saida = executar_validacao_2025(importar=True, logger=logger)
|
||||
metricas = saida["resultado"]["metricas"]
|
||||
print("\n=== Validacao 2025 concluida ===")
|
||||
print("\n=== Validação 2025 concluída ===")
|
||||
print(metricas.to_string(index=False))
|
||||
print("\nArquivos gerados:")
|
||||
for path in saida["arquivos"]:
|
||||
|
||||
@@ -30,7 +30,7 @@ def comando_python_app() -> list[str]:
|
||||
|
||||
|
||||
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()
|
||||
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:
|
||||
Ambiente Python atual, dependencias instaladas e arquivos em dados/.
|
||||
|
||||
Saidas:
|
||||
Diagnostico amigavel no terminal e log em logs/execucao.log.
|
||||
Saídas:
|
||||
Diagnóstico amigável no terminal e log em logs/execucao.log.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
@@ -56,7 +56,7 @@ def main() -> None:
|
||||
testar_solver_minimo()
|
||||
print("Solver scipy.optimize.milp/HiGHS OK")
|
||||
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__":
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -110,7 +110,7 @@ def importar(origem: Path, destino: Path) -> pd.DataFrame:
|
||||
|
||||
|
||||
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(
|
||||
"--origem",
|
||||
type=Path,
|
||||
@@ -121,7 +121,7 @@ def main() -> None:
|
||||
"--destino",
|
||||
type=Path,
|
||||
default=BASE_DIR / "dados" / "validacao" / "voos_2025.csv",
|
||||
help="CSV de validacao gerado.",
|
||||
help="CSV de validação gerado.",
|
||||
)
|
||||
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
|
||||
|
||||
@@ -29,7 +29,7 @@ def main() -> None:
|
||||
|
||||
voos = carregar_voos_2025(entrada)
|
||||
if voos.empty:
|
||||
print("Base de validacao ainda vazia.")
|
||||
print("Base de validação ainda vazia.")
|
||||
print(f"Preencha o arquivo: {entrada}")
|
||||
print("Colunas esperadas: data,aeronave,tipo_escala,tripulante,funcao,oi,horas_voadas,sbv")
|
||||
return
|
||||
@@ -43,13 +43,13 @@ def main() -> None:
|
||||
)
|
||||
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"Voos/slots avaliados: {len(resultado['slots'])}")
|
||||
print(f"Tripulantes comparados: {len(resultado['comparativo'])}")
|
||||
print(f"Relatorio: {arquivo}")
|
||||
print(f"Relatório: {arquivo}")
|
||||
print(f"Solver: {resultado['status_solver']['mensagem']}")
|
||||
print("\nMetricas:")
|
||||
print("\nMétricas:")
|
||||
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)
|
||||
if candidatos.empty:
|
||||
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:
|
||||
horas_disp = horas_disponiveis(aeronaves, av)
|
||||
candidatos_local = candidatos_missao(data_planejamento, av, "LOCAL", cadastro, indisponibilidades, progresso, catalogo, disponiveis)
|
||||
candidatos_local = filtrar_por_horas_aeronave(candidatos_local, horas_disp)
|
||||
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 = []
|
||||
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):
|
||||
@@ -191,7 +191,7 @@ def gerar_colunas(data_planejamento, rotas, aeronaves, cadastro, indisponibilida
|
||||
r["bloqueado"] = False
|
||||
candidatos_sbv.append(r)
|
||||
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:
|
||||
return pd.DataFrame()
|
||||
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
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ def executar_planejamento(base_dir: Path) -> dict:
|
||||
# Bloco 03 - Modelo MILP e selecao final.
|
||||
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)
|
||||
salvar_historico(base_dir, registros)
|
||||
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))
|
||||
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"]))):
|
||||
linha = np.zeros(n)
|
||||
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_sbv = (colunas["aeronave"] == av) & (colunas["tipo_escala"] == "SBV")
|
||||
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[idx_rota | idx_sbv] = 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):
|
||||
idx = (colunas["tipo_escala"] == "ROTA_ACIONADA") & (colunas["rota_id"] == rota_id)
|
||||
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[idx] = 1
|
||||
restricoes.append(LinearConstraint(linha, 1, 1))
|
||||
|
||||
resultado = milp(c=-colunas["score_milp"].to_numpy(dtype=float), integrality=integrality, bounds=bounds, constraints=restricoes)
|
||||
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
|
||||
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
|
||||
|
||||
@@ -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"].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, "SOBREAVISO", bloco(solucao, "SBV"), 5, 13, "8064A2")
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Validacao retrospectiva usando voos historicos."""
|
||||
"""Validação retrospectiva usando voos históricos."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -110,11 +110,11 @@ def otimizar_equalizacao_50(
|
||||
|
||||
slots = _montar_slots(voos)
|
||||
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)
|
||||
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 = []
|
||||
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)):
|
||||
pares.append((s_idx, t_idx, float(slot["horas_voadas"])))
|
||||
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_t = len(trips)
|
||||
@@ -188,7 +188,7 @@ def otimizar_equalizacao_50(
|
||||
options={"time_limit": tempo_limite_segundos, "mip_rel_gap": gap_relativo},
|
||||
)
|
||||
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
|
||||
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
|
||||
|
||||
@@ -222,7 +222,7 @@ INDEX_HTML = r"""<!doctype html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<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">
|
||||
<style>
|
||||
:root {
|
||||
@@ -291,7 +291,7 @@ INDEX_HTML = r"""<!doctype html>
|
||||
<div class="brand">
|
||||
<img src="/assets/app_logo.png" alt="">
|
||||
<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>
|
||||
</div>
|
||||
@@ -310,13 +310,13 @@ INDEX_HTML = r"""<!doctype html>
|
||||
<div id="planejamento" class="view active">
|
||||
<div class="grid">
|
||||
<section>
|
||||
<h2>Parametros</h2>
|
||||
<h2>Parâmetros</h2>
|
||||
<div class="row">
|
||||
<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 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>
|
||||
</section>
|
||||
|
||||
@@ -326,14 +326,14 @@ INDEX_HTML = r"""<!doctype html>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Missao acionada</h2>
|
||||
<label class="check"><input id="tem_rota" type="checkbox"> Existe missao de rota acionada</label>
|
||||
<h2>Missão acionada</h2>
|
||||
<label class="check"><input id="tem_rota" type="checkbox"> Existe missão de rota acionada</label>
|
||||
<div class="row">
|
||||
<div><label>Origem</label><input id="origem"></div>
|
||||
<div><label>Destino</label><input id="destino"></div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="row">
|
||||
@@ -344,7 +344,7 @@ INDEX_HTML = r"""<!doctype html>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Tripulantes disponiveis</h2>
|
||||
<h2>Tripulantes disponíveis</h2>
|
||||
<div class="crewbar">
|
||||
<button onclick="setCrew(true)">Marcar todos</button>
|
||||
<button onclick="setCrew(false)">Desmarcar todos</button>
|
||||
@@ -365,10 +365,10 @@ INDEX_HTML = r"""<!doctype html>
|
||||
<div class="summary">
|
||||
<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">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 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_meta" onchange="renderQuadrinhos()"><option value="">Todos</option><option value="abaixo50">Abaixo de 50h</option></select>
|
||||
</div>
|
||||
@@ -376,8 +376,8 @@ INDEX_HTML = r"""<!doctype html>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Trip</th><th>Aeronave</th><th>Qualificacao</th><th>Proxima 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>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>Último voo</th><th>Obs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<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.innerHTML = `
|
||||
<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_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">
|
||||
`;
|
||||
root.appendChild(item);
|
||||
|
||||
Reference in New Issue
Block a user