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}