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}