Skip to main content

lcax_models/
project.rs

1use crate::assembly::AssemblyReference;
2use crate::life_cycle_base::{ImpactCategoryKey, Impacts, LifeCycleModule};
3use crate::shared::{MetaData, Unit};
4use lcax_core::country::Country;
5use lcax_core::utils::get_version;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9#[cfg(feature = "jsbindings")]
10use wasm_bindgen::prelude::*;
11
12#[cfg(feature = "jsbindings")]
13use tsify_next::Tsify;
14
15#[cfg(feature = "pybindings")]
16use pyo3::exceptions::PyTypeError;
17
18#[cfg(feature = "pybindings")]
19use pyo3::prelude::*;
20
21#[cfg(feature = "pybindings")]
22use pyo3::types::PyType;
23
24#[cfg(feature = "jsbindings")]
25use strum::IntoEnumIterator;
26
27#[cfg(feature = "jsbindings")]
28use strum_macros::EnumIter;
29
30#[derive(Deserialize, Serialize, JsonSchema, Default, Clone, PartialEq)]
31#[serde(rename_all = "camelCase")]
32#[cfg_attr(
33    feature = "jsbindings",
34    derive(Tsify),
35    tsify(into_wasm_abi, from_wasm_abi)
36)]
37#[cfg_attr(feature = "pybindings", pyclass(get_all, set_all))]
38pub struct Project {
39    pub id: String,
40    pub name: String,
41    pub description: Option<String>,
42    pub comment: Option<String>,
43    pub location: Location,
44    pub owner: Option<String>,
45    pub format_version: String,
46    pub lcia_method: Option<String>,
47    pub classification_systems: Option<Vec<String>>,
48    pub reference_study_period: Option<u8>,
49    pub life_cycle_modules: Vec<LifeCycleModule>,
50    pub impact_categories: Vec<ImpactCategoryKey>,
51    pub assemblies: Vec<AssemblyReference>,
52    pub results: Option<Impacts>,
53    pub project_info: Option<BuildingInfo>,
54    pub project_phase: ProjectPhase,
55    pub software_info: SoftwareInfo,
56    pub meta_data: Option<MetaData>,
57}
58
59#[cfg_attr(feature = "pybindings", pymethods)]
60impl Project {
61    #[cfg(feature = "pybindings")]
62    #[new]
63    #[pyo3(signature=(name, location, project_phase, software_info, life_cycle_modules, impact_categories, assemblies, id=None, description=None, comment=None, owner=None, format_version=None, lcia_method=None, classification_systems=None, reference_study_period=None, results=None, project_info=None, meta_data=None ))]
64    pub fn new_py(
65        name: &str,
66        location: Location,
67        project_phase: ProjectPhase,
68        software_info: SoftwareInfo,
69        life_cycle_modules: Vec<LifeCycleModule>,
70        impact_categories: Vec<ImpactCategoryKey>,
71        assemblies: Vec<AssemblyReference>,
72        id: Option<String>,
73        description: Option<String>,
74        comment: Option<String>,
75        owner: Option<String>,
76        format_version: Option<String>,
77        lcia_method: Option<String>,
78        classification_systems: Option<Vec<String>>,
79        reference_study_period: Option<u8>,
80        results: Option<Impacts>,
81        project_info: Option<BuildingInfo>,
82        meta_data: Option<MetaData>,
83    ) -> Project {
84        Project::new(
85            id,
86            name,
87            description,
88            comment,
89            location,
90            owner,
91            format_version,
92            lcia_method,
93            classification_systems,
94            reference_study_period,
95            life_cycle_modules,
96            impact_categories,
97            assemblies,
98            results,
99            project_info,
100            project_phase,
101            software_info,
102            meta_data,
103        )
104    }
105
106    #[cfg(feature = "pybindings")]
107    #[classmethod]
108    #[pyo3(name = "loads")]
109    pub fn loads_py(_cls: &Bound<'_, PyType>, value: &str) -> PyResult<Self> {
110        match Project::loads(value) {
111            Ok(project) => Ok(project),
112            Err(error) => Err(PyTypeError::new_err(error.to_string())),
113        }
114    }
115
116    #[cfg(feature = "pybindings")]
117    #[pyo3(name = "dumps")]
118    pub fn dumps_py(&self) -> PyResult<String> {
119        match Project::dumps(self) {
120            Ok(project) => Ok(project),
121            Err(error) => Err(PyTypeError::new_err(error.to_string())),
122        }
123    }
124    #[cfg(feature = "pybindings")]
125    fn __repr__(&self) -> String {
126        format!("Project: {}", self.id)
127    }
128
129    #[cfg(feature = "pybindings")]
130    fn __str__(&self) -> String {
131        self.__repr__()
132    }
133}
134
135impl Project {
136    pub fn new(
137        id: Option<String>,
138        name: &str,
139        description: Option<String>,
140        comment: Option<String>,
141        location: Location,
142        owner: Option<String>,
143        format_version: Option<String>,
144        lcia_method: Option<String>,
145        classification_systems: Option<Vec<String>>,
146        reference_study_period: Option<u8>,
147        life_cycle_modules: Vec<LifeCycleModule>,
148        impact_categories: Vec<ImpactCategoryKey>,
149        assemblies: Vec<AssemblyReference>,
150        results: Option<Impacts>,
151        project_info: Option<BuildingInfo>,
152        project_phase: ProjectPhase,
153        software_info: SoftwareInfo,
154        meta_data: Option<MetaData>,
155    ) -> Self {
156        let _id = id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
157        let _format_version = format_version.unwrap_or_else(|| get_version().to_string());
158        Project {
159            id: _id,
160            name: name.to_string(),
161            description,
162            comment,
163            location,
164            owner,
165            format_version: _format_version,
166            lcia_method,
167            classification_systems,
168            reference_study_period,
169            life_cycle_modules,
170            impact_categories,
171            assemblies,
172            results,
173            project_info,
174            project_phase,
175            software_info,
176            meta_data,
177        }
178    }
179
180    pub fn loads(value: &str) -> Result<Project, serde_json::Error> {
181        serde_json::from_str(value)
182    }
183
184    pub fn dumps(&self) -> Result<String, serde_json::Error> {
185        serde_json::to_string(self)
186    }
187
188    pub fn resolve_impact_categories(&mut self) {
189        self.impact_categories = match self.results {
190            Some(ref results) => {
191                let mut keys = Vec::new();
192                for (key, _) in results.iter() {
193                    keys.push(key.clone());
194                }
195                keys
196            }
197            None => vec![],
198        }
199    }
200    pub fn resolve_life_cycle_stages(&mut self) {
201        self.life_cycle_modules = match self.results {
202            Some(ref results) => {
203                let mut keys = Vec::new();
204                for (_, value) in results.iter() {
205                    for (key, _) in value.iter() {
206                        if keys.contains(key) {
207                            continue;
208                        }
209                        keys.push(key.clone());
210                    }
211                }
212                keys
213            }
214            None => vec![],
215        }
216    }
217}
218
219#[derive(Deserialize, Serialize, JsonSchema, Default, Clone, PartialEq)]
220#[serde(rename_all = "camelCase")]
221#[cfg_attr(feature = "jsbindings", derive(Tsify))]
222#[cfg_attr(feature = "pybindings", pyclass(get_all, set_all))]
223pub struct SoftwareInfo {
224    pub lca_software: String,
225    pub lca_software_version: Option<String>,
226    pub goal_and_scope_definition: Option<String>,
227    pub calculation_type: Option<String>,
228}
229
230#[cfg_attr(feature = "pybindings", pymethods)]
231impl SoftwareInfo {
232    #[cfg(feature = "pybindings")]
233    #[new]
234    #[pyo3(signature=(lca_software, lca_software_version=None, goal_and_scope_definition=None, calculation_type=None))]
235    pub fn new_py(
236        lca_software: String,
237        lca_software_version: Option<String>,
238        goal_and_scope_definition: Option<String>,
239        calculation_type: Option<String>,
240    ) -> SoftwareInfo {
241        SoftwareInfo::new(
242            lca_software,
243            lca_software_version,
244            goal_and_scope_definition,
245            calculation_type,
246        )
247    }
248}
249
250impl SoftwareInfo {
251    pub fn new(
252        lca_software: String,
253        lca_software_version: Option<String>,
254        goal_and_scope_definition: Option<String>,
255        calculation_type: Option<String>,
256    ) -> Self {
257        SoftwareInfo {
258            lca_software,
259            lca_software_version,
260            goal_and_scope_definition,
261            calculation_type,
262        }
263    }
264}
265
266#[derive(Deserialize, Serialize, JsonSchema, Default, Clone, PartialEq)]
267#[serde(rename_all = "lowercase")]
268#[cfg_attr(
269    feature = "jsbindings",
270    derive(Tsify, EnumIter),
271    tsify(into_wasm_abi, from_wasm_abi)
272)]
273#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
274pub enum ProjectPhase {
275    #[allow(non_camel_case_types)]
276    STRATEGIC_DESIGN,
277    #[allow(non_camel_case_types)]
278    CONCEPT_DESIGN,
279    #[allow(non_camel_case_types)]
280    TECHNICAL_DESIGN,
281    CONSTRUCTION,
282    #[allow(non_camel_case_types)]
283    POST_COMPLETION,
284    #[allow(non_camel_case_types)]
285    IN_USE,
286    #[default]
287    OTHER,
288}
289
290#[cfg(feature = "jsbindings")]
291#[allow(non_snake_case)]
292#[wasm_bindgen]
293pub fn projectPhases() -> Vec<ProjectPhase> {
294    ProjectPhase::iter().collect()
295}
296
297#[derive(Deserialize, Serialize, JsonSchema, Default, Clone, PartialEq)]
298#[serde(rename_all = "camelCase")]
299#[cfg_attr(feature = "jsbindings", derive(Tsify))]
300#[cfg_attr(feature = "pybindings", pyclass(get_all, set_all))]
301pub struct Location {
302    pub country: Country,
303    pub city: Option<String>,
304    pub address: Option<String>,
305}
306
307#[cfg_attr(feature = "pybindings", pymethods)]
308impl Location {
309    #[cfg(feature = "pybindings")]
310    #[new]
311    #[pyo3(signature=(country, city=None, address=None))]
312    pub fn new_py(country: Country, city: Option<String>, address: Option<String>) -> Location {
313        Location::new(country, city, address)
314    }
315}
316
317impl Location {
318    pub fn new(country: Country, city: Option<String>, address: Option<String>) -> Self {
319        Location {
320            country,
321            city,
322            address,
323        }
324    }
325}
326
327#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
328#[serde(rename_all = "camelCase")]
329#[cfg_attr(feature = "jsbindings", derive(Tsify))]
330#[cfg_attr(feature = "pybindings", pyclass(get_all, set_all))]
331pub struct BuildingInfo {
332    pub building_type: BuildingType,
333    pub building_typology: Vec<BuildingTypology>,
334    pub certifications: Option<Vec<String>>,
335    pub building_mass: Option<ValueUnit>,
336    pub building_height: Option<ValueUnit>,
337    pub gross_floor_area: Option<AreaType>,
338    pub heated_floor_area: Option<AreaType>,
339    pub building_footprint: Option<ValueUnit>,
340    pub floors_above_ground: u16,
341    pub floors_below_ground: Option<u16>,
342    pub roof_type: Option<RoofType>,
343    pub frame_type: Option<String>,
344    pub building_completion_year: Option<u16>,
345    pub building_permit_year: Option<u16>,
346    pub energy_demand_heating: Option<f64>,
347    pub energy_supply_heating: Option<f64>,
348    pub energy_demand_electricity: Option<f64>,
349    pub energy_supply_electricity: Option<f64>,
350    pub exported_electricity: Option<f64>,
351    pub general_energy_class: GeneralEnergyClass,
352    pub local_energy_class: Option<String>,
353    pub building_users: Option<u32>,
354    pub building_model_scope: Option<Vec<BuildingModelScope>>,
355}
356
357#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
358#[serde(rename_all = "camelCase")]
359#[cfg_attr(feature = "jsbindings", derive(Tsify))]
360#[cfg_attr(feature = "pybindings", pyclass(get_all, set_all))]
361pub struct AreaType {
362    pub value: f64,
363    pub unit: Unit,
364    pub definition: String,
365}
366
367#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
368#[serde(rename_all = "camelCase")]
369#[cfg_attr(feature = "jsbindings", derive(Tsify))]
370#[cfg_attr(feature = "pybindings", pyclass(get_all, set_all))]
371pub struct ValueUnit {
372    pub value: f64,
373    pub unit: Unit,
374}
375
376#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
377#[serde(rename_all = "lowercase")]
378#[cfg_attr(
379    feature = "jsbindings",
380    derive(Tsify, EnumIter),
381    tsify(into_wasm_abi, from_wasm_abi)
382)]
383#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
384pub enum RoofType {
385    FLAT,
386    PITCHED,
387    SADDLE,
388    PYRAMID,
389    UNKNOWN,
390    OTHER,
391}
392
393#[cfg(feature = "jsbindings")]
394#[allow(non_snake_case)]
395#[wasm_bindgen]
396pub fn roofTypes() -> Vec<RoofType> {
397    RoofType::iter().collect()
398}
399
400#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
401#[serde(rename_all = "lowercase")]
402#[cfg_attr(
403    feature = "jsbindings",
404    derive(Tsify, EnumIter),
405    tsify(into_wasm_abi, from_wasm_abi)
406)]
407#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
408pub enum GeneralEnergyClass {
409    EXISTING,
410    STANDARD,
411    ADVANCED,
412    UNKNOWN,
413}
414
415#[cfg(feature = "jsbindings")]
416#[allow(non_snake_case)]
417#[wasm_bindgen]
418pub fn generalEnergyClasses() -> Vec<GeneralEnergyClass> {
419    GeneralEnergyClass::iter().collect()
420}
421
422impl From<&String> for GeneralEnergyClass {
423    fn from(class: &String) -> Self {
424        match class.to_ascii_lowercase().as_str() {
425            "lowenergy" => GeneralEnergyClass::ADVANCED,
426            _ => GeneralEnergyClass::UNKNOWN,
427        }
428    }
429}
430
431#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
432#[serde(rename_all = "lowercase")]
433#[cfg_attr(
434    feature = "jsbindings",
435    derive(Tsify, EnumIter),
436    tsify(into_wasm_abi, from_wasm_abi)
437)]
438#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
439pub enum BuildingModelScope {
440    #[allow(non_camel_case_types)]
441    FACILITATING_WORKS,
442    SUBSTRUCTURE,
443    #[allow(non_camel_case_types)]
444    SUPERSTRUCTURE_FRAME,
445    #[allow(non_camel_case_types)]
446    SUPERSTRUCTURE_ENVELOPE,
447    #[allow(non_camel_case_types)]
448    SUPERSTRUCTURE_INTERNAL_ELEMENTS,
449    FINISHES,
450    #[allow(non_camel_case_types)]
451    BUILDING_SERVICES,
452    #[allow(non_camel_case_types)]
453    EXTERNAL_WORKS,
454    #[allow(non_camel_case_types)]
455    FF_E,
456}
457
458#[cfg(feature = "jsbindings")]
459#[allow(non_snake_case)]
460#[wasm_bindgen]
461pub fn buildingModelScopes() -> Vec<BuildingModelScope> {
462    BuildingModelScope::iter().collect()
463}
464
465#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
466#[serde(rename_all = "lowercase")]
467#[cfg_attr(
468    feature = "jsbindings",
469    derive(Tsify, EnumIter),
470    tsify(into_wasm_abi, from_wasm_abi)
471)]
472#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
473pub enum BuildingType {
474    #[allow(non_camel_case_types)]
475    NEW_CONSTRUCTION_WORKS,
476    DEMOLITION,
477    #[allow(non_camel_case_types)]
478    DECONSTRUCTION_AND_NEW_CONSTRUCTION_WORKS,
479    #[allow(non_camel_case_types)]
480    RETROFIT_WORKS,
481    #[allow(non_camel_case_types)]
482    EXTENSION_WORKS,
483    #[allow(non_camel_case_types)]
484    RETROFIT_AND_EXTENSION_WORKS,
485    #[allow(non_camel_case_types)]
486    FIT_OUT_WORKS,
487    OPERATIONS,
488    UNKNOWN,
489    OTHER,
490}
491
492#[cfg(feature = "jsbindings")]
493#[allow(non_snake_case)]
494#[wasm_bindgen]
495pub fn buildingTypes() -> Vec<BuildingType> {
496    BuildingType::iter().collect()
497}
498
499#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
500#[serde(rename_all = "lowercase")]
501#[cfg_attr(
502    feature = "jsbindings",
503    derive(Tsify, EnumIter),
504    tsify(into_wasm_abi, from_wasm_abi)
505)]
506#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int))]
507pub enum BuildingTypology {
508    OFFICE,
509    RESIDENTIAL,
510    PUBLIC,
511    COMMERCIAL,
512    INDUSTRIAL,
513    INFRASTRUCTURE,
514    AGRICULTURAL,
515    EDUCATIONAL,
516    HEALTH,
517    UNKNOWN,
518    OTHER,
519}
520
521#[cfg(feature = "jsbindings")]
522#[allow(non_snake_case)]
523#[wasm_bindgen]
524pub fn buildingTypologies() -> Vec<BuildingTypology> {
525    BuildingTypology::iter().collect()
526}
527
528impl From<&String> for BuildingTypology {
529    fn from(unit: &String) -> Self {
530        match unit.to_ascii_lowercase().as_str() {
531            "office" => BuildingTypology::OFFICE,
532            "residential" => BuildingTypology::RESIDENTIAL,
533            "public" => BuildingTypology::PUBLIC,
534            "commercial" => BuildingTypology::COMMERCIAL,
535            "industrial" => BuildingTypology::INDUSTRIAL,
536            "infrastructure" => BuildingTypology::INFRASTRUCTURE,
537            "agricultural" => BuildingTypology::AGRICULTURAL,
538            "educational" => BuildingTypology::EDUCATIONAL,
539            "health" => BuildingTypology::HEALTH,
540            _ => BuildingTypology::OTHER,
541        }
542    }
543}