epinterface 1.0.2__tar.gz → 1.0.4__tar.gz

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 (119) hide show
  1. {epinterface-1.0.2 → epinterface-1.0.4}/PKG-INFO +2 -1
  2. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/builder.py +65 -13
  3. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/components/schedules.py +20 -0
  4. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/fields/spec.py +28 -0
  5. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/utils.py +25 -2
  6. {epinterface-1.0.2 → epinterface-1.0.4}/pyproject.toml +2 -1
  7. {epinterface-1.0.2 → epinterface-1.0.4}/uv.lock +25 -1
  8. {epinterface-1.0.2 → epinterface-1.0.4}/.editorconfig +0 -0
  9. {epinterface-1.0.2 → epinterface-1.0.4}/.github/actions/setup-python-env/action.yml +0 -0
  10. {epinterface-1.0.2 → epinterface-1.0.4}/.github/workflows/main.yml +0 -0
  11. {epinterface-1.0.2 → epinterface-1.0.4}/.github/workflows/on-release-main.yml +0 -0
  12. {epinterface-1.0.2 → epinterface-1.0.4}/.github/workflows/validate-codecov-config.yml +0 -0
  13. {epinterface-1.0.2 → epinterface-1.0.4}/.gitignore +0 -0
  14. {epinterface-1.0.2 → epinterface-1.0.4}/.pre-commit-config.yaml +0 -0
  15. {epinterface-1.0.2 → epinterface-1.0.4}/.prettierignore +0 -0
  16. {epinterface-1.0.2 → epinterface-1.0.4}/.python-version +0 -0
  17. {epinterface-1.0.2 → epinterface-1.0.4}/.vscode/settings.json +0 -0
  18. {epinterface-1.0.2 → epinterface-1.0.4}/CONTRIBUTING.md +0 -0
  19. {epinterface-1.0.2 → epinterface-1.0.4}/LICENSE +0 -0
  20. {epinterface-1.0.2 → epinterface-1.0.4}/Makefile +0 -0
  21. {epinterface-1.0.2 → epinterface-1.0.4}/README.md +0 -0
  22. {epinterface-1.0.2 → epinterface-1.0.4}/codecov.yaml +0 -0
  23. {epinterface-1.0.2 → epinterface-1.0.4}/docs/cli.md +0 -0
  24. {epinterface-1.0.2 → epinterface-1.0.4}/docs/index.md +0 -0
  25. {epinterface-1.0.2 → epinterface-1.0.4}/docs/modules/actions.md +0 -0
  26. {epinterface-1.0.2 → epinterface-1.0.4}/docs/modules/builder.md +0 -0
  27. {epinterface-1.0.2 → epinterface-1.0.4}/docs/modules/climate-studio.md +0 -0
  28. {epinterface-1.0.2 → epinterface-1.0.4}/docs/modules/geometry.md +0 -0
  29. {epinterface-1.0.2 → epinterface-1.0.4}/docs/modules/interface.md +0 -0
  30. {epinterface-1.0.2 → epinterface-1.0.4}/docs/modules/sbem/components.md +0 -0
  31. {epinterface-1.0.2 → epinterface-1.0.4}/docs/modules/sbem/composer.md +0 -0
  32. {epinterface-1.0.2 → epinterface-1.0.4}/docs/modules/sbem/index.md +0 -0
  33. {epinterface-1.0.2 → epinterface-1.0.4}/docs/modules/weather.md +0 -0
  34. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/__init__.py +0 -0
  35. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/actions.py +0 -0
  36. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/builder.py +0 -0
  37. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/cli.py +0 -0
  38. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/climate_studio/__init__.py +0 -0
  39. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/climate_studio/builder.py +0 -0
  40. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/climate_studio/interface.py +0 -0
  41. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/constants/__init__.py +0 -0
  42. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/data/Minimal.idf +0 -0
  43. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/data/USA_MA_Boston-Logan.Intl.AP.725090_TMYx.2009-2023.ddy +0 -0
  44. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/data/USA_MA_Boston-Logan.Intl.AP.725090_TMYx.2009-2023.epw +0 -0
  45. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/data/USA_MA_Boston-Logan.Intl.AP.725090_TMYx.2009-2023.zip +0 -0
  46. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/data/__init__.py +0 -0
  47. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/data/res_schedules.parquet +0 -0
  48. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/ddy_injector_bayes.py +0 -0
  49. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/ddy_interface_bayes.py +0 -0
  50. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/geometry.py +0 -0
  51. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/interface.py +0 -0
  52. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/py.typed +0 -0
  53. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/__init__.py +0 -0
  54. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/annotations.py +0 -0
  55. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/common.py +0 -0
  56. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/components/__init__.py +0 -0
  57. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/components/composer.py +0 -0
  58. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/components/envelope.py +0 -0
  59. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/components/materials.py +0 -0
  60. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/components/operations.py +0 -0
  61. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/components/space_use.py +0 -0
  62. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/components/systems.py +0 -0
  63. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/components/zones.py +0 -0
  64. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/exceptions.py +0 -0
  65. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/fields/__init__.py +0 -0
  66. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/flat_model.py +0 -0
  67. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/interface.py +0 -0
  68. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/model_graph_structure.md +0 -0
  69. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/client.py +0 -0
  70. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/migrations/20250309133033_create_initial_schema/migration.sql +0 -0
  71. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/migrations/20250310162045_switch_to_using_one_week_per_month/migration.sql +0 -0
  72. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/migrations/20250317202733_change_names_ventilation/migration.sql +0 -0
  73. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/migrations/20250325185158_add_mutually_exclusive_ventilation_techtypes/migration.sql +0 -0
  74. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/migrations/20250326141941_reduce_naming_complexity_ventilation/migration.sql +0 -0
  75. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/migrations/20250331141910_add_support_for_attic_and_basement_constructions/migration.sql +0 -0
  76. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/migrations/20250919152559_decouple_basement_infiltration/migration.sql +0 -0
  77. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/migrations/migration_lock.toml +0 -0
  78. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/partial_types.py +0 -0
  79. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/schema.prisma +0 -0
  80. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/sbem/prisma/seed_fns.py +0 -0
  81. {epinterface-1.0.2 → epinterface-1.0.4}/epinterface/weather.py +0 -0
  82. {epinterface-1.0.2 → epinterface-1.0.4}/mkdocs.yml +0 -0
  83. {epinterface-1.0.2 → epinterface-1.0.4}/notebooks/everett_lib.json +0 -0
  84. {epinterface-1.0.2 → epinterface-1.0.4}/notebooks/everett_lib_v2.ipynb +0 -0
  85. {epinterface-1.0.2 → epinterface-1.0.4}/notebooks/everett_retrofit_actions.ipynb +0 -0
  86. {epinterface-1.0.2 → epinterface-1.0.4}/notebooks/everett_retrofits.yaml +0 -0
  87. {epinterface-1.0.2 → epinterface-1.0.4}/notebooks/idf-demo.ipynb +0 -0
  88. {epinterface-1.0.2 → epinterface-1.0.4}/notebooks/idf-demo.py +0 -0
  89. {epinterface-1.0.2 → epinterface-1.0.4}/notebooks/view-geo.ipynb +0 -0
  90. {epinterface-1.0.2 → epinterface-1.0.4}/scripts/gridsim.py +0 -0
  91. {epinterface-1.0.2 → epinterface-1.0.4}/tests/conftest.py +0 -0
  92. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/20251001_Template_MAWebApp.xlsx +0 -0
  93. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/USA_MA_Chicopee-Westover.Metro.AP.744910_TMYx.2009-2023.zip +0 -0
  94. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/component-map-ma.yml +0 -0
  95. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/components-ma.db +0 -0
  96. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/old/0318_Template_MAWebApp.xlsx +0 -0
  97. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/old/Template_MAWebApp.xlsx +0 -0
  98. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/old/component-map-ma.yml +0 -0
  99. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/old/components-ma-old.db +0 -0
  100. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/old/components-ma.db +0 -0
  101. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/old/components-ma.db Kopie +0 -0
  102. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/old/components-ma.db Kopie 2 +0 -0
  103. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/old/components-ma.db Kopie 3 +0 -0
  104. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/old/semantic-fields-ma.yml +0 -0
  105. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/semantic-fields-ma.yml +0 -0
  106. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/test_lib.json +0 -0
  107. {epinterface-1.0.2 → epinterface-1.0.4}/tests/data/tester_lib.xlsx +0 -0
  108. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_actions.py +0 -0
  109. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_components/test_builder.py +0 -0
  110. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_components/test_component_lib.py +0 -0
  111. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_components/test_envelope_comps.py +0 -0
  112. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_components/test_operations.py +0 -0
  113. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_components/test_space_use_comps.py +0 -0
  114. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_composer.py +0 -0
  115. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_db.py +0 -0
  116. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_example.py +0 -0
  117. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_geometry.py +0 -0
  118. {epinterface-1.0.2 → epinterface-1.0.4}/tests/test_weather.py +0 -0
  119. {epinterface-1.0.2 → epinterface-1.0.4}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: epinterface
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: This is a repository for dynamically generating energy models within Python, relying on Archetypal and Eppy for most of its functionality.
5
5
  Project-URL: Homepage, https://github.com/szvsw/epinterface
