File size: 3,571 Bytes
c40c447
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""
Modelo de dominio para configuraci贸n de forecasting.

Este m贸dulo define la entidad ForecastConfig, cumpliendo con SRP.
"""

from dataclasses import dataclass, field
from typing import List


@dataclass
class ForecastConfig:
    """
    Configuraci贸n para operaciones de forecasting.
    
    Define los par谩metros necesarios para realizar un pron贸stico,
    incluyendo horizonte de predicci贸n, cuantiles y frecuencia.
    
    Attributes:
        prediction_length: N煤mero de per铆odos a pronosticar
        quantile_levels: Cuantiles a calcular (ej: [0.1, 0.5, 0.9])
        freq: Frecuencia temporal (D, H, M, etc.)
    
    Example:
        >>> config = ForecastConfig(
        ...     prediction_length=7,
        ...     quantile_levels=[0.1, 0.5, 0.9],
        ...     freq="D"
        ... )
        >>> config.has_median
        True
    """
    
    prediction_length: int
    quantile_levels: List[float] = field(default_factory=lambda: [0.1, 0.5, 0.9])
    freq: str = "D"
    
    def __post_init__(self):
        """Validaci贸n y normalizaci贸n autom谩tica"""
        self.validate()
        self._ensure_median()
        self._sort_quantiles()
    
    @property
    def has_median(self) -> bool:
        """Verifica si el cuantil 0.5 (mediana) est谩 incluido"""
        return 0.5 in self.quantile_levels
    
    def validate(self) -> bool:
        """
        Valida la configuraci贸n.
        
        Returns:
            bool: True si es v谩lida
        
        Raises:
            ValueError: Si la configuraci贸n es inv谩lida
        """
        # Validar prediction_length
        if self.prediction_length < 1:
            raise ValueError(
                f"prediction_length debe ser >= 1, recibido: {self.prediction_length}"
            )
        
        # Validar quantile_levels
        if not self.quantile_levels:
            raise ValueError("quantile_levels no puede estar vac铆o")
        
        # Verificar que los cuantiles est茅n en [0, 1]
        for q in self.quantile_levels:
            if not 0 <= q <= 1:
                raise ValueError(
                    f"Todos los cuantiles deben estar en [0, 1], encontrado: {q}"
                )
        
        # Validar freq
        valid_freqs = {"D", "H", "M", "W", "Y", "Q", "S", "T", "min"}
        if self.freq not in valid_freqs:
            raise ValueError(
                f"Frecuencia '{self.freq}' no reconocida. "
                f"V谩lidas: {valid_freqs}"
            )
        
        return True
    
    def _ensure_median(self):
        """Asegura que la mediana (0.5) est茅 incluida"""
        if not self.has_median:
            self.quantile_levels.append(0.5)
    
    def _sort_quantiles(self):
        """Ordena los cuantiles de menor a mayor"""
        self.quantile_levels = sorted(set(self.quantile_levels))
    
    @classmethod
    def default(cls) -> "ForecastConfig":
        """
        Crea una configuraci贸n con valores por defecto.
        
        Returns:
            ForecastConfig: Configuraci贸n por defecto
                - prediction_length: 7
                - quantile_levels: [0.1, 0.5, 0.9]
                - freq: "D"
        """
        return cls(
            prediction_length=7,
            quantile_levels=[0.1, 0.5, 0.9],
            freq="D"
        )
    
    def to_dict(self) -> dict:
        """Serializa la configuraci贸n a diccionario"""
        return {
            "prediction_length": self.prediction_length,
            "quantile_levels": self.quantile_levels,
            "freq": self.freq
        }