Reimplementa sistema de ranking com novos critérios V2
Mudanças principais: - Substitui 4 Componentes (A,B,C,D) por 3 Blocos (A,C,D) - Remove Componente B (Coordenação PPG = 0 pts no V1) - Adiciona novos tipos de atuação do Elasticsearch - Implementa critérios de pontuação com tetos individuais Bloco A - Coordenação CAPES: - CA (max 450), CAJ (max 370), CAJ_MP (max 315), CAM (max 280) - Calcula base + tempo + bônus atualidade + bônus retorno Bloco C - Consultoria: - CONS_ATIVO (base 150), CONS_HIST (base 100), CONS_FALECIDO (base 100) - Bônus continuidade: 3anos=+5, 5anos=+10, 8anos=+15 - Bônus retorno: +15 Bloco D - Premiações/Avaliações: - Inscrições (INSC_AUTOR, INSC_INST) - Avaliações (AVAL_COMIS_PREMIO, AVAL_COMIS_GP) - Coordenações (COORD_COMIS_PREMIO, COORD_COMIS_GP) - Premiações (PREMIACAO, PREMIACAO_GP, MENCAO) - Bolsas CNPQ, Participações, Orientações, Membros de Banca Frontend: - Header, ConsultorCard, CompararModal atualizados para 3 blocos - API service atualizado para nova estrutura de dados
This commit is contained in:
@@ -1,13 +1,34 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
from ..value_objects.periodo import Periodo
|
||||
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
|
||||
class CoordenacaoCapes:
|
||||
codigo: str
|
||||
tipo: str
|
||||
area_avaliacao: str
|
||||
periodo: Periodo
|
||||
@@ -16,46 +37,70 @@ class CoordenacaoCapes:
|
||||
|
||||
|
||||
@dataclass
|
||||
class CoordenacaoPrograma:
|
||||
id_programa: int
|
||||
nome_programa: str
|
||||
codigo_programa: str
|
||||
nota_ppg: str
|
||||
modalidade: str
|
||||
area_avaliacao: str
|
||||
class Consultoria:
|
||||
codigo: str
|
||||
situacao: str
|
||||
periodo: Periodo
|
||||
areas: List[str] = field(default_factory=list)
|
||||
anos_consecutivos: int = 0
|
||||
retornos: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class Consultoria:
|
||||
total_eventos: int
|
||||
eventos_recentes: int
|
||||
primeiro_evento: datetime
|
||||
ultimo_evento: datetime
|
||||
areas: List[str] = field(default_factory=list)
|
||||
situacao: str = "N/A"
|
||||
anos_completos: int = 0
|
||||
anos_consecutivos: int = 0
|
||||
retornos: int = 0
|
||||
vezes_responsavel: int = 0
|
||||
class Inscricao:
|
||||
codigo: str
|
||||
tipo: str
|
||||
premio: str
|
||||
ano: int
|
||||
situacao: str = ""
|
||||
|
||||
@property
|
||||
def continuidade(self) -> int:
|
||||
if self.anos_consecutivos >= 8:
|
||||
return 15
|
||||
elif self.anos_consecutivos >= 5:
|
||||
return 10
|
||||
elif self.anos_consecutivos >= 3:
|
||||
return 5
|
||||
return 0
|
||||
|
||||
@dataclass
|
||||
class AvaliacaoComissao:
|
||||
codigo: str
|
||||
tipo: str
|
||||
premio: str
|
||||
ano: int
|
||||
comissao_tipo: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Premiacao:
|
||||
codigo: str
|
||||
tipo: str
|
||||
nome_premio: str
|
||||
ano: int
|
||||
pontos: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class BolsaCNPQ:
|
||||
codigo: str
|
||||
nivel: str
|
||||
area: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Participacao:
|
||||
codigo: str
|
||||
tipo: str
|
||||
descricao: str = ""
|
||||
ano: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Orientacao:
|
||||
codigo: str
|
||||
tipo: str
|
||||
nivel: str
|
||||
ano: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class MembroBanca:
|
||||
codigo: str
|
||||
tipo: str
|
||||
nivel: str
|
||||
ano: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -64,28 +109,29 @@ class Consultor:
|
||||
nome: str
|
||||
cpf: Optional[str] = None
|
||||
coordenacoes_capes: List[CoordenacaoCapes] = field(default_factory=list)
|
||||
coordenacoes_programas: List[CoordenacaoPrograma] = field(default_factory=list)
|
||||
consultoria: Optional[Consultoria] = None
|
||||
inscricoes: List[Inscricao] = field(default_factory=list)
|
||||
avaliacoes_comissao: List[AvaliacaoComissao] = field(default_factory=list)
|
||||
premiacoes: List[Premiacao] = field(default_factory=list)
|
||||
bolsas_cnpq: List[BolsaCNPQ] = field(default_factory=list)
|
||||
participacoes: List[Participacao] = field(default_factory=list)
|
||||
orientacoes: List[Orientacao] = field(default_factory=list)
|
||||
membros_banca: List[MembroBanca] = field(default_factory=list)
|
||||
pontuacao: Optional[PontuacaoCompleta] = None
|
||||
atuacoes_raw: List[Atuacao] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def anos_atuacao(self) -> float:
|
||||
if not self.consultoria:
|
||||
if not self.consultoria or not self.consultoria.periodo.inicio:
|
||||
return 0.0
|
||||
dias = (datetime.now() - self.consultoria.primeiro_evento).days
|
||||
dias = (datetime.now() - self.consultoria.periodo.inicio).days
|
||||
return round(dias / 365.25, 1)
|
||||
|
||||
@property
|
||||
def ativo(self) -> bool:
|
||||
if not self.consultoria:
|
||||
return False
|
||||
situacao = (self.consultoria.situacao or "").lower()
|
||||
if "atividade" in situacao:
|
||||
return True
|
||||
if "inativ" in situacao:
|
||||
return False
|
||||
return self.consultoria.eventos_recentes > 0
|
||||
return self.consultoria.codigo == "CONS_ATIVO"
|
||||
|
||||
@property
|
||||
def veterano(self) -> bool:
|
||||
@@ -94,3 +140,15 @@ class Consultor:
|
||||
@property
|
||||
def pontuacao_total(self) -> int:
|
||||
return self.pontuacao.total if self.pontuacao else 0
|
||||
|
||||
@property
|
||||
def pontuacao_bloco_a(self) -> int:
|
||||
return self.pontuacao.bloco_a.total if self.pontuacao else 0
|
||||
|
||||
@property
|
||||
def pontuacao_bloco_c(self) -> int:
|
||||
return self.pontuacao.bloco_c.total if self.pontuacao else 0
|
||||
|
||||
@property
|
||||
def pontuacao_bloco_d(self) -> int:
|
||||
return self.pontuacao.bloco_d.total if self.pontuacao else 0
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from typing import List, Dict
|
||||
from collections import defaultdict
|
||||
|
||||
from ..entities.consultor import (
|
||||
Consultor,
|
||||
CoordenacaoCapes,
|
||||
CoordenacaoPrograma,
|
||||
Consultoria,
|
||||
Inscricao,
|
||||
AvaliacaoComissao,
|
||||
Premiacao,
|
||||
BolsaCNPQ,
|
||||
Participacao,
|
||||
Orientacao,
|
||||
MembroBanca,
|
||||
)
|
||||
from ..value_objects.pontuacao import ComponentePontuacao, PontuacaoCompleta
|
||||
from ..value_objects.pontuacao import PontuacaoAtuacao, PontuacaoBloco, PontuacaoCompleta
|
||||
from ..value_objects.criterios_pontuacao import CRITERIOS, get_criterio, Bloco
|
||||
from ..value_objects.periodo import Periodo
|
||||
|
||||
|
||||
class CalculadorPontuacao:
|
||||
|
||||
@staticmethod
|
||||
def _mesclar_periodos(periodos: List[Periodo]) -> List[Periodo]:
|
||||
"""
|
||||
Mescla períodos sobrepostos/contíguos para evitar contagem dupla.
|
||||
"""
|
||||
if not periodos:
|
||||
return []
|
||||
|
||||
periodos_ordenados = sorted(periodos, key=lambda p: p.inicio)
|
||||
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 <= ultimo_fim:
|
||||
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
|
||||
@@ -47,164 +47,183 @@ class CalculadorPontuacao:
|
||||
return sum(p.anos_completos(ref) for p in periodos)
|
||||
|
||||
@staticmethod
|
||||
def calcular_componente_a(coordenacoes: List[CoordenacaoCapes]) -> ComponentePontuacao:
|
||||
def calcular_bloco_a(coordenacoes: List[CoordenacaoCapes]) -> PontuacaoBloco:
|
||||
if not coordenacoes:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0, retorno=0)
|
||||
return PontuacaoBloco(bloco="A", atuacoes=[])
|
||||
|
||||
base_map = {"CA": 200, "CAJ": 150, "CAJ-MP": 120, "CAM": 100}
|
||||
tempo_max_map = {"CA": 100, "CAJ": 80, "CAJ-MP": 60, "CAM": 50}
|
||||
bonus_atual_map = {"CA": 30, "CAJ": 20, "CAJ-MP": 15, "CAM": 10}
|
||||
mult_tempo_map = {"CA": 10, "CAJ": 8, "CAJ-MP": 6, "CAM": 5}
|
||||
|
||||
# Agrupa por tipo de coordenação e considera o melhor tipo (hierarquia)
|
||||
tipos_ordenados = ["CA", "CAJ", "CAJ-MP", "CAM"]
|
||||
coord_por_tipo = {t: [] for t in tipos_ordenados}
|
||||
tipos_ordenados = ["CA", "CAJ", "CAJ_MP", "CAM"]
|
||||
coord_por_tipo: Dict[str, List[CoordenacaoCapes]] = defaultdict(list)
|
||||
for c in coordenacoes:
|
||||
coord_por_tipo.setdefault(c.tipo, []).append(c)
|
||||
codigo = c.codigo.replace("-", "_")
|
||||
coord_por_tipo[codigo].append(c)
|
||||
|
||||
coord_escolhida_tipo = None
|
||||
for t in tipos_ordenados:
|
||||
if coord_por_tipo.get(t):
|
||||
coord_escolhida_tipo = t
|
||||
break
|
||||
|
||||
if not coord_escolhida_tipo:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0, retorno=0)
|
||||
|
||||
coord_do_tipo = coord_por_tipo.get(coord_escolhida_tipo, [])
|
||||
coord_por_area = {}
|
||||
for c in coord_do_tipo:
|
||||
area = c.area_avaliacao or "N/A"
|
||||
coord_por_area.setdefault(area, []).append(c.periodo)
|
||||
|
||||
anos_total = 0
|
||||
ativo = False
|
||||
retornos_encontrados = 0
|
||||
|
||||
for _, periodos_area in coord_por_area.items():
|
||||
mesclados = CalculadorPontuacao._mesclar_periodos(periodos_area)
|
||||
anos_total += CalculadorPontuacao._anos_completos_periodos(mesclados)
|
||||
ativo = ativo or any(p.ativo for p in periodos_area)
|
||||
if len(mesclados) > 1:
|
||||
retornos_encontrados += 1
|
||||
|
||||
base = base_map.get(coord_escolhida_tipo, 0)
|
||||
tempo = min(int(anos_total * mult_tempo_map.get(coord_escolhida_tipo, 0)), tempo_max_map.get(coord_escolhida_tipo, 0))
|
||||
|
||||
extras = 0
|
||||
areas_distintas = list(coord_por_area.keys())
|
||||
if len(areas_distintas) > 1:
|
||||
extras = min((len(areas_distintas) - 1) * 20, 100)
|
||||
|
||||
bonus = bonus_atual_map.get(coord_escolhida_tipo, 0) if ativo else 0
|
||||
retorno = 20 if retornos_encontrados > 0 else 0
|
||||
|
||||
return ComponentePontuacao(base=base, tempo=tempo, extras=extras, bonus=bonus, retorno=retorno, teto=450)
|
||||
|
||||
@staticmethod
|
||||
def calcular_componente_b(coordenacoes: List[CoordenacaoPrograma]) -> ComponentePontuacao:
|
||||
"""
|
||||
Calcula pontuação do Componente B (Coordenação de Programa PPG).
|
||||
|
||||
Regras (máximo 180 pts):
|
||||
- Base: 70 pts por ser coordenador
|
||||
- Tempo: 5 pts/ano (máx 50)
|
||||
- Programas adicionais: 20 pts/programa (máx 40)
|
||||
- Nota do PPG: usar MAIOR nota (7=20, 6=15, 5=10, 4=5, 3=0)
|
||||
"""
|
||||
if not coordenacoes:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0)
|
||||
|
||||
# Base: 70 pts por ser coordenador
|
||||
base = 70
|
||||
|
||||
# Tempo: 5 pts/ano (máx 50)
|
||||
anos_totais = sum(c.periodo.anos_completos(datetime.now()) for c in coordenacoes)
|
||||
tempo = min(int(anos_totais * 5), 50)
|
||||
|
||||
# Programas adicionais: 20 pts/programa extra (máx 40)
|
||||
programas_distintos = len({c.id_programa for c in coordenacoes})
|
||||
extras = min((programas_distintos - 1) * 20, 40) if programas_distintos > 1 else 0
|
||||
|
||||
# Nota do PPG: usar MAIOR nota entre os programas
|
||||
# Escala: 7=20, 6=15, 5=10, 4=5, 3=0
|
||||
maior_nota = 0
|
||||
for c in coordenacoes:
|
||||
try:
|
||||
nota_str = str(c.nota_ppg).strip()
|
||||
# Trata notas válidas (3-7 ou A para programas novos)
|
||||
if nota_str in ['7']:
|
||||
maior_nota = max(maior_nota, 7)
|
||||
elif nota_str in ['6']:
|
||||
maior_nota = max(maior_nota, 6)
|
||||
elif nota_str in ['5']:
|
||||
maior_nota = max(maior_nota, 5)
|
||||
elif nota_str in ['4']:
|
||||
maior_nota = max(maior_nota, 4)
|
||||
elif nota_str in ['3']:
|
||||
maior_nota = max(maior_nota, 3)
|
||||
except Exception:
|
||||
atuacoes = []
|
||||
for tipo in tipos_ordenados:
|
||||
if tipo not in coord_por_tipo:
|
||||
continue
|
||||
|
||||
# Mapeia nota para pontos
|
||||
mapa_nota = {7: 20, 6: 15, 5: 10, 4: 5, 3: 0}
|
||||
bonus = mapa_nota.get(maior_nota, 0)
|
||||
criterio = get_criterio(tipo)
|
||||
if not criterio:
|
||||
continue
|
||||
|
||||
return ComponentePontuacao(base=base, tempo=tempo, extras=extras, bonus=bonus, teto=180)
|
||||
coords = coord_por_tipo[tipo]
|
||||
periodos = [c.periodo for c in coords]
|
||||
mesclados = CalculadorPontuacao._mesclar_periodos(periodos)
|
||||
|
||||
anos_total = CalculadorPontuacao._anos_completos_periodos(mesclados)
|
||||
ativo = any(c.periodo.ativo for c in coords)
|
||||
tem_retorno = len(mesclados) > 1
|
||||
|
||||
base = criterio.base
|
||||
tempo = min(anos_total * criterio.multiplicador_tempo, criterio.teto_tempo)
|
||||
bonus_atualidade = criterio.bonus_atualidade if ativo else 0
|
||||
bonus_retorno = criterio.bonus_retorno if tem_retorno else 0
|
||||
bonus = bonus_atualidade + bonus_retorno
|
||||
|
||||
total_bruto = base + tempo + bonus
|
||||
total = min(total_bruto, criterio.teto)
|
||||
|
||||
atuacoes.append(PontuacaoAtuacao(
|
||||
codigo=tipo,
|
||||
base=base,
|
||||
tempo=tempo,
|
||||
bonus=bonus,
|
||||
total=total,
|
||||
quantidade=len(coords),
|
||||
))
|
||||
|
||||
return PontuacaoBloco(bloco="A", atuacoes=atuacoes)
|
||||
|
||||
@staticmethod
|
||||
def calcular_componente_c(consultoria: Consultoria) -> ComponentePontuacao:
|
||||
def calcular_bloco_c(consultoria: Consultoria) -> PontuacaoBloco:
|
||||
if not consultoria:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0)
|
||||
return PontuacaoBloco(bloco="C", atuacoes=[])
|
||||
|
||||
base = 150 if consultoria.eventos_recentes > 0 else 100
|
||||
codigo = consultoria.codigo
|
||||
criterio = get_criterio(codigo)
|
||||
if not criterio:
|
||||
return PontuacaoBloco(bloco="C", atuacoes=[])
|
||||
|
||||
anos = consultoria.anos_completos if consultoria.anos_completos else int(
|
||||
((datetime.now() - consultoria.primeiro_evento).days) // 365
|
||||
)
|
||||
tempo = min(int(anos * 5), 50)
|
||||
base = criterio.base
|
||||
|
||||
extras_eventos = min(consultoria.total_eventos * 2, 20)
|
||||
extras_responsavel = min(consultoria.vezes_responsavel * 5, 25)
|
||||
extras_areas = min((len(consultoria.areas) - 1) * 10, 30) if len(consultoria.areas) > 1 else 0
|
||||
extras = extras_eventos + extras_responsavel + extras_areas
|
||||
tempo = 0
|
||||
if criterio.pontua_tempo and consultoria.periodo.inicio:
|
||||
anos = consultoria.periodo.anos_completos(datetime.now())
|
||||
tempo = min(anos * criterio.multiplicador_tempo, criterio.teto_tempo)
|
||||
|
||||
continuidade = consultoria.anos_consecutivos
|
||||
if continuidade >= 8:
|
||||
bonus_continuidade = 15
|
||||
elif continuidade >= 5:
|
||||
bonus_continuidade = 10
|
||||
elif continuidade >= 3:
|
||||
bonus_continuidade = 5
|
||||
else:
|
||||
bonus_continuidade = 0
|
||||
bonus = 0
|
||||
if codigo == "CONS_ATIVO":
|
||||
if consultoria.anos_consecutivos >= 8:
|
||||
bonus += criterio.bonus_continuidade_8anos
|
||||
elif consultoria.anos_consecutivos >= 5:
|
||||
bonus += criterio.bonus_continuidade_5anos
|
||||
elif consultoria.anos_consecutivos >= 3:
|
||||
bonus += criterio.bonus_continuidade_3anos
|
||||
if consultoria.retornos > 0:
|
||||
bonus += criterio.bonus_retorno
|
||||
|
||||
retorno_bonus = 15 if consultoria.retornos > 0 else 0
|
||||
bonus = bonus_continuidade + retorno_bonus
|
||||
total_bruto = base + tempo + bonus
|
||||
total = min(total_bruto, criterio.teto)
|
||||
|
||||
return ComponentePontuacao(base=base, tempo=tempo, extras=extras, bonus=bonus, teto=230)
|
||||
atuacoes = [PontuacaoAtuacao(
|
||||
codigo=codigo,
|
||||
base=base,
|
||||
tempo=tempo,
|
||||
bonus=bonus,
|
||||
total=total,
|
||||
quantidade=1,
|
||||
)]
|
||||
|
||||
return PontuacaoBloco(bloco="C", atuacoes=atuacoes)
|
||||
|
||||
@staticmethod
|
||||
def calcular_componente_d(premiacoes: List[Premiacao]) -> ComponentePontuacao:
|
||||
if not premiacoes:
|
||||
return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0)
|
||||
def calcular_bloco_d(
|
||||
inscricoes: List[Inscricao],
|
||||
avaliacoes: List[AvaliacaoComissao],
|
||||
premiacoes: List[Premiacao],
|
||||
bolsas: List[BolsaCNPQ],
|
||||
participacoes: List[Participacao],
|
||||
orientacoes: List[Orientacao],
|
||||
membros_banca: List[MembroBanca],
|
||||
) -> PontuacaoBloco:
|
||||
atuacoes = []
|
||||
totais_por_codigo: Dict[str, Dict] = defaultdict(lambda: {"base": 0, "qtd": 0})
|
||||
|
||||
avaliador = [p for p in premiacoes if "avaliador" in (p.tipo or "").lower()]
|
||||
outros = [p for p in premiacoes if p not in avaliador]
|
||||
for insc in inscricoes:
|
||||
criterio = get_criterio(insc.codigo)
|
||||
if criterio:
|
||||
totais_por_codigo[insc.codigo]["base"] += criterio.base
|
||||
totais_por_codigo[insc.codigo]["qtd"] += 1
|
||||
|
||||
pontos_avaliador = min(sum(p.pontos for p in avaliador), 20)
|
||||
total_pontos = pontos_avaliador + sum(p.pontos for p in outros)
|
||||
total_pontos = min(total_pontos, 180)
|
||||
for aval in avaliacoes:
|
||||
criterio = get_criterio(aval.codigo)
|
||||
if criterio:
|
||||
totais_por_codigo[aval.codigo]["base"] += criterio.base
|
||||
totais_por_codigo[aval.codigo]["qtd"] += 1
|
||||
|
||||
return ComponentePontuacao(base=total_pontos, tempo=0, extras=0, bonus=0, teto=180)
|
||||
for prem in premiacoes:
|
||||
criterio = get_criterio(prem.codigo)
|
||||
if criterio:
|
||||
totais_por_codigo[prem.codigo]["base"] += criterio.base
|
||||
totais_por_codigo[prem.codigo]["qtd"] += 1
|
||||
|
||||
for bolsa in bolsas:
|
||||
criterio = get_criterio(bolsa.codigo)
|
||||
if criterio:
|
||||
totais_por_codigo[bolsa.codigo]["base"] += criterio.base
|
||||
totais_por_codigo[bolsa.codigo]["qtd"] += 1
|
||||
|
||||
for part in participacoes:
|
||||
criterio = get_criterio(part.codigo)
|
||||
if criterio:
|
||||
totais_por_codigo[part.codigo]["base"] += criterio.base
|
||||
totais_por_codigo[part.codigo]["qtd"] += 1
|
||||
|
||||
for orient in orientacoes:
|
||||
criterio = get_criterio(orient.codigo)
|
||||
if criterio:
|
||||
totais_por_codigo[orient.codigo]["base"] += criterio.base
|
||||
totais_por_codigo[orient.codigo]["qtd"] += 1
|
||||
|
||||
for mb in membros_banca:
|
||||
criterio = get_criterio(mb.codigo)
|
||||
if criterio:
|
||||
totais_por_codigo[mb.codigo]["base"] += criterio.base
|
||||
totais_por_codigo[mb.codigo]["qtd"] += 1
|
||||
|
||||
for codigo, dados in totais_por_codigo.items():
|
||||
criterio = get_criterio(codigo)
|
||||
if not criterio:
|
||||
continue
|
||||
|
||||
total = min(dados["base"], criterio.teto)
|
||||
atuacoes.append(PontuacaoAtuacao(
|
||||
codigo=codigo,
|
||||
base=dados["base"],
|
||||
tempo=0,
|
||||
bonus=0,
|
||||
total=total,
|
||||
quantidade=dados["qtd"],
|
||||
))
|
||||
|
||||
return PontuacaoBloco(bloco="D", atuacoes=atuacoes)
|
||||
|
||||
@classmethod
|
||||
def calcular_pontuacao_completa(cls, consultor: Consultor) -> PontuacaoCompleta:
|
||||
comp_a = cls.calcular_componente_a(consultor.coordenacoes_capes)
|
||||
comp_b = cls.calcular_componente_b(consultor.coordenacoes_programas)
|
||||
comp_c = cls.calcular_componente_c(consultor.consultoria)
|
||||
comp_d = cls.calcular_componente_d(consultor.premiacoes)
|
||||
bloco_a = cls.calcular_bloco_a(consultor.coordenacoes_capes)
|
||||
bloco_c = cls.calcular_bloco_c(consultor.consultoria)
|
||||
bloco_d = cls.calcular_bloco_d(
|
||||
inscricoes=consultor.inscricoes,
|
||||
avaliacoes=consultor.avaliacoes_comissao,
|
||||
premiacoes=consultor.premiacoes,
|
||||
bolsas=consultor.bolsas_cnpq,
|
||||
participacoes=consultor.participacoes,
|
||||
orientacoes=consultor.orientacoes,
|
||||
membros_banca=consultor.membros_banca,
|
||||
)
|
||||
|
||||
return PontuacaoCompleta(
|
||||
componente_a=comp_a, componente_b=comp_b, componente_c=comp_c, componente_d=comp_d
|
||||
bloco_a=bloco_a,
|
||||
bloco_c=bloco_c,
|
||||
bloco_d=bloco_d,
|
||||
)
|
||||
|
||||
289
backend/src/domain/value_objects/criterios_pontuacao.py
Normal file
289
backend/src/domain/value_objects/criterios_pontuacao.py
Normal file
@@ -0,0 +1,289 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Bloco(Enum):
|
||||
A = "A"
|
||||
C = "C"
|
||||
D = "D"
|
||||
|
||||
|
||||
class TipoAtuacao(Enum):
|
||||
FUNCAO = "Função"
|
||||
RESULTADO = "Resultado"
|
||||
PAPEL = "Papel"
|
||||
PARTICIPACAO = "Participação"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CriterioPontuacao:
|
||||
codigo: str
|
||||
bloco: Bloco
|
||||
tipo: TipoAtuacao
|
||||
base: int
|
||||
teto: int
|
||||
pontua_tempo: bool = False
|
||||
multiplicador_tempo: int = 0
|
||||
teto_tempo: int = 0
|
||||
bonus_atualidade: int = 0
|
||||
bonus_retorno: int = 0
|
||||
bonus_continuidade_3anos: int = 0
|
||||
bonus_continuidade_5anos: int = 0
|
||||
bonus_continuidade_8anos: int = 0
|
||||
|
||||
|
||||
CRITERIOS: Dict[str, CriterioPontuacao] = {
|
||||
"CA": CriterioPontuacao(
|
||||
codigo="CA",
|
||||
bloco=Bloco.A,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=200,
|
||||
teto=450,
|
||||
pontua_tempo=True,
|
||||
multiplicador_tempo=10,
|
||||
teto_tempo=100,
|
||||
bonus_atualidade=30,
|
||||
bonus_retorno=20,
|
||||
),
|
||||
"CAJ": CriterioPontuacao(
|
||||
codigo="CAJ",
|
||||
bloco=Bloco.A,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=150,
|
||||
teto=370,
|
||||
pontua_tempo=True,
|
||||
multiplicador_tempo=8,
|
||||
teto_tempo=80,
|
||||
bonus_atualidade=20,
|
||||
bonus_retorno=20,
|
||||
),
|
||||
"CAJ_MP": CriterioPontuacao(
|
||||
codigo="CAJ_MP",
|
||||
bloco=Bloco.A,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=120,
|
||||
teto=315,
|
||||
pontua_tempo=True,
|
||||
multiplicador_tempo=6,
|
||||
teto_tempo=60,
|
||||
bonus_atualidade=15,
|
||||
bonus_retorno=20,
|
||||
),
|
||||
"CAM": CriterioPontuacao(
|
||||
codigo="CAM",
|
||||
bloco=Bloco.A,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=100,
|
||||
teto=280,
|
||||
pontua_tempo=True,
|
||||
multiplicador_tempo=5,
|
||||
teto_tempo=50,
|
||||
bonus_atualidade=10,
|
||||
bonus_retorno=20,
|
||||
),
|
||||
"PPG_COORD": CriterioPontuacao(
|
||||
codigo="PPG_COORD",
|
||||
bloco=Bloco.A,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=0,
|
||||
teto=0,
|
||||
pontua_tempo=True,
|
||||
multiplicador_tempo=0,
|
||||
teto_tempo=0,
|
||||
),
|
||||
"CONS_ATIVO": CriterioPontuacao(
|
||||
codigo="CONS_ATIVO",
|
||||
bloco=Bloco.C,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=150,
|
||||
teto=230,
|
||||
pontua_tempo=True,
|
||||
multiplicador_tempo=5,
|
||||
teto_tempo=50,
|
||||
bonus_continuidade_3anos=5,
|
||||
bonus_continuidade_5anos=10,
|
||||
bonus_continuidade_8anos=15,
|
||||
bonus_retorno=15,
|
||||
),
|
||||
"CONS_HIST": CriterioPontuacao(
|
||||
codigo="CONS_HIST",
|
||||
bloco=Bloco.C,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=100,
|
||||
teto=230,
|
||||
pontua_tempo=True,
|
||||
multiplicador_tempo=5,
|
||||
teto_tempo=50,
|
||||
),
|
||||
"CONS_FALECIDO": CriterioPontuacao(
|
||||
codigo="CONS_FALECIDO",
|
||||
bloco=Bloco.C,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=100,
|
||||
teto=230,
|
||||
pontua_tempo=False,
|
||||
),
|
||||
"INSC_AUTOR": CriterioPontuacao(
|
||||
codigo="INSC_AUTOR",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PAPEL,
|
||||
base=10,
|
||||
teto=20,
|
||||
),
|
||||
"INSC_INST": CriterioPontuacao(
|
||||
codigo="INSC_INST",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PAPEL,
|
||||
base=30,
|
||||
teto=60,
|
||||
),
|
||||
"AVAL_COMIS_PREMIO": CriterioPontuacao(
|
||||
codigo="AVAL_COMIS_PREMIO",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=30,
|
||||
teto=60,
|
||||
),
|
||||
"AVAL_COMIS_GP": CriterioPontuacao(
|
||||
codigo="AVAL_COMIS_GP",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=50,
|
||||
teto=100,
|
||||
),
|
||||
"COORD_COMIS_PREMIO": CriterioPontuacao(
|
||||
codigo="COORD_COMIS_PREMIO",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=50,
|
||||
teto=100,
|
||||
),
|
||||
"COORD_COMIS_GP": CriterioPontuacao(
|
||||
codigo="COORD_COMIS_GP",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.FUNCAO,
|
||||
base=60,
|
||||
teto=120,
|
||||
),
|
||||
"BOL_BPQ_SUPERIOR": CriterioPontuacao(
|
||||
codigo="BOL_BPQ_SUPERIOR",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.RESULTADO,
|
||||
base=30,
|
||||
teto=60,
|
||||
),
|
||||
"BOL_BPQ_INTERMEDIARIO": CriterioPontuacao(
|
||||
codigo="BOL_BPQ_INTERMEDIARIO",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.RESULTADO,
|
||||
base=50,
|
||||
teto=100,
|
||||
),
|
||||
"PREMIACAO": CriterioPontuacao(
|
||||
codigo="PREMIACAO",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.RESULTADO,
|
||||
base=150,
|
||||
teto=180,
|
||||
),
|
||||
"PREMIACAO_GP": CriterioPontuacao(
|
||||
codigo="PREMIACAO_GP",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.RESULTADO,
|
||||
base=30,
|
||||
teto=60,
|
||||
),
|
||||
"MENCAO": CriterioPontuacao(
|
||||
codigo="MENCAO",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.RESULTADO,
|
||||
base=10,
|
||||
teto=20,
|
||||
),
|
||||
"EVENTO": CriterioPontuacao(
|
||||
codigo="EVENTO",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=1,
|
||||
teto=5,
|
||||
),
|
||||
"PROJ": CriterioPontuacao(
|
||||
codigo="PROJ",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=10,
|
||||
teto=40,
|
||||
),
|
||||
"ORIENT_POS_DOC": CriterioPontuacao(
|
||||
codigo="ORIENT_POS_DOC",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=15,
|
||||
teto=100,
|
||||
),
|
||||
"ORIENT_TESE": CriterioPontuacao(
|
||||
codigo="ORIENT_TESE",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=10,
|
||||
teto=50,
|
||||
),
|
||||
"ORIENT_DISS": CriterioPontuacao(
|
||||
codigo="ORIENT_DISS",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=5,
|
||||
teto=25,
|
||||
),
|
||||
"CO_ORIENT_POS_DOC": CriterioPontuacao(
|
||||
codigo="CO_ORIENT_POS_DOC",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=7,
|
||||
teto=35,
|
||||
),
|
||||
"CO_ORIENT_TESE": CriterioPontuacao(
|
||||
codigo="CO_ORIENT_TESE",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=5,
|
||||
teto=25,
|
||||
),
|
||||
"CO_ORIENT_DISS": CriterioPontuacao(
|
||||
codigo="CO_ORIENT_DISS",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=3,
|
||||
teto=15,
|
||||
),
|
||||
"MB_BANCA_POS_DOC": CriterioPontuacao(
|
||||
codigo="MB_BANCA_POS_DOC",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=3,
|
||||
teto=15,
|
||||
),
|
||||
"MB_BANCA_TESE": CriterioPontuacao(
|
||||
codigo="MB_BANCA_TESE",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=3,
|
||||
teto=15,
|
||||
),
|
||||
"MB_BANCA_DISS": CriterioPontuacao(
|
||||
codigo="MB_BANCA_DISS",
|
||||
bloco=Bloco.D,
|
||||
tipo=TipoAtuacao.PARTICIPACAO,
|
||||
base=2,
|
||||
teto=10,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def get_criterio(codigo: str) -> Optional[CriterioPontuacao]:
|
||||
return CRITERIOS.get(codigo)
|
||||
|
||||
|
||||
def get_criterios_bloco(bloco: Bloco) -> Dict[str, CriterioPontuacao]:
|
||||
return {k: v for k, v in CRITERIOS.items() if v.bloco == bloco}
|
||||
@@ -1,71 +1,62 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ComponentePontuacao:
|
||||
class PontuacaoAtuacao:
|
||||
codigo: str
|
||||
base: int
|
||||
tempo: int
|
||||
extras: int
|
||||
bonus: int
|
||||
retorno: int = 0
|
||||
teto: int = 0
|
||||
total: int
|
||||
quantidade: int = 1
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PontuacaoBloco:
|
||||
bloco: str
|
||||
atuacoes: List[PontuacaoAtuacao] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def total(self) -> int:
|
||||
soma = self.base + self.tempo + self.extras + self.bonus + self.retorno
|
||||
if self.teto > 0:
|
||||
return min(soma, self.teto)
|
||||
return soma
|
||||
return sum(a.total for a in self.atuacoes)
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
return {
|
||||
"bloco": self.bloco,
|
||||
"total": self.total,
|
||||
"atuacoes": [
|
||||
{
|
||||
"codigo": a.codigo,
|
||||
"base": a.base,
|
||||
"tempo": a.tempo,
|
||||
"bonus": a.bonus,
|
||||
"total": a.total,
|
||||
"quantidade": a.quantidade,
|
||||
}
|
||||
for a in self.atuacoes
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PontuacaoCompleta:
|
||||
componente_a: ComponentePontuacao
|
||||
componente_b: ComponentePontuacao
|
||||
componente_c: ComponentePontuacao
|
||||
componente_d: ComponentePontuacao
|
||||
bloco_a: PontuacaoBloco
|
||||
bloco_c: PontuacaoBloco
|
||||
bloco_d: PontuacaoBloco
|
||||
|
||||
@property
|
||||
def total(self) -> int:
|
||||
return (
|
||||
self.componente_a.total
|
||||
+ self.componente_b.total
|
||||
+ self.componente_c.total
|
||||
+ self.componente_d.total
|
||||
)
|
||||
return self.bloco_a.total + self.bloco_c.total + self.bloco_d.total
|
||||
|
||||
@property
|
||||
def detalhamento(self) -> Dict[str, Dict[str, int]]:
|
||||
def to_dict(self) -> Dict:
|
||||
return {
|
||||
"componente_a": {
|
||||
"base": self.componente_a.base,
|
||||
"tempo": self.componente_a.tempo,
|
||||
"extras": self.componente_a.extras,
|
||||
"bonus": self.componente_a.bonus,
|
||||
"retorno": self.componente_a.retorno,
|
||||
"total": self.componente_a.total,
|
||||
},
|
||||
"componente_b": {
|
||||
"base": self.componente_b.base,
|
||||
"tempo": self.componente_b.tempo,
|
||||
"extras": self.componente_b.extras,
|
||||
"bonus": self.componente_b.bonus,
|
||||
"total": self.componente_b.total,
|
||||
},
|
||||
"componente_c": {
|
||||
"base": self.componente_c.base,
|
||||
"tempo": self.componente_c.tempo,
|
||||
"extras": self.componente_c.extras,
|
||||
"bonus": self.componente_c.bonus,
|
||||
"total": self.componente_c.total,
|
||||
},
|
||||
"componente_d": {
|
||||
"base": self.componente_d.base,
|
||||
"tempo": self.componente_d.tempo,
|
||||
"extras": self.componente_d.extras,
|
||||
"bonus": self.componente_d.bonus,
|
||||
"total": self.componente_d.total,
|
||||
},
|
||||
"bloco_a": self.bloco_a.to_dict(),
|
||||
"bloco_c": self.bloco_c.to_dict(),
|
||||
"bloco_d": self.bloco_d.to_dict(),
|
||||
"pontuacao_total": self.total,
|
||||
}
|
||||
|
||||
@property
|
||||
def detalhamento(self) -> Dict:
|
||||
return self.to_dict()
|
||||
|
||||
Reference in New Issue
Block a user