1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Battery (EU Battery Regulation 2023/1542).
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::domain::gtin::Gtin;
use crate::domain::sector::enums::{BatteryChemistry, BatteryType, CarbonFootprintClass};
use super::shared::CriticalRawMaterial;
/// Battery-specific fields required by the EU Battery Regulation 2023/1542.
///
/// All `Option` fields are optional under the regulation; non-`Option` fields
/// are mandatory for publishing a battery DPP. Fields added in v2.0.0 of the
/// schema are marked `Option` and `skip_serializing_if` to maintain backward
/// compatibility with v1.0.0 data.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BatteryData {
// ── v1.0.0 mandatory fields ──────────────────────────────────────────
/// 14-digit Global Trade Item Number identifying the battery model.
pub gtin: Gtin,
/// Battery electrochemical chemistry.
pub battery_chemistry: BatteryChemistry,
/// Nominal voltage in volts.
pub nominal_voltage_v: f64,
/// Nominal capacity in ampere-hours.
pub nominal_capacity_ah: f64,
/// Expected lifetime in full charge–discharge cycles.
pub expected_lifetime_cycles: u32,
/// Carbon footprint in kg CO₂e per battery unit (manufacturer-supplied or calculated).
pub co2e_per_unit_kg: f64,
// ── v1.0.0 optional fields ───────────────────────────────────────────
/// Recycled cobalt content as a percentage of total cobalt (0.0–100.0).
pub recycled_content_cobalt_pct: Option<f64>,
/// Recycled lithium content as a percentage of total lithium (0.0–100.0).
pub recycled_content_lithium_pct: Option<f64>,
/// Recycled nickel content as a percentage of total nickel (0.0–100.0).
pub recycled_content_nickel_pct: Option<f64>,
/// Current state of health as a percentage of original rated capacity.
pub state_of_health_pct: Option<f64>,
/// Rated energy in kilowatt-hours (distinct from capacity in Ah).
pub rated_capacity_kwh: Option<f64>,
// ── v2.0.0 — Annex XIII compliance fields (Battery Reg. 2023/1542) ──
/// Carbon footprint performance class (A–E) per Battery Regulation Art. 7(2).
#[serde(skip_serializing_if = "Option::is_none")]
pub carbon_footprint_class: Option<CarbonFootprintClass>,
/// URL to supply chain due diligence documentation (Art. 47–52).
/// Must link to a publicly accessible policy describing the due
/// diligence process for raw material sourcing.
#[serde(skip_serializing_if = "Option::is_none")]
pub due_diligence_url: Option<String>,
/// Cathode active material composition.
#[serde(skip_serializing_if = "Option::is_none")]
pub cathode_material: Option<Vec<MaterialComposition>>,
/// Anode active material composition.
#[serde(skip_serializing_if = "Option::is_none")]
pub anode_material: Option<Vec<MaterialComposition>>,
/// Electrolyte composition.
#[serde(skip_serializing_if = "Option::is_none")]
pub electrolyte_material: Option<Vec<MaterialComposition>>,
/// Critical raw materials present (Art. 5(2)) — list of CAS or EC numbers.
/// The EU Critical Raw Materials Act (2024/1252) defines the canonical list.
#[serde(skip_serializing_if = "Option::is_none")]
pub critical_raw_materials: Option<Vec<CriticalRawMaterial>>,
/// URL or text for disassembly / dismantling instructions (Annex XIII §6).
#[serde(skip_serializing_if = "Option::is_none")]
pub disassembly_instructions_url: Option<String>,
/// State-of-health determination methodology identifier, e.g.
/// `"IEC 62660-1:2018"` or `"proprietary:vendor-model-v3"`.
#[serde(skip_serializing_if = "Option::is_none")]
pub soh_methodology: Option<String>,
/// Minimum operating temperature in °C.
#[serde(skip_serializing_if = "Option::is_none")]
pub operating_temp_min_c: Option<f64>,
/// Maximum operating temperature in °C.
#[serde(skip_serializing_if = "Option::is_none")]
pub operating_temp_max_c: Option<f64>,
/// Rated energy in watt-hours (Wh). Required by Annex XIII separately
/// from `rated_capacity_kwh`. For cells this is the Wh stamping value.
#[serde(skip_serializing_if = "Option::is_none")]
pub rated_energy_wh: Option<f64>,
/// Recycled lead content as a percentage (for lead-acid batteries).
#[serde(skip_serializing_if = "Option::is_none")]
pub recycled_content_lead_pct: Option<f64>,
/// Weight of the battery in kilograms.
#[serde(skip_serializing_if = "Option::is_none")]
pub battery_weight_kg: Option<f64>,
/// Battery type category per EU Battery Regulation 2023/1542 Art. 2.
#[serde(skip_serializing_if = "Option::is_none")]
pub battery_type: Option<BatteryType>,
/// Round-trip energy efficiency at 50% state of charge (percentage).
#[serde(skip_serializing_if = "Option::is_none")]
pub round_trip_efficiency_pct: Option<f64>,
/// Internal resistance in milliohms (mΩ) at 50% SoC.
#[serde(skip_serializing_if = "Option::is_none")]
pub internal_resistance_mohm: Option<f64>,
// ── v2.0.0 — Annex XIII identity & origin fields (Battery Reg. 2023/1542) ─
/// Date and time of manufacture (Annex XIII §2 — "date of manufacture").
#[serde(skip_serializing_if = "Option::is_none")]
pub manufacturing_date: Option<DateTime<Utc>>,
/// Plant / location of manufacture (ISO 3166-1 alpha-2 country code or
/// "ISO country:city" free-text per Annex XIII §2).
#[serde(skip_serializing_if = "Option::is_none")]
pub manufacturing_place: Option<String>,
/// Manufacturer's battery model identifier as it appears on the physical label
/// or accompanying technical documentation (Annex XIII §1).
#[serde(skip_serializing_if = "Option::is_none")]
pub battery_model_id: Option<String>,
/// Unique battery passport identifier issued at commissioning.
/// Format: per the Commission's implementing act on the battery passport
/// (expected ~2026); until then a UUID v4 is accepted.
#[serde(skip_serializing_if = "Option::is_none")]
pub battery_passport_number: Option<String>,
}
/// Material composition entry for cathode, anode, or electrolyte.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MaterialComposition {
/// Chemical name or formula, e.g. `"LiFePO4"`, `"graphite"`, `"LiPF6"`.
pub name: String,
/// Weight percentage in the component (0.0–100.0).
pub weight_pct: f64,
/// CAS Registry Number if applicable.
#[serde(skip_serializing_if = "Option::is_none")]
pub cas_number: Option<String>,
}