Corrige acentuacao dos textos

This commit is contained in:
Lucas
2026-06-26 18:15:55 -03:00
parent 6443c0e2dc
commit f02167ff92
24 changed files with 174 additions and 175 deletions

130
README.md
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"]))

View File

@@ -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"],

View File

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

View File

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

View File

@@ -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"]:

View File

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

View File

@@ -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__":

View File

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

View File

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

View File

@@ -1,2 +1 @@
"""Planejador de missao e sobreaviso por MILP."""
"""Planejador de missão e sobreaviso por MILP."""

View File

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

View File

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

View File

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

View File

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

View File

@@ -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):

View File

@@ -1,4 +1,4 @@
"""Regras operacionais de disponibilidade, qualificacao e progressao."""
"""Regras operacionais de disponibilidade, qualificação e progressão."""
from __future__ import annotations

View File

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

View File

@@ -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 = []

View File

@@ -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);