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

@@ -6,9 +6,14 @@ import asyncio
from ...domain.entities.consultor import (
Consultor,
CoordenacaoCapes,
CoordenacaoPrograma,
Consultoria,
Inscricao,
AvaliacaoComissao,
Premiacao,
BolsaCNPQ,
Participacao,
Orientacao,
MembroBanca,
)
from ...domain.repositories.consultor_repository import ConsultorRepository
from ...domain.services.calculador_pontuacao import CalculadorPontuacao
@@ -42,36 +47,12 @@ _ranking_cache = RankingCache(ttl_seconds=300)
class ConsultorRepositoryImpl(ConsultorRepository):
def __init__(self, es_client: ElasticsearchClient, oracle_client: OracleClient):
def __init__(self, es_client: ElasticsearchClient, oracle_client: OracleClient = None):
self.es_client = es_client
self.oracle_client = oracle_client
self.calculador = CalculadorPontuacao()
self.es_disponivel = True
def _mesclar_periodos(self, periodos: List[Periodo]) -> List[Periodo]:
"""
Mescla períodos sobrepostos/contíguos para evitar contagem dupla de tempo.
"""
if not periodos:
return []
periodos = sorted(periodos, key=lambda p: p.inicio)
mesclados: List[Periodo] = []
for p in periodos:
if not mesclados:
mesclados.append(p)
continue
ultimo = mesclados[-1]
fim_ultimo = ultimo.fim or datetime.now()
fim_atual = p.fim or datetime.now()
if p.inicio <= fim_ultimo:
novo_fim = max(fim_ultimo, fim_atual)
mesclados[-1] = Periodo(inicio=ultimo.inicio, fim=novo_fim if not ultimo.ativo else None)
else:
mesclados.append(p)
return mesclados
def _parse_date(self, date_str: Optional[str]) -> Optional[datetime]:
if not date_str:
return None
@@ -80,10 +61,87 @@ class ConsultorRepositoryImpl(ConsultorRepository):
except:
return None
def _extrair_consultoria(self, atuacoes: List[Dict[str, Any]]) -> Optional[Consultoria]:
consultorias = [
a for a in atuacoes if a.get("tipo") in ["Consultor", "Histórico de Consultoria"]
def _mesclar_periodos(self, periodos: List[Periodo]) -> List[Periodo]:
if not periodos:
return []
periodos = sorted(periodos, key=lambda p: p.inicio if p.inicio else datetime.min)
mesclados: List[Periodo] = []
for p in periodos:
if not mesclados:
mesclados.append(p)
continue
ultimo = mesclados[-1]
fim_ultimo = ultimo.fim or datetime.now()
fim_atual = p.fim or datetime.now()
if p.inicio and p.inicio <= fim_ultimo:
novo_fim = max(fim_ultimo, fim_atual)
mesclados[-1] = Periodo(inicio=ultimo.inicio, fim=novo_fim if not ultimo.ativo else None)
else:
mesclados.append(p)
return mesclados
def _inferir_tipo_coordenacao(self, coord: Dict[str, Any]) -> str:
dados_coord = coord.get("dadosCoordenacaoArea", {}) or {}
tipo_coord = dados_coord.get("tipo", "").lower()
if "câmara" in tipo_coord or "camara" in tipo_coord:
return "CAM"
elif "adjunt" in tipo_coord:
if "profissional" in tipo_coord or "mestrado" in tipo_coord:
return "CAJ_MP"
return "CAJ"
elif "coordenador de área" in tipo_coord:
return "CA"
descricao = coord.get("descricao", "").lower()
nome = coord.get("nome", "").lower()
texto = f"{descricao} {nome}"
if "câmara" in texto or "camara" in texto:
return "CAM"
elif "mestrado profissional" in texto:
return "CAJ_MP"
elif "adjunt" in texto:
return "CAJ"
return "CA"
def _extrair_coordenacoes_capes(self, atuacoes: List[Dict[str, Any]]) -> List[CoordenacaoCapes]:
coordenacoes = [
a for a in atuacoes
if a.get("tipo") in ["Coordenação de Área de Avaliação", "Histórico de Coordenação de Área de Avaliação"]
]
resultado = []
for coord in coordenacoes:
dados_coord = coord.get("dadosCoordenacaoArea", {}) or {}
inicio = self._parse_date(dados_coord.get("inicioVinculacao")) or self._parse_date(coord.get("inicio"))
if not inicio:
continue
codigo = self._inferir_tipo_coordenacao(coord)
fim = self._parse_date(dados_coord.get("fimVinculacao")) or self._parse_date(coord.get("fim"))
if inicio and fim and fim < inicio:
fim = None
area_avaliacao_obj = dados_coord.get("areaAvaliacao", {}) or {}
area_avaliacao = area_avaliacao_obj.get("nome") if isinstance(area_avaliacao_obj, dict) else coord.get("areaAvaliacao", "N/A")
if not area_avaliacao:
area_avaliacao = coord.get("descricao", "N/A").split(" - ")[0] if coord.get("descricao") else "N/A"
resultado.append(CoordenacaoCapes(
codigo=codigo,
tipo=codigo,
area_avaliacao=area_avaliacao,
periodo=Periodo(inicio=inicio, fim=fim),
areas_adicionais=[],
ja_coordenou_antes=len(resultado) > 0,
))
return resultado
def _extrair_consultoria(self, atuacoes: List[Dict[str, Any]]) -> Optional[Consultoria]:
consultorias = [a for a in atuacoes if a.get("tipo") in ["Consultor", "Histórico de Consultoria"]]
if not consultorias:
return None
@@ -109,7 +167,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
)
if inicio and fim and fim < inicio:
fim = None # dados inconsistentes: trata como em aberto
fim = None
if inicio:
try:
periodos.append(Periodo(inicio=inicio, fim=fim))
@@ -125,283 +183,302 @@ class ConsultorRepositoryImpl(ConsultorRepository):
return None
mesclados = self._mesclar_periodos(periodos)
anos_total = sum(p.anos_completos(datetime.now()) for p in mesclados)
anos_consecutivos = max((p.anos_completos(datetime.now()) for p in mesclados), default=0)
retornos = max(0, len(mesclados) - 1)
ativo = any(p.ativo for p in periodos)
eventos_sae = [a for a in atuacoes if a.get("tipo") == "Evento"]
total_eventos = len(eventos_sae)
limite_recente = datetime.now() - timedelta(days=730)
eventos_recentes = 0
vezes_responsavel = 0
for ev in eventos_sae:
data_fim = self._parse_date(ev.get("fim")) or self._parse_date(ev.get("inicio"))
if data_fim and data_fim >= limite_recente:
eventos_recentes += 1
dados_evento = ev.get("dadosEvento", {}) or {}
if dados_evento.get("consultorResponsavel") == "Sim":
vezes_responsavel += 1
situacao_final = situacoes[0] if situacoes else "N/A"
is_ativo = ativo or "atividade" in situacao_final.lower() or "ativo" in situacao_final.lower()
is_falecido = "falecido" in situacao_final.lower()
if is_falecido:
codigo = "CONS_FALECIDO"
elif is_ativo:
codigo = "CONS_ATIVO"
else:
codigo = "CONS_HIST"
primeiro_evento = min(p.inicio for p in periodos)
ultimo_evento = max((p.fim or datetime.now()) for p in periodos) if not ativo else datetime.now()
areas = list(set(areas)) if areas else ["N/A"]
situacao_final = situacoes[0] if situacoes else "N/A"
return Consultoria(
total_eventos=total_eventos,
eventos_recentes=eventos_recentes,
primeiro_evento=primeiro_evento,
ultimo_evento=ultimo_evento,
areas=areas,
codigo=codigo,
situacao=situacao_final,
anos_completos=anos_total,
periodo=Periodo(inicio=primeiro_evento, fim=None if ativo else datetime.now()),
areas=areas,
anos_consecutivos=anos_consecutivos,
retornos=retornos,
vezes_responsavel=vezes_responsavel,
)
def _extrair_coordenacoes_capes(
self, atuacoes: List[Dict[str, Any]]
) -> List[CoordenacaoCapes]:
coordenacoes = [
a
for a in atuacoes
if a.get("tipo")
in [
"Coordenação de Área de Avaliação",
"Histórico de Coordenação de Área de Avaliação",
]
]
resultado = []
for coord in coordenacoes:
dados_coord = coord.get("dadosCoordenacaoArea", {}) or {}
inicio = (
self._parse_date(dados_coord.get("inicioVinculacao"))
or self._parse_date(coord.get("inicio"))
)
if not inicio:
def _extrair_inscricoes(self, atuacoes: List[Dict[str, Any]]) -> List[Inscricao]:
inscricoes = []
for a in atuacoes:
if a.get("tipo") != "Inscrição Prêmio":
continue
tipo = self._inferir_tipo_coordenacao(coord)
fim = (
self._parse_date(dados_coord.get("fimVinculacao"))
or self._parse_date(coord.get("fim"))
)
dados = a.get("dadosParticipacaoInscricaoPremio", {}) or {}
tipo_part = dados.get("tipo", "")
nome_premio = dados.get("nomePremio") or dados.get("premio") or a.get("descricao", "")
ano = dados.get("ano")
if not ano:
inicio = self._parse_date(a.get("inicio"))
ano = inicio.year if inicio else datetime.now().year
if inicio and fim and fim < inicio:
fim = None # ignora fins inconsistentes para não quebrar cálculo
is_institucional = "coordenador" in tipo_part.lower() or "ppg" in tipo_part.lower()
codigo = "INSC_INST" if is_institucional else "INSC_AUTOR"
area_avaliacao_obj = dados_coord.get("areaAvaliacao", {}) or {}
area_avaliacao = area_avaliacao_obj.get("nome") if isinstance(area_avaliacao_obj, dict) else coord.get("areaAvaliacao", "N/A")
if not area_avaliacao:
area_avaliacao = coord.get("descricao", "N/A").split(" - ")[0] if coord.get("descricao") else "N/A"
inscricoes.append(Inscricao(
codigo=codigo,
tipo=tipo_part,
premio=nome_premio,
ano=ano,
situacao=dados.get("situacao", ""),
))
resultado.append(
CoordenacaoCapes(
tipo=tipo,
area_avaliacao=area_avaliacao,
periodo=Periodo(inicio=inicio, fim=fim),
areas_adicionais=[],
ja_coordenou_antes=len(resultado) > 0,
)
)
return inscricoes
return resultado
def _extrair_avaliacoes_comissao(self, atuacoes: List[Dict[str, Any]]) -> List[AvaliacaoComissao]:
avaliacoes = []
for a in atuacoes:
if a.get("tipo") != "Avaliação Prêmio":
continue
def _inferir_tipo_coordenacao(self, coord: Dict[str, Any]) -> str:
dados_coord = coord.get("dadosCoordenacaoArea", {}) or {}
tipo_coord = dados_coord.get("tipo", "").lower()
dados = a.get("dadosParticipacaoPremio", {}) or {}
tipo_part = dados.get("tipo", "")
nome_premio = dados.get("nomePremio") or dados.get("premio") or a.get("descricao", "")
ano = dados.get("ano")
if not ano:
inicio = self._parse_date(a.get("inicio"))
ano = inicio.year if inicio else datetime.now().year
if "câmara" in tipo_coord or "camara" in tipo_coord:
return "CAM"
elif "adjunt" in tipo_coord:
if "profissional" in tipo_coord or "mestrado" in tipo_coord:
return "CAJ-MP"
return "CAJ"
elif "coordenador de área" in tipo_coord:
return "CA"
comissao = dados.get("comissao", {}) or {}
comissao_tipo = comissao.get("tipo", "") if isinstance(comissao, dict) else ""
descricao = coord.get("descricao", "").lower()
nome = coord.get("nome", "").lower()
texto = f"{descricao} {nome}"
is_grande_premio = "grande" in nome_premio.lower()
is_coordenador = "coordenador" in tipo_part.lower() or "presidente" in tipo_part.lower()
if "câmara" in texto or "camara" in texto:
return "CAM"
elif "mestrado profissional" in texto:
return "CAJ-MP"
elif "adjunt" in texto:
return "CAJ"
else:
return "CA"
def _classificar_nivel_premio(self, nome: str) -> str:
nome = (nome or "").lower()
if "grande prêmio capes de tese" in nome or "grande premio capes de tese" in nome:
return "nivel1_grande"
if "prêmio capes de tese" in nome or "premio capes de tese" in nome:
return "nivel1_pct"
if "interfarma" in nome or "vale-capes" in nome or "vale capes" in nome:
return "nivel2"
if nome:
return "nivel3"
return "desconhecido"
def _pontuar_premiacao_recebida(self, nivel: str, tipo_premiacao: str) -> int:
tipo = (tipo_premiacao or "").lower()
if nivel == "nivel1_grande":
base = 150
extra = 50 if "grande" in tipo else 0
return min(base + extra, 180)
if nivel == "nivel1_pct":
base = 100
if "mencao" in tipo:
extra = 15
elif "premio" in tipo:
extra = 25
if is_coordenador:
codigo = "COORD_COMIS_GP" if is_grande_premio else "COORD_COMIS_PREMIO"
else:
extra = 0
return min(base + extra, 150)
if nivel == "nivel2":
base = 30
if "premio" in tipo:
extra = 20
elif "mencao" in tipo:
extra = 10
else:
extra = 0
return min(base + extra, 60)
# nivel3 e fallback
base = 10
if "premio" in tipo:
extra = 5
elif "mencao" in tipo:
extra = 3
else:
extra = 0
return min(base + extra, 20)
codigo = "AVAL_COMIS_GP" if is_grande_premio else "AVAL_COMIS_PREMIO"
def _pontuar_participacao_premio(self, nivel: str, tipo_participacao: str) -> int:
tipo = (tipo_participacao or "").lower()
if "avaliador" in tipo or "banca" in tipo:
return 2 # teto final tratado em componente D
if "coordenador" in tipo or "comissao" in tipo or "comissão" in tipo:
if nivel == "nivel1_grande":
return 115 # valor máximo já com peso
if nivel == "nivel1_pct":
return 115 # aproximação segura para teto
if nivel == "nivel2":
return 80
return 40
if "inscricao" in tipo or "inscrição" in tipo:
if nivel in ["nivel1_grande", "nivel1_pct"]:
return 2
if nivel == "nivel2":
return 1
return 1
return 0
avaliacoes.append(AvaliacaoComissao(
codigo=codigo,
tipo=tipo_part,
premio=nome_premio,
ano=ano,
comissao_tipo=comissao_tipo,
))
return avaliacoes
def _extrair_premiacoes(self, atuacoes: List[Dict[str, Any]]) -> List[Premiacao]:
premiacoes = []
for a in atuacoes:
tipo_atuacao = a.get("tipo", "")
dados_premiacao = a.get("dadosPremiacaoPremio") or a.get("dadosPremio") or {}
dados_participacao = (
a.get("dadosParticipacaoPremio")
or a.get("dadosParticipacaoInscricaoPremio")
or {}
)
# Premiações recebidas
if dados_premiacao:
nome_premio = dados_premiacao.get("nomePremio") or a.get("descricao", "N/A")
tipo_premiacao = dados_premiacao.get("tipoPremiacao") or dados_premiacao.get("tipo", "")
ano = dados_premiacao.get("ano") or a.get("ano")
if not ano:
inicio = self._parse_date(a.get("inicio"))
ano = inicio.year if inicio else datetime.now().year
nivel = self._classificar_nivel_premio(nome_premio)
pontos = self._pontuar_premiacao_recebida(nivel, tipo_premiacao)
premiacoes.append(
Premiacao(
tipo=tipo_premiacao or tipo_atuacao or "Premiação",
nome_premio=nome_premio,
ano=ano or datetime.now().year,
pontos=int(pontos),
)
)
if a.get("tipo") != "Premiação Prêmio":
continue
# Participações (inscrição/avaliação/coordenação)
if dados_participacao:
tipo_part = (
dados_participacao.get("tipoParticipacao")
or dados_participacao.get("tipo")
or tipo_atuacao
)
nome_premio = dados_participacao.get("nomePremio") or a.get("descricao", "N/A")
ano = dados_participacao.get("ano") or a.get("ano")
if not ano:
inicio = self._parse_date(a.get("inicio"))
ano = inicio.year if inicio else datetime.now().year
nivel = self._classificar_nivel_premio(nome_premio)
pontos = self._pontuar_participacao_premio(nivel, tipo_part)
premiacoes.append(
Premiacao(
tipo=tipo_part or "Participação Prêmio",
nome_premio=nome_premio,
ano=ano or datetime.now().year,
pontos=int(pontos),
)
)
continue
# Fallback para tipos antigos
if tipo_atuacao in [
"Premiação Prêmio",
"Premiação",
"Avaliação Prêmio",
"Inscrição Prêmio",
]:
pontos = self._pontuar_participacao_premio("nivel3", tipo_atuacao)
dados = a.get("dadosPremiacaoPremio", {}) or a.get("dadosPremio", {}) or {}
tipo_premiacao = dados.get("tipoPremiacao") or dados.get("premiacao") or ""
nome_premio = dados.get("nomePremio") or dados.get("evento") or a.get("descricao", "")
ano = dados.get("ano")
if not ano:
inicio = self._parse_date(a.get("inicio"))
ano = inicio.year if inicio else datetime.now().year
premiacoes.append(
Premiacao(
tipo=tipo_atuacao,
nome_premio=a.get("descricao", "N/A"),
ano=ano,
pontos=int(pontos),
)
)
tipo_lower = tipo_premiacao.lower()
nome_lower = nome_premio.lower()
if "grande" in nome_lower or "grande" in tipo_lower:
codigo = "PREMIACAO"
elif "menção" in tipo_lower or "mencao" in tipo_lower or "honrosa" in tipo_lower:
codigo = "MENCAO"
else:
codigo = "PREMIACAO_GP"
premiacoes.append(Premiacao(
codigo=codigo,
tipo=tipo_premiacao,
nome_premio=nome_premio,
ano=ano,
))
return premiacoes
def _extrair_bolsas_cnpq(self, atuacoes: List[Dict[str, Any]]) -> List[BolsaCNPQ]:
bolsas = []
for a in atuacoes:
if a.get("tipo") != "Bolsa CNPQ" and "bolsa" not in a.get("tipo", "").lower():
continue
dados = a.get("dadosBolsa", {}) or {}
nivel = dados.get("nivel", "") or dados.get("categoria", "") or ""
area = dados.get("areaConhecimento", "") or ""
nivel_lower = nivel.lower()
if "1a" in nivel_lower or "1b" in nivel_lower or "1c" in nivel_lower or "1d" in nivel_lower:
codigo = "BOL_BPQ_SUPERIOR"
else:
codigo = "BOL_BPQ_INTERMEDIARIO"
bolsas.append(BolsaCNPQ(
codigo=codigo,
nivel=nivel,
area=area,
))
return bolsas
def _extrair_participacoes(self, atuacoes: List[Dict[str, Any]]) -> List[Participacao]:
participacoes = []
for a in atuacoes:
tipo = a.get("tipo", "")
if tipo == "Evento":
participacoes.append(Participacao(
codigo="EVENTO",
tipo="Evento",
descricao=a.get("descricao", ""),
ano=self._parse_date(a.get("inicio")).year if self._parse_date(a.get("inicio")) else None,
))
elif tipo == "Projeto" or "projeto" in tipo.lower():
participacoes.append(Participacao(
codigo="PROJ",
tipo="Projeto",
descricao=a.get("descricao", ""),
ano=self._parse_date(a.get("inicio")).year if self._parse_date(a.get("inicio")) else None,
))
return participacoes
def _extrair_orientacoes(self, atuacoes: List[Dict[str, Any]]) -> List[Orientacao]:
orientacoes = []
for a in atuacoes:
tipo = a.get("tipo", "").lower()
if "orientação" not in tipo and "orientacao" not in tipo:
continue
if "co-orientação" in tipo or "coorientação" in tipo or "co_orient" in tipo:
continue
dados = a.get("dadosOrientacao", {}) or {}
nivel = dados.get("nivel", "") or dados.get("tipo", "") or ""
ano = dados.get("ano")
if not ano:
inicio = self._parse_date(a.get("inicio"))
ano = inicio.year if inicio else None
nivel_lower = nivel.lower()
if "pós-doc" in nivel_lower or "pos-doc" in nivel_lower or "posdoc" in nivel_lower:
codigo = "ORIENT_POS_DOC"
elif "tese" in nivel_lower or "doutorado" in nivel_lower:
codigo = "ORIENT_TESE"
else:
codigo = "ORIENT_DISS"
orientacoes.append(Orientacao(
codigo=codigo,
tipo=tipo,
nivel=nivel,
ano=ano,
))
return orientacoes
def _extrair_coorientacoes(self, atuacoes: List[Dict[str, Any]]) -> List[Orientacao]:
coorientacoes = []
for a in atuacoes:
tipo = a.get("tipo", "").lower()
if "co-orientação" not in tipo and "coorientação" not in tipo and "co_orient" not in tipo:
continue
dados = a.get("dadosOrientacao", {}) or a.get("dadosCoorientacao", {}) or {}
nivel = dados.get("nivel", "") or dados.get("tipo", "") or ""
ano = dados.get("ano")
if not ano:
inicio = self._parse_date(a.get("inicio"))
ano = inicio.year if inicio else None
nivel_lower = nivel.lower()
if "pós-doc" in nivel_lower or "pos-doc" in nivel_lower or "posdoc" in nivel_lower:
codigo = "CO_ORIENT_POS_DOC"
elif "tese" in nivel_lower or "doutorado" in nivel_lower:
codigo = "CO_ORIENT_TESE"
else:
codigo = "CO_ORIENT_DISS"
coorientacoes.append(Orientacao(
codigo=codigo,
tipo=tipo,
nivel=nivel,
ano=ano,
))
return coorientacoes
def _extrair_membros_banca(self, atuacoes: List[Dict[str, Any]]) -> List[MembroBanca]:
membros = []
for a in atuacoes:
tipo = a.get("tipo", "").lower()
if "banca" not in tipo:
continue
dados = a.get("dadosBanca", {}) or {}
nivel = dados.get("nivel", "") or dados.get("tipo", "") or ""
ano = dados.get("ano")
if not ano:
inicio = self._parse_date(a.get("inicio"))
ano = inicio.year if inicio else None
nivel_lower = nivel.lower()
if "pós-doc" in nivel_lower or "pos-doc" in nivel_lower or "posdoc" in nivel_lower:
codigo = "MB_BANCA_POS_DOC"
elif "tese" in nivel_lower or "doutorado" in nivel_lower:
codigo = "MB_BANCA_TESE"
else:
codigo = "MB_BANCA_DISS"
membros.append(MembroBanca(
codigo=codigo,
tipo=tipo,
nivel=nivel,
ano=ano,
))
return membros
async def _construir_consultor(self, doc: Dict[str, Any]) -> Consultor:
id_pessoa = doc["id"]
dados_pessoais = doc.get("dadosPessoais", {})
atuacoes = doc.get("atuacoes", [])
consultoria = self._extrair_consultoria(atuacoes)
coordenacoes_capes = self._extrair_coordenacoes_capes(atuacoes)
consultoria = self._extrair_consultoria(atuacoes)
inscricoes = self._extrair_inscricoes(atuacoes)
avaliacoes = self._extrair_avaliacoes_comissao(atuacoes)
premiacoes = self._extrair_premiacoes(atuacoes)
bolsas = self._extrair_bolsas_cnpq(atuacoes)
participacoes = self._extrair_participacoes(atuacoes)
orientacoes = self._extrair_orientacoes(atuacoes)
coorientacoes = self._extrair_coorientacoes(atuacoes)
membros_banca = self._extrair_membros_banca(atuacoes)
consultor = Consultor(
id_pessoa=id_pessoa,
nome=dados_pessoais.get("nome", "N/A"),
cpf=dados_pessoais.get("cpf"),
coordenacoes_capes=coordenacoes_capes,
coordenacoes_programas=[], # PPG vem do job/ETL de Componente B
consultoria=consultoria,
inscricoes=inscricoes,
avaliacoes_comissao=avaliacoes,
premiacoes=premiacoes,
bolsas_cnpq=bolsas,
participacoes=participacoes,
orientacoes=orientacoes + coorientacoes,
membros_banca=membros_banca,
)
consultor.pontuacao = self.calculador.calcular_pontuacao_completa(consultor)
@@ -457,8 +534,6 @@ class ConsultorRepositoryImpl(ConsultorRepository):
consultores = []
for doc in docs:
consultor = await self._construir_consultor(doc)
score_es = doc.get("_score_es", 0)
consultor.score_es = score_es
consultores.append(consultor)
consultores_ordenados = sorted(