Skip to main content

cobre_io/config/
estimation.rs

1//! Time series estimation configuration types for `config.json → estimation`.
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5/// Order selection criterion for autoregressive model fitting.
6///
7/// Controls how the lag order is chosen when fitting a time series model.
8/// Two variants are accepted:
9///
10/// - `"pacf"` — classical periodic Yule-Walker with PACF-based order
11///   selection. Default.
12/// - `"pacf_annual"` — extends `"pacf"` with an annual component (PAR(p)-A),
13///   adding one extra coefficient ψ per (entity, season) that multiplies
14///   the rolling 12-month average of past observations.
15#[derive(Debug, Clone, Serialize, Default)]
16#[serde(rename_all = "snake_case")]
17#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
18pub enum OrderSelectionMethod {
19    /// Periodic Yule-Walker partial autocorrelation method (PACF).
20    #[default]
21    Pacf,
22    /// Periodic Yule-Walker order selection augmented with an annual component.
23    ///
24    /// When selected, the estimation pipeline performs four steps beyond the
25    /// classical [`Self::Pacf`] path:
26    ///
27    /// 1. **Extended Yule-Walker fitting** — the system is augmented with a
28    ///    cross-correlation term between the current-season inflow and the
29    ///    rolling 12-month average, yielding the annual coefficient ψ
30    ///    alongside the classical AR coefficients.
31    /// 2. **Annual-stats computation** — per-season sample mean μ^A and
32    ///    Bessel-corrected standard deviation σ^A of the rolling 12-month
33    ///    average are computed for each hydro plant.
34    /// 3. **Parquet emission** — the triple (ψ, μ^A, σ^A) is written to
35    ///    `inflow_annual_component.parquet` in the output directory.
36    /// 4. **Widened LP lag stride** — the noise-column layout in the LP is
37    ///    extended to accommodate the annual term alongside the classical lags.
38    PacfAnnual,
39}
40
41impl<'de> serde::Deserialize<'de> for OrderSelectionMethod {
42    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
43        let s = String::deserialize(deserializer)?;
44        match s.as_str() {
45            "pacf" => Ok(Self::Pacf),
46            "pacf_annual" => Ok(Self::PacfAnnual),
47            other => Err(serde::de::Error::unknown_variant(
48                other,
49                &["pacf", "pacf_annual"],
50            )),
51        }
52    }
53}
54
55/// Time series estimation settings (`config.json → estimation`).
56///
57/// Controls automatic parameter estimation when historical inflow data is
58/// provided without explicit model statistics or coefficients.
59#[derive(Debug, Clone, Deserialize, Serialize)]
60#[serde(default, deny_unknown_fields)]
61#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
62pub struct EstimationConfig {
63    /// Maximum lag order considered during autoregressive model fitting.
64    pub max_order: u32,
65
66    /// Order selection criterion. Accepts `"pacf"` (classical PACF, default)
67    /// or `"pacf_annual"` (PACF augmented with an annual component, PAR(p)-A).
68    pub order_selection: OrderSelectionMethod,
69
70    /// Minimum number of observations required per (entity, season) group
71    /// to proceed with estimation. Groups below this threshold are skipped.
72    pub min_observations_per_season: u32,
73
74    /// Maximum allowed absolute magnitude for any AR coefficient.
75    ///
76    /// When set, any (entity, season) pair with `|coefficient| > threshold`
77    /// is immediately reduced to order 0 before the contribution analysis
78    /// runs. This acts as a fast-path safety net for the most extreme
79    /// explosive models. Defaults to `None` (disabled; contribution analysis
80    /// is the primary guard).
81    #[serde(default)]
82    pub max_coefficient_magnitude: Option<f64>,
83}
84
85impl Default for EstimationConfig {
86    fn default() -> Self {
87        Self {
88            max_order: 6,
89            order_selection: OrderSelectionMethod::Pacf,
90            min_observations_per_season: 30,
91            max_coefficient_magnitude: None,
92        }
93    }
94}