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