6
6
  Project-URL: Documentation, https://szvsw.github.io/epinterface
@@ -13,6 +13,7 @@ Requires-Dist: archetypal
13
13
  Requires-Dist: click==8.1.7
14
14
  Requires-Dist: geopandas~=1.0.1
15
15
  Requires-Dist: httpx~=0.27.2
16
+ Requires-Dist: ladybug-core>=0.44.30
16
17
  Requires-Dist: openpyxl~=3.1.5
17
18
  Requires-Dist: pandas<2.3,>=2.2
18
19
  Requires-Dist: prisma~=0.15.0
@@ -2,6 +2,7 @@
2
2
 
3
3
  import asyncio
4
4
  import gc
5
+ import logging
5
6
  import shutil
6
7
  import tempfile
7
8
  from collections.abc import Callable
@@ -14,6 +15,7 @@ import numpy as np
14
15
  import pandas as pd
15
16
  from archetypal.idfclass import IDF
16
17
  from archetypal.idfclass.sql import Sql
18
+ from ladybug.epw import EPW
17
19
  from pydantic import BaseModel, Field, field_validator, model_validator
18
20
 
19
21
  from epinterface.constants import assumed_constants, physical_constants
@@ -37,6 +39,8 @@ from epinterface.sbem.components.zones import ZoneComponent
37
39
  from epinterface.sbem.exceptions import NotImplementedParameter
