openbim_epd/lib.rs
1//! `openbim-epd` — ISO 22057 EPD data-template contracts.
2//!
3//! ISO 22057:2022 defines data templates for using environmental product
4//! declaration (EPD) information in building information modelling. It builds
5//! on the data-template concepts from ISO 23387 and the life-cycle modules used
6//! by construction-product EPDs.
7//!
8//! # No ISO 22057 XML schema
9//!
10//! ISO 22057 defines the information structure and mappings; it does **not**
11//! standardize one XML namespace, XSD, or wire encoding. Its informative mapping
12//! material references multiple established exchange formats. Consequently this
13//! crate deliberately exposes no invented ISO 22057 XML namespace and does not
14//! claim that XML parsing exists.
15//!
16//! # Status
17//!
18//! **Reserved scaffold.** The standard edition and all 18 EPD information-module
19//! codes (including aggregated `A1-A3`) are represented and tested. EPD parsing,
20//! writing, validation, data-template exchange, and format-specific adapters are
21//! not implemented.
22//!
23//! # Examples
24//!
25//! ```
26//! use openbim_epd::{InformationModule, InformationModuleGroup, StandardEdition};
27//!
28//! assert_eq!(StandardEdition::CURRENT.designation(), "ISO 22057:2022");
29//! assert_eq!(InformationModule::A1ToA3.code(), "A1-A3");
30//! assert_eq!(
31//! InformationModule::D.group(),
32//! InformationModuleGroup::BeyondSystemBoundary
33//! );
34//! ```
35//!
36//! # Repository boundary
37//!
38//! EPD consumes shared data-template and, eventually, IFC contracts. IFC, core,
39//! and codec crates must never depend on EPD. The `openbimrs/openbim`
40//! integration repository pins compatible family revisions without reversing
41//! that dependency direction.
42
43#![forbid(unsafe_code)]
44
45/// An edition of the EPD data-template standard represented by this crate.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
47pub enum StandardEdition {
48 /// ISO 22057:2022, the first published edition.
49 Iso22057_2022,
50}
51
52impl StandardEdition {
53 /// The edition used for new EPD data-template work.
54 pub const CURRENT: Self = Self::Iso22057_2022;
55
56 /// The normative standard designation.
57 #[must_use]
58 pub const fn designation(self) -> &'static str {
59 match self {
60 Self::Iso22057_2022 => "ISO 22057:2022",
61 }
62 }
63}
64
65/// A semantic grouping for EPD information-module codes.
66///
67/// This is deliberately broader than a life-cycle stage: module D represents
68/// benefits and loads beyond the product-system boundary.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
70pub enum InformationModuleGroup {
71 /// Product stage: modules A1 to A3 and the aggregated A1-A3 value.
72 Product,
73 /// Construction-process stage: modules A4 and A5.
74 ConstructionProcess,
75 /// Use stage: modules B1 to B7.
76 Use,
77 /// End-of-life stage: modules C1 to C4.
78 EndOfLife,
79 /// Benefits and loads beyond the product-system boundary: module D.
80 BeyondSystemBoundary,
81}
82
83/// One of the 18 EPD information-module codes from A1 through D.
84///
85/// `A1ToA3` represents the aggregated `A1-A3` value that appears alongside
86/// the individual A1, A2, and A3 values in the ISO 22057 template.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
88pub enum InformationModule {
89 /// Raw-material supply.
90 A1,
91 /// Transport to the manufacturer.
92 A2,
93 /// Manufacturing.
94 A3,
95 /// Aggregated product stage covering A1, A2, and A3.
96 A1ToA3,
97 /// Transport to the construction site.
98 A4,
99 /// Construction and installation.
100 A5,
101 /// Use.
102 B1,
103 /// Maintenance.
104 B2,
105 /// Repair.
106 B3,
107 /// Replacement.
108 B4,
109 /// Refurbishment.
110 B5,
111 /// Operational energy use.
112 B6,
113 /// Operational water use.
114 B7,
115 /// Deconstruction and demolition.
116 C1,
117 /// Transport during end of life.
118 C2,
119 /// Waste processing.
120 C3,
121 /// Disposal.
122 C4,
123 /// Benefits and loads beyond the system boundary.
124 D,
125}
126
127impl InformationModule {
128 /// Every module in normative order.
129 pub const ALL: [Self; 18] = [
130 Self::A1,
131 Self::A2,
132 Self::A3,
133 Self::A1ToA3,
134 Self::A4,
135 Self::A5,
136 Self::B1,
137 Self::B2,
138 Self::B3,
139 Self::B4,
140 Self::B5,
141 Self::B6,
142 Self::B7,
143 Self::C1,
144 Self::C2,
145 Self::C3,
146 Self::C4,
147 Self::D,
148 ];
149
150 /// The compact module code used by EPD datasets.
151 #[must_use]
152 pub const fn code(self) -> &'static str {
153 match self {
154 Self::A1 => "A1",
155 Self::A2 => "A2",
156 Self::A3 => "A3",
157 Self::A1ToA3 => "A1-A3",
158 Self::A4 => "A4",
159 Self::A5 => "A5",
160 Self::B1 => "B1",
161 Self::B2 => "B2",
162 Self::B3 => "B3",
163 Self::B4 => "B4",
164 Self::B5 => "B5",
165 Self::B6 => "B6",
166 Self::B7 => "B7",
167 Self::C1 => "C1",
168 Self::C2 => "C2",
169 Self::C3 => "C3",
170 Self::C4 => "C4",
171 Self::D => "D",
172 }
173 }
174
175 /// Resolves an exact, case-sensitive EPD module code.
176 #[must_use]
177 pub const fn from_code(code: &str) -> Option<Self> {
178 match code.as_bytes() {
179 b"A1" => Some(Self::A1),
180 b"A2" => Some(Self::A2),
181 b"A3" => Some(Self::A3),
182 b"A1-A3" => Some(Self::A1ToA3),
183 b"A4" => Some(Self::A4),
184 b"A5" => Some(Self::A5),
185 b"B1" => Some(Self::B1),
186 b"B2" => Some(Self::B2),
187 b"B3" => Some(Self::B3),
188 b"B4" => Some(Self::B4),
189 b"B5" => Some(Self::B5),
190 b"B6" => Some(Self::B6),
191 b"B7" => Some(Self::B7),
192 b"C1" => Some(Self::C1),
193 b"C2" => Some(Self::C2),
194 b"C3" => Some(Self::C3),
195 b"C4" => Some(Self::C4),
196 b"D" => Some(Self::D),
197 _ => None,
198 }
199 }
200
201 /// The semantic group containing this information-module code.
202 ///
203 /// Module D returns [`InformationModuleGroup::BeyondSystemBoundary`]; it is
204 /// deliberately not described as a life-cycle stage.
205 #[must_use]
206 pub const fn group(self) -> InformationModuleGroup {
207 match self {
208 Self::A1 | Self::A2 | Self::A3 | Self::A1ToA3 => InformationModuleGroup::Product,
209 Self::A4 | Self::A5 => InformationModuleGroup::ConstructionProcess,
210 Self::B1 | Self::B2 | Self::B3 | Self::B4 | Self::B5 | Self::B6 | Self::B7 => {
211 InformationModuleGroup::Use
212 }
213 Self::C1 | Self::C2 | Self::C3 | Self::C4 => InformationModuleGroup::EndOfLife,
214 Self::D => InformationModuleGroup::BeyondSystemBoundary,
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn current_edition_has_the_normative_designation() {
225 assert_eq!(StandardEdition::CURRENT.designation(), "ISO 22057:2022");
226 }
227
228 #[test]
229 fn information_modules_are_complete_and_round_trip() {
230 const EXPECTED_CODES: [&str; 18] = [
231 "A1", "A2", "A3", "A1-A3", "A4", "A5", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "C1",
232 "C2", "C3", "C4", "D",
233 ];
234
235 assert_eq!(
236 InformationModule::ALL.map(InformationModule::code),
237 EXPECTED_CODES
238 );
239
240 for module in InformationModule::ALL {
241 assert_eq!(InformationModule::from_code(module.code()), Some(module));
242 }
243 }
244
245 #[test]
246 fn information_modules_map_to_their_groups() {
247 assert_eq!(
248 InformationModule::A1.group(),
249 InformationModuleGroup::Product
250 );
251 assert_eq!(
252 InformationModule::A3.group(),
253 InformationModuleGroup::Product
254 );
255 assert_eq!(
256 InformationModule::A1ToA3.group(),
257 InformationModuleGroup::Product
258 );
259 assert_eq!(
260 InformationModule::A4.group(),
261 InformationModuleGroup::ConstructionProcess
262 );
263 assert_eq!(InformationModule::B7.group(), InformationModuleGroup::Use);
264 assert_eq!(
265 InformationModule::C4.group(),
266 InformationModuleGroup::EndOfLife
267 );
268 assert_eq!(
269 InformationModule::D.group(),
270 InformationModuleGroup::BeyondSystemBoundary
271 );
272 }
273
274 #[test]
275 fn unknown_module_codes_are_rejected() {
276 assert_eq!(InformationModule::from_code("A0"), None);
277 assert_eq!(InformationModule::from_code("a1"), None);
278 assert_eq!(InformationModule::from_code(""), None);
279 }
280}