Luno - Gerador de Escalas de Rotinas
Aplicação desktop (PyQt5) para criação e gestão automatizada de escalas de trabalho semanais para farmácias.
O Desafio
A criação manual de escalas de rotinas semanais em farmácias é uma tarefa complexa e demorada. É preciso conciliar as rotinas específicas de cada turno e grupo de trabalho com as folgas individuais dos funcionários, garantindo a cobertura de todas as tarefas e uma distribuição justa.
Fazer isso manualmente, muitas vezes em planilhas, é propenso a erros, difícil de manter e consome um tempo valioso que poderia ser dedicado a outras atividades da farmácia.
A Solução: Aplicação Desktop Luno
Desenvolvi o "Luno", uma aplicação desktop em Python com interface gráfica PyQt5, projetada para simplificar e automatizar a geração de escalas de rotinas em farmácias:
- Gestão Centralizada: Permite cadastrar funcionários (manualmente ou via importação Excel), definir seus grupos de trabalho (cores) e gerenciar rotinas específicas para cada turno e grupo (salvas em JSON).
- Gerenciador de Folgas: Interface dedicada para registrar os dias de folga fixos de cada funcionário, salvando os dados em formato JSON para persistência.
- Dois Modos de Geração:
- Grupos Fixos (Manual): Aloca funcionários às rotinas baseando-se estritamente no grupo de cor definido, rotacionando dentro do grupo para evitar sobrecarga e respeitando folgas.
- Rotação Diária (Automático): Distribui aleatoriamente os funcionários disponíveis (sem folga no dia) entre os grupos de rotinas ativos, garantindo cobertura e variação.
- Visualização Clara da Escala: Apresenta a escala semanal gerada numa tabela (QTableWidget) colorida de acordo com os grupos, facilitando a visualização.
- Fixação Manual (Pinning): Permite ao usuário clicar com o botão direito numa célula da escala e "fixar" um funcionário específico para aquela rotina/dia, sobrepondo a geração automática.
- Importação/Exportação Excel: Facilita a entrada de dados (funcionários, rotinas via Excel) e a partilha da escala final gerada (formato Excel).
- Interface Personalizada: Utiliza um stylesheet customizado (`ui_design.py`) e ícones SVG para um visual moderno e agradável, focado na usabilidade.
Interface da Aplicação Luno
Visualização das principais telas: configuração inicial, gerenciador de rotinas e gerenciador de folgas.
Código em Destaque
Trecho do `main_app.py` que implementa a lógica de geração da escala no modo "Grupos Fixos". Ele utiliza `deque` para rotacionar os funcionários dentro de cada grupo e verifica as folgas (`days_off_data`) antes de alocar um funcionário a uma célula da escala.
# (Trecho adaptado de main_app.py - PharmacyScheduler)
from collections import deque
# ... outras importações ...
class PharmacyScheduler(QMainWindow):
# ... (init, setup_ui, etc.) ...
def generate_with_fixed_groups(self):
# Cria um deque (fila circular) para cada grupo de cor com os funcionários
group_deques = {group: deque(sorted([name for name, g in self.employees.items() if g == group]))
for group in GROUP_COLORS}
# Itera sobre cada dia da semana (colunas da tabela)
for day_idx, day in enumerate(DAYS_OF_WEEK):
col_idx = day_idx + 1 # Índice da coluna na tabela (começa em 1)
# Itera sobre cada rotina (linhas da tabela)
for row_idx in range(self.schedule_table.rowCount()):
# self.routine_map mapeia linha da tabela -> grupo de cor
routine_group = self.routine_map[row_idx]
found_employee = None
# Verifica se há funcionários no deque para o grupo da rotina
if group_deques[routine_group]:
# Tenta encontrar um funcionário disponível (sem folga)
# Loopa no máximo o número de funcionários no grupo
for _ in range(len(group_deques[routine_group])):
candidate = group_deques[routine_group][0] # Pega o próximo da fila
# Verifica se o candidato NÃO está de folga neste dia
# self.days_off_data é um dict: {'NomeFunc': ['SEGUNDA', 'TERÇA'], ...}
if day not in self.days_off_data.get(candidate, []):
found_employee = candidate # Encontrou!
break # Para de procurar neste grupo para esta rotina
# Se estava de folga, rotaciona o deque para testar o próximo
group_deques[routine_group].rotate(-1)
employee_name = found_employee # Nome a ser exibido (pode ser None se ninguém estiver disponível)
# Verifica se há um funcionário fixado (pinned) pelo usuário para esta célula
if (row_idx, col_idx) in self.pinned_assignments:
employee_name = self.pinned_assignments[(row_idx, col_idx)]
# Se encontrou um funcionário (ou ele estava fixado)
if employee_name:
# Define o texto e a cor da célula na tabela
self.set_table_cell(row_idx, col_idx, employee_name, routine_group)
# Após processar todas as rotinas do dia, rotaciona TODOS os deques
# Isso garante que a próxima pessoa do grupo comece no dia seguinte
for group in group_deques:
if group_deques[group]:
group_deques[group].rotate(-1)
# ... (generate_with_daily_rotation, setup_table, set_table_cell, etc.) ...