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

@@ -8,46 +8,8 @@ const api = axios.create({
timeout: 180000,
});
const calcularComponenteB = (coordenacoesProgramas = []) => {
if (!coordenacoesProgramas.length) {
return { base: 0, tempo: 0, extras: 0, bonus: 0, total: 0 };
}
const agora = new Date();
const base = 70;
let anosTotais = 0;
coordenacoesProgramas.forEach((coord) => {
const inicio = coord.periodo?.inicio ? new Date(coord.periodo.inicio) : null;
const fim = coord.periodo?.fim ? new Date(coord.periodo.fim) : agora;
if (inicio && fim >= inicio) {
const diffAnos = Math.floor((fim - inicio) / (365 * 24 * 60 * 60 * 1000));
anosTotais += diffAnos;
}
});
const tempo = Math.min(anosTotais * 5, 50);
const programasDistintos = new Set(
coordenacoesProgramas.map((c) => c.id_programa || c.codigo_programa || c.nome_programa)
).size;
const extras = programasDistintos > 1 ? Math.min((programasDistintos - 1) * 20, 40) : 0;
let maiorNota = 0;
coordenacoesProgramas.forEach((coord) => {
const n = String(coord.nota_ppg || '').trim();
if (['7', '6', '5', '4', '3'].includes(n)) {
maiorNota = Math.max(maiorNota, parseInt(n, 10));
}
});
const bonus = ({ 7: 20, 6: 15, 5: 10, 4: 5, 3: 0 }[maiorNota] ?? 0);
const total = base + tempo + extras + bonus;
return { base, tempo, extras, bonus, total };
};
export const rankingService = {
async getRanking(page = 1, size = 100) {
// Usa ranking paginado (Oracle) para percorrer os 350k
const params = { page, size };
const response = await api.get('/ranking/paginado', { params });
const data = response.data;
@@ -57,8 +19,8 @@ export const rankingService = {
const consultores = (data.consultores || []).map((c) => {
const anos = Number(c.anos_atuacao || 0);
const consultoria = c.consultoria || {};
const primeiroEvento = consultoria.primeiro_evento
? new Date(consultoria.primeiro_evento)
const primeiroEvento = consultoria.inicio
? new Date(consultoria.inicio)
: (() => {
const d = new Date(hoje);
d.setFullYear(d.getFullYear() - Math.floor(anos));
@@ -75,62 +37,41 @@ export const rankingService = {
periodo: mapPeriodo(coord),
}));
const coordenacoesProgramas = (c.coordenacoes_programas || []).map((coord) => ({
...coord,
periodo: mapPeriodo(coord),
}));
let compB;
if (coordenacoesProgramas.length > 0) {
compB = calcularComponenteB(coordenacoesProgramas);
} else {
const totalB = Number(c.componente_b || 0);
compB = {
base: totalB > 0 ? totalB : 0,
tempo: 0,
extras: 0,
bonus: 0,
total: totalB,
};
}
return {
id_pessoa: c.id_pessoa,
nome: c.nome,
rank: c.posicao,
posicao: c.posicao,
pontuacao_total: c.pontuacao_total,
componente_a: c.componente_a,
componente_b: compB.total,
componente_c: c.componente_c,
componente_d: c.componente_d,
bloco_a: c.bloco_a,
bloco_c: c.bloco_c,
bloco_d: c.bloco_d,
ativo: c.ativo,
anos_atuacao: anos,
veterano: anos >= 10,
pontuacao: {
pontuacao_total: c.pontuacao_total,
componente_a: { base: c.componente_a, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_a },
componente_b: {
base: compB.base,
tempo: compB.tempo,
extras: compB.extras,
bonus: compB.bonus,
retorno: 0,
total: compB.total,
},
componente_c: { base: c.componente_c, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_c },
componente_d: { base: c.componente_d, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_d },
bloco_a: { total: c.bloco_a, atuacoes: [] },
bloco_c: { total: c.bloco_c, atuacoes: [] },
bloco_d: { total: c.bloco_d, atuacoes: [] },
},
consultoria: {
total_eventos: consultoria.total_eventos ?? 0,
eventos_recentes: consultoria.eventos_recentes ?? 0,
vezes_responsavel: consultoria.vezes_responsavel ?? 0,
primeiro_evento: consultoria.primeiro_evento || primeiroEvento.toISOString(),
ultimo_evento: consultoria.ultimo_evento || null,
codigo: consultoria.codigo || null,
situacao: consultoria.situacao || null,
inicio: consultoria.inicio || primeiroEvento.toISOString(),
fim: consultoria.fim || null,
areas: consultoria.areas || [],
anos_consecutivos: consultoria.anos_consecutivos || 0,
retornos: consultoria.retornos || 0,
},
coordenacoes_capes: coordenacoesCapes,
coordenacoes_programas: coordenacoesProgramas,
inscricoes: c.inscricoes || [],
avaliacoes_comissao: c.avaliacoes_comissao || [],
premiacoes: c.premiacoes || [],
bolsas_cnpq: c.bolsas_cnpq || [],
participacoes: c.participacoes || [],
orientacoes: c.orientacoes || [],
membros_banca: c.membros_banca || [],
};
});