Files
ranking/backend/src/domain/value_objects/periodo.py
Frederico Castro 6297d135ec fix: corrigir cálculo de anos consecutivos em períodos sobrepostos
Corrige bug onde consultores ativos com vínculos sobrepostos tinham
anos_consecutivos zerados incorretamente.

Problema: Ao mesclar períodos sobrepostos (ex: UP encerrado + UTFPR ativo),
o período mesclado era marcado como encerrado, resultando em
anos_consecutivos = 0 mesmo para consultores ativos.

Solução: Período mesclado é ativo se QUALQUER um dos períodos originais
for ativo (lógica OR em vez de considerar apenas o primeiro período).

Impacto:
- Consultores ativos com múltiplos vínculos agora recebem corretamente
  bônus de continuidade (até +20 pontos)
- Exemplo: Valdir Fernandes (#1) tinha 0 anos consecutivos, agora tem 10

Otimizações adicionais:
- Aumenta batch_size de 5.000 para 10.000 consultores
- Reduz tempo de processamento de ~60min para ~25min (58% mais rápido)
- Reduz requisições ao Elasticsearch pela metade

Arquivos alterados:
- backend/src/domain/value_objects/periodo.py: lógica de mesclagem corrigida
- backend/src/application/jobs/processar_ranking.py: batch_size otimizado

Testado com 350.222 consultores em 25min 35s
2025-12-19 14:14:57 -03:00

64 lines
2.0 KiB
Python

from dataclasses import dataclass
from datetime import datetime
from typing import List, Optional
@dataclass(frozen=True)
class Periodo:
inicio: datetime
fim: Optional[datetime] = None
@property
def ativo(self) -> bool:
return self.fim is None
@property
def anos_decorridos(self) -> float:
fim = self.fim if self.fim else datetime.now()
dias = (fim - self.inicio).days
return round(dias / 365.25, 1)
def anos_completos(self, data_referencia: Optional[datetime] = None) -> int:
"""
Retorna apenas anos completos entre início e fim (ou data de referência).
Usado para pontuação que desconsidera frações de ano.
"""
fim = self.fim or data_referencia or datetime.now()
if fim < self.inicio:
return 0
return int((fim - self.inicio).days // 365)
def __post_init__(self) -> None:
if self.fim and self.fim < self.inicio:
object.__setattr__(self, "fim", None)
def mesclar_periodos(periodos: List[Periodo]) -> List[Periodo]:
if not periodos:
return []
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 and p.inicio <= ultimo_fim:
novo_fim = max(ultimo_fim, atual_fim)
mesclados[-1] = Periodo(
inicio=ultimo.inicio,
fim=None if (ultimo.ativo or p.ativo) else novo_fim
)
else:
mesclados.append(p)
return mesclados
def anos_completos_periodos(periodos: List[Periodo], data_ref: Optional[datetime] = None) -> int:
ref = data_ref or datetime.now()
return sum(p.anos_completos(ref) for p in periodos)