dpp_domain/product_group/data/battery/dynamic_performance.rs
1//! [`DynamicPerformance`] — Annex XIII point 2 dynamic performance data.
2
3use serde::{Deserialize, Serialize};
4
5/// Measured performance and durability of one physical battery — Annex XIII
6/// point 4(a).
7///
8/// # Why this is a block and not ten more fields on [`BatteryData`](crate::product_group::BatteryData)
9///
10/// Point 4 describes **an individual battery**; points 1 to 3 describe a
11/// **model**. The Commission's own data-point guidance makes the pairing
12/// explicit rather than implicit — its entry for `ratedCapacityAh` here reads
13/// *"same as data point number 11 (capacity), but now dynamic"* — so the same
14/// quantity is deliberately carried twice, once as declared and once as
15/// measured. Flattening these onto `BatteryData` would put the two readings
16/// side by side distinguished only by name, and would let a filer put a
17/// measured value in a declared field. Keeping the block separate makes the
18/// distinction structural, and lets one `individual` disclosure entry cover
19/// the whole set — the same shape [`StateOfHealth`](crate::product_group::StateOfHealth) and [`ExpectedLifetime`](crate::product_group::ExpectedLifetime)
20/// already use.
21///
22/// # Optionality
23///
24/// Every field is `Option`. The guidance marks this set mandatory for EV and
25/// LMT batteries but *"if applicable"* for industrial ones, and marks round
26/// trip efficiency and its fade *"where applicable"* for all three. Which
27/// fields a given battery owes is a category-conditional rule, not a schema
28/// constraint.
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
30#[serde(rename_all = "camelCase")]
31#[non_exhaustive]
32pub struct DynamicPerformance {
33 /// Rated capacity in ampere-hours, measured. The dynamic counterpart of
34 /// [`BatteryData::nominal_capacity_ah`](crate::product_group::BatteryData::nominal_capacity_ah).
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub rated_capacity_ah: Option<f64>,
37 /// Capacity fade, as a percentage of the original rated capacity.
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub capacity_fade_pct: Option<f64>,
40 /// Power, in watts.
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub power_w: Option<f64>,
43 /// Power fade, as a percentage of the original power.
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub power_fade_pct: Option<f64>,
46 /// Internal resistance, in milliohms.
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub internal_resistance_mohm: Option<f64>,
49 /// Internal resistance increase, as a percentage of the original.
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub internal_resistance_increase_pct: Option<f64>,
52 /// Energy round trip efficiency, as a percentage. *"Where applicable"* for
53 /// every category, unlike the fields above.
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub round_trip_efficiency_pct: Option<f64>,
56 /// Energy round trip efficiency fade, as a percentage. *"Where
57 /// applicable"*.
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub round_trip_efficiency_fade_pct: Option<f64>,
60 /// Expected lifetime under the reference conditions the battery was
61 /// designed for, in cycles — *"except for non-cycle applications"*. The
62 /// measured counterpart of [`BatteryData::expected_lifetime_cycles`](crate::product_group::BatteryData::expected_lifetime_cycles).
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub expected_lifetime_cycles: Option<u32>,
65 /// The same expectation in **calendar years**, which the annex lists as a
66 /// separate data point rather than a unit conversion: a battery can have a
67 /// calendar-life expectation and no meaningful cycle count.
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub expected_lifetime_years: Option<f64>,
70}