Refatoracao de qualidade de codigo
- Mover logica de routes.py para RankingMapper na camada de aplicacao - Consolidar funcoes mesclar_periodos e anos_completos_periodos em periodo.py - Extrair RankingCache para modulo separado em infrastructure/cache - Substituir todos os print() por logging adequado - Corrigir exception handlers genericos para tipos especificos - Remover classe Atuacao e atributo atuacoes_raw nao utilizados - Documentar status dos scripts utilitarios
This commit is contained in:
46
backend/scripts/SCRIPTS_STATUS.md
Normal file
46
backend/scripts/SCRIPTS_STATUS.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Status dos Scripts - Backend
|
||||||
|
|
||||||
|
## Resumo
|
||||||
|
|
||||||
|
Scripts utilitários para operações manuais e debug do sistema de ranking.
|
||||||
|
|
||||||
|
## Scripts e Status
|
||||||
|
|
||||||
|
### auditar_ranking.py
|
||||||
|
**Status:** PARCIALMENTE INCOMPATIVEL
|
||||||
|
**Problema:** Usa propriedades antigas (`componente_a`, `componente_b`, `componente_c`, `componente_d`) que agora são `bloco_a`, `bloco_c`, `bloco_d`.
|
||||||
|
**Acao:** Atualizar referencias de propriedades.
|
||||||
|
|
||||||
|
### popular_componente_b.py
|
||||||
|
**Status:** OK
|
||||||
|
**Descricao:** Script standalone para popular COMPONENTE_B manualmente. Duplica logica do `PopularComponenteBJob` mas e util para execucao independente.
|
||||||
|
**Nota:** Usa variaveis de ambiente do `.env`.
|
||||||
|
|
||||||
|
### top10_ranking.py
|
||||||
|
**Status:** INCOMPATIVEL
|
||||||
|
**Problemas:**
|
||||||
|
- Credenciais hardcoded
|
||||||
|
- Usa estrutura antiga de `Consultoria` (total_eventos, eventos_recentes, primeiro_evento, ultimo_evento)
|
||||||
|
- Usa estrutura antiga de `Premiacao` (campo pontos)
|
||||||
|
- `CoordenacaoCapes` sem campo `codigo`
|
||||||
|
- Referencias a `componente_a/b/c/d` em vez de `bloco_a/c/d`
|
||||||
|
- Referencias a `coordenacoes_programas` que nao existe mais
|
||||||
|
|
||||||
|
**Acao:** Reescrever usando `ConsultorRepositoryImpl` do sistema.
|
||||||
|
|
||||||
|
### buscar_consultores_especificos.py
|
||||||
|
**Status:** INCOMPATIVEL
|
||||||
|
**Problemas:** Mesmos problemas do top10_ranking.py.
|
||||||
|
**Acao:** Reescrever usando infraestrutura do sistema.
|
||||||
|
|
||||||
|
### analise_detalhada.py
|
||||||
|
**Status:** FUNCIONAL COM RESSALVAS
|
||||||
|
**Descricao:** Script de analise que apenas le dados brutos do Elasticsearch.
|
||||||
|
**Problema:** Credenciais hardcoded.
|
||||||
|
**Acao:** Mover credenciais para .env.
|
||||||
|
|
||||||
|
## Recomendacoes
|
||||||
|
|
||||||
|
1. Scripts que precisam usar entidades do sistema devem usar os repositorios existentes em vez de reimplementar extracoes
|
||||||
|
2. Credenciais devem sempre vir de variaveis de ambiente
|
||||||
|
3. Considerar consolidar scripts repetitivos em comandos CLI do sistema
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from ...infrastructure.oracle.client import OracleClient
|
from ...infrastructure.oracle.client import OracleClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PopularComponenteBJob:
|
class PopularComponenteBJob:
|
||||||
"""
|
"""
|
||||||
@@ -89,15 +92,15 @@ class PopularComponenteBJob:
|
|||||||
Este método é síncrono; use asyncio.to_thread quando chamá-lo em corrotina.
|
Este método é síncrono; use asyncio.to_thread quando chamá-lo em corrotina.
|
||||||
"""
|
"""
|
||||||
if not self.oracle_local.is_connected:
|
if not self.oracle_local.is_connected:
|
||||||
print("PopularComponenteB: Oracle LOCAL não conectado, abortando.")
|
logger.warning("PopularComponenteB: Oracle LOCAL não conectado, abortando.")
|
||||||
return
|
return
|
||||||
if not self.oracle_remote.is_connected:
|
if not self.oracle_remote.is_connected:
|
||||||
print("PopularComponenteB: Oracle REMOTO não conectado, abortando.")
|
logger.warning("PopularComponenteB: Oracle REMOTO não conectado, abortando.")
|
||||||
return
|
return
|
||||||
|
|
||||||
ids_pessoas = self._buscar_ids_pendentes()
|
ids_pessoas = self._buscar_ids_pendentes()
|
||||||
total_ids = len(ids_pessoas)
|
total_ids = len(ids_pessoas)
|
||||||
print(f"PopularComponenteB: {total_ids} consultores pendentes para COMPONENTE_B")
|
logger.info(f"PopularComponenteB: {total_ids} consultores pendentes para COMPONENTE_B")
|
||||||
|
|
||||||
processados = 0
|
processados = 0
|
||||||
com_ppg = 0
|
com_ppg = 0
|
||||||
@@ -107,7 +110,7 @@ class PopularComponenteBJob:
|
|||||||
try:
|
try:
|
||||||
registros = self._buscar_ppg_lote(lote)
|
registros = self._buscar_ppg_lote(lote)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"PopularComponenteB: erro ao buscar lote {lote[:3]}... -> {e}")
|
logger.warning(f"PopularComponenteB: erro ao buscar lote {lote[:3]}... -> {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
por_pessoa: Dict[int, List[Dict]] = {}
|
por_pessoa: Dict[int, List[Dict]] = {}
|
||||||
@@ -127,14 +130,14 @@ class PopularComponenteBJob:
|
|||||||
|
|
||||||
if len(batch) >= batch_updates:
|
if len(batch) >= batch_updates:
|
||||||
self._aplicar_batch(batch)
|
self._aplicar_batch(batch)
|
||||||
print(f"PopularComponenteB: Processados {processados}/{total_ids} | Com PPG: {com_ppg}")
|
logger.info(f"PopularComponenteB: Processados {processados}/{total_ids} | Com PPG: {com_ppg}")
|
||||||
batch = []
|
batch = []
|
||||||
|
|
||||||
if batch:
|
if batch:
|
||||||
self._aplicar_batch(batch)
|
self._aplicar_batch(batch)
|
||||||
|
|
||||||
self._atualizar_posicoes()
|
self._atualizar_posicoes()
|
||||||
print(f"PopularComponenteB: Finalizado. Processados={processados} Com PPG={com_ppg}")
|
logger.info(f"PopularComponenteB: Finalizado. Processados={processados} Com PPG={com_ppg}")
|
||||||
|
|
||||||
def _aplicar_batch(self, batch: List[Dict[str, int]]) -> None:
|
def _aplicar_batch(self, batch: List[Dict[str, int]]) -> None:
|
||||||
if not batch:
|
if not batch:
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
from ...infrastructure.elasticsearch.client import ElasticsearchClient
|
from ...infrastructure.elasticsearch.client import ElasticsearchClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
from ...infrastructure.oracle.client import OracleClient
|
from ...infrastructure.oracle.client import OracleClient
|
||||||
from ...infrastructure.oracle.ranking_repository import RankingOracleRepository
|
from ...infrastructure.oracle.ranking_repository import RankingOracleRepository
|
||||||
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
||||||
@@ -87,8 +90,8 @@ class ProcessarRankingJob:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
print(f"AVISO: Erro ao processar consultor {doc.get('id')}: {e}")
|
logger.warning(f"Erro ao processar consultor {doc.get('id')}: {e}")
|
||||||
print(f"Traceback: {traceback.format_exc()}")
|
logger.debug(f"Traceback: {traceback.format_exc()}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if consultores_para_inserir:
|
if consultores_para_inserir:
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from datetime import datetime, time, timedelta
|
from datetime import datetime, time, timedelta
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .processar_ranking import ProcessarRankingJob
|
from .processar_ranking import ProcessarRankingJob
|
||||||
from .popular_componente_b_job import PopularComponenteBJob
|
from .popular_componente_b_job import PopularComponenteBJob
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RankingScheduler:
|
class RankingScheduler:
|
||||||
def __init__(self, job: ProcessarRankingJob, job_componente_b: PopularComponenteBJob | None = None):
|
def __init__(self, job: ProcessarRankingJob, job_componente_b: PopularComponenteBJob | None = None):
|
||||||
@@ -24,7 +27,7 @@ class RankingScheduler:
|
|||||||
proxima_execucao += timedelta(days=1)
|
proxima_execucao += timedelta(days=1)
|
||||||
|
|
||||||
segundos_ate_proxima = (proxima_execucao - agora).total_seconds()
|
segundos_ate_proxima = (proxima_execucao - agora).total_seconds()
|
||||||
print(f"Próxima execução do ranking: {proxima_execucao.strftime('%d/%m/%Y %H:%M:%S')}")
|
logger.info(f"Próxima execução do ranking: {proxima_execucao.strftime('%d/%m/%Y %H:%M:%S')}")
|
||||||
|
|
||||||
await asyncio.sleep(segundos_ate_proxima)
|
await asyncio.sleep(segundos_ate_proxima)
|
||||||
|
|
||||||
@@ -39,18 +42,18 @@ class RankingScheduler:
|
|||||||
if not self.running:
|
if not self.running:
|
||||||
break
|
break
|
||||||
|
|
||||||
print(f"[{datetime.now().strftime('%d/%m/%Y %H:%M:%S')}] Executando job de ranking automático")
|
logger.info("Executando job de ranking automático")
|
||||||
await self.job.executar(limpar_antes=True)
|
await self.job.executar(limpar_antes=True)
|
||||||
|
|
||||||
if self.job_componente_b:
|
if self.job_componente_b:
|
||||||
print(f"[{datetime.now().strftime('%d/%m/%Y %H:%M:%S')}] Executando popular_componente_b após ranking")
|
logger.info("Executando popular_componente_b após ranking")
|
||||||
await asyncio.to_thread(self.job_componente_b.executar)
|
await asyncio.to_thread(self.job_componente_b.executar)
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
print("Scheduler cancelado")
|
logger.info("Scheduler cancelado")
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erro no scheduler: {e}")
|
logger.error(f"Erro no scheduler: {e}")
|
||||||
await asyncio.sleep(3600)
|
await asyncio.sleep(3600)
|
||||||
|
|
||||||
async def iniciar(self, hora_alvo: int = 3) -> None:
|
async def iniciar(self, hora_alvo: int = 3) -> None:
|
||||||
@@ -64,7 +67,7 @@ class RankingScheduler:
|
|||||||
self.running = True
|
self.running = True
|
||||||
self.task = asyncio.create_task(self._loop_diario(hora_alvo))
|
self.task = asyncio.create_task(self._loop_diario(hora_alvo))
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
print(f"Scheduler do ranking iniciado: job rodará diariamente às {hora_alvo}h")
|
logger.info(f"Scheduler do ranking iniciado: job rodará diariamente às {hora_alvo}h")
|
||||||
|
|
||||||
def parar(self) -> None:
|
def parar(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
3
backend/src/application/mappers/__init__.py
Normal file
3
backend/src/application/mappers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .ranking_mapper import RankingMapper
|
||||||
|
|
||||||
|
__all__ = ["RankingMapper"]
|
||||||
104
backend/src/application/mappers/ranking_mapper.py
Normal file
104
backend/src/application/mappers/ranking_mapper.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from ...domain.entities.consultor_ranking import ConsultorRanking
|
||||||
|
from ...interface.schemas.ranking_schema import ConsultorRankingResumoSchema
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RankingMapper:
|
||||||
|
@staticmethod
|
||||||
|
def consultor_ranking_to_schema(c: ConsultorRanking) -> ConsultorRankingResumoSchema:
|
||||||
|
consultoria = None
|
||||||
|
coordenacoes_capes = None
|
||||||
|
inscricoes = None
|
||||||
|
avaliacoes_comissao = None
|
||||||
|
premiacoes = None
|
||||||
|
bolsas_cnpq = None
|
||||||
|
participacoes = None
|
||||||
|
orientacoes = None
|
||||||
|
membros_banca = None
|
||||||
|
pontuacao = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
jd = json.loads(c.json_detalhes) if c.json_detalhes else {}
|
||||||
|
if isinstance(jd, dict):
|
||||||
|
consultoria = jd.get("consultoria")
|
||||||
|
coordenacoes_capes = jd.get("coordenacoes_capes")
|
||||||
|
inscricoes = jd.get("inscricoes")
|
||||||
|
avaliacoes_comissao = jd.get("avaliacoes_comissao")
|
||||||
|
premiacoes = jd.get("premiacoes")
|
||||||
|
bolsas_cnpq = jd.get("bolsas_cnpq")
|
||||||
|
participacoes = jd.get("participacoes")
|
||||||
|
orientacoes = jd.get("orientacoes")
|
||||||
|
membros_banca = jd.get("membros_banca")
|
||||||
|
pontuacao = jd.get("pontuacao")
|
||||||
|
except (json.JSONDecodeError, TypeError) as e:
|
||||||
|
logger.warning(f"Erro ao parsear json_detalhes do consultor {c.id_pessoa}: {e}")
|
||||||
|
|
||||||
|
pontuacao_total = float(c.pontuacao_total or 0)
|
||||||
|
bloco_a = float(c.componente_a or 0)
|
||||||
|
bloco_b = float(c.componente_b or 0)
|
||||||
|
bloco_c = float(c.componente_c or 0)
|
||||||
|
bloco_d = float(c.componente_d or 0)
|
||||||
|
|
||||||
|
pontuacao_ajustada = RankingMapper._ajustar_pontuacao(
|
||||||
|
pontuacao, bloco_a, bloco_b, bloco_c, bloco_d, pontuacao_total
|
||||||
|
)
|
||||||
|
|
||||||
|
return ConsultorRankingResumoSchema(
|
||||||
|
id_pessoa=c.id_pessoa,
|
||||||
|
nome=c.nome,
|
||||||
|
posicao=c.posicao,
|
||||||
|
pontuacao_total=pontuacao_total,
|
||||||
|
bloco_a=bloco_a,
|
||||||
|
bloco_b=bloco_b,
|
||||||
|
bloco_c=bloco_c,
|
||||||
|
bloco_d=bloco_d,
|
||||||
|
ativo=c.ativo,
|
||||||
|
anos_atuacao=c.anos_atuacao,
|
||||||
|
consultoria=consultoria,
|
||||||
|
coordenacoes_capes=coordenacoes_capes,
|
||||||
|
inscricoes=inscricoes,
|
||||||
|
avaliacoes_comissao=avaliacoes_comissao,
|
||||||
|
premiacoes=premiacoes,
|
||||||
|
bolsas_cnpq=bolsas_cnpq,
|
||||||
|
participacoes=participacoes,
|
||||||
|
orientacoes=orientacoes,
|
||||||
|
membros_banca=membros_banca,
|
||||||
|
pontuacao=pontuacao_ajustada if pontuacao_ajustada else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ajustar_pontuacao(
|
||||||
|
pontuacao: Optional[Dict],
|
||||||
|
bloco_a: float,
|
||||||
|
bloco_b: float,
|
||||||
|
bloco_c: float,
|
||||||
|
bloco_d: float,
|
||||||
|
pontuacao_total: float,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
if isinstance(pontuacao, dict):
|
||||||
|
pontuacao_ajustada = dict(pontuacao)
|
||||||
|
else:
|
||||||
|
pontuacao_ajustada = {}
|
||||||
|
|
||||||
|
def ajustar_bloco(chave: str, total: float, letra: str):
|
||||||
|
b = pontuacao_ajustada.get(chave)
|
||||||
|
if isinstance(b, dict):
|
||||||
|
b2 = dict(b)
|
||||||
|
b2["bloco"] = letra
|
||||||
|
b2["total"] = total
|
||||||
|
pontuacao_ajustada[chave] = b2
|
||||||
|
else:
|
||||||
|
pontuacao_ajustada[chave] = {"bloco": letra, "total": total, "atuacoes": []}
|
||||||
|
|
||||||
|
ajustar_bloco("bloco_a", bloco_a, "A")
|
||||||
|
ajustar_bloco("bloco_b", bloco_b, "B")
|
||||||
|
ajustar_bloco("bloco_c", bloco_c, "C")
|
||||||
|
ajustar_bloco("bloco_d", bloco_d, "D")
|
||||||
|
pontuacao_ajustada["pontuacao_total"] = pontuacao_total
|
||||||
|
|
||||||
|
return pontuacao_ajustada
|
||||||
@@ -1,31 +1,11 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import List, Optional, Dict, Any
|
from typing import List, Optional
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from ..value_objects.periodo import Periodo
|
from ..value_objects.periodo import Periodo
|
||||||
from ..value_objects.pontuacao import PontuacaoCompleta
|
from ..value_objects.pontuacao import PontuacaoCompleta
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Atuacao:
|
|
||||||
codigo: str
|
|
||||||
tipo_es: str
|
|
||||||
inicio: Optional[datetime] = None
|
|
||||||
fim: Optional[datetime] = None
|
|
||||||
dados: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ativo(self) -> bool:
|
|
||||||
return self.fim is None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def anos_completos(self) -> int:
|
|
||||||
if not self.inicio:
|
|
||||||
return 0
|
|
||||||
fim = self.fim or datetime.now()
|
|
||||||
return int((fim - self.inicio).days // 365)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CoordenacaoCapes:
|
class CoordenacaoCapes:
|
||||||
codigo: str
|
codigo: str
|
||||||
@@ -119,7 +99,6 @@ class Consultor:
|
|||||||
orientacoes: List[Orientacao] = field(default_factory=list)
|
orientacoes: List[Orientacao] = field(default_factory=list)
|
||||||
membros_banca: List[MembroBanca] = field(default_factory=list)
|
membros_banca: List[MembroBanca] = field(default_factory=list)
|
||||||
pontuacao: Optional[PontuacaoCompleta] = None
|
pontuacao: Optional[PontuacaoCompleta] = None
|
||||||
atuacoes_raw: List[Atuacao] = field(default_factory=list)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def anos_atuacao(self) -> float:
|
def anos_atuacao(self) -> float:
|
||||||
|
|||||||
@@ -16,36 +16,11 @@ from ..entities.consultor import (
|
|||||||
)
|
)
|
||||||
from ..value_objects.pontuacao import PontuacaoAtuacao, PontuacaoBloco, PontuacaoCompleta
|
from ..value_objects.pontuacao import PontuacaoAtuacao, PontuacaoBloco, PontuacaoCompleta
|
||||||
from ..value_objects.criterios_pontuacao import CRITERIOS, get_criterio, Bloco
|
from ..value_objects.criterios_pontuacao import CRITERIOS, get_criterio, Bloco
|
||||||
from ..value_objects.periodo import Periodo
|
from ..value_objects.periodo import Periodo, mesclar_periodos, anos_completos_periodos
|
||||||
|
|
||||||
|
|
||||||
class CalculadorPontuacao:
|
class CalculadorPontuacao:
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _mesclar_periodos(periodos: List[Periodo]) -> List[Periodo]:
|
|
||||||
if not periodos:
|
|
||||||
return []
|
|
||||||
periodos_ordenados = sorted(periodos, key=lambda p: p.inicio if p.inicio else datetime.min)
|
|
||||||
mesclados: List[Periodo] = []
|
|
||||||
for p in periodos_ordenados:
|
|
||||||
if not mesclados:
|
|
||||||
mesclados.append(p)
|
|
||||||
continue
|
|
||||||
ultimo = mesclados[-1]
|
|
||||||
ultimo_fim = ultimo.fim or datetime.now()
|
|
||||||
atual_fim = p.fim or datetime.now()
|
|
||||||
if p.inicio and p.inicio <= ultimo_fim:
|
|
||||||
novo_fim = max(ultimo_fim, atual_fim)
|
|
||||||
mesclados[-1] = Periodo(inicio=ultimo.inicio, fim=novo_fim if not ultimo.ativo else None)
|
|
||||||
else:
|
|
||||||
mesclados.append(p)
|
|
||||||
return mesclados
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _anos_completos_periodos(periodos: List[Periodo]) -> int:
|
|
||||||
ref = datetime.now()
|
|
||||||
return sum(p.anos_completos(ref) for p in periodos)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def calcular_bloco_a(coordenacoes: List[CoordenacaoCapes]) -> PontuacaoBloco:
|
def calcular_bloco_a(coordenacoes: List[CoordenacaoCapes]) -> PontuacaoBloco:
|
||||||
if not coordenacoes:
|
if not coordenacoes:
|
||||||
@@ -68,9 +43,9 @@ class CalculadorPontuacao:
|
|||||||
|
|
||||||
coords = coord_por_tipo[tipo]
|
coords = coord_por_tipo[tipo]
|
||||||
periodos = [c.periodo for c in coords]
|
periodos = [c.periodo for c in coords]
|
||||||
mesclados = CalculadorPontuacao._mesclar_periodos(periodos)
|
mesclados = mesclar_periodos(periodos)
|
||||||
|
|
||||||
anos_total = CalculadorPontuacao._anos_completos_periodos(mesclados)
|
anos_total = anos_completos_periodos(mesclados)
|
||||||
ativo = any(c.periodo.ativo for c in coords)
|
ativo = any(c.periodo.ativo for c in coords)
|
||||||
tem_retorno = len(mesclados) > 1
|
tem_retorno = len(mesclados) > 1
|
||||||
|
|
||||||
@@ -109,8 +84,8 @@ class CalculadorPontuacao:
|
|||||||
tempo = 0
|
tempo = 0
|
||||||
if criterio.pontua_tempo:
|
if criterio.pontua_tempo:
|
||||||
periodos = consultoria.periodos if consultoria.periodos else [consultoria.periodo]
|
periodos = consultoria.periodos if consultoria.periodos else [consultoria.periodo]
|
||||||
mesclados = CalculadorPontuacao._mesclar_periodos(periodos)
|
mesclados = mesclar_periodos(periodos)
|
||||||
anos_total = CalculadorPontuacao._anos_completos_periodos(mesclados)
|
anos_total = anos_completos_periodos(mesclados)
|
||||||
tempo = min(anos_total * criterio.multiplicador_tempo, criterio.teto_tempo)
|
tempo = min(anos_total * criterio.multiplicador_tempo, criterio.teto_tempo)
|
||||||
|
|
||||||
bonus = 0
|
bonus = 0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -29,6 +29,35 @@ class Periodo:
|
|||||||
return int((fim - self.inicio).days // 365)
|
return int((fim - self.inicio).days // 365)
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
# Se houver fim anterior ao início, o período é tratado como aberto.
|
|
||||||
if self.fim and self.fim < self.inicio:
|
if self.fim and self.fim < self.inicio:
|
||||||
object.__setattr__(self, "fim", None)
|
object.__setattr__(self, "fim", None)
|
||||||
|
|
||||||
|
|
||||||
|
def mesclar_periodos(periodos: List[Periodo]) -> List[Periodo]:
|
||||||
|
if not periodos:
|
||||||
|
return []
|
||||||
|
periodos_ordenados = sorted(
|
||||||
|
periodos, key=lambda p: p.inicio if p.inicio else datetime.min
|
||||||
|
)
|
||||||
|
mesclados: List[Periodo] = []
|
||||||
|
for p in periodos_ordenados:
|
||||||
|
if not mesclados:
|
||||||
|
mesclados.append(p)
|
||||||
|
continue
|
||||||
|
ultimo = mesclados[-1]
|
||||||
|
ultimo_fim = ultimo.fim or datetime.now()
|
||||||
|
atual_fim = p.fim or datetime.now()
|
||||||
|
if p.inicio and p.inicio <= ultimo_fim:
|
||||||
|
novo_fim = max(ultimo_fim, atual_fim)
|
||||||
|
mesclados[-1] = Periodo(
|
||||||
|
inicio=ultimo.inicio,
|
||||||
|
fim=novo_fim if not ultimo.ativo else None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
mesclados.append(p)
|
||||||
|
return mesclados
|
||||||
|
|
||||||
|
|
||||||
|
def anos_completos_periodos(periodos: List[Periodo], data_ref: Optional[datetime] = None) -> int:
|
||||||
|
ref = data_ref or datetime.now()
|
||||||
|
return sum(p.anos_completos(ref) for p in periodos)
|
||||||
|
|||||||
3
backend/src/infrastructure/cache/__init__.py
vendored
Normal file
3
backend/src/infrastructure/cache/__init__.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .ranking_cache import RankingCache, ranking_cache
|
||||||
|
|
||||||
|
__all__ = ["RankingCache", "ranking_cache"]
|
||||||
34
backend/src/infrastructure/cache/ranking_cache.py
vendored
Normal file
34
backend/src/infrastructure/cache/ranking_cache.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ...domain.entities.consultor import Consultor
|
||||||
|
|
||||||
|
|
||||||
|
class RankingCache:
|
||||||
|
def __init__(self, ttl_seconds: int = 300):
|
||||||
|
self.ttl = ttl_seconds
|
||||||
|
self._cache: List["Consultor"] = []
|
||||||
|
self._last_update: Optional[datetime] = None
|
||||||
|
self._loading = False
|
||||||
|
self._lock = asyncio.Lock()
|
||||||
|
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
if not self._cache or not self._last_update:
|
||||||
|
return False
|
||||||
|
return (datetime.now() - self._last_update).total_seconds() < self.ttl
|
||||||
|
|
||||||
|
def get(self) -> List["Consultor"]:
|
||||||
|
return self._cache
|
||||||
|
|
||||||
|
def set(self, consultores: List["Consultor"]) -> None:
|
||||||
|
self._cache = consultores
|
||||||
|
self._last_update = datetime.now()
|
||||||
|
|
||||||
|
def invalidate(self) -> None:
|
||||||
|
self._cache = []
|
||||||
|
self._last_update = None
|
||||||
|
|
||||||
|
|
||||||
|
ranking_cache = RankingCache(ttl_seconds=300)
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
import cx_Oracle
|
import cx_Oracle
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OracleClient:
|
class OracleClient:
|
||||||
def __init__(self, user: str, password: str, dsn: str):
|
def __init__(self, user: str, password: str, dsn: str):
|
||||||
@@ -24,14 +28,14 @@ class OracleClient:
|
|||||||
)
|
)
|
||||||
self._connected = True
|
self._connected = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"AVISO Oracle: {e}")
|
logger.warning(f"Oracle: {e}")
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
if self._pool:
|
if self._pool:
|
||||||
try:
|
try:
|
||||||
self._pool.close()
|
self._pool.close()
|
||||||
except:
|
except cx_Oracle.Error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -71,7 +75,7 @@ class OracleClient:
|
|||||||
cursor.close()
|
cursor.close()
|
||||||
return results
|
return results
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"AVISO Oracle: falha ao executar query: {e}")
|
logger.warning(f"Oracle: falha ao executar query: {e}")
|
||||||
self._connected = False
|
self._connected = False
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
from typing import List, Optional, Dict, Any
|
from typing import List, Optional, Dict, Any
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from dateutil import parser as date_parser
|
from dateutil import parser as date_parser
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from ...domain.entities.consultor import (
|
from ...domain.entities.consultor import (
|
||||||
Consultor,
|
Consultor,
|
||||||
@@ -17,33 +18,12 @@ from ...domain.entities.consultor import (
|
|||||||
)
|
)
|
||||||
from ...domain.repositories.consultor_repository import ConsultorRepository
|
from ...domain.repositories.consultor_repository import ConsultorRepository
|
||||||
from ...domain.services.calculador_pontuacao import CalculadorPontuacao
|
from ...domain.services.calculador_pontuacao import CalculadorPontuacao
|
||||||
from ...domain.value_objects.periodo import Periodo
|
from ...domain.value_objects.periodo import Periodo, mesclar_periodos
|
||||||
|
from ..cache import ranking_cache
|
||||||
from ..elasticsearch.client import ElasticsearchClient
|
from ..elasticsearch.client import ElasticsearchClient
|
||||||
from ..oracle.client import OracleClient
|
from ..oracle.client import OracleClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
class RankingCache:
|
|
||||||
def __init__(self, ttl_seconds: int = 300):
|
|
||||||
self.ttl = ttl_seconds
|
|
||||||
self._cache: List[Consultor] = []
|
|
||||||
self._last_update: Optional[datetime] = None
|
|
||||||
self._loading = False
|
|
||||||
self._lock = asyncio.Lock()
|
|
||||||
|
|
||||||
def is_valid(self) -> bool:
|
|
||||||
if not self._cache or not self._last_update:
|
|
||||||
return False
|
|
||||||
return (datetime.now() - self._last_update).total_seconds() < self.ttl
|
|
||||||
|
|
||||||
def get(self) -> List[Consultor]:
|
|
||||||
return self._cache
|
|
||||||
|
|
||||||
def set(self, consultores: List[Consultor]) -> None:
|
|
||||||
self._cache = consultores
|
|
||||||
self._last_update = datetime.now()
|
|
||||||
|
|
||||||
|
|
||||||
_ranking_cache = RankingCache(ttl_seconds=300)
|
|
||||||
|
|
||||||
|
|
||||||
class ConsultorRepositoryImpl(ConsultorRepository):
|
class ConsultorRepositoryImpl(ConsultorRepository):
|
||||||
@@ -58,28 +38,9 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
return date_parser.parse(date_str, dayfirst=True)
|
return date_parser.parse(date_str, dayfirst=True)
|
||||||
except:
|
except (ValueError, TypeError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _mesclar_periodos(self, periodos: List[Periodo]) -> List[Periodo]:
|
|
||||||
if not periodos:
|
|
||||||
return []
|
|
||||||
periodos = sorted(periodos, key=lambda p: p.inicio if p.inicio else datetime.min)
|
|
||||||
mesclados: List[Periodo] = []
|
|
||||||
for p in periodos:
|
|
||||||
if not mesclados:
|
|
||||||
mesclados.append(p)
|
|
||||||
continue
|
|
||||||
ultimo = mesclados[-1]
|
|
||||||
fim_ultimo = ultimo.fim or datetime.now()
|
|
||||||
fim_atual = p.fim or datetime.now()
|
|
||||||
if p.inicio and p.inicio <= fim_ultimo:
|
|
||||||
novo_fim = max(fim_ultimo, fim_atual)
|
|
||||||
mesclados[-1] = Periodo(inicio=ultimo.inicio, fim=novo_fim if not ultimo.ativo else None)
|
|
||||||
else:
|
|
||||||
mesclados.append(p)
|
|
||||||
return mesclados
|
|
||||||
|
|
||||||
def _inferir_tipo_coordenacao(self, coord: Dict[str, Any]) -> str:
|
def _inferir_tipo_coordenacao(self, coord: Dict[str, Any]) -> str:
|
||||||
dados_coord = coord.get("dadosCoordenacaoArea", {}) or {}
|
dados_coord = coord.get("dadosCoordenacaoArea", {}) or {}
|
||||||
tipo_coord = dados_coord.get("tipo", "").lower()
|
tipo_coord = dados_coord.get("tipo", "").lower()
|
||||||
@@ -182,7 +143,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
if not periodos:
|
if not periodos:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mesclados = self._mesclar_periodos(periodos)
|
mesclados = mesclar_periodos(periodos)
|
||||||
periodo_ativo = next((p for p in mesclados if p.ativo), None)
|
periodo_ativo = next((p for p in mesclados if p.ativo), None)
|
||||||
anos_consecutivos = periodo_ativo.anos_completos(datetime.now()) if periodo_ativo else 0
|
anos_consecutivos = periodo_ativo.anos_completos(datetime.now()) if periodo_ativo else 0
|
||||||
retornos = max(0, len(mesclados) - 1)
|
retornos = max(0, len(mesclados) - 1)
|
||||||
@@ -495,7 +456,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
try:
|
try:
|
||||||
doc = await self.es_client.buscar_por_id(id_pessoa)
|
doc = await self.es_client.buscar_por_id(id_pessoa)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"AVISO Elasticsearch: falha ao buscar consultor {id_pessoa}: {e}")
|
logger.warning(f"Elasticsearch: falha ao buscar consultor {id_pessoa}: {e}")
|
||||||
return None
|
return None
|
||||||
if not doc:
|
if not doc:
|
||||||
return None
|
return None
|
||||||
@@ -511,7 +472,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
docs = await self.es_client.buscar_com_atuacoes(size=limite, from_=offset)
|
docs = await self.es_client.buscar_com_atuacoes(size=limite, from_=offset)
|
||||||
self.es_disponivel = True
|
self.es_disponivel = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"AVISO Elasticsearch: falha ao buscar consultores: {e}")
|
logger.warning(f"Elasticsearch: falha ao buscar consultores: {e}")
|
||||||
self.es_disponivel = False
|
self.es_disponivel = False
|
||||||
return []
|
return []
|
||||||
consultores = [await self._construir_consultor(doc) for doc in docs]
|
consultores = [await self._construir_consultor(doc) for doc in docs]
|
||||||
@@ -524,15 +485,13 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
async def buscar_ranking(
|
async def buscar_ranking(
|
||||||
self, limite: int = 100, componente: Optional[str] = None
|
self, limite: int = 100, componente: Optional[str] = None
|
||||||
) -> List[Consultor]:
|
) -> List[Consultor]:
|
||||||
global _ranking_cache
|
if ranking_cache.is_valid():
|
||||||
|
consultores_ordenados = ranking_cache.get()
|
||||||
if _ranking_cache.is_valid():
|
|
||||||
consultores_ordenados = _ranking_cache.get()
|
|
||||||
return consultores_ordenados[:limite]
|
return consultores_ordenados[:limite]
|
||||||
|
|
||||||
async with _ranking_cache._lock:
|
async with ranking_cache._lock:
|
||||||
if _ranking_cache.is_valid():
|
if ranking_cache.is_valid():
|
||||||
return _ranking_cache.get()[:limite]
|
return ranking_cache.get()[:limite]
|
||||||
|
|
||||||
tamanho_busca = max(limite * 3, 1000)
|
tamanho_busca = max(limite * 3, 1000)
|
||||||
docs = await self.es_client.buscar_candidatos_ranking(size=tamanho_busca)
|
docs = await self.es_client.buscar_candidatos_ranking(size=tamanho_busca)
|
||||||
@@ -545,7 +504,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
consultores_ordenados = sorted(
|
consultores_ordenados = sorted(
|
||||||
consultores, key=lambda c: c.pontuacao_total, reverse=True
|
consultores, key=lambda c: c.pontuacao_total, reverse=True
|
||||||
)
|
)
|
||||||
_ranking_cache.set(consultores_ordenados)
|
ranking_cache.set(consultores_ordenados)
|
||||||
|
|
||||||
return consultores_ordenados[:limite]
|
return consultores_ordenados[:limite]
|
||||||
|
|
||||||
@@ -558,6 +517,6 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
try:
|
try:
|
||||||
return await self.es_client.contar_com_atuacoes()
|
return await self.es_client.contar_com_atuacoes()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"AVISO Elasticsearch: falha ao contar consultores: {e}")
|
logger.warning(f"Elasticsearch: falha ao contar consultores: {e}")
|
||||||
self.es_disponivel = False
|
self.es_disponivel = False
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from .routes import router
|
from .routes import router
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
from .config import settings
|
from .config import settings
|
||||||
from .dependencies import (
|
from .dependencies import (
|
||||||
es_client,
|
es_client,
|
||||||
@@ -18,19 +22,17 @@ from ...application.jobs.scheduler import RankingScheduler
|
|||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
await es_client.connect()
|
await es_client.connect()
|
||||||
|
|
||||||
# Conectar Oracle LOCAL (Docker)
|
|
||||||
try:
|
try:
|
||||||
oracle_local_client.connect()
|
oracle_local_client.connect()
|
||||||
print("Oracle LOCAL conectado (Docker)")
|
logger.info("Oracle LOCAL conectado (Docker)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"AVISO: Oracle LOCAL não conectou: {e}")
|
logger.warning(f"Oracle LOCAL não conectou: {e}")
|
||||||
|
|
||||||
# Conectar Oracle REMOTO (CAPES)
|
|
||||||
try:
|
try:
|
||||||
oracle_remote_client.connect()
|
oracle_remote_client.connect()
|
||||||
print("Oracle REMOTO conectado (CAPES)")
|
logger.info("Oracle REMOTO conectado (CAPES)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"AVISO: Oracle REMOTO não conectou: {e}. Sistema rodando sem Componente B (PPG).")
|
logger.warning(f"Oracle REMOTO não conectou: {e}. Sistema rodando sem Componente B (PPG).")
|
||||||
|
|
||||||
scheduler = None
|
scheduler = None
|
||||||
try:
|
try:
|
||||||
@@ -39,7 +41,7 @@ async def lifespan(app: FastAPI):
|
|||||||
scheduler = RankingScheduler(job, job_componente_b=job_b)
|
scheduler = RankingScheduler(job, job_componente_b=job_b)
|
||||||
await scheduler.iniciar()
|
await scheduler.iniciar()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"AVISO: Scheduler não iniciou: {e}")
|
logger.warning(f"Scheduler não iniciou: {e}")
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from typing import Optional, List
|
|||||||
|
|
||||||
from ...application.use_cases.obter_ranking import ObterRankingUseCase
|
from ...application.use_cases.obter_ranking import ObterRankingUseCase
|
||||||
from ...application.use_cases.obter_consultor import ObterConsultorUseCase
|
from ...application.use_cases.obter_consultor import ObterConsultorUseCase
|
||||||
|
from ...application.mappers import RankingMapper
|
||||||
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
||||||
from ..schemas.consultor_schema import (
|
from ..schemas.consultor_schema import (
|
||||||
RankingResponseSchema,
|
RankingResponseSchema,
|
||||||
@@ -21,7 +22,6 @@ from ..schemas.ranking_schema import (
|
|||||||
)
|
)
|
||||||
from .dependencies import get_repository, get_ranking_repository, get_processar_job
|
from .dependencies import get_repository, get_ranking_repository, get_processar_job
|
||||||
from ...application.jobs.job_status import job_status
|
from ...application.jobs.job_status import job_status
|
||||||
import json
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1", tags=["ranking"])
|
router = APIRouter(prefix="/api/v1", tags=["ranking"])
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ async def ranking_paginado(
|
|||||||
|
|
||||||
total_pages = (total + size - 1) // size
|
total_pages = (total + size - 1) // size
|
||||||
|
|
||||||
consultores_schema = [_consultor_resumo_from_ranking(c) for c in consultores]
|
consultores_schema = [RankingMapper.consultor_ranking_to_schema(c) for c in consultores]
|
||||||
|
|
||||||
return RankingPaginadoResponseSchema(
|
return RankingPaginadoResponseSchema(
|
||||||
total=total,
|
total=total,
|
||||||
@@ -129,87 +129,6 @@ async def buscar_por_nome(
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def _consultor_resumo_from_ranking(c):
|
|
||||||
consultoria = None
|
|
||||||
coordenacoes_capes = None
|
|
||||||
inscricoes = None
|
|
||||||
avaliacoes_comissao = None
|
|
||||||
premiacoes = None
|
|
||||||
bolsas_cnpq = None
|
|
||||||
participacoes = None
|
|
||||||
orientacoes = None
|
|
||||||
membros_banca = None
|
|
||||||
pontuacao = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
jd = json.loads(c.json_detalhes) if c.json_detalhes else {}
|
|
||||||
if isinstance(jd, dict):
|
|
||||||
consultoria = jd.get("consultoria")
|
|
||||||
coordenacoes_capes = jd.get("coordenacoes_capes")
|
|
||||||
inscricoes = jd.get("inscricoes")
|
|
||||||
avaliacoes_comissao = jd.get("avaliacoes_comissao")
|
|
||||||
premiacoes = jd.get("premiacoes")
|
|
||||||
bolsas_cnpq = jd.get("bolsas_cnpq")
|
|
||||||
participacoes = jd.get("participacoes")
|
|
||||||
orientacoes = jd.get("orientacoes")
|
|
||||||
membros_banca = jd.get("membros_banca")
|
|
||||||
pontuacao = jd.get("pontuacao")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Ajusta pontuação detalhada para refletir os valores atuais do ranking (incluindo COMPONENTE_B),
|
|
||||||
# já que o JSON pode ter sido gerado antes do job de preenchimento do Componente B.
|
|
||||||
pontuacao_total = float(c.pontuacao_total or 0)
|
|
||||||
bloco_a = float(c.componente_a or 0)
|
|
||||||
bloco_b = float(c.componente_b or 0)
|
|
||||||
bloco_c = float(c.componente_c or 0)
|
|
||||||
bloco_d = float(c.componente_d or 0)
|
|
||||||
|
|
||||||
if isinstance(pontuacao, dict):
|
|
||||||
pontuacao_ajustada = dict(pontuacao)
|
|
||||||
else:
|
|
||||||
pontuacao_ajustada = {}
|
|
||||||
|
|
||||||
def _ajustar_bloco(chave: str, total: float, letra: str):
|
|
||||||
b = pontuacao_ajustada.get(chave)
|
|
||||||
if isinstance(b, dict):
|
|
||||||
b2 = dict(b)
|
|
||||||
b2["bloco"] = letra
|
|
||||||
b2["total"] = total
|
|
||||||
pontuacao_ajustada[chave] = b2
|
|
||||||
else:
|
|
||||||
pontuacao_ajustada[chave] = {"bloco": letra, "total": total, "atuacoes": []}
|
|
||||||
|
|
||||||
_ajustar_bloco("bloco_a", bloco_a, "A")
|
|
||||||
_ajustar_bloco("bloco_b", bloco_b, "B")
|
|
||||||
_ajustar_bloco("bloco_c", bloco_c, "C")
|
|
||||||
_ajustar_bloco("bloco_d", bloco_d, "D")
|
|
||||||
pontuacao_ajustada["pontuacao_total"] = pontuacao_total
|
|
||||||
|
|
||||||
return ConsultorRankingResumoSchema(
|
|
||||||
id_pessoa=c.id_pessoa,
|
|
||||||
nome=c.nome,
|
|
||||||
posicao=c.posicao,
|
|
||||||
pontuacao_total=pontuacao_total,
|
|
||||||
bloco_a=bloco_a,
|
|
||||||
bloco_b=bloco_b,
|
|
||||||
bloco_c=bloco_c,
|
|
||||||
bloco_d=bloco_d,
|
|
||||||
ativo=c.ativo,
|
|
||||||
anos_atuacao=c.anos_atuacao,
|
|
||||||
consultoria=consultoria,
|
|
||||||
coordenacoes_capes=coordenacoes_capes,
|
|
||||||
inscricoes=inscricoes,
|
|
||||||
avaliacoes_comissao=avaliacoes_comissao,
|
|
||||||
premiacoes=premiacoes,
|
|
||||||
bolsas_cnpq=bolsas_cnpq,
|
|
||||||
participacoes=participacoes,
|
|
||||||
orientacoes=orientacoes,
|
|
||||||
membros_banca=membros_banca,
|
|
||||||
pontuacao=pontuacao_ajustada if pontuacao_ajustada else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/ranking/estatisticas", response_model=EstatisticasRankingSchema)
|
@router.get("/ranking/estatisticas", response_model=EstatisticasRankingSchema)
|
||||||
async def ranking_estatisticas(
|
async def ranking_estatisticas(
|
||||||
ranking_repo = Depends(get_ranking_repository),
|
ranking_repo = Depends(get_ranking_repository),
|
||||||
|
|||||||
Reference in New Issue
Block a user