fix(backend): corrigir exibicao de idiomas e selos multilingue
- Adicionar idiomas e formacoes ao _source das queries ES (client.py) - Corrigir type mismatch int/str no endpoint paginado (routes.py) - Adicionar campo evento nas inscricoes para nome do premio - Implementar extracao de idiomas do ES no repository - Ajustar frontend para exibir selo multilingue corretamente
This commit is contained in:
@@ -57,6 +57,7 @@ class InscricaoDTO:
|
|||||||
premio: str
|
premio: str
|
||||||
ano: int
|
ano: int
|
||||||
situacao: str
|
situacao: str
|
||||||
|
evento: str = ""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -112,6 +113,15 @@ class MembroBancaDTO:
|
|||||||
ano: Optional[int]
|
ano: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class IdiomaDTO:
|
||||||
|
idioma: str
|
||||||
|
nivel_leitura: str = ""
|
||||||
|
nivel_escrita: str = ""
|
||||||
|
nivel_fala: str = ""
|
||||||
|
nivel_compreensao: str = ""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PontuacaoAtuacaoDTO:
|
class PontuacaoAtuacaoDTO:
|
||||||
codigo: str
|
codigo: str
|
||||||
@@ -168,6 +178,7 @@ class ConsultorDetalhadoDTO:
|
|||||||
participacoes: List[ParticipacaoDTO]
|
participacoes: List[ParticipacaoDTO]
|
||||||
orientacoes: List[OrientacaoDTO]
|
orientacoes: List[OrientacaoDTO]
|
||||||
membros_banca: List[MembroBancaDTO]
|
membros_banca: List[MembroBancaDTO]
|
||||||
|
idiomas: List[IdiomaDTO]
|
||||||
pontuacao: PontuacaoCompletaDTO
|
pontuacao: PontuacaoCompletaDTO
|
||||||
rank: Optional[int] = None
|
rank: Optional[int] = None
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ class ProcessarRankingJob:
|
|||||||
"tipo": i.tipo,
|
"tipo": i.tipo,
|
||||||
"premio": i.premio,
|
"premio": i.premio,
|
||||||
"ano": i.ano,
|
"ano": i.ano,
|
||||||
"situacao": i.situacao
|
"situacao": i.situacao,
|
||||||
|
"evento": i.evento
|
||||||
}
|
}
|
||||||
for i in consultor.inscricoes
|
for i in consultor.inscricoes
|
||||||
],
|
],
|
||||||
@@ -223,6 +224,17 @@ class ProcessarRankingJob:
|
|||||||
}
|
}
|
||||||
for d in consultor.docencias
|
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,
|
"pontuacao": pontuacao,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from ..dtos.consultor_dto import (
|
|||||||
ParticipacaoDTO,
|
ParticipacaoDTO,
|
||||||
OrientacaoDTO,
|
OrientacaoDTO,
|
||||||
MembroBancaDTO,
|
MembroBancaDTO,
|
||||||
|
IdiomaDTO,
|
||||||
PontuacaoAtuacaoDTO,
|
PontuacaoAtuacaoDTO,
|
||||||
PontuacaoBlocoDTO,
|
PontuacaoBlocoDTO,
|
||||||
PontuacaoCompletaDTO,
|
PontuacaoCompletaDTO,
|
||||||
@@ -118,6 +119,7 @@ class ObterRankingUseCase:
|
|||||||
premio=i.premio,
|
premio=i.premio,
|
||||||
ano=i.ano,
|
ano=i.ano,
|
||||||
situacao=i.situacao,
|
situacao=i.situacao,
|
||||||
|
evento=i.evento,
|
||||||
)
|
)
|
||||||
for i in consultor.inscricoes
|
for i in consultor.inscricoes
|
||||||
],
|
],
|
||||||
@@ -180,6 +182,16 @@ class ObterRankingUseCase:
|
|||||||
)
|
)
|
||||||
for m in consultor.membros_banca
|
for m in consultor.membros_banca
|
||||||
],
|
],
|
||||||
|
idiomas=[
|
||||||
|
IdiomaDTO(
|
||||||
|
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
|
||||||
|
],
|
||||||
pontuacao=PontuacaoCompletaDTO(
|
pontuacao=PontuacaoCompletaDTO(
|
||||||
bloco_a=PontuacaoBlocoDTO(
|
bloco_a=PontuacaoBlocoDTO(
|
||||||
bloco="A",
|
bloco="A",
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class Inscricao:
|
|||||||
premio: str
|
premio: str
|
||||||
ano: int
|
ano: int
|
||||||
situacao: str = ""
|
situacao: str = ""
|
||||||
|
evento: str = ""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -119,6 +120,15 @@ class DocenciaPPG:
|
|||||||
linhas_pesquisa: List[str] = field(default_factory=list)
|
linhas_pesquisa: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Idioma:
|
||||||
|
idioma: str
|
||||||
|
nivel_leitura: str = ""
|
||||||
|
nivel_escrita: str = ""
|
||||||
|
nivel_fala: str = ""
|
||||||
|
nivel_compreensao: str = ""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Consultor:
|
class Consultor:
|
||||||
id_pessoa: int
|
id_pessoa: int
|
||||||
@@ -135,6 +145,8 @@ class Consultor:
|
|||||||
orientacoes: List[Orientacao] = field(default_factory=list)
|
orientacoes: List[Orientacao] = field(default_factory=list)
|
||||||
membros_banca: List[MembroBanca] = field(default_factory=list)
|
membros_banca: List[MembroBanca] = field(default_factory=list)
|
||||||
docencias: List[DocenciaPPG] = field(default_factory=list)
|
docencias: List[DocenciaPPG] = field(default_factory=list)
|
||||||
|
idiomas: List[Idioma] = field(default_factory=list)
|
||||||
|
titulacao: Optional[str] = None
|
||||||
pontuacao: Optional[PontuacaoCompleta] = None
|
pontuacao: Optional[PontuacaoCompleta] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class ElasticsearchClient:
|
|||||||
try:
|
try:
|
||||||
query = {
|
query = {
|
||||||
"query": {"term": {"id": id_pessoa}},
|
"query": {"term": {"id": id_pessoa}},
|
||||||
"_source": ["id", "dadosPessoais", "atuacoes"],
|
"_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"],
|
||||||
"size": 1,
|
"size": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +68,28 @@ class ElasticsearchClient:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError(f"Erro ao buscar consultor {id_pessoa}: {e}")
|
raise RuntimeError(f"Erro ao buscar consultor {id_pessoa}: {e}")
|
||||||
|
|
||||||
|
async def buscar_por_ids(self, ids: list, source_fields: Optional[list] = None) -> list:
|
||||||
|
if not ids:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
query = {
|
||||||
|
"query": {"terms": {"id": ids}},
|
||||||
|
"size": len(ids),
|
||||||
|
}
|
||||||
|
if source_fields:
|
||||||
|
query["_source"] = source_fields
|
||||||
|
|
||||||
|
response = await self.client.post(
|
||||||
|
f"{self.url}/{self.index}/_search",
|
||||||
|
json=query
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
return [hit["_source"] for hit in data.get("hits", {}).get("hits", [])]
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Erro ao buscar consultores por ids: {e}")
|
||||||
|
|
||||||
async def buscar_documento_completo(self, id_pessoa: int) -> Optional[dict]:
|
async def buscar_documento_completo(self, id_pessoa: int) -> Optional[dict]:
|
||||||
try:
|
try:
|
||||||
query = {
|
query = {
|
||||||
@@ -104,7 +126,7 @@ class ElasticsearchClient:
|
|||||||
"query": {"exists": {"field": "atuacoes.tipo"}}
|
"query": {"exists": {"field": "atuacoes.tipo"}}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"_source": ["id", "dadosPessoais", "atuacoes"],
|
"_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"],
|
||||||
"size": size,
|
"size": size,
|
||||||
"from": from_,
|
"from": from_,
|
||||||
"sort": [{"id": "asc"}],
|
"sort": [{"id": "asc"}],
|
||||||
@@ -196,7 +218,7 @@ class ElasticsearchClient:
|
|||||||
"minimum_should_match": 1
|
"minimum_should_match": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"_source": ["id", "dadosPessoais", "atuacoes"],
|
"_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"],
|
||||||
"sort": [{"_score": "desc"}]
|
"sort": [{"_score": "desc"}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +401,7 @@ class ElasticsearchClient:
|
|||||||
"boost_mode": "replace"
|
"boost_mode": "replace"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"_source": ["id", "dadosPessoais", "atuacoes"],
|
"_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"],
|
||||||
"sort": [{"_score": "desc"}]
|
"sort": [{"_score": "desc"}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +434,7 @@ class ElasticsearchClient:
|
|||||||
"query": {"exists": {"field": "atuacoes.tipo"}}
|
"query": {"exists": {"field": "atuacoes.tipo"}}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"_source": ["id", "dadosPessoais", "atuacoes"],
|
"_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"],
|
||||||
"size": size,
|
"size": size,
|
||||||
"sort": [{"id": "asc"}]
|
"sort": [{"id": "asc"}]
|
||||||
}
|
}
|
||||||
@@ -638,7 +660,7 @@ class ElasticsearchClient:
|
|||||||
"minimum_should_match": 1
|
"minimum_should_match": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"_source": ["id", "dadosPessoais", "atuacoes"],
|
"_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"],
|
||||||
"sort": [{"_score": "desc"}]
|
"sort": [{"_score": "desc"}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from ...domain.entities.consultor import (
|
|||||||
Orientacao,
|
Orientacao,
|
||||||
MembroBanca,
|
MembroBanca,
|
||||||
DocenciaPPG,
|
DocenciaPPG,
|
||||||
|
Idioma,
|
||||||
)
|
)
|
||||||
from ...domain.repositories.consultor_repository import ConsultorRepository
|
from ...domain.repositories.consultor_repository import ConsultorRepository
|
||||||
from ...domain.services.calculador_pontuacao import CalculadorPontuacao
|
from ...domain.services.calculador_pontuacao import CalculadorPontuacao
|
||||||
@@ -277,6 +278,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
premio=nome_premio,
|
premio=nome_premio,
|
||||||
ano=ano,
|
ano=ano,
|
||||||
situacao=dados.get("situacao", ""),
|
situacao=dados.get("situacao", ""),
|
||||||
|
evento=dados.get("evento", ""),
|
||||||
))
|
))
|
||||||
|
|
||||||
return inscricoes
|
return inscricoes
|
||||||
@@ -604,6 +606,77 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
docencias.sort(key=lambda d: (d.periodo.fim is not None, d.periodo.inicio or datetime.min), reverse=True)
|
docencias.sort(key=lambda d: (d.periodo.fim is not None, d.periodo.inicio or datetime.min), reverse=True)
|
||||||
return docencias
|
return docencias
|
||||||
|
|
||||||
|
def _extrair_idiomas(self, doc: Dict[str, Any]) -> List[Idioma]:
|
||||||
|
idiomas_data = doc.get("idiomas") or []
|
||||||
|
if not idiomas_data:
|
||||||
|
dados_pessoais = doc.get("dadosPessoais", {}) or {}
|
||||||
|
idiomas_data = (
|
||||||
|
dados_pessoais.get("idiomas")
|
||||||
|
or dados_pessoais.get("idiomasEstrangeiros")
|
||||||
|
or []
|
||||||
|
)
|
||||||
|
idiomas = []
|
||||||
|
|
||||||
|
for item in idiomas_data:
|
||||||
|
if isinstance(item, str):
|
||||||
|
idioma_nome = item
|
||||||
|
item_dict = {}
|
||||||
|
else:
|
||||||
|
item_dict = item or {}
|
||||||
|
idioma_nome = item_dict.get("idioma") or item_dict.get("nome") or item_dict.get("descricao")
|
||||||
|
if isinstance(idioma_nome, dict):
|
||||||
|
idioma_nome = (
|
||||||
|
idioma_nome.get("nome")
|
||||||
|
or idioma_nome.get("descricao")
|
||||||
|
or idioma_nome.get("idioma")
|
||||||
|
)
|
||||||
|
if not idioma_nome:
|
||||||
|
continue
|
||||||
|
nivel_padrao = (
|
||||||
|
item_dict.get("nivel")
|
||||||
|
or item_dict.get("nivelConhecimento")
|
||||||
|
or item_dict.get("nivel_leitura")
|
||||||
|
or item_dict.get("nivelLeitura")
|
||||||
|
or ""
|
||||||
|
)
|
||||||
|
idiomas.append(Idioma(
|
||||||
|
idioma=idioma_nome,
|
||||||
|
nivel_leitura=item_dict.get("nivelLeitura", "") or item_dict.get("leitura", "") or nivel_padrao,
|
||||||
|
nivel_escrita=item_dict.get("nivelEscrita", "") or item_dict.get("escrita", "") or nivel_padrao,
|
||||||
|
nivel_fala=item_dict.get("nivelFala", "") or item_dict.get("fala", "") or nivel_padrao,
|
||||||
|
nivel_compreensao=item_dict.get("nivelCompreensao", "") or item_dict.get("compreensao", "") or nivel_padrao,
|
||||||
|
))
|
||||||
|
|
||||||
|
return idiomas
|
||||||
|
|
||||||
|
def _extrair_titulacao(self, doc: Dict[str, Any]) -> Optional[str]:
|
||||||
|
dados_pessoais = doc.get("dadosPessoais", {}) or {}
|
||||||
|
titulacao = dados_pessoais.get("titulacao") or dados_pessoais.get("grauFormacao")
|
||||||
|
if titulacao:
|
||||||
|
return titulacao
|
||||||
|
|
||||||
|
formacoes = doc.get("formacoes", []) or []
|
||||||
|
for f in formacoes:
|
||||||
|
nivel = f.get("nivel", "").lower()
|
||||||
|
if "pós-doutorado" in nivel or "pos-doutorado" in nivel or "posdoc" in nivel:
|
||||||
|
return "Pós-Doutorado"
|
||||||
|
elif "doutorado" in nivel:
|
||||||
|
return "Doutorado"
|
||||||
|
elif "mestrado" in nivel:
|
||||||
|
return "Mestrado"
|
||||||
|
|
||||||
|
atuacoes = doc.get("atuacoes", []) or []
|
||||||
|
for a in atuacoes:
|
||||||
|
if a.get("tipo") == "Docência":
|
||||||
|
dados = a.get("dadosDocencia", {}) or {}
|
||||||
|
categoria = (dados.get("categoria", "") or "").lower()
|
||||||
|
if "doutor" in categoria:
|
||||||
|
return "Doutorado"
|
||||||
|
elif "mestre" in categoria:
|
||||||
|
return "Mestrado"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
async def _construir_consultor(self, doc: Dict[str, Any]) -> Consultor:
|
async def _construir_consultor(self, doc: Dict[str, Any]) -> Consultor:
|
||||||
id_pessoa = doc["id"]
|
id_pessoa = doc["id"]
|
||||||
dados_pessoais = doc.get("dadosPessoais", {})
|
dados_pessoais = doc.get("dadosPessoais", {})
|
||||||
@@ -621,6 +694,8 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
membros_banca = self._extrair_membros_banca(atuacoes)
|
membros_banca = self._extrair_membros_banca(atuacoes)
|
||||||
docencias = self._extrair_docencias(atuacoes)
|
docencias = self._extrair_docencias(atuacoes)
|
||||||
coordenador_ppg = self._tem_coordenacao_ppg(atuacoes)
|
coordenador_ppg = self._tem_coordenacao_ppg(atuacoes)
|
||||||
|
idiomas = self._extrair_idiomas(doc)
|
||||||
|
titulacao = self._extrair_titulacao(doc)
|
||||||
|
|
||||||
consultor = Consultor(
|
consultor = Consultor(
|
||||||
id_pessoa=id_pessoa,
|
id_pessoa=id_pessoa,
|
||||||
@@ -637,6 +712,8 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
orientacoes=orientacoes + coorientacoes,
|
orientacoes=orientacoes + coorientacoes,
|
||||||
membros_banca=membros_banca,
|
membros_banca=membros_banca,
|
||||||
docencias=docencias,
|
docencias=docencias,
|
||||||
|
idiomas=idiomas,
|
||||||
|
titulacao=titulacao,
|
||||||
)
|
)
|
||||||
|
|
||||||
consultor.pontuacao = self.calculador.calcular_pontuacao_completa(consultor)
|
consultor.pontuacao = self.calculador.calcular_pontuacao_completa(consultor)
|
||||||
|
|||||||
@@ -206,6 +206,8 @@ async def ranking_paginado(
|
|||||||
ativo: Optional[bool] = Query(default=None, description="Filtrar por status ativo"),
|
ativo: Optional[bool] = Query(default=None, description="Filtrar por status ativo"),
|
||||||
selos: Optional[str] = Query(default=None, description="Filtrar por selos (separados por vírgula)"),
|
selos: Optional[str] = Query(default=None, description="Filtrar por selos (separados por vírgula)"),
|
||||||
oracle_repo = Depends(get_ranking_oracle_repo),
|
oracle_repo = Depends(get_ranking_oracle_repo),
|
||||||
|
es_client: ElasticsearchClient = Depends(get_es_client),
|
||||||
|
repository: ConsultorRepositoryImpl = Depends(get_repository),
|
||||||
):
|
):
|
||||||
import json as json_lib
|
import json as json_lib
|
||||||
|
|
||||||
@@ -223,12 +225,46 @@ async def ranking_paginado(
|
|||||||
total_pages = (total + size - 1) // size
|
total_pages = (total + size - 1) // size
|
||||||
|
|
||||||
consultores_schema = []
|
consultores_schema = []
|
||||||
|
consultores_dados = []
|
||||||
|
faltando_idiomas = []
|
||||||
for c in consultores:
|
for c in consultores:
|
||||||
try:
|
try:
|
||||||
d = json_lib.loads(c.json_detalhes) if isinstance(c.json_detalhes, str) else c.json_detalhes or {}
|
d = json_lib.loads(c.json_detalhes) if isinstance(c.json_detalhes, str) else c.json_detalhes or {}
|
||||||
except (json_lib.JSONDecodeError, TypeError):
|
except (json_lib.JSONDecodeError, TypeError):
|
||||||
d = {}
|
d = {}
|
||||||
|
consultores_dados.append((c, d))
|
||||||
|
if not d.get("idiomas"):
|
||||||
|
faltando_idiomas.append((c.id_pessoa, d))
|
||||||
|
|
||||||
|
if faltando_idiomas:
|
||||||
|
ids = [item[0] for item in faltando_idiomas]
|
||||||
|
docs = await es_client.buscar_por_ids(
|
||||||
|
ids,
|
||||||
|
source_fields=["id", "dadosPessoais", "idiomas", "atuacoes", "formacoes"],
|
||||||
|
)
|
||||||
|
docs_map = {int(doc.get("id")): doc for doc in docs if doc.get("id")}
|
||||||
|
for id_pessoa, detalhes in faltando_idiomas:
|
||||||
|
doc = docs_map.get(int(id_pessoa))
|
||||||
|
if not doc:
|
||||||
|
continue
|
||||||
|
idiomas = repository._extrair_idiomas(doc)
|
||||||
|
if idiomas:
|
||||||
|
detalhes["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 idiomas
|
||||||
|
]
|
||||||
|
if not detalhes.get("titulacao"):
|
||||||
|
titulacao = repository._extrair_titulacao(doc)
|
||||||
|
if titulacao:
|
||||||
|
detalhes["titulacao"] = titulacao
|
||||||
|
|
||||||
|
for c, d in consultores_dados:
|
||||||
tipos_atuacao = RankingMapper._extrair_tipos_atuacao(d)
|
tipos_atuacao = RankingMapper._extrair_tipos_atuacao(d)
|
||||||
consultores_schema.append(
|
consultores_schema.append(
|
||||||
ConsultorRankingResumoSchema(
|
ConsultorRankingResumoSchema(
|
||||||
@@ -255,6 +291,8 @@ async def ranking_paginado(
|
|||||||
orientacoes=d.get("orientacoes"),
|
orientacoes=d.get("orientacoes"),
|
||||||
membros_banca=d.get("membros_banca"),
|
membros_banca=d.get("membros_banca"),
|
||||||
docencias=d.get("docencias"),
|
docencias=d.get("docencias"),
|
||||||
|
idiomas=d.get("idiomas"),
|
||||||
|
titulacao=d.get("titulacao"),
|
||||||
pontuacao=d.get("pontuacao"),
|
pontuacao=d.get("pontuacao"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class InscricaoSchema(BaseModel):
|
|||||||
premio: str
|
premio: str
|
||||||
ano: int
|
ano: int
|
||||||
situacao: str
|
situacao: str
|
||||||
|
evento: str = ""
|
||||||
|
|
||||||
|
|
||||||
class AvaliacaoComissaoSchema(BaseModel):
|
class AvaliacaoComissaoSchema(BaseModel):
|
||||||
@@ -96,6 +97,14 @@ class MembroBancaSchema(BaseModel):
|
|||||||
ano: Optional[int] = None
|
ano: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class IdiomaSchema(BaseModel):
|
||||||
|
idioma: str
|
||||||
|
nivel_leitura: str = ""
|
||||||
|
nivel_escrita: str = ""
|
||||||
|
nivel_fala: str = ""
|
||||||
|
nivel_compreensao: str = ""
|
||||||
|
|
||||||
|
|
||||||
class PontuacaoAtuacaoSchema(BaseModel):
|
class PontuacaoAtuacaoSchema(BaseModel):
|
||||||
codigo: str
|
codigo: str
|
||||||
base: int
|
base: int
|
||||||
@@ -147,6 +156,7 @@ class ConsultorDetalhadoSchema(BaseModel):
|
|||||||
participacoes: List[ParticipacaoSchema]
|
participacoes: List[ParticipacaoSchema]
|
||||||
orientacoes: List[OrientacaoSchema]
|
orientacoes: List[OrientacaoSchema]
|
||||||
membros_banca: List[MembroBancaSchema]
|
membros_banca: List[MembroBancaSchema]
|
||||||
|
idiomas: List[IdiomaSchema] = []
|
||||||
pontuacao: PontuacaoCompletaSchema
|
pontuacao: PontuacaoCompletaSchema
|
||||||
rank: Optional[int] = None
|
rank: Optional[int] = None
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ class ConsultorRankingResumoSchema(BaseModel):
|
|||||||
orientacoes: Optional[list] = None
|
orientacoes: Optional[list] = None
|
||||||
membros_banca: Optional[list] = None
|
membros_banca: Optional[list] = None
|
||||||
docencias: Optional[list] = None
|
docencias: Optional[list] = None
|
||||||
|
idiomas: Optional[list] = None
|
||||||
|
titulacao: Optional[str] = None
|
||||||
pontuacao: Optional[dict] = None
|
pontuacao: Optional[dict] = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ const DADOS_BLOCOS = {
|
|||||||
],
|
],
|
||||||
selos: [
|
selos: [
|
||||||
{ cod: 'PPG_COORD', nome: 'Coordenador PPG', obs: 'Indicador (sem pontuação V1)' },
|
{ cod: 'PPG_COORD', nome: 'Coordenador PPG', obs: 'Indicador (sem pontuação V1)' },
|
||||||
{ cod: 'IDIOMA_BILINGUE', nome: 'Bilíngue', obs: '2+ idiomas' },
|
|
||||||
{ cod: 'IDIOMA_MULTILINGUE', nome: 'Multilíngue', obs: '3+ idiomas' },
|
{ cod: 'IDIOMA_MULTILINGUE', nome: 'Multilíngue', obs: '3+ idiomas' },
|
||||||
{ cod: 'TITULACAO_MESTRE', nome: 'Mestre', obs: 'Maior titulação' },
|
{ cod: 'TITULACAO_MESTRE', nome: 'Mestre', obs: 'Maior titulação' },
|
||||||
{ cod: 'TITULACAO_DOUTOR', nome: 'Doutor', obs: 'Maior titulação' },
|
{ cod: 'TITULACAO_DOUTOR', nome: 'Doutor', obs: 'Maior titulação' },
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ const SELOS = {
|
|||||||
MB_BANCA_DISS: { codigo: 'MB_BANCA_DISS', label: 'Banca Diss.', cor: 'selo-banca', icone: '📄' },
|
MB_BANCA_DISS: { codigo: 'MB_BANCA_DISS', label: 'Banca Diss.', cor: 'selo-banca', icone: '📄' },
|
||||||
EVENTO: { codigo: 'EVENTO', label: 'Evento', cor: 'selo-evento', icone: '📅' },
|
EVENTO: { codigo: 'EVENTO', label: 'Evento', cor: 'selo-evento', icone: '📅' },
|
||||||
PROJ: { codigo: 'PROJ', label: 'Projeto', cor: 'selo-proj', icone: '📁' },
|
PROJ: { codigo: 'PROJ', label: 'Projeto', cor: 'selo-proj', icone: '📁' },
|
||||||
IDIOMA_BILINGUE: { codigo: 'IDIOMA_BILINGUE', label: 'Bilingue', cor: 'selo-idioma', icone: '🌍' },
|
|
||||||
IDIOMA_MULTILINGUE: { codigo: 'IDIOMA_MULTILINGUE', label: 'Multilingue', cor: 'selo-idioma', icone: '🌐' },
|
IDIOMA_MULTILINGUE: { codigo: 'IDIOMA_MULTILINGUE', label: 'Multilingue', cor: 'selo-idioma', icone: '🌐' },
|
||||||
TITULACAO_MESTRE: { codigo: 'TITULACAO_MESTRE', label: 'Mestre', cor: 'selo-titulacao', icone: '🎓' },
|
TITULACAO_MESTRE: { codigo: 'TITULACAO_MESTRE', label: 'Mestre', cor: 'selo-titulacao', icone: '🎓' },
|
||||||
TITULACAO_DOUTOR: { codigo: 'TITULACAO_DOUTOR', label: 'Doutor', cor: 'selo-titulacao', icone: '🎓' },
|
TITULACAO_DOUTOR: { codigo: 'TITULACAO_DOUTOR', label: 'Doutor', cor: 'selo-titulacao', icone: '🎓' },
|
||||||
@@ -49,6 +48,13 @@ const TIPOS_ATUACAO_CONFIG = {
|
|||||||
|
|
||||||
const gerarSelos = (consultor) => {
|
const gerarSelos = (consultor) => {
|
||||||
const selos = [];
|
const selos = [];
|
||||||
|
const normalizarIdioma = (valor) => (valor || '')
|
||||||
|
.toString()
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.trim();
|
||||||
|
const normalizarCodigoIdioma = (valor) => normalizarIdioma(valor).replace(/[^a-z0-9]/g, '');
|
||||||
|
|
||||||
const isPresidCamaraVigente = consultor.coordenacoes_capes?.some(
|
const isPresidCamaraVigente = consultor.coordenacoes_capes?.some(
|
||||||
(c) => c.codigo === 'CAM' && c.presidente && (c.ativo ?? !c.fim)
|
(c) => c.codigo === 'CAM' && c.presidente && (c.ativo ?? !c.fim)
|
||||||
@@ -132,6 +138,40 @@ const gerarSelos = (consultor) => {
|
|||||||
selos.push({ ...SELOS.PROJ, qtd: projetos.length, hint: `Projetos (${projetos.length}x)` });
|
selos.push({ ...SELOS.PROJ, qtd: projetos.length, hint: `Projetos (${projetos.length}x)` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const idiomas = Array.isArray(consultor.idiomas) ? consultor.idiomas : [];
|
||||||
|
const idiomasUnicosMap = new Map();
|
||||||
|
let temPortugues = false;
|
||||||
|
for (const idioma of idiomas) {
|
||||||
|
const nome = idioma?.idioma || idioma?.nome || idioma?.descricao || '';
|
||||||
|
const chave = normalizarIdioma(nome);
|
||||||
|
if (!chave) continue;
|
||||||
|
if (!idiomasUnicosMap.has(chave)) {
|
||||||
|
idiomasUnicosMap.set(chave, nome);
|
||||||
|
}
|
||||||
|
if (chave.includes('portugues') || chave.includes('portuguese')) {
|
||||||
|
temPortugues = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const idiomasUnicos = Array.from(idiomasUnicosMap.values());
|
||||||
|
const totalIdiomas = idiomasUnicos.length + (!temPortugues && idiomasUnicos.length > 0 ? 1 : 0);
|
||||||
|
if (totalIdiomas >= 3) {
|
||||||
|
selos.push({
|
||||||
|
...SELOS.IDIOMA_MULTILINGUE,
|
||||||
|
qtd: totalIdiomas,
|
||||||
|
hint: `Multilingue: ${idiomasUnicos.join(', ')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const titulacao = consultor.titulacao || '';
|
||||||
|
const titulacaoLower = titulacao.toLowerCase();
|
||||||
|
if (titulacaoLower.includes('pós-doutorado') || titulacaoLower.includes('pos-doutorado') || titulacaoLower.includes('posdoc') || titulacaoLower.includes('pós-doc')) {
|
||||||
|
selos.push({ ...SELOS.TITULACAO_POS_DOUTOR, qtd: 1, hint: 'Pós-Doutorado' });
|
||||||
|
} else if (titulacaoLower.includes('doutorado') || titulacaoLower.includes('doutor')) {
|
||||||
|
selos.push({ ...SELOS.TITULACAO_DOUTOR, qtd: 1, hint: 'Doutorado' });
|
||||||
|
} else if (titulacaoLower.includes('mestrado') || titulacaoLower.includes('mestre')) {
|
||||||
|
selos.push({ ...SELOS.TITULACAO_MESTRE, qtd: 1, hint: 'Mestrado' });
|
||||||
|
}
|
||||||
|
|
||||||
return selos;
|
return selos;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -144,6 +184,8 @@ const SELOS_COM_DADOS = [
|
|||||||
'CO_ORIENT_TESE', 'CO_ORIENT_DISS', 'CO_ORIENT_POS_DOC',
|
'CO_ORIENT_TESE', 'CO_ORIENT_DISS', 'CO_ORIENT_POS_DOC',
|
||||||
'MB_BANCA_POS_DOC', 'MB_BANCA_TESE', 'MB_BANCA_DISS',
|
'MB_BANCA_POS_DOC', 'MB_BANCA_TESE', 'MB_BANCA_DISS',
|
||||||
'EVENTO', 'PROJ',
|
'EVENTO', 'PROJ',
|
||||||
|
'IDIOMA_MULTILINGUE',
|
||||||
|
'TITULACAO_MESTRE', 'TITULACAO_DOUTOR', 'TITULACAO_POS_DOUTOR',
|
||||||
];
|
];
|
||||||
|
|
||||||
const SelosBadges = ({ selos, compacto = false, onSeloClick }) => {
|
const SelosBadges = ({ selos, compacto = false, onSeloClick }) => {
|
||||||
@@ -355,7 +397,7 @@ const TipoAtuacaoModal = ({ tipo, consultor, onClose }) => {
|
|||||||
{[...inscs].sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((ins, i) => (
|
{[...inscs].sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((ins, i) => (
|
||||||
<div key={i} className="modal-item">
|
<div key={i} className="modal-item">
|
||||||
<span className="badge">{ins.codigo}</span>
|
<span className="badge">{ins.codigo}</span>
|
||||||
<span className="modal-item-main">{ins.premio || ins.descricao || '-'}</span>
|
<span className="modal-item-main">{ins.evento || ins.premio || ins.descricao || '-'}</span>
|
||||||
<span className="muted">{ins.ano || '-'}</span>
|
<span className="muted">{ins.ano || '-'}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -837,6 +879,12 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => {
|
|||||||
<span className="modal-detalhe-label">Código</span>
|
<span className="modal-detalhe-label">Código</span>
|
||||||
<span className="badge">{item.codigo}</span>
|
<span className="badge">{item.codigo}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{item.evento && (
|
||||||
|
<div className="modal-detalhe-row">
|
||||||
|
<span className="modal-detalhe-label">Evento</span>
|
||||||
|
<span className="modal-detalhe-value">{item.evento}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="modal-detalhe-row">
|
<div className="modal-detalhe-row">
|
||||||
<span className="modal-detalhe-label">Prêmio</span>
|
<span className="modal-detalhe-label">Prêmio</span>
|
||||||
<span className="modal-detalhe-value">{item.premio || 'N/A'}</span>
|
<span className="modal-detalhe-value">{item.premio || 'N/A'}</span>
|
||||||
@@ -1461,7 +1509,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
|||||||
onClick={handleRawDataClick}
|
onClick={handleRawDataClick}
|
||||||
title="Ver dados completos do ATUACAPES"
|
title="Ver dados completos do ATUACAPES"
|
||||||
>
|
>
|
||||||
⋮
|
📋
|
||||||
</button>
|
</button>
|
||||||
<div className="expand-icon">{expanded ? '▲' : '▼'}</div>
|
<div className="expand-icon">{expanded ? '▲' : '▼'}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1687,7 +1735,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
|||||||
>
|
>
|
||||||
<span className="badge">{insc.codigo}</span>
|
<span className="badge">{insc.codigo}</span>
|
||||||
<span className="pontos">{PONTOS_BASE[insc.codigo] || 0} pts</span>
|
<span className="pontos">{PONTOS_BASE[insc.codigo] || 0} pts</span>
|
||||||
<span>{insc.premio}</span>
|
<span>{insc.evento || insc.premio}</span>
|
||||||
<span className="muted">{insc.ano}</span>
|
<span className="muted">{insc.ano}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ const SELOS_CONFIG = {
|
|||||||
{ codigo: 'ORIENT_GP', label: 'Orient. GP', icone: '🏆' },
|
{ codigo: 'ORIENT_GP', label: 'Orient. GP', icone: '🏆' },
|
||||||
{ codigo: 'ORIENT_PREMIO', label: 'Orient. Prêmio', icone: '🎖️' },
|
{ codigo: 'ORIENT_PREMIO', label: 'Orient. Prêmio', icone: '🎖️' },
|
||||||
{ codigo: 'ORIENT_MENCAO', label: 'Orient. Menção', icone: '📜' },
|
{ codigo: 'ORIENT_MENCAO', label: 'Orient. Menção', icone: '📜' },
|
||||||
|
{ codigo: 'COORIENT_GP', label: 'Coorient. GP', icone: '🏆' },
|
||||||
|
{ codigo: 'COORIENT_PREMIO', label: 'Coorient. Prêmio', icone: '🎖️' },
|
||||||
|
{ codigo: 'COORIENT_MENCAO', label: 'Coorient. Menção', icone: '📜' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
orientacoes: {
|
orientacoes: {
|
||||||
|
|||||||
@@ -493,6 +493,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.equipe-count {
|
.equipe-count {
|
||||||
@@ -518,6 +521,7 @@
|
|||||||
.equipe-acoes {
|
.equipe-acoes {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-limpar {
|
.btn-limpar {
|
||||||
@@ -530,6 +534,7 @@
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-limpar:hover {
|
.btn-limpar:hover {
|
||||||
@@ -547,6 +552,7 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-gerar-pdf:hover:not(:disabled) {
|
.btn-gerar-pdf:hover:not(:disabled) {
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ export const rankingService = {
|
|||||||
participacoes: c.participacoes || [],
|
participacoes: c.participacoes || [],
|
||||||
orientacoes: c.orientacoes || [],
|
orientacoes: c.orientacoes || [],
|
||||||
membros_banca: c.membros_banca || [],
|
membros_banca: c.membros_banca || [],
|
||||||
|
idiomas: c.idiomas || [],
|
||||||
|
titulacao: c.titulacao || '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user