diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..ef4f894 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1 @@ +instantclient_23_7/ diff --git a/backend/src/interface/api/routes.py b/backend/src/interface/api/routes.py index 33403ac..59557cc 100644 --- a/backend/src/interface/api/routes.py +++ b/backend/src/interface/api/routes.py @@ -236,13 +236,16 @@ async def ranking_paginado( if not d.get("idiomas"): faltando_idiomas.append((c.id_pessoa, d)) - if faltando_idiomas: - ids = [item[0] for item in faltando_idiomas] + faltando_lattes = [(c.id_pessoa, d) for c, d in consultores_dados if not d.get("lattes")] + ids_buscar = list(set([item[0] for item in faltando_idiomas] + [item[0] for item in faltando_lattes])) + + if ids_buscar: docs = await es_client.buscar_por_ids( - ids, - source_fields=["id", "dadosPessoais", "idiomas", "atuacoes", "formacoes"], + ids_buscar, + source_fields=["id", "dadosPessoais", "idiomas", "atuacoes", "formacoes", "identificadorLattes", "titulacoes"], ) 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: @@ -264,6 +267,32 @@ async def ranking_paginado( if titulacao: detalhes["titulacao"] = titulacao + for id_pessoa, detalhes in faltando_lattes: + doc = docs_map.get(int(id_pessoa)) + if not doc: + continue + id_lattes_obj = doc.get("identificadorLattes") + titulacoes_raw = doc.get("titulacoes", []) + if id_lattes_obj and id_lattes_obj.get("descricao"): + id_lattes = id_lattes_obj.get("descricao") + titulacoes_formatadas = [] + for t in titulacoes_raw: + grau_obj = t.get("grauAcademico", {}) + ies_obj = t.get("ies", {}) + titulacoes_formatadas.append({ + "grau": grau_obj.get("nome", ""), + "ano": t.get("ano"), + "ies_nome": ies_obj.get("nome"), + "ies_sigla": ies_obj.get("sigla"), + "area": t.get("areaConhecimento", {}).get("nome"), + "pais": "Brasil", + }) + detalhes["lattes"] = { + "id_lattes": id_lattes, + "url": f"http://lattes.cnpq.br/{id_lattes}", + "titulacoes": titulacoes_formatadas, + } + for c, d in consultores_dados: tipos_atuacao = RankingMapper._extrair_tipos_atuacao(d) consultores_schema.append( @@ -294,6 +323,7 @@ async def ranking_paginado( idiomas=d.get("idiomas"), titulacao=d.get("titulacao"), pontuacao=d.get("pontuacao"), + lattes=d.get("lattes"), ) ) @@ -436,6 +466,53 @@ async def obter_consultor_raw( raise HTTPException(status_code=500, detail=str(e)) +@router.get("/consultor/{id_pessoa}/lattes") +async def obter_lattes( + id_pessoa: int, + es_client: ElasticsearchClient = Depends(get_es_client), +): + docs = await es_client.buscar_por_ids( + [id_pessoa], + source_fields=["id", "identificadorLattes", "titulacoes"], + ) + if not docs: + return {"encontrado": False, "motivo": "Consultor não encontrado"} + + doc = docs[0] + id_lattes_obj = doc.get("identificadorLattes") + + if not id_lattes_obj or not id_lattes_obj.get("descricao"): + return {"encontrado": False, "motivo": "Currículo Lattes não cadastrado"} + + id_lattes = id_lattes_obj.get("descricao") + titulacoes_raw = doc.get("titulacoes", []) + + titulacoes = [] + for t in titulacoes_raw: + grau_obj = t.get("grauAcademico", {}) + ies_obj = t.get("ies", {}) + area_obj = t.get("areaConhecimento", {}) + titulacoes.append({ + "grau": grau_obj.get("nome", ""), + "ano": t.get("ano"), + "inicio": t.get("inicio"), + "fim": t.get("fim"), + "ies_nome": ies_obj.get("nome"), + "ies_sigla": ies_obj.get("sigla"), + "area": area_obj.get("nome"), + "area_avaliacao": area_obj.get("areaAvaliacao", {}).get("nome") if area_obj.get("areaAvaliacao") else None, + "programa": t.get("programa", {}).get("nome") if t.get("programa") else None, + }) + + return { + "encontrado": True, + "id_lattes": id_lattes, + "url": f"http://lattes.cnpq.br/{id_lattes}", + "titulacoes": titulacoes, + "orientacoes_lattes": len([t for t in titulacoes if t.get("grau") in ["Doutorado", "Mestrado"]]), + } + + @router.get("/consultor/{id_pessoa}/pdf") async def exportar_ficha_pdf( id_pessoa: int, diff --git a/backend/src/interface/schemas/ranking_schema.py b/backend/src/interface/schemas/ranking_schema.py index 65c7b63..cf4a0fa 100644 --- a/backend/src/interface/schemas/ranking_schema.py +++ b/backend/src/interface/schemas/ranking_schema.py @@ -3,6 +3,12 @@ from typing import Optional, List from datetime import datetime +class LattesSchema(BaseModel): + id_lattes: Optional[str] = None + url: Optional[str] = None + titulacoes: Optional[list] = None + + class ConsultorRankingResumoSchema(BaseModel): id_pessoa: int nome: str @@ -30,6 +36,7 @@ class ConsultorRankingResumoSchema(BaseModel): idiomas: Optional[list] = None titulacao: Optional[str] = None pontuacao: Optional[dict] = None + lattes: Optional[LattesSchema] = None class RankingPaginadoResponseSchema(BaseModel): diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index d946ec1..0047bab 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -697,6 +697,8 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => { const getTitulo = () => { switch (tipo) { + case 'titulacao': return 'Titulação'; + case 'producoes_lattes': return 'Produções Lattes'; case 'vinculo': return 'Vínculo de Consultoria'; case 'coordenacao': return 'Coordenação CAPES'; case 'premiacao': return 'Premiação'; @@ -710,6 +712,8 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => { const getIcone = () => { switch (tipo) { + case 'titulacao': return '🎓'; + case 'producoes_lattes': return '📚'; case 'vinculo': return '💼'; case 'coordenacao': return '🎯'; case 'premiacao': return '🏆'; @@ -723,6 +727,90 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => { const renderContent = () => { switch (tipo) { + case 'titulacao': { + return ( +
Nenhuma produção encontrada.
+ )} +