diff --git a/backend/src/domain/services/calculador_pontuacao.py b/backend/src/domain/services/calculador_pontuacao.py index ee80dcf..81e5a18 100644 --- a/backend/src/domain/services/calculador_pontuacao.py +++ b/backend/src/domain/services/calculador_pontuacao.py @@ -114,18 +114,13 @@ class CalculadorPontuacao: 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 + if consultoria.anos_consecutivos >= 8 and criterio.bonus_continuidade_8anos: + bonus += criterio.bonus_continuidade_8anos + if codigo == "CONS_ATIVO" and consultoria.retornos > 0 and criterio.bonus_retorno: + bonus += criterio.bonus_retorno total_bruto = base + tempo + bonus - total = min(total_bruto, criterio.teto) + total = min(total_bruto, criterio.teto) if criterio.teto > 0 else total_bruto atuacoes = [PontuacaoAtuacao( codigo=codigo, @@ -149,7 +144,7 @@ class CalculadorPontuacao: membros_banca: List[MembroBanca], ) -> PontuacaoBloco: atuacoes = [] - totais_por_codigo: Dict[str, Dict] = defaultdict(lambda: {"base": 0, "qtd": 0}) + totais_por_codigo: Dict[str, Dict] = defaultdict(lambda: {"base": 0, "qtd": 0, "anos": set()}) for insc in inscricoes: criterio = get_criterio(insc.codigo) @@ -162,6 +157,8 @@ class CalculadorPontuacao: 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) @@ -198,12 +195,24 @@ class CalculadorPontuacao: if not criterio: continue - total = min(dados["base"], criterio.teto) + 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=dados["base"], + base=base_total, tempo=0, - bonus=0, + bonus=bonus, total=total, quantidade=dados["qtd"], )) diff --git a/backend/src/domain/value_objects/criterios_pontuacao.py b/backend/src/domain/value_objects/criterios_pontuacao.py index fd4222a..368a1af 100644 --- a/backend/src/domain/value_objects/criterios_pontuacao.py +++ b/backend/src/domain/value_objects/criterios_pontuacao.py @@ -11,7 +11,7 @@ class Bloco(Enum): class TipoAtuacao(Enum): FUNCAO = "Função" - RESULTADO = "Resultado" + COMPETENCIA_RECONHECIMENTO = "Competência e Reconhecimento" PAPEL = "Papel" PARTICIPACAO = "Participação" @@ -28,9 +28,9 @@ class CriterioPontuacao: teto_tempo: int = 0 bonus_atualidade: int = 0 bonus_retorno: int = 0 - bonus_continuidade_3anos: int = 0 - bonus_continuidade_5anos: int = 0 bonus_continuidade_8anos: int = 0 + bonus_recorrencia_anual: int = 0 + teto_recorrencia: int = 0 CRITERIOS: Dict[str, CriterioPontuacao] = { @@ -56,7 +56,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { multiplicador_tempo=8, teto_tempo=80, bonus_atualidade=20, - bonus_retorno=20, + bonus_retorno=15, ), "CAJ_MP": CriterioPontuacao( codigo="CAJ_MP", @@ -68,7 +68,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { multiplicador_tempo=6, teto_tempo=60, bonus_atualidade=15, - bonus_retorno=20, + bonus_retorno=10, ), "CAM": CriterioPontuacao( codigo="CAM", @@ -79,8 +79,8 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { pontua_tempo=True, multiplicador_tempo=5, teto_tempo=50, - bonus_atualidade=10, - bonus_retorno=20, + bonus_atualidade=20, + bonus_retorno=10, ), "PPG_COORD": CriterioPontuacao( codigo="PPG_COORD", @@ -101,9 +101,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { pontua_tempo=True, multiplicador_tempo=5, teto_tempo=50, - bonus_continuidade_3anos=5, - bonus_continuidade_5anos=10, - bonus_continuidade_8anos=15, + bonus_continuidade_8anos=20, bonus_retorno=15, ), "CONS_HIST": CriterioPontuacao( @@ -115,6 +113,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { pontua_tempo=True, multiplicador_tempo=5, teto_tempo=50, + bonus_continuidade_8anos=20, ), "CONS_FALECIDO": CriterioPontuacao( codigo="CONS_FALECIDO", @@ -122,35 +121,42 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { tipo=TipoAtuacao.FUNCAO, base=100, teto=230, - pontua_tempo=False, + pontua_tempo=True, + multiplicador_tempo=5, + teto_tempo=50, + bonus_continuidade_8anos=20, ), "INSC_AUTOR": CriterioPontuacao( codigo="INSC_AUTOR", bloco=Bloco.D, - tipo=TipoAtuacao.PAPEL, + tipo=TipoAtuacao.FUNCAO, base=10, teto=20, ), "INSC_INST": CriterioPontuacao( codigo="INSC_INST", bloco=Bloco.D, - tipo=TipoAtuacao.PAPEL, + tipo=TipoAtuacao.FUNCAO, base=30, teto=60, ), "AVAL_COMIS_PREMIO": CriterioPontuacao( codigo="AVAL_COMIS_PREMIO", bloco=Bloco.D, - tipo=TipoAtuacao.FUNCAO, + tipo=TipoAtuacao.PAPEL, base=30, teto=60, + bonus_recorrencia_anual=2, + teto_recorrencia=15, ), "AVAL_COMIS_GP": CriterioPontuacao( codigo="AVAL_COMIS_GP", bloco=Bloco.D, - tipo=TipoAtuacao.FUNCAO, + tipo=TipoAtuacao.PAPEL, base=50, - teto=100, + teto=80, + bonus_recorrencia_anual=3, + teto_recorrencia=20, ), "COORD_COMIS_PREMIO": CriterioPontuacao( codigo="COORD_COMIS_PREMIO", @@ -158,6 +164,8 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { tipo=TipoAtuacao.FUNCAO, base=50, teto=100, + bonus_recorrencia_anual=4, + teto_recorrencia=20, ), "COORD_COMIS_GP": CriterioPontuacao( codigo="COORD_COMIS_GP", @@ -165,40 +173,42 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { tipo=TipoAtuacao.FUNCAO, base=60, teto=120, + bonus_recorrencia_anual=6, + teto_recorrencia=20, ), "BOL_BPQ_SUPERIOR": CriterioPontuacao( codigo="BOL_BPQ_SUPERIOR", bloco=Bloco.D, - tipo=TipoAtuacao.RESULTADO, + tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO, base=30, teto=60, ), "BOL_BPQ_INTERMEDIARIO": CriterioPontuacao( codigo="BOL_BPQ_INTERMEDIARIO", bloco=Bloco.D, - tipo=TipoAtuacao.RESULTADO, - base=50, - teto=100, + tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO, + base=30, + teto=60, ), "PREMIACAO": CriterioPontuacao( codigo="PREMIACAO", bloco=Bloco.D, - tipo=TipoAtuacao.RESULTADO, - base=150, + tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO, + base=100, teto=180, ), "PREMIACAO_GP": CriterioPontuacao( codigo="PREMIACAO_GP", bloco=Bloco.D, - tipo=TipoAtuacao.RESULTADO, - base=30, + tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO, + base=50, teto=60, ), "MENCAO": CriterioPontuacao( codigo="MENCAO", bloco=Bloco.D, - tipo=TipoAtuacao.RESULTADO, - base=10, + tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO, + base=30, teto=20, ), "EVENTO": CriterioPontuacao( @@ -220,63 +230,63 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=15, - teto=100, + teto=0, ), "ORIENT_TESE": CriterioPontuacao( codigo="ORIENT_TESE", bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=10, - teto=50, + teto=0, ), "ORIENT_DISS": CriterioPontuacao( codigo="ORIENT_DISS", bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=5, - teto=25, + teto=0, ), "CO_ORIENT_POS_DOC": CriterioPontuacao( codigo="CO_ORIENT_POS_DOC", bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=7, - teto=35, + teto=0, ), "CO_ORIENT_TESE": CriterioPontuacao( codigo="CO_ORIENT_TESE", bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=5, - teto=25, + teto=0, ), "CO_ORIENT_DISS": CriterioPontuacao( codigo="CO_ORIENT_DISS", bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=3, - teto=15, + teto=0, ), "MB_BANCA_POS_DOC": CriterioPontuacao( codigo="MB_BANCA_POS_DOC", bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=3, - teto=15, + teto=0, ), "MB_BANCA_TESE": CriterioPontuacao( codigo="MB_BANCA_TESE", bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=3, - teto=15, + teto=0, ), "MB_BANCA_DISS": CriterioPontuacao( codigo="MB_BANCA_DISS", bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=2, - teto=10, + teto=0, ), } diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index c0c8beb..49f149e 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -4,7 +4,7 @@ import './ConsultorCard.css'; const FORMULAS = { bloco_a: { titulo: 'Coordenacao CAPES', - descricao: 'CA=200 | CAJ=150 | CAJ_MP=120 | CAM=100\nTempo: multiplicador por ano\nBonus atualidade + Retorno', + descricao: 'CA=200 | CAJ=150 | CAJ_MP=120 | CAM=100\nTempo: multiplicador por ano\nBonus atualidade + Retorno (V2)', }, bloco_b: { titulo: 'Coordenacao PPG', @@ -12,11 +12,11 @@ const FORMULAS = { }, bloco_c: { titulo: 'Consultoria', - descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade 8a+=15 | Retorno=15', + descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade 8a+=20 | Retorno=15 (V2)', }, bloco_d: { titulo: 'Premiacoes/Avaliacoes', - descricao: 'PREMIACAO=150 | PREMIACAO_GP=30 | MENCAO=10\nAVAL_COMIS=30-50 | COORD_COMIS=50-60\nINSC_AUTOR=10 | INSC_INST=30', + descricao: 'GP=100 | Premio=50 | Mencao=30\nAVAL_COMIS=30-50 | COORD_COMIS=50-60\nINSC_AUTOR=10 | INSC_INST=30 (V2)', }, }; @@ -26,34 +26,37 @@ const PONTOS_BASE = { INSC_AUTOR: 10, INSC_INST: 30, AVAL_COMIS_PREMIO: 30, AVAL_COMIS_GP: 50, COORD_COMIS_PREMIO: 50, COORD_COMIS_GP: 60, - PREMIACAO: 150, PREMIACAO_GP: 30, MENCAO: 10, - BOL_BPQ_SUPERIOR: 30, BOL_BPQ_INTERMEDIARIO: 50, + PREMIACAO: 100, PREMIACAO_GP: 50, MENCAO: 30, + BOL_BPQ_SUPERIOR: 30, BOL_BPQ_INTERMEDIARIO: 30, EVENTO: 1, PROJ: 10, ORIENT_POS_DOC: 15, ORIENT_TESE: 10, ORIENT_DISS: 5, CO_ORIENT_POS_DOC: 7, CO_ORIENT_TESE: 5, CO_ORIENT_DISS: 3, MB_BANCA_POS_DOC: 3, MB_BANCA_TESE: 3, MB_BANCA_DISS: 2, }; -// Teto oficial por código, conforme documento de critérios (seção 3.x) const TETOS = { - // 3.3 Inscrições INSC_AUTOR: { teto: 20, doc: '3.3 Inscrições' }, INSC_INST: { teto: 60, doc: '3.3 Inscrições' }, - // 3.4 Avaliação / Coordenação de Comissão - AVAL_COMIS_PREMIO: { teto: 60, doc: '3.4 Avaliação/Comissão' }, - AVAL_COMIS_GP: { teto: 100, doc: '3.4 Avaliação/Comissão' }, - COORD_COMIS_PREMIO: { teto: 100, doc: '3.4 Avaliação/Comissão' }, - COORD_COMIS_GP: { teto: 120, doc: '3.4 Avaliação/Comissão' }, - // 3.6 Premiações - PREMIACAO: { teto: 180, doc: '3.6 Premiações' }, - PREMIACAO_GP: { teto: 60, doc: '3.6 Premiações' }, - MENCAO: { teto: 20, doc: '3.6 Premiações' }, - // 3.7 Participações + AVAL_COMIS_PREMIO: { teto: 60, doc: '3.4 Avaliação/Comissão', bonus: '+2/ano (max 15)' }, + AVAL_COMIS_GP: { teto: 80, doc: '3.4 Avaliação/Comissão', bonus: '+3/ano (max 20)' }, + COORD_COMIS_PREMIO: { teto: 100, doc: '3.4 Avaliação/Comissão', bonus: '+4/ano (max 20)' }, + COORD_COMIS_GP: { teto: 120, doc: '3.4 Avaliação/Comissão', bonus: '+6/ano (max 20)' }, + PREMIACAO: { teto: 180, doc: '3.6 Grande Prêmio' }, + PREMIACAO_GP: { teto: 60, doc: '3.6 Prêmio' }, + MENCAO: { teto: 20, doc: '3.6 Menção Honrosa' }, EVENTO: { teto: 5, doc: '3.7 Participações' }, PROJ: { teto: 40, doc: '3.7 Participações' }, - // 3.5 Bolsas CNPQ (nomes resumidos) BOL_BPQ_SUPERIOR: { teto: 60, doc: '3.5 Bolsas CNPQ' }, - BOL_BPQ_INTERMEDIARIO: { teto: 100, doc: '3.5 Bolsas CNPQ' }, + BOL_BPQ_INTERMEDIARIO: { teto: 60, doc: '3.5 Bolsas CNPQ' }, + ORIENT_POS_DOC: { teto: 0, doc: '3.8 Orientação (sem limite)' }, + ORIENT_TESE: { teto: 0, doc: '3.8 Orientação (sem limite)' }, + ORIENT_DISS: { teto: 0, doc: '3.8 Orientação (sem limite)' }, + CO_ORIENT_POS_DOC: { teto: 0, doc: '3.9 Co-Orientação (sem limite)' }, + CO_ORIENT_TESE: { teto: 0, doc: '3.9 Co-Orientação (sem limite)' }, + CO_ORIENT_DISS: { teto: 0, doc: '3.9 Co-Orientação (sem limite)' }, + MB_BANCA_POS_DOC: { teto: 0, doc: '3.10 Banca (sem limite)' }, + MB_BANCA_TESE: { teto: 0, doc: '3.10 Banca (sem limite)' }, + MB_BANCA_DISS: { teto: 0, doc: '3.10 Banca (sem limite)' }, }; const ScoreItemWithTooltip = ({ value, label, formula, style }) => ( @@ -313,7 +316,8 @@ const BlocoDetalhes = ({ titulo, bloco, cor }) => ( const bonus = at.bonus || 0; const bruto = base + tempo + bonus; const meta = TETOS[at.codigo]; - const capped = bruto !== at.total; + const hasTeto = meta && meta.teto > 0; + const capped = hasTeto && bruto > meta.teto; const unidade = at.quantidade > 1 ? Math.round(base / at.quantidade) : null; const partes = []; @@ -322,13 +326,11 @@ const BlocoDetalhes = ({ titulo, bloco, cor }) => ( ? `Base ${unidade} x ${at.quantidade} = ${base}` : `Base ${base}` ); - if (capped) { - partes.push(`Bruto ${bruto}`); - } if (tempo) partes.push(`Tempo ${tempo}`); if (bonus) partes.push(`Bônus ${bonus}`); - if (meta) partes.push(`Teto ${meta.teto}`); - partes.push(capped && meta ? `Total ${at.total} (teto)` : `Total ${at.total}`); + if (hasTeto) partes.push(`Teto ${meta.teto}`); + if (meta && meta.bonus) partes.push(meta.bonus); + partes.push(capped ? `Total ${at.total} (teto)` : `Total ${at.total}`); return partes.join(" | "); })()}