From e7b34e33a8f5c0df06bf0abd53ea1bdca922af47 Mon Sep 17 00:00:00 2001 From: Frederico Castro Date: Tue, 9 Dec 2025 19:57:35 -0300 Subject: [PATCH] =?UTF-8?q?fix:=20Corre=C3=A7=C3=B5es=20no=20c=C3=A1lculo?= =?UTF-8?q?=20de=20pontua=C3=A7=C3=A3o=20e=20extra=C3=A7=C3=A3o=20de=20dad?= =?UTF-8?q?os?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refatora calculador para agrupar coordenações por tipo com hierarquia - Corrige contagem de eventos separando SAE de consultorias - Melhora extração de área de avaliação usando dadosConsultoria - Ajusta pontuação de premiações conforme regras documentadas - Usa alias ES em vez de índice específico --- .../domain/services/calculador_pontuacao.py | 39 ++++++-- .../repositories/consultor_repository_impl.py | 92 ++++++++++++++----- backend/src/interface/api/config.py | 3 +- 3 files changed, 98 insertions(+), 36 deletions(-) diff --git a/backend/src/domain/services/calculador_pontuacao.py b/backend/src/domain/services/calculador_pontuacao.py index c6f2226..7be6e68 100644 --- a/backend/src/domain/services/calculador_pontuacao.py +++ b/backend/src/domain/services/calculador_pontuacao.py @@ -17,21 +17,40 @@ class CalculadorPontuacao: if not coordenacoes: return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0, retorno=0) - coord_atual = next((c for c in coordenacoes if c.periodo.ativo), None) - if not coord_atual: - return ComponentePontuacao(base=0, tempo=0, extras=0, bonus=0, retorno=0) - 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} - base = base_map.get(coord_atual.tipo, 0) - anos = coord_atual.periodo.anos_decorridos - tempo = min(int(anos * 10), tempo_max_map.get(coord_atual.tipo, 0)) + # 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} + for c in coordenacoes: + coord_por_tipo.setdefault(c.tipo, []).append(c) - extras = min(len(coord_atual.areas_adicionais) * 20, 100) - bonus = bonus_atual_map.get(coord_atual.tipo, 0) if coord_atual.periodo.ativo else 0 - retorno = 20 if coord_atual.ja_coordenou_antes else 0 + 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, []) + anos_total = sum(c.periodo.anos_decorridos for c in coord_do_tipo) + ativo = any(c.periodo.ativo for c in coord_do_tipo) + + 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_adicionais = [a for c in coord_do_tipo for a in c.areas_adicionais] + if areas_adicionais: + extras = min(len(set(areas_adicionais)) * 20, 100) + + bonus = bonus_atual_map.get(coord_escolhida_tipo, 0) if ativo else 0 + retorno = 20 if len(coord_do_tipo) > 1 else 0 return ComponentePontuacao(base=base, tempo=tempo, extras=extras, bonus=bonus, retorno=retorno) diff --git a/backend/src/infrastructure/repositories/consultor_repository_impl.py b/backend/src/infrastructure/repositories/consultor_repository_impl.py index ee22981..e3be4ec 100644 --- a/backend/src/infrastructure/repositories/consultor_repository_impl.py +++ b/backend/src/infrastructure/repositories/consultor_repository_impl.py @@ -63,33 +63,51 @@ class ConsultorRepositoryImpl(ConsultorRepository): if not consultorias: return None - datas_inicio = [ + datas_inicio_consultoria = [ self._parse_date(c.get("inicio")) for c in consultorias ] - datas_inicio = [d for d in datas_inicio if d] + datas_inicio_consultoria = [d for d in datas_inicio_consultoria if d] - datas_fim = [ - self._parse_date(c.get("fim")) - for c in consultorias - ] - datas_fim = [d for d in datas_fim if d] - - if not datas_inicio: + if not datas_inicio_consultoria: return None - limite_recente = datetime.now() - timedelta(days=730) - eventos_recentes = sum(1 for d in datas_fim if d >= limite_recente) + eventos_sae = [ + a for a in atuacoes if a.get("tipo") == "Evento" + ] - areas = list({c.get("areaAvaliacao", "N/A") for c in consultorias if c.get("areaAvaliacao")}) + total_eventos = len(eventos_sae) + + # considerar últimos 24 meses como janela de atividade + limite_recente = datetime.now() - timedelta(days=730) + eventos_recentes = 0 + for ev in eventos_sae: + data_fim = self._parse_date(ev.get("fim")) or self._parse_date(ev.get("inicio")) + if data_fim and data_fim >= limite_recente: + eventos_recentes += 1 + + dados_consultoria = consultorias[0].get("dadosConsultoria", {}) or {} + areas = [] + for c in consultorias: + dc = c.get("dadosConsultoria", {}) or {} + area = dc.get("areaAvaliacao") or c.get("areaAvaliacao") + if area: + areas.append(area) + areas = list(set(areas)) if areas else ["N/A"] vezes_responsavel = sum(1 for c in consultorias if c.get("responsavel", False)) + datas_fim_consultoria = [ + self._parse_date(c.get("fim")) + for c in consultorias + ] + datas_fim_consultoria = [d for d in datas_fim_consultoria if d] + return Consultoria( - total_eventos=len(consultorias), + total_eventos=total_eventos, eventos_recentes=eventos_recentes, - primeiro_evento=min(datas_inicio), - ultimo_evento=max(datas_fim) if datas_fim else datetime.now(), + primeiro_evento=min(datas_inicio_consultoria), + ultimo_evento=max(datas_fim_consultoria) if datas_fim_consultoria else datetime.now(), vezes_responsavel=vezes_responsavel, areas=areas, ) @@ -116,25 +134,46 @@ class ConsultorRepositoryImpl(ConsultorRepository): tipo = self._inferir_tipo_coordenacao(coord) fim = self._parse_date(coord.get("fim")) + dados_coord = coord.get("dadosCoordenacaoArea", {}) or {} + area_avaliacao_obj = dados_coord.get("areaAvaliacao", {}) or {} + area_avaliacao = area_avaliacao_obj.get("nome") if isinstance(area_avaliacao_obj, dict) else coord.get("areaAvaliacao", "N/A") + if not area_avaliacao: + area_avaliacao = coord.get("descricao", "N/A").split(" - ")[0] if coord.get("descricao") else "N/A" + resultado.append( CoordenacaoCapes( tipo=tipo, - area_avaliacao=coord.get("areaAvaliacao", "N/A"), + area_avaliacao=area_avaliacao, periodo=Periodo(inicio=inicio, fim=fim), areas_adicionais=[], - ja_coordenou_antes=False, + ja_coordenou_antes=len(resultado) > 0, ) ) return resultado def _inferir_tipo_coordenacao(self, coord: Dict[str, Any]) -> str: - nome = coord.get("nome", "").lower() - if "câmara" in nome or "camara" in nome: + dados_coord = coord.get("dadosCoordenacaoArea", {}) or {} + tipo_coord = dados_coord.get("tipo", "").lower() + + if "câmara" in tipo_coord or "camara" in tipo_coord: return "CAM" - elif "mestrado profissional" in nome: + elif "adjunt" in tipo_coord: + if "profissional" in tipo_coord or "mestrado" in tipo_coord: + return "CAJ-MP" + return "CAJ" + elif "coordenador de área" in tipo_coord: + return "CA" + + descricao = coord.get("descricao", "").lower() + nome = coord.get("nome", "").lower() + texto = f"{descricao} {nome}" + + if "câmara" in texto or "camara" in texto: + return "CAM" + elif "mestrado profissional" in texto: return "CAJ-MP" - elif "adjunta" in nome: + elif "adjunt" in texto: return "CAJ" else: return "CA" @@ -148,6 +187,7 @@ class ConsultorRepositoryImpl(ConsultorRepository): "Premiação Prêmio", "Avaliação Prêmio", "Inscrição Prêmio", + "Premiação", ] ] @@ -169,10 +209,12 @@ class ConsultorRepositoryImpl(ConsultorRepository): return premiacoes def _calcular_pontos_premiacao(self, tipo: str) -> int: + # Aproximação das regras (D) seguindo .claude/rules/ranking-consultores-capes.md mapa = { - "Premiação Prêmio": 60, + "Premiação Prêmio": 150, + "Premiação": 150, "Avaliação Prêmio": 40, - "Inscrição Prêmio": 20, + "Inscrição Prêmio": 10, } return mapa.get(tipo, 0) @@ -186,9 +228,9 @@ class ConsultorRepositoryImpl(ConsultorRepository): premiacoes = self._extrair_premiacoes(atuacoes) coordenacoes_programas_raw = [] - if self.oracle_client.is_connected: + if self.oracle_client and self.oracle_client.is_connected: try: - coordenacoes_programas_raw = self.oracle_client.buscar_coordenacoes_programa(id_pessoa) + coordenacoes_programas_raw = self.oracle_client.buscar_coordenacoes_programa(int(id_pessoa)) except Exception as e: print(f"AVISO Oracle: erro ao buscar coordenacoes do programa para {id_pessoa}: {e}") coordenacoes_programas = [ diff --git a/backend/src/interface/api/config.py b/backend/src/interface/api/config.py index ce81a45..a1b1f74 100644 --- a/backend/src/interface/api/config.py +++ b/backend/src/interface/api/config.py @@ -5,8 +5,9 @@ from typing import List class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") + # Preferir o alias apontado para o índice vigente do Atuacapes ES_URL: str = "http://localhost:9200" - ES_INDEX: str = "atuacapes__1763197236" + ES_INDEX: str = "atuacapes" ES_USER: str = "" ES_PASSWORD: str = ""