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:
407
TIPOS_ATUACAO_ELASTICSEARCH.md
Normal file
407
TIPOS_ATUACAO_ELASTICSEARCH.md
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
# Tipos de Atuação Disponíveis no Elasticsearch (AtuaCAPES)
|
||||||
|
|
||||||
|
> Mapeamento completo dos tipos de atuação encontrados no índice `atuacapes` do Elasticsearch CAPES
|
||||||
|
|
||||||
|
## Visão Geral
|
||||||
|
|
||||||
|
Este documento lista todos os tipos de atuação (`atuacoes.tipo`) disponíveis no índice Elasticsearch do sistema AtuaCAPES, identificados através da análise do código implementado e da documentação do projeto.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tipos de Atuação Implementados
|
||||||
|
|
||||||
|
### Componente A - Coordenação CAPES
|
||||||
|
|
||||||
|
| Tipo | Descrição | Boost | Pontuação Base |
|
||||||
|
|------|-----------|-------|----------------|
|
||||||
|
| `Coordenação de Área de Avaliação` | Coordenadores ativos (CA, CAJ, CAJ-MP, CAM) | 10 | 200-100 pts |
|
||||||
|
| `Histórico de Coordenação de Área de Avaliação` | Coordenadores históricos (mandatos encerrados) | 5 | 200-100 pts |
|
||||||
|
|
||||||
|
**Subtipos identificados em `dadosCoordenacaoArea.tipo`:**
|
||||||
|
- `Coordenador de Área` (CA) - Base: 200 pts
|
||||||
|
- `Coordenador Adjunto` (CAJ) - Base: 150 pts
|
||||||
|
- `Coordenador Adjunto de Mestrado Profissionalizante` (CAJ-MP) - Base: 120 pts
|
||||||
|
- `Coordenador de Câmara Temática` (CAM) - Base: 100 pts
|
||||||
|
|
||||||
|
### Componente C - Consultoria
|
||||||
|
|
||||||
|
| Tipo | Descrição | Boost | Pontuação Base |
|
||||||
|
|------|-----------|-------|----------------|
|
||||||
|
| `Consultor` | Registro de consultoria ativa ou histórica | 2 | 150 pts (ativo) |
|
||||||
|
| `Histórico de Consultoria` | Consultorias encerradas | 1 | 100 pts (histórico) |
|
||||||
|
|
||||||
|
**Valores de `situacaoConsultoria`:**
|
||||||
|
- `Atividade Contínua` - Consultor Ativo
|
||||||
|
- `Ativo` - Consultor Ativo
|
||||||
|
- Outros valores ou com `inativacaoSituacao` preenchido = Histórico
|
||||||
|
|
||||||
|
### Componente D - Premiações
|
||||||
|
|
||||||
|
| Tipo | Descrição | Boost | Pontuação |
|
||||||
|
|------|-----------|-------|-----------|
|
||||||
|
| `Premiação Prêmio` | Prêmio recebido | 3 | 60-180 pts |
|
||||||
|
| `Avaliação Prêmio` | Participação em avaliação/comissão | 2 | 2-115 pts |
|
||||||
|
| `Inscrição Prêmio` | Inscrição em prêmio | 1 | 1-2 pts |
|
||||||
|
|
||||||
|
**Níveis de Premiação:**
|
||||||
|
|
||||||
|
#### Nível 1 - Grande Prêmio CAPES
|
||||||
|
- `dadosPremiacaoPremio.premiacao`: "Grande Prêmio"
|
||||||
|
- Pontuação: 150 + 50 = 200 pts (limitado a 180)
|
||||||
|
|
||||||
|
#### Nível 1 - Prêmio CAPES de Tese (PCT)
|
||||||
|
- `dadosPremiacaoPremio.premio`: "PCT"
|
||||||
|
- `dadosPremiacaoPremio.premiacao`: "Prêmio" (+25) ou "Menção Honrosa" (+15)
|
||||||
|
- Pontuação base: 100 pts (máx 150 pts)
|
||||||
|
|
||||||
|
#### Nível 2 - Institucionais
|
||||||
|
- Interfarma, Vale-CAPES, etc.
|
||||||
|
- Base: 30 pts
|
||||||
|
- Prêmio: +20 pts / Menção: +10 pts
|
||||||
|
- Máximo: 60 pts
|
||||||
|
|
||||||
|
#### Nível 3 - Autoinscrição
|
||||||
|
- CDTDN, Vínculos Familiares, etc.
|
||||||
|
- Base: 10 pts
|
||||||
|
- Prêmio: +5 pts / Menção: +3 pts
|
||||||
|
- Máximo: 20 pts
|
||||||
|
|
||||||
|
### Outros Tipos Identificados
|
||||||
|
|
||||||
|
| Tipo | Descrição | Uso no Ranking |
|
||||||
|
|------|-----------|----------------|
|
||||||
|
| `Evento` | Participação em eventos SAE | Sim - bônus consultoria |
|
||||||
|
|
||||||
|
**Campos relacionados a eventos:**
|
||||||
|
- `dadosEvento.consultorResponsavel`: "Sim" ou "Não"
|
||||||
|
- Eventos recentes (últimos 2 anos): bonus para consultoria ativa
|
||||||
|
- Responsável por evento: +5 pts por vez (máx 25)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estrutura de Dados por Componente
|
||||||
|
|
||||||
|
### Coordenação CAPES
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tipo": "Coordenação de Área de Avaliação",
|
||||||
|
"inicio": "01/01/2020",
|
||||||
|
"fim": null,
|
||||||
|
"dadosCoordenacaoArea": {
|
||||||
|
"tipo": "Coordenador de Área",
|
||||||
|
"areaAvaliacao": {
|
||||||
|
"id": 123,
|
||||||
|
"nome": "CIÊNCIAS AMBIENTAIS"
|
||||||
|
},
|
||||||
|
"inicioVinculacao": "01/01/2020",
|
||||||
|
"fimVinculacao": null,
|
||||||
|
"colegio": [
|
||||||
|
{"nome": "Colégio de Ciências Exatas, Tecnológicas e Multidisciplinar"}
|
||||||
|
],
|
||||||
|
"portaria": "123/2020",
|
||||||
|
"ies": {
|
||||||
|
"nome": "Universidade Federal do Brasil",
|
||||||
|
"sigla": "UFB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Consultoria
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tipo": "Consultor",
|
||||||
|
"inicio": "15/03/2018",
|
||||||
|
"fim": null,
|
||||||
|
"dadosConsultoria": {
|
||||||
|
"situacaoConsultoria": "Atividade Contínua",
|
||||||
|
"inicioSituacao": "15/03/2018",
|
||||||
|
"inativacaoSituacao": null,
|
||||||
|
"ies": {
|
||||||
|
"nome": "Universidade Federal do Brasil",
|
||||||
|
"sigla": "UFB"
|
||||||
|
},
|
||||||
|
"areaConhecimentoPos": [
|
||||||
|
{
|
||||||
|
"areaAvaliacao": {
|
||||||
|
"nome": "CIÊNCIAS AMBIENTAIS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"areaPesquisa": [
|
||||||
|
{"descricao": "Mudanças Climáticas"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Premiações
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tipo": "Premiação Prêmio",
|
||||||
|
"dadosPremiacaoPremio": {
|
||||||
|
"nomePremio": "Prêmio CAPES de Tese",
|
||||||
|
"premio": "PCT",
|
||||||
|
"premiacao": "Prêmio",
|
||||||
|
"ano": 2022,
|
||||||
|
"areaConhecimento": {
|
||||||
|
"areaAvaliacao": {
|
||||||
|
"nome": "CIÊNCIAS AMBIENTAIS"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"papelPessoa": "Autor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Participação em Prêmios
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tipo": "Avaliação Prêmio",
|
||||||
|
"dadosParticipacaoPremio": {
|
||||||
|
"tipo": "Membro de Comissão",
|
||||||
|
"premio": "PCT",
|
||||||
|
"ano": 2023,
|
||||||
|
"comissao": {
|
||||||
|
"nome": "Comissão Julgadora",
|
||||||
|
"tipo": "Avaliação"
|
||||||
|
},
|
||||||
|
"areaAvaliacao": {
|
||||||
|
"nome": "CIÊNCIAS AMBIENTAIS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eventos
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tipo": "Evento",
|
||||||
|
"inicio": "10/05/2023",
|
||||||
|
"fim": "15/05/2023",
|
||||||
|
"dadosEvento": {
|
||||||
|
"consultorResponsavel": "Sim",
|
||||||
|
"nome": "Seminário de Avaliação",
|
||||||
|
"tipo": "Presencial"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Query de Agregação - Descobrir Todos os Tipos
|
||||||
|
|
||||||
|
Para descobrir todos os tipos de atuação disponíveis no índice:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"size": 0,
|
||||||
|
"aggs": {
|
||||||
|
"tipos": {
|
||||||
|
"nested": {"path": "atuacoes"},
|
||||||
|
"aggs": {
|
||||||
|
"tipos_unicos": {
|
||||||
|
"terms": {
|
||||||
|
"field": "atuacoes.tipo.keyword",
|
||||||
|
"size": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mapeamento de Tipos vs. Componentes de Pontuação
|
||||||
|
|
||||||
|
| Componente | Tipos Relacionados | Pontuação Máxima |
|
||||||
|
|------------|-------------------|------------------|
|
||||||
|
| **A - Coordenação CAPES** | `Coordenação de Área de Avaliação`<br>`Histórico de Coordenação de Área de Avaliação` | 450 pts (CA) |
|
||||||
|
| **B - Coordenação PPG** | *Não existe no ES - usar Oracle* | 180 pts |
|
||||||
|
| **C - Consultoria** | `Consultor`<br>`Histórico de Consultoria`<br>`Evento` (bônus) | 230 pts |
|
||||||
|
| **D - Premiações** | `Premiação Prêmio`<br>`Avaliação Prêmio`<br>`Inscrição Prêmio` | 180 pts |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Campos Faltantes / Limitações
|
||||||
|
|
||||||
|
### Não Disponíveis no Elasticsearch
|
||||||
|
|
||||||
|
1. **Coordenação de Programa (PPG)**
|
||||||
|
- Não existe tipo `atuacoes.tipo` para coordenação de programa
|
||||||
|
- Fonte alternativa: Oracle `SUCUPIRA_PAINEL.VM_COORDENADOR`
|
||||||
|
|
||||||
|
2. **Nota do PPG**
|
||||||
|
- Não disponível em `dadosGestaoPrograma`
|
||||||
|
- Fonte alternativa: Oracle `VM_PROGRAMA_SUCUPIRA.DS_CONCEITO`
|
||||||
|
|
||||||
|
3. **Dados detalhados de Câmara Temática**
|
||||||
|
- Inferido pelo campo `dadosCoordenacaoArea.tipo` ou `nome`
|
||||||
|
- Pode haver imprecisão na classificação
|
||||||
|
|
||||||
|
4. **Situação "Falecido"**
|
||||||
|
- Existe `dadosPessoais.anoObito` mas não `situacaoConsultoria="Falecido"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementação no Código
|
||||||
|
|
||||||
|
### Arquivo: `backend/src/infrastructure/elasticsearch/client.py`
|
||||||
|
|
||||||
|
Query principal com boost por tipo:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def buscar_candidatos_ranking(self, size: int = 1000) -> List[Dict[str, Any]]:
|
||||||
|
query = {
|
||||||
|
"size": size,
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"should": [
|
||||||
|
# Coordenação CAPES
|
||||||
|
{
|
||||||
|
"nested": {
|
||||||
|
"path": "atuacoes",
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"should": [
|
||||||
|
{"term": {"atuacoes.tipo": {"value": "Coordenação de Área de Avaliação", "boost": 10}}},
|
||||||
|
{"term": {"atuacoes.tipo": {"value": "Histórico de Coordenação de Área de Avaliação", "boost": 5}}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"score_mode": "sum"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# Consultoria
|
||||||
|
{
|
||||||
|
"nested": {
|
||||||
|
"path": "atuacoes",
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"should": [
|
||||||
|
{"term": {"atuacoes.tipo": {"value": "Consultor", "boost": 2}}},
|
||||||
|
{"term": {"atuacoes.tipo": {"value": "Histórico de Consultoria", "boost": 1}}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"score_mode": "sum"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# Premiações
|
||||||
|
{
|
||||||
|
"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}}},
|
||||||
|
{"term": {"atuacoes.tipo": {"value": "Inscrição Prêmio", "boost": 1}}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"score_mode": "sum"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimum_should_match": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_source": ["id", "dadosPessoais", "atuacoes"],
|
||||||
|
"sort": [{"_score": "desc"}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arquivo: `backend/src/infrastructure/repositories/consultor_repository_impl.py`
|
||||||
|
|
||||||
|
Extração de tipos:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Consultoria
|
||||||
|
consultorias = [
|
||||||
|
a for a in atuacoes
|
||||||
|
if a.get("tipo") in ["Consultor", "Histórico de Consultoria"]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Coordenação CAPES
|
||||||
|
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",
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Eventos (bônus consultoria)
|
||||||
|
eventos_sae = [
|
||||||
|
a for a in atuacoes
|
||||||
|
if a.get("tipo") == "Evento"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Premiações (todos os tipos são processados genericamente)
|
||||||
|
premiacoes = [
|
||||||
|
a for a in atuacoes
|
||||||
|
if a.get("dadosPremiacaoPremio") or
|
||||||
|
a.get("dadosParticipacaoPremio") or
|
||||||
|
a.get("dadosParticipacaoInscricaoPremio")
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estatísticas
|
||||||
|
|
||||||
|
### Estimativas de Volume (baseado em documentação)
|
||||||
|
|
||||||
|
| Tipo | Quantidade Estimada |
|
||||||
|
|------|---------------------|
|
||||||
|
| Coordenadores de área (ativos) | ~200 |
|
||||||
|
| Coordenadores históricos | ~322 |
|
||||||
|
| Consultores | ~52.551 |
|
||||||
|
| Premiações | ~63.799 |
|
||||||
|
| **Total de registros com atuações** | **~90.482** |
|
||||||
|
|
||||||
|
### Boost Aplicado
|
||||||
|
|
||||||
|
O boost é usado para priorizar candidatos no score inicial do Elasticsearch:
|
||||||
|
|
||||||
|
| Tipo | Boost | Justificativa |
|
||||||
|
|------|-------|---------------|
|
||||||
|
| Coordenação de Área (ativa) | 10 | Máxima prioridade (200-450 pts) |
|
||||||
|
| Coordenação de Área (histórica) | 5 | Alta prioridade histórica |
|
||||||
|
| Premiação Prêmio | 3 | Mérito significativo |
|
||||||
|
| Consultor | 2 | Base importante |
|
||||||
|
| Avaliação Prêmio | 2 | Participação ativa |
|
||||||
|
| Histórico de Consultoria | 1 | Relevância histórica |
|
||||||
|
| Inscrição Prêmio | 1 | Participação básica |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notas de Implementação
|
||||||
|
|
||||||
|
1. **Paginação**: Query principal retorna top 1000 candidatos ordenados por score ES
|
||||||
|
2. **Recálculo**: Python recalcula pontuação completa e reordena por pontuação real
|
||||||
|
3. **Cache**: Resultados cacheados por 5 minutos (TTL=300s)
|
||||||
|
4. **Performance**: Primeira requisição ~1m34s, cacheadas ~0.27s
|
||||||
|
5. **Timeout**: 120s para queries ES
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Referências
|
||||||
|
|
||||||
|
- **Documentação**: `/home/fred/projetos/ranking/.claude/rules/ranking-queries-elasticsearch.md`
|
||||||
|
- **Critérios**: `/home/fred/projetos/ranking/.claude/rules/ranking-consultores-capes.md`
|
||||||
|
- **Implementação ES**: `/home/fred/projetos/ranking/backend/src/infrastructure/elasticsearch/client.py`
|
||||||
|
- **Extração**: `/home/fred/projetos/ranking/backend/src/infrastructure/repositories/consultor_repository_impl.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última atualização**: 2025-12-13
|
||||||
|
**Índice Elasticsearch**: `atuacapes` (servidor: `elastic-atuacapes.hom.capes.gov.br:9200`)
|
||||||
291
TOP_10_RANKING_CAPES.md
Normal file
291
TOP_10_RANKING_CAPES.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
# TOP 10 CONSULTORES - RANKING CAPES
|
||||||
|
|
||||||
|
**Data da Consulta:** 13/12/2025
|
||||||
|
**Base de Dados:** Elasticsearch AtuaCAPES (índice: atuacapes)
|
||||||
|
**Total de Candidatos Analisados:** 100
|
||||||
|
**Critérios:** Sistema de Ranking Integrado CAPES v1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumo Executivo
|
||||||
|
|
||||||
|
O ranking foi calculado com base em 4 componentes principais:
|
||||||
|
|
||||||
|
- **Componente A:** Coordenação CAPES (máx 450 pts) - CA, CAJ, CAJ-MP, CAM
|
||||||
|
- **Componente B:** Coordenação de Programa PPG (máx 180 pts) - *Não disponível nesta consulta*
|
||||||
|
- **Componente C:** Consultoria (máx 230 pts)
|
||||||
|
- **Componente D:** Premiações (máx 180 pts)
|
||||||
|
|
||||||
|
**Pontuação Máxima Teórica:** 1.040 pontos
|
||||||
|
**Pontuação Máxima Observada:** 685 pontos (65,9% do máximo)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Top 10 Consultores
|
||||||
|
|
||||||
|
### 1º LUGAR - EDSON APARECIDO MITISHITA
|
||||||
|
**ID:** 519524
|
||||||
|
**Pontuação Total:** 685 pontos
|
||||||
|
|
||||||
|
**Breakdown de Pontuação:**
|
||||||
|
- **Componente A (Coordenação CAPES):** 340 pts
|
||||||
|
- Base: 200 (Coordenador de Área)
|
||||||
|
- Tempo: 90 (9 anos completos)
|
||||||
|
- Áreas Adicionais: 0
|
||||||
|
- Bônus Atualidade: 30 (coordenação ativa)
|
||||||
|
- Retorno: 20 (já coordenou antes)
|
||||||
|
|
||||||
|
- **Componente B (Coordenação PPG):** 0 pts
|
||||||
|
|
||||||
|
- **Componente C (Consultoria):** 165 pts
|
||||||
|
- Base: 100 (histórico)
|
||||||
|
- Tempo: 50 (10+ anos)
|
||||||
|
- Total eventos: 2
|
||||||
|
- Eventos recentes: 0 (último há mais de 2 anos)
|
||||||
|
- Bônus continuidade: 15 (8+ anos consecutivos)
|
||||||
|
|
||||||
|
- **Componente D (Premiações):** 180 pts
|
||||||
|
- Total de premiações: 7
|
||||||
|
|
||||||
|
**Perfil:**
|
||||||
|
- Anos de atuação: 11,3 anos
|
||||||
|
- Status: Inativo (sem eventos recentes)
|
||||||
|
- Veterano: Sim
|
||||||
|
- Área principal: GEOCIÊNCIAS
|
||||||
|
- Coordenações: 3 (incluindo 1 ativa)
|
||||||
|
|
||||||
|
**Destaque:** Possui a maior pontuação no Componente A devido à combinação de coordenação ativa (bônus de 30 pts) + retorno (20 pts) + 9 anos completos de experiência.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2º LUGAR - ANDRE MOREIRA CUNHA
|
||||||
|
**ID:** 45997
|
||||||
|
**Pontuação Total:** 675 pontos
|
||||||
|
|
||||||
|
**Breakdown de Pontuação:**
|
||||||
|
- **Componente A:** 330 pts
|
||||||
|
- Base: 200
|
||||||
|
- Tempo: 100 (10 anos - máximo permitido)
|
||||||
|
- Bônus Atualidade: 30
|
||||||
|
- Retorno: 0 (sem retorno)
|
||||||
|
|
||||||
|
- **Componente C:** 165 pts
|
||||||
|
- **Componente D:** 180 pts (7 premiações)
|
||||||
|
|
||||||
|
**Perfil:**
|
||||||
|
- Anos de atuação: 14,2 anos
|
||||||
|
- Status: Inativo
|
||||||
|
- Área principal: ECONOMIA
|
||||||
|
- Coordenações: 2 (1 ativa)
|
||||||
|
|
||||||
|
**Destaque:** Atingiu o teto máximo de pontuação por tempo (100 pts) no Componente A.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3º LUGAR - FLAVIO AUGUSTO SENRA RIBEIRO
|
||||||
|
**ID:** 7794
|
||||||
|
**Pontuação Total:** 665 pontos
|
||||||
|
|
||||||
|
**Breakdown de Pontuação:**
|
||||||
|
- **Componente A:** 320 pts
|
||||||
|
- Base: 200
|
||||||
|
- Tempo: 100
|
||||||
|
- Áreas Adicionais: 20 (coordenou 2 áreas diferentes)
|
||||||
|
- Bônus Atualidade: 0 (coordenação encerrada)
|
||||||
|
- Retorno: 0
|
||||||
|
|
||||||
|
- **Componente C:** 165 pts
|
||||||
|
- **Componente D:** 180 pts (11 premiações)
|
||||||
|
|
||||||
|
**Perfil:**
|
||||||
|
- Anos de atuação: 14,5 anos
|
||||||
|
- Status: Inativo
|
||||||
|
- Áreas: FILOSOFIA, CIÊNCIAS DA RELIGIÃO E TEOLOGIA
|
||||||
|
- Coordenações: 3 (todas encerradas)
|
||||||
|
|
||||||
|
**Destaque:** Único no top 10 com pontuação por áreas adicionais (coordenou múltiplas áreas). Maior número de premiações entre os 10 primeiros (11 premiações).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4º ao 10º LUGAR (Empate em 655 pontos)
|
||||||
|
|
||||||
|
Os consultores da 4ª à 10ª posição compartilham a mesma pontuação total de **655 pontos**, com o seguinte padrão:
|
||||||
|
|
||||||
|
**Componente A:** 310 pts
|
||||||
|
- Base: 200
|
||||||
|
- Tempo: 60 (6 anos)
|
||||||
|
- Bônus Atualidade: 30
|
||||||
|
- Retorno: 20
|
||||||
|
|
||||||
|
**Componente C:** 165 pts
|
||||||
|
**Componente D:** 180 pts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4º - CARLOS FREDERICO MARTINS MENCK
|
||||||
|
**ID:** 20912
|
||||||
|
**Área:** CIÊNCIAS BIOLÓGICAS I
|
||||||
|
**Anos:** 14,5 | **Status:** Inativo
|
||||||
|
**Premiações:** 25 (maior número absoluto)
|
||||||
|
**Coordenações:** 2 (1 ativa)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 5º - MARCELO ALBANO MORET SIMOES GONCALVES
|
||||||
|
**ID:** 5888
|
||||||
|
**Área:** INTERDISCIPLINAR
|
||||||
|
**Anos:** 11,0 | **Status:** Ativo ✓
|
||||||
|
**Premiações:** 9
|
||||||
|
**Eventos recentes:** 2
|
||||||
|
**Coordenações:** 2 (1 ativa)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 6º - CARLOS PELLESCHI TABORDA
|
||||||
|
**ID:** 15100
|
||||||
|
**Área:** CIÊNCIAS BIOLÓGICAS III
|
||||||
|
**Anos:** 12,6 | **Status:** Inativo
|
||||||
|
**Premiações:** 15
|
||||||
|
**Coordenações:** 2 (1 ativa)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 7º - CLÁUDIA LUCIA DE MORAES FORJAZ
|
||||||
|
**ID:** 14569
|
||||||
|
**Área:** EDUCAÇÃO FÍSICA, FISIOTERAPIA, FONOAUDIOLOGIA E TERAPIA OCUPACIONAL
|
||||||
|
**Anos:** 14,5 | **Status:** Inativo
|
||||||
|
**Premiações:** 9
|
||||||
|
**Coordenações:** 2 (1 ativa)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 8º - ALTAIR ANTONINHA DEL BEL CURY
|
||||||
|
**ID:** 8639
|
||||||
|
**Área:** ODONTOLOGIA
|
||||||
|
**Anos:** 12,1 | **Status:** Ativo ✓
|
||||||
|
**Premiações:** 11
|
||||||
|
**Eventos recentes:** 2
|
||||||
|
**Coordenações:** 2 (1 ativa)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 9º - DEBORA FOGUEL
|
||||||
|
**ID:** 12271
|
||||||
|
**Área:** CIÊNCIAS BIOLÓGICAS II
|
||||||
|
**Anos:** 14,5 | **Status:** Inativo
|
||||||
|
**Premiações:** 7
|
||||||
|
**Coordenações:** 2 (1 ativa)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 10º - MARCELO TÁVORA MIRA
|
||||||
|
**ID:** 509665
|
||||||
|
**Área:** MEDICINA I
|
||||||
|
**Anos:** 11,3 | **Status:** Ativo ✓
|
||||||
|
**Premiações:** 7
|
||||||
|
**Eventos recentes:** 1
|
||||||
|
**Coordenações:** 2 (1 ativa)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Análise Estatística
|
||||||
|
|
||||||
|
### Distribuição de Pontuação
|
||||||
|
|
||||||
|
| Posição | Pontuação | Diferença para 1º |
|
||||||
|
|---------|-----------|-------------------|
|
||||||
|
| 1º | 685 | - |
|
||||||
|
| 2º | 675 | -10 |
|
||||||
|
| 3º | 665 | -20 |
|
||||||
|
| 4º-10º | 655 | -30 |
|
||||||
|
|
||||||
|
**Média (top 10):** 661 pontos
|
||||||
|
**Desvio padrão:** ~10 pontos
|
||||||
|
|
||||||
|
### Componentes - Análise
|
||||||
|
|
||||||
|
**Componente A (Coordenação CAPES):**
|
||||||
|
- Média: 319 pts (71% do máximo)
|
||||||
|
- Variação: 310-340 pts
|
||||||
|
- Padrão dominante: CA (Coordenador de Área) com coordenação ativa
|
||||||
|
|
||||||
|
**Componente B (Coordenação PPG):**
|
||||||
|
- Todos: 0 pts (dados não disponíveis no Elasticsearch)
|
||||||
|
|
||||||
|
**Componente C (Consultoria):**
|
||||||
|
- Todos: 165 pts
|
||||||
|
- Padrão: Base 100 + Tempo 50 + Bônus continuidade 15
|
||||||
|
- Uniformidade total no top 10
|
||||||
|
|
||||||
|
**Componente D (Premiações):**
|
||||||
|
- Todos: 180 pts (máximo permitido)
|
||||||
|
- Média de premiações: 10,3 por consultor
|
||||||
|
- Variação: 7-25 premiações
|
||||||
|
|
||||||
|
### Perfil dos Líderes
|
||||||
|
|
||||||
|
**Anos de Atuação:**
|
||||||
|
- Média: 12,7 anos
|
||||||
|
- Variação: 11,0 - 14,5 anos
|
||||||
|
- Todos são veteranos (10+ anos)
|
||||||
|
|
||||||
|
**Status de Atividade:**
|
||||||
|
- Ativos: 3 (30%)
|
||||||
|
- Inativos: 7 (70%)
|
||||||
|
|
||||||
|
**Áreas de Conhecimento:**
|
||||||
|
- Ciências Biológicas: 3 consultores
|
||||||
|
- Áreas diversas: 7 consultores
|
||||||
|
|
||||||
|
**Padrão de Coordenação:**
|
||||||
|
- Todos possuem coordenação ATIVA no momento
|
||||||
|
- Todos exceto 1 tiveram retorno à coordenação
|
||||||
|
- Média de 2,3 coordenações por consultor
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Observações Técnicas
|
||||||
|
|
||||||
|
### Limitações da Análise
|
||||||
|
|
||||||
|
1. **Componente B = 0:** O Elasticsearch não contém dados de coordenação de PPG. Esses dados estão no Oracle (SUCUPIRA_PAINEL). Se incluídos, o ranking poderia mudar significativamente.
|
||||||
|
|
||||||
|
2. **Amostra de 100 candidatos:** A query com boost buscou apenas 100 candidatos pré-ordenados pelo Elasticsearch. É possível que candidatos com alta pontuação real estejam fora dessa amostra.
|
||||||
|
|
||||||
|
3. **Dados de Consultoria incompletos:** O campo "áreas" da consultoria retornou vazio para todos os consultores, sugerindo possível problema na extração de dados do Elasticsearch.
|
||||||
|
|
||||||
|
### Critérios de Desempate
|
||||||
|
|
||||||
|
Quando há empate na pontuação total (casos 4º-10º), a ordenação segue a ordem de retorno do Elasticsearch, que não necessariamente reflete um critério definido.
|
||||||
|
|
||||||
|
**Sugestão:** Implementar critério de desempate explícito:
|
||||||
|
1. Componente A (maior)
|
||||||
|
2. Anos de atuação (maior)
|
||||||
|
3. Status ativo (preferencial)
|
||||||
|
4. ID menor (mais antigo no sistema)
|
||||||
|
|
||||||
|
### Validação dos Cálculos
|
||||||
|
|
||||||
|
Os cálculos seguem rigorosamente os critérios definidos em:
|
||||||
|
- `/home/fred/projetos/ranking/.claude/rules/ranking-consultores-capes.md`
|
||||||
|
- `/home/fred/projetos/ranking/.claude/rules/ranking-queries-implementadas.md`
|
||||||
|
|
||||||
|
**Implementação:** `CalculadorPontuacao` em `/home/fred/projetos/ranking/backend/src/domain/services/calculador_pontuacao.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Próximos Passos Recomendados
|
||||||
|
|
||||||
|
1. **Integrar dados do Oracle** para calcular Componente B (Coordenação PPG)
|
||||||
|
2. **Expandir amostra** para 1.000+ candidatos para garantir cobertura completa
|
||||||
|
3. **Corrigir extração de áreas** na consultoria
|
||||||
|
4. **Implementar critério de desempate** explícito
|
||||||
|
5. **Validar dados de premiações** (verificar se todas as 7-25 premiações são válidas)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Script de Geração:**
|
||||||
|
`/home/fred/projetos/ranking/backend/scripts/top10_ranking.py`
|
||||||
|
|
||||||
|
**Método de Consulta:**
|
||||||
|
Query Elasticsearch com boost por tipo de atuação (conforme especificação técnica)
|
||||||
170
backend/scripts/analise_detalhada.py
Normal file
170
backend/scripts/analise_detalhada.py
Normal 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())
|
||||||
296
backend/scripts/buscar_consultores_especificos.py
Normal file
296
backend/scripts/buscar_consultores_especificos.py
Normal 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())
|
||||||
265
backend/scripts/top10_ranking.py
Normal file
265
backend/scripts/top10_ranking.py
Normal 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())
|
||||||
79
scripts/reload_atuacapes.sh
Executable file
79
scripts/reload_atuacapes.sh
Executable file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
echo "[1/5] Garantindo rede Docker compartilhada..."
|
||||||
|
docker network create shared_network >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
echo "[2/5] Subindo oracle18c e backend..."
|
||||||
|
docker compose up -d oracle18c backend
|
||||||
|
|
||||||
|
echo "[3/5] Aguardando backend responder /health..."
|
||||||
|
for i in {1..30}; do
|
||||||
|
if docker compose exec backend python - <<'PY' >/dev/null 2>&1; then
|
||||||
|
import httpx, sys
|
||||||
|
try:
|
||||||
|
r = httpx.get("http://localhost:8000/api/v1/health", verify=False, timeout=15)
|
||||||
|
if r.status_code == 200:
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
sys.exit(1)
|
||||||
|
PY
|
||||||
|
echo " Backend OK."
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo " Tentativa ${i}/30... aguardando 5s"
|
||||||
|
sleep 5
|
||||||
|
if [ "$i" -eq 30 ]; then
|
||||||
|
echo "ERRO: backend não respondeu /health."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[4/5] Disparando job do ranking (limpar_antes=true)..."
|
||||||
|
docker compose exec backend python - <<'PY'
|
||||||
|
import httpx
|
||||||
|
client = httpx.Client(verify=False, timeout=120)
|
||||||
|
resp = client.post("http://localhost:8000/api/v1/ranking/processar", json={"limpar_antes": True})
|
||||||
|
print("POST /api/v1/ranking/processar ->", resp.status_code, resp.text)
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "[5/5] Acompanhando status até finalizar..."
|
||||||
|
docker compose exec backend python - <<'PY'
|
||||||
|
import httpx, time
|
||||||
|
client = httpx.Client(verify=False, timeout=120)
|
||||||
|
while True:
|
||||||
|
r = client.get("http://localhost:8000/api/v1/ranking/status")
|
||||||
|
data = r.json()
|
||||||
|
print(f"{time.strftime('%H:%M:%S')} | running={data.get('running')} | {data.get('processados')}/{data.get('total')} ({data.get('progress')}%) | msg={data.get('mensagem')}")
|
||||||
|
if not data.get("running"):
|
||||||
|
break
|
||||||
|
time.sleep(15)
|
||||||
|
|
||||||
|
print("Coletando contagens finais...")
|
||||||
|
dsn = None
|
||||||
|
try:
|
||||||
|
import os, cx_Oracle
|
||||||
|
dsn = os.getenv("ORACLE_LOCAL_DSN", "oracle18c:1521/XEPDB1")
|
||||||
|
user = os.getenv("ORACLE_LOCAL_USER", "local123")
|
||||||
|
pwd = os.getenv("ORACLE_LOCAL_PASSWORD", "local123")
|
||||||
|
conn = cx_Oracle.connect(user, pwd, dsn)
|
||||||
|
cur = conn.cursor()
|
||||||
|
for sql in [
|
||||||
|
"select count(*) from tb_ranking_consultor",
|
||||||
|
"select count(*) from tb_ranking_consultor where componente_a>0",
|
||||||
|
"select count(*) from tb_ranking_consultor where componente_c>0",
|
||||||
|
"select count(*) from tb_ranking_consultor where componente_d>0",
|
||||||
|
"select max(pontuacao_total) from tb_ranking_consultor"
|
||||||
|
]:
|
||||||
|
cur.execute(sql)
|
||||||
|
print(sql, cur.fetchone()[0])
|
||||||
|
cur.close(); conn.close()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"AVISO: não foi possível coletar contagens finais (dsn={dsn}): {e}")
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "Pronto."
|
||||||
Reference in New Issue
Block a user