feat: extrair docencias PPG e simplificar blocos de pontuacao

Backend:
- Adicionar entidade DocenciaPPG para dados de docencia
- Extrair docencias do Elasticsearch (tipo "Docência")
- Serializar docencias no JSON de detalhes do consultor
- Aumentar batch size de 500 para 2000 para melhor performance

Frontend:
- Remover Bloco B (Coord. PPG) - reservado para V2
- Simplificar formula para: Bloco A + Bloco C + Bloco D
- Filtrar orientacoes/bancas da listagem (sao apenas selos)
- Atualizar Header com nota que PPG_COORD e apenas indicador
- Exibir pontuacao base nos modais de orientacao/banca
This commit is contained in:
Frederico Castro
2025-12-23 04:27:36 -03:00
parent d33695ed65
commit 8799a68c30
11 changed files with 154 additions and 123 deletions

View File

@@ -12,7 +12,7 @@ class RankingOracleRepository:
def inserir_batch(self, consultores: List[Dict[str, Any]]) -> int:
"""
Insere ou atualiza um batch de consultores usando MERGE.
Insere batch de consultores usando executemany (muito mais rápido).
Retorna o número de registros processados.
"""
import oracledb
@@ -20,68 +20,39 @@ class RankingOracleRepository:
if not consultores:
return 0
merge_sql = """
MERGE INTO TB_RANKING_CONSULTOR t
USING (
SELECT
:id_pessoa AS ID_PESSOA,
:nome AS NOME,
:pontuacao_total AS PONTUACAO_TOTAL,
:componente_a AS COMPONENTE_A,
:componente_b AS COMPONENTE_B,
:componente_c AS COMPONENTE_C,
:componente_d AS COMPONENTE_D,
:ativo AS ATIVO,
:anos_atuacao AS ANOS_ATUACAO,
TO_CLOB(:json_detalhes) AS JSON_DETALHES
FROM DUAL
) s
ON (t.ID_PESSOA = s.ID_PESSOA)
WHEN MATCHED THEN
UPDATE SET
t.NOME = s.NOME,
t.PONTUACAO_TOTAL = s.PONTUACAO_TOTAL,
t.COMPONENTE_A = s.COMPONENTE_A,
t.COMPONENTE_B = s.COMPONENTE_B,
t.COMPONENTE_C = s.COMPONENTE_C,
t.COMPONENTE_D = s.COMPONENTE_D,
t.ATIVO = s.ATIVO,
t.ANOS_ATUACAO = s.ANOS_ATUACAO,
t.DT_CALCULO = CURRENT_TIMESTAMP,
t.JSON_DETALHES = s.JSON_DETALHES
WHEN NOT MATCHED THEN
INSERT (
ID_PESSOA, NOME, PONTUACAO_TOTAL,
COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D,
ATIVO, ANOS_ATUACAO, JSON_DETALHES, DT_CALCULO
)
VALUES (
s.ID_PESSOA, s.NOME, s.PONTUACAO_TOTAL,
s.COMPONENTE_A, s.COMPONENTE_B, s.COMPONENTE_C, s.COMPONENTE_D,
s.ATIVO, s.ANOS_ATUACAO, s.JSON_DETALHES, CURRENT_TIMESTAMP
)
insert_sql = """
INSERT INTO TB_RANKING_CONSULTOR (
ID_PESSOA, NOME, PONTUACAO_TOTAL,
COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D,
ATIVO, ANOS_ATUACAO, JSON_DETALHES, DT_CALCULO
) VALUES (
:id_pessoa, :nome, :pontuacao_total,
:componente_a, :componente_b, :componente_c, :componente_d,
:ativo, :anos_atuacao, :json_detalhes, CURRENT_TIMESTAMP
)
"""
batch_data = []
for consultor in consultores:
json_str = json.dumps(consultor, ensure_ascii=False)
batch_data.append({
"id_pessoa": int(consultor["id_pessoa"]),
"nome": str(consultor.get("nome", ""))[:500],
"pontuacao_total": int(consultor.get("pontuacao_total") or 0),
"componente_a": int(consultor.get("bloco_a") or consultor.get("componente_a") or 0),
"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),
"ativo": "S" if consultor.get("ativo") else "N",
"anos_atuacao": float(consultor.get("anos_atuacao") or 0),
"json_detalhes": json_str
})
with self.client.get_connection() as conn:
cursor = conn.cursor()
try:
for consultor in consultores:
json_str = json.dumps(consultor, ensure_ascii=False)
cursor.setinputsizes(json_detalhes=oracledb.DB_TYPE_CLOB)
params = {
"id_pessoa": consultor["id_pessoa"],
"nome": consultor["nome"],
"pontuacao_total": consultor["pontuacao_total"],
"componente_a": consultor.get("bloco_a") or consultor.get("componente_a", 0),
"componente_b": consultor.get("bloco_b") or consultor.get("componente_b", 0),
"componente_c": consultor.get("bloco_c") or consultor.get("componente_c", 0),
"componente_d": consultor.get("bloco_d") or consultor.get("componente_d", 0),
"ativo": "S" if consultor.get("ativo") else "N",
"anos_atuacao": consultor.get("anos_atuacao", 0),
"json_detalhes": json_str
}
cursor.execute(merge_sql, params)
cursor.setinputsizes(json_detalhes=oracledb.DB_TYPE_CLOB)
cursor.executemany(insert_sql, batch_data)
conn.commit()
return len(consultores)
except Exception as e:
@@ -372,14 +343,18 @@ class RankingOracleRepository:
"""
Limpa todos os registros da tabela de ranking.
Usar apenas quando for reprocessar do zero.
TRUNCATE é muito mais rápido que DELETE para grandes volumes.
"""
with self.client.get_connection() as conn:
cursor = conn.cursor()
try:
cursor.execute("DELETE FROM TB_RANKING_CONSULTOR")
conn.commit()
cursor.execute("TRUNCATE TABLE TB_RANKING_CONSULTOR")
except Exception as e:
conn.rollback()
raise RuntimeError(f"Erro ao limpar tabela: {e}")
try:
cursor.execute("DELETE FROM TB_RANKING_CONSULTOR")
conn.commit()
except Exception as e2:
conn.rollback()
raise RuntimeError(f"Erro ao limpar tabela: {e2}")
finally:
cursor.close()

View File

@@ -17,6 +17,7 @@ from ...domain.entities.consultor import (
Participacao,
Orientacao,
MembroBanca,
DocenciaPPG,
)
from ...domain.repositories.consultor_repository import ConsultorRepository
from ...domain.services.calculador_pontuacao import CalculadorPontuacao
@@ -554,6 +555,51 @@ class ConsultorRepositoryImpl(ConsultorRepository):
return membros
def _extrair_docencias(self, atuacoes: List[Dict[str, Any]]) -> List[DocenciaPPG]:
docencias = []
for a in atuacoes:
if a.get("tipo") != "Docência":
continue
dados = a.get("dadosDocencia", {}) or {}
if not dados:
continue
programa_data = dados.get("programa", {}) or {}
ies_data = dados.get("ies", {}) or {}
area_data = dados.get("areaConhecimento", {}) or {}
area_aval_data = area_data.get("areaAvaliacao", {}) or {}
programa = programa_data.get("nome", "") or a.get("descricao", "")
codigo_programa = programa_data.get("codigo", "")
if not programa and not codigo_programa:
continue
inicio = self._parse_date(a.get("inicio"))
fim = self._parse_date(a.get("fim"))
linhas = []
for lp in dados.get("linhaPesquisa", []) or []:
if lp.get("nome"):
linhas.append(lp["nome"])
docencias.append(DocenciaPPG(
programa=programa,
codigo_programa=codigo_programa,
ies_sigla=ies_data.get("sigla", ""),
ies_nome=ies_data.get("nome", ""),
categoria=dados.get("categoria", ""),
area_avaliacao=area_aval_data.get("nome", "") or area_data.get("nome", ""),
modalidade=programa_data.get("modalidade", ""),
periodo=Periodo(inicio=inicio, fim=fim),
carga_horaria=dados.get("cargaHoraria"),
linhas_pesquisa=linhas[:3],
))
docencias.sort(key=lambda d: (d.periodo.fim is not None, d.periodo.inicio or datetime.min), reverse=True)
return docencias
async def _construir_consultor(self, doc: Dict[str, Any]) -> Consultor:
id_pessoa = doc["id"]
dados_pessoais = doc.get("dadosPessoais", {})
@@ -569,6 +615,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
orientacoes = self._extrair_orientacoes(atuacoes)
coorientacoes = self._extrair_coorientacoes(atuacoes)
membros_banca = self._extrair_membros_banca(atuacoes)
docencias = self._extrair_docencias(atuacoes)
coordenador_ppg = self._tem_coordenacao_ppg(atuacoes)
consultor = Consultor(
@@ -585,6 +632,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
participacoes=participacoes,
orientacoes=orientacoes + coorientacoes,
membros_banca=membros_banca,
docencias=docencias,
)
consultor.pontuacao = self.calculador.calcular_pontuacao_completa(consultor)