Files
ranking/backend/src/domain/services/calculador_pontuacao.py

225 lines
7.8 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, mesclar_periodos, anos_completos_periodos
class CalculadorPontuacao:
@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 = mesclar_periodos(periodos)
anos_total = 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 = mesclar_periodos(periodos)
anos_total = anos_completos_periodos(mesclados)
tempo = min(anos_total * criterio.multiplicador_tempo, criterio.teto_tempo)
bonus = 0
# Bônus de continuidade (escalonado, não cumulativo) - apenas CONS_ATIVO
if codigo == "CONS_ATIVO":
if consultoria.anos_consecutivos >= 8:
bonus += 15
elif consultoria.anos_consecutivos >= 5:
bonus += 10
elif consultoria.anos_consecutivos >= 3:
bonus += 5
# Bônus de retorno (uma vez) - apenas CONS_ATIVO
if consultoria.retornos > 0 and criterio.bonus_retorno:
bonus += criterio.bonus_retorno
total_bruto = base + tempo + bonus
total = min(total_bruto, criterio.teto) if criterio.teto > 0 else total_bruto
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, "anos": set()})
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
if hasattr(aval, 'ano') and aval.ano:
totais_por_codigo[aval.codigo]["anos"].add(aval.ano)
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
base_total = dados["base"]
bonus = 0
if criterio.bonus_recorrencia_anual > 0:
anos_distintos = len(dados["anos"])
bonus_recorrencia = anos_distintos * criterio.bonus_recorrencia_anual
bonus = min(bonus_recorrencia, criterio.teto_recorrencia)
total_bruto = base_total + bonus
if criterio.teto > 0:
total = min(total_bruto, criterio.teto)
else:
total = total_bruto
atuacoes.append(PontuacaoAtuacao(
codigo=codigo,
base=base_total,
tempo=0,
bonus=bonus,
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,
)