import pytest from datetime import datetime from dateutil.relativedelta import relativedelta from src.domain.services.calculador_pontuacao import CalculadorPontuacao from src.domain.entities.consultor import ( Consultor, CoordenacaoCapes, Consultoria, Inscricao, AvaliacaoComissao, Premiacao, BolsaCNPQ, Participacao, ) from src.domain.value_objects.periodo import Periodo from src.domain.value_objects.criterios_pontuacao import CRITERIOS def criar_periodo(anos_atras: int, ativo: bool = False, duracao_anos: int = 0) -> Periodo: inicio = datetime.now() - relativedelta(years=anos_atras) if ativo: fim = None else: fim = inicio + relativedelta(years=duracao_anos) if duracao_anos > 0 else datetime.now() return Periodo(inicio=inicio, fim=fim) class TestBlocoACoordenacaoCapes: def test_coordenacao_vazia_retorna_bloco_vazio(self): resultado = CalculadorPontuacao.calcular_bloco_a([]) assert resultado.bloco == "A" assert resultado.total == 0 assert len(resultado.atuacoes) == 0 def test_coordenador_area_base_200_pontos(self): coord = CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="CIÊNCIAS AMBIENTAIS", periodo=criar_periodo(anos_atras=0, duracao_anos=0), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) assert resultado.total >= 200 def test_coordenador_area_ativo_recebe_bonus_atualidade(self): coord = CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="CIÊNCIAS AMBIENTAIS", periodo=criar_periodo(anos_atras=1, ativo=True), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) atuacao = resultado.atuacoes[0] assert atuacao.bonus >= 30 def test_coordenador_area_historico_sem_bonus_atualidade(self): coord = CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="MEDICINA I", periodo=criar_periodo(anos_atras=5, duracao_anos=3), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) atuacao = resultado.atuacoes[0] assert atuacao.codigo == "CA" assert 30 not in [atuacao.bonus] or atuacao.bonus < 30 def test_coordenador_area_5_anos_pontos_tempo(self): coord = CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="CIÊNCIAS AMBIENTAIS", periodo=criar_periodo(anos_atras=5, duracao_anos=5), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) atuacao = resultado.atuacoes[0] assert atuacao.tempo == 50 def test_coordenador_area_teto_tempo_100_pontos(self): coord = CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="CIÊNCIAS AMBIENTAIS", periodo=criar_periodo(anos_atras=15, duracao_anos=15), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) atuacao = resultado.atuacoes[0] assert atuacao.tempo == 100 def test_coordenador_area_teto_maximo_450(self): coord = CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="CIÊNCIAS AMBIENTAIS", periodo=criar_periodo(anos_atras=20, ativo=True), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) atuacao = resultado.atuacoes[0] assert atuacao.total <= 450 def test_coordenador_adjunto_base_150_pontos(self): coord = CoordenacaoCapes( codigo="CAJ", tipo="Coordenador Adjunto", area_avaliacao="ENGENHARIA I", periodo=criar_periodo(anos_atras=0, duracao_anos=0), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) assert resultado.total >= 150 def test_coordenador_adjunto_mp_base_120_pontos(self): coord = CoordenacaoCapes( codigo="CAJ_MP", tipo="Coordenador Adjunto de Mestrado Profissionalizante", area_avaliacao="ADMINISTRAÇÃO", periodo=criar_periodo(anos_atras=0, duracao_anos=0), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) assert resultado.total >= 120 def test_camara_tematica_base_100_pontos(self): coord = CoordenacaoCapes( codigo="CAM", tipo="Câmara Temática", area_avaliacao="INTERDISCIPLINAR", periodo=criar_periodo(anos_atras=0, duracao_anos=0), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) assert resultado.total >= 100 def test_multiplas_coordenacoes_mesmo_tipo_soma_tempo(self): periodo1 = criar_periodo(anos_atras=10, duracao_anos=3) periodo2 = criar_periodo(anos_atras=5, duracao_anos=3) coords = [ CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="ÁREA 1", periodo=periodo1 ), CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="ÁREA 2", periodo=periodo2 ), ] resultado = CalculadorPontuacao.calcular_bloco_a(coords) assert len(resultado.atuacoes) == 1 atuacao = resultado.atuacoes[0] assert atuacao.quantidade == 2 assert atuacao.tempo == 60 def test_retorno_coordenacao_gera_bonus(self): periodo1 = criar_periodo(anos_atras=10, duracao_anos=3) periodo2 = criar_periodo(anos_atras=3, ativo=True) coords = [ CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="ÁREA 1", periodo=periodo1 ), CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="ÁREA 1", periodo=periodo2 ), ] resultado = CalculadorPontuacao.calcular_bloco_a(coords) atuacao = resultado.atuacoes[0] assert atuacao.bonus >= 20 def test_tipos_diferentes_geram_atuacoes_separadas(self): coords = [ CoordenacaoCapes( codigo="CA", tipo="Coordenador de Área", area_avaliacao="ÁREA 1", periodo=criar_periodo(2, duracao_anos=2) ), CoordenacaoCapes( codigo="CAJ", tipo="Coordenador Adjunto", area_avaliacao="ÁREA 1", periodo=criar_periodo(2, duracao_anos=2) ), ] resultado = CalculadorPontuacao.calcular_bloco_a(coords) assert len(resultado.atuacoes) == 2 def test_codigo_com_hifen_normalizado(self): coord = CoordenacaoCapes( codigo="CAJ-MP", tipo="Coordenador Adjunto de Mestrado Profissionalizante", area_avaliacao="ADMINISTRAÇÃO", periodo=criar_periodo(anos_atras=2, duracao_anos=2), ) resultado = CalculadorPontuacao.calcular_bloco_a([coord]) assert len(resultado.atuacoes) == 1 assert resultado.atuacoes[0].codigo == "CAJ_MP" class TestBlocoBConsultoria: def test_consultoria_vazia_retorna_bloco_vazio(self): resultado = CalculadorPontuacao.calcular_bloco_b(None) assert resultado.bloco == "B" assert resultado.total == 0 def test_consultor_ativo_base_150_pontos(self): consultoria = Consultoria( codigo="CONS_ATIVO", situacao="Atividade Contínua", periodo=criar_periodo(anos_atras=1, ativo=True), anos_consecutivos=1, retornos=0, ) resultado = CalculadorPontuacao.calcular_bloco_b(consultoria) assert resultado.total >= 150 def test_consultor_historico_base_100_pontos(self): consultoria = Consultoria( codigo="CONS_HIST", situacao="Inativo", periodo=criar_periodo(anos_atras=5, duracao_anos=3), anos_consecutivos=3, retornos=0, ) resultado = CalculadorPontuacao.calcular_bloco_b(consultoria) assert resultado.total >= 100 def test_consultor_falecido_base_100_pontos(self): consultoria = Consultoria( codigo="CONS_FALECIDO", situacao="Falecido", periodo=criar_periodo(anos_atras=10, duracao_anos=8), anos_consecutivos=8, retornos=0, ) resultado = CalculadorPontuacao.calcular_bloco_b(consultoria) assert resultado.total >= 100 def test_consultoria_5_anos_pontos_tempo(self): consultoria = Consultoria( codigo="CONS_ATIVO", situacao="Atividade Contínua", periodo=criar_periodo(anos_atras=5, ativo=True), periodos=[criar_periodo(anos_atras=5, ativo=True)], anos_consecutivos=5, retornos=0, ) resultado = CalculadorPontuacao.calcular_bloco_b(consultoria) atuacao = resultado.atuacoes[0] assert atuacao.tempo == 25 def test_consultoria_teto_tempo_50_pontos(self): consultoria = Consultoria( codigo="CONS_ATIVO", situacao="Atividade Contínua", periodo=criar_periodo(anos_atras=15, ativo=True), periodos=[criar_periodo(anos_atras=15, ativo=True)], anos_consecutivos=15, retornos=0, ) resultado = CalculadorPontuacao.calcular_bloco_b(consultoria) atuacao = resultado.atuacoes[0] assert atuacao.tempo == 50 def test_consultor_ativo_bonus_atualidade_20(self): consultoria = Consultoria( codigo="CONS_ATIVO", situacao="Atividade Contínua", periodo=criar_periodo(anos_atras=1, ativo=True), anos_consecutivos=1, retornos=0, ) resultado = CalculadorPontuacao.calcular_bloco_b(consultoria) atuacao = resultado.atuacoes[0] assert atuacao.bonus >= 20 def test_consultor_8_anos_bonus_continuidade(self): consultoria = Consultoria( codigo="CONS_ATIVO", situacao="Atividade Contínua", periodo=criar_periodo(anos_atras=8, ativo=True), anos_consecutivos=8, retornos=0, ) resultado = CalculadorPontuacao.calcular_bloco_b(consultoria) atuacao = resultado.atuacoes[0] assert atuacao.bonus >= 40 def test_consultor_com_retorno_bonus_15(self): consultoria = Consultoria( codigo="CONS_ATIVO", situacao="Atividade Contínua", periodo=criar_periodo(anos_atras=2, ativo=True), periodos=[ criar_periodo(anos_atras=8, duracao_anos=3), criar_periodo(anos_atras=2, ativo=True), ], anos_consecutivos=2, retornos=1, ) resultado = CalculadorPontuacao.calcular_bloco_b(consultoria) atuacao = resultado.atuacoes[0] assert atuacao.bonus >= 35 def test_consultoria_teto_maximo_230(self): consultoria = Consultoria( codigo="CONS_ATIVO", situacao="Atividade Contínua", periodo=criar_periodo(anos_atras=20, ativo=True), periodos=[criar_periodo(anos_atras=20, ativo=True)], anos_consecutivos=20, retornos=1, ) resultado = CalculadorPontuacao.calcular_bloco_b(consultoria) atuacao = resultado.atuacoes[0] assert atuacao.total <= 230 class TestBlocoCPremiacoesAvaliacoes: def test_bloco_c_vazio(self): resultado = CalculadorPontuacao.calcular_bloco_c([], [], [], [], []) assert resultado.bloco == "C" assert resultado.total == 0 def test_inscricao_autor_base_10_pontos(self): inscricao = Inscricao( codigo="INSC_AUTOR", tipo="Autor", premio="PCT", ano=2024 ) resultado = CalculadorPontuacao.calcular_bloco_c([inscricao], [], [], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "INSC_AUTOR"), None) assert atuacao is not None assert atuacao.base >= 10 def test_inscricao_institucional_base_20_pontos(self): inscricao = Inscricao( codigo="INSC_INST_AUTOR", tipo="Institucional", premio="PCT", ano=2024 ) resultado = CalculadorPontuacao.calcular_bloco_c([inscricao], [], [], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "INSC_INST_AUTOR"), None) assert atuacao is not None assert atuacao.base >= 20 def test_avaliacao_comissao_premio_base_30(self): avaliacao = AvaliacaoComissao( codigo="AVAL_COMIS_PREMIO", tipo="Membro de Comissão", premio="PCT", ano=2024 ) resultado = CalculadorPontuacao.calcular_bloco_c([], [avaliacao], [], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "AVAL_COMIS_PREMIO"), None) assert atuacao is not None assert atuacao.base >= 30 def test_avaliacao_comissao_gp_base_40(self): avaliacao = AvaliacaoComissao( codigo="AVAL_COMIS_GP", tipo="Membro de Comissão", premio="Grande Prêmio", ano=2024 ) resultado = CalculadorPontuacao.calcular_bloco_c([], [avaliacao], [], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "AVAL_COMIS_GP"), None) assert atuacao is not None assert atuacao.base >= 40 def test_coord_comissao_premio_base_40(self): avaliacao = AvaliacaoComissao( codigo="COORD_COMIS_PREMIO", tipo="Coordenador", premio="PCT", ano=2024 ) resultado = CalculadorPontuacao.calcular_bloco_c([], [avaliacao], [], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "COORD_COMIS_PREMIO"), None) assert atuacao is not None assert atuacao.base >= 40 def test_coord_comissao_gp_base_50(self): avaliacao = AvaliacaoComissao( codigo="COORD_COMIS_GP", tipo="Coordenador", premio="Grande Prêmio", ano=2024 ) resultado = CalculadorPontuacao.calcular_bloco_c([], [avaliacao], [], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "COORD_COMIS_GP"), None) assert atuacao is not None assert atuacao.base >= 50 def test_premiacao_gp_autor_base_100(self): premiacao = Premiacao( codigo="PREMIACAO_GP_AUTOR", tipo="Grande Prêmio", nome_premio="Grande Prêmio CAPES", ano=2024 ) resultado = CalculadorPontuacao.calcular_bloco_c([], [], [premiacao], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "PREMIACAO_GP_AUTOR"), None) assert atuacao is not None assert atuacao.base >= 100 def test_premiacao_autor_base_50(self): premiacao = Premiacao( codigo="PREMIACAO_AUTOR", tipo="Prêmio", nome_premio="Prêmio CAPES de Tese", ano=2024 ) resultado = CalculadorPontuacao.calcular_bloco_c([], [], [premiacao], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "PREMIACAO_AUTOR"), None) assert atuacao is not None assert atuacao.base >= 50 def test_mencao_autor_base_30(self): premiacao = Premiacao( codigo="MENCAO_AUTOR", tipo="Menção Honrosa", nome_premio="Prêmio CAPES de Tese", ano=2024 ) resultado = CalculadorPontuacao.calcular_bloco_c([], [], [premiacao], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "MENCAO_AUTOR"), None) assert atuacao is not None assert atuacao.base >= 30 def test_avaliacao_recorrente_bonus_anual(self): avaliacoes = [ AvaliacaoComissao( codigo="AVAL_COMIS_PREMIO", tipo="Membro", premio="PCT", ano=2022 ), AvaliacaoComissao( codigo="AVAL_COMIS_PREMIO", tipo="Membro", premio="PCT", ano=2023 ), AvaliacaoComissao( codigo="AVAL_COMIS_PREMIO", tipo="Membro", premio="PCT", ano=2024 ), ] resultado = CalculadorPontuacao.calcular_bloco_c([], avaliacoes, [], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "AVAL_COMIS_PREMIO"), None) assert atuacao is not None assert atuacao.bonus > 0 assert atuacao.quantidade == 3 def test_inscricoes_multiplas_acumulam(self): inscricoes = [ Inscricao(codigo="INSC_AUTOR", tipo="Autor", premio="PCT", ano=2022), Inscricao(codigo="INSC_AUTOR", tipo="Autor", premio="PCT", ano=2023), ] resultado = CalculadorPontuacao.calcular_bloco_c(inscricoes, [], [], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "INSC_AUTOR"), None) assert atuacao is not None assert atuacao.quantidade == 2 assert atuacao.base == 20 def test_teto_inscricao_autor_20_pontos(self): inscricoes = [ Inscricao(codigo="INSC_AUTOR", tipo="Autor", premio="PCT", ano=i) for i in range(2015, 2025) ] resultado = CalculadorPontuacao.calcular_bloco_c(inscricoes, [], [], [], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "INSC_AUTOR"), None) assert atuacao is not None assert atuacao.total <= 20 class TestBlocoDParticipacoes: def test_bloco_d_vazio(self): resultado = CalculadorPontuacao.calcular_bloco_d([], []) assert resultado.bloco == "D" assert resultado.total == 0 def test_bolsa_cnpq_base_30(self): bolsa = BolsaCNPQ(codigo="BOL_BPQ_NIVEL", nivel="1A") resultado = CalculadorPontuacao.calcular_bloco_d([bolsa], []) atuacao = next((a for a in resultado.atuacoes if a.codigo == "BOL_BPQ_NIVEL"), None) assert atuacao is not None assert atuacao.base >= 30 def test_evento_base_1_ponto(self): evento = Participacao(codigo="EVENTO", tipo="Evento", ano=2024) resultado = CalculadorPontuacao.calcular_bloco_d([], [evento]) atuacao = next((a for a in resultado.atuacoes if a.codigo == "EVENTO"), None) assert atuacao is not None assert atuacao.base >= 1 def test_projeto_base_10_pontos(self): projeto = Participacao(codigo="PROJ", tipo="Projeto", ano=2024) resultado = CalculadorPontuacao.calcular_bloco_d([], [projeto]) atuacao = next((a for a in resultado.atuacoes if a.codigo == "PROJ"), None) assert atuacao is not None assert atuacao.base >= 10 def test_eventos_multiplos_bonus_recorrencia(self): eventos = [ Participacao(codigo="EVENTO", tipo="Evento", ano=i) for i in range(2020, 2025) ] resultado = CalculadorPontuacao.calcular_bloco_d([], eventos) atuacao = next((a for a in resultado.atuacoes if a.codigo == "EVENTO"), None) assert atuacao is not None assert atuacao.quantidade == 5 assert atuacao.bonus > 0 def test_evento_teto_5_pontos(self): eventos = [ Participacao(codigo="EVENTO", tipo="Evento", ano=i) for i in range(2000, 2025) ] resultado = CalculadorPontuacao.calcular_bloco_d([], eventos) atuacao = next((a for a in resultado.atuacoes if a.codigo == "EVENTO"), None) assert atuacao is not None assert atuacao.total <= 5 def test_projeto_teto_30_pontos(self): projetos = [ Participacao(codigo="PROJ", tipo="Projeto", ano=i) for i in range(2000, 2025) ] resultado = CalculadorPontuacao.calcular_bloco_d([], projetos) atuacao = next((a for a in resultado.atuacoes if a.codigo == "PROJ"), None) assert atuacao is not None assert atuacao.total <= 30 class TestBlocoECoordPPG: def test_bloco_e_sem_coordenador(self): resultado = CalculadorPontuacao.calcular_bloco_e(False) assert resultado.bloco == "E" assert resultado.total == 0 def test_bloco_e_com_coordenador(self): resultado = CalculadorPontuacao.calcular_bloco_e(True) assert resultado.bloco == "E" assert len(resultado.atuacoes) == 1 assert resultado.atuacoes[0].codigo == "PPG_COORD" class TestPontuacaoCompleta: def test_consultor_vazio_pontuacao_zero(self, consultor_vazio): resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor_vazio) assert resultado.total == 0 def test_consultor_com_coordenacao(self, coordenacao_ca_ativa): consultor = Consultor( id_pessoa=1, nome="Coordenador", coordenacoes_capes=[coordenacao_ca_ativa], ) resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor) assert resultado.bloco_a.total > 0 assert resultado.bloco_b.total == 0 def test_consultor_completo_todos_blocos(self, consultor_completo): resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor_completo) assert resultado.bloco_a.total > 0 assert resultado.bloco_b.total > 0 assert resultado.bloco_c.total > 0 assert resultado.bloco_d.total > 0 def test_pontuacao_total_soma_blocos(self, consultor_completo): resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor_completo) soma_esperada = ( resultado.bloco_a.total + resultado.bloco_b.total + resultado.bloco_c.total + resultado.bloco_d.total + resultado.bloco_e.total ) assert resultado.total == soma_esperada def test_detalhamento_retorna_dict(self, consultor_completo): resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor_completo) detalhes = resultado.detalhamento assert isinstance(detalhes, dict) assert "bloco_a" in detalhes assert "bloco_b" in detalhes assert "bloco_c" in detalhes assert "bloco_d" in detalhes assert "bloco_e" in detalhes assert "pontuacao_total" in detalhes class TestCriteriosConfigurados: def test_criterio_ca_existe(self): assert "CA" in CRITERIOS criterio = CRITERIOS["CA"] assert criterio.base == 200 assert criterio.teto == 450 def test_criterio_caj_existe(self): assert "CAJ" in CRITERIOS criterio = CRITERIOS["CAJ"] assert criterio.base == 150 assert criterio.teto == 370 def test_criterio_cons_ativo_existe(self): assert "CONS_ATIVO" in CRITERIOS criterio = CRITERIOS["CONS_ATIVO"] assert criterio.base == 150 assert criterio.teto == 230 def test_todos_criterios_tem_codigo_valido(self): for codigo, criterio in CRITERIOS.items(): assert criterio.codigo == codigo assert criterio.base >= 0 assert criterio.teto >= 0