diff --git a/backend/src/application/jobs/processar_ranking.py b/backend/src/application/jobs/processar_ranking.py index 58636ea..31d66e0 100644 --- a/backend/src/application/jobs/processar_ranking.py +++ b/backend/src/application/jobs/processar_ranking.py @@ -73,9 +73,7 @@ class ProcessarRankingJob: for doc in docs: try: consultor = await self.consultor_repo._construir_consultor(doc) - self._consultores.append(self._gerar_json_detalhes(consultor)) - except Exception as e: import traceback logger.warning(f"Erro ao processar consultor {doc.get('id')}: {e}") diff --git a/backend/src/domain/services/calculador_pontuacao.py b/backend/src/domain/services/calculador_pontuacao.py index 27dcce1..ae1796e 100644 --- a/backend/src/domain/services/calculador_pontuacao.py +++ b/backend/src/domain/services/calculador_pontuacao.py @@ -90,19 +90,19 @@ class CalculadorPontuacao: bonus = 0 - # Bônus de continuidade (escalonado, não cumulativo) - apenas CONS_ATIVO - # Spec: 8+ anos = 20 pts (era 15, corrigido conforme especificação) - if codigo == "CONS_ATIVO": - if consultoria.anos_consecutivos >= 8: - bonus += 20 - elif consultoria.anos_consecutivos >= 5: + if consultoria.periodo.ativo and criterio.bonus_atualidade: + bonus += criterio.bonus_atualidade + + if consultoria.anos_consecutivos >= 8 and criterio.bonus_continuidade_8anos: + bonus += criterio.bonus_continuidade_8anos + elif codigo == "CONS_ATIVO": + if 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 + 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 @@ -185,7 +185,13 @@ class CalculadorPontuacao: 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) + bonus += min(bonus_recorrencia, criterio.teto_recorrencia) + + if criterio.bonus_recorrencia_participacao > 0: + bonus_participacao = dados["qtd"] * criterio.bonus_recorrencia_participacao + if criterio.teto_recorrencia_participacao > 0: + bonus_participacao = min(bonus_participacao, criterio.teto_recorrencia_participacao) + bonus += bonus_participacao total_bruto = base_total + bonus if criterio.teto > 0: diff --git a/backend/src/domain/value_objects/criterios_pontuacao.py b/backend/src/domain/value_objects/criterios_pontuacao.py index 71204c4..27156ae 100644 --- a/backend/src/domain/value_objects/criterios_pontuacao.py +++ b/backend/src/domain/value_objects/criterios_pontuacao.py @@ -31,6 +31,8 @@ class CriterioPontuacao: bonus_continuidade_8anos: int = 0 bonus_recorrencia_anual: int = 0 teto_recorrencia: int = 0 + bonus_recorrencia_participacao: int = 0 + teto_recorrencia_participacao: int = 0 CRITERIOS: Dict[str, CriterioPontuacao] = { @@ -101,6 +103,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { pontua_tempo=True, multiplicador_tempo=5, teto_tempo=50, + bonus_atualidade=20, bonus_retorno=15, bonus_continuidade_8anos=20, ), @@ -113,6 +116,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { pontua_tempo=True, multiplicador_tempo=5, teto_tempo=50, + bonus_retorno=20, bonus_continuidade_8anos=20, ), "CONS_FALECIDO": CriterioPontuacao( @@ -132,6 +136,17 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { tipo=TipoAtuacao.FUNCAO, base=10, teto=20, + bonus_recorrencia_participacao=2, + teto_recorrencia_participacao=10, + ), + "INSC_INST_AUTOR": CriterioPontuacao( + codigo="INSC_INST_AUTOR", + bloco=Bloco.D, + tipo=TipoAtuacao.FUNCAO, + base=20, + teto=50, + bonus_recorrencia_participacao=5, + teto_recorrencia_participacao=10, ), "INSC_INST": CriterioPontuacao( codigo="INSC_INST", @@ -204,6 +219,13 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { base=50, teto=100, ), + "BOL_BPQ_NIVEL": CriterioPontuacao( + codigo="BOL_BPQ_NIVEL", + bloco=Bloco.D, + tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO, + base=30, + teto=60, + ), "PREMIACAO": CriterioPontuacao( codigo="PREMIACAO", bloco=Bloco.D, @@ -218,6 +240,20 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { base=50, teto=60, ), + "PREMIACAO_GP_AUTOR": CriterioPontuacao( + codigo="PREMIACAO_GP_AUTOR", + bloco=Bloco.D, + tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO, + base=100, + teto=300, + ), + "PREMIACAO_AUTOR": CriterioPontuacao( + codigo="PREMIACAO_AUTOR", + bloco=Bloco.D, + tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO, + base=50, + teto=150, + ), "MENCAO": CriterioPontuacao( codigo="MENCAO", bloco=Bloco.D, @@ -225,12 +261,21 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { base=30, teto=30, ), + "MENCAO_AUTOR": CriterioPontuacao( + codigo="MENCAO_AUTOR", + bloco=Bloco.D, + tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO, + base=30, + teto=90, + ), "EVENTO": CriterioPontuacao( codigo="EVENTO", bloco=Bloco.D, tipo=TipoAtuacao.PARTICIPACAO, base=1, teto=5, + bonus_recorrencia_participacao=1, + teto_recorrencia_participacao=10, ), "PROJ": CriterioPontuacao( codigo="PROJ", @@ -238,6 +283,8 @@ CRITERIOS: Dict[str, CriterioPontuacao] = { tipo=TipoAtuacao.PARTICIPACAO, base=10, teto=40, + bonus_recorrencia_participacao=2, + teto_recorrencia_participacao=10, ), "ORIENT_POS_DOC": CriterioPontuacao( codigo="ORIENT_POS_DOC", diff --git a/backend/src/infrastructure/ranking_store.py b/backend/src/infrastructure/ranking_store.py index 8034db0..3de58a2 100644 --- a/backend/src/infrastructure/ranking_store.py +++ b/backend/src/infrastructure/ranking_store.py @@ -51,6 +51,15 @@ def extrair_selos_entry(detalhes: Dict[str, Any]) -> Set[str]: tipo_prem = "PREMIO" elif codigo == "MENCAO": tipo_prem = "MENCAO" + elif codigo == "PREMIACAO_GP_AUTOR": + selos.add("AUTOR_GP") + continue + elif codigo == "PREMIACAO_AUTOR": + selos.add("AUTOR_PREMIO") + continue + elif codigo == "MENCAO_AUTOR": + selos.add("AUTOR_MENCAO") + continue else: continue diff --git a/backend/src/infrastructure/repositories/consultor_repository_impl.py b/backend/src/infrastructure/repositories/consultor_repository_impl.py index a58415f..99149d7 100644 --- a/backend/src/infrastructure/repositories/consultor_repository_impl.py +++ b/backend/src/infrastructure/repositories/consultor_repository_impl.py @@ -262,7 +262,7 @@ class ConsultorRepositoryImpl(ConsultorRepository): ano = inicio.year if inicio else datetime.now().year is_institucional = "coordenador" in tipo_part.lower() or "ppg" in tipo_part.lower() - codigo = "INSC_INST" if is_institucional else "INSC_AUTOR" + codigo = "INSC_INST_AUTOR" if is_institucional else "INSC_AUTOR" inscricoes.append(Inscricao( codigo=codigo, @@ -330,12 +330,23 @@ class ConsultorRepositoryImpl(ConsultorRepository): nome_lower = str(nome_premio).lower() tipo_norm = self._inferir_premiacao_tipo(f"{tipo_lower} {nome_lower}") - if tipo_norm == "GP": - codigo = "PREMIACAO" - elif tipo_norm == "MENCAO": - codigo = "MENCAO" + papel_lower = str(papel or "").lower() + is_autor = "autor" in papel_lower + + if is_autor: + if tipo_norm == "GP": + codigo = "PREMIACAO_GP_AUTOR" + elif tipo_norm == "MENCAO": + codigo = "MENCAO_AUTOR" + else: + codigo = "PREMIACAO_AUTOR" else: - codigo = "PREMIACAO_GP" + if tipo_norm == "GP": + codigo = "PREMIACAO" + elif tipo_norm == "MENCAO": + codigo = "MENCAO" + else: + codigo = "PREMIACAO_GP" premiacoes.append(Premiacao( codigo=codigo, @@ -358,7 +369,9 @@ class ConsultorRepositoryImpl(ConsultorRepository): area = dados.get("areaConhecimento", "") or "" nivel_lower = nivel.lower() - if "1d" in nivel_lower or "2" in nivel_lower: + if not nivel_lower: + codigo = "BOL_BPQ_NIVEL" + elif "1d" in nivel_lower or "2" in nivel_lower: codigo = "BOL_BPQ_INT" else: codigo = "BOL_BPQ_SUP" diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index 3ed39d6..db70b69 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -55,9 +55,9 @@ const gerarSelos = (consultor) => { const gerarSelosPorPapel = (papel, seloGP, seloPremio, seloMencao, hintPrefix) => { const lista = premiacoes.filter((p) => (p.papel || '').toString().toLowerCase() === papel.toLowerCase()); - const gp = lista.filter((p) => p.codigo === 'PREMIACAO').length; - const premio = lista.filter((p) => p.codigo === 'PREMIACAO_GP').length; - const mencao = lista.filter((p) => p.codigo === 'MENCAO').length; + const gp = lista.filter((p) => ['PREMIACAO', 'PREMIACAO_GP_AUTOR'].includes(p.codigo)).length; + const premio = lista.filter((p) => ['PREMIACAO_GP', 'PREMIACAO_AUTOR'].includes(p.codigo)).length; + const mencao = lista.filter((p) => ['MENCAO', 'MENCAO_AUTOR'].includes(p.codigo)).length; if (gp > 0) selos.push({ ...seloGP, qtd: gp, hint: `${hintPrefix} - Grande Prêmio` }); if (premio > 0) selos.push({ ...seloPremio, qtd: premio, hint: `${hintPrefix} - Prêmio` }); @@ -104,7 +104,7 @@ const SelosBadges = ({ selos, compacto = false }) => { > {selo.icone} {!compacto && {selo.label}} - {selo.qtd > 1 && {selo.qtd}} + {!compacto && {selo.qtd || 1}} ))} {selosOcultos > 0 && ( @@ -137,11 +137,14 @@ const PONTOS_BASE = { CA: 200, CAJ: 150, CAJ_MP: 120, CAM: 100, CONS_ATIVO: 150, CONS_HIST: 100, CONS_FALECIDO: 100, INSC_AUTOR: 10, INSC_INST: 30, + INSC_INST_AUTOR: 20, AVAL_COMIS_PREMIO: 30, AVAL_COMIS_GP: 50, COORD_COMIS_PREMIO: 50, COORD_COMIS_GP: 60, PREMIACAO: 100, PREMIACAO_GP: 50, MENCAO: 30, + PREMIACAO_GP_AUTOR: 100, PREMIACAO_AUTOR: 50, MENCAO_AUTOR: 30, BOL_BPQ_SUP: 30, BOL_BPQ_INT: 50, BOL_BPQ_SUPERIOR: 30, BOL_BPQ_INTERMEDIARIO: 50, + BOL_BPQ_NIVEL: 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, @@ -149,8 +152,9 @@ const PONTOS_BASE = { }; const TETOS = { - INSC_AUTOR: { teto: 20, doc: '3.3 Inscrições' }, + INSC_AUTOR: { teto: 20, doc: '3.3 Inscrições', bonus: '+2/participação (max 10)' }, INSC_INST: { teto: 60, doc: '3.3 Inscrições' }, + INSC_INST_AUTOR: { teto: 50, doc: '3.3 Inscrições', bonus: '+5/participação (max 10)' }, 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)' }, @@ -158,12 +162,16 @@ const TETOS = { PREMIACAO: { teto: 180, doc: '3.4 Premiações e Bolsas' }, PREMIACAO_GP: { teto: 60, doc: '3.4 Premiações e Bolsas' }, MENCAO: { teto: 30, doc: '3.4 Premiações e Bolsas' }, - EVENTO: { teto: 5, doc: '3.5 Participações Acadêmicas' }, - PROJ: { teto: 40, doc: '3.5 Participações Acadêmicas' }, + PREMIACAO_GP_AUTOR: { teto: 300, doc: '3.4 Premiações e Bolsas' }, + PREMIACAO_AUTOR: { teto: 150, doc: '3.4 Premiações e Bolsas' }, + MENCAO_AUTOR: { teto: 90, doc: '3.4 Premiações e Bolsas' }, + EVENTO: { teto: 5, doc: '3.5 Participações Acadêmicas', bonus: '+1/participação (max 10)' }, + PROJ: { teto: 40, doc: '3.5 Participações Acadêmicas', bonus: '+2/participação (max 10)' }, BOL_BPQ_SUP: { teto: 60, doc: '3.4 Premiações e Bolsas' }, BOL_BPQ_INT: { teto: 100, doc: '3.4 Premiações e Bolsas' }, BOL_BPQ_SUPERIOR: { teto: 60, doc: '3.4 Premiações e Bolsas' }, BOL_BPQ_INTERMEDIARIO: { teto: 100, doc: '3.4 Premiações e Bolsas' }, + BOL_BPQ_NIVEL: { teto: 60, doc: '3.4 Premiações e Bolsas' }, ORIENT_POS_DOC: { teto: 100, doc: '3.5 Participações Acadêmicas' }, ORIENT_TESE: { teto: 50, doc: '3.5 Participações Acadêmicas' }, ORIENT_DISS: { teto: 25, doc: '3.5 Participações Acadêmicas' },