bo4e_core/com/
substitution_value.rs

1//! Substitution value (Ersatzwert) component.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::enums::Unit;
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9/// A substituted/replacement value for missing or invalid measurements.
10///
11/// German: Ersatzwert
12///
13/// # Example
14///
15/// ```rust
16/// use bo4e_core::com::SubstitutionValue;
17/// use bo4e_core::enums::Unit;
18/// use chrono::Utc;
19///
20/// let value = SubstitutionValue {
21///     timestamp: Some(Utc::now()),
22///     value: Some(125.5),
23///     unit: Some(Unit::KilowattHour),
24///     substitution_method: Some("Interpolation".to_string()),
25///     ..Default::default()
26/// };
27/// ```
28#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct SubstitutionValue {
31    /// BO4E metadata
32    #[serde(flatten)]
33    pub meta: Bo4eMeta,
34
35    /// Timestamp for the substituted value (Zeitpunkt)
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub timestamp: Option<DateTime<Utc>>,
38
39    /// The substituted value (Wert)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub value: Option<f64>,
42
43    /// Unit of the value (Einheit)
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub unit: Option<Unit>,
46
47    /// Method used for substitution (Ersatzwertmethode)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub substitution_method: Option<String>,
50
51    /// Reason for substitution (Grund)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub reason: Option<String>,
54
55    /// Original value that was replaced, if available (Originalwert)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub original_value: Option<f64>,
58
59    /// OBIS code (OBIS-Kennzahl)
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub obis_code: Option<String>,
62}
63
64impl Bo4eObject for SubstitutionValue {
65    fn type_name_german() -> &'static str {
66        "Ersatzwert"
67    }
68
69    fn type_name_english() -> &'static str {
70        "SubstitutionValue"
71    }
72
73    fn meta(&self) -> &Bo4eMeta {
74        &self.meta
75    }
76
77    fn meta_mut(&mut self) -> &mut Bo4eMeta {
78        &mut self.meta
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use chrono::TimeZone;
86
87    #[test]
88    fn test_substitution_value() {
89        let value = SubstitutionValue {
90            timestamp: Some(Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap()),
91            value: Some(125.5),
92            unit: Some(Unit::KilowattHour),
93            substitution_method: Some("Interpolation".to_string()),
94            reason: Some("Meter communication failure".to_string()),
95            ..Default::default()
96        };
97
98        let json = serde_json::to_string(&value).unwrap();
99        assert!(json.contains("125.5"));
100        assert!(json.contains("Interpolation"));
101    }
102
103    #[test]
104    fn test_with_original() {
105        let value = SubstitutionValue {
106            value: Some(100.0),
107            original_value: Some(-50.0),
108            reason: Some("Negative value not allowed".to_string()),
109            ..Default::default()
110        };
111
112        let json = serde_json::to_string(&value).unwrap();
113        assert!(json.contains("100"));
114        assert!(json.contains("-50"));
115    }
116
117    #[test]
118    fn test_roundtrip() {
119        let value = SubstitutionValue {
120            timestamp: Some(Utc::now()),
121            value: Some(999.99),
122            substitution_method: Some("Historical average".to_string()),
123            ..Default::default()
124        };
125
126        let json = serde_json::to_string(&value).unwrap();
127        let parsed: SubstitutionValue = serde_json::from_str(&json).unwrap();
128        assert_eq!(value.value, parsed.value);
129        assert_eq!(value.substitution_method, parsed.substitution_method);
130    }
131
132    #[test]
133    fn test_bo4e_object_impl() {
134        assert_eq!(SubstitutionValue::type_name_german(), "Ersatzwert");
135        assert_eq!(SubstitutionValue::type_name_english(), "SubstitutionValue");
136    }
137}