38
40
  from epinterface.weather import BaseWeather
39
41
 
42
+ logger = logging.getLogger(__name__)
43
+
40
44
  DESIRED_METERS = (
41
45
  "InteriorEquipment:Electricity",
42
46
  "InteriorLights:Electricity",
@@ -803,7 +807,7 @@ class Model(BaseWeather, validate_assignment=True):
803
807
 
804
808
  return idf
805
809
 
806
- def build(
810
+ def build( # noqa: C901
807
811
  self,
808
812
  config: SimulationPathConfig,
809
813
  post_geometry_callback: Callable[[IDF], IDF] | None = None,
@@ -870,10 +874,6 @@ class Model(BaseWeather, validate_assignment=True):
870
874
  idf = add_default_sim_controls(idf)
871
875
  idf, _scheds = add_default_schedules(idf)
872
876
 
873
- idf = SiteGroundTemperature.FromValues(
874
- assumed_constants.SiteGroundTemperature_degC
875
- ).add(idf)
876
-
877
877
  idf = self.geometry.add(idf)
878
878
  if post_geometry_callback is not None:
879
879
  idf = post_geometry_callback(idf)
@@ -885,6 +885,44 @@ class Model(BaseWeather, validate_assignment=True):
885
885
  for zone in added_zone_lists.main_zone_list.Names:
886
886
  self.Zone.add_to_idf_zone(idf, zone)
887
887
 
888
+ # Handle setting ground temperature
889
+ subtractor = (
890
+ 4 if (self.geometry.basement and not self.Basement.Conditioned) else 2
891
+ )
892
+ has_heating = self.Zone.Operations.HVAC.ConditioningSystems.Heating is not None
893
+ has_cooling = self.Zone.Operations.HVAC.ConditioningSystems.Cooling is not None
894
+ hsp = self.Zone.Operations.SpaceUse.Thermostat.HeatingSchedule
895
+ csp = self.Zone.Operations.SpaceUse.Thermostat.CoolingSchedule
896
+ epw = EPW(epw_path.as_posix())
897
+ epw_ground_vals_all = epw.monthly_ground_temperature
898
+ if self.geometry.basement:
899
+ # if there is a basement, we use the 2m depth to account for the basement depth.
900
+ epw_ground_vals = epw_ground_vals_all[4].values
901
+ else:
902
+ # if there is no basement, we use the 0.5m depth to account for the ground temperature.
903
+ epw_ground_vals = epw_ground_vals_all[0.5].values
904
+ low_ground_val = min(epw_ground_vals)
905
+ high_ground_val = max(epw_ground_vals)
906
+ phase = (np.array(epw_ground_vals) - low_ground_val) / (
907
+ high_ground_val - low_ground_val
908
+ )
909
+ if has_heating and has_cooling:
910
+ winter_line = np.array(hsp.MonthlyAverageValues) - subtractor
911
+ summer_line = np.array(csp.MonthlyAverageValues) - subtractor
912
+ elif has_heating:
913
+ winter_line = np.array(hsp.MonthlyAverageValues) - subtractor
914
+ summer_line = np.array(hsp.MonthlyAverageValues)
915
+ elif has_cooling:
916
+ winter_line = np.array(csp.MonthlyAverageValues) - subtractor
917
+ summer_line = np.array(csp.MonthlyAverageValues) - subtractor
918
+ else:
919
+ # No heating or cooling, so we use the default ground temperature, should not matter much.
920
+ winter_line = np.array(assumed_constants.SiteGroundTemperature_degC)
921
+ summer_line = np.array(assumed_constants.SiteGroundTemperature_degC)
922
+ interp_temp = phase * np.abs(summer_line - winter_line) + winter_line
923
+ ground_vals = [max(epw_ground_vals[i], interp_temp[i]) for i in range(12)]
924
+ idf = SiteGroundTemperature.FromValues(ground_vals).add(idf)
925
+
888
926
  # handle basements
889
927
  if self.Basement.UseFraction or self.Basement.Conditioned:
890
928
  new_zone_def = self.Zone.model_copy(deep=True)
@@ -1310,9 +1348,15 @@ if __name__ == "__main__":
1310
1348
  from epinterface.sbem.prisma.client import PrismaSettings
1311
1349
 
1312
1350
  with tempfile.TemporaryDirectory() as temp_dir:
1313
- database_path = Path("/Users/daryaguettler/globi/data/Brazil/components-lib.db")
1351
+ # database_path = Path("/Users/daryaguettler/globi/data/Brazil/components-lib.db")
1352
+ # component_map_path = Path(
1353
+ # "/Users/daryaguettler/globi/data/Brazil/component-map.yaml"
1354
+ # )
1355
+ database_path = Path(
1356
+ "/Users/daryaguettler/globi/data/Portugal/components-lib.db"
1357
+ )
1314
1358
  component_map_path = Path(
1315
- "/Users/daryaguettler/globi/data/Brazil/component-map.yaml"
1359
+ "/Users/daryaguettler/globi/data/Portugal/component-map.yaml"
1316
1360
  )
1317
1361
  settings = PrismaSettings(
1318
1362
  database_path=database_path,
@@ -1329,18 +1373,26 @@ if __name__ == "__main__":
1329
1373
  component_map_yaml = yaml.safe_load(f)
1330
1374
  selector = SelectorModel.model_validate(component_map_yaml)
1331
1375
  db = settings.db
1376
+ # context = {
1377
+ # "region": "SP",
1378
+ # "income": "Low",
1379
+ # "typology": "Residential",
1380
+ # "scenario": "withAC",
1381
+ # }
1332
1382
  context = {
1333
- "region": "SP",
1334
- "income": "Low",
1335
- "typology": "Residential",
1336
- "scenario": "withAC",
1383
+ "Region": "I1_V2",
1384
+ "City": "LS",
1385
+ "Typology": "Single_Family_Residential",
1386
+ "Age_buckets": "1971_1980",
1387
+ "scenario": "Baseline",
1337
1388
  }
1338
1389
  with settings.db:
1339
1390
  zone = cast(ZoneComponent, selector.get_component(context=context, db=db))
1340
1391
 
1341
1392
  model = Model(
1342
1393
  Weather=(
1343
- "https://climate.onebuilding.org/WMO_Region_3_South_America/BRA_Brazil/SP_Sao_Paulo/BRA_SP_Guaratingueta.AP.837080_TMYx.2009-2023.zip"
1394
+ # "https://climate.onebuilding.org/WMO_Region_3_South_America/BRA_Brazil/SP_Sao_Paulo/BRA_SP_Guaratingueta.AP.837080_TMYx.2009-2023.zip"
1395
+ "https://climate.onebuilding.org/WMO_Region_6_Europe/PRT_Portugal/LB_Lisboa/PRT_LB_Lisboa.Portela.AP.085360_TMYx.2009-2023.zip"
1344
1396
  ), # pyright: ignore [reportArgumentType]
1345
1397
  Zone=zone,
1346
1398
  Attic=AtticAssumptions(
@@ -1358,7 +1410,7 @@ if __name__ == "__main__":
1358
1410
  d=20,
1359
1411
  h=3,
1360
1412
  wwr=0.3,
1361
- num_stories=3,
1413
+ num_stories=1,
1362
1414
  basement=False,
1363
1415
  zoning="by_storey",
1364
1416
  roof_height=None,
@@ -70,6 +70,11 @@ class DayComponent(NamedObject, extra="forbid"):
70
70
  Hour_22: float
71
71
  Hour_23: float
72
72
 
73
+ @property
74
+ def AverageValue(self) -> float:
75
+ """Get the average value of the day."""
76
+ return sum(self.Values) / len(self.Values)
77
+
73
78
  @property
74
79
  def bounds(self) -> tuple[float, float]:
75
80
  """Get the bounds of the day."""
@@ -200,6 +205,11 @@ class WeekComponent(NamedObject, extra="forbid"):
200
205
  highs = [day.bounds[1] for day in self.Days]
201
206
  return min(lows), max(highs)
202
207
 
208
+ @property
209
+ def AverageValue(self) -> float:
210
+ """Get the average value of the week."""
211
+ return sum(day.AverageValue for day in self.Days) / len(self.Days)
212
+
203
213
  @property
204
214
  def Days(self) -> list[DayComponent]:
205
215
  """Get the days of the week as a list."""
@@ -338,6 +348,16 @@ class YearComponent(NamedObject, extra="forbid"):
338
348
  November: WeekComponent
339
349
  December: WeekComponent
340
350
 
351
+ @property
352
+ def AverageValue(self) -> float:
353
+ """Get the average value of the year."""
354
+ return sum(week.AverageValue for week in self.Weeks) / len(self.Weeks)
355
+
356
+ @property
357
+ def MonthlyAverageValues(self) -> list[float]:
358
+ """Get the average values of the year."""
359
+ return [week.AverageValue for week in self.Weeks]
360
+
341
361
  @property
342
362
  def bounds(self) -> tuple[float, float]:
343
363
  """Get the bounds of the year."""
@@ -51,6 +51,34 @@ class SemanticModelFields(BaseModel):
51
51
  GFA_col: str | None = Field(
52
52
  default=None, description="The gross floor area column name [m2]."
53
53
  )
54
+ Building_ID_col: str | None = Field(
55
+ default=None, description="The building ID column name."
56
+ )
57
+ Basement_col: str | None = Field(
58
+ default=None, description="The basement column name."
59
+ )
60
+ Attic_col: str | None = Field(default=None, description="The attic column name.")
61
+ Exposed_Basement_Frac_col: str | None = Field(
62
+ default=None, description="The exposed basement fraction column name."
63
+ )
64
+ Weather_File_col: str | None = Field(
65
+ default=None, description="The weather file column name."
66
+ )
67
+
68
+ @property
69
+ def semantic_field_names(self) -> list[str]:
70
+ """The names of the semantic fields."""
71
+ return [f.Name for f in self.Fields]
72
+
73
+ @property
74
+ def rich_field_names(self) -> list[str | None]:
75
+ """The names of the rich fields."""
76
+ return [v for k, v in self.model_dump().items() if k.endswith("_col")]
77
+
78
+ @property
79
+ def field_names(self) -> list[str | None]:
80
+ """The names of the fields."""
81
+ return [*self.semantic_field_names, *self.rich_field_names]
54
82
 
55
83
  @model_validator(mode="after")
56
84
  def check_at_least_one_height_or_num_floors(self):
@@ -1,7 +1,7 @@
1
1
  """Utility functions for working with SBEMs."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import cast
4
+ from typing import Any, cast
5
5
 
6
6
  import yaml
7
7
  from tqdm.autonotebook import tqdm
@@ -15,11 +15,25 @@ from epinterface.sbem.fields.spec import SemanticModelFields
15
15
  from epinterface.sbem.prisma.client import PrismaSettings
16
16
 
17
17
 
18
+ class ModelsNotConstructableError(Exception):
19
+ """An error raised when the models are not constructable."""
20
+
21
+ def __init__(self, failing_contexts: list[dict[str, Any]]):
22
+ """Initialize the error."""
23
+ self.failing_contexts = failing_contexts
24
+ printable_contexts = failing_contexts[: min(10, len(failing_contexts))]
25
+ msg = "The following contexts failed to resolve to a valid zone component:\n"
26
+ msg += "\n\n"
27
+ msg += yaml.dump(printable_contexts, indent=2, sort_keys=False)
28
+ super().__init__(msg)
29
+
30
+
18
31
  def check_model_existence(
19
32
  component_map_path: Path,
20
33
  semantic_fields_path: Path,
21
34
  db_path: Path,
22
35
  max_tests: int = 100,
36
+ raise_on_error: bool = False,
23
37
  ) -> None:
24
38
  """Check if all semantic field combinations resolve to a valid zone component (up to some sampling limit).
25
39
 
@@ -34,6 +48,7 @@ def check_model_existence(
34
48
  semantic_fields_path: Path to the semantic fields file.
35
49
  db_path: Path to the database file.
36
50
  max_tests: Maximum number of tests to run.
51
+ raise_on_error: Whether to raise an error if any tests fail.
37
52
  """
38
53
  with open(semantic_fields_path) as f:
39
54
  semantic_fields_yaml = yaml.safe_load(f)
@@ -61,8 +76,13 @@ def check_model_existence(
61
76
  grid = grid.sample(min(max_tests, len(grid)))
62
77
 
63
78
  # Checks that things that should be in the db are in the db
79
+ failing_contexts = []
64
80
  with db:
65
- for _ix, row in tqdm(grid.iterrows(), total=len(grid)):
81
+ for _ix, row in tqdm(
82
+ grid.iterrows(),
83
+ total=len(grid),
84
+ desc="Checking semantic field/component lib compatibility.",
85
+ ):
66
86
  context = row.to_dict()
67
87
  for field_name, field_val in field_vals.items():
68
88
  context[field_name] = field_val[context[field_name]]
@@ -72,3 +92,6 @@ def check_model_existence(
72
92
  )
73
93
  except Exception as e:
74
94
  print(f"\nError: {e}, context: {context}\n")
95
+ failing_contexts.append(context)
96
+ if raise_on_error and failing_contexts:
97
+ raise ModelsNotConstructableError(failing_contexts)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "epinterface"
3
- version = "v1.0.2"
3
+ version = "v1.0.4"
4
4
  description = "This is a repository for dynamically generating energy models within Python, relying on Archetypal and Eppy for most of its functionality."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -12,6 +12,7 @@ dependencies = [
12
12
  "click==8.1.7",
13
13
  "geopandas~=1.0.1",
14
14
  "httpx~=0.27.2",
15
+ "ladybug-core>=0.44.30",
15
16
  "openpyxl~=3.1.5",
16
17
  "pandas>=2.2,<2.3",
17
18
  "prisma~=0.15.0",
@@ -598,13 +598,14 @@ wheels = [
598
598
 
599
599
  [[package]]
600
600
  name = "epinterface"
601
- version = "1.0.2"
601
+ version = "1.0.4"
602
602
  source = { editable = "." }
603
603
  dependencies = [
604
604
  { name = "archetypal" },
605
605
  { name = "click" },
606
606
  { name = "geopandas" },
607
607
  { name = "httpx" },
608
+ { name = "ladybug-core" },
608
609
  { name = "openpyxl" },
609
610
  { name = "pandas" },
610
611
  { name = "prisma" },
@@ -636,6 +637,7 @@ requires-dist = [
636
637
  { name = "click", specifier = "==8.1.7" },
637
638
  { name = "geopandas", specifier = "~=1.0.1" },
638
639
  { name = "httpx", specifier = "~=0.27.2" },
640
+ { name = "ladybug-core", specifier = ">=0.44.30" },
639
641
  { name = "openpyxl", specifier = "~=3.1.5" },
640
642
  { name = "pandas", specifier = ">=2.2,<2.3" },
641
643
  { name = "prisma", specifier = "~=0.15.0" },
@@ -1428,6 +1430,28 @@ wheels = [
1428
1430
  { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762, upload_time = "2024-12-24T18:30:48.903Z" },
1429
1431
  ]
1430
1432
 
1433
+ [[package]]
1434
+ name = "ladybug-core"
1435
+ version = "0.44.30"
1436
+ source = { registry = "https://pypi.org/simple" }
1437
+ dependencies = [
1438
+ { name = "click" },
1439
+ { name = "ladybug-geometry" },
1440
+ ]
1441
+ sdist = { url = "https://files.pythonhosted.org/packages/bf/d5/c97c70cfbe345d3076a7a4c1c39285bd8617d47a170a9d931882d93019d3/ladybug_core-0.44.30.tar.gz", hash = "sha256:c01e1953b598a4d379d3996403ec6d1686870a3bd8474501be7281068c260aff", size = 242468, upload_time = "2026-01-06T17:26:42.834Z" }
1442
+ wheels = [
1443
+ { url = "https://files.pythonhosted.org/packages/c8/f9/90a46ce7e20d404c310ea1a68479a0ab0be4f995dc5c741bc17fda1702a3/ladybug_core-0.44.30-py3-none-any.whl", hash = "sha256:e5b65802adfd00d65cc1122a52b7ec561fa166c9bc78b240e05940c5f15be96f", size = 272523, upload_time = "2026-01-06T17:26:40.842Z" },
1444
+ ]
1445
+
1446
+ [[package]]
1447
+ name = "ladybug-geometry"
1448
+ version = "1.34.14"
1449
+ source = { registry = "https://pypi.org/simple" }
1450
+ sdist = { url = "https://files.pythonhosted.org/packages/91/0c/1803f43741957787399c802ce07b833f0b69e8bf93630f5b275f88d65881/ladybug_geometry-1.34.14.tar.gz", hash = "sha256:7531f4d6a9f35e1c15f8307282af6ef43056e1652910bd0188114fcecae9a01d", size = 617102, upload_time = "2025-11-07T04:16:48.676Z" }
1451
+ wheels = [
1452
+ { url = "https://files.pythonhosted.org/packages/d4/52/7b8421a8ace22a17ae77dd9a8367e916364ed8be72502cb744805f06d6ac/ladybug_geometry-1.34.14-py3-none-any.whl", hash = "sha256:af91ee9285333ca1ddfaf439530306dff7f0a891cae40d4dc5491f139fcf7d36", size = 198221, upload_time = "2025-11-07T04:16:46.986Z" },
1453
+ ]
1454
+
1431
1455
  [[package]]
1432
1456
  name = "lark"
1433
1457
  version = "1.2.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes