Skip to main content

bo4e_core/com/
consumption.rs

1//! Consumption (Verbrauch) component.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::enums::{MeasuredValueStatus, Unit};
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9/// Consumption data for a specific period.
10///
11/// German: Verbrauch
12///
13/// # Example
14///
15/// ```rust
16/// use bo4e_core::com::Consumption;
17/// use bo4e_core::enums::Unit;
18///
19/// let consumption = Consumption {
20///     value: Some(3660.0),
21///     unit: Some(Unit::KilowattHour),
22///     obis_code: Some("1-1:1.8.0".to_string()),
23///     ..Default::default()
24/// };
25/// ```
26#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
27#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
28#[cfg_attr(feature = "json-schema", schemars(rename = "Verbrauch"))]
29#[serde(rename_all = "camelCase")]
30pub struct Consumption {
31    /// BO4E metadata
32    #[serde(flatten)]
33    pub meta: Bo4eMeta,
34
35    /// Consumption value (Wert)
36    #[serde(skip_serializing_if = "Option::is_none")]
37    #[cfg_attr(feature = "json-schema", schemars(rename = "wert"))]
38    pub value: Option<f64>,
39
40    /// Unit of measurement (Einheit)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    #[cfg_attr(feature = "json-schema", schemars(rename = "einheit"))]
43    pub unit: Option<Unit>,
44
45    /// Start date of consumption period inclusive (Startdatum)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    #[cfg_attr(feature = "json-schema", schemars(rename = "startdatum"))]
48    pub start_date: Option<DateTime<Utc>>,
49
50    /// End date of consumption period exclusive (Enddatum)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    #[cfg_attr(feature = "json-schema", schemars(rename = "enddatum"))]
53    pub end_date: Option<DateTime<Utc>>,
54
55    /// OBIS code identifying the measured value (OBIS-Kennzahl)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    #[cfg_attr(feature = "json-schema", schemars(rename = "obisKennzahl"))]
58    pub obis_code: Option<String>,
59
60    /// Status of the measured value (Messwertstatus)
61    #[serde(skip_serializing_if = "Option::is_none")]
62    #[cfg_attr(feature = "json-schema", schemars(rename = "messwertstatus"))]
63    pub measured_value_status: Option<MeasuredValueStatus>,
64}
65
66impl Bo4eObject for Consumption {
67    fn type_name_german() -> &'static str {
68        "Verbrauch"
69    }
70
71    fn type_name_english() -> &'static str {
72        "Consumption"
73    }
74
75    fn meta(&self) -> &Bo4eMeta {
76        &self.meta
77    }
78
79    fn meta_mut(&mut self) -> &mut Bo4eMeta {
80        &mut self.meta
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_electricity_consumption() {
90        let consumption = Consumption {
91            value: Some(3660.0),
92            unit: Some(Unit::KilowattHour),
93            obis_code: Some("1-1:1.8.0".to_string()),
94            measured_value_status: Some(MeasuredValueStatus::Read),
95            ..Default::default()
96        };
97
98        assert_eq!(consumption.value, Some(3660.0));
99        assert_eq!(consumption.unit, Some(Unit::KilowattHour));
100    }
101
102    #[test]
103    fn test_gas_consumption() {
104        let consumption = Consumption {
105            value: Some(1250.0),
106            unit: Some(Unit::CubicMeter),
107            obis_code: Some("7-0:3.0.0".to_string()),
108            ..Default::default()
109        };
110
111        assert_eq!(consumption.unit, Some(Unit::CubicMeter));
112    }
113
114    #[test]
115    fn test_default() {
116        let consumption = Consumption::default();
117        assert!(consumption.value.is_none());
118        assert!(consumption.unit.is_none());
119        assert!(consumption.obis_code.is_none());
120    }
121
122    #[test]
123    fn test_serialize() {
124        let consumption = Consumption {
125            value: Some(1500.0),
126            unit: Some(Unit::KilowattHour),
127            obis_code: Some("1-1:1.8.1".to_string()),
128            ..Default::default()
129        };
130
131        let json = serde_json::to_string(&consumption).unwrap();
132        assert!(json.contains(r#""value":1500"#));
133        assert!(json.contains(r#""obisCode":"1-1:1.8.1""#));
134    }
135
136    #[test]
137    fn test_roundtrip() {
138        let consumption = Consumption {
139            value: Some(4500.5),
140            unit: Some(Unit::KilowattHour),
141            start_date: Some(
142                DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
143                    .unwrap()
144                    .into(),
145            ),
146            end_date: Some(
147                DateTime::parse_from_rfc3339("2024-12-31T23:59:59Z")
148                    .unwrap()
149                    .into(),
150            ),
151            obis_code: Some("1-1:1.8.0".to_string()),
152            measured_value_status: Some(MeasuredValueStatus::Read),
153            ..Default::default()
154        };
155
156        let json = serde_json::to_string(&consumption).unwrap();
157        let parsed: Consumption = serde_json::from_str(&json).unwrap();
158        assert_eq!(consumption, parsed);
159    }
160
161    #[test]
162    fn test_bo4e_object_impl() {
163        assert_eq!(Consumption::type_name_german(), "Verbrauch");
164        assert_eq!(Consumption::type_name_english(), "Consumption");
165    }
166}