openph 0.1.0__py3-none-any.whl

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 (40) hide show
  1. openph/__init__.py +11 -0
  2. openph/from_HBJSON/__init__.py +9 -0
  3. openph/from_HBJSON/create_phpp.py +648 -0
  4. openph/model/__init__.py +0 -0
  5. openph/model/areas.py +452 -0
  6. openph/model/climate.py +793 -0
  7. openph/model/components.py +42 -0
  8. openph/model/constants.py +30 -0
  9. openph/model/constructions.py +47 -0
  10. openph/model/enums.py +49 -0
  11. openph/model/envelope.py +642 -0
  12. openph/model/hvac/__init__.py +0 -0
  13. openph/model/hvac/hot_water.py +16 -0
  14. openph/model/hvac/hvac.py +25 -0
  15. openph/model/hvac/ventilation_device.py +1253 -0
  16. openph/model/hvac/ventilation_system.py +471 -0
  17. openph/model/ihg.py +53 -0
  18. openph/model/loads/__init__.py +0 -0
  19. openph/model/loads/infiltration.py +44 -0
  20. openph/model/loads/ventilation.py +15 -0
  21. openph/model/programs/__init__.py +0 -0
  22. openph/model/programs/ventilation.py +24 -0
  23. openph/model/rooms.py +314 -0
  24. openph/model/schedules/__init__.py +0 -0
  25. openph/model/schedules/collection.py +81 -0
  26. openph/model/schedules/occupancy.py +15 -0
  27. openph/model/schedules/ventilation.py +98 -0
  28. openph/model/set_points.py +13 -0
  29. openph/phpp.py +99 -0
  30. openph/table_views/__init__.py +48 -0
  31. openph/table_views/areas.py +239 -0
  32. openph/table_views/climate.py +192 -0
  33. openph/table_views/cooling_demand.py +1184 -0
  34. openph/table_views/ground.py +83 -0
  35. openph/table_views/heating_demand.py +241 -0
  36. openph/table_views/rooms.py +119 -0
  37. openph/table_views/ventilation.py +419 -0
  38. openph-0.1.0.dist-info/METADATA +742 -0
  39. openph-0.1.0.dist-info/RECORD +40 -0
  40. openph-0.1.0.dist-info/WHEEL +4 -0
