Skip to main content

bo4e_core/bo/
load_profile.rs

1//! Load profile (Lastgang) business object.
2//!
3//! Represents a load profile - time series of power consumption or generation.
4
5use serde::{Deserialize, Serialize};
6
7use crate::com::{LoadProfileValue, TimePeriod};
8use crate::enums::{Division, EnergyDirection, MeasurementType, Unit};
9use crate::traits::{Bo4eMeta, Bo4eObject};
10
11/// A load profile containing time series of power data.
12///
13/// German: Lastgang
14///
15/// Load profiles represent power measurements over time,
16/// typically in 15-minute or hourly intervals.
17///
18/// # Example
19///
20/// ```rust
21/// use bo4e_core::bo::LoadProfile;
22/// use bo4e_core::enums::{Division, EnergyDirection};
23///
24/// let profile = LoadProfile {
25///     load_profile_id: Some("LP001".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#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
33#[cfg_attr(feature = "json-schema", schemars(rename = "Lastgang"))]
34#[serde(rename_all = "camelCase")]
35pub struct LoadProfile {
36    /// BO4E metadata
37    #[serde(flatten)]
38    pub meta: Bo4eMeta,
39
40    /// Load profile ID (Lastgang-ID)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    #[cfg_attr(feature = "json-schema", schemars(rename = "lastgangId"))]
43    pub load_profile_id: Option<String>,
44
45    /// Energy division (Sparte)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
48    pub division: Option<Division>,
49
50    /// Energy direction (Energierichtung)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    #[cfg_attr(feature = "json-schema", schemars(rename = "energierichtung"))]
53    pub energy_direction: Option<EnergyDirection>,
54
55    /// Measurement type (Messart)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    #[cfg_attr(feature = "json-schema", schemars(rename = "messart"))]
58    pub measurement_type: Option<MeasurementType>,
59
60    /// Unit of measurement (Einheit)
61    #[serde(skip_serializing_if = "Option::is_none")]
62    #[cfg_attr(feature = "json-schema", schemars(rename = "einheit"))]
63    pub unit: Option<Unit>,
64
65    /// Validity period (Gueltigkeitszeitraum)
66    #[serde(skip_serializing_if = "Option::is_none")]
67    #[cfg_attr(feature = "json-schema", schemars(rename = "gueltigkeitszeitraum"))]
68    pub validity_period: Option<TimePeriod>,
69
70    /// Load profile values (Lastgangwerte)
71    #[serde(default, skip_serializing_if = "Vec::is_empty")]
72    #[cfg_attr(feature = "json-schema", schemars(rename = "lastgangwerte"))]
73    pub values: Vec<LoadProfileValue>,
74
75    /// Associated market location ID
76    #[serde(skip_serializing_if = "Option::is_none")]
77    #[cfg_attr(feature = "json-schema", schemars(rename = "marktlokationsId"))]
78    pub market_location_id: Option<String>,
79
80    /// Associated metering location ID
81    #[serde(skip_serializing_if = "Option::is_none")]
82    #[cfg_attr(feature = "json-schema", schemars(rename = "messlokationsId"))]
83    pub metering_location_id: Option<String>,
84
85    /// OBIS code
86    #[serde(skip_serializing_if = "Option::is_none")]
87    #[cfg_attr(feature = "json-schema", schemars(rename = "obisKennzahl"))]
88    pub obis_code: Option<String>,
89
90    /// Interval duration in minutes (Intervalllaenge)
91    #[serde(skip_serializing_if = "Option::is_none")]
92    #[cfg_attr(feature = "json-schema", schemars(rename = "intervalllaenge"))]
93    pub interval_minutes: Option<i32>,
94
95    /// Standard load profile type (Standardlastprofil)
96    #[serde(skip_serializing_if = "Option::is_none")]
97    #[cfg_attr(feature = "json-schema", schemars(rename = "standardlastprofil"))]
98    pub standard_profile_type: Option<String>,
99}
100
101impl Bo4eObject for LoadProfile {
102    fn type_name_german() -> &'static str {
103        "Lastgang"
104    }
105
106    fn type_name_english() -> &'static str {
107        "LoadProfile"
108    }
109
110    fn meta(&self) -> &Bo4eMeta {
111        &self.meta
112    }
113
114    fn meta_mut(&mut self) -> &mut Bo4eMeta {
115        &mut self.meta
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_load_profile_creation() {
125        let profile = LoadProfile {
126            load_profile_id: Some("LP001".to_string()),
127            division: Some(Division::Electricity),
128            energy_direction: Some(EnergyDirection::FeedOut),
129            ..Default::default()
130        };
131
132        assert_eq!(profile.load_profile_id, Some("LP001".to_string()));
133    }
134
135    #[test]
136    fn test_serialize() {
137        let profile = LoadProfile {
138            meta: Bo4eMeta::with_type("Lastgang"),
139            load_profile_id: Some("LP001".to_string()),
140            ..Default::default()
141        };
142
143        let json = serde_json::to_string(&profile).unwrap();
144        assert!(json.contains(r#""_typ":"Lastgang""#));
145    }
146
147    #[test]
148    fn test_roundtrip() {
149        let profile = LoadProfile {
150            meta: Bo4eMeta::with_type("Lastgang"),
151            load_profile_id: Some("LP001".to_string()),
152            division: Some(Division::Electricity),
153            energy_direction: Some(EnergyDirection::FeedOut),
154            unit: Some(Unit::Kilowatt),
155            interval_minutes: Some(15),
156            ..Default::default()
157        };
158
159        let json = serde_json::to_string(&profile).unwrap();
160        let parsed: LoadProfile = serde_json::from_str(&json).unwrap();
161        assert_eq!(profile, parsed);
162    }
163
164    #[test]
165    fn test_bo4e_object_impl() {
166        assert_eq!(LoadProfile::type_name_german(), "Lastgang");
167        assert_eq!(LoadProfile::type_name_english(), "LoadProfile");
168    }
169}