Files
ranking/backend/src/application/jobs/processar_ranking.py
Frederico Castro 919d95d1e8 fix(backend): corrigir exibicao de idiomas e selos multilingue
- Adicionar idiomas e formacoes ao _source das queries ES (client.py)
- Corrigir type mismatch int/str no endpoint paginado (routes.py)
- Adicionar campo evento nas inscricoes para nome do premio
- Implementar extracao de idiomas do ES no repository
- Ajustar frontend para exibir selo multilingue corretamente
2025-12-24 18:12:22 -03:00

323 lines
13 KiB
Python

import logging
from typing import Dict, Any, List, Optional
from ...infrastructure.elasticsearch.client import ElasticsearchClient
from ...infrastructure.ranking_store import RankingEntry, RankingStore
from ...infrastructure.oracle.ranking_repository import RankingOracleRepository
logger = logging.getLogger(__name__)
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
from .job_status import job_status
class ProcessarRankingJob:
def __init__(
self,
es_client: ElasticsearchClient,
ranking_store: RankingStore,
ranking_oracle_repo: Optional[RankingOracleRepository] = None,
):
self.es_client = es_client
self.ranking_store = ranking_store
self.ranking_oracle_repo = ranking_oracle_repo
self.consultor_repo = ConsultorRepositoryImpl(es_client, oracle_client=None)
self._consultores: List[dict] = []
async def executar(self, limpar_antes: bool = True) -> Dict[str, Any]:
if job_status.is_running:
raise RuntimeError("Job já está em execução")
try:
total = await self.es_client.contar_com_atuacoes()
job_status.iniciar(total_esperado=total)
self._consultores = []
job_status.mensagem = "Iniciando processamento do ranking via Scroll API (Elasticsearch)..."
resultado = await self.es_client.buscar_todos_consultores(
callback=self._processar_batch,
batch_size=10000
)
job_status.mensagem = "Ordenando e gerando posições..."
entries = self._gerar_entries_ordenadas(self._consultores)
await self.ranking_store.set_entries(entries)
if self.ranking_oracle_repo:
job_status.mensagem = "Persistindo no Oracle..."
await self._persistir_oracle(self._consultores, limpar_antes)
estatisticas = self._obter_estatisticas(entries)
job_status.finalizar(sucesso=True)
resultado_final = {
"sucesso": True,
"total_processados": resultado.get("processados", len(entries)),
"total_batches": resultado.get("batches", 0),
"tempo_decorrido": job_status.tempo_decorrido,
"estatisticas": estatisticas
}
self._consultores = []
return resultado_final
except Exception as e:
self._consultores = []
job_status.finalizar(sucesso=False, erro=str(e))
logger.error(f"Erro ao processar ranking: {e}", exc_info=True)
raise RuntimeError(f"Erro ao processar ranking: {e}") from e
async def _processar_batch(self, docs: list, progress: dict) -> None:
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}")
logger.debug(f"Traceback: {traceback.format_exc()}")
continue
job_status.atualizar_progresso(
processados=progress["processados"],
batch_atual=progress["batch_atual"],
mensagem=f"Processando batch {progress['batch_atual']} ({progress['percentual']}%)"
)
def _gerar_json_detalhes(self, consultor) -> dict:
pontuacao = consultor.pontuacao.to_dict() if consultor.pontuacao else None
return {
"id_pessoa": consultor.id_pessoa,
"nome": consultor.nome,
"posicao": None,
"pontuacao_total": consultor.pontuacao_total,
"bloco_a": consultor.pontuacao_bloco_a,
"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,
"coordenacoes_capes": [
{
"codigo": c.codigo,
"tipo": c.tipo,
"area_avaliacao": c.area_avaliacao,
"inicio": c.periodo.inicio.isoformat() if c.periodo.inicio else None,
"fim": c.periodo.fim.isoformat() if c.periodo.fim else None,
"ativo": c.periodo.ativo,
"presidente": c.presidente,
}
for c in consultor.coordenacoes_capes
],
"consultoria": {
"codigo": consultor.consultoria.codigo,
"situacao": consultor.consultoria.situacao,
"inicio": consultor.consultoria.periodo.inicio.isoformat() if consultor.consultoria.periodo.inicio else None,
"fim": consultor.consultoria.periodo.fim.isoformat() if consultor.consultoria.periodo.fim else None,
"areas": consultor.consultoria.areas,
"anos_consecutivos": consultor.consultoria.anos_consecutivos,
"retornos": consultor.consultoria.retornos,
"vinculos": [
{
"inicio": v.periodo.inicio.isoformat() if v.periodo.inicio else None,
"fim": v.periodo.fim.isoformat() if v.periodo.fim else None,
"ativo": v.periodo.ativo,
"situacao": v.situacao,
"ies": {
"id": v.ies.id,
"nome": v.ies.nome,
"sigla": v.ies.sigla,
} if v.ies else None,
}
for v in consultor.consultoria.vinculos
],
} if consultor.consultoria else None,
"inscricoes": [
{
"codigo": i.codigo,
"tipo": i.tipo,
"premio": i.premio,
"ano": i.ano,
"situacao": i.situacao,
"evento": i.evento
}
for i in consultor.inscricoes
],
"avaliacoes_comissao": [
{
"codigo": a.codigo,
"tipo": a.tipo,
"premio": a.premio,
"ano": a.ano,
"comissao_tipo": a.comissao_tipo,
"nome_comissao": a.nome_comissao,
}
for a in consultor.avaliacoes_comissao
],
"premiacoes": [
{
"codigo": p.codigo,
"tipo": p.tipo,
"nome_premio": p.nome_premio,
"ano": p.ano,
"papel": p.papel,
}
for p in consultor.premiacoes
],
"bolsas_cnpq": [
{
"codigo": b.codigo,
"nivel": b.nivel,
"area": b.area
}
for b in consultor.bolsas_cnpq
],
"participacoes": [
{
"codigo": p.codigo,
"tipo": p.tipo,
"descricao": p.descricao,
"ano": p.ano
}
for p in consultor.participacoes
],
"orientacoes": [
{
"codigo": o.codigo,
"tipo": o.tipo,
"nivel": o.nivel,
"ano": o.ano,
"coorientacao": o.coorientacao,
"premiada": o.premiada,
"premiacao_tipo": o.premiacao_tipo,
}
for o in consultor.orientacoes
],
"membros_banca": [
{
"codigo": m.codigo,
"tipo": m.tipo,
"nivel": m.nivel,
"ano": m.ano
}
for m in consultor.membros_banca
],
"docencias": [
{
"programa": d.programa,
"codigo_programa": d.codigo_programa,
"ies_sigla": d.ies_sigla,
"ies_nome": d.ies_nome,
"categoria": d.categoria,
"area_avaliacao": d.area_avaliacao,
"modalidade": d.modalidade,
"inicio": d.periodo.inicio.isoformat() if d.periodo.inicio else None,
"fim": d.periodo.fim.isoformat() if d.periodo.fim else None,
"ativo": d.periodo.ativo,
"carga_horaria": d.carga_horaria,
"linhas_pesquisa": d.linhas_pesquisa,
}
for d in consultor.docencias
],
"idiomas": [
{
"idioma": i.idioma,
"nivel_leitura": i.nivel_leitura,
"nivel_escrita": i.nivel_escrita,
"nivel_fala": i.nivel_fala,
"nivel_compreensao": i.nivel_compreensao,
}
for i in consultor.idiomas
],
"titulacao": consultor.titulacao,
"pontuacao": pontuacao,
}
@staticmethod
def _gerar_entries_ordenadas(consultores: List[dict]) -> List[RankingEntry]:
consultores_ordenados = sorted(
consultores,
key=lambda c: (int(c.get("pontuacao_total", 0)), -int(c.get("id_pessoa", 0))),
reverse=True,
)
entries: List[RankingEntry] = []
for idx, c in enumerate(consultores_ordenados, start=1):
c["posicao"] = idx
entries.append(
RankingEntry(
id_pessoa=int(c["id_pessoa"]),
nome=str(c.get("nome", "")),
posicao=idx,
pontuacao_total=int(c.get("pontuacao_total", 0)),
bloco_a=int(c.get("bloco_a", 0)),
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,
)
)
return entries
@staticmethod
def _obter_estatisticas(entries: List[RankingEntry]) -> Dict[str, Any]:
if not entries:
return {
"total_consultores": 0,
"total_ativos": 0,
"total_inativos": 0,
"ultima_atualizacao": None,
"pontuacao_media": 0,
"pontuacao_maxima": 0,
"pontuacao_minima": 0,
"media_componentes": {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0},
}
total = len(entries)
ativos = sum(1 for e in entries if e.ativo)
inativos = total - ativos
totais = [e.pontuacao_total for e in entries]
media_total = sum(totais) / total if total else 0
return {
"total_consultores": total,
"total_ativos": ativos,
"total_inativos": inativos,
"ultima_atualizacao": None,
"pontuacao_media": float(round(media_total, 2)),
"pontuacao_maxima": float(max(totais) if totais else 0),
"pontuacao_minima": float(min(totais) if totais else 0),
"media_componentes": {
"a": float(round(sum(e.bloco_a for e in entries) / total, 2)),
"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)),
},
}
async def _persistir_oracle(self, consultores: List[dict], limpar_antes: bool) -> None:
import asyncio
if not self.ranking_oracle_repo:
return
def _sync_persist():
if limpar_antes:
self.ranking_oracle_repo.limpar_tabela()
batch_size = 2000
for i in range(0, len(consultores), batch_size):
batch = consultores[i:i + batch_size]
self.ranking_oracle_repo.inserir_batch(batch)
self.ranking_oracle_repo.atualizar_posicoes()
await asyncio.get_event_loop().run_in_executor(None, _sync_persist)