feat: adicionar sistema de sugestao de consultores por tema
- Novo endpoint GET /api/v1/consultores/sugerir com busca por tema - Busca inteligente em areas de avaliacao, conhecimento e pesquisa - Filtro por consultores ativos e area de avaliacao especifica - Endpoint GET /api/v1/consultores/areas-avaliacao com lista de areas - Novo componente SugerirConsultores no frontend - Botao 'Sugerir por Tema' integrado na interface principal - Score de match baseado em relevancia do tema e experiencia
This commit is contained in:
@@ -525,3 +525,189 @@ class ElasticsearchClient:
|
||||
finally:
|
||||
if scroll_id:
|
||||
await self.limpar_scroll(scroll_id)
|
||||
|
||||
async def sugerir_consultores(
|
||||
self,
|
||||
tema: str,
|
||||
area_avaliacao: Optional[str] = None,
|
||||
apenas_ativos: bool = True,
|
||||
size: int = 20
|
||||
) -> list:
|
||||
must_conditions = []
|
||||
should_conditions = []
|
||||
|
||||
tema_lower = tema.lower().strip()
|
||||
|
||||
should_conditions.extend([
|
||||
{
|
||||
"nested": {
|
||||
"path": "atuacoes",
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{"term": {"atuacoes.tipo": "Consultor"}},
|
||||
{
|
||||
"bool": {
|
||||
"should": [
|
||||
{"match": {"atuacoes.dadosConsultoria.areaConhecimentoPos.nome": {"query": tema, "boost": 3}}},
|
||||
{"match": {"atuacoes.dadosConsultoria.areaConhecimentoPos.areaAvaliacao.nome": {"query": tema, "boost": 5}}},
|
||||
{"match": {"atuacoes.dadosConsultoria.areaPesquisa.descricao": {"query": tema, "boost": 2}}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"score_mode": "max",
|
||||
"boost": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"nested": {
|
||||
"path": "atuacoes",
|
||||
"query": {
|
||||
"bool": {
|
||||
"should": [
|
||||
{"term": {"atuacoes.tipo": {"value": "Coordenação de Área de Avaliação", "boost": 8}}},
|
||||
{"term": {"atuacoes.tipo": {"value": "Histórico de Coordenação de Área de Avaliação", "boost": 4}}}
|
||||
]
|
||||
}
|
||||
},
|
||||
"score_mode": "sum"
|
||||
}
|
||||
},
|
||||
{
|
||||
"nested": {
|
||||
"path": "atuacoes",
|
||||
"query": {
|
||||
"bool": {
|
||||
"should": [
|
||||
{"term": {"atuacoes.tipo": {"value": "Premiação Prêmio", "boost": 3}}},
|
||||
{"term": {"atuacoes.tipo": {"value": "Avaliação Prêmio", "boost": 2}}}
|
||||
]
|
||||
}
|
||||
},
|
||||
"score_mode": "sum"
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
if area_avaliacao:
|
||||
must_conditions.append({
|
||||
"nested": {
|
||||
"path": "atuacoes",
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{"term": {"atuacoes.tipo": "Consultor"}},
|
||||
{"match": {"atuacoes.dadosConsultoria.areaConhecimentoPos.areaAvaliacao.nome": area_avaliacao}}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if apenas_ativos:
|
||||
must_conditions.append({
|
||||
"nested": {
|
||||
"path": "atuacoes",
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{"term": {"atuacoes.tipo": "Consultor"}}
|
||||
],
|
||||
"should": [
|
||||
{"match": {"atuacoes.dadosConsultoria.situacaoConsultoria": "Atividade Contínua"}},
|
||||
{"match": {"atuacoes.dadosConsultoria.situacaoConsultoria": "Ativo"}},
|
||||
{"match": {"atuacoes.dadosConsultoria.situacaoConsultoria": "Contínua"}}
|
||||
],
|
||||
"minimum_should_match": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
query = {
|
||||
"size": size,
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": must_conditions if must_conditions else [{"match_all": {}}],
|
||||
"should": should_conditions,
|
||||
"minimum_should_match": 1
|
||||
}
|
||||
},
|
||||
"_source": ["id", "dadosPessoais", "atuacoes"],
|
||||
"sort": [{"_score": "desc"}]
|
||||
}
|
||||
|
||||
try:
|
||||
response = await self.client.post(
|
||||
f"{self.url}/{self.index}/_search",
|
||||
json=query,
|
||||
timeout=60.0
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
results = []
|
||||
for hit in data.get("hits", {}).get("hits", []):
|
||||
doc = hit["_source"]
|
||||
doc["_score_match"] = hit.get("_score", 0)
|
||||
results.append(doc)
|
||||
return results
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Erro ao sugerir consultores: {e}")
|
||||
|
||||
async def listar_areas_avaliacao(self) -> list:
|
||||
areas_conhecidas = [
|
||||
"ADMINISTRAÇÃO PÚBLICA E DE EMPRESAS, CIÊNCIAS CONTÁBEIS E TURISMO",
|
||||
"ANTROPOLOGIA E ARQUEOLOGIA",
|
||||
"ARQUITETURA, URBANISMO E DESIGN",
|
||||
"ARTES",
|
||||
"ASTRONOMIA E FÍSICA",
|
||||
"BIODIVERSIDADE",
|
||||
"BIOTECNOLOGIA",
|
||||
"CIÊNCIA DA COMPUTAÇÃO",
|
||||
"CIÊNCIA DE ALIMENTOS",
|
||||
"CIÊNCIA POLÍTICA E RELAÇÕES INTERNACIONAIS",
|
||||
"CIÊNCIAS AGRÁRIAS I",
|
||||
"CIÊNCIAS AMBIENTAIS",
|
||||
"CIÊNCIAS BIOLÓGICAS I",
|
||||
"CIÊNCIAS BIOLÓGICAS II",
|
||||
"CIÊNCIAS BIOLÓGICAS III",
|
||||
"CIÊNCIAS DA RELIGIÃO E TEOLOGIA",
|
||||
"COMUNICAÇÃO E INFORMAÇÃO E MUSEOLOGIA",
|
||||
"DIREITO",
|
||||
"ECONOMIA",
|
||||
"EDUCAÇÃO",
|
||||
"EDUCAÇÃO FÍSICA",
|
||||
"ENFERMAGEM",
|
||||
"ENGENHARIAS I",
|
||||
"ENGENHARIAS II",
|
||||
"ENGENHARIAS III",
|
||||
"ENGENHARIAS IV",
|
||||
"ENSINO",
|
||||
"FARMÁCIA",
|
||||
"FILOSOFIA",
|
||||
"GEOGRAFIA",
|
||||
"GEOCIÊNCIAS",
|
||||
"HISTÓRIA",
|
||||
"INTERDISCIPLINAR",
|
||||
"LETRAS E LINGUÍSTICA",
|
||||
"MATEMÁTICA E ESTATÍSTICA",
|
||||
"MATERIAIS",
|
||||
"MEDICINA I",
|
||||
"MEDICINA II",
|
||||
"MEDICINA III",
|
||||
"MEDICINA VETERINÁRIA",
|
||||
"NUTRIÇÃO",
|
||||
"ODONTOLOGIA",
|
||||
"PLANEJAMENTO URBANO E REGIONAL E DEMOGRAFIA",
|
||||
"PSICOLOGIA",
|
||||
"QUÍMICA",
|
||||
"SAÚDE COLETIVA",
|
||||
"SERVIÇO SOCIAL",
|
||||
"SOCIOLOGIA",
|
||||
"ZOOTECNIA E RECURSOS PESQUEIROS"
|
||||
]
|
||||
return [{"nome": area, "count": 0} for area in areas_conhecidas]
|
||||
|
||||
@@ -24,6 +24,9 @@ from ..schemas.ranking_schema import (
|
||||
ProcessarRankingResponseSchema,
|
||||
ConsultaNomeSchema,
|
||||
PosicaoRankingSchema,
|
||||
SugestaoConsultorSchema,
|
||||
SugerirConsultoresResponseSchema,
|
||||
AreaAvaliacaoSchema,
|
||||
)
|
||||
from .dependencies import get_repository, get_ranking_store, get_processar_job, get_es_client
|
||||
from ...infrastructure.elasticsearch.client import ElasticsearchClient
|
||||
@@ -410,3 +413,99 @@ async def exportar_ficha_pdf(
|
||||
"Content-Disposition": f'attachment; filename="{nome_arquivo}"'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/consultores/sugerir", response_model=SugerirConsultoresResponseSchema)
|
||||
async def sugerir_consultores(
|
||||
tema: str = Query(..., min_length=2, description="Tema ou assunto para buscar consultores"),
|
||||
area_avaliacao: Optional[str] = Query(None, description="Filtrar por area de avaliacao especifica"),
|
||||
apenas_ativos: bool = Query(True, description="Filtrar apenas consultores ativos"),
|
||||
quantidade: int = Query(20, ge=1, le=100, description="Quantidade maxima de sugestoes"),
|
||||
es_client: ElasticsearchClient = Depends(get_es_client),
|
||||
store = Depends(get_ranking_store),
|
||||
):
|
||||
try:
|
||||
resultados = await es_client.sugerir_consultores(
|
||||
tema=tema,
|
||||
area_avaliacao=area_avaliacao,
|
||||
apenas_ativos=apenas_ativos,
|
||||
size=quantidade
|
||||
)
|
||||
|
||||
consultores = []
|
||||
for doc in resultados:
|
||||
id_pessoa = doc.get("id")
|
||||
nome = doc.get("dadosPessoais", {}).get("nome", "")
|
||||
score_match = doc.get("_score_match", 0)
|
||||
|
||||
areas_avaliacao = set()
|
||||
areas_conhecimento = set()
|
||||
linhas_pesquisa = set()
|
||||
situacao = ""
|
||||
ies = None
|
||||
foi_coordenador = False
|
||||
foi_premiado = False
|
||||
|
||||
for atuacao in doc.get("atuacoes", []):
|
||||
tipo = atuacao.get("tipo", "")
|
||||
|
||||
if tipo == "Consultor":
|
||||
dados = atuacao.get("dadosConsultoria", {})
|
||||
situacao = dados.get("situacaoConsultoria", "")
|
||||
if dados.get("ies"):
|
||||
ies = dados["ies"].get("sigla") or dados["ies"].get("nome")
|
||||
|
||||
for area in dados.get("areaConhecimentoPos", []):
|
||||
if area.get("nome"):
|
||||
areas_conhecimento.add(area["nome"])
|
||||
area_aval = area.get("areaAvaliacao", {})
|
||||
if area_aval and area_aval.get("nome"):
|
||||
areas_avaliacao.add(area_aval["nome"])
|
||||
|
||||
for pesq in dados.get("areaPesquisa", []):
|
||||
if pesq.get("descricao"):
|
||||
linhas_pesquisa.add(pesq["descricao"])
|
||||
|
||||
elif "Coordenação" in tipo:
|
||||
foi_coordenador = True
|
||||
|
||||
elif "Premiação" in tipo:
|
||||
foi_premiado = True
|
||||
|
||||
posicao_ranking = None
|
||||
if store.is_ready():
|
||||
entry = store.get_by_id(id_pessoa)
|
||||
if entry:
|
||||
posicao_ranking = entry.posicao
|
||||
|
||||
consultores.append(SugestaoConsultorSchema(
|
||||
id_pessoa=id_pessoa,
|
||||
nome=nome,
|
||||
score_match=score_match,
|
||||
areas_avaliacao=list(areas_avaliacao),
|
||||
areas_conhecimento=list(areas_conhecimento),
|
||||
linhas_pesquisa=list(linhas_pesquisa),
|
||||
situacao=situacao,
|
||||
ies=ies,
|
||||
foi_coordenador=foi_coordenador,
|
||||
foi_premiado=foi_premiado,
|
||||
))
|
||||
|
||||
return SugerirConsultoresResponseSchema(
|
||||
tema_buscado=tema,
|
||||
total_encontrados=len(consultores),
|
||||
consultores=consultores
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Erro ao sugerir consultores: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/consultores/areas-avaliacao", response_model=List[AreaAvaliacaoSchema])
|
||||
async def listar_areas_avaliacao(
|
||||
es_client: ElasticsearchClient = Depends(get_es_client),
|
||||
):
|
||||
try:
|
||||
areas = await es_client.listar_areas_avaliacao()
|
||||
return [AreaAvaliacaoSchema(**a) for a in areas]
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Erro ao listar areas de avaliacao: {str(e)}")
|
||||
|
||||
@@ -91,3 +91,34 @@ class PosicaoRankingSchema(BaseModel):
|
||||
bloco_d: float
|
||||
ativo: bool
|
||||
encontrado: bool = True
|
||||
|
||||
|
||||
class SugestaoConsultorSchema(BaseModel):
|
||||
id_pessoa: int
|
||||
nome: str
|
||||
score_match: float
|
||||
areas_avaliacao: List[str] = []
|
||||
areas_conhecimento: List[str] = []
|
||||
linhas_pesquisa: List[str] = []
|
||||
situacao: str = ""
|
||||
ies: Optional[str] = None
|
||||
foi_coordenador: bool = False
|
||||
foi_premiado: bool = False
|
||||
|
||||
|
||||
class SugerirConsultoresRequestSchema(BaseModel):
|
||||
tema: str = Field(..., min_length=2, description="Tema ou assunto para buscar consultores")
|
||||
area_avaliacao: Optional[str] = Field(None, description="Filtrar por area de avaliacao especifica")
|
||||
apenas_ativos: bool = Field(True, description="Filtrar apenas consultores ativos")
|
||||
quantidade: int = Field(20, ge=1, le=100, description="Quantidade maxima de sugestoes")
|
||||
|
||||
|
||||
class SugerirConsultoresResponseSchema(BaseModel):
|
||||
tema_buscado: str
|
||||
total_encontrados: int
|
||||
consultores: List[SugestaoConsultorSchema]
|
||||
|
||||
|
||||
class AreaAvaliacaoSchema(BaseModel):
|
||||
nome: str
|
||||
count: int
|
||||
|
||||
Reference in New Issue
Block a user