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:
Frederico Castro
2025-12-20 07:35:03 -03:00
parent f7557831eb
commit 45ab7412fe
8 changed files with 917 additions and 0 deletions

View File

@@ -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]