1use crate::{
4 event::EventId,
5 interval::{Interval, IntervalPeriod},
6 target::Target,
7 ClientId, Identifier, IdentifierError, Unit,
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 values_map::{Value, ValueType, ValuesMap},
253 Duration,
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 "programID": "object-999",
368 "eventID": "object-999",
369 "clientName": "VEN-999",
370 "reportName": "Battery_usage_04112023",
371 "payloadDescriptors": null,
372 "resources": [
373 {
374 "resourceName": "RESOURCE-999",
375 "intervalPeriod": {
376 "start": "2023-06-15T09:30:00Z",
377 "duration": "PT1H",
378 "randomizeStart": "PT1H"
379 },
380 "intervals": [
381 {
382 "id": 0,
383 "intervalPeriod": {
384 "start": "2023-06-15T09:30:00Z",
385 "duration": "PT1H",
386 "randomizeStart": "PT1H"
387 },
388 "payloads": [
389 {
390 "type": "PRICE",
391 "values": [0.17]
392 }
393 ]
394 }
395 ]
396 }
397 ],
398 "clientID": "249rj49jiej"
399 }]"#;
400
401 let expected = Report {
402 id: ReportId("object-999".parse().unwrap()),
403 created_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
404 modification_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
405 content: ReportRequest {
406 event_id: EventId("object-999".parse().unwrap()),
407 client_name: "VEN-999".into(),
408 report_name: Some("Battery_usage_04112023".into()),
409 payload_descriptors: None,
410 resources: vec![ReportResource {
411 resource_name: ResourceName::Private("RESOURCE-999".into()),
412 interval_period: Some(IntervalPeriod {
413 start: "2023-06-15T09:30:00Z".parse().unwrap(),
414 duration: Some(Duration::PT1H),
415 randomize_start: Some(Duration::PT1H),
416 }),
417 intervals: vec![Interval {
418 id: 0,
419 interval_period: Some(IntervalPeriod {
420 start: "2023-06-15T09:30:00Z".parse().unwrap(),
421 duration: Some(Duration::PT1H),
422 randomize_start: Some(Duration::PT1H),
423 }),
424 payloads: vec![ValuesMap {
425 value_type: ValueType("PRICE".into()),
426 values: vec![Value::Number(0.17)],
427 }],
428 }],
429 }],
430 },
431 client_id: ClientId::new("249rj49jiej").unwrap(),
432 };
433
434 assert_eq!(
435 serde_json::from_str::<Vec<Report>>(example).unwrap()[0],
436 expected
437 );
438 }
439}
440
441#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
442#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
443pub enum ReportType {
444 Reading,
445 Usage,
446 Demand,
447 Setpoint,
448 DeltaUsage,
449 Baseline,
450 OperatingState,
451 UpRegulationAvailable,
452 DownRegulationAvailable,
453 RegulationSetpoint,
454 StorageUsableCapacity,
455 StorageChargeLevel,
456 StorageMaxDischargePower,
457 StorageMaxChargePower,
458 SimpleLevel,
459 UsageForecast,
460 StorageDispatchForecast,
461 LoadShedDeltaAvailable,
462 GenerationDeltaAvailable,
463 DataQuality,
464 ImportReservationCapacity,
465 ImportReservationFee,
466 ExportReservationCapacity,
467 ExportReservationFee,
468 #[serde(untagged)]
469 Private(
470 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")] String,
471 ),
472}
473
474#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
475#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
476pub enum ReadingType {
477 #[default]
478 DirectRead,
479 Estimated,
480 Summed,
481 Mean,
482 Peak,
483 Forecast,
484 Average,
485 #[serde(untagged)]
486 Private(String),
487}
488
489impl ReadingType {
490 fn is_default(&self) -> bool {
491 *self == Self::default()
492 }
493}
494
495#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
496#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
497pub enum ResourceName {
498 AggregatedReport,
499 #[serde(untagged)]
500 Private(
501 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")] String,
502 ),
503}