Skip to main content

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}