diff --git a/backend/sql/migrations/v1.1_add_componente_e.sql b/backend/sql/migrations/v1.1_add_componente_e.sql new file mode 100644 index 0000000..ec54796 --- /dev/null +++ b/backend/sql/migrations/v1.1_add_componente_e.sql @@ -0,0 +1,7 @@ +-- Migration V1.1: Adicionar COMPONENTE_E (Bloco E: Coordenação PPG) +-- Data: 2025-12-23 +-- Descrição: Adiciona coluna COMPONENTE_E para suportar a estrutura de 5 blocos conforme PDF V1.0 + +ALTER TABLE TB_RANKING_CONSULTOR ADD (COMPONENTE_E NUMBER(10,2) DEFAULT 0); + +COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_E IS 'Bloco E: Coordenação de PPG (PPG_COORD - V1: dados incompletos)'; diff --git a/backend/sql/schema_ranking.sql b/backend/sql/schema_ranking.sql index 87d54a2..6168580 100644 --- a/backend/sql/schema_ranking.sql +++ b/backend/sql/schema_ranking.sql @@ -12,6 +12,7 @@ CREATE TABLE TB_RANKING_CONSULTOR ( COMPONENTE_B NUMBER(10,2) DEFAULT 0, COMPONENTE_C NUMBER(10,2) DEFAULT 0, COMPONENTE_D NUMBER(10,2) DEFAULT 0, + COMPONENTE_E NUMBER(10,2) DEFAULT 0, ATIVO CHAR(1) DEFAULT 'N', ANOS_ATUACAO NUMBER(5,1) DEFAULT 0, DT_CALCULO TIMESTAMP DEFAULT CURRENT_TIMESTAMP, @@ -54,7 +55,8 @@ SELECT AVG(COMPONENTE_A) AS MEDIA_COMP_A, AVG(COMPONENTE_B) AS MEDIA_COMP_B, AVG(COMPONENTE_C) AS MEDIA_COMP_C, - AVG(COMPONENTE_D) AS MEDIA_COMP_D + AVG(COMPONENTE_D) AS MEDIA_COMP_D, + AVG(COMPONENTE_E) AS MEDIA_COMP_E FROM TB_RANKING_CONSULTOR; -- View para distribuição por faixas de pontuação @@ -92,11 +94,12 @@ COMMENT ON TABLE TB_RANKING_CONSULTOR IS 'Tabela de ranking pré-calculado de co COMMENT ON COLUMN TB_RANKING_CONSULTOR.ID_PESSOA IS 'ID da pessoa no sistema AtuaCAPES'; COMMENT ON COLUMN TB_RANKING_CONSULTOR.NOME IS 'Nome completo do consultor'; COMMENT ON COLUMN TB_RANKING_CONSULTOR.POSICAO IS 'Posição no ranking (1 = primeiro lugar)'; -COMMENT ON COLUMN TB_RANKING_CONSULTOR.PONTUACAO_TOTAL IS 'Pontuação total calculada (soma dos 4 componentes)'; -COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_A IS 'Pontuação do Componente A (Coordenação CAPES)'; -COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_B IS 'Pontuação do Componente B (Coordenação PPG)'; -COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_C IS 'Pontuação do Componente C (Consultoria)'; -COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_D IS 'Pontuação do Componente D (Premiações)'; +COMMENT ON COLUMN TB_RANKING_CONSULTOR.PONTUACAO_TOTAL IS 'Pontuação total calculada (soma dos 5 componentes)'; +COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_A IS 'Bloco A: Coordenação CAPES (CA, CAJ, CAJ_MP, CAM)'; +COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_B IS 'Bloco B: Consultoria (CONS_ATIVO, CONS_HIST, CONS_FALECIDO)'; +COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_C IS 'Bloco C: Avaliações, Premiações, Orientações, Bancas, Inscrições'; +COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_D IS 'Bloco D: Bolsas CNPq, Participações em Eventos/Projetos'; +COMMENT ON COLUMN TB_RANKING_CONSULTOR.COMPONENTE_E IS 'Bloco E: Coordenação de PPG (PPG_COORD - V1: dados incompletos)'; COMMENT ON COLUMN TB_RANKING_CONSULTOR.ATIVO IS 'Indicador se o consultor está ativo (S/N)'; COMMENT ON COLUMN TB_RANKING_CONSULTOR.ANOS_ATUACAO IS 'Anos de atuação do consultor'; COMMENT ON COLUMN TB_RANKING_CONSULTOR.DT_CALCULO IS 'Data/hora do último cálculo'; diff --git a/backend/src/application/jobs/processar_ranking.py b/backend/src/application/jobs/processar_ranking.py index 54f52e8..1095b7c 100644 --- a/backend/src/application/jobs/processar_ranking.py +++ b/backend/src/application/jobs/processar_ranking.py @@ -87,26 +87,18 @@ class ProcessarRankingJob: ) def _gerar_json_detalhes(self, consultor) -> dict: - bloco_b = 0 # reservado no V1 (dados incompletos) pontuacao = consultor.pontuacao.to_dict() if consultor.pontuacao else None - if isinstance(pontuacao, dict): - pontuacao = dict(pontuacao) - pontuacao["bloco_b"] = {"bloco": "B", "total": bloco_b, "atuacoes": []} - pontuacao["pontuacao_total"] = ( - pontuacao.get("pontuacao_total", 0) + bloco_b - if isinstance(pontuacao.get("pontuacao_total"), (int, float)) - else consultor.pontuacao_total + bloco_b - ) return { "id_pessoa": consultor.id_pessoa, "nome": consultor.nome, "posicao": None, - "pontuacao_total": consultor.pontuacao_total + bloco_b, + "pontuacao_total": consultor.pontuacao_total, "bloco_a": consultor.pontuacao_bloco_a, - "bloco_b": bloco_b, + "bloco_b": consultor.pontuacao_bloco_b, "bloco_c": consultor.pontuacao_bloco_c, "bloco_d": consultor.pontuacao_bloco_d, + "bloco_e": consultor.pontuacao_bloco_e, "ativo": consultor.ativo, "anos_atuacao": consultor.anos_atuacao, "coordenador_ppg": consultor.coordenador_ppg, @@ -254,6 +246,7 @@ class ProcessarRankingJob: bloco_b=int(c.get("bloco_b", 0)), bloco_c=int(c.get("bloco_c", 0)), bloco_d=int(c.get("bloco_d", 0)), + bloco_e=int(c.get("bloco_e", 0)), ativo=bool(c.get("ativo", False)), anos_atuacao=float(c.get("anos_atuacao", 0) or 0), detalhes=c, @@ -272,7 +265,7 @@ class ProcessarRankingJob: "pontuacao_media": 0, "pontuacao_maxima": 0, "pontuacao_minima": 0, - "media_componentes": {"a": 0, "b": 0, "c": 0, "d": 0}, + "media_componentes": {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0}, } total = len(entries) @@ -294,6 +287,7 @@ class ProcessarRankingJob: "b": float(round(sum(e.bloco_b for e in entries) / total, 2)), "c": float(round(sum(e.bloco_c for e in entries) / total, 2)), "d": float(round(sum(e.bloco_d for e in entries) / total, 2)), + "e": float(round(sum(e.bloco_e for e in entries) / total, 2)), }, } diff --git a/backend/src/domain/entities/consultor.py b/backend/src/domain/entities/consultor.py index 6194780..c368a8b 100644 --- a/backend/src/domain/entities/consultor.py +++ b/backend/src/domain/entities/consultor.py @@ -162,6 +162,10 @@ class Consultor: def pontuacao_bloco_a(self) -> int: return self.pontuacao.bloco_a.total if self.pontuacao else 0 + @property + def pontuacao_bloco_b(self) -> int: + return self.pontuacao.bloco_b.total if self.pontuacao else 0 + @property def pontuacao_bloco_c(self) -> int: return self.pontuacao.bloco_c.total if self.pontuacao else 0 @@ -169,3 +173,7 @@ class Consultor: @property def pontuacao_bloco_d(self) -> int: return self.pontuacao.bloco_d.total if self.pontuacao else 0 + + @property + def pontuacao_bloco_e(self) -> int: + return self.pontuacao.bloco_e.total if self.pontuacao else 0 diff --git a/backend/src/domain/entities/consultor_ranking.py b/backend/src/domain/entities/consultor_ranking.py index 6ca0612..b826394 100644 --- a/backend/src/domain/entities/consultor_ranking.py +++ b/backend/src/domain/entities/consultor_ranking.py @@ -13,6 +13,7 @@ class ConsultorRanking: componente_b: float componente_c: float componente_d: float + componente_e: float ativo: bool anos_atuacao: float dt_calculo: datetime diff --git a/backend/src/domain/services/calculador_pontuacao.py b/backend/src/domain/services/calculador_pontuacao.py index ae1796e..e30ff9a 100644 --- a/backend/src/domain/services/calculador_pontuacao.py +++ b/backend/src/domain/services/calculador_pontuacao.py @@ -70,14 +70,14 @@ class CalculadorPontuacao: return PontuacaoBloco(bloco="A", atuacoes=atuacoes) @staticmethod - def calcular_bloco_c(consultoria: Consultoria) -> PontuacaoBloco: + def calcular_bloco_b(consultoria: Consultoria) -> PontuacaoBloco: if not consultoria: - return PontuacaoBloco(bloco="C", atuacoes=[]) + return PontuacaoBloco(bloco="B", atuacoes=[]) codigo = consultoria.codigo criterio = get_criterio(codigo) if not criterio: - return PontuacaoBloco(bloco="C", atuacoes=[]) + return PontuacaoBloco(bloco="B", atuacoes=[]) base = criterio.base @@ -95,11 +95,6 @@ class CalculadorPontuacao: 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 if consultoria.retornos > 0 and criterio.bonus_retorno: bonus += criterio.bonus_retorno @@ -116,15 +111,13 @@ class CalculadorPontuacao: quantidade=1, )] - return PontuacaoBloco(bloco="C", atuacoes=atuacoes) + return PontuacaoBloco(bloco="B", atuacoes=atuacoes) @staticmethod - def calcular_bloco_d( + def calcular_bloco_c( inscricoes: List[Inscricao], avaliacoes: List[AvaliacaoComissao], premiacoes: List[Premiacao], - bolsas: List[BolsaCNPQ], - participacoes: List[Participacao], orientacoes: List[Orientacao], membros_banca: List[MembroBanca], ) -> PontuacaoBloco: @@ -133,13 +126,13 @@ class CalculadorPontuacao: for insc in inscricoes: criterio = get_criterio(insc.codigo) - if criterio: + if criterio and criterio.bloco == Bloco.C: 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: + if criterio and criterio.bloco == Bloco.C: totais_por_codigo[aval.codigo]["base"] += criterio.base totais_por_codigo[aval.codigo]["qtd"] += 1 if hasattr(aval, 'ano') and aval.ano: @@ -147,31 +140,19 @@ class CalculadorPontuacao: for prem in premiacoes: criterio = get_criterio(prem.codigo) - if criterio: + if criterio and criterio.bloco == Bloco.C: 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: + if criterio and criterio.bloco == Bloco.C: 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: + if criterio and criterio.bloco == Bloco.C: totais_por_codigo[mb.codigo]["base"] += criterio.base totais_por_codigo[mb.codigo]["qtd"] += 1 @@ -208,24 +189,100 @@ class CalculadorPontuacao: quantidade=dados["qtd"], )) + return PontuacaoBloco(bloco="C", atuacoes=atuacoes) + + @staticmethod + def calcular_bloco_d( + bolsas: List[BolsaCNPQ], + participacoes: List[Participacao], + ) -> PontuacaoBloco: + atuacoes = [] + totais_por_codigo: Dict[str, Dict] = defaultdict(lambda: {"base": 0, "qtd": 0}) + + for bolsa in bolsas: + criterio = get_criterio(bolsa.codigo) + if criterio and criterio.bloco == Bloco.D: + 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 and criterio.bloco == Bloco.D: + totais_por_codigo[part.codigo]["base"] += criterio.base + totais_por_codigo[part.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_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: + 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) + @staticmethod + def calcular_bloco_e(coordenador_ppg: bool) -> PontuacaoBloco: + if not coordenador_ppg: + return PontuacaoBloco(bloco="E", atuacoes=[]) + + criterio = get_criterio("PPG_COORD") + if not criterio: + return PontuacaoBloco(bloco="E", atuacoes=[]) + + atuacoes = [PontuacaoAtuacao( + codigo="PPG_COORD", + base=criterio.base, + tempo=0, + bonus=0, + total=criterio.base, + quantidade=1, + )] + + return PontuacaoBloco(bloco="E", 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( + bloco_b = cls.calcular_bloco_b(consultor.consultoria) + bloco_c = cls.calcular_bloco_c( 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, ) + bloco_d = cls.calcular_bloco_d( + bolsas=consultor.bolsas_cnpq, + participacoes=consultor.participacoes, + ) + bloco_e = cls.calcular_bloco_e(consultor.coordenador_ppg) return PontuacaoCompleta( bloco_a=bloco_a, + bloco_b=bloco_b, bloco_c=bloco_c, bloco_d=bloco_d, + bloco_e=bloco_e, ) diff --git a/backend/src/domain/value_objects/pontuacao.py b/backend/src/domain/value_objects/pontuacao.py index 7abe33a..c533cbf 100644 --- a/backend/src/domain/value_objects/pontuacao.py +++ b/backend/src/domain/value_objects/pontuacao.py @@ -42,18 +42,28 @@ class PontuacaoBloco: @dataclass(frozen=True) class PontuacaoCompleta: bloco_a: PontuacaoBloco + bloco_b: PontuacaoBloco bloco_c: PontuacaoBloco bloco_d: PontuacaoBloco + bloco_e: PontuacaoBloco @property def total(self) -> int: - return self.bloco_a.total + self.bloco_c.total + self.bloco_d.total + return ( + self.bloco_a.total + + self.bloco_b.total + + self.bloco_c.total + + self.bloco_d.total + + self.bloco_e.total + ) def to_dict(self) -> Dict: return { "bloco_a": self.bloco_a.to_dict(), + "bloco_b": self.bloco_b.to_dict(), "bloco_c": self.bloco_c.to_dict(), "bloco_d": self.bloco_d.to_dict(), + "bloco_e": self.bloco_e.to_dict(), "pontuacao_total": self.total, } diff --git a/backend/src/infrastructure/oracle/ranking_repository.py b/backend/src/infrastructure/oracle/ranking_repository.py index 8393ac9..25f6d93 100644 --- a/backend/src/infrastructure/oracle/ranking_repository.py +++ b/backend/src/infrastructure/oracle/ranking_repository.py @@ -23,11 +23,11 @@ class RankingOracleRepository: insert_sql = """ INSERT INTO TB_RANKING_CONSULTOR ( ID_PESSOA, NOME, PONTUACAO_TOTAL, - COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, + COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, COMPONENTE_E, ATIVO, ANOS_ATUACAO, JSON_DETALHES, DT_CALCULO ) VALUES ( :id_pessoa, :nome, :pontuacao_total, - :componente_a, :componente_b, :componente_c, :componente_d, + :componente_a, :componente_b, :componente_c, :componente_d, :componente_e, :ativo, :anos_atuacao, :json_detalhes, CURRENT_TIMESTAMP ) """ @@ -43,6 +43,7 @@ class RankingOracleRepository: "componente_b": int(consultor.get("bloco_b") or consultor.get("componente_b") or 0), "componente_c": int(consultor.get("bloco_c") or consultor.get("componente_c") or 0), "componente_d": int(consultor.get("bloco_d") or consultor.get("componente_d") or 0), + "componente_e": int(consultor.get("bloco_e") or consultor.get("componente_e") or 0), "ativo": "S" if consultor.get("ativo") else "N", "anos_atuacao": float(consultor.get("anos_atuacao") or 0), "json_detalhes": json_str @@ -99,6 +100,7 @@ class RankingOracleRepository: COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, + COMPONENTE_E, ATIVO, ANOS_ATUACAO, DT_CALCULO, @@ -130,6 +132,7 @@ class RankingOracleRepository: componente_b=float(r["COMPONENTE_B"]), componente_c=float(r["COMPONENTE_C"]), componente_d=float(r["COMPONENTE_D"]), + componente_e=float(r["COMPONENTE_E"]), ativo=r["ATIVO"] == "S", anos_atuacao=float(r["ANOS_ATUACAO"]), dt_calculo=r["DT_CALCULO"], @@ -201,6 +204,7 @@ class RankingOracleRepository: COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, + COMPONENTE_E, ATIVO, ANOS_ATUACAO, DT_CALCULO, @@ -231,6 +235,7 @@ class RankingOracleRepository: componente_b=float(r["COMPONENTE_B"]), componente_c=float(r["COMPONENTE_C"]), componente_d=float(r["COMPONENTE_D"]), + componente_e=float(r["COMPONENTE_E"]), ativo=r["ATIVO"] == "S", anos_atuacao=float(r["ANOS_ATUACAO"]), dt_calculo=r["DT_CALCULO"], @@ -268,7 +273,8 @@ class RankingOracleRepository: AVG(COMPONENTE_A) AS MEDIA_COMP_A, AVG(COMPONENTE_B) AS MEDIA_COMP_B, AVG(COMPONENTE_C) AS MEDIA_COMP_C, - AVG(COMPONENTE_D) AS MEDIA_COMP_D + AVG(COMPONENTE_D) AS MEDIA_COMP_D, + AVG(COMPONENTE_E) AS MEDIA_COMP_E FROM TB_RANKING_CONSULTOR """ @@ -290,7 +296,8 @@ class RankingOracleRepository: "a": float(r["MEDIA_COMP_A"]) if r["MEDIA_COMP_A"] else 0, "b": float(r["MEDIA_COMP_B"]) if r["MEDIA_COMP_B"] else 0, "c": float(r["MEDIA_COMP_C"]) if r["MEDIA_COMP_C"] else 0, - "d": float(r["MEDIA_COMP_D"]) if r["MEDIA_COMP_D"] else 0 + "d": float(r["MEDIA_COMP_D"]) if r["MEDIA_COMP_D"] else 0, + "e": float(r["MEDIA_COMP_E"]) if r["MEDIA_COMP_E"] else 0 } } diff --git a/backend/src/infrastructure/ranking_store.py b/backend/src/infrastructure/ranking_store.py index c41d259..fd2d67e 100644 --- a/backend/src/infrastructure/ranking_store.py +++ b/backend/src/infrastructure/ranking_store.py @@ -56,20 +56,37 @@ def extrair_selos_entry(detalhes: Dict[str, Any]) -> Set[str]: is_coorient = orient.get("coorientacao", False) if is_coorient: - if codigo == "CO_ORIENT_POS_DOC": + if codigo in ("CO_ORIENT_POS_DOC", "CO_ORIENT_POS_DOC_PREM"): selos.add("CO_ORIENT_POS_DOC") - elif codigo == "CO_ORIENT_TESE": + elif codigo in ("CO_ORIENT_TESE", "CO_ORIENT_TESE_PREM"): selos.add("CO_ORIENT_TESE") - elif codigo == "CO_ORIENT_DISS": + elif codigo in ("CO_ORIENT_DISS", "CO_ORIENT_DISS_PREM"): selos.add("CO_ORIENT_DISS") else: - if codigo == "ORIENT_POS_DOC": + if codigo in ("ORIENT_POS_DOC", "ORIENT_POS_DOC_PREM"): selos.add("ORIENT_POS_DOC") - elif codigo == "ORIENT_TESE": + elif codigo in ("ORIENT_TESE", "ORIENT_TESE_PREM"): selos.add("ORIENT_TESE") - elif codigo == "ORIENT_DISS": + elif codigo in ("ORIENT_DISS", "ORIENT_DISS_PREM"): selos.add("ORIENT_DISS") + if orient.get("premiada") or "_PREM" in codigo: + prem_tipo = orient.get("premiacao_tipo", "") + if is_coorient: + if prem_tipo == "GP": + selos.add("COORIENT_GP") + elif prem_tipo == "PREMIO": + selos.add("COORIENT_PREMIO") + elif prem_tipo == "MENCAO": + selos.add("COORIENT_MENCAO") + else: + if prem_tipo == "GP": + selos.add("ORIENT_GP") + elif prem_tipo == "PREMIO": + selos.add("ORIENT_PREMIO") + elif prem_tipo == "MENCAO": + selos.add("ORIENT_MENCAO") + return selos @@ -83,6 +100,7 @@ class RankingEntry: bloco_b: int bloco_c: int bloco_d: int + bloco_e: int ativo: bool anos_atuacao: float detalhes: Dict[str, Any] diff --git a/backend/src/infrastructure/repositories/consultor_repository_impl.py b/backend/src/infrastructure/repositories/consultor_repository_impl.py index 0d6dbb8..1292db5 100644 --- a/backend/src/infrastructure/repositories/consultor_repository_impl.py +++ b/backend/src/infrastructure/repositories/consultor_repository_impl.py @@ -460,12 +460,12 @@ class ConsultorRepositoryImpl(ConsultorRepository): ano = inicio.year if inicio else None nivel_lower = nivel.lower() - if "pós-doc" in nivel_lower or "pos-doc" in nivel_lower or "posdoc" in nivel_lower: - codigo = "ORIENT_POS_DOC" + if "pós-doutorado" in nivel_lower or "pos-doutorado" in nivel_lower or "pós-doc" in nivel_lower or "pos-doc" in nivel_lower or "posdoc" in nivel_lower: + codigo = "ORIENT_POS_DOC_PREM" if premiada else "ORIENT_POS_DOC" elif "tese" in nivel_lower or "doutorado" in nivel_lower: - codigo = "ORIENT_TESE" + codigo = "ORIENT_TESE_PREM" if premiada else "ORIENT_TESE" else: - codigo = "ORIENT_DISS" + codigo = "ORIENT_DISS_PREM" if premiada else "ORIENT_DISS" orientacoes.append(Orientacao( codigo=codigo, @@ -504,12 +504,12 @@ class ConsultorRepositoryImpl(ConsultorRepository): ano = inicio.year if inicio else None nivel_lower = nivel.lower() - if "pós-doc" in nivel_lower or "pos-doc" in nivel_lower or "posdoc" in nivel_lower: - codigo = "CO_ORIENT_POS_DOC" + if "pós-doutorado" in nivel_lower or "pos-doutorado" in nivel_lower or "pós-doc" in nivel_lower or "pos-doc" in nivel_lower or "posdoc" in nivel_lower: + codigo = "CO_ORIENT_POS_DOC_PREM" if premiada else "CO_ORIENT_POS_DOC" elif "tese" in nivel_lower or "doutorado" in nivel_lower: - codigo = "CO_ORIENT_TESE" + codigo = "CO_ORIENT_TESE_PREM" if premiada else "CO_ORIENT_TESE" else: - codigo = "CO_ORIENT_DISS" + codigo = "CO_ORIENT_DISS_PREM" if premiada else "CO_ORIENT_DISS" coorientacoes.append(Orientacao( codigo=codigo, diff --git a/backend/src/interface/api/routes.py b/backend/src/interface/api/routes.py index 63cc603..cf42904 100644 --- a/backend/src/interface/api/routes.py +++ b/backend/src/interface/api/routes.py @@ -240,6 +240,7 @@ async def ranking_paginado( bloco_b=float(c.componente_b), bloco_c=float(c.componente_c), bloco_d=float(c.componente_d), + bloco_e=float(c.componente_e), ativo=c.ativo, anos_atuacao=float(c.anos_atuacao), tipos_atuacao=tipos_atuacao, @@ -362,6 +363,7 @@ async def obter_posicao_ranking( bloco_b=0, bloco_c=0, bloco_d=0, + bloco_e=0, ativo=False, encontrado=False, ) @@ -376,6 +378,7 @@ async def obter_posicao_ranking( bloco_b=float(entry.componente_b), bloco_c=float(entry.componente_c), bloco_d=float(entry.componente_d), + bloco_e=float(entry.componente_e), ativo=entry.ativo, encontrado=True, ) diff --git a/backend/src/interface/schemas/ranking_schema.py b/backend/src/interface/schemas/ranking_schema.py index 1b419df..a52a7ee 100644 --- a/backend/src/interface/schemas/ranking_schema.py +++ b/backend/src/interface/schemas/ranking_schema.py @@ -12,6 +12,7 @@ class ConsultorRankingResumoSchema(BaseModel): bloco_b: float bloco_c: float bloco_d: float + bloco_e: float ativo: bool anos_atuacao: float tipos_atuacao: List[str] = [] @@ -91,6 +92,7 @@ class PosicaoRankingSchema(BaseModel): bloco_b: float bloco_c: float bloco_d: float + bloco_e: float ativo: bool encontrado: bool = True diff --git a/frontend/src/components/ConsultorCard.css b/frontend/src/components/ConsultorCard.css index a41c01c..595c0df 100644 --- a/frontend/src/components/ConsultorCard.css +++ b/frontend/src/components/ConsultorCard.css @@ -390,6 +390,11 @@ max-width: calc(33.333% - 0.3rem); } +.score-breakdown-5cols .score-item-wrapper { + flex: 0 0 calc(33.333% - 0.3rem); + max-width: calc(33.333% - 0.3rem); +} + .score-item-wrapper .score-item { cursor: help; transition: transform 150ms ease, border-color 150ms ease; diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index dc28805..a9b7031 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -853,13 +853,17 @@ const FORMULAS = { titulo: 'Coordenacao CAPES', descricao: 'CA=200 | CAJ=150 | CAJ_MP=120 | CAM=100\nTempo: multiplicador por ano (anos completos)\nBônus atualidade (mandato vigente) + Retorno (mandato anterior)', }, - bloco_c: { + bloco_b: { titulo: 'Consultoria', - descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade: 3a=+5, 5a=+10, 8a+=+20 (escalonado)\nRetorno (reativação): +15 (uma vez)', + descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade: 8a+=+20 (escalonado)\nRetorno (reativação): +15 (uma vez)', + }, + bloco_c: { + titulo: 'Avaliacoes/Premiacoes', + descricao: 'Premiações: GP=100 | Prêmio=50 | Menção=30\nAvaliações de Comissão e Coordenação\nInscrições em Prêmios\nOrientações e Bancas de trabalhos premiados', }, bloco_d: { - titulo: 'Premiacoes/Avaliacoes', - descricao: 'Premiações: GP=100 (teto 300) | Prêmio=50 (teto 150) | Menção=30 (teto 90)\nBolsas: BPQ=30 (teto 60)\nInscrições/Avaliações/Comissões/Participações (com tetos por código)\nOrientações e Bancas: apenas selos (sem pontuação)', + titulo: 'Indicadores', + descricao: 'Bolsas CNPq: BPQ Nível Superior/Intermediário\nParticipações: Eventos e Projetos\nOrientações e Bancas (sem premiação)', }, }; @@ -903,11 +907,11 @@ const PontuacaoModal = ({ dados, onClose }) => { const getIcone = () => { if (label?.includes('BLOCO A') || label === 'A') return '🎯'; - if (label?.includes('BLOCO B') || label === 'B') return '🎓'; - if (label?.includes('BLOCO C') || label === 'C') return '💼'; - if (label?.includes('BLOCO D') || label === 'D') return '🏆'; - if (tipo === 'total') return '📊'; - return '📈'; + if (label?.includes('BLOCO B') || label === 'B') return '💼'; + if (label?.includes('BLOCO C') || label === 'C') return '🏆'; + if (label?.includes('BLOCO D') || label === 'D') return '📊'; + if (tipo === 'total') return '📈'; + return '📌'; }; const renderBlocoContent = () => { @@ -1038,7 +1042,10 @@ const PontuacaoModal = ({ dados, onClose }) => {
Fórmula
-
Bloco A + Bloco C + Bloco D
+
Bloco A + Bloco B + Bloco C + Bloco D
+
+ A: Coordenação CAPES | B: Consultoria | C: Avaliações/Premiações | D: Indicadores +
@@ -1116,6 +1123,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio const { consultoria, pontuacao } = consultor; const blocoA = pontuacao?.bloco_a || { total: consultor.bloco_a || 0 }; + const blocoB = pontuacao?.bloco_b || { total: consultor.bloco_b || 0 }; const blocoC = pontuacao?.bloco_c || { total: consultor.bloco_c || 0 }; const blocoD = pontuacao?.bloco_d || { total: consultor.bloco_d || 0 }; const pontuacaoTotal = Number(consultor.pontuacao_total ?? 0); @@ -1216,10 +1224,10 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio

Pontuacao Total

-
+
0 ? 'var(--accent-2)' : 'var(--muted)' }} onClick={() => setPontuacaoModal({ tipo: 'bloco', @@ -1230,12 +1238,24 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio })} /> 0 ? 'var(--gold)' : 'var(--muted)' }} + value={blocoB.total} + label="B" + style={{ color: blocoB.total > 0 ? 'var(--gold)' : 'var(--muted)' }} onClick={() => setPontuacaoModal({ tipo: 'bloco', - label: 'BLOCO C - Consultoria', + label: 'BLOCO B - Consultoria', + value: blocoB.total, + formula: FORMULAS.bloco_b.descricao, + bloco: blocoB + })} + /> + 0 ? 'var(--bronze)' : 'var(--muted)' }} + onClick={() => setPontuacaoModal({ + tipo: 'bloco', + label: 'BLOCO C - Avaliações/Premiações', value: blocoC.total, formula: FORMULAS.bloco_c.descricao, bloco: blocoC @@ -1243,11 +1263,11 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio /> 0 ? 'var(--bronze)' : 'var(--muted)' }} + label="D" + style={{ color: blocoD.total > 0 ? 'var(--silver)' : 'var(--muted)' }} onClick={() => setPontuacaoModal({ tipo: 'bloco', - label: 'BLOCO D - Premiações/Avaliações', + label: 'BLOCO D - Indicadores', value: blocoD.total, formula: FORMULAS.bloco_d.descricao, bloco: blocoD @@ -1270,12 +1290,16 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio )} + {blocoB.atuacoes && blocoB.atuacoes.length > 0 && ( + + )} + {blocoC.atuacoes && blocoC.atuacoes.length > 0 && ( - + )} {blocoD.atuacoes && blocoD.atuacoes.length > 0 && ( - + )}
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 45eedc8..b795755 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -50,6 +50,7 @@ export const rankingService = { bloco_b: c.bloco_b, bloco_c: c.bloco_c, bloco_d: c.bloco_d, + bloco_e: c.bloco_e || 0, ativo: c.ativo, anos_atuacao: anos, veterano: anos >= 10, @@ -61,6 +62,7 @@ export const rankingService = { bloco_b: { total: c.bloco_b, atuacoes: [] }, bloco_c: { total: c.bloco_c, atuacoes: [] }, bloco_d: { total: c.bloco_d, atuacoes: [] }, + bloco_e: { total: c.bloco_e || 0, atuacoes: [] }, }, consultoria: { codigo: consultoria.codigo || null,