bo4e_core/bo/
energy_amount.rs

1//! Energy amount (Energiemenge) business object.
2//!
3//! Represents a quantity of energy with associated measurements.
4
5use serde::{Deserialize, Serialize};
6
7use crate::com::{MeasuredValue, TimePeriod};
8use crate::enums::{Division, EnergyDirection, MeasurementType};
9use crate::traits::{Bo4eMeta, Bo4eObject};
10
11/// An amount of energy with time series data.
12///
13/// German: Energiemenge
14///
15/// Energy amounts represent measured or calculated energy quantities
16/// over time, typically associated with a market or metering location.
17///
18/// # Example
19///
20/// ```rust
21/// use bo4e_core::bo::EnergyAmount;
22/// use bo4e_core::enums::{Division, EnergyDirection};
23///
24/// let energy = EnergyAmount {
25///     energy_amount_id: Some("EA001".to_string()),
26///     division: Some(Division::Electricity),
27///     energy_direction: Some(EnergyDirection::FeedOut),
28///     ..Default::default()
29/// };
30/// ```
31#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct EnergyAmount {
34    /// BO4E metadata
35    #[serde(flatten)]
36    pub meta: Bo4eMeta,
37
38    /// Energy amount ID (Energiemenge-ID)
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub energy_amount_id: Option<String>,
41
42    /// Energy division (Sparte)
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub division: Option<Division>,
45
46    /// Energy direction (Energierichtung)
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub energy_direction: Option<EnergyDirection>,
49
50    /// Measurement type (Messart)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub measurement_type: Option<MeasurementType>,
53
54    /// Validity period (Gueltigkeitszeitraum)
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub validity_period: Option<TimePeriod>,
57
58    /// Time series data (Messwerte)
59    #[serde(default, skip_serializing_if = "Vec::is_empty")]
60    pub measured_values: Vec<MeasuredValue>,
61
62    /// Associated market location ID
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub market_location_id: Option<String>,
65
66    /// Associated metering location ID
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub metering_location_id: Option<String>,
69
70    /// OBIS code for the measurement
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub obis_code: Option<String>,
73
74    /// Total energy value (Gesamtenergie)
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub total_energy: Option<f64>,
77}
78
79impl Bo4eObject for EnergyAmount {
80    fn type_name_german() -> &'static str {
81        "Energiemenge"
82    }
83
84    fn type_name_english() -> &'static str {
85        "EnergyAmount"
86    }
87
88    fn meta(&self) -> &Bo4eMeta {
89        &self.meta
90    }
91
92    fn meta_mut(&mut self) -> &mut Bo4eMeta {
93        &mut self.meta
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_energy_amount_creation() {
103        let energy = EnergyAmount {
104            energy_amount_id: Some("EA001".to_string()),
105            division: Some(Division::Electricity),
106            energy_direction: Some(EnergyDirection::FeedOut),
107            ..Default::default()
108        };
109
110        assert_eq!(energy.energy_amount_id, Some("EA001".to_string()));
111    }
112
113    #[test]
114    fn test_serialize() {
115        let energy = EnergyAmount {
116            meta: Bo4eMeta::with_type("Energiemenge"),
117            energy_amount_id: Some("EA001".to_string()),
118            ..Default::default()
119        };
120
121        let json = serde_json::to_string(&energy).unwrap();
122        assert!(json.contains(r#""_typ":"Energiemenge""#));
123    }
124
125    #[test]
126    fn test_roundtrip() {
127        let energy = EnergyAmount {
128            meta: Bo4eMeta::with_type("Energiemenge"),
129            energy_amount_id: Some("EA001".to_string()),
130            division: Some(Division::Electricity),
131            energy_direction: Some(EnergyDirection::FeedOut),
132            total_energy: Some(1234.56),
133            obis_code: Some("1-0:1.8.0".to_string()),
134            ..Default::default()
135        };
136
137        let json = serde_json::to_string(&energy).unwrap();
138        let parsed: EnergyAmount = serde_json::from_str(&json).unwrap();
139        assert_eq!(energy, parsed);
140    }
141
142    #[test]
143    fn test_bo4e_object_impl() {
144        assert_eq!(EnergyAmount::type_name_german(), "Energiemenge");
145        assert_eq!(EnergyAmount::type_name_english(), "EnergyAmount");
146    }
147}