feat: Sistema de Ranking de Consultores CAPES - versão inicial
Backend (FastAPI + DDD):
- Arquitetura DDD com camadas Domain, Application, Infrastructure, Interface
- Integração com Elasticsearch (ATUACAPES) para dados de consultores
- Integração com Oracle (SUCUPIRA_PAINEL) para coordenações PPG
- Cálculo dos 4 componentes de pontuação (A, B, C, D)
- Cache em memória para otimização de performance
- API REST com endpoints /ranking, /ranking/detalhado, /consultor/{id}
Frontend (React + Vite):
- Interface responsiva com cards expansíveis
- Visualização detalhada de pontuação por componente
- Filtro por quantidade de consultores (Top 10, 50, 100, etc)
Docker:
- docker-compose com shared_network externa
- Backend com Oracle Instant Client
- Frontend com Vite dev server
This commit is contained in:
93
backend/src/domain/services/calculador_pontuacao.py
Normal file
93
backend/src/domain/services/calculador_pontuacao.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
|
||||
from ..entities.consultor import (
|
||||
Consultor,
|
||||
CoordenacaoCapes,
|
||||
CoordenacaoPrograma,
|
||||
Consultoria,
|
||||
Premiacao,
|
||||
)
|
||||
from ..value_objects.pontuacao import ComponentePontuacao, PontuacaoCompleta
|
||||
|
||||
|
||||
class CalculadorPontuacao:
|
||||
@staticmethod
|
||||
def calcular_componente_a(coordenacoes: List[CoordenacaoCapes]) -> ComponentePontuacao:
|
||||
if not coordenacoes:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0, retorno=0)
|
||||
|
||||
coord_atual = next((c for c in coordenacoes if c.periodo.ativo), None)
|
||||
if not coord_atual:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0, retorno=0)
|
||||
|
||||
base_map = {"CA": 200, "CAJ": 150, "CAJ-MP": 120, "CAM": 100}
|
||||
tempo_max_map = {"CA": 100, "CAJ": 80, "CAJ-MP": 60, "CAM": 50}
|
||||
bonus_atual_map = {"CA": 30, "CAJ": 20, "CAJ-MP": 15, "CAM": 10}
|
||||
|
||||
base = base_map.get(coord_atual.tipo, 0)
|
||||
anos = coord_atual.periodo.anos_decorridos
|
||||
tempo = min(int(anos * 10), tempo_max_map.get(coord_atual.tipo, 0))
|
||||
|
||||
extras = min(len(coord_atual.areas_adicionais) * 20, 100)
|
||||
bonus = bonus_atual_map.get(coord_atual.tipo, 0) if coord_atual.periodo.ativo else 0
|
||||
retorno = 20 if coord_atual.ja_coordenou_antes else 0
|
||||
|
||||
return ComponentePontuacao(base=base, tempo=tempo, extras=extras, bonus=bonus, retorno=retorno)
|
||||
|
||||
@staticmethod
|
||||
def calcular_componente_b(coordenacoes: List[CoordenacaoPrograma]) -> ComponentePontuacao:
|
||||
if not coordenacoes:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0)
|
||||
|
||||
base = 70
|
||||
anos_totais = sum(c.periodo.anos_decorridos for c in coordenacoes)
|
||||
tempo = min(int(anos_totais * 5), 50)
|
||||
|
||||
programas_distintos = len({c.id_programa for c in coordenacoes})
|
||||
extras = min((programas_distintos - 1) * 20, 40)
|
||||
|
||||
coord_ativa = any(c.periodo.ativo for c in coordenacoes)
|
||||
bonus = 20 if coord_ativa else 0
|
||||
|
||||
return ComponentePontuacao(base=base, tempo=tempo, extras=extras, bonus=bonus)
|
||||
|
||||
@staticmethod
|
||||
def calcular_componente_c(consultoria: Consultoria) -> ComponentePontuacao:
|
||||
if not consultoria:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0)
|
||||
|
||||
base = 150 if consultoria.eventos_recentes > 0 else 100
|
||||
|
||||
anos = (datetime.now() - consultoria.primeiro_evento).days / 365.25
|
||||
tempo = min(int(anos * 5), 50)
|
||||
|
||||
extras_eventos = min(consultoria.total_eventos * 2, 20)
|
||||
extras_responsavel = min(consultoria.vezes_responsavel * 5, 25)
|
||||
extras_areas = min((len(consultoria.areas) - 1) * 10, 30) if len(consultoria.areas) > 1 else 0
|
||||
extras = extras_eventos + extras_responsavel + extras_areas
|
||||
|
||||
bonus = 0
|
||||
|
||||
return ComponentePontuacao(base=base, tempo=tempo, extras=extras, bonus=bonus)
|
||||
|
||||
@staticmethod
|
||||
def calcular_componente_d(premiacoes: List[Premiacao]) -> ComponentePontuacao:
|
||||
if not premiacoes:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0)
|
||||
|
||||
total_pontos = sum(p.pontos for p in premiacoes)
|
||||
total_pontos = min(total_pontos, 180)
|
||||
|
||||
return ComponentePontuacao(base=total_pontos, tempo=0, extras=0, bonus=0)
|
||||
|
||||
@classmethod
|
||||
def calcular_pontuacao_completa(cls, consultor: Consultor) -> PontuacaoCompleta:
|
||||
comp_a = cls.calcular_componente_a(consultor.coordenacoes_capes)
|
||||
comp_b = cls.calcular_componente_b(consultor.coordenacoes_programas)
|
||||
comp_c = cls.calcular_componente_c(consultor.consultoria)
|
||||
comp_d = cls.calcular_componente_d(consultor.premiacoes)
|
||||
|
||||
return PontuacaoCompleta(
|
||||
componente_a=comp_a, componente_b=comp_b, componente_c=comp_c, componente_d=comp_d
|
||||
)
|
||||
Reference in New Issue
Block a user