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 &_ => panic!("Unknown impact type!"),
341 }
342 }
343}