diff --git a/COMPONENTE_B_STATUS.md b/COMPONENTE_B_STATUS.md deleted file mode 100644 index e4fd60f..0000000 --- a/COMPONENTE_B_STATUS.md +++ /dev/null @@ -1,146 +0,0 @@ -# Componente B - Coordenação de Programa PPG - -## Status: ✅ IMPLEMENTADO E PRONTO - -O Componente B foi completamente implementado e está pronto para uso em ambiente com acesso à rede CAPES. - ---- - -## Implementação Completa - -### 1. Duas Conexões Oracle Simultâneas - -**Oracle LOCAL (Docker):** -- Credenciais: `ORACLE_LOCAL_USER/PASSWORD/DSN` -- Uso: Salvar `TB_RANKING_CONSULTOR` -- Status: ✅ Conectado - -**Oracle REMOTO (CAPES):** -- Credenciais: `ORACLE_REMOTE_USER/PASSWORD/DSN` -- Uso: Ler `SUCUPIRA_PAINEL.VM_COORDENADOR` -- Status: ✅ Conectado - -### 2. Query Oracle Implementada - -Arquivo: `backend/src/infrastructure/oracle/client.py:78` - -```sql -SELECT - c.ID_PESSOA, - c.ID_PROGRAMA_SNPG, - p.NM_PROGRAMA, - p.CD_PROGRAMA_PPG, - p.DS_CONCEITO AS NOTA_PPG, - p.NM_PROGRAMA_MODALIDADE, - aa.NM_AREA_AVALIACAO, - c.DT_INICIO_VIGENCIA, - c.DT_FIM_VIGENCIA -FROM SUCUPIRA_PAINEL.VM_COORDENADOR c -INNER JOIN SUCUPIRA_PAINEL.VM_PROGRAMA_SUCUPIRA p - ON c.ID_PROGRAMA_SNPG = p.ID_PROGRAMA -LEFT JOIN SUCUPIRA_PAINEL.VM_AREA_CONHECIMENTO ac - ON p.ID_AREA_CONHECIMENTO_ATUAL = ac.ID_AREA_CONHECIMENTO -LEFT JOIN SUCUPIRA_PAINEL.VM_AREA_AVALIACAO aa - ON ac.ID_AREA_AVALIACAO = aa.ID_AREA_AVALIACAO -WHERE c.ID_PESSOA = :id_pessoa -ORDER BY c.DT_INICIO_VIGENCIA DESC -``` - -### 3. Cálculo de Pontuação - -Arquivo: `backend/src/domain/services/calculador_pontuacao.py:105-153` - -**Regras Implementadas (máximo 180 pts):** - -| Critério | Cálculo | Máximo | -|----------|---------|--------| -| Base | 70 pts por ser coordenador | 70 | -| Tempo | 5 pts/ano completo | 50 | -| Programas adicionais | 20 pts/programa extra | 40 | -| Nota do PPG | Escala: 7=20, 6=15, 5=10, 4=5, 3=0 | 20 | -| **TOTAL** | - | **180** | - -**Características:** -- Usa **MAIOR nota** entre todos os programas coordenados -- Soma **anos completos** de todas as coordenações -- Conta programas **distintos** (mesmo coordenando múltiplas vezes) -- Suporta notas: 3, 4, 5, 6, 7 (ignora "A" ou valores inválidos) - ---- - -## Fluxo de Dados - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 1. Elasticsearch (ATUACAPES) │ -│ └─> Busca consultores com atuações relevantes │ -│ │ -│ 2. Oracle REMOTO (CAPES) │ -│ └─> Para cada ID_PESSOA: │ -│ └─> Busca coordenações PPG em SUCUPIRA_PAINEL │ -│ │ -│ 3. Backend Python │ -│ └─> Calcula pontuação A + B + C + D │ -│ └─> Componente B: 70 + tempo + extras + nota │ -│ │ -│ 4. Oracle LOCAL (Docker) │ -│ └─> Salva ranking completo em TB_RANKING_CONSULTOR │ -│ │ -└─────────────────────────────────────────────────────────────┘ -``` - ---- - -## Arquivos Modificados - -1. **`.env`** - Separou variáveis LOCAL e REMOTE -2. **`config.py`** - Lê ambas configurações -3. **`dependencies.py`** - Cria dois clientes Oracle -4. **`app.py`** - Conecta nos dois na inicialização -5. **`processar_ranking.py`** - Recebe ambos os clientes -6. **`consultor_repository_impl.py`** - Usa oracle_remote para PPG -7. **`ranking_repository.py`** - Usa oracle_local para ranking -8. **`calculador_pontuacao.py`** - Corrigido cálculo de nota - ---- - -## Teste em Desenvolvimento - -**Ambiente Docker Local:** -- ✅ Oracle LOCAL conectado -- ✅ Oracle REMOTO conectado (CAPES) -- ❌ Elasticsearch inacessível (rede CAPES necessária) - -**Próximo Passo:** -Testar em ambiente com acesso completo à rede CAPES para validar Componente B com dados reais. - ---- - -## Exemplo de Pontuação Completa - -**Coordenador com:** -- 2 programas distintos (1 com nota 7, 1 com nota 5) -- 8 anos de coordenação total -- Ambos os programas na mesma área - -**Cálculo:** -- Base: 70 pts -- Tempo: 8 anos × 5 = 40 pts -- Extras: (2-1) × 20 = 20 pts -- Nota: max(7, 5) = 7 → 20 pts -- **Total B: 150 pts** - -**Ranking final:** A + 150 + C + D - ---- - -## Validação - -✅ Código implementado conforme documento oficial -✅ Query Oracle testada e funcional -✅ Duas conexões simultâneas funcionando -✅ Escala de nota correta (7=20, 6=15, 5=10, 4=5, 3=0) -✅ Teto de 180 pts respeitado -✅ Programas distintos calculados corretamente - -**Status: PRONTO PARA PRODUÇÃO** diff --git a/RESUMO_PROJETO.md b/RESUMO_PROJETO.md deleted file mode 100644 index 3adaf75..0000000 --- a/RESUMO_PROJETO.md +++ /dev/null @@ -1,473 +0,0 @@ -# Resumo do Projeto - Ranking de 350k Consultores CAPES - -## O QUE FOI FEITO - -### ✅ Sistema de Ranking Completo Implementado - -**Objetivo:** Classificar TODOS os 300k+ consultores CAPES (governo federal, vai para auditoria) - -**Arquitetura implementada:** -``` -Elasticsearch (ATUACAPES) → Busca 350k consultores com atuações - ↓ -Oracle REMOTO (CAPES) → Busca coordenações PPG (SUCUPIRA_PAINEL) - ↓ -Backend Python → Calcula pontuação A+B+C+D - ↓ -Oracle LOCAL (Docker) → Salva em TB_RANKING_CONSULTOR (cache) - ↓ -Frontend → Lê tabela paginada (50 por página) -``` - ---- - -## COMPONENTES DE PONTUAÇÃO (Documento Oficial CAPES) - -### Componente A - Coordenação CAPES (máx 450 pts) -- CA, CAJ, CAJ-MP, CAM -- Base + tempo + áreas extras + bônus ativo + retorno -- **✅ IMPLEMENTADO E FUNCIONANDO** - -### Componente B - Coordenação de Programa PPG (máx 180 pts) -- Base: 70 pts -- Tempo: 5 pts/ano (máx 50) -- Programas extras: 20 pts/programa (máx 40) -- Nota PPG: 7=20, 6=15, 5=10, 4=5, 3=0 (máx 20) -- **✅ CÓDIGO IMPLEMENTADO MAS NÃO FUNCIONA** (problema de rede) - -### Componente C - Consultoria (máx 230 pts) -- Consultor ativo/histórico + tempo + eventos + áreas -- **✅ IMPLEMENTADO E FUNCIONANDO** - -### Componente D - Premiações (máx 180 pts) -- Premiações recebidas + avaliações + inscrições -- **✅ IMPLEMENTADO E FUNCIONANDO** - ---- - -## ARQUITETURA DO CÓDIGO - -### Camadas (Clean Architecture) - -``` -backend/ -├── src/ -│ ├── domain/ # Entidades e regras de negócio -│ │ ├── entities/ -│ │ │ └── consultor.py # Consultor, Coordenação, Consultoria, etc -│ │ ├── services/ -│ │ │ └── calculador_pontuacao.py # CÁLCULO DOS 4 COMPONENTES -│ │ └── value_objects/ -│ │ └── pontuacao.py # ComponentePontuacao, PontuacaoCompleta -│ │ -│ ├── application/ # Casos de uso e jobs -│ │ ├── use_cases/ -│ │ │ └── obter_ranking.py -│ │ └── jobs/ -│ │ ├── processar_ranking.py # JOB PRINCIPAL (350k) -│ │ ├── job_status.py # Status em tempo real -│ │ └── scheduler.py # Loop asyncio (sem cron) -│ │ -│ ├── infrastructure/ # Acesso a dados -│ │ ├── elasticsearch/ -│ │ │ └── client.py # Scroll API para 350k -│ │ ├── oracle/ -│ │ │ ├── client.py # OracleClient genérico -│ │ │ └── ranking_repository.py # CRUD da TB_RANKING_CONSULTOR -│ │ └── repositories/ -│ │ └── consultor_repository_impl.py # Constrói consultores -│ │ -│ └── interface/ # API REST -│ └── api/ -│ ├── app.py # FastAPI + lifespan (conecta Oracles) -│ ├── routes.py # Endpoints -│ ├── dependencies.py # DUAS CONEXÕES ORACLE -│ └── config.py # Settings (lê .env) -│ -├── sql/ -│ ├── schema_ranking.sql # TB_RANKING_CONSULTOR + SP_ATUALIZAR_POSICOES -│ └── schema_ppg.sql # TB_COORDENACAO_PROGRAMA (não usado) -│ -└── scripts/ - └── popular_componente_b.py # Script emergencial (roda no host) -``` - ---- - -## BANCO DE DADOS - -### TB_RANKING_CONSULTOR (Oracle LOCAL) - -```sql -CREATE TABLE TB_RANKING_CONSULTOR ( - ID_PESSOA NUMBER(10) PRIMARY KEY, - NOME VARCHAR2(200), - POSICAO NUMBER(10), - PONTUACAO_TOTAL NUMBER(10,2), - COMPONENTE_A NUMBER(10,2), - COMPONENTE_B NUMBER(10,2), ← PROBLEMA: Está zerado - COMPONENTE_C NUMBER(10,2), - COMPONENTE_D NUMBER(10,2), - ATIVO CHAR(1), - ANOS_ATUACAO NUMBER(5,1), - DT_CALCULO TIMESTAMP, - JSON_DETALHES CLOB -); -``` - -**Status atual:** 350.215 registros com A, C, D calculados, mas B=0 - ---- - -## CONFIGURAÇÃO (.env) - -### Desenvolvimento (atual) - -```bash -# Elasticsearch -ES_URL=http://elastic-atuacapes.hom.capes.gov.br:9200 -ES_INDEX=atuacapes -ES_USER=admin-atuacapes -ES_PASSWORD=O}!S0bj%FhJ: - -# Oracle LOCAL (Docker) - Para salvar ranking -ORACLE_LOCAL_USER=local123 -ORACLE_LOCAL_PASSWORD=local123 -ORACLE_LOCAL_DSN=oracle18c:1521/XEPDB1 - -# Oracle REMOTO (CAPES) - Para ler SUCUPIRA_PAINEL -ORACLE_REMOTE_USER=FREDERICOAC -ORACLE_REMOTE_PASSWORD=FREDEricoac -ORACLE_REMOTE_DSN=oracledhtsrv02.hom.capes.gov.br:1521/hom_dr -``` - -### Produção (futuro) - -```bash -# Mesmo Oracle para tudo -ORACLE_LOCAL_DSN=oracle-prod.capes:1521/PROD -ORACLE_REMOTE_DSN=oracle-prod.capes:1521/PROD # Mesmo! -``` - ---- - -## COMMITS IMPORTANTES - -``` -f69bcd9 - feat: Implementa job de ranking para 300k consultores -c6aaf66 - refactor: Substitui APScheduler por asyncio nativo para OCP -e11cdcd - feat: Implementa duas conexões Oracle simultâneas -57ef5a7 - fix: Corrige cálculo de pontuação da nota do PPG no Componente B -178fc2a - docs: Adiciona documentação completa do Componente B (PPG) -``` - ---- - -## O PROBLEMA ATUAL - -### Componente B = 0 para todos os 350k consultores - -**Causa raiz:** -Container Docker **não consegue acessar** `oracledhtsrv02.hom.capes.gov.br` porque: -- VPN CAPES está no HOST -- Container está em rede isolada -- DNS não resolve hostname interno da CAPES - -**Evidências:** -```bash -# Dentro do container: -oracle_remote_client.is_connected = False - -# Log do erro: -AVISO Oracle: ORA-12154: TNS:could not resolve the connect identifier specified -``` - -**Resultado:** -- Job processa 350k consultores ✅ -- Componente A, C, D calculados ✅ -- Componente B = 0 (não consulta Oracle REMOTO) ❌ - ---- - -## SOLUÇÕES POSSÍVEIS - -### ⭐ SOLUÇÃO 1: Script Standalone (RECOMENDADA - RÁPIDA) - -**Arquivo criado:** `backend/scripts/popular_componente_b.py` - -**Como funciona:** -- Roda DIRETO NO HOST (não no Docker) -- Usa VPN do host para acessar Oracle CAPES -- Lê 350k IDs da tabela -- Busca PPG do SUCUPIRA_PAINEL -- Atualiza COMPONENTE_B em batch de 1000 -- Atualiza posições - -**Executar:** -```bash -cd /home/fred/projetos/ranking/backend -pip3 install cx-Oracle # Se não tiver -python3 scripts/popular_componente_b.py -``` - -**Tempo estimado:** 20-40 minutos para 350k consultores - -**Vantagens:** -- ✅ Resolve AGORA sem mexer em Docker -- ✅ Usa VPN que já está funcionando -- ✅ Roda 1x para corrigir os dados -- ✅ Depois o job normal funciona (quando tiver em produção) - ---- - -### SOLUÇÃO 2: Extra Hosts no Docker - -```yaml -# docker-compose.yml -backend: - extra_hosts: - - "oracledhtsrv02.hom.capes.gov.br:IP_DO_SERVIDOR" -``` - -**Precisa:** Descobrir o IP real de `oracledhtsrv02.hom.capes.gov.br` - -```bash -nslookup oracledhtsrv02.hom.capes.gov.br -``` - ---- - -### SOLUÇÃO 3: Network Mode Host - -```yaml -# docker-compose.yml -backend: - network_mode: "host" # Usa rede do host -``` - -**Problema:** Perde isolamento, pode conflitar portas - ---- - -## FRONTEND - -### Funcionalidades Implementadas - -**2 Modos de visualização:** - -1. **Top N (Rápido)** - - Endpoint: `/api/v1/ranking/detalhado?limite=100` - - Busca do Elasticsearch + calcula na hora - - Bom para top 10/50/100/500 - -2. **Ranking Completo (300k)** - - Endpoint: `/api/v1/ranking/paginado?page=1&size=50` - - Lê `TB_RANKING_CONSULTOR` direto - - Paginação: 350k consultores, 70k páginas - -**Componentes:** -- ✅ `RankingPaginado.jsx` - Tabela com paginação -- ✅ `App.jsx` - Seletor de modo -- ✅ Barra de progresso do job em tempo real -- ✅ Estatísticas (total, ativos, média, distribuição) -- ✅ Botão reprocessar - -**Acesso:** http://localhost:5173 - ---- - -## JOB DE PROCESSAMENTO - -### Scheduler (asyncio nativo - sem cron) - -**Arquivo:** `backend/src/application/jobs/scheduler.py` - -**Como funciona:** -```python -# Calcula tempo até 3h da manhã -proxima_execucao = datetime.now().replace(hour=3, minute=0) -await asyncio.sleep(segundos_ate_proxima) -await job.executar(limpar_antes=True) -# Loop infinito -``` - -**Execução:** -- Automática: Diariamente às 3h -- Manual: `POST /api/v1/ranking/processar` - -**Compatível com OCP/Kubernetes** (não usa cron do sistema) - ---- - -## PERFORMANCE - -**Máquina atual:** -- CPU: 5.8GHz boost -- RAM: 64GB DDR5 -- Processamento: ~350k consultores em 25-30 minutos - -**Batch:** -- 1.000 consultores por batch -- ~350 batches total -- MERGE (upsert) em batch - ---- - -## O QUE PRECISA SER FEITO PARA CONCLUIR - -### 🎯 ÚNICO PROBLEMA: Componente B zerado - -**Situação:** -- ✅ Código do Componente B está correto e implementado -- ✅ Duas conexões Oracle configuradas -- ✅ Query SUCUPIRA_PAINEL pronta -- ❌ Container Docker não acessa rede CAPES - -**Solução mais rápida:** - -1. **Rodar script standalone:** - ```bash - cd /home/fred/projetos/ranking/backend - python3 scripts/popular_componente_b.py - ``` - -2. **Ou configurar extra_hosts no docker-compose.yml:** - ```bash - # Descobrir IP: - nslookup oracledhtsrv02.hom.capes.gov.br - - # Adicionar ao docker-compose.yml: - backend: - extra_hosts: - - "oracledhtsrv02.hom.capes.gov.br:IP_AQUI" - ``` - -3. **Depois rodar job completo:** - ```bash - curl -X POST -H "Content-Type: application/json" \ - -d '{"limpar_antes": true}' \ - http://localhost:8000/api/v1/ranking/processar - ``` - ---- - -## VALIDAÇÃO FINAL - -Quando o Componente B funcionar, verificar: - -```sql --- Deve ter consultores com B > 0 -SELECT - COUNT(*) AS TOTAL, - SUM(CASE WHEN COMPONENTE_B > 0 THEN 1 ELSE 0 END) AS COM_PPG, - MAX(COMPONENTE_B) AS MAX_B, - AVG(COMPONENTE_B) AS MEDIA_B -FROM TB_RANKING_CONSULTOR; - --- Ver top 5 com PPG -SELECT ID_PESSOA, NOME, COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, PONTUACAO_TOTAL -FROM TB_RANKING_CONSULTOR -WHERE POSICAO <= 5 -ORDER BY POSICAO; -``` - -**Esperado:** Alguns consultores com B entre 70-180 pts - ---- - -## DOCUMENTAÇÃO GERADA - -- `COMPONENTE_B_STATUS.md` - Implementação completa do Componente B -- `SCHEDULER.md` - Como funciona o scheduler sem cron (OCP) -- `backend/sql/schema_ranking.sql` - DDL da tabela principal -- `.claude/rules/ranking-*.md` - Regras oficiais e queries - ---- - -## COMANDOS ÚTEIS - -### Verificar tabela -```bash -echo "SELECT COUNT(*), AVG(COMPONENTE_B) FROM TB_RANKING_CONSULTOR;" | \ - sqlplus -S local123/local123@127.0.0.1:1521/XEPDB1 -``` - -### Rodar job manualmente -```bash -curl -X POST -H "Content-Type: application/json" \ - -d '{"limpar_antes": true}' \ - http://localhost:8000/api/v1/ranking/processar -``` - -### Ver status -```bash -curl -s http://localhost:8000/api/v1/ranking/status | python3 -m json.tool -``` - -### Ver estatísticas -```bash -curl -s http://localhost:8000/api/v1/ranking/estatisticas | python3 -m json.tool -``` - ---- - -## BRANCH E COMMITS - -**Branch:** `develop` - -**Últimos commits:** -``` -178fc2a - docs: Adiciona documentação completa do Componente B (PPG) -57ef5a7 - fix: Corrige cálculo de pontuação da nota do PPG no Componente B -e11cdcd - feat: Implementa duas conexões Oracle simultâneas -f69bcd9 - feat: Implementa job de ranking para 300k consultores -c6aaf66 - refactor: Substitui APScheduler por asyncio nativo para OCP -``` - ---- - -## RESUMO EXECUTIVO - -**Status Geral:** 95% completo - -**Funciona:** -- ✅ 350.215 consultores processados -- ✅ Componentes A, C, D calculados corretamente -- ✅ Paginação de 350k consultores -- ✅ Frontend com 2 modos (Top N + Completo) -- ✅ Scheduler asyncio para OCP -- ✅ Duas conexões Oracle configuradas -- ✅ Performance excelente (~30min para 350k) - -**Não funciona (1 problema):** -- ❌ Componente B = 0 (Container não acessa rede CAPES) - -**Para resolver:** -- Rodar `scripts/popular_componente_b.py` no host (20-40min) -- OU configurar DNS/extra_hosts no docker-compose.yml -- OU deploy em servidor dentro da rede CAPES - -**Depois disso:** Sistema 100% funcional e pronto para produção. - ---- - -## CONTEXTO IMPORTANTE - -- Sistema para GOVERNO FEDERAL -- Vai passar por AUDITORIA -- Dados REAIS obrigatórios (zero mocks/fakes) -- Recursos CAPES intocáveis -- Documento técnico oficial deve ser seguido à risca -- Ambiente: OCP (sem cron disponível nos PODs) - ---- - -## PRÓXIMA SESSÃO - O QUE FAZER - -1. **Verificar VPN CAPES ativa** -2. **Rodar script:** `python3 backend/scripts/popular_componente_b.py` -3. **Validar:** Componente B > 0 em alguns consultores -4. **Testar frontend:** http://localhost:5173 -5. **Commit final e push** - -**Tempo estimado:** 30-60 minutos total diff --git a/backend/SCHEDULER.md b/backend/SCHEDULER.md deleted file mode 100644 index 45fe075..0000000 --- a/backend/SCHEDULER.md +++ /dev/null @@ -1,60 +0,0 @@ -# Scheduler de Ranking - Solução para OCP - -## Problema - -Em ambientes OpenShift Container Platform (OCP), não é possível usar cron dentro dos PODs. - -## Solução Implementada - -Loop asyncio nativo Python sem dependências externas. - -### Características - -- **100% Python nativo** - Usa apenas `asyncio` e `datetime` -- **Portável** - Funciona em qualquer ambiente Docker/Kubernetes/OCP -- **Sem dependências** - Removemos `apscheduler` do requirements.txt -- **Simples** - Loop infinito com sleep calculado até próximo horário - -### Funcionamento - -```python -# Calcula tempo até próxima execução (ex: 3h da manhã) -proxima_execucao = datetime.now().replace(hour=3, minute=0, second=0) -if agora >= proxima_execucao: - proxima_execucao += timedelta(days=1) - -# Aguarda assincronamente -await asyncio.sleep(segundos_ate_proxima) - -# Executa job -await job.executar(limpar_antes=True) -``` - -### Configuração - -Padrão: **3h da manhã** (horário do servidor) - -Para alterar: edite `backend/src/interface/api/app.py` linha 23: - -```python -await scheduler.iniciar(hora_alvo=3) # Altere para outra hora -``` - -### Logs - -``` -Próxima execução do ranking: 11/12/2025 03:00:00 -Scheduler do ranking iniciado: job rodará diariamente às 3h -``` - -### Execução Manual - -Se precisar rodar fora do schedule: - -```bash -curl -X POST "http://localhost:8000/api/v1/ranking/processar?limpar=true" -``` - -### Migração para OCP - -Nenhuma configuração adicional necessária. O container já está pronto. diff --git a/backend/logs/auditoria_ranking.csv b/backend/logs/auditoria_ranking.csv new file mode 100644 index 0000000..344f5f7 --- /dev/null +++ b/backend/logs/auditoria_ranking.csv @@ -0,0 +1,62 @@ +id,pos_db,pos_calc,total_db,total_calc,delta,comp_a_db,comp_a_calc,comp_b_db,comp_b_calc,comp_c_db,comp_c_calc,comp_d_db,comp_d_calc,obs +12932,1,,775.0,775,0.0,270.0,270,135.0,135,190.0,190,180.0,180, +6370,2,,770.0,770,0.0,290.0,290,110.0,110,190.0,190,180.0,180, +536531,3,,770.0,770,0.0,300.0,300,100.0,100,190.0,190,180.0,180, +8127,4,,760.0,760,0.0,250.0,250,140.0,140,190.0,190,180.0,180, +509974,5,,760.0,760,0.0,230.0,230,160.0,160,190.0,190,180.0,180, +5420,6,,750.0,750,0.0,240.0,240,140.0,140,190.0,190,180.0,180, +7268,7,,750.0,750,0.0,270.0,270,160.0,160,190.0,190,130.0,130, +7825,8,,750.0,750,0.0,250.0,250,130.0,130,190.0,190,180.0,180, +14527,9,,750.0,750,0.0,240.0,240,140.0,140,190.0,190,180.0,180, +5843,10,,740.0,740,0.0,300.0,300,90.0,90,190.0,190,160.0,160, +5439,11,,735.0,735,0.0,250.0,250,100.0,100,215.0,215,170.0,170, +6392,12,,735.0,735,0.0,240.0,240,125.0,125,190.0,190,180.0,180, +510948,13,,735.0,735,0.0,280.0,280,110.0,110,215.0,215,130.0,130, +6273,14,,725.0,725,0.0,300.0,300,155.0,155,190.0,190,80.0,80, +22484,15,,725.0,725,0.0,220.0,220,110.0,110,215.0,215,180.0,180, +16,16,,720.0,720,0.0,220.0,220,130.0,130,190.0,190,180.0,180, +11353,17,,720.0,720,0.0,210.0,210,115.0,115,215.0,215,180.0,180, +15100,18,,720.0,720,0.0,240.0,240,110.0,110,190.0,190,180.0,180, +17385,19,,720.0,720,0.0,310.0,310,100.0,100,230.0,230,80.0,80, +510371,20,,720.0,720,0.0,200.0,200,125.0,125,215.0,215,180.0,180, +147706,100000,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147709,100001,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147711,100002,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147714,100003,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147722,100004,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147723,100005,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147730,100006,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147732,100007,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147733,100008,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147738,100009,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147739,100010,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147741,100011,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147744,100012,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147749,100013,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147753,100014,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147754,100015,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147756,100016,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147761,100017,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147768,100018,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147770,100019,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +147771,100020,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4864134,350215,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4862416,350214,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4862410,350213,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4862407,350212,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4860839,350211,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4860812,350210,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4860796,350209,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4860722,350208,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4860591,350207,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4859353,350206,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4859351,350205,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4859325,350204,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4859322,350203,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4859100,350202,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4859026,350201,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4859022,350200,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4858994,350199,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4858399,350198,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4858398,350197,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, +4857410,350196,,0.0,0,0.0,0.0,0,0.0,0,0.0,0,0.0,0, diff --git a/backend/sql/schema_ppg.sql b/backend/sql/schema_ppg.sql deleted file mode 100644 index 29e2663..0000000 --- a/backend/sql/schema_ppg.sql +++ /dev/null @@ -1,20 +0,0 @@ --- Schema para Coordenações de Programa (PPG) --- Dados extraídos de SUCUPIRA_PAINEL via MCP - -CREATE TABLE TB_COORDENACAO_PROGRAMA ( - ID_PESSOA NUMBER(10) NOT NULL, - ID_PROGRAMA_SNPG NUMBER(10) NOT NULL, - NM_PROGRAMA VARCHAR2(500), - CD_PROGRAMA_PPG VARCHAR2(50), - NOTA_PPG VARCHAR2(10), - NM_PROGRAMA_MODALIDADE VARCHAR2(100), - NM_AREA_AVALIACAO VARCHAR2(200), - DT_INICIO_VIGENCIA DATE, - DT_FIM_VIGENCIA DATE, - DH_CARGA TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT PK_COORDENACAO_PROGRAMA PRIMARY KEY (ID_PESSOA, ID_PROGRAMA_SNPG, DT_INICIO_VIGENCIA) -); - -CREATE INDEX IDX_COORD_PPG_PESSOA ON TB_COORDENACAO_PROGRAMA(ID_PESSOA); -CREATE INDEX IDX_COORD_PPG_PROGRAMA ON TB_COORDENACAO_PROGRAMA(ID_PROGRAMA_SNPG); -CREATE INDEX IDX_COORD_PPG_ATIVO ON TB_COORDENACAO_PROGRAMA(DT_FIM_VIGENCIA); diff --git a/docker-compose.yml b/docker-compose.yml index 88cf470..95caa04 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,13 +17,6 @@ services: volumes: - ./backend/src:/app/src - /etc/localtime:/etc/localtime:ro - dns: - - 172.19.100.16 - - 172.19.100.17 - - 8.8.8.8 - depends_on: - oracle18c: - condition: service_healthy networks: - shared_network restart: unless-stopped diff --git a/frontend/src/components/RankingPaginado.css b/frontend/src/components/RankingPaginado.css deleted file mode 100644 index b9681b4..0000000 --- a/frontend/src/components/RankingPaginado.css +++ /dev/null @@ -1,197 +0,0 @@ -.ranking-paginado { - padding: 1rem; - max-width: 1400px; - margin: 0 auto; -} - -.job-progress { - background: #fff3cd; - border: 1px solid #ffc107; - border-radius: 8px; - padding: 1.5rem; - margin-bottom: 2rem; -} - -.job-progress h3 { - margin: 0 0 1rem 0; - color: #856404; -} - -.progress-bar { - width: 100%; - height: 30px; - background: #e0e0e0; - border-radius: 15px; - overflow: hidden; - margin: 1rem 0; -} - -.progress-fill { - height: 100%; - background: linear-gradient(90deg, #4caf50, #66bb6a); - display: flex; - align-items: center; - justify-content: center; - color: white; - font-weight: bold; - transition: width 0.3s ease; -} - -.estatisticas { - background: #f5f5f5; - border-radius: 8px; - padding: 1.5rem; - margin-bottom: 2rem; -} - -.estatisticas h3 { - margin: 0 0 1rem 0; -} - -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; - margin-bottom: 1rem; -} - -.stat { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.stat-label { - font-size: 0.85rem; - color: #666; -} - -.stat-value { - font-size: 1.25rem; - font-weight: 600; - color: #333; -} - -.btn-processar { - padding: 0.75rem 1.5rem; - background: #2196f3; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 1rem; - font-weight: 500; - transition: background 0.2s; -} - -.btn-processar:hover:not(:disabled) { - background: #1976d2; -} - -.btn-processar:disabled { - background: #ccc; - cursor: not-allowed; -} - -.loading, -.error { - text-align: center; - padding: 2rem; - font-size: 1.1rem; -} - -.error { - color: #d32f2f; -} - -.ranking-table { - width: 100%; - border-collapse: collapse; - background: white; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); - border-radius: 8px; - overflow: hidden; -} - -.ranking-table thead { - background: #1976d2; - color: white; -} - -.ranking-table th, -.ranking-table td { - padding: 0.75rem 1rem; - text-align: left; -} - -.ranking-table th { - font-weight: 600; - font-size: 0.9rem; - text-transform: uppercase; -} - -.ranking-table tbody tr:nth-child(even) { - background: #f9f9f9; -} - -.ranking-table tbody tr:hover { - background: #e3f2fd; -} - -.posicao { - font-weight: 700; - color: #1976d2; -} - -.nome { - font-weight: 500; -} - -.pontuacao-total { - font-weight: 700; - color: #2e7d32; - font-size: 1.1rem; -} - -.ativo { - color: #2e7d32; - font-weight: 600; -} - -.inativo { - color: #666; -} - -.paginacao { - display: flex; - justify-content: center; - align-items: center; - gap: 1rem; - margin-top: 2rem; - padding: 1rem; -} - -.paginacao button { - padding: 0.5rem 1rem; - background: #1976d2; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-weight: 500; - transition: background 0.2s; -} - -.paginacao button:hover:not(:disabled) { - background: #1565c0; -} - -.paginacao button:disabled { - background: #ccc; - cursor: not-allowed; -} - -.page-info { - font-weight: 500; - color: #333; -} diff --git a/frontend/src/components/RankingPaginado.jsx b/frontend/src/components/RankingPaginado.jsx deleted file mode 100644 index 9892a6c..0000000 --- a/frontend/src/components/RankingPaginado.jsx +++ /dev/null @@ -1,196 +0,0 @@ -import { useState, useEffect } from 'react'; -import './RankingPaginado.css'; - -const RankingPaginado = () => { - const [consultores, setConsultores] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [page, setPage] = useState(1); - const [size] = useState(50); - const [total, setTotal] = useState(0); - const [totalPages, setTotalPages] = useState(0); - const [jobStatus, setJobStatus] = useState(null); - const [estatisticas, setEstatisticas] = useState(null); - - const fetchRanking = async () => { - setLoading(true); - setError(null); - try { - const response = await fetch(`/api/v1/ranking/paginado?page=${page}&size=${size}`); - if (!response.ok) throw new Error('Erro ao buscar ranking'); - const data = await response.json(); - setConsultores(data.consultores); - setTotal(data.total); - setTotalPages(data.total_pages); - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - }; - - const fetchJobStatus = async () => { - try { - const response = await fetch('/api/v1/ranking/status'); - if (!response.ok) return; - const data = await response.json(); - setJobStatus(data); - } catch (err) { - console.error('Erro ao buscar status do job:', err); - } - }; - - const fetchEstatisticas = async () => { - try { - const response = await fetch('/api/v1/ranking/estatisticas'); - if (!response.ok) return; - const data = await response.json(); - setEstatisticas(data); - } catch (err) { - console.error('Erro ao buscar estatísticas:', err); - } - }; - - const processarRanking = async () => { - try { - const response = await fetch('/api/v1/ranking/processar', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ limpar_antes: true }) - }); - if (!response.ok) throw new Error('Erro ao processar ranking'); - alert('Processamento iniciado! Acompanhe o progresso abaixo.'); - } catch (err) { - alert('Erro: ' + err.message); - } - }; - - useEffect(() => { - fetchRanking(); - fetchEstatisticas(); - }, [page]); - - useEffect(() => { - fetchJobStatus(); - const interval = setInterval(fetchJobStatus, 5000); - return () => clearInterval(interval); - }, []); - - const irParaPagina = (novaPagina) => { - if (novaPagina >= 1 && novaPagina <= totalPages) { - setPage(novaPagina); - } - }; - - return ( -
{jobStatus.mensagem}
-- {jobStatus.processados.toLocaleString('pt-BR')} / {jobStatus.total.toLocaleString('pt-BR')} consultores - {jobStatus.tempo_decorrido && ` | Tempo: ${jobStatus.tempo_decorrido}`} - {jobStatus.tempo_estimado && ` | Estimado: ${jobStatus.tempo_estimado}`} -
-| Posição | -Nome | -Pontuação Total | -Comp. A | -Comp. B | -Comp. C | -Comp. D | -Status | -Anos | -
|---|---|---|---|---|---|---|---|---|
| #{consultor.posicao} | -{consultor.nome} | -{consultor.pontuacao_total.toFixed(1)} | -{consultor.componente_a.toFixed(1)} | -{consultor.componente_b.toFixed(1)} | -{consultor.componente_c.toFixed(1)} | -{consultor.componente_d.toFixed(1)} | -- {consultor.ativo ? 'Ativo' : 'Inativo'} - | -{consultor.anos_atuacao.toFixed(1)} | -