Skip to main content

lcax_models/
epd.rs

1use crate::life_cycle_base::Impacts;
2use crate::shared::{Conversion, MetaData, Reference, Source, Unit};
3use chrono::NaiveDate;
4use lcax_core::country::Country;
5use lcax_core::dates::{deserialize_yyyy_mm_dd, serialize_yyyy_mm_dd};
6use lcax_core::utils::get_version;
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10#[cfg(feature = "jsbindings")]
11use wasm_bindgen::prelude::*;
12
13#[cfg(feature = "jsbindings")]
14use tsify_next::Tsify;
15
16#[cfg(feature = "jsbindings")]
17use strum::IntoEnumIterator;
18
19#[cfg(feature = "jsbindings")]
20use strum_macros::EnumIter;
21
22#[cfg(feature = "pybindings")]
23use pyo3::exceptions::PyTypeError;
24#[cfg(feature = "pybindings")]
25use pyo3::prelude::*;
26#[cfg(feature = "pybindings")]
27use pyo3::types::PyType;
28
29#[derive(Serialize, Deserialize, JsonSchema, Clone, PartialEq)]
30#[serde(rename_all = "camelCase")]
31#[cfg_attr(
32    feature = "jsbindings",
33    derive(Tsify),
34    tsify(into_wasm_abi, from_wasm_abi)
35)]
36#[cfg_attr(feature = "pybindings", pyclass(get_all, set_all))]
37pub struct EPD {
38    pub id: String,
39    pub name: String,
40    pub declared_unit: Unit,
41    pub version: String,
42
43    #[serde(serialize_with = "serialize_yyyy_mm_dd")]
44    #[serde(deserialize_with = "deserialize_yyyy_mm_dd")]
45    #[cfg_attr(feature = "jsbindings", tsify(type = "Date"))]
46    pub published_date: NaiveDate,
47
48    #[serde(serialize_with = "serialize_yyyy_mm_dd")]
49    #[serde(deserialize_with = "deserialize_yyyy_mm_dd")]
50    #[cfg_attr(feature = "jsbindings", tsify(type = "Date"))]
51    pub valid_until: NaiveDate,
52
53    pub source: Option<Source>,
54    pub reference_service_life: Option<u32>,
55    pub standard: Standard,
56    pub comment: Option<String>,
57    pub location: Country,
58    pub subtype: SubType,
59    pub conversions: Option<Vec<Conversion>>,
60    pub impacts: Impacts,
61    pub meta_data: Option<MetaData>,
62}
63
64impl Default for EPD {
65    fn default() -> Self {
66        Self {
67            id: uuid::Uuid::new_v4().to_string(),
68            name: "".to_string(),
69            declared_unit: Unit::UNKNOWN,
70            version: get_version(),
71            published_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
72            valid_until: NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
73            source: None,
74            reference_service_life: None,
75            standard: Standard::UNKNOWN,
76            comment: None,
77            location: Country::UNKNOWN,
78            subtype: SubType::GENERIC,
79            conversions: None,
80            impacts: Impacts::new(),
81            meta_data: None,
82        }
83    }
84}
85
86#[cfg_attr(feature = "pybindings", pymethods)]
87impl EPD {
88    #[cfg(feature = "pybindings")]
89    #[new]
90    #[pyo3(signature=(name, declared_unit, version, published_date, valid_until, standard, location, subtype, impacts, id=None, source=None, reference_service_life=None, comment=None, conversions=None, meta_data=None ))]
91    pub fn new_py(
92        name: String,
93        declared_unit: Unit,
94        version: String,
95        published_date: NaiveDate,
96        valid_until: NaiveDate,
97        standard: Standard,
98        location: Country,
99        subtype: SubType,
100        impacts: Impacts,
101        id: Option<String>,
102        source: Option<Source>,
103        reference_service_life: Option<u32>,
104        comment: Option<String>,
105        conversions: Option<Vec<Conversion>>,
106        meta_data: Option<MetaData>,
107    ) -> Self {
108        let _id = id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
109        Self {
110            id: _id,
111            name,
112            declared_unit,
113            version,
114            published_date,
115            valid_until,
116            source,
117            reference_service_life,
118            standard,
119            comment,
120            location,
121            subtype,
122            conversions,
123            impacts,
124            meta_data,
125        }
126    }
127
128    #[cfg(feature = "pybindings")]
129    #[classmethod]
130    #[pyo3(name = "loads")]
131    pub fn loads_py(_cls: &Bound<'_, PyType>, value: &str) -> PyResult<Self> {
132        match EPD::loads(value) {
133            Ok(epd) => Ok(epd),
134            Err(error) => Err(PyTypeError::new_err(error.to_string())),
135        }
136    }
137
138    #[cfg(feature = "pybindings")]
139    #[pyo3(name = "dumps")]
140    pub fn dumps_py(&self) -> PyResult<String> {
141        match EPD::dumps(self) {
142            Ok(data) => Ok(data),
143            Err(error) => Err(PyTypeError::new_err(error.to_string())),
144        }
145    }
146
147    #[cfg(feature = "pybindings")]
148    fn __repr__(&self) -> String {
149        format!("EPD: {}", self.id)
150    }
151
152    #[cfg(feature = "pybindings")]
153    fn __str__(&self) -> String {
154        format!("EPD: {}", self.id)
155    }
156}
157
158impl EPD {
159    pub fn new(
160        id: Option<String>,
161        name: String,
162        declared_unit: Unit,
163        version: String,
164        published_date: NaiveDate,
165        valid_until: NaiveDate,
166        source: Option<Source>,
167        reference_service_life: Option<u32>,
168        standard: Standard,
169        comment: Option<String>,
170        location: Country,
171        subtype: SubType,
172        conversions: Option<Vec<Conversion>>,
173        impacts: Impacts,
174        meta_data: Option<MetaData>,
175    ) -> Self {
176        let _id = id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
177        Self {
178            id: _id,
179            name,
180            declared_unit,
181            version,
182            published_date,
183            valid_until,
184            source,
185            reference_service_life,
186            standard,
187            comment,
188            location,
189            subtype,
190            conversions,
191            impacts,
192            meta_data,
193        }
194    }
195
196    pub fn loads(value: &str) -> Result<EPD, serde_json::Error> {
197        serde_json::from_str(value)
198    }
199
200    pub fn dumps(&self) -> Result<String, serde_json::Error> {
201        serde_json::to_string(self)
202    }
203}
204
205#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
206#[serde(rename_all = "lowercase")]
207#[cfg_attr(
208    feature = "jsbindings",
209    derive(Tsify, EnumIter),
210    tsify(into_wasm_abi, from_wasm_abi)
211)]
212#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
213pub enum Standard {
214    EN15804A1,
215    EN15804A2,
216    UNKNOWN,
217}
218
219#[cfg(feature = "jsbindings")]
220#[allow(non_snake_case)]
221#[wasm_bindgen]
222pub fn standards() -> Vec<Standard> {
223    Standard::iter().collect()
224}
225
226impl From<&String> for Standard {
227    fn from(value: &String) -> Self {
228        if value.to_ascii_lowercase().contains("15804+a2") {
229            Standard::EN15804A2
230        } else if value.to_ascii_lowercase().contains("15804") {
231            Standard::EN15804A1
232        } else {
233            Standard::UNKNOWN
234        }
235    }
236}
237
238#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
239#[serde(rename_all = "lowercase")]
240#[cfg_attr(
241    feature = "jsbindings",
242    derive(Tsify, EnumIter),
243    tsify(into_wasm_abi, from_wasm_abi)
244)]
245#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
246pub enum SubType {
247    GENERIC,
248    SPECIFIC,
249    INDUSTRY,
250    REPRESENTATIVE,
251}
252
253#[cfg(feature = "jsbindings")]
254#[allow(non_snake_case)]
255#[wasm_bindgen]
256pub fn subTypes() -> Vec<SubType> {
257    SubType::iter().collect()
258}
259
260impl From<&Option<String>> for SubType {
261    fn from(value: &Option<String>) -> Self {
262        match value {
263            Some(_value) if _value.to_lowercase().contains("representative") => {
264                SubType::REPRESENTATIVE
265            }
266            Some(_value) if _value.to_lowercase().contains("specific") => SubType::SPECIFIC,
267            Some(_value) if _value.to_lowercase().contains("industry") => SubType::INDUSTRY,
268            _ => SubType::GENERIC,
269        }
270    }
271}
272
273impl Into<String> for SubType {
274    fn into(self) -> String {
275        match self {
276            SubType::REPRESENTATIVE => "representative".to_string(),
277            SubType::SPECIFIC => "specific".to_string(),
278            SubType::INDUSTRY => "industry".to_string(),
279            SubType::GENERIC => "generic".to_string(),
280        }
281    }
282}
283
284#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
285#[serde(rename_all = "camelCase")]
286#[serde(tag = "type")]
287#[cfg_attr(feature = "jsbindings", derive(Tsify))]
288#[cfg_attr(feature = "pybindings", derive(FromPyObject, IntoPyObject))]
289pub enum EPDReference {
290    #[serde(rename = "EPD")]
291    EPD(EPD),
292    Reference(Reference),
293}
294
295impl EPDReference {
296    pub fn resolve(&self) -> Result<&EPD, String> {
297        match self {
298            EPDReference::EPD(epd) => Ok(epd),
299            _ => Err("Handling of references not implemented yet!".to_string()),
300        }
301    }
302
303    pub fn new(
304        _type: &str,
305        id: Option<String>,
306        name: String,
307        declared_unit: Unit,
308        version: String,
309        published_date: NaiveDate,
310        valid_until: NaiveDate,
311        source: Option<Source>,
312        reference_service_life: Option<u32>,
313        standard: Standard,
314        comment: Option<String>,
315        location: Country,
316        subtype: SubType,
317        conversions: Option<Vec<Conversion>>,
318        impacts: Impacts,
319        meta_data: Option<MetaData>,
320    ) -> Self {
321        match _type {
322            "epd" => EPDReference::EPD(EPD::new(
323                id,
324                name,
325                declared_unit,
326                version,
327                published_date,
328                valid_until,
329                source,
330                reference_service_life,
331                standard,
332                comment,
333                location,
334                subtype,
335                conversions,
336                impacts,
337                meta_data,
338            )),
339            // "reference" => EPDReference::Reference(Reference::new()),
340            &_ => panic!("Unknown impact type!"),
341        }
342    }
343}