openstudio-ee 0.12.0 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.coverage +0 -0
  3. data/.github/workflows/test-with-openstudio.yml +109 -74
  4. data/.gitignore +21 -0
  5. data/.rubocop.yml +15 -2
  6. data/CHANGELOG.md +12 -0
  7. data/Gemfile +7 -8
  8. data/README.md +5 -1
  9. data/WORKFLOW_CHANGES.md +74 -0
  10. data/doc_templates/LICENSE.md +1 -1
  11. data/lib/measures/AddDaylightSensors/measure.rb +79 -79
  12. data/lib/measures/AddDaylightSensors/measure.xml +5 -5
  13. data/lib/measures/AddOverhangsByProjectionFactor/measure.rb +38 -41
  14. data/lib/measures/AddOverhangsByProjectionFactor/measure.xml +5 -5
  15. data/lib/measures/EnableDemandControlledVentilation/measure.rb +37 -40
  16. data/lib/measures/EnableDemandControlledVentilation/measure.xml +5 -5
  17. data/lib/measures/EnableEconomizerControl/measure.rb +36 -37
  18. data/lib/measures/EnableEconomizerControl/measure.xml +5 -5
  19. data/lib/measures/GLHEProExportLoadsforGroundHeatExchangerSizing/measure.rb +27 -41
  20. data/lib/measures/GLHEProExportLoadsforGroundHeatExchangerSizing/measure.xml +5 -5
  21. data/lib/measures/GLHEProGFunctionImport/measure.rb +11 -15
  22. data/lib/measures/GLHEProGFunctionImport/measure.xml +5 -5
  23. data/lib/measures/GLHEProSetupExportLoadsforGroundHeatExchangerSizing/measure.rb +5 -9
  24. data/lib/measures/GLHEProSetupExportLoadsforGroundHeatExchangerSizing/measure.xml +4 -4
  25. data/lib/measures/ImproveFanBeltEfficiency/measure.rb +78 -95
  26. data/lib/measures/ImproveFanBeltEfficiency/measure.xml +7 -7
  27. data/lib/measures/ImproveMotorEfficiency/measure.rb +75 -100
  28. data/lib/measures/ImproveMotorEfficiency/measure.xml +7 -7
  29. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.rb +137 -130
  30. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.xml +5 -5
  31. data/lib/measures/IncreaseInsulationRValueForExteriorWallsByPercentage/measure.rb +114 -115
  32. data/lib/measures/IncreaseInsulationRValueForExteriorWallsByPercentage/measure.xml +4 -4
  33. data/lib/measures/IncreaseInsulationRValueForRoofs/measure.rb +137 -130
  34. data/lib/measures/IncreaseInsulationRValueForRoofs/measure.xml +5 -5
  35. data/lib/measures/IncreaseInsulationRValueForRoofsByPercentage/measure.rb +114 -115
  36. data/lib/measures/IncreaseInsulationRValueForRoofsByPercentage/measure.xml +4 -4
  37. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.rb +69 -63
  38. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.xml +7 -7
  39. data/lib/measures/ReduceLightingLoadsByPercentage/measure.rb +77 -66
  40. data/lib/measures/ReduceLightingLoadsByPercentage/measure.xml +7 -7
  41. data/lib/measures/ReduceNightTimeElectricEquipmentLoads/measure.rb +45 -43
  42. data/lib/measures/ReduceNightTimeElectricEquipmentLoads/measure.xml +5 -5
  43. data/lib/measures/ReduceNightTimeLightingLoads/measure.rb +45 -43
  44. data/lib/measures/ReduceNightTimeLightingLoads/measure.xml +5 -5
  45. data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.rb +58 -52
  46. data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.xml +7 -7
  47. data/lib/measures/ReduceVentilationByPercentage/measure.rb +49 -46
  48. data/lib/measures/ReduceVentilationByPercentage/measure.xml +7 -7
  49. data/lib/measures/add_variable_speed_rtu_control_logic/measure.rb +31 -23
  50. data/lib/measures/add_variable_speed_rtu_control_logic/measure.xml +5 -5
  51. data/lib/measures/create_variable_speed_rtu/measure.rb +166 -174
  52. data/lib/measures/create_variable_speed_rtu/measure.xml +7 -7
  53. data/lib/measures/fan_assist_night_ventilation/measure.rb +33 -32
  54. data/lib/measures/fan_assist_night_ventilation/measure.xml +5 -5
  55. data/lib/measures/nze_hvac/measure.rb +72 -62
  56. data/lib/measures/nze_hvac/measure.xml +5 -5
  57. data/lib/measures/replace_water_heater_mixed_with_thermal_storage_chilled_water/measure.rb +16 -19
  58. data/lib/measures/replace_water_heater_mixed_with_thermal_storage_chilled_water/measure.xml +5 -5
  59. data/lib/measures/window_enhancement/LICENSE.md +14 -0
  60. data/lib/measures/window_enhancement/README.md +112 -0
  61. data/lib/measures/window_enhancement/docs/.gitkeep +0 -0
  62. data/lib/measures/window_enhancement/measure.py +386 -0
  63. data/lib/measures/window_enhancement/measure.xml +128 -0
  64. data/lib/measures/window_enhancement/resources/EC3_lookup.py +321 -0
  65. data/lib/measures/window_enhancement/resources/Test_API.py +32 -0
  66. data/lib/measures/window_enhancement/resources/__pycache__/EC3_lookup.cpython-39.pyc +0 -0
  67. data/lib/measures/window_enhancement/resources/__pycache__/Original_EC3_lookup.py +322 -0
  68. data/lib/measures/window_enhancement/resources/__pycache__/Test_API.cpython-39.pyc +0 -0
  69. data/lib/measures/window_enhancement/resources/calculate_perimeter.py +39 -0
  70. data/lib/measures/window_enhancement/test_output.log +39 -0
  71. data/lib/openstudio/ee_measures/version.rb +1 -1
  72. data/openstudio-ee.gemspec +11 -9
  73. data/test-workflow-locally.sh +152 -0
  74. metadata +66 -37
  75. data/Jenkinsfile +0 -11
