fix(selos): corrigir geração de selos e adicionar ícones visuais
- Corrigir extração de orientações (tipo "Orientação de Discentes") - Selos de premiação agora usam campo papel (Autor/Orientador/Coorientador) - Adicionar ícones visuais aos selos (emojis Unicode) - Adicionar estilos CSS para novos tipos de selos - Melhorias no Oracle client e ranking repository
This commit is contained in:
@@ -4,6 +4,10 @@ ES_USER=seu_usuario_elastic
|
||||
ES_PASSWORD=sua_senha_elastic
|
||||
ES_VERIFY_SSL=true
|
||||
|
||||
ORACLE_LOCAL_USER=ranking
|
||||
ORACLE_LOCAL_PASSWORD=senha_oracle
|
||||
ORACLE_LOCAL_DSN=localhost:1521/XEPDB1
|
||||
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
API_RELOAD=true
|
||||
|
||||
@@ -6,3 +6,4 @@ python-dateutil==2.8.2
|
||||
httpx==0.26.0
|
||||
python-dotenv==1.0.0
|
||||
rich==13.7.0
|
||||
oracledb==2.5.1
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import logging
|
||||
from typing import Dict, Any, List
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from ...infrastructure.elasticsearch.client import ElasticsearchClient
|
||||
from ...infrastructure.ranking_store import RankingEntry, RankingStore
|
||||
from ...infrastructure.oracle.ranking_repository import RankingOracleRepository
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
||||
@@ -14,9 +15,11 @@ class ProcessarRankingJob:
|
||||
self,
|
||||
es_client: ElasticsearchClient,
|
||||
ranking_store: RankingStore,
|
||||
ranking_oracle_repo: Optional[RankingOracleRepository] = None,
|
||||
):
|
||||
self.es_client = es_client
|
||||
self.ranking_store = ranking_store
|
||||
self.ranking_oracle_repo = ranking_oracle_repo
|
||||
self.consultor_repo = ConsultorRepositoryImpl(es_client, oracle_client=None)
|
||||
self._consultores: List[dict] = []
|
||||
|
||||
@@ -40,6 +43,10 @@ class ProcessarRankingJob:
|
||||
entries = self._gerar_entries_ordenadas(self._consultores)
|
||||
await self.ranking_store.set_entries(entries)
|
||||
|
||||
if self.ranking_oracle_repo:
|
||||
job_status.mensagem = "Persistindo no Oracle..."
|
||||
await self._persistir_oracle(self._consultores, limpar_antes)
|
||||
|
||||
estatisticas = self._obter_estatisticas(entries)
|
||||
|
||||
job_status.finalizar(sucesso=True)
|
||||
@@ -253,3 +260,21 @@ class ProcessarRankingJob:
|
||||
"d": float(round(sum(e.bloco_d for e in entries) / total, 2)),
|
||||
},
|
||||
}
|
||||
|
||||
async def _persistir_oracle(self, consultores: List[dict], limpar_antes: bool) -> None:
|
||||
import asyncio
|
||||
if not self.ranking_oracle_repo:
|
||||
return
|
||||
|
||||
def _sync_persist():
|
||||
if limpar_antes:
|
||||
self.ranking_oracle_repo.limpar_tabela()
|
||||
|
||||
batch_size = 500
|
||||
for i in range(0, len(consultores), batch_size):
|
||||
batch = consultores[i:i + batch_size]
|
||||
self.ranking_oracle_repo.inserir_batch(batch)
|
||||
|
||||
self.ranking_oracle_repo.atualizar_posicoes()
|
||||
|
||||
await asyncio.get_event_loop().run_in_executor(None, _sync_persist)
|
||||
|
||||
@@ -102,6 +102,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = {
|
||||
multiplicador_tempo=5,
|
||||
teto_tempo=50,
|
||||
bonus_retorno=15,
|
||||
bonus_continuidade_8anos=20,
|
||||
),
|
||||
"CONS_HIST": CriterioPontuacao(
|
||||
codigo="CONS_HIST",
|
||||
@@ -112,6 +113,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = {
|
||||
pontua_tempo=True,
|
||||
multiplicador_tempo=5,
|
||||
teto_tempo=50,
|
||||
bonus_continuidade_8anos=20,
|
||||
),
|
||||
"CONS_FALECIDO": CriterioPontuacao(
|
||||
codigo="CONS_FALECIDO",
|
||||
@@ -122,6 +124,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = {
|
||||
pontua_tempo=True,
|
||||
multiplicador_tempo=5,
|
||||
teto_tempo=50,
|
||||
bonus_continuidade_8anos=20,
|
||||
),
|
||||
"INSC_AUTOR": CriterioPontuacao(
|
||||
codigo="INSC_AUTOR",
|
||||
@@ -151,7 +154,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = {
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PAPEL,
|
||||
base=50,
|
||||
teto=100,
|
||||
teto=80,
|
||||
bonus_recorrencia_anual=3,
|
||||
teto_recorrencia=20,
|
||||
),
|
||||
@@ -212,15 +215,15 @@ CRITERIOS: Dict[str, CriterioPontuacao] = {
|
||||
codigo="PREMIACAO_GP",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO,
|
||||
base=30,
|
||||
base=50,
|
||||
teto=60,
|
||||
),
|
||||
"MENCAO": CriterioPontuacao(
|
||||
codigo="MENCAO",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO,
|
||||
base=10,
|
||||
teto=20,
|
||||
base=30,
|
||||
teto=30,
|
||||
),
|
||||
"EVENTO": CriterioPontuacao(
|
||||
codigo="EVENTO",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
|
||||
import cx_Oracle
|
||||
import oracledb
|
||||
from typing import List, Dict, Any, Optional
|
||||
from contextlib import contextmanager
|
||||
|
||||
@@ -12,19 +12,18 @@ class OracleClient:
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.dsn = dsn
|
||||
self._pool: Optional[cx_Oracle.SessionPool] = None
|
||||
self._pool: Optional[oracledb.ConnectionPool] = None
|
||||
self._connected = False
|
||||
|
||||
def connect(self) -> None:
|
||||
try:
|
||||
self._pool = cx_Oracle.SessionPool(
|
||||
self._pool = oracledb.create_pool(
|
||||
user=self.user,
|
||||
password=self.password,
|
||||
dsn=self.dsn,
|
||||
min=2,
|
||||
max=10,
|
||||
increment=1,
|
||||
encoding="UTF-8",
|
||||
)
|
||||
self._connected = True
|
||||
except Exception as e:
|
||||
@@ -35,7 +34,7 @@ class OracleClient:
|
||||
if self._pool:
|
||||
try:
|
||||
self._pool.close()
|
||||
except cx_Oracle.Error:
|
||||
except oracledb.Error:
|
||||
pass
|
||||
|
||||
@property
|
||||
|
||||
@@ -15,7 +15,7 @@ class RankingOracleRepository:
|
||||
Insere ou atualiza um batch de consultores usando MERGE.
|
||||
Retorna o número de registros processados.
|
||||
"""
|
||||
import cx_Oracle
|
||||
import oracledb
|
||||
|
||||
if not consultores:
|
||||
return 0
|
||||
@@ -66,18 +66,18 @@ class RankingOracleRepository:
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
for consultor in consultores:
|
||||
json_str = json.dumps(consultor.get("detalhes", {}), ensure_ascii=False)
|
||||
cursor.setinputsizes(json_detalhes=cx_Oracle.CLOB)
|
||||
json_str = json.dumps(consultor, ensure_ascii=False)
|
||||
cursor.setinputsizes(json_detalhes=oracledb.DB_TYPE_CLOB)
|
||||
params = {
|
||||
"id_pessoa": consultor["id_pessoa"],
|
||||
"nome": consultor["nome"],
|
||||
"pontuacao_total": consultor["pontuacao_total"],
|
||||
"componente_a": consultor["componente_a"],
|
||||
"componente_b": consultor["componente_b"],
|
||||
"componente_c": consultor["componente_c"],
|
||||
"componente_d": consultor["componente_d"],
|
||||
"ativo": "S" if consultor["ativo"] else "N",
|
||||
"anos_atuacao": consultor["anos_atuacao"],
|
||||
"componente_a": consultor.get("bloco_a") or consultor.get("componente_a", 0),
|
||||
"componente_b": consultor.get("bloco_b") or consultor.get("componente_b", 0),
|
||||
"componente_c": consultor.get("bloco_c") or consultor.get("componente_c", 0),
|
||||
"componente_d": consultor.get("bloco_d") or consultor.get("componente_d", 0),
|
||||
"ativo": "S" if consultor.get("ativo") else "N",
|
||||
"anos_atuacao": consultor.get("anos_atuacao", 0),
|
||||
"json_detalhes": json_str
|
||||
}
|
||||
cursor.execute(merge_sql, params)
|
||||
|
||||
@@ -355,10 +355,52 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
||||
orientacoes = []
|
||||
|
||||
for a in atuacoes:
|
||||
tipo = a.get("tipo", "").lower()
|
||||
if "orientação" not in tipo and "orientacao" not in tipo:
|
||||
tipo = a.get("tipo", "")
|
||||
tipo_lower = tipo.lower()
|
||||
|
||||
if tipo == "Orientação de Discentes":
|
||||
dados = a.get("dadosOrientacaoDiscente", {}) or {}
|
||||
total_mestrado = dados.get("totalOrientacaoFinalizadaMestrado") or 0
|
||||
total_doutorado = dados.get("totalOrientacaoFinalizadaDoutorado") or 0
|
||||
total_pos_doc = dados.get("totalAcompanhamentoPosDoutorado") or 0
|
||||
|
||||
for _ in range(int(total_pos_doc)):
|
||||
orientacoes.append(Orientacao(
|
||||
codigo="ORIENT_POS_DOC",
|
||||
tipo="Orientação Pós-Doutorado",
|
||||
nivel="Pós-Doutorado",
|
||||
ano=None,
|
||||
coorientacao=False,
|
||||
premiada=False,
|
||||
premiacao_tipo=None,
|
||||
))
|
||||
|
||||
for _ in range(int(total_doutorado)):
|
||||
orientacoes.append(Orientacao(
|
||||
codigo="ORIENT_TESE",
|
||||
tipo="Orientação Doutorado",
|
||||
nivel="Doutorado",
|
||||
ano=None,
|
||||
coorientacao=False,
|
||||
premiada=False,
|
||||
premiacao_tipo=None,
|
||||
))
|
||||
|
||||
for _ in range(int(total_mestrado)):
|
||||
orientacoes.append(Orientacao(
|
||||
codigo="ORIENT_DISS",
|
||||
tipo="Orientação Mestrado",
|
||||
nivel="Mestrado",
|
||||
ano=None,
|
||||
coorientacao=False,
|
||||
premiada=False,
|
||||
premiacao_tipo=None,
|
||||
))
|
||||
continue
|
||||
if "co-orientação" in tipo or "coorientação" in tipo or "co_orient" in tipo:
|
||||
|
||||
if "orientação" not in tipo_lower and "orientacao" not in tipo_lower:
|
||||
continue
|
||||
if "co-orientação" in tipo_lower or "coorientação" in tipo_lower or "co_orient" in tipo_lower:
|
||||
continue
|
||||
|
||||
dados = a.get("dadosOrientacao", {}) or {}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import logging
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@@ -8,14 +10,102 @@ from .routes import router
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from .config import settings
|
||||
from .dependencies import es_client, get_processar_job
|
||||
from .dependencies import es_client, oracle_client, ranking_oracle_repo, get_processar_job
|
||||
from ...application.jobs.scheduler import RankingScheduler
|
||||
from ...infrastructure.ranking_store import ranking_store, RankingEntry
|
||||
|
||||
|
||||
async def carregar_ranking_do_oracle() -> int:
|
||||
if not ranking_oracle_repo or not oracle_client:
|
||||
logger.warning("Oracle não configurado - ranking será carregado do Elasticsearch quando solicitado")
|
||||
return 0
|
||||
|
||||
try:
|
||||
if not oracle_client.is_connected:
|
||||
oracle_client.connect()
|
||||
if not oracle_client.is_connected:
|
||||
logger.warning("Não foi possível conectar ao Oracle")
|
||||
return 0
|
||||
|
||||
def _sync_load():
|
||||
total = ranking_oracle_repo.contar_total()
|
||||
if total == 0:
|
||||
return []
|
||||
|
||||
consultores = ranking_oracle_repo.buscar_paginado(page=1, size=total)
|
||||
return consultores
|
||||
|
||||
consultores = await asyncio.wait_for(
|
||||
asyncio.get_event_loop().run_in_executor(None, _sync_load),
|
||||
timeout=30.0
|
||||
)
|
||||
|
||||
if not consultores:
|
||||
logger.info("Nenhum dado encontrado no Oracle - ranking vazio")
|
||||
return 0
|
||||
|
||||
entries = []
|
||||
for c in consultores:
|
||||
try:
|
||||
detalhes = json.loads(c.json_detalhes) if isinstance(c.json_detalhes, str) else c.json_detalhes or {}
|
||||
except:
|
||||
detalhes = {}
|
||||
|
||||
entries.append(
|
||||
RankingEntry(
|
||||
id_pessoa=c.id_pessoa,
|
||||
nome=c.nome,
|
||||
posicao=c.posicao or 0,
|
||||
pontuacao_total=int(c.pontuacao_total),
|
||||
bloco_a=int(c.componente_a),
|
||||
bloco_b=int(c.componente_b),
|
||||
bloco_c=int(c.componente_c),
|
||||
bloco_d=int(c.componente_d),
|
||||
ativo=c.ativo,
|
||||
anos_atuacao=float(c.anos_atuacao or 0),
|
||||
detalhes=detalhes,
|
||||
)
|
||||
)
|
||||
|
||||
await ranking_store.set_entries(entries)
|
||||
return len(entries)
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("Timeout ao carregar ranking do Oracle")
|
||||
return 0
|
||||
except Exception as e:
|
||||
logger.warning(f"Erro ao carregar ranking do Oracle: {e}")
|
||||
return 0
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
await es_client.connect()
|
||||
|
||||
try:
|
||||
if oracle_client:
|
||||
def _connect_oracle():
|
||||
oracle_client.connect()
|
||||
return oracle_client.is_connected
|
||||
|
||||
connected = await asyncio.wait_for(
|
||||
asyncio.get_event_loop().run_in_executor(None, _connect_oracle),
|
||||
timeout=10.0
|
||||
)
|
||||
|
||||
if connected:
|
||||
logger.info("Conectado ao Oracle")
|
||||
total = await carregar_ranking_do_oracle()
|
||||
if total > 0:
|
||||
logger.info(f"Ranking carregado do Oracle: {total} consultores")
|
||||
else:
|
||||
logger.info("Ranking vazio no Oracle - aguardando processamento")
|
||||
else:
|
||||
logger.warning("Não foi possível conectar ao Oracle - ranking será carregado do ES")
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("Timeout ao conectar ao Oracle - ranking será carregado do ES")
|
||||
except Exception as e:
|
||||
logger.warning(f"Erro ao inicializar Oracle: {e}")
|
||||
|
||||
scheduler = None
|
||||
try:
|
||||
if settings.SCHEDULER_ENABLED:
|
||||
@@ -33,6 +123,12 @@ async def lifespan(app: FastAPI):
|
||||
except:
|
||||
pass
|
||||
|
||||
if oracle_client:
|
||||
try:
|
||||
oracle_client.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
await es_client.close()
|
||||
|
||||
|
||||
|
||||
@@ -5,13 +5,16 @@ from typing import List
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
||||
|
||||
# Preferir o alias apontado para o índice vigente do Atuacapes
|
||||
ES_URL: str = "http://localhost:9200"
|
||||
ES_INDEX: str = "atuacapes"
|
||||
ES_USER: str = ""
|
||||
ES_PASSWORD: str = ""
|
||||
ES_VERIFY_SSL: bool = True
|
||||
|
||||
ORACLE_LOCAL_USER: str = ""
|
||||
ORACLE_LOCAL_PASSWORD: str = ""
|
||||
ORACLE_LOCAL_DSN: str = ""
|
||||
|
||||
API_HOST: str = "0.0.0.0"
|
||||
API_PORT: int = 8000
|
||||
API_RELOAD: bool = True
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from ...infrastructure.elasticsearch.client import ElasticsearchClient
|
||||
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
||||
from ...infrastructure.oracle.client import OracleClient
|
||||
from ...infrastructure.oracle.ranking_repository import RankingOracleRepository
|
||||
from ...application.jobs.processar_ranking import ProcessarRankingJob
|
||||
from ...infrastructure.ranking_store import ranking_store, RankingStore
|
||||
from .config import settings
|
||||
@@ -13,6 +15,14 @@ es_client = ElasticsearchClient(
|
||||
verify_ssl=settings.ES_VERIFY_SSL,
|
||||
)
|
||||
|
||||
oracle_client = OracleClient(
|
||||
user=settings.ORACLE_LOCAL_USER,
|
||||
password=settings.ORACLE_LOCAL_PASSWORD,
|
||||
dsn=settings.ORACLE_LOCAL_DSN,
|
||||
) if settings.ORACLE_LOCAL_USER and settings.ORACLE_LOCAL_DSN else None
|
||||
|
||||
ranking_oracle_repo = RankingOracleRepository(oracle_client) if oracle_client else None
|
||||
|
||||
_repository: ConsultorRepositoryImpl = None
|
||||
_processar_job: ProcessarRankingJob = None
|
||||
|
||||
@@ -20,7 +30,7 @@ _processar_job: ProcessarRankingJob = None
|
||||
def get_repository() -> ConsultorRepositoryImpl:
|
||||
global _repository
|
||||
if _repository is None:
|
||||
_repository = ConsultorRepositoryImpl(es_client=es_client, oracle_client=None)
|
||||
_repository = ConsultorRepositoryImpl(es_client=es_client, oracle_client=oracle_client)
|
||||
return _repository
|
||||
|
||||
|
||||
@@ -28,11 +38,20 @@ def get_ranking_store() -> RankingStore:
|
||||
return ranking_store
|
||||
|
||||
|
||||
def get_oracle_client() -> OracleClient:
|
||||
return oracle_client
|
||||
|
||||
|
||||
def get_ranking_oracle_repo() -> RankingOracleRepository:
|
||||
return ranking_oracle_repo
|
||||
|
||||
|
||||
def get_processar_job() -> ProcessarRankingJob:
|
||||
global _processar_job
|
||||
if _processar_job is None:
|
||||
_processar_job = ProcessarRankingJob(
|
||||
es_client=es_client,
|
||||
ranking_store=ranking_store,
|
||||
ranking_oracle_repo=ranking_oracle_repo,
|
||||
)
|
||||
return _processar_job
|
||||
|
||||
Reference in New Issue
Block a user