feat: Sistema de Ranking de Consultores CAPES - versão inicial

Backend (FastAPI + DDD):
- Arquitetura DDD com camadas Domain, Application, Infrastructure, Interface
- Integração com Elasticsearch (ATUACAPES) para dados de consultores
- Integração com Oracle (SUCUPIRA_PAINEL) para coordenações PPG
- Cálculo dos 4 componentes de pontuação (A, B, C, D)
- Cache em memória para otimização de performance
- API REST com endpoints /ranking, /ranking/detalhado, /consultor/{id}

Frontend (React + Vite):
- Interface responsiva com cards expansíveis
- Visualização detalhada de pontuação por componente
- Filtro por quantidade de consultores (Top 10, 50, 100, etc)

Docker:
- docker-compose com shared_network externa
- Backend com Oracle Instant Client
- Frontend com Vite dev server
This commit is contained in:
Frederico Castro
2025-12-09 01:24:35 -03:00
commit 9e6ba459a8
69 changed files with 4902 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
from typing import List, Optional
from datetime import datetime
from ...domain.repositories.consultor_repository import ConsultorRepository
from ...domain.entities.consultor import Consultor
from ..dtos.consultor_dto import (
ConsultorResumoDTO,
ConsultorDetalhadoDTO,
PeriodoDTO,
CoordenacaoCapesDTO,
CoordenacaoProgramaDTO,
ConsultoriaDTO,
PremiacaoDTO,
ComponentePontuacaoDTO,
PontuacaoCompletaDTO,
)
class ObterRankingUseCase:
def __init__(self, repository: ConsultorRepository):
self.repository = repository
async def executar(
self, limite: int = 100, componente: Optional[str] = None
) -> List[ConsultorResumoDTO]:
consultores = await self.repository.buscar_ranking(limite=limite, componente=componente)
return [
ConsultorResumoDTO(
id_pessoa=c.id_pessoa,
nome=c.nome,
anos_atuacao=c.anos_atuacao,
ativo=c.ativo,
veterano=c.veterano,
pontuacao_total=c.pontuacao_total,
rank=idx + 1,
)
for idx, c in enumerate(consultores)
]
async def executar_detalhado(
self, limite: int = 100, componente: Optional[str] = None
) -> List[ConsultorDetalhadoDTO]:
consultores = await self.repository.buscar_ranking(limite=limite, componente=componente)
return [self._converter_para_dto_detalhado(c, idx + 1) for idx, c in enumerate(consultores)]
def _converter_para_dto_detalhado(
self, consultor: Consultor, rank: int
) -> ConsultorDetalhadoDTO:
return ConsultorDetalhadoDTO(
id_pessoa=consultor.id_pessoa,
nome=consultor.nome,
cpf=consultor.cpf,
anos_atuacao=consultor.anos_atuacao,
ativo=consultor.ativo,
veterano=consultor.veterano,
coordenacoes_capes=[
CoordenacaoCapesDTO(
tipo=cc.tipo,
area_avaliacao=cc.area_avaliacao,
periodo=PeriodoDTO(
inicio=cc.periodo.inicio.isoformat(),
fim=cc.periodo.fim.isoformat() if cc.periodo.fim else None,
ativo=cc.periodo.ativo,
anos_decorridos=cc.periodo.anos_decorridos,
),
areas_adicionais=cc.areas_adicionais,
ja_coordenou_antes=cc.ja_coordenou_antes,
)
for cc in consultor.coordenacoes_capes
],
coordenacoes_programas=[
CoordenacaoProgramaDTO(
id_programa=cp.id_programa,
nome_programa=cp.nome_programa,
codigo_programa=cp.codigo_programa,
nota_ppg=cp.nota_ppg,
modalidade=cp.modalidade,
area_avaliacao=cp.area_avaliacao,
periodo=PeriodoDTO(
inicio=cp.periodo.inicio.isoformat(),
fim=cp.periodo.fim.isoformat() if cp.periodo.fim else None,
ativo=cp.periodo.ativo,
anos_decorridos=cp.periodo.anos_decorridos,
),
)
for cp in consultor.coordenacoes_programas
],
consultoria=ConsultoriaDTO(
total_eventos=consultor.consultoria.total_eventos,
eventos_recentes=consultor.consultoria.eventos_recentes,
primeiro_evento=consultor.consultoria.primeiro_evento.isoformat(),
ultimo_evento=consultor.consultoria.ultimo_evento.isoformat(),
vezes_responsavel=consultor.consultoria.vezes_responsavel,
areas=consultor.consultoria.areas,
)
if consultor.consultoria
else None,
premiacoes=[
PremiacaoDTO(
tipo=p.tipo,
nome_premio=p.nome_premio,
ano=p.ano,
pontos=p.pontos,
)
for p in consultor.premiacoes
],
pontuacao=PontuacaoCompletaDTO(
componente_a=ComponentePontuacaoDTO(
base=consultor.pontuacao.componente_a.base,
tempo=consultor.pontuacao.componente_a.tempo,
extras=consultor.pontuacao.componente_a.extras,
bonus=consultor.pontuacao.componente_a.bonus,
retorno=consultor.pontuacao.componente_a.retorno,
total=consultor.pontuacao.componente_a.total,
),
componente_b=ComponentePontuacaoDTO(
base=consultor.pontuacao.componente_b.base,
tempo=consultor.pontuacao.componente_b.tempo,
extras=consultor.pontuacao.componente_b.extras,
bonus=consultor.pontuacao.componente_b.bonus,
retorno=0,
total=consultor.pontuacao.componente_b.total,
),
componente_c=ComponentePontuacaoDTO(
base=consultor.pontuacao.componente_c.base,
tempo=consultor.pontuacao.componente_c.tempo,
extras=consultor.pontuacao.componente_c.extras,
bonus=consultor.pontuacao.componente_c.bonus,
retorno=0,
total=consultor.pontuacao.componente_c.total,
),
componente_d=ComponentePontuacaoDTO(
base=consultor.pontuacao.componente_d.base,
tempo=consultor.pontuacao.componente_d.tempo,
extras=consultor.pontuacao.componente_d.extras,
bonus=consultor.pontuacao.componente_d.bonus,
retorno=0,
total=consultor.pontuacao.componente_d.total,
),
pontuacao_total=consultor.pontuacao.total,
),
rank=rank,
)