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)