Add utility scripts and documentation

- Add TIPOS_ATUACAO_ELASTICSEARCH.md: mapping of ES activity types
- Add TOP_10_RANKING_CAPES.md: sample ranking output documentation
- Add backend/scripts/: utility scripts for analysis and debugging
  - analise_detalhada.py: detailed consultant analysis
  - auditar_ranking.py: ranking audit tool
  - bootstrap_ranking.sh: bootstrap script
  - buscar_consultores_especificos.py: search specific consultants
  - popular_componente_b.py: populate component B
  - top10_ranking.py: generate top 10 ranking
- Add scripts/reload_atuacapes.sh: reload ES index script
This commit is contained in:
Frederico Castro
2025-12-14 21:36:57 -03:00
parent 10d8efc96a
commit 4a98e8b38c
6 changed files with 1508 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
#!/usr/bin/env python3
import sys
import os
import asyncio
import json
from datetime import datetime
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from infrastructure.elasticsearch.client import ElasticsearchClient
ES_URL = "http://elastic-atuacapes.hom.capes.gov.br:9200"
ES_INDEX = "atuacapes"
ES_USER = "admin-atuacapes"
ES_PASSWORD = "O}!S0bj%FhJ:"
IDS = [12932, 6273]
async def buscar_por_id(es_client, id_pessoa):
response = await es_client.client.post(
f"{es_client.url}/{es_client.index}/_search",
json={
"size": 1,
"query": {"term": {"id": id_pessoa}},
"_source": ["id", "dadosPessoais", "atuacoes"]
}
)
response.raise_for_status()
result = response.json()
hits = result.get("hits", {}).get("hits", [])
return hits[0]["_source"] if hits else None
async def main():
print("Conectando ao Elasticsearch...")
es_client = ElasticsearchClient(ES_URL, ES_INDEX, ES_USER, ES_PASSWORD)
await es_client.connect()
try:
for id_pessoa in IDS:
print(f"\n{'='*120}")
print(f"ANÁLISE DETALHADA - ID: {id_pessoa}")
print(f"{'='*120}")
doc = await buscar_por_id(es_client, id_pessoa)
if not doc:
print(f" Não encontrado")
continue
dados_pessoais = doc.get("dadosPessoais", {})
nome = dados_pessoais.get("nome", "N/A")
atuacoes = doc.get("atuacoes", [])
print(f"\nNOME: {nome}")
print(f"ID: {id_pessoa}")
print(f"CPF: {dados_pessoais.get('cpf', 'N/A')}")
print(f"\nTOTAL DE ATUAÇÕES: {len(atuacoes)}")
por_tipo = {}
for a in atuacoes:
tipo = a.get("tipo", "Desconhecido")
por_tipo[tipo] = por_tipo.get(tipo, 0) + 1
print(f"\nDISTRIBUIÇÃO POR TIPO:")
for tipo, count in sorted(por_tipo.items()):
print(f" {tipo}: {count}")
print(f"\n{'='*120}")
print("EVENTOS DETALHADOS")
print(f"{'='*120}")
eventos = [a for a in atuacoes if a.get("tipo") == "Evento"]
print(f"\nTOTAL DE EVENTOS: {len(eventos)}")
if eventos:
por_descricao = {}
for e in eventos:
desc = e.get("descricao", "Sem descrição")
por_descricao[desc] = por_descricao.get(desc, 0) + 1
print(f"\nEVENTOS POR DESCRIÇÃO:")
for desc, count in sorted(por_descricao.items(), key=lambda x: x[1], reverse=True):
print(f" {desc}: {count}")
print(f"\nPRIMEIROS 10 EVENTOS:")
for idx, e in enumerate(eventos[:10], 1):
print(f"\n Evento {idx}:")
print(f" Descrição: {e.get('descricao', 'N/A')}")
print(f" Início: {e.get('inicio', 'N/A')}")
print(f" Fim: {e.get('fim', 'N/A')}")
print(f" Nome: {e.get('nome', 'N/A')}")
if e.get("dadosEvento"):
print(f" Dados Evento: {json.dumps(e['dadosEvento'], indent=8, ensure_ascii=False)[:200]}")
print(f"\n{'='*120}")
print("COORDENAÇÕES CAPES")
print(f"{'='*120}")
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"
]]
for idx, coord in enumerate(coordenacoes, 1):
print(f"\nCoordenação {idx}:")
print(f" Tipo: {coord.get('tipo')}")
print(f" Nome: {coord.get('nome', 'N/A')}")
print(f" Descrição: {coord.get('descricao', 'N/A')}")
print(f" Início: {coord.get('inicio', 'N/A')}")
print(f" Fim: {coord.get('fim', 'N/A')}")
dados = coord.get("dadosCoordenacaoArea") or coord.get("dadosHistoricoCoordenacaoArea")
if dados:
print(f" Dados detalhados:")
print(f" Tipo função: {dados.get('tipo', 'N/A')}")
area = dados.get("areaAvaliacao", {})
if isinstance(area, dict):
print(f" Área: {area.get('nome', 'N/A')} (ID: {area.get('id', 'N/A')})")
else:
print(f" Área: {area}")
colegios = dados.get("colegio", [])
if colegios:
print(f" Colégios: {[c.get('nome') for c in colegios if isinstance(c, dict)]}")
print(f"\n{'='*120}")
print("CONSULTORIAS")
print(f"{'='*120}")
consultorias = [a for a in atuacoes if a.get("tipo") in ["Consultor", "Histórico de Consultoria"]]
for idx, cons in enumerate(consultorias, 1):
print(f"\nConsultoria {idx}:")
print(f" Tipo: {cons.get('tipo')}")
print(f" Início: {cons.get('inicio', 'N/A')}")
print(f" Fim: {cons.get('fim', 'N/A')}")
dados = cons.get("dadosConsultoria", {})
if dados:
print(f" Situação: {dados.get('situacaoConsultoria', 'N/A')}")
print(f" Início situação: {dados.get('inicioSituacao', 'N/A')}")
print(f" Inativação: {dados.get('inativacaoSituacao', 'N/A')}")
print(f"\n{'='*120}")
print("PREMIAÇÕES")
print(f"{'='*120}")
premiacoes = [a for a in atuacoes if "Prêmio" in a.get("tipo", "")]
print(f"\nTOTAL DE PREMIAÇÕES: {len(premiacoes)}")
por_tipo_prem = {}
for p in premiacoes:
tipo = p.get("tipo", "Desconhecido")
por_tipo_prem[tipo] = por_tipo_prem.get(tipo, 0) + 1
print(f"\nPREMIAÇÕES POR TIPO:")
for tipo, count in sorted(por_tipo_prem.items()):
print(f" {tipo}: {count}")
for tipo_prem in ["Premiação Prêmio", "Avaliação Prêmio", "Inscrição Prêmio"]:
prems = [p for p in premiacoes if p.get("tipo") == tipo_prem]
if prems:
print(f"\n{tipo_prem} ({len(prems)}):")
for idx, p in enumerate(prems[:5], 1):
print(f" {idx}. {p.get('descricao', 'N/A')} ({p.get('inicio', 'N/A')})")
finally:
await es_client.close()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,296 @@
#!/usr/bin/env python3
import sys
import os
import asyncio
from datetime import datetime
from typing import List, Dict, Any
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from infrastructure.elasticsearch.client import ElasticsearchClient
from domain.services.calculador_pontuacao import CalculadorPontuacao
from domain.entities.consultor import Consultor, CoordenacaoCapes, Consultoria, Premiacao, Periodo
ES_URL = "http://elastic-atuacapes.hom.capes.gov.br:9200"
ES_INDEX = "atuacapes"
ES_USER = "admin-atuacapes"
ES_PASSWORD = "O}!S0bj%FhJ:"
NOMES_BUSCAR = [
"Veronica Maria de Araujo Calado",
"Manoel Damiao de Sousa Neto"
]
def parse_date(date_str):
if not date_str:
return None
try:
if '/' in date_str:
return datetime.strptime(date_str[:10], '%d/%m/%Y')
else:
return datetime.strptime(date_str[:10], '%Y-%m-%d')
except:
return None
def extrair_coordenacoes_capes(atuacoes: List[Dict]) -> List[CoordenacaoCapes]:
coordenacoes_data = [
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",
]
]
result = []
for coord in coordenacoes_data:
dados = coord.get("dadosCoordenacaoArea") or coord.get("dadosHistoricoCoordenacaoArea") or {}
nome = coord.get("nome", "")
if "câmara" in nome.lower() or "camara" in nome.lower():
tipo = "CAM"
elif "mestrado profissional" in nome.lower():
tipo = "CAJ-MP"
elif "adjunt" in nome.lower():
tipo = "CAJ"
else:
tipo = "CA"
inicio = parse_date(coord.get("inicio") or dados.get("inicio"))
fim = parse_date(coord.get("fim") or dados.get("fim"))
if not inicio:
continue
area = dados.get("areaAvaliacao", {})
if isinstance(area, dict):
area_nome = area.get("nome", "N/A")
else:
area_nome = str(area) if area else "N/A"
periodo = Periodo(inicio=inicio, fim=fim)
result.append(CoordenacaoCapes(
tipo=tipo,
area_avaliacao=area_nome,
periodo=periodo,
areas_adicionais=[],
ja_coordenou_antes=False
))
return result
def extrair_consultoria(atuacoes: List[Dict]) -> Consultoria:
consultorias = [
a for a in atuacoes
if a.get("tipo") in ["Consultor", "Histórico de Consultoria"]
]
if not consultorias:
return None
datas = []
areas = set()
total_eventos = len(consultorias)
vezes_responsavel = 0
for cons in consultorias:
dados = cons.get("dadosConsultoria", {})
inicio = parse_date(cons.get("inicio"))
fim = parse_date(cons.get("fim"))
if inicio:
datas.append(inicio)
if fim:
datas.append(fim)
area = dados.get("areaAvaliacao")
if area:
areas.add(area)
if dados.get("responsavel"):
vezes_responsavel += 1
if not datas:
return None
primeiro_evento = min(datas)
ultimo_evento = max(datas)
limite_recente = datetime.now().replace(year=datetime.now().year - 2)
eventos_recentes = sum(1 for d in datas if d >= limite_recente)
anos = (datetime.now() - primeiro_evento).days / 365.25
return Consultoria(
total_eventos=total_eventos,
eventos_recentes=eventos_recentes,
primeiro_evento=primeiro_evento,
ultimo_evento=ultimo_evento,
areas=list(areas),
situacao="Ativo" if eventos_recentes > 0 else "Histórico",
anos_completos=int(anos),
anos_consecutivos=int(anos),
retornos=0
)
def extrair_premiacoes(atuacoes: List[Dict]) -> List[Premiacao]:
premiacoes_data = [
a for a in atuacoes
if a.get("tipo") in [
"Premiação Prêmio",
"Avaliação Prêmio",
"Inscrição Prêmio",
]
]
result = []
mapa_pontos = {
"Premiação Prêmio": 60,
"Avaliação Prêmio": 40,
"Inscrição Prêmio": 20,
}
for prem in premiacoes_data:
tipo = prem.get("tipo", "")
dados = prem.get("dadosPremiacaoPremio") or prem.get("dadosParticipacaoPremio") or prem.get("dadosParticipacaoInscricaoPremio") or {}
nome_premio = dados.get("premio", "") or prem.get("descricao", "")
ano_str = dados.get("ano") or prem.get("inicio", "")
try:
ano = int(ano_str) if isinstance(ano_str, (int, str)) and str(ano_str).isdigit() else 0
except:
ano = 0
pontos = mapa_pontos.get(tipo, 0)
result.append(Premiacao(
tipo=tipo,
nome_premio=nome_premio,
ano=ano,
pontos=pontos
))
return result
async def buscar_por_nome(es_client, nome):
query = {
"size": 5,
"query": {
"match": {
"dadosPessoais.nome": {
"query": nome,
"fuzziness": "AUTO"
}
}
},
"_source": ["id", "dadosPessoais", "atuacoes"]
}
response = await es_client.client.post(
f"{es_client.url}/{es_client.index}/_search",
json=query
)
response.raise_for_status()
result = response.json()
return result.get("hits", {}).get("hits", [])
async def main():
print("Conectando ao Elasticsearch...")
es_client = ElasticsearchClient(ES_URL, ES_INDEX, ES_USER, ES_PASSWORD)
await es_client.connect()
calculador = CalculadorPontuacao()
try:
for nome_buscar in NOMES_BUSCAR:
print(f"\n{'='*100}")
print(f"BUSCANDO: {nome_buscar}")
print(f"{'='*100}")
hits = await buscar_por_nome(es_client, nome_buscar)
if not hits:
print(f" Nenhum resultado encontrado para '{nome_buscar}'")
continue
print(f" Encontrados {len(hits)} resultados")
for hit in hits:
doc = hit["_source"]
id_pessoa = doc.get("id")
dados_pessoais = doc.get("dadosPessoais", {})
nome = dados_pessoais.get("nome", "N/A")
cpf = dados_pessoais.get("cpf", "")
atuacoes = doc.get("atuacoes", [])
print(f"\n CONSULTOR ENCONTRADO: {nome}")
print(f" ID: {id_pessoa}")
tipos_atuacao = {}
for a in atuacoes:
tipo = a.get("tipo", "Desconhecido")
tipos_atuacao[tipo] = tipos_atuacao.get(tipo, 0) + 1
print(f"\n TIPOS DE ATUAÇÃO:")
for tipo, count in sorted(tipos_atuacao.items()):
print(f" - {tipo}: {count}")
coordenacoes_capes = extrair_coordenacoes_capes(atuacoes)
consultoria = extrair_consultoria(atuacoes)
premiacoes = extrair_premiacoes(atuacoes)
consultor = Consultor(
id_pessoa=id_pessoa,
nome=nome,
cpf=cpf,
coordenacoes_capes=coordenacoes_capes,
coordenacoes_programas=[],
consultoria=consultoria,
premiacoes=premiacoes
)
pontuacao = calculador.calcular_pontuacao_completa(consultor)
consultor.pontuacao = pontuacao
print(f"\n PONTUAÇÃO CALCULADA:")
print(f" TOTAL: {consultor.pontuacao_total:.2f} pontos")
print(f" Componente A (Coordenação CAPES): {consultor.pontuacao.componente_a.total:.2f}")
print(f" Base: {consultor.pontuacao.componente_a.base} | Tempo: {consultor.pontuacao.componente_a.tempo}")
print(f" Extras: {consultor.pontuacao.componente_a.extras} | Bônus: {consultor.pontuacao.componente_a.bonus} | Retorno: {consultor.pontuacao.componente_a.retorno}")
print(f" Componente B (Coordenação PPG): {consultor.pontuacao.componente_b.total:.2f}")
print(f" Componente C (Consultoria): {consultor.pontuacao.componente_c.total:.2f}")
if consultoria:
print(f" Base: {consultor.pontuacao.componente_c.base} | Tempo: {consultor.pontuacao.componente_c.tempo}")
print(f" Extras: {consultor.pontuacao.componente_c.extras}")
print(f" Eventos: {consultoria.total_eventos} | Recentes: {consultoria.eventos_recentes}")
print(f" Situação: {consultoria.situacao}")
print(f" Anos: {consultoria.anos_completos}")
print(f" Componente D (Premiações): {consultor.pontuacao.componente_d.total:.2f}")
if premiacoes:
print(f" Total de premiações: {len(premiacoes)}")
for p in premiacoes[:5]:
print(f" - {p.tipo}: {p.nome_premio} ({p.ano}) = {p.pontos} pts")
print(f"\n DETALHAMENTO COMPLETO:")
if coordenacoes_capes:
print(f" Coordenações CAPES ({len(coordenacoes_capes)}):")
for coord in coordenacoes_capes:
status = "ATIVA" if coord.periodo.ativo else "ENCERRADA"
print(f" - {coord.tipo}: {coord.area_avaliacao}")
print(f" Período: {coord.periodo.inicio} até {coord.periodo.fim or 'atual'} ({status})")
print(f" Anos: {coord.periodo.anos_decorridos:.1f}")
finally:
await es_client.close()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,265 @@
#!/usr/bin/env python3
import sys
import os
import asyncio
from datetime import datetime
from typing import List, Dict, Any
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from infrastructure.elasticsearch.client import ElasticsearchClient
from domain.services.calculador_pontuacao import CalculadorPontuacao
from domain.entities.consultor import Consultor, CoordenacaoCapes, Consultoria, Premiacao, Periodo
ES_URL = "http://elastic-atuacapes.hom.capes.gov.br:9200"
ES_INDEX = "atuacapes"
ES_USER = "admin-atuacapes"
ES_PASSWORD = "O}!S0bj%FhJ:"
def parse_date(date_str):
if not date_str:
return None
try:
if '/' in date_str:
return datetime.strptime(date_str[:10], '%d/%m/%Y')
else:
return datetime.strptime(date_str[:10], '%Y-%m-%d')
except:
return None
def extrair_coordenacoes_capes(atuacoes: List[Dict]) -> List[CoordenacaoCapes]:
coordenacoes_data = [
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",
]
]
result = []
for coord in coordenacoes_data:
dados = coord.get("dadosCoordenacaoArea") or coord.get("dadosHistoricoCoordenacaoArea") or {}
nome = coord.get("nome", "")
if "câmara" in nome.lower() or "camara" in nome.lower():
tipo = "CAM"
elif "mestrado profissional" in nome.lower():
tipo = "CAJ-MP"
elif "adjunt" in nome.lower():
tipo = "CAJ"
else:
tipo = "CA"
inicio = parse_date(coord.get("inicio") or dados.get("inicio"))
fim = parse_date(coord.get("fim") or dados.get("fim"))
if not inicio:
continue
area = dados.get("areaAvaliacao", {})
if isinstance(area, dict):
area_nome = area.get("nome", "N/A")
else:
area_nome = str(area) if area else "N/A"
periodo = Periodo(inicio=inicio, fim=fim)
result.append(CoordenacaoCapes(
tipo=tipo,
area_avaliacao=area_nome,
periodo=periodo,
areas_adicionais=[],
ja_coordenou_antes=False
))
return result
def extrair_consultoria(atuacoes: List[Dict]) -> Consultoria:
consultorias = [
a for a in atuacoes
if a.get("tipo") in ["Consultor", "Histórico de Consultoria"]
]
if not consultorias:
return None
datas = []
areas = set()
total_eventos = len(consultorias)
vezes_responsavel = 0
for cons in consultorias:
dados = cons.get("dadosConsultoria", {})
inicio = parse_date(cons.get("inicio"))
fim = parse_date(cons.get("fim"))
if inicio:
datas.append(inicio)
if fim:
datas.append(fim)
area = dados.get("areaAvaliacao")
if area:
areas.add(area)
if dados.get("responsavel"):
vezes_responsavel += 1
if not datas:
return None
primeiro_evento = min(datas)
ultimo_evento = max(datas)
limite_recente = datetime.now().replace(year=datetime.now().year - 2)
eventos_recentes = sum(1 for d in datas if d >= limite_recente)
anos = (datetime.now() - primeiro_evento).days / 365.25
return Consultoria(
total_eventos=total_eventos,
eventos_recentes=eventos_recentes,
primeiro_evento=primeiro_evento,
ultimo_evento=ultimo_evento,
areas=list(areas),
situacao="Ativo" if eventos_recentes > 0 else "Histórico",
anos_completos=int(anos),
anos_consecutivos=int(anos),
retornos=0
)
def extrair_premiacoes(atuacoes: List[Dict]) -> List[Premiacao]:
premiacoes_data = [
a for a in atuacoes
if a.get("tipo") in [
"Premiação Prêmio",
"Avaliação Prêmio",
"Inscrição Prêmio",
]
]
result = []
mapa_pontos = {
"Premiação Prêmio": 60,
"Avaliação Prêmio": 40,
"Inscrição Prêmio": 20,
}
for prem in premiacoes_data:
tipo = prem.get("tipo", "")
dados = prem.get("dadosPremiacaoPremio") or prem.get("dadosParticipacaoPremio") or prem.get("dadosParticipacaoInscricaoPremio") or {}
nome_premio = dados.get("premio", "") or prem.get("descricao", "")
ano_str = dados.get("ano") or prem.get("inicio", "")
try:
ano = int(ano_str) if isinstance(ano_str, (int, str)) and str(ano_str).isdigit() else 0
except:
ano = 0
pontos = mapa_pontos.get(tipo, 0)
result.append(Premiacao(
tipo=tipo,
nome_premio=nome_premio,
ano=ano,
pontos=pontos
))
return result
async def main():
print("Conectando ao Elasticsearch...")
es_client = ElasticsearchClient(ES_URL, ES_INDEX, ES_USER, ES_PASSWORD)
await es_client.connect()
try:
print("Buscando candidatos com boost...")
docs = await es_client.buscar_candidatos_ranking(size=100)
print(f"Encontrados {len(docs)} candidatos")
calculador = CalculadorPontuacao()
consultores = []
print("\nProcessando consultores...")
for idx, doc in enumerate(docs, 1):
id_pessoa = doc.get("id")
dados_pessoais = doc.get("dadosPessoais", {})
nome = dados_pessoais.get("nome", "N/A")
cpf = dados_pessoais.get("cpf", "")
atuacoes = doc.get("atuacoes", [])
coordenacoes_capes = extrair_coordenacoes_capes(atuacoes)
consultoria = extrair_consultoria(atuacoes)
premiacoes = extrair_premiacoes(atuacoes)
consultor = Consultor(
id_pessoa=id_pessoa,
nome=nome,
cpf=cpf,
coordenacoes_capes=coordenacoes_capes,
coordenacoes_programas=[],
consultoria=consultoria,
premiacoes=premiacoes
)
pontuacao = calculador.calcular_pontuacao_completa(consultor)
consultor.pontuacao = pontuacao
consultores.append(consultor)
if idx % 10 == 0:
print(f" Processados {idx}/{len(docs)} consultores...")
print("\nOrdenando por pontuação...")
consultores.sort(key=lambda c: c.pontuacao_total, reverse=True)
print("\n" + "="*100)
print("TOP 10 CONSULTORES - RANKING CAPES")
print("="*100)
for rank, c in enumerate(consultores[:10], 1):
print(f"\n{rank}º LUGAR - {c.nome}")
print(f" ID: {c.id_pessoa}")
print(f" PONTUAÇÃO TOTAL: {c.pontuacao_total:.2f} pontos")
print(f" Componente A (Coordenação CAPES): {c.pontuacao.componente_a.total:.2f}")
print(f" Base: {c.pontuacao.componente_a.base} | Tempo: {c.pontuacao.componente_a.tempo}")
print(f" Extras: {c.pontuacao.componente_a.extras} | Bônus: {c.pontuacao.componente_a.bonus} | Retorno: {c.pontuacao.componente_a.retorno}")
print(f" Componente B (Coordenação PPG): {c.pontuacao.componente_b.total:.2f}")
print(f" Componente C (Consultoria): {c.pontuacao.componente_c.total:.2f}")
if c.consultoria:
print(f" Base: {c.pontuacao.componente_c.base} | Tempo: {c.pontuacao.componente_c.tempo}")
print(f" Eventos: {c.consultoria.total_eventos} | Recentes: {c.consultoria.eventos_recentes}")
print(f" Áreas: {', '.join(c.consultoria.areas[:3])}")
print(f" Componente D (Premiações): {c.pontuacao.componente_d.total:.2f}")
if c.premiacoes:
print(f" Total de premiações: {len(c.premiacoes)}")
print(f" Anos de atuação: {c.anos_atuacao}")
print(f" Ativo: {'Sim' if c.ativo else 'Não'}")
if c.coordenacoes_capes:
print(f" Coordenações CAPES:")
for coord in c.coordenacoes_capes[:3]:
status = "ATIVA" if coord.periodo.ativo else "ENCERRADA"
print(f" - {coord.tipo}: {coord.area_avaliacao} ({status})")
print("\n" + "="*100)
print(f"\nEstatísticas Gerais:")
print(f" Total de candidatos processados: {len(consultores)}")
print(f" Pontuação máxima: {consultores[0].pontuacao_total:.2f}")
print(f" Pontuação mínima (top 10): {consultores[9].pontuacao_total:.2f}")
print(f" Média de pontuação (top 10): {sum(c.pontuacao_total for c in consultores[:10])/10:.2f}")
finally:
await es_client.close()
if __name__ == "__main__":
asyncio.run(main())