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:
Frederico Castro
2025-12-13 16:41:55 -03:00
parent 97cd328415
commit 2d4e93f82a
15 changed files with 1517 additions and 1001 deletions

View File

@@ -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

View File

@@ -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,
)

View 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}

View File

@@ -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()