openph/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """Open-PH: Passive House energy processing toolkit.
2
+
3
+ This package provides exact PHPP (Passive House Planning Package) calculation
4
+ replication in Python, with complete transparency and validation capabilities.
5
+ """
6
+
7
+ from openph.from_HBJSON.create_phpp import from_phx_variant
8
+ from openph.phpp import OpPhPHPP
9
+
10
+ __version__ = "0.1.0"
11
+ __all__ = ["OpPhPHPP", "from_phx_variant"]
@@ -0,0 +1,9 @@
1
+ """Input processing for Open-PH models from external sources.
2
+
3
+ This module provides functions to convert external building models
4
+ (HBJSON, PHX) into Open-PH data structures.
5
+ """
6
+
7
+ from .create_phpp import from_phx_variant
8
+
9
+ __all__ = ["from_phx_variant"]
@@ -0,0 +1,648 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- Python Version: 3.10 -*-
3
+
4
+ import math
5
+
6
+ from PHX.model.components import PhxApertureElement, PhxComponentOpaque
7
+ from PHX.model.constructions import (
8
+ PhxConstructionOpaque,
9
+ PhxConstructionWindow,
10
+ PhxWindowFrameElement,
11
+ )
12
+ from PHX.model.enums.hvac import PhxVentDuctType
13
+ from PHX.model.geometry import PhxPolygon, PhxPolygonRectangular
14
+ from PHX.model.hvac.ducting import PhxDuctElement
15
+ from PHX.model.hvac.ventilation import AnyPhxVentilation
16
+ from PHX.model.loads.ventilation import PhxLoadVentilation
17
+ from PHX.model.phx_site import PhxClimatePeakLoad
18
+ from PHX.model.project import PhxVariant
19
+ from PHX.model.schedules.ventilation import PhxScheduleVentilation
20
+ from PHX.model.spaces import PhxSpace
21
+
22
+ from openph.model.areas import OpPhAreas
23
+ from openph.model.climate import (
24
+ OpPhClimate,
25
+ OpPhClimateCalcPeriod,
26
+ OpPhClimatePeakCoolingLoad,
27
+ OpPhClimatePeakHeatingLoad,
28
+ )
29
+ from openph.model.components import OpPhConstructionCollection
30
+ from openph.model.constructions import (
31
+ OpPhConstructionAperture,
32
+ OpPhConstructionOpaque,
33
+ OpPhGlazing,
34
+ OpPhWindowFrameElement,
35
+ )
36
+ from openph.model.enums import ComponentExposureExterior, ComponentFaceType
37
+ from openph.model.envelope import OpPhApertureSurface, OpPhOpaqueSurface
38
+ from openph.model.hvac.hvac import OpPhHVAC
39
+ from openph.model.hvac.ventilation_device import (
40
+ OpPhDuct,
41
+ OpPhDuctType,
42
+ OpPhVentilationDeviceDucting,
43
+ )
44
+ from openph.model.hvac.ventilation_system import (
45
+ OpPhVentilationDevice,
46
+ OpPhVentilationSystem,
47
+ )
48
+ from openph.model.loads.infiltration import OpPhInfiltration
49
+ from openph.model.loads.ventilation import OpPhLoadVentilation
50
+ from openph.model.rooms import OpPhRoom, OpPhRooms
51
+ from openph.model.schedules.collection import OpPhScheduleCollection
52
+ from openph.model.schedules.ventilation import OpPhScheduleVentilation
53
+ from openph.phpp import OpPhPHPP
54
+
55
+ # ----------------------------------------------------------------------------------------------------------------------
56
+ # -- Errors
57
+
58
+
59
+ class VentilationDuctError(Exception):
60
+ def __init__(self, _ducts: list[PhxDuctElement], _duct_type: str):
61
+ self.msg = f"\nError: There are {len(_ducts)} {_duct_type} ducts assigned to the Ventilator. Expected 1?"
62
+ super().__init__(self.msg)
63
+
64
+
65
+ # ----------------------------------------------------------------------------------------------------------------------
66
+ # -- Climate
67
+
68
+
69
+ def create_ph_energy_peak_heating_load(
70
+ _ph_energy_phpp: OpPhPHPP, _phx_climate_peak_load: PhxClimatePeakLoad
71
+ ) -> OpPhClimatePeakHeatingLoad:
72
+ """Create a new OpPhClimatePeakHeatingLoad object from a PHX Climate."""
73
+ obj = OpPhClimatePeakHeatingLoad(
74
+ phpp=_ph_energy_phpp,
75
+ period_number=1,
76
+ _radiation_north_kwh_m2=_phx_climate_peak_load.radiation_north or 0.0,
77
+ _radiation_east_kwh_m2=_phx_climate_peak_load.radiation_east or 0.0,
78
+ _radiation_south_kwh_m2=_phx_climate_peak_load.radiation_south or 0.0,
79
+ _radiation_west_kwh_m2=_phx_climate_peak_load.radiation_west or 0.0,
80
+ _radiation_horizontal_kwh_m2=_phx_climate_peak_load.radiation_global or 0.0,
81
+ )
82
+ obj.temperature_air_c = _phx_climate_peak_load.temperature_air or 0.0
83
+ obj.temperature_sky_c = _phx_climate_peak_load.temperature_sky or 0.0
84
+ obj.temperature_dewpoint_c = _phx_climate_peak_load.temperature_dewpoint or 0.0
85
+ obj.temperature_ground_c = _phx_climate_peak_load.temperature_ground or 0.0
86
+ return obj
87
+
88
+
89
+ def create_ph_energy_peak_cooling_load(
90
+ _ph_energy_phpp: OpPhPHPP, _phx_climate_peak_load: PhxClimatePeakLoad
91
+ ) -> OpPhClimatePeakCoolingLoad:
92
+ """Create a new OpPhClimatePeakCoolingLoad object from a PHX Climate."""
93
+ obj = OpPhClimatePeakCoolingLoad(
94
+ phpp=_ph_energy_phpp,
95
+ period_number=1,
96
+ _radiation_north_kwh_m2=_phx_climate_peak_load.radiation_north or 0.0,
97
+ _radiation_east_kwh_m2=_phx_climate_peak_load.radiation_east or 0.0,
98
+ _radiation_south_kwh_m2=_phx_climate_peak_load.radiation_south or 0.0,
99
+ _radiation_west_kwh_m2=_phx_climate_peak_load.radiation_west or 0.0,
100
+ _radiation_horizontal_kwh_m2=_phx_climate_peak_load.radiation_global or 0.0,
101
+ )
102
+ obj.temperature_air_c = _phx_climate_peak_load.temperature_air or 0.0
103
+ obj.temperature_sky_c = _phx_climate_peak_load.temperature_sky or 0.0
104
+ obj.temperature_dewpoint_c = _phx_climate_peak_load.temperature_dewpoint or 0.0
105
+ obj.temperature_ground_c = _phx_climate_peak_load.temperature_ground or 0.0
106
+ return obj
107
+
108
+
109
+ def create_ph_energy_climate(
110
+ _ph_energy_phpp: OpPhPHPP, _phx_variant: PhxVariant
111
+ ) -> OpPhClimate:
112
+ """Create a OpPhClimate object from a PhxVariant."""
113
+
114
+ ph_energy_climate = OpPhClimate(_ph_energy_phpp)
115
+
116
+ # -------------------------------------------------------------------------
117
+ # -- Initialize all the calc-steps
118
+ for i, period in enumerate(_phx_variant.site.climate.monthly_hours, start=1):
119
+ period_name, period_length_hours = period
120
+ ph_energy_climate.periods.append(
121
+ OpPhClimateCalcPeriod(
122
+ phpp=_ph_energy_phpp,
123
+ period_number=i,
124
+ display_name=period_name,
125
+ _period_length_hours=period_length_hours,
126
+ )
127
+ )
128
+
129
+ # ------------------------------------------------------------------------
130
+ # -- Set the calc-step data
131
+ for i, period in enumerate(ph_energy_climate.periods):
132
+ # -- Set Climate Radiation
133
+ period.radiation_north_kwh_m2 = _phx_variant.site.climate.radiation_north[i]
134
+ period.radiation_east_kwh_m2 = _phx_variant.site.climate.radiation_east[i]
135
+ period.radiation_south_kwh_m2 = _phx_variant.site.climate.radiation_south[i]
136
+ period.radiation_west_kwh_m2 = _phx_variant.site.climate.radiation_west[i]
137
+ period.radiation_horizontal_kwh_m2 = _phx_variant.site.climate.radiation_global[
138
+ i
139
+ ]
140
+
141
+ # -- Set Climate Temperatures
142
+ period.temperature_air_c = _phx_variant.site.climate.temperature_air[i]
143
+ period.temperature_sky_c = _phx_variant.site.climate.temperature_sky[i]
144
+ period.temperature_dewpoint_c = _phx_variant.site.climate.temperature_dewpoint[
145
+ i
146
+ ]
147
+
148
+ # -------------------------------------------------------------------------
149
+ # --Calculate and set the period radiation factors for the Monthly Periods
150
+ for period in ph_energy_climate.periods:
151
+ period.calculate_radiation_factors()
152
+
153
+ # -------------------------------------------------------------------------
154
+ # -- Set Peak Load Data for Heating and Cooling
155
+ ph_energy_climate.peak_heating_1 = create_ph_energy_peak_heating_load(
156
+ _ph_energy_phpp, _phx_variant.site.climate.peak_heating_1
157
+ )
158
+ ph_energy_climate.peak_heating_2 = create_ph_energy_peak_heating_load(
159
+ _ph_energy_phpp, _phx_variant.site.climate.peak_heating_2
160
+ )
161
+ ph_energy_climate.peak_cooling_1 = create_ph_energy_peak_cooling_load(
162
+ _ph_energy_phpp, _phx_variant.site.climate.peak_cooling_1
163
+ )
164
+ ph_energy_climate.peak_cooling_2 = create_ph_energy_peak_cooling_load(
165
+ _ph_energy_phpp, _phx_variant.site.climate.peak_cooling_2
166
+ )
167
+
168
+ # -------------------------------------------------------------------------
169
+ # --Calculate and set the period radiation factors for the Peak-Load Periods
170
+ ph_energy_climate.peak_heating_1.calculate_radiation_factors()
171
+ ph_energy_climate.peak_heating_2.calculate_radiation_factors()
172
+ ph_energy_climate.peak_cooling_1.calculate_radiation_factors()
173
+ ph_energy_climate.peak_cooling_2.calculate_radiation_factors()
174
+
175
+ return ph_energy_climate
176
+
177
+
178
+ # ----------------------------------------------------------------------------------------------------------------------
179
+ # -- Envelope
180
+
181
+
182
+ def create_ph_energy_opaque_surface(
183
+ _phx_compo: PhxComponentOpaque,
184
+ _phx_polygon: PhxPolygon,
185
+ _construction: OpPhConstructionOpaque,
186
+ ) -> OpPhOpaqueSurface:
187
+ """Create a OpPhSurface_Opaque object from a PhxComponentOpaque and PhxPolygon."""
188
+
189
+ ph_energy_surface = OpPhOpaqueSurface(
190
+ construction=_construction,
191
+ id_num=_phx_polygon.id_num,
192
+ display_name=_phx_polygon.display_name,
193
+ area_m2=_phx_polygon.area,
194
+ angle_from_horizontal=_phx_polygon.angle_from_horizontal,
195
+ cardinal_orientation_angle=_phx_polygon.cardinal_orientation_angle,
196
+ face_type=ComponentFaceType(_phx_compo.face_type.value),
197
+ exposure_exterior=ComponentExposureExterior(_phx_compo.exposure_exterior.value),
198
+ )
199
+
200
+ return ph_energy_surface
201
+
202
+
203
+ def create_ph_energy_aperture_surface(
204
+ _phx_ap_element: PhxApertureElement,
205
+ _host_surface: OpPhOpaqueSurface,
206
+ _construction: OpPhConstructionAperture,
207
+ ) -> OpPhApertureSurface | None:
208
+ """Create a OpPhSurface_Aperture object from a PhxApertureElement."""
209
+
210
+ if not _phx_ap_element.polygon:
211
+ return None
212
+
213
+ # -- Set all the Open-PH-Aperture data from the PHX-Aperture
214
+ ph_e_ap = OpPhApertureSurface(
215
+ host=_host_surface,
216
+ construction=_construction,
217
+ face_type=ComponentFaceType(_phx_ap_element.host.face_type.value),
218
+ exposure_exterior=ComponentExposureExterior(
219
+ _phx_ap_element.host.exposure_exterior.value
220
+ ),
221
+ id_num=_phx_ap_element.polygon.id_num,
222
+ display_name=_phx_ap_element.polygon.display_name,
223
+ )
224
+ ph_e_ap.heat_gain.winter.shading_factor = _phx_ap_element.winter_shading_factor
225
+ ph_e_ap.heat_gain.summer.shading_factor = _phx_ap_element.summer_shading_factor
226
+
227
+ if isinstance(_phx_ap_element.polygon, PhxPolygonRectangular):
228
+ ph_e_ap.height_m = _phx_ap_element.polygon.height
229
+ ph_e_ap.width_m = _phx_ap_element.polygon.width
230
+ else:
231
+ ph_e_ap.height_m = math.sqrt(_phx_ap_element.polygon.area)
232
+ ph_e_ap.width_m = math.sqrt(_phx_ap_element.polygon.area)
233
+
234
+ return ph_e_ap
235
+
236
+
237
+ def create_ph_energy_areas(
238
+ _ph_energy_phpp: OpPhPHPP, _phx_variant: PhxVariant
239
+ ) -> OpPhAreas:
240
+ """Create a OpPhAreas object from a PhxVariant."""
241
+
242
+ if len(_ph_energy_phpp.opaque_constructions) == 0:
243
+ raise ValueError(
244
+ "Make sure that you populate the Opaque Constructions Collection with all the constructions "
245
+ "before trying to create the OpPhAreas object. Use 'create_opaque_construction_collection()'."
246
+ )
247
+
248
+ if len(_ph_energy_phpp.aperture_constructions) == 0:
249
+ raise ValueError(
250
+ "Make sure that you populate the Aperture Constructions Collection with all the constructions "
251
+ "before trying to create the OpPhAreas object. Use 'create_aperture_construction_collection()'."
252
+ )
253
+
254
+ ph_energy_areas = OpPhAreas(_ph_energy_phpp)
255
+ for phx_compo in _phx_variant.building.opaque_components:
256
+ opaque_construction = (
257
+ _ph_energy_phpp.opaque_constructions.get_construction_by_identifier(
258
+ phx_compo.assembly.identifier
259
+ )
260
+ )
261
+
262
+ for phx_polygon in phx_compo.polygons:
263
+ # -----------------------------------------------------------------
264
+ # -- build the opaque surface
265
+ ph_energy_surface = create_ph_energy_opaque_surface(
266
+ phx_compo, phx_polygon, opaque_construction
267
+ )
268
+ ph_energy_areas._opaque_surface_list.append(ph_energy_surface)
269
+
270
+ # -----------------------------------------------------------------
271
+ # -- build the child apertures, if any
272
+ for child_polygon_id in phx_polygon.child_polygon_ids:
273
+ phx_aperture_element = phx_compo.get_aperture_element_by_polygon_id_num(
274
+ child_polygon_id
275
+ )
276
+ window_construction = _ph_energy_phpp.aperture_constructions.get_construction_by_identifier(
277
+ phx_aperture_element.host.window_type.identifier
278
+ )
279
+
280
+ ph_energy_aperture = create_ph_energy_aperture_surface(
281
+ phx_compo.get_aperture_element_by_polygon_id_num(child_polygon_id),
282
+ ph_energy_surface,
283
+ window_construction,
284
+ )
285
+ ph_energy_surface.add_aperture(ph_energy_aperture)
286
+
287
+ return ph_energy_areas
288
+
289
+
290
+ def create_opaque_construction(
291
+ _construction: PhxConstructionOpaque,
292
+ ) -> OpPhConstructionOpaque:
293
+ """Create a new OpPhConstructionOpaque object from a PhxConstructionOpaque."""
294
+ return OpPhConstructionOpaque(
295
+ identifier=_construction.identifier,
296
+ id_num=_construction.id_num,
297
+ display_name=_construction.display_name,
298
+ u_value=_construction.u_value,
299
+ )
300
+
301
+
302
+ def create_opaque_construction_collection(
303
+ _phx_variant: PhxVariant,
304
+ ) -> OpPhConstructionCollection[OpPhConstructionOpaque]:
305
+ """Create a new ConstructionCollection[OpPhConstructionOpaque] object from a PhxVariant."""
306
+ opaque_construction_collection = OpPhConstructionCollection[
307
+ OpPhConstructionOpaque
308
+ ]()
309
+ for phx_compo in _phx_variant.building.opaque_components:
310
+ opaque_construction_collection.add_new_construction(
311
+ create_opaque_construction(phx_compo.assembly)
312
+ )
313
+ return opaque_construction_collection
314
+
315
+
316
+ def create_frame_element(
317
+ _phx_frame_element: PhxWindowFrameElement,
318
+ ) -> OpPhWindowFrameElement:
319
+ """Create a new OpPhWindowFrameElement object from a PhxWindowFrameElement."""
320
+ return OpPhWindowFrameElement(
321
+ width=_phx_frame_element.width,
322
+ u_value=_phx_frame_element.u_value,
323
+ psi_glazing=_phx_frame_element.psi_glazing,
324
+ psi_install=_phx_frame_element.psi_install,
325
+ )
326
+
327
+
328
+ def create_aperture_construction(
329
+ _construction: PhxConstructionWindow,
330
+ ) -> OpPhConstructionAperture:
331
+ """Create a new OpPhConstructionAperture object from a PhxConstructionWindow."""
332
+ return OpPhConstructionAperture(
333
+ identifier=_construction.identifier,
334
+ id_num=_construction.id_num,
335
+ display_name=_construction.display_name,
336
+ glazing_type_display_name=_construction.glazing_type_display_name,
337
+ frame_type_display_name=_construction.frame_type_display_name,
338
+ glazing=OpPhGlazing(
339
+ identifier=_construction.identifier,
340
+ id_num=_construction.id_num,
341
+ u_value=_construction.u_value_glass,
342
+ g_value=_construction.glass_g_value,
343
+ ),
344
+ frame_top=create_frame_element(_construction.frame_top),
345
+ frame_bottom=create_frame_element(_construction.frame_bottom),
346
+ frame_left=create_frame_element(_construction.frame_left),
347
+ frame_right=create_frame_element(_construction.frame_right),
348
+ )
349
+
350
+
351
+ def create_aperture_construction_collection(
352
+ _phx_variant: PhxVariant,
353
+ ) -> OpPhConstructionCollection[OpPhConstructionAperture]:
354
+ """Create a new ConstructionCollection[OpPhConstructionAperture] object from a PhxVariant."""
355
+ aperture_construction_collection = OpPhConstructionCollection[
356
+ OpPhConstructionAperture
357
+ ]()
358
+ for phx_compo in _phx_variant.building.aperture_components:
359
+ aperture_construction_collection.add_new_construction(
360
+ create_aperture_construction(phx_compo.window_type)
361
+ )
362
+ return aperture_construction_collection
363
+
364
+
365
+ # ----------------------------------------------------------------------------------------------------------------------
366
+ # -- Ventilation - Devices
367
+
368
+
369
+ def get_phx_duct_from_list(
370
+ _phx_ducts: list[PhxDuctElement], _duct_type: PhxVentDuctType
371
+ ) -> PhxDuctElement:
372
+ """Find the PHX-Duct of the specified type (Supply | Exhaust) for the designated Ventilator."""
373
+
374
+ phx_ducting = [d for d in _phx_ducts if d.duct_type == _duct_type]
375
+ if len(phx_ducting) != 1:
376
+ raise VentilationDuctError(phx_ducting, _duct_type.name)
377
+ return phx_ducting[0]
378
+
379
+
380
+ def create_ph_en_duct(
381
+ _device: OpPhVentilationDevice, _phx_duct: PhxDuctElement
382
+ ) -> OpPhDuct:
383
+ """Create a new OpPhDuct from a source PhxDuctElement."""
384
+
385
+ return OpPhDuct(
386
+ device=_device,
387
+ length_m=_phx_duct.length_m,
388
+ diameter_mm=_phx_duct.diameter_mm,
389
+ height_mm=_phx_duct.height_mm,
390
+ width_mm=_phx_duct.width_mm,
391
+ insulation_thickness_mm=_phx_duct.insulation_thickness_mm,
392
+ insulation_conductivity_w_mk=_phx_duct.insulation_conductivity_wmk,
393
+ insulation_reflective=_phx_duct.is_reflective,
394
+ duct_type=OpPhDuctType(_phx_duct.duct_type.value),
395
+ )
396
+
397
+
398
+ def create_ph_energy_ventilator_ducting(
399
+ _device: OpPhVentilationDevice,
400
+ _phx_supply_duct: PhxDuctElement,
401
+ _phx_exhaust_duct: PhxDuctElement,
402
+ ) -> OpPhVentilationDeviceDucting:
403
+ """Create a new OpPhVentilationDeviceDucting from source Supply and Exhaust PhxDuctElements."""
404
+
405
+ ducting = OpPhVentilationDeviceDucting(_device)
406
+ ducting.supply_ducting = create_ph_en_duct(_device, _phx_supply_duct)
407
+ ducting.exhaust_ducting = create_ph_en_duct(_device, _phx_exhaust_duct)
408
+ return ducting
409
+
410
+
411
+ def create_ph_energy_ventilator(
412
+ _ph_energy_phpp: OpPhPHPP,
413
+ _phx_ventilator: AnyPhxVentilation,
414
+ _phx_supply_duct: PhxDuctElement,
415
+ _phx_exhaust_duct: PhxDuctElement,
416
+ ) -> OpPhVentilationDevice:
417
+ """Create a new OpPhVentilationDevice (HRV/ERV) from a source PHX-Ventilation Device."""
418
+
419
+ device = OpPhVentilationDevice(
420
+ phpp=_ph_energy_phpp,
421
+ id_num=_phx_ventilator.id_num,
422
+ display_name=_phx_ventilator.display_name,
423
+ quantity=_phx_ventilator.quantity,
424
+ sensible_heat_recovery_effic=_phx_ventilator.params.sensible_heat_recovery,
425
+ latent_heat_recovery_effic=_phx_ventilator.params.latent_heat_recovery,
426
+ electric_efficiency_wh_m3=_phx_ventilator.params.electric_efficiency,
427
+ frost_protection_reqd=_phx_ventilator.params.frost_protection_reqd,
428
+ temperature_c_below_defrost_used=_phx_ventilator.params.temperature_below_defrost_used,
429
+ )
430
+ device.ducting = create_ph_energy_ventilator_ducting(
431
+ device, _phx_supply_duct, _phx_exhaust_duct
432
+ )
433
+ return device
434
+
435
+
436
+ def create_ph_energy_ventilation_system(
437
+ _ph_energy_phpp: OpPhPHPP,
438
+ _phx_variant: PhxVariant,
439
+ ) -> OpPhVentilationSystem:
440
+ """Create a new OpPhMechanicalSystemCollection object from a PhxMechanicalSystemCollection."""
441
+
442
+ ph_energy_vent_system = OpPhVentilationSystem(phpp=_ph_energy_phpp)
443
+
444
+ # -- Get the Mechanical Vent Devices
445
+ for phx_mech_collection in _phx_variant.mech_collections:
446
+ for phx_ventilator in phx_mech_collection.ventilation_devices:
447
+ phx_ducts = [
448
+ d
449
+ for d in phx_mech_collection.vent_ducting
450
+ if phx_ventilator.id_num in d.assigned_vent_unit_ids
451
+ ]
452
+ ph_energy_ventilator = create_ph_energy_ventilator(
453
+ _ph_energy_phpp=_ph_energy_phpp,
454
+ _phx_ventilator=phx_ventilator,
455
+ _phx_supply_duct=get_phx_duct_from_list(
456
+ phx_ducts, PhxVentDuctType.SUPPLY
457
+ ),
458
+ _phx_exhaust_duct=get_phx_duct_from_list(
459
+ phx_ducts, PhxVentDuctType.EXHAUST
460
+ ),
461
+ )
462
+
463
+ ph_energy_vent_system.device_collection.add_new_mech_device(
464
+ str(ph_energy_ventilator.id_num), ph_energy_ventilator
465
+ )
466
+ return ph_energy_vent_system
467
+
468
+
469
+ # ----------------------------------------------------------------------------------------------------------------------
470
+ # -- Program
471
+
472
+
473
+ def create_ventilation_schedule(
474
+ _phx_schedule: PhxScheduleVentilation,
475
+ ) -> OpPhScheduleVentilation:
476
+ """Create a new Ph-Energy Ventilation Schedule from a PHX Ventilation Schedule"""
477
+
478
+ # -- Create the basic object with attributes from PHX
479
+ new_schedule = OpPhScheduleVentilation(
480
+ id_num=_phx_schedule.id_num,
481
+ name=_phx_schedule.name,
482
+ identifier=str(_phx_schedule.identifier),
483
+ operating_hours=_phx_schedule.operating_hours,
484
+ operating_days=_phx_schedule.operating_days,
485
+ operating_weeks=_phx_schedule.operating_weeks,
486
+ holiday_days=_phx_schedule.holiday_days,
487
+ )
488
+
489
+ # -- Transfer the Operating-Period values from PHX
490
+ new_schedule.operating_periods.high.period_operating_hours = (
491
+ _phx_schedule.operating_periods.high.period_operating_hours
492
+ )
493
+ new_schedule.operating_periods.high.period_operation_speed = (
494
+ _phx_schedule.operating_periods.high.period_operation_speed
495
+ )
496
+ new_schedule.operating_periods.basic.period_operating_hours = (
497
+ _phx_schedule.operating_periods.standard.period_operating_hours
498
+ )
499
+ new_schedule.operating_periods.standard.period_operation_speed = (
500
+ _phx_schedule.operating_periods.standard.period_operation_speed
501
+ )
502
+ new_schedule.operating_periods.basic.period_operating_hours = (
503
+ _phx_schedule.operating_periods.basic.period_operating_hours
504
+ )
505
+ new_schedule.operating_periods.basic.period_operation_speed = (
506
+ _phx_schedule.operating_periods.basic.period_operation_speed
507
+ )
508
+ new_schedule.operating_periods.minimum.period_operating_hours = (
509
+ _phx_schedule.operating_periods.minimum.period_operating_hours
510
+ )
511
+ new_schedule.operating_periods.minimum.period_operation_speed = (
512
+ _phx_schedule.operating_periods.minimum.period_operation_speed
513
+ )
514
+
515
+ return new_schedule
516
+
517
+
518
+ def create_ventilation_load(
519
+ _phx_ventilation_load: PhxLoadVentilation,
520
+ ) -> OpPhLoadVentilation:
521
+ """Return a new OpPhLoadVentilation object with values set from the PhxLoadVentilation."""
522
+ return OpPhLoadVentilation(
523
+ supply_airflow_m3_h=_phx_ventilation_load.flow_supply,
524
+ exhaust_airflow_m3_h=_phx_ventilation_load.flow_extract,
525
+ transfer_airflow_m3_h=_phx_ventilation_load.flow_transfer,
526
+ )
527
+
528
+
529
+ # ----------------------------------------------------------------------------------------------------------------------
530
+ # -- Rooms
531
+
532
+
533
+ def create_ph_energy_room(
534
+ _phx_space: PhxSpace,
535
+ _schedule_collection: OpPhScheduleCollection[OpPhScheduleVentilation],
536
+ _ph_en_hvac: OpPhHVAC,
537
+ ) -> OpPhRoom:
538
+ """Create a new OpPhRoom object from a PhxSpace."""
539
+ new_room = OpPhRoom()
540
+ new_room.quantity = _phx_space.quantity
541
+ new_room.display_name = _phx_space.display_name
542
+ new_room.floor_area_m2 = _phx_space.floor_area
543
+ try:
544
+ new_room.weighting_factor = (
545
+ _phx_space.weighted_floor_area / _phx_space.floor_area
546
+ )
547
+ except ZeroDivisionError:
548
+ new_room.weighting_factor = 0.0
549
+ new_room.net_volume_m3 = _phx_space.net_volume
550
+
551
+ # -- Assign Ventilator Device
552
+ new_room.vent_unit = (
553
+ _ph_en_hvac.ventilation_system.device_collection.get_device_by_key(
554
+ str(_phx_space.vent_unit_id_num)
555
+ )
556
+ )
557
+
558
+ # -- Build the Room's Ventilation Program
559
+ new_room.ventilation.display_name = _phx_space.ventilation.display_name
560
+ new_room.ventilation.schedule = _schedule_collection.get_schedule_by_key(
561
+ str(_phx_space.ventilation.schedule.identifier)
562
+ )
563
+ new_room.ventilation.load = create_ventilation_load(_phx_space.ventilation.load)
564
+
565
+ # TODO: Build Occupancy Program
566
+
567
+ # TODO: Build Lighting Program
568
+
569
+ return new_room
570
+
571
+
572
+ def create_ph_energy_rooms(
573
+ _ph_energy_phpp: OpPhPHPP, _phx_variant: PhxVariant
574
+ ) -> OpPhRooms:
575
+ """Create a OpPhRooms object from a PhxVariant's 'Zones'."""
576
+ ph_energy_rooms = OpPhRooms(_ph_energy_phpp)
577
+ for phx_zone in _phx_variant.building.zones:
578
+ for phx_space in phx_zone.spaces:
579
+ ph_energy_rooms.add_room(
580
+ create_ph_energy_room(
581
+ phx_space,
582
+ _ph_energy_phpp.schedules,
583
+ _ph_energy_phpp.hvac,
584
+ )
585
+ )
586
+
587
+ return ph_energy_rooms
588
+
589
+
590
+ def create_schedules(_phx_variant: PhxVariant) -> list[OpPhScheduleVentilation]:
591
+ """Return a list of each unique Schedule object found on the PHX model's Spaces."""
592
+ schedules = {}
593
+ for phx_zone in _phx_variant.building.zones:
594
+ for phx_space in phx_zone.spaces:
595
+ # TODO: Create new OpPh Objects from the PHX Schedules...
596
+ schedules[str(phx_space.ventilation.schedule.identifier)] = (
597
+ create_ventilation_schedule(phx_space.ventilation.schedule)
598
+ )
599
+ # TODO: handle Occupancy and Lighting Schedules
600
+ # schedules[str(phx_space.occupancy.schedule.identifier)] = \
601
+ # create_new_schedule(phx_space.occupancy.schedule)
602
+ # schedules[str(phx_space.lighting.schedule.identifier)] = \
603
+ # create_new_schedule(phx_space.lighting.schedule)
604
+ return list(schedules.values())
605
+
606
+
607
+ def create_ph_energy_infiltration(
608
+ _ph_energy_phpp: OpPhPHPP, _phx_variant: PhxVariant
609
+ ) -> OpPhInfiltration:
610
+ """Return a new OpPhInfiltration object with props based on a PHX Variant"""
611
+
612
+ return OpPhInfiltration(
613
+ phpp=_ph_energy_phpp,
614
+ wind_coefficient_e=_phx_variant.phius_cert.ph_building_data.wind_coefficient_e,
615
+ wind_coefficient_f=_phx_variant.phius_cert.ph_building_data.wind_coefficient_f,
616
+ airtightness_n50=_phx_variant.phius_cert.ph_building_data.airtightness_n50,
617
+ )
618
+
619
+
620
+ # ----------------------------------------------------------------------------------------------------------------------
621
+ # -- Entry Point
622
+
623
+
624
+ def from_phx_variant(_phx_variant: PhxVariant) -> OpPhPHPP:
625
+ """Create a new OpPhergyPHPP object from a PhxVariant."""
626
+
627
+ ph_energy_phpp = OpPhPHPP()
628
+
629
+ # -- Setup the PHPP Data
630
+ for program in create_schedules(_phx_variant):
631
+ ph_energy_phpp.schedules.add_new_schedule(program)
632
+ ph_energy_phpp.opaque_constructions = create_opaque_construction_collection(
633
+ _phx_variant
634
+ )
635
+ ph_energy_phpp.aperture_constructions = create_aperture_construction_collection(
636
+ _phx_variant
637
+ )
638
+ ph_energy_phpp.hvac.ventilation_system = create_ph_energy_ventilation_system(
639
+ ph_energy_phpp, _phx_variant
640
+ )
641
+ ph_energy_phpp.climate = create_ph_energy_climate(ph_energy_phpp, _phx_variant)
642
+ ph_energy_phpp.infiltration = create_ph_energy_infiltration(
643
+ ph_energy_phpp, _phx_variant
644
+ )
645
+ ph_energy_phpp.areas = create_ph_energy_areas(ph_energy_phpp, _phx_variant)
646
+ ph_energy_phpp.rooms = create_ph_energy_rooms(ph_energy_phpp, _phx_variant)
647
+
648
+ return ph_energy_phpp
File without changes