Files
ranking/backend/src/domain/services/calculador_pontuacao.py
2025-12-13 18:25:40 -03:00

232 lines
8.1 KiB
Python

from datetime import datetime
from typing import List, Dict
from collections import defaultdict
from ..entities.consultor import (
Consultor,
CoordenacaoCapes,
Consultoria,
Inscricao,
AvaliacaoComissao,
Premiacao,
BolsaCNPQ,
Participacao,
Orientacao,
MembroBanca,
)
from ..value_objects.pontuacao import PontuacaoAtuacao, PontuacaoBloco, PontuacaoCompleta
from ..value_objects.criterios_pontuacao import CRITERIOS, get_criterio, Bloco
from ..value_objects.periodo import Periodo
class CalculadorPontuacao:
@staticmethod
def _mesclar_periodos(periodos: List[Periodo]) -> List[Periodo]:
if not periodos:
return []
periodos_ordenados = sorted(periodos, key=lambda p: p.inicio if p.inicio else datetime.min)
mesclados: List[Periodo] = []
for p in periodos_ordenados:
if not mesclados:
mesclados.append(p)
continue
ultimo = mesclados[-1]
ultimo_fim = ultimo.fim or datetime.now()
atual_fim = p.fim or datetime.now()
if p.inicio and p.inicio <= ultimo_fim:
novo_fim = max(ultimo_fim, atual_fim)
mesclados[-1] = Periodo(inicio=ultimo.inicio, fim=novo_fim if not ultimo.ativo else None)
else:
mesclados.append(p)
return mesclados
@staticmethod
def _anos_completos_periodos(periodos: List[Periodo]) -> int:
ref = datetime.now()
return sum(p.anos_completos(ref) for p in periodos)
@staticmethod
def calcular_bloco_a(coordenacoes: List[CoordenacaoCapes]) -> PontuacaoBloco:
if not coordenacoes:
return PontuacaoBloco(bloco="A", atuacoes=[])
tipos_ordenados = ["CA", "CAJ", "CAJ_MP", "CAM"]
coord_por_tipo: Dict[str, List[CoordenacaoCapes]] = defaultdict(list)
for c in coordenacoes:
codigo = c.codigo.replace("-", "_")
coord_por_tipo[codigo].append(c)
atuacoes = []
for tipo in tipos_ordenados:
if tipo not in coord_por_tipo:
continue
criterio = get_criterio(tipo)
if not criterio:
continue
coords = coord_por_tipo[tipo]
periodos = [c.periodo for c in coords]
mesclados = CalculadorPontuacao._mesclar_periodos(periodos)
anos_total = CalculadorPontuacao._anos_completos_periodos(mesclados)
ativo = any(c.periodo.ativo for c in coords)
tem_retorno = len(mesclados) > 1
base = criterio.base
tempo = min(anos_total * criterio.multiplicador_tempo, criterio.teto_tempo)
bonus_atualidade = criterio.bonus_atualidade if ativo else 0
bonus_retorno = criterio.bonus_retorno if tem_retorno else 0
bonus = bonus_atualidade + bonus_retorno
total_bruto = base + tempo + bonus
total = min(total_bruto, criterio.teto)
atuacoes.append(PontuacaoAtuacao(
codigo=tipo,
base=base,
tempo=tempo,
bonus=bonus,
total=total,
quantidade=len(coords),
))
return PontuacaoBloco(bloco="A", atuacoes=atuacoes)
@staticmethod
def calcular_bloco_c(consultoria: Consultoria) -> PontuacaoBloco:
if not consultoria:
return PontuacaoBloco(bloco="C", atuacoes=[])
codigo = consultoria.codigo
criterio = get_criterio(codigo)
if not criterio:
return PontuacaoBloco(bloco="C", atuacoes=[])
base = criterio.base
tempo = 0
if criterio.pontua_tempo:
periodos = consultoria.periodos if consultoria.periodos else [consultoria.periodo]
mesclados = CalculadorPontuacao._mesclar_periodos(periodos)
anos_total = CalculadorPontuacao._anos_completos_periodos(mesclados)
tempo = min(anos_total * criterio.multiplicador_tempo, criterio.teto_tempo)
bonus = 0
if codigo == "CONS_ATIVO":
if consultoria.anos_consecutivos >= 8:
bonus += criterio.bonus_continuidade_8anos
elif consultoria.anos_consecutivos >= 5:
bonus += criterio.bonus_continuidade_5anos
elif consultoria.anos_consecutivos >= 3:
bonus += criterio.bonus_continuidade_3anos
if consultoria.retornos > 0:
bonus += criterio.bonus_retorno
total_bruto = base + tempo + bonus
total = min(total_bruto, criterio.teto)
atuacoes = [PontuacaoAtuacao(
codigo=codigo,
base=base,
tempo=tempo,
bonus=bonus,
total=total,
quantidade=1,
)]
return PontuacaoBloco(bloco="C", atuacoes=atuacoes)
@staticmethod
def calcular_bloco_d(
inscricoes: List[Inscricao],
avaliacoes: List[AvaliacaoComissao],
premiacoes: List[Premiacao],
bolsas: List[BolsaCNPQ],
participacoes: List[Participacao],
orientacoes: List[Orientacao],
membros_banca: List[MembroBanca],
) -> PontuacaoBloco:
atuacoes = []
totais_por_codigo: Dict[str, Dict] = defaultdict(lambda: {"base": 0, "qtd": 0})
for insc in inscricoes:
criterio = get_criterio(insc.codigo)
if criterio:
totais_por_codigo[insc.codigo]["base"] += criterio.base
totais_por_codigo[insc.codigo]["qtd"] += 1
for aval in avaliacoes:
criterio = get_criterio(aval.codigo)
if criterio:
totais_por_codigo[aval.codigo]["base"] += criterio.base
totais_por_codigo[aval.codigo]["qtd"] += 1
for prem in premiacoes:
criterio = get_criterio(prem.codigo)
if criterio:
totais_por_codigo[prem.codigo]["base"] += criterio.base
totais_por_codigo[prem.codigo]["qtd"] += 1
for bolsa in bolsas:
criterio = get_criterio(bolsa.codigo)
if criterio:
totais_por_codigo[bolsa.codigo]["base"] += criterio.base
totais_por_codigo[bolsa.codigo]["qtd"] += 1
for part in participacoes:
criterio = get_criterio(part.codigo)
if criterio:
totais_por_codigo[part.codigo]["base"] += criterio.base
totais_por_codigo[part.codigo]["qtd"] += 1
for orient in orientacoes:
criterio = get_criterio(orient.codigo)
if criterio:
totais_por_codigo[orient.codigo]["base"] += criterio.base
totais_por_codigo[orient.codigo]["qtd"] += 1
for mb in membros_banca:
criterio = get_criterio(mb.codigo)
if criterio:
totais_por_codigo[mb.codigo]["base"] += criterio.base
totais_por_codigo[mb.codigo]["qtd"] += 1
for codigo, dados in totais_por_codigo.items():
criterio = get_criterio(codigo)
if not criterio:
continue
total = min(dados["base"], criterio.teto)
atuacoes.append(PontuacaoAtuacao(
codigo=codigo,
base=dados["base"],
tempo=0,
bonus=0,
total=total,
quantidade=dados["qtd"],
))
return PontuacaoBloco(bloco="D", atuacoes=atuacoes)
@classmethod
def calcular_pontuacao_completa(cls, consultor: Consultor) -> PontuacaoCompleta:
bloco_a = cls.calcular_bloco_a(consultor.coordenacoes_capes)
bloco_c = cls.calcular_bloco_c(consultor.consultoria)
bloco_d = cls.calcular_bloco_d(
inscricoes=consultor.inscricoes,
avaliacoes=consultor.avaliacoes_comissao,
premiacoes=consultor.premiacoes,
bolsas=consultor.bolsas_cnpq,
participacoes=consultor.participacoes,
orientacoes=consultor.orientacoes,
membros_banca=consultor.membros_banca,
)
return PontuacaoCompleta(
bloco_a=bloco_a,
bloco_c=bloco_c,
bloco_d=bloco_d,
)