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)