feat(selos): implementar sistema completo de selos para consultores

- Adicionar coluna SELOS na tabela TB_RANKING_CONSULTOR (migration v1.2)
- Criar script popular_selos.py para popular/atualizar selos em batch
- Simplificar para 22 selos com dados reais (remover selos sem dados)
- Adicionar selo IDIOMA_MULTILINGUE para consultores com 3+ idiomas
- Corrigir filtro de selos com query LIKE exata (evitar matches parciais)
- Alinhar ícones entre FiltroSelos e ConsultorCard
- Reorganizar filtro em 7 categorias: Coordenação, Consultoria, Avaliações,
  Premiações, Orientações, Participações, Características

Selos disponíveis: CA, CAJ, CAJ_MP, CAM, PRESID_CAMARA, CONS_ATIVO,
AVAL_COMIS, COORD_COMIS, AUTOR_GP, AUTOR_PREMIO, AUTOR_MENCAO,
ORIENT_GP, ORIENT_PREMIO, ORIENT_MENCAO, COORIENT_GP, COORIENT_PREMIO,
COORIENT_MENCAO, ORIENT_TESE, ORIENT_DISS, EVENTO, PROJ, IDIOMA_MULTILINGUE
This commit is contained in:
Frederico Castro
2025-12-27 21:08:22 -03:00
parent edb4e00880
commit 13ccfb02d3
8 changed files with 361 additions and 130 deletions

View File

