1use crate::{
4 ClientId, Identifier, IdentifierError, Unit,
5 event::EventId,
6 interval::{Interval, IntervalPeriod},
7 target::Target,
8};
9use chrono::{DateTime, Utc};
10use serde::{Deserialize, Serialize};
11use serde_with::skip_serializing_none;
12use std::{
13 fmt::{Display, Formatter},
14 str::FromStr,
15};
16use validator::{Validate, ValidateRange};
17
18#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
20#[serde(rename_all = "camelCase")]
21pub struct Report {
22 pub id: ReportId,
24 #[serde(with = "crate::serde_rfc3339")]
26 pub created_date_time: DateTime<Utc>,
27 #[serde(with = "crate::serde_rfc3339")]
29 pub modification_date_time: DateTime<Utc>,
30 #[serde(flatten)]
31 #[validate(nested)]
32 pub content: ReportRequest,
33 #[serde(rename = "clientID")]
34 pub client_id: ClientId,
35}
36
37#[skip_serializing_none]
38#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
39#[serde(rename_all = "camelCase", tag = "objectType", rename = "REPORT")]
40pub struct ReportRequest {
41 #[serde(rename = "eventID")]
43 pub event_id: EventId,
44 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")]
46 pub client_name: String,
47 pub report_name: Option<String>,
49 #[validate(nested)]
53 pub payload_descriptors: Option<Vec<ReportPayloadDescriptor>>,
54 pub resources: Vec<ReportResource>,
56}
57
58impl ReportRequest {
59 pub fn with_client_name(mut self, client_name: &str) -> Self {
60 self.client_name = client_name.to_string();
61 self
62 }
63
64 pub fn with_name(mut self, name: &str) -> Self {
65 self.report_name = Some(name.to_string());
66 self
67 }
68
69 pub fn with_payload_descriptors(mut self, descriptors: Vec<ReportPayloadDescriptor>) -> Self {
70 self.payload_descriptors = Some(descriptors);
71 self
72 }
73
74 pub fn with_resources(mut self, resources: Vec<ReportResource>) -> Self {
75 self.resources = resources;
76 self
77 }
78}
79
80#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash, Eq)]
82pub struct ReportId(pub(crate) Identifier);
83
84impl ReportId {
85 pub fn as_str(&self) -> &str {
86 self.0.as_str()
87 }
88}
89
90impl Display for ReportId {
91 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
92 write!(f, "{}", self.0)
93 }
94}
95
96impl FromStr for ReportId {
97 type Err = IdentifierError;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 Ok(Self(s.parse()?))
101 }
102}
103
104#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct ReportResource {
108 pub resource_name: ResourceName,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub interval_period: Option<IntervalPeriod>,
114 pub intervals: Vec<Interval>,
116}
117
118#[skip_serializing_none]
121#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct ReportDescriptor {
124 pub payload_type: ReportType,
128 pub reading_type: Option<ReadingType>,
130 pub units: Option<Unit>,
132 pub targets: Option<Vec<Target>>,
134 #[serde(default = "bool_false")]
136 pub aggregate: bool,
137 #[serde(default = "neg_one")]
139 pub start_interval: i32,
140 #[serde(default = "neg_one")]
142 pub num_intervals: i32,
143 #[serde(default = "bool_true")]
145 pub historical: bool,
146 #[serde(default = "neg_one")]
148 pub frequency: i32,
149 #[serde(default = "pos_one")]
151 pub repeat: i32,
152 #[serde(default)]
154 pub report_intervals: ReportIntervals,
155}
156
157impl ReportDescriptor {
158 pub fn new(payload_type: ReportType) -> Self {
160 Self {
161 payload_type,
162 reading_type: None,
163 units: None,
164 targets: None,
165 aggregate: false,
166 start_interval: -1,
167 num_intervals: -1,
168 historical: true,
169 frequency: -1,
170 repeat: 1,
171 report_intervals: Default::default(),
172 }
173 }
174}
175
176#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
177#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
178pub enum ReportIntervals {
179 #[default]
180 Intervals,
181 SubIntervals,
182 OpenIntervals,
183}
184
185fn bool_false() -> bool {
186 false
187}
188
189fn bool_true() -> bool {
190 true
191}
192
193fn neg_one() -> i32 {
194 -1
195}
196
197fn pos_one() -> i32 {
198 1
199}
200
201#[skip_serializing_none]
205#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
206#[serde(rename_all = "camelCase")]
207pub struct ReportPayloadDescriptor {
208 pub payload_type: ReportType,
212 #[serde(skip_serializing_if = "ReadingType::is_default", default)]
214 pub reading_type: ReadingType,
215 pub units: Option<Unit>,
217 pub accuracy: Option<f32>,
219 #[validate(range(min = Confidence(0), max = Confidence(100)))]
221 pub confidence: Option<Confidence>,
222}
223
224impl ReportPayloadDescriptor {
225 pub fn new(payload_type: ReportType) -> Self {
226 Self {
227 payload_type,
228 reading_type: Default::default(),
229 units: None,
230 accuracy: None,
231 confidence: None,
232 }
233 }
234}
235
236#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, PartialOrd)]
237pub struct Confidence(u8);
238
239impl ValidateRange<Confidence> for Confidence {
240 fn greater_than(&self, _: Confidence) -> Option<bool> {
241 None
242 }
243
244 fn less_than(&self, _: Confidence) -> Option<bool> {
245 None
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use crate::{
252 Duration,
253 values_map::{Value, ValueType, ValuesMap},
254 };
255
256 use super::*;
257
258 #[test]
259 fn test_report_type_serialization() {
260 assert_eq!(
261 serde_json::to_string(&ReportType::Baseline).unwrap(),
262 r#""BASELINE""#
263 );
264 assert_eq!(
265 serde_json::to_string(&ReportType::RegulationSetpoint).unwrap(),
266 r#""REGULATION_SETPOINT""#
267 );
268 assert_eq!(
269 serde_json::to_string(&ReportType::Private(String::from("something else"))).unwrap(),
270 r#""something else""#
271 );
272 assert_eq!(
273 serde_json::from_str::<ReportType>(r#""DEMAND""#).unwrap(),
274 ReportType::Demand
275 );
276 assert_eq!(
277 serde_json::from_str::<ReportType>(r#""EXPORT_RESERVATION_FEE""#).unwrap(),
278 ReportType::ExportReservationFee
279 );
280 assert_eq!(
281 serde_json::from_str::<ReportType>(r#""something else""#).unwrap(),
282 ReportType::Private(String::from("something else"))
283 );
284
285 assert!(serde_json::from_str::<ReportType>(r#""""#).is_err());
286 assert!(serde_json::from_str::<ReportType>(&format!("\"{}\"", "x".repeat(129))).is_err());
287 }
288
289 #[test]
290 fn test_reading_type_serialization() {
291 assert_eq!(
292 serde_json::to_string(&ReadingType::DirectRead).unwrap(),
293 r#""DIRECT_READ""#
294 );
295 assert_eq!(
296 serde_json::to_string(&ReadingType::Private(String::from("something else"))).unwrap(),
297 r#""something else""#
298 );
299 assert_eq!(
300 serde_json::from_str::<ReadingType>(r#""AVERAGE""#).unwrap(),
301 ReadingType::Average
302 );
303 assert_eq!(
304 serde_json::from_str::<ReadingType>(r#""something else""#).unwrap(),
305 ReadingType::Private(String::from("something else"))
306 );
307 }
308
309 #[test]
310 fn descriptor_parses_minimal() {
311 let json = r#"{"payloadType":"hello"}"#;
312 let expected = ReportDescriptor::new(ReportType::Private("hello".into()));
313
314 assert_eq!(
315 serde_json::from_str::<ReportDescriptor>(json).unwrap(),
316 expected
317 );
318 }
319
320 #[test]
321 fn parses_minimal_report() {
322 let example = r#"{"eventID":"e1","clientName":"c","resources":[]}"#;
323 let expected = ReportRequest {
324 event_id: EventId("e1".parse().unwrap()),
325 client_name: "c".to_string(),
326 report_name: None,
327 payload_descriptors: None,
328 resources: vec![],
329 };
330
331 assert_eq!(
332 serde_json::from_str::<ReportRequest>(example).unwrap(),
333 expected
334 );
335 }
336
337 #[test]
338 fn test_resource_name_serialization() {
339 assert_eq!(
340 serde_json::to_string(&ResourceName::AggregatedReport).unwrap(),
341 r#""AGGREGATED_REPORT""#
342 );
343 assert_eq!(
344 serde_json::to_string(&ResourceName::Private(String::from("something else"))).unwrap(),
345 r#""something else""#
346 );
347 assert_eq!(
348 serde_json::from_str::<ResourceName>(r#""AGGREGATED_REPORT""#).unwrap(),
349 ResourceName::AggregatedReport
350 );
351 assert_eq!(
352 serde_json::from_str::<ResourceName>(r#""something else""#).unwrap(),
353 ResourceName::Private(String::from("something else"))
354 );
355
356 assert!(serde_json::from_str::<ResourceName>(r#""""#).is_err());
357 assert!(serde_json::from_str::<ResourceName>(&format!("\"{}\"", "x".repeat(129))).is_err());
358 }
359
360 #[test]
361 fn parses_example() {
362 let example = r#"[{
363 "id": "object-999",
364 "createdDateTime": "2023-06-15T09:30:00Z",
365 "modificationDateTime": "2023-06-15T09:30:00Z",
366 "objectType": "REPORT",
367 "eventID": "object-999",
368 "clientName": "VEN-999",
369 "reportName": "Battery_usage_04112023",
370 "payloadDescriptors": null,
371 "resources": [
372 {
373 "resourceName": "RESOURCE-999",
374 "intervalPeriod": {
375 "start": "2023-06-15T09:30:00Z",
376 "duration": "PT1H",
377 "randomizeStart": "PT1H"
378 },
379 "intervals": [
380 {
381 "id": 0,
382 "intervalPeriod": {
383 "start": "2023-06-15T09:30:00Z",
384 "duration": "PT1H",
385 "randomizeStart": "PT1H"
386 },
387 "payloads": [
388 {
389 "type": "PRICE",
390 "values": [0.17]
391 }
392 ]
393 }
394 ]
395 }
396 ],
397 "clientID": "249rj49jiej"
398 }]"#;
399
400 let expected = Report {
401 id: ReportId("object-999".parse().unwrap()),
402 created_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
403 modification_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
404 content: ReportRequest {
405 event_id: EventId("object-999".parse().unwrap()),
406 client_name: "VEN-999".into(),
407 report_name: Some("Battery_usage_04112023".into()),
408 payload_descriptors: None,
409 resources: vec![ReportResource {
410 resource_name: ResourceName::Private("RESOURCE-999".into()),
411 interval_period: Some(IntervalPeriod {
412 start: "2023-06-15T09:30:00Z".parse().unwrap(),
413 duration: Some(Duration::PT1H),
414 randomize_start: Some(Duration::PT1H),
415 }),
416 intervals: vec![Interval {
417 id: 0,
418 interval_period: Some(IntervalPeriod {
419 start: "2023-06-15T09:30:00Z".parse().unwrap(),
420 duration: Some(Duration::PT1H),
421 randomize_start: Some(Duration::PT1H),
422 }),
423 payloads: vec![ValuesMap {
424 value_type: ValueType("PRICE".into()),
425 values: vec![Value::Number(0.17)],
426 }],
427 }],
428 }],
429 },
430 client_id: ClientId::new("249rj49jiej").unwrap(),
431 };
432
433 assert_eq!(
434 serde_json::from_str::<Vec<Report>>(example).unwrap()[0],
435 expected
436 );
437 }
438}
439
440#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
441#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
442pub enum ReportType {
443 Reading,
444 Usage,
445 Demand,
446 Setpoint,
447 DeltaUsage,
448 Baseline,
449 OperatingState,
450 UpRegulationAvailable,
451 DownRegulationAvailable,
452 RegulationSetpoint,
453 StorageUsableCapacity,
454 StorageChargeLevel,
455 StorageMaxDischargePower,
456 StorageMaxChargePower,
457 SimpleLevel,
458 UsageForecast,
459 StorageDispatchForecast,
460 LoadShedDeltaAvailable,
461 GenerationDeltaAvailable,
462 DataQuality,
463 ImportReservationCapacity,
464 ImportReservationFee,
465 ExportReservationCapacity,
466 ExportReservationFee,
467 #[serde(untagged)]
468 Private(
469 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")] String,
470 ),
471}
472
473#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
474#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
475pub enum ReadingType {
476 #[default]
477 DirectRead,
478 Estimated,
479 Summed,
480 Mean,
481 Peak,
482 Forecast,
483 Average,
484 #[serde(untagged)]
485 Private(String),
486}
487
488impl ReadingType {
489 fn is_default(&self) -> bool {
490 *self == Self::default()
491 }
492}
493
494#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
495#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
496pub enum ResourceName {
497 AggregatedReport,
498 #[serde(untagged)]
499 Private(
500 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")] String,
501 ),
502}