Skip to main content

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}