Reimplementa sistema de ranking com novos critérios V2

Mudanças principais:
- Substitui 4 Componentes (A,B,C,D) por 3 Blocos (A,C,D)
- Remove Componente B (Coordenação PPG = 0 pts no V1)
- Adiciona novos tipos de atuação do Elasticsearch
- Implementa critérios de pontuação com tetos individuais

Bloco A - Coordenação CAPES:
- CA (max 450), CAJ (max 370), CAJ_MP (max 315), CAM (max 280)
- Calcula base + tempo + bônus atualidade + bônus retorno

Bloco C - Consultoria:
- CONS_ATIVO (base 150), CONS_HIST (base 100), CONS_FALECIDO (base 100)
- Bônus continuidade: 3anos=+5, 5anos=+10, 8anos=+15
- Bônus retorno: +15

Bloco D - Premiações/Avaliações:
- Inscrições (INSC_AUTOR, INSC_INST)
- Avaliações (AVAL_COMIS_PREMIO, AVAL_COMIS_GP)
- Coordenações (COORD_COMIS_PREMIO, COORD_COMIS_GP)
- Premiações (PREMIACAO, PREMIACAO_GP, MENCAO)
- Bolsas CNPQ, Participações, Orientações, Membros de Banca

Frontend:
- Header, ConsultorCard, CompararModal atualizados para 3 blocos
- API service atualizado para nova estrutura de dados
This commit is contained in:
Frederico Castro
2025-12-13 16:41:55 -03:00
parent 97cd328415
commit 2d4e93f82a
15 changed files with 1517 additions and 1001 deletions

View File

@@ -31,7 +31,7 @@ async def obter_ranking(
limite: int = Query(default=100, ge=1, le=1000, description="Limite de consultores"),
offset: int = Query(default=0, ge=0, description="Offset para paginação"),
componente: Optional[str] = Query(
default=None, description="Filtrar por componente (a, b, c, d)"
default=None, description="Filtrar por bloco (a, c, d)"
),
repository: ConsultorRepositoryImpl = Depends(get_repository),
):
@@ -53,7 +53,7 @@ async def obter_ranking(
async def obter_ranking_detalhado(
limite: int = Query(default=100, ge=1, le=1000, description="Limite de consultores"),
componente: Optional[str] = Query(
default=None, description="Filtrar por componente (a, b, c, d)"
default=None, description="Filtrar por bloco (a, c, d)"
),
repository: ConsultorRepositoryImpl = Depends(get_repository),
):
@@ -95,9 +95,6 @@ async def ranking_paginado(
ativo: Optional[bool] = Query(default=None, description="Filtrar por status ativo"),
ranking_repo = Depends(get_ranking_repository),
):
"""
Retorna ranking paginado do Oracle (pré-calculado).
"""
total = ranking_repo.contar_total(filtro_ativo=ativo)
consultores = ranking_repo.buscar_paginado(page=page, size=size, filtro_ativo=ativo)
@@ -132,50 +129,51 @@ async def buscar_por_nome(
]
def _calcular_continuidade(anos_consecutivos: int) -> int:
if anos_consecutivos >= 8:
return 15
elif anos_consecutivos >= 5:
return 10
elif anos_consecutivos >= 3:
return 5
return 0
def _consultor_resumo_from_ranking(c):
consultoria = None
coordenacoes_capes = None
coordenacoes_programas = None
inscricoes = None
avaliacoes_comissao = None
premiacoes = None
bolsas_cnpq = None
participacoes = None
orientacoes = None
membros_banca = None
try:
jd = json.loads(c.json_detalhes) if c.json_detalhes else {}
if isinstance(jd, dict):
consultoria = jd.get("consultoria")
coordenacoes_capes = jd.get("coordenacoes_capes")
coordenacoes_programas = jd.get("coordenacoes_programas")
inscricoes = jd.get("inscricoes")
avaliacoes_comissao = jd.get("avaliacoes_comissao")
premiacoes = jd.get("premiacoes")
if consultoria and isinstance(consultoria, dict):
anos_consec = consultoria.get("anos_consecutivos") or consultoria.get("anos_completos") or 0
consultoria["continuidade"] = _calcular_continuidade(anos_consec)
consultoria["anos_consecutivos"] = anos_consec
bolsas_cnpq = jd.get("bolsas_cnpq")
participacoes = jd.get("participacoes")
orientacoes = jd.get("orientacoes")
membros_banca = jd.get("membros_banca")
except Exception:
consultoria = None
pass
return ConsultorRankingResumoSchema(
id_pessoa=c.id_pessoa,
nome=c.nome,
posicao=c.posicao,
pontuacao_total=c.pontuacao_total,
componente_a=c.componente_a,
componente_b=c.componente_b,
componente_c=c.componente_c,
componente_d=c.componente_d,
bloco_a=c.componente_a,
bloco_c=c.componente_c,
bloco_d=c.componente_d,
ativo=c.ativo,
anos_atuacao=c.anos_atuacao,
consultoria=consultoria,
coordenacoes_capes=coordenacoes_capes,
coordenacoes_programas=coordenacoes_programas,
inscricoes=inscricoes,
avaliacoes_comissao=avaliacoes_comissao,
premiacoes=premiacoes,
bolsas_cnpq=bolsas_cnpq,
participacoes=participacoes,
orientacoes=orientacoes,
membros_banca=membros_banca,
)
@@ -183,23 +181,24 @@ def _consultor_resumo_from_ranking(c):
async def ranking_estatisticas(
ranking_repo = Depends(get_ranking_repository),
):
"""
Retorna estatísticas do ranking.
"""
estatisticas = ranking_repo.obter_estatisticas()
distribuicao = ranking_repo.obter_distribuicao()
return EstatisticasRankingSchema(
**estatisticas,
total_consultores=estatisticas.get("total_consultores", 0),
total_ativos=estatisticas.get("total_ativos", 0),
total_inativos=estatisticas.get("total_inativos", 0),
ultima_atualizacao=estatisticas.get("ultima_atualizacao"),
pontuacao_media=estatisticas.get("pontuacao_media", 0),
pontuacao_maxima=estatisticas.get("pontuacao_maxima", 0),
pontuacao_minima=estatisticas.get("pontuacao_minima", 0),
media_blocos=estatisticas.get("media_componentes", {}),
distribuicao=distribuicao
)
@router.get("/ranking/status", response_model=JobStatusSchema)
async def status_processamento():
"""
Retorna o status do job de processamento do ranking.
"""
return JobStatusSchema(**job_status.to_dict())
@@ -209,9 +208,6 @@ async def processar_ranking(
request: ProcessarRankingRequestSchema = ProcessarRankingRequestSchema(),
job = Depends(get_processar_job),
):
"""
Dispara o processamento do ranking em background.
"""
if job_status.is_running:
raise HTTPException(status_code=409, detail="Job já está em execução")