@@ -1,9 +1,10 @@
from typing import List, Optional, Dict, Any
from typing import List, Optional, Dict, Any, Set
from datetime import datetime
import json
from ...domain.entities.consultor_ranking import ConsultorRanking
from .client import OracleClient
from ..ranking_store import extrair_selos_entry
class RankingOracleRepository:
@@ -24,17 +25,19 @@ class RankingOracleRepository:
INSERT INTO TB_RANKING_CONSULTOR (
ID_PESSOA, NOME, PONTUACAO_TOTAL,
COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, COMPONENTE_E,
ATIVO, ANOS_ATUACAO, JSON_DETALHES, DT_CALCULO
ATIVO, ANOS_ATUACAO, JSON_DETALHES, SELOS, DT_CALCULO
) VALUES (
:id_pessoa, :nome, :pontuacao_total,
:componente_a, :componente_b, :componente_c, :componente_d, :componente_e,
:ativo, :anos_atuacao, :json_detalhes, CURRENT_TIMESTAMP
:ativo, :anos_atuacao, :json_detalhes, :selos, CURRENT_TIMESTAMP
)
"""
batch_data = []
for consultor in consultores:
json_str = json.dumps(consultor, ensure_ascii=False)
selos_set = extrair_selos_entry(consultor)
selos_str = ",".join(sorted(selos_set)) if selos_set else None
batch_data.append({
"id_pessoa": int(consultor["id_pessoa"]),
"nome": str(consultor.get("nome", ""))[:500],
@@ -46,7 +49,8 @@ class RankingOracleRepository:
"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
"json_detalhes": json_str,
"selos": selos_str
})
with self.client.get_connection() as conn:
@@ -66,7 +70,8 @@ class RankingOracleRepository:
self,
page: int = 1,
size: int = 50,
filtro_ativo: Optional[bool] = None
filtro_ativo: Optional[bool] = None,
filtro_selos: Optional[List[str]] = None
) -> List[ConsultorRanking]:
"""
Busca ranking paginado ordenado por posição.
@@ -79,16 +84,26 @@ class RankingOracleRepository:
offset = (page - 1) * size
limit_end = offset + size
where_clause = ""
where_clauses = []
params = {
"offset": offset,
"limit_end": limit_end,
}
if filtro_ativo is not None:
where_clause = "AND ATIVO = :ativo"
where_clauses.append("ATIVO = :ativo")
params["ativo"] = "S" if filtro_ativo else "N"
if filtro_selos:
for i, selo in enumerate(filtro_selos):
param_name = f"selo_{i}"
where_clauses.append(f"((',' || SELOS || ',') LIKE '%,' || :{param_name} || ',%')")
params[param_name] = selo
where_clause = ""
if where_clauses:
where_clause = "AND " + " AND ".join(where_clauses)
query = f"""
SELECT * FROM (
SELECT
@@ -142,17 +157,31 @@ class RankingOracleRepository:
return consultores
def contar_total(self, filtro_ativo: Optional[bool] = None) -> int:
def contar_total(
self,
filtro_ativo: Optional[bool] = None,
filtro_selos: Optional[List[str]] = None
) -> int:
"""
Conta total de consultores no ranking.
"""
where_clause = ""
where_clauses = []
params = {}
if filtro_ativo is not None:
where_clause = "WHERE ATIVO = :ativo"
where_clauses.append("ATIVO = :ativo")
params["ativo"] = "S" if filtro_ativo else "N"
if filtro_selos:
for i, selo in enumerate(filtro_selos):
param_name = f"selo_{i}"
where_clauses.append(f"((',' || SELOS || ',') LIKE '%,' || :{param_name} || ',%')")
params[param_name] = selo
where_clause = ""
if where_clauses:
where_clause = "WHERE " + " AND ".join(where_clauses)
query = f"SELECT COUNT(*) AS TOTAL FROM TB_RANKING_CONSULTOR {where_clause}"
results = self.client.executar_query(query, params)

View File

@@ -7,9 +7,14 @@ from typing import Any, Dict, List, Optional, Set, Tuple
SELOS_DISPONIVEIS = [
"CA",
"CAJ",
"CAJ_MP",
"CAM",
"PRESID_CAMARA",
"COORD_PPG",
"BPQ",
"CONS_ATIVO",
"AVAL_COMIS",
"COORD_COMIS",
"AUTOR_GP",
"AUTOR_PREMIO",
"AUTOR_MENCAO",
@@ -19,12 +24,11 @@ SELOS_DISPONIVEIS = [
"COORIENT_GP",
"COORIENT_PREMIO",
"COORIENT_MENCAO",
"ORIENT_POS_DOC",
"ORIENT_TESE",
"ORIENT_DISS",
"CO_ORIENT_POS_DOC",
"CO_ORIENT_TESE",
"CO_ORIENT_DISS",
"EVENTO",
"PROJ",
"IDIOMA_MULTILINGUE",
]
@@ -32,46 +36,75 @@ def extrair_selos_entry(detalhes: Dict[str, Any]) -> Set[str]:
selos = set()
for c in detalhes.get("coordenacoes_capes", []):
codigo = c.get("codigo", "")
if codigo == "CA":
selos.add("CA")
elif codigo == "CAJ":
selos.add("CAJ")
elif codigo == "CAJ_MP":
selos.add("CAJ_MP")
elif codigo == "CAM":
selos.add("CAM")
if c.get("presidente"):
selos.add("PRESID_CAMARA")
if detalhes.get("coordenador_ppg"):
selos.add("COORD_PPG")
consultoria = detalhes.get("consultoria")
if consultoria and consultoria.get("codigo") == "CONS_ATIVO":
selos.add("CONS_ATIVO")
if detalhes.get("bolsas_cnpq"):
selos.add("BPQ")
for aval in detalhes.get("avaliacoes_comissao", []):
codigo = aval.get("codigo", "")
if "COORD" in codigo:
selos.add("COORD_COMIS")
elif "AVAL" in codigo:
selos.add("AVAL_COMIS")
for part in detalhes.get("participacoes", []):
codigo = part.get("codigo", "")
if codigo == "EVENTO":
selos.add("EVENTO")
elif codigo == "PROJ":
selos.add("PROJ")
for prem in detalhes.get("premiacoes", []):
codigo = prem.get("codigo", "")
papel = prem.get("papel", "").lower() if prem.get("papel") else ""
is_orientador = "orientador" in papel
is_coorientador = "coorientador" in papel or "co-orientador" in papel
if codigo == "PREMIACAO_GP_AUTOR":
selos.add("AUTOR_GP")
if is_coorientador:
selos.add("COORIENT_GP")
elif is_orientador:
selos.add("ORIENT_GP")
else:
selos.add("AUTOR_GP")
elif codigo == "PREMIACAO_AUTOR":
selos.add("AUTOR_PREMIO")
if is_coorientador:
selos.add("COORIENT_PREMIO")
elif is_orientador:
selos.add("ORIENT_PREMIO")
else:
selos.add("AUTOR_PREMIO")
elif codigo == "MENCAO_AUTOR":
selos.add("AUTOR_MENCAO")
if is_coorientador:
selos.add("COORIENT_MENCAO")
elif is_orientador:
selos.add("ORIENT_MENCAO")
else:
selos.add("AUTOR_MENCAO")
for orient in detalhes.get("orientacoes", []):
codigo = orient.get("codigo", "")
is_coorient = orient.get("coorientacao", False)
if is_coorient:
if codigo in ("CO_ORIENT_POS_DOC", "CO_ORIENT_POS_DOC_PREM"):
selos.add("CO_ORIENT_POS_DOC")
elif codigo in ("CO_ORIENT_TESE", "CO_ORIENT_TESE_PREM"):
selos.add("CO_ORIENT_TESE")
elif codigo in ("CO_ORIENT_DISS", "CO_ORIENT_DISS_PREM"):
selos.add("CO_ORIENT_DISS")
else:
if codigo in ("ORIENT_POS_DOC", "ORIENT_POS_DOC_PREM"):
selos.add("ORIENT_POS_DOC")
elif codigo in ("ORIENT_TESE", "ORIENT_TESE_PREM"):
selos.add("ORIENT_TESE")
elif codigo in ("ORIENT_DISS", "ORIENT_DISS_PREM"):
selos.add("ORIENT_DISS")
if codigo in ("ORIENT_TESE", "ORIENT_TESE_PREM"):
selos.add("ORIENT_TESE")
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", "")
is_coorient = orient.get("coorientacao", False)
if is_coorient:
if prem_tipo == "GP":
selos.add("COORIENT_GP")
@@ -87,6 +120,10 @@ def extrair_selos_entry(detalhes: Dict[str, Any]) -> Set[str]:
elif prem_tipo == "MENCAO":
selos.add("ORIENT_MENCAO")
idiomas = detalhes.get("idiomas", [])
if len(idiomas) >= 3:
selos.add("IDIOMA_MULTILINGUE")
return selos

View File

@@ -214,14 +214,20 @@ async def ranking_paginado(
if not oracle_repo:
raise HTTPException(status_code=503, detail="Oracle não configurado")
total = oracle_repo.contar_total(filtro_ativo=ativo)
selos_lista = [s.strip() for s in selos.split(",") if s.strip()] if selos else None
total = oracle_repo.contar_total(filtro_ativo=ativo, filtro_selos=selos_lista)
if total == 0:
if selos_lista:
return RankingPaginadoResponseSchema(
total=0, page=page, size=size, total_pages=0, consultores=[]
)
raise HTTPException(
status_code=503,
detail="Ranking ainda não foi processado. Execute POST /api/v1/ranking/processar.",
)
consultores = oracle_repo.buscar_paginado(page=page, size=size, filtro_ativo=ativo)
consultores = oracle_repo.buscar_paginado(page=page, size=size, filtro_ativo=ativo, filtro_selos=selos_lista)
total_pages = (total + size - 1) // size
consultores_schema = []