Skip to main content

dpp_domain/product_group/data/electronics/
data.rs

1//! Electronics — EU Reg. 2023/1670 (ecodesign) + 2023/1669 (energy labelling).
2//!
3//! Applicability dates and regulatory status live in the product group manifest
4//! (`product-groups/electronics.json`); see `docs/regulatory/REGULATORY.md` for what is
5//! implemented versus pending.
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10use super::index_scope_exclusion::IndexScopeExclusion;
11use super::repairability_index::RepairabilityIndexDeclaration;
12use crate::identifier::ProductIdentifier;
13use crate::product_group::repairability_score::RepairabilityScore;
14use crate::product_group::{DeviceType, EnergyEfficiencyClass};
15
16use super::super::common::{CriticalRawMaterial, SvhcSubstance};
17
18/// Electronics product group data.
19///
20/// Scope and applicability dates are held in the product group manifest
21/// (`product-groups/electronics.json`), not restated here.
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23#[serde(rename_all = "camelCase")]
24pub struct ElectronicsData {
25    /// The unique product identifier for the product model, in whichever EN 18219
26    /// clause 5 scheme issued it — a 14-digit GTIN under scheme 1.
27    pub product_identifier: ProductIdentifier,
28    /// Device type per EU Regulation (EU) 2023/1670 Art. 1(1).
29    pub product_category: DeviceType,
30    /// EU energy label class (A–G) per Energy Labelling Regulation 2017/1369.
31    pub energy_efficiency_class: EnergyEfficiencyClass,
32    /// Whole-lifecycle carbon footprint in kg CO₂e per unit.
33    pub co2e_per_unit_kg: f64,
34
35    /// Repairability score (non-regulatory heuristic — not EN 45554 / EU 2023/1669).
36    /// `overall` ≥ 6.0 = good; < 4.0 = fails minimum standard.
37    ///
38    /// **Not comparable to the enacted index.** Where both are present they are
39    /// two different numbers on two different scales, and only
40    /// [`repairability_index_inputs`](Self::repairability_index_inputs) feeds the
41    /// one Reg. (EU) 2023/1669 defines.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub repairability_score: Option<RepairabilityScore>,
44    /// The Annex IV point 5 parameters an operator declares, from which the
45    /// enacted repairability index of Reg. (EU) 2023/1669 is computed.
46    ///
47    /// ✅ COMPLIANCE-PIN: EU 2023/1669, Annex IV point 5 (OJ L 214, 31.8.2023,
48    /// p. 26). Declared values, not a score — see
49    /// [`RepairabilityIndexDeclaration`] for why the two are kept apart, and for
50    /// the Annex IX Table 10 verification tolerance that makes the split the
51    /// same shape the Regulation itself describes.
52    ///
53    /// `None` is the ordinary case and always will be for a device type the act
54    /// does not reach. **Nothing obliges a passport to carry this** — the word
55    /// "passport" does not occur in Reg. (EU) 2023/1669, which puts the index on
56    /// the energy label and in the product information sheet.
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub repairability_index_inputs: Option<RepairabilityIndexDeclaration>,
59    /// A declaration that Art. 1(a) or Art. 1(b) of Reg. (EU) 2023/1669 carves
60    /// this unit out of the index's scope.
61    ///
62    /// ✅ COMPLIANCE-PIN: EU 2023/1669, Art. 1 (OJ L 214, 31.8.2023, p. 12). A
63    /// rollable-display phone and a high-security smartphone are both
64    /// [`DeviceType::Smartphone`], so without this the index would be claimed
65    /// over two product classes the Regulation expressly disclaims.
66    ///
67    /// `None` means **not excluded**, never "unknown": the carve-out is what
68    /// removes the obligation, so an operator who declares nothing has claimed
69    /// nothing, and reading silence as an exclusion would exempt a product on a
70    /// missing field.
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub index_scope_exclusion: Option<IndexScopeExclusion>,
73    /// Whether spare parts are commercially available from the manufacturer.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub spare_parts_available: Option<bool>,
76    /// URL to the repair manual or repair information portal.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub repair_manual_url: Option<String>,
79    /// URL to disassembly / dismantling instructions.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub disassembly_instructions_url: Option<String>,
82    /// SVHC substances present above 0.1% w/w (REACH Art. 33).
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub svhc_substances: Option<Vec<SvhcSubstance>>,
85    /// Whether the product complies with RoHS Directive 2011/65/EU.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub rohs_compliant: Option<bool>,
88    /// Critical raw materials present (EU CRM Act 2024/1252).
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub critical_raw_materials: Option<Vec<CriticalRawMaterial>>,
91    /// Recycled content as a percentage of total product weight (0.0–100.0).
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub recycled_content_pct: Option<f64>,
94    /// Standby power consumption in watts.
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub standby_power_w: Option<f64>,
97    /// Expected product lifetime in years under normal use.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub expected_lifetime_years: Option<u32>,
100    /// Date until which firmware / software updates are guaranteed.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub firmware_update_until: Option<DateTime<Utc>>,
103}
104
105impl crate::product_group::payload::ProductGroupPayload for ElectronicsData {
106    fn product_identifier(&self) -> Option<&crate::identifier::ProductIdentifier> {
107        Some(&self.product_identifier)
108    }
109
110    /// This act defines no model identifier.
111    fn model_identifier(&self) -> Option<&str> {
112        None
113    }
114
115    /// This group's schema carries a REACH candidate-list declaration.
116    fn svhc_substances(&self) -> Option<&[crate::product_group::SvhcSubstance]> {
117        self.svhc_substances.as_deref()
118    }
119
120    /// Reg. (EU) 2023/1670 Art. 1(1)'s four device types. Always present: the
121    /// field is required and the enum is closed.
122    fn product_category(&self) -> Option<&str> {
123        Some(self.product_category.wire_str())
124    }
125}