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_PASSWORD=sua_senha_elastic
|
||||||
ES_VERIFY_SSL=true
|
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_HOST=0.0.0.0
|
||||||
API_PORT=8000
|
API_PORT=8000
|
||||||
API_RELOAD=true
|
API_RELOAD=true
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ python-dateutil==2.8.2
|
|||||||
httpx==0.26.0
|
httpx==0.26.0
|
||||||
python-dotenv==1.0.0
|
python-dotenv==1.0.0
|
||||||
rich==13.7.0
|
rich==13.7.0
|
||||||
|
oracledb==2.5.1
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
from ...infrastructure.elasticsearch.client import ElasticsearchClient
|
from ...infrastructure.elasticsearch.client import ElasticsearchClient
|
||||||
from ...infrastructure.ranking_store import RankingEntry, RankingStore
|
from ...infrastructure.ranking_store import RankingEntry, RankingStore
|
||||||
|
from ...infrastructure.oracle.ranking_repository import RankingOracleRepository
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
||||||
@@ -14,9 +15,11 @@ class ProcessarRankingJob:
|
|||||||
self,
|
self,
|
||||||
es_client: ElasticsearchClient,
|
es_client: ElasticsearchClient,
|
||||||
ranking_store: RankingStore,
|
ranking_store: RankingStore,
|
||||||
|
ranking_oracle_repo: Optional[RankingOracleRepository] = None,
|
||||||
):
|
):
|
||||||
self.es_client = es_client
|
self.es_client = es_client
|
||||||
self.ranking_store = ranking_store
|
self.ranking_store = ranking_store
|
||||||
|
self.ranking_oracle_repo = ranking_oracle_repo
|
||||||
self.consultor_repo = ConsultorRepositoryImpl(es_client, oracle_client=None)
|
self.consultor_repo = ConsultorRepositoryImpl(es_client, oracle_client=None)
|
||||||
self._consultores: List[dict] = []
|
self._consultores: List[dict] = []
|
||||||
|
|
||||||
@@ -40,6 +43,10 @@ class ProcessarRankingJob:
|
|||||||
entries = self._gerar_entries_ordenadas(self._consultores)
|
entries = self._gerar_entries_ordenadas(self._consultores)
|
||||||
await self.ranking_store.set_entries(entries)
|
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)
|
estatisticas = self._obter_estatisticas(entries)
|
||||||
|
|
||||||
job_status.finalizar(sucesso=True)
|
job_status.finalizar(sucesso=True)
|
||||||
@@ -253,3 +260,21 @@ class ProcessarRankingJob:
|
|||||||
"d": float(round(sum(e.bloco_d for e in entries) / total, 2)),
|
"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,
|
multiplicador_tempo=5,
|
||||||
teto_tempo=50,
|
teto_tempo=50,
|
||||||
bonus_retorno=15,
|
bonus_retorno=15,
|
||||||
|
bonus_continuidade_8anos=20,
|
||||||
),
|
),
|
||||||
"CONS_HIST": CriterioPontuacao(
|
"CONS_HIST": CriterioPontuacao(
|
||||||
codigo="CONS_HIST",
|
codigo="CONS_HIST",
|
||||||
@@ -112,6 +113,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = {
|
|||||||
pontua_tempo=True,
|
pontua_tempo=True,
|
||||||
multiplicador_tempo=5,
|
multiplicador_tempo=5,
|
||||||
teto_tempo=50,
|
teto_tempo=50,
|
||||||
|
bonus_continuidade_8anos=20,
|
||||||
),
|
),
|
||||||
"CONS_FALECIDO": CriterioPontuacao(
|
"CONS_FALECIDO": CriterioPontuacao(
|
||||||
codigo="CONS_FALECIDO",
|
codigo="CONS_FALECIDO",
|
||||||
@@ -122,6 +124,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = {
|
|||||||
pontua_tempo=True,
|
pontua_tempo=True,
|
||||||
multiplicador_tempo=5,
|
multiplicador_tempo=5,
|
||||||
teto_tempo=50,
|
teto_tempo=50,
|
||||||
|
bonus_continuidade_8anos=20,
|
||||||
),
|
),
|
||||||
"INSC_AUTOR": CriterioPontuacao(
|
"INSC_AUTOR": CriterioPontuacao(
|
||||||
codigo="INSC_AUTOR",
|
codigo="INSC_AUTOR",
|
||||||
@@ -151,7 +154,7 @@ CRITERIOS: Dict[str, CriterioPontuacao] = {
|
|||||||
bloco=Bloco.D,
|
bloco=Bloco.D,
|
||||||
tipo=TipoAtuacao.PAPEL,
|
tipo=TipoAtuacao.PAPEL,
|
||||||
base=50,
|
base=50,
|
||||||
teto=100,
|
teto=80,
|
||||||
bonus_recorrencia_anual=3,
|
bonus_recorrencia_anual=3,
|
||||||
teto_recorrencia=20,
|
teto_recorrencia=20,
|
||||||
),
|
),
|
||||||
@@ -212,15 +215,15 @@ CRITERIOS: Dict[str, CriterioPontuacao] = {
|
|||||||
codigo="PREMIACAO_GP",
|
codigo="PREMIACAO_GP",
|
||||||
bloco=Bloco.D,
|
bloco=Bloco.D,
|
||||||
tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO,
|
tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO,
|
||||||
base=30,
|
base=50,
|
||||||
teto=60,
|
teto=60,
|
||||||
),
|
),
|
||||||
"MENCAO": CriterioPontuacao(
|
"MENCAO": CriterioPontuacao(
|
||||||
codigo="MENCAO",
|
codigo="MENCAO",
|
||||||
bloco=Bloco.D,
|
bloco=Bloco.D,
|
||||||
tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO,
|
tipo=TipoAtuacao.COMPETENCIA_RECONHECIMENTO,
|
||||||
base=10,
|
base=30,
|
||||||
teto=20,
|
teto=30,
|
||||||
),
|
),
|
||||||
"EVENTO": CriterioPontuacao(
|
"EVENTO": CriterioPontuacao(
|
||||||
codigo="EVENTO",
|
codigo="EVENTO",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import cx_Oracle
|
import oracledb
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
@@ -12,19 +12,18 @@ class OracleClient:
|
|||||||
self.user = user
|
self.user = user
|
||||||
self.password = password
|
self.password = password
|
||||||
self.dsn = dsn
|
self.dsn = dsn
|
||||||
self._pool: Optional[cx_Oracle.SessionPool] = None
|
self._pool: Optional[oracledb.ConnectionPool] = None
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
|
||||||
def connect(self) -> None:
|
def connect(self) -> None:
|
||||||
try:
|
try:
|
||||||
self._pool = cx_Oracle.SessionPool(
|
self._pool = oracledb.create_pool(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
password=self.password,
|
password=self.password,
|
||||||
dsn=self.dsn,
|
dsn=self.dsn,
|
||||||
min=2,
|
min=2,
|
||||||
max=10,
|
max=10,
|
||||||
increment=1,
|
increment=1,
|
||||||
encoding="UTF-8",
|
|
||||||
)
|
)
|
||||||
self._connected = True
|
self._connected = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -35,7 +34,7 @@ class OracleClient:
|
|||||||
if self._pool:
|
if self._pool:
|
||||||
try:
|
try:
|
||||||
self._pool.close()
|
self._pool.close()
|
||||||
except cx_Oracle.Error:
|
except oracledb.Error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class RankingOracleRepository:
|
|||||||
Insere ou atualiza um batch de consultores usando MERGE.
|
Insere ou atualiza um batch de consultores usando MERGE.
|
||||||
Retorna o número de registros processados.
|
Retorna o número de registros processados.
|
||||||
"""
|
"""
|
||||||
import cx_Oracle
|
import oracledb
|
||||||
|
|
||||||
if not consultores:
|
if not consultores:
|
||||||
return 0
|
return 0
|
||||||
@@ -66,18 +66,18 @@ class RankingOracleRepository:
|
|||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
try:
|
try:
|
||||||
for consultor in consultores:
|
for consultor in consultores:
|
||||||
json_str = json.dumps(consultor.get("detalhes", {}), ensure_ascii=False)
|
json_str = json.dumps(consultor, ensure_ascii=False)
|
||||||
cursor.setinputsizes(json_detalhes=cx_Oracle.CLOB)
|
cursor.setinputsizes(json_detalhes=oracledb.DB_TYPE_CLOB)
|
||||||
params = {
|
params = {
|
||||||
"id_pessoa": consultor["id_pessoa"],
|
"id_pessoa": consultor["id_pessoa"],
|
||||||
"nome": consultor["nome"],
|
"nome": consultor["nome"],
|
||||||
"pontuacao_total": consultor["pontuacao_total"],
|
"pontuacao_total": consultor["pontuacao_total"],
|
||||||
"componente_a": consultor["componente_a"],
|
"componente_a": consultor.get("bloco_a") or consultor.get("componente_a", 0),
|
||||||
"componente_b": consultor["componente_b"],
|
"componente_b": consultor.get("bloco_b") or consultor.get("componente_b", 0),
|
||||||
"componente_c": consultor["componente_c"],
|
"componente_c": consultor.get("bloco_c") or consultor.get("componente_c", 0),
|
||||||
"componente_d": consultor["componente_d"],
|
"componente_d": consultor.get("bloco_d") or consultor.get("componente_d", 0),
|
||||||
"ativo": "S" if consultor["ativo"] else "N",
|
"ativo": "S" if consultor.get("ativo") else "N",
|
||||||
"anos_atuacao": consultor["anos_atuacao"],
|
"anos_atuacao": consultor.get("anos_atuacao", 0),
|
||||||
"json_detalhes": json_str
|
"json_detalhes": json_str
|
||||||
}
|
}
|
||||||
cursor.execute(merge_sql, params)
|
cursor.execute(merge_sql, params)
|
||||||
|
|||||||
@@ -355,10 +355,52 @@ class ConsultorRepositoryImpl(ConsultorRepository):
|
|||||||
orientacoes = []
|
orientacoes = []
|
||||||
|
|
||||||
for a in atuacoes:
|
for a in atuacoes:
|
||||||
tipo = a.get("tipo", "").lower()
|
tipo = a.get("tipo", "")
|
||||||
if "orientação" not in tipo and "orientacao" not in 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
|
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
|
continue
|
||||||
|
|
||||||
dados = a.get("dadosOrientacao", {}) or {}
|
dados = a.get("dadosOrientacao", {}) or {}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
@@ -8,14 +10,102 @@ from .routes import router
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
from .config import settings
|
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 ...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
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
await es_client.connect()
|
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
|
scheduler = None
|
||||||
try:
|
try:
|
||||||
if settings.SCHEDULER_ENABLED:
|
if settings.SCHEDULER_ENABLED:
|
||||||
@@ -33,6 +123,12 @@ async def lifespan(app: FastAPI):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if oracle_client:
|
||||||
|
try:
|
||||||
|
oracle_client.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
await es_client.close()
|
await es_client.close()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,16 @@ from typing import List
|
|||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
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_URL: str = "http://localhost:9200"
|
||||||
ES_INDEX: str = "atuacapes"
|
ES_INDEX: str = "atuacapes"
|
||||||
ES_USER: str = ""
|
ES_USER: str = ""
|
||||||
ES_PASSWORD: str = ""
|
ES_PASSWORD: str = ""
|
||||||
ES_VERIFY_SSL: bool = True
|
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_HOST: str = "0.0.0.0"
|
||||||
API_PORT: int = 8000
|
API_PORT: int = 8000
|
||||||
API_RELOAD: bool = True
|
API_RELOAD: bool = True
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from ...infrastructure.elasticsearch.client import ElasticsearchClient
|
from ...infrastructure.elasticsearch.client import ElasticsearchClient
|
||||||
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
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 ...application.jobs.processar_ranking import ProcessarRankingJob
|
||||||
from ...infrastructure.ranking_store import ranking_store, RankingStore
|
from ...infrastructure.ranking_store import ranking_store, RankingStore
|
||||||
from .config import settings
|
from .config import settings
|
||||||
@@ -13,6 +15,14 @@ es_client = ElasticsearchClient(
|
|||||||
verify_ssl=settings.ES_VERIFY_SSL,
|
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
|
_repository: ConsultorRepositoryImpl = None
|
||||||
_processar_job: ProcessarRankingJob = None
|
_processar_job: ProcessarRankingJob = None
|
||||||
|
|
||||||
@@ -20,7 +30,7 @@ _processar_job: ProcessarRankingJob = None
|
|||||||
def get_repository() -> ConsultorRepositoryImpl:
|
def get_repository() -> ConsultorRepositoryImpl:
|
||||||
global _repository
|
global _repository
|
||||||
if _repository is None:
|
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
|
return _repository
|
||||||
|
|
||||||
|
|
||||||
@@ -28,11 +38,20 @@ def get_ranking_store() -> RankingStore:
|
|||||||
return ranking_store
|
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:
|
def get_processar_job() -> ProcessarRankingJob:
|
||||||
global _processar_job
|
global _processar_job
|
||||||
if _processar_job is None:
|
if _processar_job is None:
|
||||||
_processar_job = ProcessarRankingJob(
|
_processar_job = ProcessarRankingJob(
|
||||||
es_client=es_client,
|
es_client=es_client,
|
||||||
ranking_store=ranking_store,
|
ranking_store=ranking_store,
|
||||||
|
ranking_oracle_repo=ranking_oracle_repo,
|
||||||
)
|
)
|
||||||
return _processar_job
|
return _processar_job
|
||||||
|
|||||||
@@ -43,7 +43,33 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- shared_network
|
- shared_network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
oracle18c:
|
||||||
|
container_name: mqapilc_oracle18c
|
||||||
|
image: gvenzl/oracle-xe:18-slim
|
||||||
|
environment:
|
||||||
|
- ORACLE_PASSWORD=local123
|
||||||
|
- ORACLE_CHARACTERSET=AL32UTF8
|
||||||
|
- APP_USER=local123
|
||||||
|
- APP_USER_PASSWORD=local123
|
||||||
|
- TZ=America/Sao_Paulo
|
||||||
|
ports:
|
||||||
|
- "1521:1521"
|
||||||
|
- "5500:5500"
|
||||||
|
volumes:
|
||||||
|
- oracle_data:/opt/oracle/oradata
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "bash", "-c", "echo 'SELECT 1 FROM DUAL;' | sqlplus -s SYSTEM/\"$${ORACLE_PASSWORD}\"@localhost:1521/XEPDB1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 20
|
||||||
|
networks:
|
||||||
|
- shared_network
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
shared_network:
|
shared_network:
|
||||||
external: true
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
oracle_data:
|
||||||
|
driver: local
|
||||||
|
|||||||
@@ -594,10 +594,17 @@
|
|||||||
color: #7dd3fc;
|
color: #7dd3fc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selo-orient-prem {
|
.selo-orient-prem,
|
||||||
background: linear-gradient(135deg, rgba(14, 165, 233, 0.35), rgba(251, 191, 36, 0.2));
|
.selo-orient-premio {
|
||||||
border-color: rgba(251, 191, 36, 0.5);
|
background: linear-gradient(135deg, rgba(14, 165, 233, 0.35), rgba(249, 115, 22, 0.2));
|
||||||
color: #fcd34d;
|
border-color: rgba(249, 115, 22, 0.5);
|
||||||
|
color: #fdba74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selo-orient-mencao {
|
||||||
|
background: linear-gradient(135deg, rgba(14, 165, 233, 0.25), rgba(203, 213, 225, 0.15));
|
||||||
|
border-color: rgba(203, 213, 225, 0.4);
|
||||||
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selo-coorient {
|
.selo-coorient {
|
||||||
@@ -606,10 +613,17 @@
|
|||||||
color: #a5b4fc;
|
color: #a5b4fc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selo-coorient-prem {
|
.selo-coorient-prem,
|
||||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.35), rgba(251, 191, 36, 0.2));
|
.selo-coorient-premio {
|
||||||
border-color: rgba(251, 191, 36, 0.5);
|
background: linear-gradient(135deg, rgba(99, 102, 241, 0.35), rgba(249, 115, 22, 0.2));
|
||||||
color: #fcd34d;
|
border-color: rgba(249, 115, 22, 0.5);
|
||||||
|
color: #fdba74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selo-coorient-mencao {
|
||||||
|
background: linear-gradient(135deg, rgba(99, 102, 241, 0.25), rgba(203, 213, 225, 0.15));
|
||||||
|
border-color: rgba(203, 213, 225, 0.4);
|
||||||
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selos-section {
|
.selos-section {
|
||||||
|
|||||||
@@ -2,24 +2,24 @@ import React, { useState, useRef, useEffect } from 'react';
|
|||||||
import './ConsultorCard.css';
|
import './ConsultorCard.css';
|
||||||
|
|
||||||
const SELOS = {
|
const SELOS = {
|
||||||
PRESID_CAMARA: { label: 'Presidente Câmara Temática', cor: 'selo-camara', icone: '🏛️' },
|
PRESID_CAMARA: { label: 'Presidente Camara', cor: 'selo-camara', icone: '👑' },
|
||||||
COORD_PPG: { label: 'Coordenador de PPG', cor: 'selo-coord', icone: '🎓' },
|
COORD_PPG: { label: 'Coord. PPG', cor: 'selo-coord', icone: '🎓' },
|
||||||
BPQ: { label: 'BPQ', cor: 'selo-bpq', icone: '🔬' },
|
BPQ: { label: 'BPQ', cor: 'selo-bpq', icone: '🏅' },
|
||||||
AUTOR_GP: { label: 'Autor - Grande Prêmio', cor: 'selo-gp', icone: '🏆' },
|
AUTOR_GP: { label: 'Autor GP', cor: 'selo-gp', icone: '🏆' },
|
||||||
AUTOR_PREMIO: { label: 'Autor - Prêmio', cor: 'selo-premio', icone: '🥇' },
|
AUTOR_PREMIO: { label: 'Autor Premio', cor: 'selo-premio', icone: '🥇' },
|
||||||
AUTOR_MENCAO: { label: 'Autor - Menção Honrosa', cor: 'selo-mencao', icone: '🎖️' },
|
AUTOR_MENCAO: { label: 'Autor Mencao', cor: 'selo-mencao', icone: '🥈' },
|
||||||
ORIENT_POS_DOC: { label: 'Orient. Pós-Doc', cor: 'selo-orient', icone: '📚' },
|
ORIENT_GP: { label: 'Orient. GP', cor: 'selo-gp', icone: '🏆' },
|
||||||
ORIENT_POS_DOC_PREM: { label: 'Orient. Pós-Doc Premiada', cor: 'selo-orient-prem', icone: '📚🏆' },
|
ORIENT_PREMIO: { label: 'Orient. Premio', cor: 'selo-orient-premio', icone: '🎖️' },
|
||||||
ORIENT_TESE: { label: 'Orient. Tese', cor: 'selo-orient', icone: '📖' },
|
ORIENT_MENCAO: { label: 'Orient. Mencao', cor: 'selo-orient-mencao', icone: '📜' },
|
||||||
ORIENT_TESE_PREM: { label: 'Orient. Tese Premiada', cor: 'selo-orient-prem', icone: '📖🏆' },
|
COORIENT_GP: { label: 'Coorient. GP', cor: 'selo-gp', icone: '🏆' },
|
||||||
ORIENT_DISS: { label: 'Orient. Dissertação', cor: 'selo-orient', icone: '📝' },
|
COORIENT_PREMIO: { label: 'Coorient. Premio', cor: 'selo-coorient-premio', icone: '🎖️' },
|
||||||
ORIENT_DISS_PREM: { label: 'Orient. Diss. Premiada', cor: 'selo-orient-prem', icone: '📝🏆' },
|
COORIENT_MENCAO: { label: 'Coorient. Mencao', cor: 'selo-coorient-mencao', icone: '📜' },
|
||||||
CO_ORIENT_POS_DOC: { label: 'Co-Orient. Pós-Doc', cor: 'selo-coorient', icone: '📚' },
|
ORIENT_TESE: { label: 'Orient. Tese', cor: 'selo-orient', icone: '📚' },
|
||||||
CO_ORIENT_POS_DOC_PREM: { label: 'Co-Orient. Pós-Doc Prem.', cor: 'selo-coorient-prem', icone: '📚🏆' },
|
ORIENT_DISS: { label: 'Orient. Diss.', cor: 'selo-orient', icone: '📄' },
|
||||||
CO_ORIENT_TESE: { label: 'Co-Orient. Tese', cor: 'selo-coorient', icone: '📖' },
|
ORIENT_POS_DOC: { label: 'Orient. Pos-Doc', cor: 'selo-orient', icone: '🔬' },
|
||||||
CO_ORIENT_TESE_PREM: { label: 'Co-Orient. Tese Premiada', cor: 'selo-coorient-prem', icone: '📖🏆' },
|
CO_ORIENT_TESE: { label: 'Coorient. Tese', cor: 'selo-coorient', icone: '📚' },
|
||||||
CO_ORIENT_DISS: { label: 'Co-Orient. Diss.', cor: 'selo-coorient', icone: '📝' },
|
CO_ORIENT_DISS: { label: 'Coorient. Diss.', cor: 'selo-coorient', icone: '📄' },
|
||||||
CO_ORIENT_DISS_PREM: { label: 'Co-Orient. Diss. Prem.', cor: 'selo-coorient-prem', icone: '📝🏆' },
|
CO_ORIENT_POS_DOC: { label: 'Coorient. Pos-Doc', cor: 'selo-coorient', icone: '🔬' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const gerarSelos = (consultor) => {
|
const gerarSelos = (consultor) => {
|
||||||
@@ -29,11 +29,11 @@ const gerarSelos = (consultor) => {
|
|||||||
(c) => c.codigo === 'CAM' && c.presidente && (c.ativo ?? !c.fim)
|
(c) => c.codigo === 'CAM' && c.presidente && (c.ativo ?? !c.fim)
|
||||||
);
|
);
|
||||||
if (isPresidCamaraVigente) {
|
if (isPresidCamaraVigente) {
|
||||||
selos.push({ ...SELOS.PRESID_CAMARA, qtd: 1, hint: 'Presidente Câmara Temática: mandato vigente como presidente.' });
|
selos.push({ ...SELOS.PRESID_CAMARA, qtd: 1, hint: 'Presidente Câmara Temática' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consultor.coordenador_ppg) {
|
if (consultor.coordenador_ppg) {
|
||||||
selos.push({ ...SELOS.COORD_PPG, qtd: 1, hint: 'Coordenador de PPG: possui perfil/atuação de coordenação de programa no ATUACAPES.' });
|
selos.push({ ...SELOS.COORD_PPG, qtd: 1, hint: 'Coordenador de PPG' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const bolsas = Array.isArray(consultor.bolsas_cnpq) ? consultor.bolsas_cnpq : [];
|
const bolsas = Array.isArray(consultor.bolsas_cnpq) ? consultor.bolsas_cnpq : [];
|
||||||
@@ -43,68 +43,45 @@ const gerarSelos = (consultor) => {
|
|||||||
const nivel = (b.nivel || 'N/A').toString().trim();
|
const nivel = (b.nivel || 'N/A').toString().trim();
|
||||||
porNivel[nivel] = (porNivel[nivel] || 0) + 1;
|
porNivel[nivel] = (porNivel[nivel] || 0) + 1;
|
||||||
}
|
}
|
||||||
const niveis = Object.keys(porNivel);
|
const niveis = Object.keys(porNivel).sort();
|
||||||
const label = niveis.length === 1 ? `BPQ ${niveis[0]}` : 'BPQ';
|
const label = niveis.length === 1 ? `BPQ ${niveis[0]}` : 'BPQ';
|
||||||
const breakdown = niveis
|
const niveisStr = niveis.join(', ');
|
||||||
.sort()
|
selos.push({ ...SELOS.BPQ, label, qtd: bolsas.length, hint: `BPQ NIVEL ${niveisStr}` });
|
||||||
.map((n) => `${n}=${porNivel[n]}`)
|
|
||||||
.join(' | ');
|
|
||||||
selos.push({ ...SELOS.BPQ, label, qtd: bolsas.length, hint: `BPQ NIVEL: ${breakdown}` });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const premiacoes = Array.isArray(consultor.premiacoes) ? consultor.premiacoes : [];
|
const premiacoes = Array.isArray(consultor.premiacoes) ? consultor.premiacoes : [];
|
||||||
const premiacoesAutor = premiacoes.filter((p) => (p.papel || '').toString().toLowerCase() === 'autor');
|
|
||||||
const autorGp = premiacoesAutor.filter((p) => p.codigo === 'PREMIACAO').length;
|
|
||||||
const autorPremio = premiacoesAutor.filter((p) => p.codigo === 'PREMIACAO_GP').length;
|
|
||||||
const autorMencao = premiacoesAutor.filter((p) => p.codigo === 'MENCAO').length;
|
|
||||||
|
|
||||||
if (autorGp > 0) selos.push({ ...SELOS.AUTOR_GP, qtd: autorGp, hint: `Autor - Grande Prêmio: ${autorGp} ocorrência(s).` });
|
const gerarSelosPorPapel = (papel, seloGP, seloPremio, seloMencao, hintPrefix) => {
|
||||||
if (autorPremio > 0) selos.push({ ...SELOS.AUTOR_PREMIO, qtd: autorPremio, hint: `Autor - Prêmio: ${autorPremio} ocorrência(s).` });
|
const lista = premiacoes.filter((p) => (p.papel || '').toString().toLowerCase() === papel.toLowerCase());
|
||||||
if (autorMencao > 0) selos.push({ ...SELOS.AUTOR_MENCAO, qtd: autorMencao, hint: `Autor - Menção Honrosa: ${autorMencao} ocorrência(s).` });
|
const gp = lista.filter((p) => p.codigo === 'PREMIACAO').length;
|
||||||
|
const premio = lista.filter((p) => p.codigo === 'PREMIACAO_GP').length;
|
||||||
|
const mencao = lista.filter((p) => p.codigo === 'MENCAO').length;
|
||||||
|
|
||||||
|
if (gp > 0) selos.push({ ...seloGP, qtd: gp, hint: `${hintPrefix} - Grande Prêmio` });
|
||||||
|
if (premio > 0) selos.push({ ...seloPremio, qtd: premio, hint: `${hintPrefix} - Prêmio` });
|
||||||
|
if (mencao > 0) selos.push({ ...seloMencao, qtd: mencao, hint: `${hintPrefix} - Menção Honrosa` });
|
||||||
|
};
|
||||||
|
|
||||||
|
gerarSelosPorPapel('autor', SELOS.AUTOR_GP, SELOS.AUTOR_PREMIO, SELOS.AUTOR_MENCAO, 'Autor');
|
||||||
|
gerarSelosPorPapel('orientador', SELOS.ORIENT_GP, SELOS.ORIENT_PREMIO, SELOS.ORIENT_MENCAO, 'Orientador');
|
||||||
|
gerarSelosPorPapel('coorientador', SELOS.COORIENT_GP, SELOS.COORIENT_PREMIO, SELOS.COORIENT_MENCAO, 'Coorientador');
|
||||||
|
|
||||||
const orientacoes = Array.isArray(consultor.orientacoes) ? consultor.orientacoes : [];
|
const orientacoes = Array.isArray(consultor.orientacoes) ? consultor.orientacoes : [];
|
||||||
const contarPremiadas = (lista) => {
|
|
||||||
const acc = { GP: 0, PREMIO: 0, MENCAO: 0 };
|
|
||||||
for (const o of lista) {
|
|
||||||
if (!o?.premiada) continue;
|
|
||||||
const t = (o.premiacao_tipo || '').toString().toUpperCase();
|
|
||||||
if (t.includes('GP')) acc.GP += 1;
|
|
||||||
else if (t.includes('MENCAO')) acc.MENCAO += 1;
|
|
||||||
else acc.PREMIO += 1;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
};
|
|
||||||
const hintPremiadas = (labelBase, counts) =>
|
|
||||||
`${labelBase} (GP / Prêmio / Menção): GP=${counts.GP} | Prêmio=${counts.PREMIO} | Menção=${counts.MENCAO}`;
|
|
||||||
|
|
||||||
const selosOrientacao = (codigo, seloNormal, seloPrem) => {
|
const gerarSelosOrientacaoContagem = (codigo, isCoorientacao, seloBase) => {
|
||||||
const base = orientacoes.filter((o) => o.codigo === codigo && !o.coorientacao);
|
const lista = orientacoes.filter((o) => o.codigo === codigo && (isCoorientacao ? o.coorientacao : !o.coorientacao));
|
||||||
const prem = base.filter((o) => o.premiada);
|
if (lista.length > 0) {
|
||||||
const naoPrem = base.filter((o) => !o.premiada);
|
selos.push({ ...seloBase, qtd: lista.length, hint: `${seloBase.label} (${lista.length}x)` });
|
||||||
if (prem.length > 0) {
|
|
||||||
selos.push({ ...seloPrem, qtd: prem.length, hint: hintPremiadas(seloPrem.label, contarPremiadas(prem)) });
|
|
||||||
} else if (naoPrem.length > 0) {
|
|
||||||
selos.push({ ...seloNormal, qtd: naoPrem.length, hint: `${seloNormal.label}: ${naoPrem.length} ocorrência(s).` });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selosCoorientacao = (codigo, seloNormal, seloPrem) => {
|
gerarSelosOrientacaoContagem('ORIENT_POS_DOC', false, SELOS.ORIENT_POS_DOC);
|
||||||
const base = orientacoes.filter((o) => o.codigo === codigo && o.coorientacao);
|
gerarSelosOrientacaoContagem('ORIENT_TESE', false, SELOS.ORIENT_TESE);
|
||||||
const prem = base.filter((o) => o.premiada);
|
gerarSelosOrientacaoContagem('ORIENT_DISS', false, SELOS.ORIENT_DISS);
|
||||||
const naoPrem = base.filter((o) => !o.premiada);
|
|
||||||
if (prem.length > 0) {
|
|
||||||
selos.push({ ...seloPrem, qtd: prem.length, hint: hintPremiadas(seloPrem.label, contarPremiadas(prem)) });
|
|
||||||
} else if (naoPrem.length > 0) {
|
|
||||||
selos.push({ ...seloNormal, qtd: naoPrem.length, hint: `${seloNormal.label}: ${naoPrem.length} ocorrência(s).` });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
selosOrientacao('ORIENT_POS_DOC', SELOS.ORIENT_POS_DOC, SELOS.ORIENT_POS_DOC_PREM);
|
gerarSelosOrientacaoContagem('CO_ORIENT_POS_DOC', true, SELOS.CO_ORIENT_POS_DOC);
|
||||||
selosOrientacao('ORIENT_TESE', SELOS.ORIENT_TESE, SELOS.ORIENT_TESE_PREM);
|
gerarSelosOrientacaoContagem('CO_ORIENT_TESE', true, SELOS.CO_ORIENT_TESE);
|
||||||
selosOrientacao('ORIENT_DISS', SELOS.ORIENT_DISS, SELOS.ORIENT_DISS_PREM);
|
gerarSelosOrientacaoContagem('CO_ORIENT_DISS', true, SELOS.CO_ORIENT_DISS);
|
||||||
selosCoorientacao('CO_ORIENT_POS_DOC', SELOS.CO_ORIENT_POS_DOC, SELOS.CO_ORIENT_POS_DOC_PREM);
|
|
||||||
selosCoorientacao('CO_ORIENT_TESE', SELOS.CO_ORIENT_TESE, SELOS.CO_ORIENT_TESE_PREM);
|
|
||||||
selosCoorientacao('CO_ORIENT_DISS', SELOS.CO_ORIENT_DISS, SELOS.CO_ORIENT_DISS_PREM);
|
|
||||||
|
|
||||||
return selos;
|
return selos;
|
||||||
};
|
};
|
||||||
@@ -146,11 +123,11 @@ const FORMULAS = {
|
|||||||
},
|
},
|
||||||
bloco_c: {
|
bloco_c: {
|
||||||
titulo: 'Consultoria',
|
titulo: 'Consultoria',
|
||||||
descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade (escalonado): 3a=+5 | 5a=+10 | 8a+=+15\nRetorno (reativação): +15 (uma vez)',
|
descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade 8a+=+20 (escalonado)\nRetorno (reativação): +15 (uma vez)',
|
||||||
},
|
},
|
||||||
bloco_d: {
|
bloco_d: {
|
||||||
titulo: 'Premiacoes/Avaliacoes',
|
titulo: 'Premiacoes/Avaliacoes',
|
||||||
descricao: 'Premiações: GP=150 (teto 180) | Prêmio=30 (teto 60) | Menção=10 (teto 20)\nBolsas: BPQ_SUP=30 (teto 60) | BPQ_INT=50 (teto 100)\nInscrições/Avaliações/Comissões/Participações/Orientações/Bancas (com tetos por código)',
|
descricao: 'Premiações: GP=100 (teto 180) | Prêmio=50 (teto 60) | Menção=30 (teto 30)\nBolsas: BPQ_SUP=30 (teto 60) | BPQ_INT=50 (teto 100)\nInscrições/Avaliações/Comissões/Participações/Orientações/Bancas (com tetos por código)',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,7 +137,7 @@ const PONTOS_BASE = {
|
|||||||
INSC_AUTOR: 10, INSC_INST: 30,
|
INSC_AUTOR: 10, INSC_INST: 30,
|
||||||
AVAL_COMIS_PREMIO: 30, AVAL_COMIS_GP: 50,
|
AVAL_COMIS_PREMIO: 30, AVAL_COMIS_GP: 50,
|
||||||
COORD_COMIS_PREMIO: 50, COORD_COMIS_GP: 60,
|
COORD_COMIS_PREMIO: 50, COORD_COMIS_GP: 60,
|
||||||
PREMIACAO: 150, PREMIACAO_GP: 30, MENCAO: 10,
|
PREMIACAO: 100, PREMIACAO_GP: 50, MENCAO: 30,
|
||||||
BOL_BPQ_SUP: 30, BOL_BPQ_INT: 50,
|
BOL_BPQ_SUP: 30, BOL_BPQ_INT: 50,
|
||||||
BOL_BPQ_SUPERIOR: 30, BOL_BPQ_INTERMEDIARIO: 50,
|
BOL_BPQ_SUPERIOR: 30, BOL_BPQ_INTERMEDIARIO: 50,
|
||||||
EVENTO: 1, PROJ: 10,
|
EVENTO: 1, PROJ: 10,
|
||||||
@@ -173,12 +150,12 @@ const TETOS = {
|
|||||||
INSC_AUTOR: { teto: 20, doc: '3.3 Inscrições' },
|
INSC_AUTOR: { teto: 20, doc: '3.3 Inscrições' },
|
||||||
INSC_INST: { teto: 60, doc: '3.3 Inscrições' },
|
INSC_INST: { teto: 60, doc: '3.3 Inscrições' },
|
||||||
AVAL_COMIS_PREMIO: { teto: 60, doc: '3.4 Avaliação/Comissão', bonus: '+2/ano (max 15)' },
|
AVAL_COMIS_PREMIO: { teto: 60, doc: '3.4 Avaliação/Comissão', bonus: '+2/ano (max 15)' },
|
||||||
AVAL_COMIS_GP: { teto: 100, doc: '3.4 Avaliação/Comissão', bonus: '+3/ano (max 20)' },
|
AVAL_COMIS_GP: { teto: 80, doc: '3.4 Avaliação/Comissão', bonus: '+3/ano (max 20)' },
|
||||||
COORD_COMIS_PREMIO: { teto: 100, doc: '3.4 Avaliação/Comissão', bonus: '+4/ano (max 20)' },
|
COORD_COMIS_PREMIO: { teto: 100, doc: '3.4 Avaliação/Comissão', bonus: '+4/ano (max 20)' },
|
||||||
COORD_COMIS_GP: { teto: 120, doc: '3.4 Avaliação/Comissão', bonus: '+6/ano (max 20)' },
|
COORD_COMIS_GP: { teto: 120, doc: '3.4 Avaliação/Comissão', bonus: '+6/ano (max 20)' },
|
||||||
PREMIACAO: { teto: 180, doc: '3.4 Premiações e Bolsas' },
|
PREMIACAO: { teto: 180, doc: '3.4 Premiações e Bolsas' },
|
||||||
PREMIACAO_GP: { teto: 60, doc: '3.4 Premiações e Bolsas' },
|
PREMIACAO_GP: { teto: 60, doc: '3.4 Premiações e Bolsas' },
|
||||||
MENCAO: { teto: 20, doc: '3.4 Premiações e Bolsas' },
|
MENCAO: { teto: 30, doc: '3.4 Premiações e Bolsas' },
|
||||||
EVENTO: { teto: 5, doc: '3.5 Participações Acadêmicas' },
|
EVENTO: { teto: 5, doc: '3.5 Participações Acadêmicas' },
|
||||||
PROJ: { teto: 40, doc: '3.5 Participações Acadêmicas' },
|
PROJ: { teto: 40, doc: '3.5 Participações Acadêmicas' },
|
||||||
BOL_BPQ_SUP: { teto: 60, doc: '3.4 Premiações e Bolsas' },
|
BOL_BPQ_SUP: { teto: 60, doc: '3.4 Premiações e Bolsas' },
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ docker compose up -d oracle18c backend
|
|||||||
|
|
||||||
echo "[3/5] Aguardando backend responder /health..."
|
echo "[3/5] Aguardando backend responder /health..."
|
||||||
for i in {1..30}; do
|
for i in {1..30}; do
|
||||||
if docker compose exec backend python - <<'PY' >/dev/null 2>&1; then
|
if docker compose exec -T backend python - <<'PY' >/dev/null 2>&1; then
|
||||||
import httpx, sys
|
import httpx, sys
|
||||||
try:
|
try:
|
||||||
r = httpx.get("http://localhost:8000/api/v1/health", timeout=15)
|
r = httpx.get("http://localhost:8000/api/v1/health", timeout=15)
|
||||||
@@ -34,7 +34,7 @@ PY
|
|||||||
done
|
done
|
||||||
|
|
||||||
echo "[4/5] Disparando job do ranking (limpar_antes=true)..."
|
echo "[4/5] Disparando job do ranking (limpar_antes=true)..."
|
||||||
docker compose exec backend python - <<'PY'
|
docker compose exec -T backend python - <<'PY'
|
||||||
import httpx
|
import httpx
|
||||||
client = httpx.Client(timeout=120)
|
client = httpx.Client(timeout=120)
|
||||||
resp = client.post("http://localhost:8000/api/v1/ranking/processar", json={"limpar_antes": True})
|
resp = client.post("http://localhost:8000/api/v1/ranking/processar", json={"limpar_antes": True})
|
||||||
@@ -42,7 +42,7 @@ print("POST /api/v1/ranking/processar ->", resp.status_code, resp.text)
|
|||||||
PY
|
PY
|
||||||
|
|
||||||
echo "[5/5] Acompanhando status até finalizar..."
|
echo "[5/5] Acompanhando status até finalizar..."
|
||||||
docker compose exec backend python - <<'PY'
|
docker compose exec -T backend python - <<'PY'
|
||||||
import httpx, time
|
import httpx, time
|
||||||
client = httpx.Client(timeout=120)
|
client = httpx.Client(timeout=120)
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
Reference in New Issue
Block a user