dpp_domain/product_group/data/electronics/repairability_index.rs
1//! The Annex IV point 5 inputs an operator declares, and the Art. 1 exclusion.
2//!
3//! ✅ COMPLIANCE-PIN: EU 2023/1669, Annex IV point 5 and Art. 1 (OJ L 214,
4//! 31.8.2023, pp. 12 and 26). Read from the Official Journal text.
5//!
6//! # What this is, and what it deliberately is not
7//!
8//! **Declared inputs, not a score.** Annex IV point 5 computes the index as
9//! `R = (SDD*0,25)+(SF*0,15)+(ST*0,15)+(SSP*0,15)+(SSU*0,15)+(SRI*0,15)` over
10//! six parameters, three of them scored per priority part. This module carries
11//! the six parameters as an operator states them. It does not compute `R`, and
12//! nothing here grades anything.
13//!
14//! That split is the useful shape and it is the one the Regulation itself
15//! describes: **Annex IX Table 10** sets a verification tolerance — *"The
16//! determined value shall not be more than 4 % lower than the declared
17//! value."* Declared by the supplier, re-determined by an authority. A passport
18//! carrying the inputs, and a receipt recomputing the index from them, is the
19//! same structure.
20//!
21//! # Why the shape is restated here rather than shared with the calculator
22//!
23//! The calculator's input type and this one describe the same six parameters and
24//! are answerable to different things. This is **passport content** — a set of
25//! values an operator declares, versioned with the electronics schema, and bound
26//! by the signature over the record. The calculator's is a function signature.
27//! Neither crate depends on the other by design, and that boundary is what keeps
28//! a change to a calculation from silently changing what a passport means.
29//!
30//! The cost is real: the two can drift, and only a consumer wiring them together
31//! would notice. Worth a shared home if a third consumer appears; not worth
32//! inverting the crate graph for a second.
33//!
34//! # No obligation attaches to any of this
35//!
36//! **The word "passport" does not appear in Reg. (EU) 2023/1669.** The index
37//! belongs on the energy label and in the product information sheet. Carrying it
38//! here is a product decision, not a duty being discharged, and this note exists
39//! so nobody later cites the Regulation for an obligation it does not create.
40
41use super::priority_part_scores::{MAX_SCORE, MIN_SCORE, PriorityPartScores};
42use serde::{Deserialize, Serialize};
43
44/// The complete Annex IV point 5 input set for one smartphone or slate tablet.
45///
46/// Three part-level parameters carrying per-part scores, and three product-level
47/// parameters that are single 1–5 scores. Every rubric below is Annex IV's.
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct RepairabilityIndexDeclaration {
51 /// SDD — disassembly depth, scored per part from the number of steps:
52 /// ≤2 steps scores 5, >15 steps scores 1.
53 ///
54 /// Annex IV's counting rules apply, including the five-step penalty where
55 /// reassembly needs a remote serial-number authorisation.
56 pub disassembly_depth: PriorityPartScores,
57 /// SF — fasteners: reusable 5, resupplied 3, removable 1.
58 pub fasteners: PriorityPartScores,
59 /// ST — tools: none 5, basic 4, supplied with the spare part 3, supplied
60 /// with the product 2, commercially available 1.
61 pub tools: PriorityPartScores,
62 /// SSP — spare-part availability, product level, 1–5.
63 pub spare_parts: u8,
64 /// SSU — operating-system update duration, product level: ≥7 years scores 5,
65 /// 6 years scores 3, 5 years scores 1.
66 pub software_updates: u8,
67 /// SRI — repair-information availability, product level, 1–5.
68 pub repair_information: u8,
69}
70
71impl RepairabilityIndexDeclaration {
72 /// Whether the three part-level parameters agree on whether the product folds.
73 ///
74 /// Annex IV selects between two weight sets on the presence of the hinge
75 /// assembly, so a declaration scoring FM under one parameter and omitting it
76 /// under another does not describe any product the annex can grade. Checked
77 /// rather than assumed, because the failure is silent: the index would be
78 /// computed against whichever weight set the first parameter happened to
79 /// suggest.
80 #[must_use]
81 pub const fn foldable_is_consistent(&self) -> bool {
82 let a = self.disassembly_depth.is_foldable();
83 let b = self.fasteners.is_foldable();
84 let c = self.tools.is_foldable();
85 a == b && b == c
86 }
87
88 /// Whether every score, part-level and product-level, is within 1–5.
89 #[must_use]
90 pub fn scores_are_in_range(&self) -> bool {
91 let in_range = |s: u8| (MIN_SCORE..=MAX_SCORE).contains(&s);
92 self.disassembly_depth.scores_are_in_range()
93 && self.fasteners.scores_are_in_range()
94 && self.tools.scores_are_in_range()
95 && in_range(self.spare_parts)
96 && in_range(self.software_updates)
97 && in_range(self.repair_information)
98 }
99}