@@ -0,0 +1,321 @@
1
+ # EC3 API Lookup Script
2
+ import json
3
+ import re
4
+ from typing import Dict, Any, List
5
+ from pathlib import Path
6
+ import configparser
7
+ from datetime import datetime
8
+ import numpy as np
9
+ import os
10
+
11
+ script_dir = os.path.dirname(os.path.abspath(__file__))
12
+ repo_root = os.path.abspath(os.path.join(script_dir, "../../../.."))
13
+ config_path = os.path.join(repo_root, "config.ini")
14
+
15
+ # this measure doesn't function without EC3 token and required Python libraries installed
16
+ if not os.path.exists(config_path):
17
+ raise FileNotFoundError(f"Config file not found: {config_path}. Please setup your EC3 token before attempting to run this measure.")
18
+
19
+ # load custom libraries after confirming if config.ini
20
+ import requests
21
+
22
+ config = configparser.ConfigParser()
23
+ config.read(config_path)
24
+ API_TOKEN= config["EC3_API_TOKEN"]["API_TOKEN"]
25
+
26
+ # #find material_name by category
27
+ # material_category = {"concrete":{"ReadyMix","PrecastConcrete","CementGrout","FlowableFill"},
28
+ # "masonry":{"Brick", "CMU"},
29
+ # "steel":{"RebarSteel","WireMeshSteel","ColdFormedSteel","StructuralSteel"},
30
+ # "aluminum":{"AluminiumExtrusions"},
31
+ # "wood":{"PrefabricatedWood","DimensionLumbe","SheathingPanels","CompositeLumber","MassTimber","NonStructuralWood"},
32
+ # "sheathing":{"GypsumSheathingBoard","CementitiousSheathingBoard"},
33
+ # "thermal_moisture_protection":{"Insulation","MembraneRoofing"},
34
+ # "cladding":{"RoofPanels","InsulatedRoofPanels","WallPanels","InsulatedWallPanels"},
35
+ # "openings":{"glazing":["InsulatingGlazingUnits","FlatGlassPanes","ProcessedNonInsulatingGlassPanes"],
36
+ # "extrusions":["AluminiumExtrusions"]},
37
+ # "finishes":{"CementBoard","Gypsum","Tiling","CeilingPanel","Flooring","PaintingAndCoating"}
38
+ # }
39
+ # for testing use, do not delete
40
+ material_category = {
41
+ "test":["InsulatingGlazingUnits"]
42
+ }
43
+
44
+ def generate_url(material_name, endpoint ="materials", page_number=1, page_size=250, jurisdiction="021", date=None, option=None, boolean="yes",
45
+ glass_panes=None, epd_type="Product"):
46
+ '''
47
+ jurisdiction = "021" means Northern America region
48
+ '''
49
+ if date is None:
50
+ date = datetime.today().strftime("%Y-%m-%d") # use today's date as default
51
+ url = (
52
+ f"https://api.buildingtransparency.org/api/{endpoint}"
53
+ f"?page_number={page_number}&page_size={page_size}"
54
+ f"&mf=!EC3%20search(%22{material_name}%22)%20WHERE%20"
55
+ f"%0A%20%20jurisdiction%3A%20IN(%22{jurisdiction}%22)%20AND%0A%20%20"
56
+ f"epd__date_validity_ends%3A%20%3E%20%22{date}%22%20AND%0A%20%20"
57
+ f"epd_types%3A%20IN(%22{epd_type}%20EPDs%22)%20"
58
+ )
59
+ conditions = []
60
+ if option and boolean:
61
+ conditions.append(f"{option}%3A%20{boolean}")
62
+
63
+ if glass_panes:
64
+ conditions.append(f"glass_panes%3A%20%3E~%20{glass_panes}")
65
+
66
+ if conditions:
67
+ url += "AND%0A%20%20" + "%20AND%0A%20%20".join(conditions) + "%20%0A"
68
+
69
+ url += "!pragma%20eMF(%222.0%2F1%22)%2C%20lcia(%22TRACI%202.1%22)"
70
+
71
+ return url
72
+
73
+ def fetch_epd_data(url,api_token):
74
+ """
75
+ input url address generted by generate_url()
76
+ Fetch EPD data from the EC3 API.
77
+ return: Parsed JSON response or empty list on failure.
78
+ """
79
+ try:
80
+ print(f"Fetching data from URL: {url}") # Log the URL being fetched
81
+ # API configuration
82
+ HEADERS = {"Accept": "application/json", "Authorization": "Bearer " + api_token}
83
+ response = requests.get(url, headers=HEADERS, verify=False)
84
+ response.raise_for_status() # HTTPError if failure
85
+ return response.json()
86
+ except requests.exceptions.RequestException as e:
87
+ print(f"Error fetching data from {url}: {e}")
88
+ if 'response' in locals(): # Check if response was defined
89
+ print(f"Response content: {response.text}")
90
+ else:
91
+ print("No response content available.")
92
+ return []
93
+
94
+ def parse_product_epd(epd: Dict[str, Any]) -> Dict[str, Any]:
95
+ """
96
+ Parse GWP data for a given EPD.
97
+ :param epd: EPD dictionary
98
+ :return: Parsed GWP data
99
+ """
100
+ # initialize parameters and dictionary
101
+ parsed_data = {}
102
+ gwp_per_m3 = 0.0
103
+ gwp_per_m2 = 0.0
104
+ gwp_per_kg = 0.0
105
+
106
+ # extract information from EPD's json repsonse
107
+ declared_unit = epd.get("declared_unit")
108
+ thickness = epd.get("thickness")
109
+ gwp_per_declared_unit = epd.get("gwp")
110
+ mass_per_declared_unit = epd.get("mass_per_declared_unit")
111
+ density = epd.get("density")
112
+ gwp_per_kg = epd.get("gwp_per_kg")
113
+ epd_name = epd.get('name')
114
+ description = epd.get('description')
115
+ original_ec3_link = epd['manufacturer']['original_ec3_link']
116
+
117
+ # Per mass
118
+ if gwp_per_kg != None:
119
+ gwp_per_kg = extract_numeric_value(gwp_per_kg)
120
+
121
+ elif gwp_per_kg == None and "t" in declared_unit:
122
+ gwp_per_kg = divide(gwp_per_declared_unit, declared_unit)
123
+ gwp_per_kg = gwp_per_kg/1000 # convert from t to kg
124
+
125
+ elif gwp_per_kg == None and "kg" in declared_unit:
126
+ gwp_per_kg = divide(gwp_per_declared_unit, declared_unit)
127
+
128
+ elif mass_per_declared_unit != None and gwp_per_kg == None and not any(unit in declared_unit for unit in ["kg", "t"]):
129
+ gwp_per_kg = divide(gwp_per_declared_unit, mass_per_declared_unit)
130
+
131
+ # Per volume
132
+ if "m3" in declared_unit:
133
+ gwp_per_m3 = divide(gwp_per_declared_unit, declared_unit)
134
+
135
+ elif "cf" in declared_unit:
136
+ gwp_per_m3 = divide(gwp_per_declared_unit, declared_unit)
137
+ gwp_per_m3 = gwp_per_m3 * 35.3147 # convert from cubic feet to m3
138
+
139
+ elif "m2" in declared_unit and thickness and "mm" in thickness:
140
+ gwp_per_m2 = divide(gwp_per_declared_unit, declared_unit)
141
+ gwp_per_m3 = gwp_per_m2/(extract_numeric_value(thickness)/1000)
142
+
143
+ elif density and "kg / m3" in density and gwp_per_kg:
144
+ gwp_per_m3 = multiply(gwp_per_kg, density)
145
+
146
+ # Per area
147
+ if "m2" in declared_unit:
148
+ gwp_per_m2 = divide(gwp_per_declared_unit, declared_unit)
149
+
150
+ elif "sf" in declared_unit:
151
+ gwp_per_m2 = divide(gwp_per_declared_unit, declared_unit)
152
+ gwp_per_m2 = gwp_per_m2 * 10.7639 # convert from square feet to m2
153
+
154
+ elif "m3" in declared_unit and thickness and "mm" in thickness:
155
+ gwp_per_m3 = divide(gwp_per_declared_unit, declared_unit)
156
+ gwp_per_m2 = gwp_per_m3 * (extract_numeric_value(thickness)/1000)
157
+
158
+ parsed_data["epd_name"] = epd_name
159
+ parsed_data["declared_unit"] = declared_unit
160
+ parsed_data["gwp_per_declared_unit"] = gwp_per_declared_unit
161
+ parsed_data["mass_per_declared_unit"] = mass_per_declared_unit
162
+ parsed_data["thickness"] = thickness
163
+ parsed_data["density"] = density
164
+ parsed_data["gwp_per_m3 (kg CO2 eq/m3)"] = gwp_per_m3
165
+ parsed_data["gwp_per_m2 (kg CO2 eq/m2)"] = gwp_per_m2
166
+ parsed_data["gwp_per_kg (kg CO2 eq/kg)"] = gwp_per_kg
167
+ parsed_data["original_ec3_link"] = original_ec3_link
168
+ parsed_data["description"] = description
169
+
170
+ return parsed_data
171
+
172
+ def parse_industrial_epd(epd: Dict[str, Any]) -> Dict[str, Any]:
173
+ """
174
+ Parse GWP data for a given EPD.
175
+ :param epd: EPD dictionary
176
+ :return: Parsed GWP data
177
+ """
178
+
179
+ parsed_data = {}
180
+ gwp_per_m3 = 0.0
181
+ gwp_per_m2 = 0.0
182
+ gwp_per_kg = 0.0
183
+
184
+ declared_unit = epd.get("declared_unit")
185
+ gwp_per_kg = epd.get("gwp_per_kg")
186
+ gwp_per_declared_unit = epd.get("gwp")
187
+ epd_name = epd.get('name')
188
+ original_ec3_link = epd.get('original_ec3_link')
189
+ description = epd.get('description')
190
+ density_min = epd.get('density_min')
191
+ density_max = epd.get('density_max')
192
+ area = epd.get('area')
193
+
194
+ if density_min is not None and density_max is not None:
195
+ density_avg = (extract_numeric_value(density_max) + extract_numeric_value(density_min))/2
196
+ elif density_min is not None:
197
+ density_avg = extract_numeric_value(density_min)
198
+ elif density_max is not None:
199
+ density_avg = extract_numeric_value(density_max)
200
+ else:
201
+ density_avg = None
202
+
203
+ # Per area (stop using this becasue the area value is not sensible)
204
+ # if "m^2" in area:
205
+ # gwp_per_m2 = extract_numeric_value(gwp_per_declared_unit)/extract_numeric_value(area)
206
+
207
+ # Per mass
208
+ if "t" in declared_unit:
209
+ gwp_per_kg = divide(gwp_per_declared_unit, declared_unit)
210
+ gwp_per_kg = gwp_per_kg / 1000 # convert to kg
211
+ elif "kg" in declared_unit:
212
+ gwp_per_kg = divide(gwp_per_declared_unit, declared_unit)
213
+
214
+ # Per volume
215
+ if gwp_per_kg != None and density_avg != None:
216
+ gwp_per_m3 = gwp_per_kg * density_avg
217
+
218
+ parsed_data["epd_name"] = epd_name
219
+ parsed_data["declared_unit"] = declared_unit
220
+ parsed_data["gwp_per_declared_unit"] = gwp_per_declared_unit
221
+ parsed_data["density_min"] = density_min
222
+ parsed_data["density_max"] = density_max
223
+ parsed_data["area"] = area
224
+ parsed_data["gwp_per_m3 (kg CO2 eq/m3)"] = gwp_per_m3
225
+ parsed_data["gwp_per_m2 (kg CO2 eq/m2)"] = gwp_per_m2
226
+ parsed_data["gwp_per_kg (kg CO2 eq/kg)"] = gwp_per_kg
227
+ parsed_data["original_ec3_link"] = original_ec3_link
228
+ parsed_data["description"] = description
229
+
230
+ return parsed_data
231
+
232
+ def extract_numeric_value(value: Any) -> float:
233
+ """
234
+ Extract numeric value from a string or number.
235
+ :param value: Value to process
236
+ :return: Extracted numeric value
237
+ """
238
+ match = re.search(r"[-+]?\d*\.?\d+", str(value))
239
+ return float(match.group()) if match else 0.0
240
+
241
+ # extract numeric values then divide
242
+ def divide(member: Any, denominator: Any) -> float:
243
+ try:
244
+ member_value = extract_numeric_value(member)
245
+ denominator_value = extract_numeric_value(denominator)
246
+ return round(member_value/denominator_value, 2)
247
+ except ZeroDivisionError:
248
+ return 0.0
249
+
250
+ # extract numeric values then multiply
251
+ def multiply(multiplicand: Any, multiplier: Any) -> float:
252
+
253
+ multiplicand_value = extract_numeric_value(multiplicand)
254
+ multiplier_value = extract_numeric_value(multiplier)
255
+ return round(multiplicand_value * multiplier_value, 2)
256
+
257
+ def calculate_geometry(self, sub_surface):
258
+ """
259
+ Calculate the length, width, perimeter, and area of the window from its vertices.
260
+ Assumes the window is a quadrilateral (typically a rectangle).
261
+ """
262
+ vertices = sub_surface.vertices()
263
+ if len(vertices) != 4:
264
+ return {
265
+ "length": 0.0,
266
+ "width": 0.0,
267
+ "perimeter": 0.0,
268
+ "area": 0.0
269
+ }
270
+
271
+ # Calculate all edge lengths
272
+ edge_lengths = []
273
+ perimeter = 0.0
274
+ for i in range(4):
275
+ v1 = vertices[i]
276
+ v2 = vertices[(i + 1) % 4]
277
+ edge = v2 - v1
278
+ length = edge.length()
279
+ edge_lengths.append(length)
280
+ perimeter += length
281
+
282
+ # Assume opposite edges are equal, so we can group into two unique lengths
283
+ length = max(edge_lengths)
284
+ width = min(edge_lengths)
285
+
286
+ # Area = length × width
287
+ area = length * width
288
+
289
+ return {
290
+ "length": length,
291
+ "width": width,
292
+ "perimeter": perimeter,
293
+ "area": area
294
+ }
295
+
296
+ def main():
297
+ """
298
+ Main function to execute the script.
299
+ """
300
+ print("Fetching EC3 EPD data...")
301
+
302
+ for category, list in material_category.items():
303
+ print(material_category[category])
304
+ for name in list:
305
+ print(name)
306
+ product_url = generate_url(name, endpoint="materials", epd_type="Product")
307
+ industry_url = generate_url(name, endpoint="industry_epds", epd_type="Industry")
308
+
309
+ product_epd_data = fetch_epd_data(product_url,API_TOKEN)
310
+ industrial_epd_data = fetch_epd_data(industry_url,API_TOKEN)
311
+ print(f"Number of product EPDs for {name}: {len(product_epd_data)}")
312
+ print(f"Number of industrial EPDs for {name}: {len(industrial_epd_data)}")
313
+ for idx, epd in enumerate(product_epd_data, start=1):
314
+ parsed_data = parse_product_epd(epd)
315
+ print(f"Product EPD #{idx}: {json.dumps(parsed_data, indent=4)}")
316
+ for idx, epd in enumerate(industrial_epd_data, start=1):
317
+ parsed_data = parse_industrial_epd(epd)
318
+ print(f"Industrial EPD #{idx}: {json.dumps(parsed_data, indent=4)}")
319
+
320
+ if __name__ == "__main__":
321
+ main()
@@ -0,0 +1,32 @@
1
+ # Test EC3 API Call
2
+ import requests
3
+ import pprint
4
+ import os
5
+ import configparser
6
+
7
+ script_dir = os.path.dirname(os.path.abspath(__file__))
8
+ repo_root = os.path.abspath(os.path.join(script_dir, "../../../.."))
9
+ config_path = os.path.join(repo_root, "config.ini")
10
+
11
+ if not os.path.exists(config_path):
12
+ raise FileNotFoundError(f"Config file not found: {config_path}")
13
+
14
+ config = configparser.ConfigParser()
15
+ config.read(config_path)
16
+ API_TOKEN= config["EC3_API_TOKEN"]["API_TOKEN"]
17
+
18
+ test_url = ("https://api.buildingtransparency.org/api/materials?page_number=1&page_size=25&mf=!EC3%20search(%22InsulatingGlazingUnits%22)%20WHERE%20%0A%20%20jurisdiction%3A%20IN(%22021%22)%20AND%0A%20%20epd__date_validity_ends%3A%20%3E%20%222025-04-18%22%20AND%0A%20%20epd_types%3A%20IN(%22Product%20EPDs%22)%20%0A!pragma%20eMF(%222.0%2F1%22)%2C%20lcia(%22TRACI%202.1%22)")
19
+ # Headers for the request
20
+ headers = {
21
+ "Accept": "application/json",
22
+ "Authorization": "Bearer "+ API_TOKEN
23
+ }
24
+
25
+ # Execute the GET request
26
+ test_response = requests.get(test_url, headers=headers, verify=False)
27
+
28
+ # Parse the JSON response
29
+ test_response = test_response.json()
30
+ pprint.pp(test_response)
31
+ # Print the response and the number of EPDs
32
+ print(f"Number of EPDs: {len(test_response)}")
@@ -0,0 +1,322 @@
1
+ # EC3 API Call
2
+ import requests
3
+ import json
4
+ import re
5
+ import pprint
6
+ import statistics
7
+
8
+ print("EC3 API Call:")
9
+
10
+ EC3_category_array = {
11
+ "ConstructionMaterials": {
12
+ "Concrete": [
13
+ "ReadyMix",
14
+ "Shotcrete",
15
+ "CementGrout",
16
+ "FlowableFill",
17
+ "Precast",
18
+ "Rebar",
19
+ "WireMeshSteel",
20
+ "SCM",
21
+ "Cement",
22
+ "Aggregates"
23
+ ],
24
+ "Masonry": [
25
+ "Brick",
26
+ "ConcreteUnitMasonry",
27
+ "Mortar",
28
+ "Cementitious",
29
+ "Aggregates"
30
+ ],
31
+ "Steel": [],
32
+ "Aluminum": [],
33
+ "Wood": [],
34
+ "Sheathing": [],
35
+ "ThermalMoistureProtection": [],
36
+ "Cladding": [],
37
+ "Openings": [],
38
+ "Finishes": [],
39
+ "ConveyingEquipment": [],
40
+ "NetworkInfrastrucure": [],
41
+ "Asphalt": [],
42
+ "Accessories": [],
43
+ "ManufacturingInputs": [],
44
+ "BulkMaterials": [],
45
+ "Placeholders": []
46
+ },
47
+ "BuildingAssemblies": {
48
+ "NewCoustomAssembly": [],
49
+ "ReinforcedConcrete": [],
50
+ "Walls": [],
51
+ "Floors": [],
52
+ "GlazingFenesration": [],
53
+ },
54
+ }
55
+
56
+ # Define the URL with query parameters (FIXME: Not including API key in public repo. Will need to be included in a gitignored config.ini file)
57
+ windows_url = (
58
+ "https://api.buildingtransparency.org/api/epds"
59
+ "?page_number=1&page_size=25&fields=id%2Copen_xpd_uuid%2Cis_failed%2Cfailures%2Cerrors%2Cwarnings"
60
+ "%2Cdate_validity_ends%2Ccqd_sync_unlocked%2Cmy_capabilities%2Coriginal_data_format%2Ccategory"
61
+ "%2Cdisplay_name%2Cmanufacturer%2Cplant_or_group%2Cname%2Cdescription%2Cprogram_operator"
62
+ "%2Cprogram_operator_fkey%2Cverifier%2Cdeveloper%2Cmatched_plants_count%2Cplant_geography%2Cpcr"
63
+ "%2Cshort_name%2Cversion%2Cdate_of_issue%2Clanguage%2Cgwp%2Cuncertainty_adjusted_gwp%2Cdeclared_unit"
64
+ "%2Cupdated_on%2Ccorrections_count%2Cdeclaration_type%2Cbox_id%2Cis_downloadable"
65
+ "&sort_by=-updated_on&name__like=window&description__like=window&q=windows&plant_geography=US&declaration_type=Product%20EPD"
66
+ )
67
+
68
+ igu_url = (
69
+ "https://api.buildingtransparency.org/api/materials"
70
+ "?page_number=1&page_size=100"
71
+ "&mf=!EC3%20search(%22InsulatingGlazingUnits%22)%20WHERE%20"
72
+ "%0A%20%20jurisdiction%3A%20IN(%22021%22)%20AND%0A%20%20"
73
+ "epd__date_validity_ends%3A%20%3E%20%222024-12-05%22%20AND%0A%20%20"
74
+ "epd_types%3A%20IN(%22Product%20EPDs%22)%20"
75
+ "!pragma%20eMF(%222.0%2F1%22)%2C%20lcia(%22TRACI%202.1%22)"
76
+ )
77
+
78
+ wframe_url = (
79
+ "https://api.buildingtransparency.org/api/materials"
80
+ "?page_number=1&page_size=25"
81
+ "&mf=!EC3%20search(%22AluminiumExtrusions%22)%20WHERE%20"
82
+ "%0A%20%20jurisdiction%3A%20IN(%22021%22)%20AND%0A%20%20"
83
+ "epd__date_validity_ends%3A%20%3E%20%222024-12-09%22%20AND%0A%20%20"
84
+ "epd_types%3A%20IN(%22Product%20EPDs%22)%20"
85
+ "!pragma%20eMF(%222.0%2F1%22)%2C%20lcia(%22TRACI%202.1%22)"
86
+ )
87
+
88
+
89
+ gypsum_url = (
90
+ "https://api.buildingtransparency.org/api/materials"
91
+ "?page_number=1&page_size=25"
92
+ "&mf=!EC3%20search(%22Gypsum%22)%20WHERE%20"
93
+ "%0A%20%20jurisdiction%3A%20IN(%22US%22%2C%20%22CA%22)%20AND%0A%20%20"
94
+ "epd__date_validity_ends%3A%20%3E%20%222024-12-24%22%20AND%0A%20%20"
95
+ "epd_types%3A%20IN(%22Product%20EPDs%22)%20AND%0A%20%20"
96
+ "gypsum_fire_rating%3A%20IN(%22X%22)%20AND%0A%20%20"
97
+ "gypsum_thickness%3A%20IN(%220.625%20in%22)%20AND%0A%20%20"
98
+ "gypsum_facing%3A%20IN(%22Paper%22)%20"
99
+ "!pragma%20eMF(%222.0%2F1%22)%2C%20lcia(%22TRACI%202.1%22)"
100
+ )
101
+
102
+ # Headers for the request
103
+ headers = {
104
+ "Accept": "application/json",
105
+ "Authorization": "Bearer 5fk7wP4cJg6pcmx6ncZN0ftMdoVR8u"
106
+ }
107
+
108
+ # Execute the GET request
109
+ igu_response = requests.get(igu_url, headers=headers, verify=False)
110
+ wframe_response = requests.get(wframe_url, headers=headers, verify=False)
111
+
112
+ # Parse the JSON response
113
+ igu_response = igu_response.json()
114
+ wframe_response = wframe_response.json()
115
+ #print(igu_response)
116
+ # Print the response and the number of EPDs
117
+ print(f"Number of EPDs for IGU: {len(igu_response)}")
118
+ print(f"Number of EPDs for window frame: {len(wframe_response)}")
119
+
120
+ # Exception for EPD: 0
121
+ if len(igu_response) == 0:
122
+ print("No EPD returned for IGU, please change the search terms")
123
+ if len(wframe_response) == 0:
124
+ print("No EPD returned for window frame, please change the search terms")
125
+
126
+ # List of keys to exclude
127
+ exclude_keys = [
128
+ 'manufacturer',
129
+ 'plant_or_group',
130
+ 'category',
131
+ 'created_on',
132
+ 'updated_on',
133
+ 'gwp',
134
+ 'gwp_z',
135
+ 'pct80_gwp_per_category_declared_unit',
136
+ 'conservative_estimate',
137
+ 'best_practice',
138
+ 'standard_deviation',
139
+ 'uncertainty_factor',
140
+ 'uncertainty_adjusted_gwp',
141
+ 'lowest_plausible_gwp',
142
+ ]
143
+
144
+ # Create an empty list to store EPDs of IGU
145
+ igu_epd_data = {}
146
+ igu_gwp_per_volume = []
147
+ igu_gwp_per_mass = []
148
+ igu_gwp_per_area = []
149
+ wframe_epd_data = {}
150
+ wframe_gwp_per_volume = []
151
+ wframe_gwp_per_mass = []
152
+ wframe_gwp_per_area = []
153
+
154
+ for igu_epd_no, igu_epd in enumerate(igu_response, start=1):
155
+ print(f"========================================EPD No. {igu_epd_no}:==========================================")
156
+ # Initialize gwp per unit volume of IGU
157
+ gwp_per_unit_volume = "Not specified"
158
+ gwp_per_unit_area = "Not specified"
159
+ gwp_per_unit_mass = "Not specified"
160
+ # Create an empty list to store each key,value pair in an individual EPD object
161
+ igu_object = {}
162
+
163
+ for key, value in igu_epd.items():
164
+ if value is not None and key not in exclude_keys:
165
+ # Append the key-value pair to the list
166
+ igu_object[key] = value
167
+ # Extract the relevant keys
168
+ declared_unit = igu_epd.get("declared_unit")
169
+ thickness_unit = igu_epd.get("thickness")
170
+ gwp_per_category_declared_unit = igu_epd.get("gwp_per_category_declared_unit")
171
+ warnings = igu_epd.get("warnings")
172
+ mass_per_declared_unit = igu_epd.get("mass_per_declared_unit")
173
+ density_unit = igu_epd.get("density")
174
+ gwp_per_unit_mass = igu_epd.get("gwp_per_kg")
175
+
176
+ # Per volume
177
+ if declared_unit and "m3" in declared_unit:
178
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
179
+ declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(declared_unit)).group())
180
+ gwp_per_unit_volume = gwp_per_category_declared_unit_value/declared_unit_value
181
+
182
+ elif declared_unit and "m2" in declared_unit and thickness_unit and "mm" in str(thickness_unit):
183
+ thickness = float(re.search(r"[-+]?\d*\.?\d+", thickness_unit).group()) # Extract numeric part
184
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
185
+ thickness = thickness * 1e-3 # Convert thickness to meters
186
+ gwp_per_unit_volume = round(gwp_per_category_declared_unit_value / thickness,2)
187
+
188
+ elif density_unit and gwp_per_unit_mass:
189
+ density = float(re.search(r"[-+]?\d*\.?\d+", density_unit).group()) # Extract numeric part
190
+ gwp_per_unit_mass = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_unit_mass)).group())
191
+ gwp_per_unit_volume = gwp_per_unit_mass*density
192
+ gwp_per_unit_volume = round(gwp_per_unit_volume,2)
193
+
194
+ elif declared_unit and "t" in declared_unit and density_unit:
195
+ density = float(re.search(r"[-+]?\d*\.?\d+", density_unit).group()) # Extract numeric part
196
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
197
+ gwp_per_unit_volume = (gwp_per_category_declared_unit_value / 1000) * density
198
+ gwp_per_unit_volume = round(gwp_per_unit_volume,2)
199
+
200
+ # Per area
201
+ if declared_unit and "sf" in declared_unit:
202
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
203
+ declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(declared_unit)).group())
204
+ gwp_per_unit_area = (gwp_per_category_declared_unit_value/declared_unit_value)/0.092903
205
+ gwp_per_unit_area = round(gwp_per_unit_area,2)
206
+
207
+ igu_object["gwp_per_unit_volume"] = f"{gwp_per_unit_volume} kg CO2 e/m3"
208
+
209
+ if declared_unit and "m2" in declared_unit:
210
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
211
+ igu_object["gwp_per_unit_area"] = f"{gwp_per_category_declared_unit_value} kg CO2 e/m2"
212
+ else:
213
+ igu_object["gwp_per_unit_area"] = f"{gwp_per_unit_area} kg CO2 e/m2"
214
+
215
+ if gwp_per_unit_mass:
216
+ gwp_per_unit_mass = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_unit_mass)).group())
217
+ gwp_per_unit_mass = round(gwp_per_unit_mass,2)
218
+ igu_object["gwp_per_unit_mass"] = f"{gwp_per_unit_mass} kg CO2 e/kg"
219
+ else:
220
+ igu_object["gwp_per_unit_mass"] = "Not avaialble in this EPD"
221
+ object_key = "object" + str(igu_epd_no)
222
+ igu_epd_data[object_key] = igu_object
223
+ igu_gwp_per_volume.append(gwp_per_unit_volume)
224
+ igu_gwp_per_area.append(gwp_per_unit_area)
225
+ igu_gwp_per_mass.append(gwp_per_unit_mass)
226
+ pprint.pp(igu_object)
227
+ #print all gwp per volume
228
+ igu_gwp_per_volume = [float(item) for item in igu_gwp_per_volume if isinstance(item, (int, float)) or item.replace('.', '', 1).isdigit()]
229
+ # Calculate the mean
230
+ if igu_gwp_per_volume: # Check if the list is not empty
231
+ mean_value = statistics.mean(igu_gwp_per_volume)
232
+ print("Mean of GWP per volume:", mean_value)
233
+ else:
234
+ print("No numeric values to calculate mean.")
235
+ print(igu_gwp_per_volume)
236
+
237
+ for wframe_epd_no, wframe_epd in enumerate(wframe_response, start=1):
238
+ print(f"========================================EPD No. {wframe_epd_no}:==========================================")
239
+ # Initialize gwp per unit volume of window frame
240
+ gwp_per_unit_volume = "Not specified"
241
+ gwp_per_unit_area = "Not specified"
242
+ gwp_per_unit_mass = "Not specified"
243
+ # Create an empty list to store each key,value pair in an individual EPD object
244
+ wframe_object = {}
245
+
246
+ for key, value in wframe_epd.items():
247
+ if value is not None and key not in exclude_keys:
248
+ # Append the key-value pair to the list
249
+ wframe_object[key] = value
250
+ # Extract the relevant keys
251
+ declared_unit = wframe_epd.get("declared_unit")
252
+ thickness_unit = wframe_epd.get("thickness")
253
+ gwp_per_category_declared_unit = wframe_epd.get("gwp_per_category_declared_unit")
254
+ warnings = wframe_epd.get("warnings")
255
+ mass_per_declared_unit = wframe_epd.get("mass_per_declared_unit")
256
+ density_unit = wframe_epd.get("density")
257
+ gwp_per_unit_mass = wframe_epd.get("gwp_per_kg")
258
+
259
+ # Per volume
260
+ if declared_unit and "m3" in declared_unit:
261
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
262
+ declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(declared_unit)).group())
263
+ gwp_per_unit_volume = gwp_per_category_declared_unit_value/declared_unit_value
264
+
265
+ elif declared_unit and "m2" in declared_unit and thickness_unit and "mm" in str(thickness_unit):
266
+ thickness = float(re.search(r"[-+]?\d*\.?\d+", thickness_unit).group()) # Extract numeric part
267
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
268
+ thickness = thickness * 1e-3 # Convert thickness to meters
269
+ gwp_per_unit_volume = round(gwp_per_category_declared_unit_value / thickness,2)
270
+
271
+ elif density_unit and gwp_per_unit_mass:
272
+ density = float(re.search(r"[-+]?\d*\.?\d+", density_unit).group()) # Extract numeric part
273
+ gwp_per_unit_mass = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_unit_mass)).group())
274
+ gwp_per_unit_volume = gwp_per_unit_mass*density
275
+ gwp_per_unit_volume = round(gwp_per_unit_volume,2)
276
+
277
+ elif declared_unit and "t" in declared_unit and density_unit:
278
+ density = float(re.search(r"[-+]?\d*\.?\d+", density_unit).group()) # Extract numeric part
279
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
280
+ gwp_per_unit_volume = (gwp_per_category_declared_unit_value / 1000) * density
281
+ gwp_per_unit_volume = round(gwp_per_unit_volume,2)
282
+
283
+ # Per area
284
+ if declared_unit and "sf" in declared_unit:
285
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
286
+ declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(declared_unit)).group())
287
+ gwp_per_unit_area = (gwp_per_category_declared_unit_value/declared_unit_value)/0.092903
288
+ gwp_per_unit_area = round(gwp_per_unit_area,2)
289
+
290
+ wframe_object["gwp_per_unit_volume"] = f"{gwp_per_unit_volume} kg CO2 e/m3"
291
+
292
+ if declared_unit and "m2" in declared_unit:
293
+ gwp_per_category_declared_unit_value = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_category_declared_unit)).group())
294
+ wframe_object["gwp_per_unit_area"] = f"{gwp_per_category_declared_unit_value} kg CO2 e/m2"
295
+ else:
296
+ wframe_object["gwp_per_unit_area"] = f"{gwp_per_unit_area} kg CO2 e/m2"
297
+
298
+ if gwp_per_unit_mass:
299
+ gwp_per_unit_mass = float(re.search(r"[-+]?\d*\.?\d+", str(gwp_per_unit_mass)).group())
300
+ gwp_per_unit_mass = round(gwp_per_unit_mass,2)
301
+ wframe_object["gwp_per_unit_mass"] = f"{gwp_per_unit_mass} kg CO2 e/kg"
302
+ else:
303
+ wframe_object["gwp_per_unit_mass"] = "Not avaialble in this EPD"
304
+ object_key = "object" + str(wframe_epd_no)
305
+ wframe_epd_data[object_key] = wframe_object
306
+ wframe_gwp_per_volume.append(gwp_per_unit_volume)
307
+ wframe_gwp_per_area.append(gwp_per_unit_area)
308
+ wframe_gwp_per_mass.append(gwp_per_unit_mass)
309
+ pprint.pp(wframe_object)
310
+
311
+ #print all gwp per volume
312
+ wframe_gwp_per_volume = [float(item) for item in wframe_gwp_per_volume if isinstance(item, (int, float)) or item.replace('.', '', 1).isdigit()]
313
+ # Calculate the mean
314
+ if wframe_gwp_per_volume: # Check if the list is not empty
315
+ mean_value = statistics.mean(wframe_gwp_per_volume)
316
+ min_value = min(wframe_gwp_per_volume)
317
+ max_value = max(wframe_gwp_per_volume)
318
+ print("Mean of GWP per m3:", mean_value)
319
+ print("Minimum GWP per m3:", min_value)
320
+ print("Maximum GWP per m3:", max_value)
321
+ else:
322
+ print("No numeric values to calculate statistical parameters.")