• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

urbanopt / geojson-modelica-translator / 10794805970

10 Sep 2024 02:38PM UTC coverage: 88.037% (-0.2%) from 88.188%
10794805970

Pull #648

github

vtnate
skip removing intermediate file if dir, and add debug logging
Pull Request #648: Update code & dependencies to address deprecation warnings

955 of 1167 branches covered (81.83%)

Branch coverage included in aggregate %.

9 of 13 new or added lines in 5 files covered. (69.23%)

1 existing line in 1 file now uncovered.

2651 of 2929 relevant lines covered (90.51%)

1.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.77
/geojson_modelica_translator/model_connectors/load_connectors/teaser.py
1
# :copyright (c) URBANopt, Alliance for Sustainable Energy, LLC, and other contributors.
2
# See also https://github.com/urbanopt/geojson-modelica-translator/blob/develop/LICENSE.md
3

4
import glob
2✔
5
import os
2✔
6
import shutil
2✔
7
from os import fdopen, remove
2✔
8
from shutil import copymode, move
2✔
9
from tempfile import mkstemp
2✔
10

11
import numpy as np
2✔
12
from modelica_builder.model import Model
2✔
13
from modelica_builder.package_parser import PackageParser
2✔
14
from teaser.project import Project
2✔
15

16
from geojson_modelica_translator.model_connectors.load_connectors.load_base import LoadBase
2✔
17
from geojson_modelica_translator.utils import ModelicaPath, convert_c_to_k, copytree
2✔
18

19

20
class Teaser(LoadBase):
2✔
21
    """TEASER is different than the other model connectors since TEASER creates all of the building models with
22
    multiple thermal zones when running, at which point each building then needs to be processed."""
23

24
    model_name = "Teaser"
2✔
25

26
    def __init__(self, system_parameters, geojson_load):
2✔
27
        super().__init__(system_parameters, geojson_load)
2✔
28
        self.id = f"TeaserLoad_{self.building_name}"
2✔
29

30
    def lookup_building_type(self, building_type):
2✔
31
        """Look up the building type from the Enumerations in the building_properties.json schema. TEASER
32
        documentation on building types is here (look into the python files):
33

34
        https://github.com/RWTH-EBC/TEASER/tree/development/teaser/logic/archetypebuildings/bmvbs
35
        """
36

37
        # Also look at using JSON as the input:
38
        # https://github.com/RWTH-EBC/TEASER/blob/master/teaser/examples/examplefiles/ASHRAE140_600.json
39
        mapping = {
2✔
40
            # Single Family is not configured right now.
41
            "Single-Family": "SingleFamilyDwelling",
42
            "Office": "bmvbs_office",
43
            "Laboratory": "bmvbs_institute8",
44
            "Education": "bmvbs_institute",
45
            "Inpatient health care": "bmvbs_institute8",
46
            "Outpatient health care": "bmvbs_institute4",
47
            "Nursing": "bmvbs_institute4",
48
            "Service": "bmvbs_institute4",
49
            "Retail other than mall": "bmvbs_office",
50
            "Strip shopping mall": "bmvbs_office",
51
            "Enclosed mall": "bmvbs_office",
52
            "Food sales": "bmvbs_institute4",
53
            "Food service": "bmvbs_institute4",
54
        }
55

56
        # Other types to map!
57
        #         "Multifamily (2 to 4 units)",
58
        #         "Multifamily (5 or more units)",
59
        #         "Mobile Home",
60
        #         "Vacant",
61
        #         "Nonrefrigerated warehouse",
62
        #         "Public order and safety",
63
        #         "Refrigerated warehouse",
64
        #         "Religious worship",
65
        #         "Public assembly",
66
        #         "Lodging",
67
        #         "Mixed use",
68
        #         "Uncovered Parking",
69
        #         "Covered Parking"
70
        if building_type in mapping:
2!
71
            return mapping[building_type]
2✔
72
        else:
73
            raise Exception(f"Building type of {building_type} not defined in GeoJSON to TEASER mappings")
×
74

75
    def to_modelica(self, scaffold, keep_original_models=False):
2✔
76
        """Save the TEASER representation of a single building to the filesystem. The path will
77
        be scaffold.loads_path.files_dir.
78

79
        :param scaffold: Scaffold object, contains all the paths of the project
80
        :param keep_original_models: boolean, whether or not to remove the models after exporting from Teaser
81
        """
82
        # Teaser changes the current dir, so make sure to reset it back to where we started
83
        curdir = os.getcwd()
2✔
84
        try:
2✔
85
            prj = Project()
2✔
86
            prj.add_non_residential(
2✔
87
                geometry_data=self.lookup_building_type(self.building["building_type"]),
88
                name=self.building_name,
89
                year_of_construction=self.building["year_built"],
90
                number_of_floors=self.building["num_stories"],
91
                height_of_floors=self.building["floor_height"],
92
                net_leased_area=self.building["area"],
93
                office_layout=1,
94
                window_layout=1,
95
                with_ahu=False,
96
                construction_data="iwu_heavy",
97
            )
98

99
            prj.used_library_calc = "IBPSA"
2✔
100
            prj.number_of_elements_calc = self.system_parameters.get_param_by_id(
2✔
101
                self.building_id, "load_model_parameters.rc.order"
102
            )
103
            prj.merge_windows_calc = False
2✔
104

105
            # calculate the properties of all the buildings (just one in this case)
106
            # and export to the Buildings library
107
            prj.calc_all_buildings()
2✔
108
            prj.export_ibpsa(library="Buildings", path=os.path.join(curdir, scaffold.loads_path.files_dir))
2✔
109

110
        finally:
111
            os.chdir(curdir)
2✔
112

113
        self.post_process(scaffold, keep_original_models=keep_original_models)
2✔
114

115
    def fix_gains_file(self, f):
2✔
116
        """Temporary hack to fix the gains files in TEASER. This method does the following:
117
            * makes the dimension of the matrix to 8761,4
118
            * adds in a timestep for t=0
119

120
        :param f: string, fully qualified path to file
121
        :return: None
122
        """
123
        fh, abs_path = mkstemp()  # make a temp file
2✔
124
        with fdopen(fh, "w") as new_file, open(f) as old_file:
2✔
125
            for line in old_file:
2✔
126
                if "double Internals(8760" in line:  # Finding the line, which may have one of several #s of columns
2!
UNCOV
127
                    new_file.write("double Internals(8761, 4)\n")
×
128
                elif line.startswith("3600\t"):
2✔
129
                    new_file.write(line.replace("3600\t", "0\t") + line)
2✔
130
                else:
131
                    new_file.write(line)
2✔
132
        copymode(f, abs_path)  # copies permissions
2✔
133
        remove(f)
2✔
134
        move(abs_path, f)
2✔
135

136
    def post_process(self, scaffold, keep_original_models=False):
2✔
137
        """Cleanup the export of the TEASER files into a format suitable for the district-based analysis.
138
        This includes the following:
139

140
            * Update the partial to inherit from the GeojsonExport class defined in MBL.
141
            * Rename the files to remove the names of the buildings
142
            * Move the files to the Loads level and remove the Project folder (default export method from TEASER)
143
            * Add heat port
144
            * Add return temperature
145
            * Add fluid ports for the indoor air volume.
146
            * Remove weaDat and rely on weaBus
147
            * Add latent fraction multiplier
148
            * Add TAir output
149
            * Add TRad output
150
            * Add nPorts variable
151
            * Propagate use of moisture balance
152
            * Wrap the thermal zones into a single model
153

154
        :param scaffold: Scaffold object, Scaffold of the entire directory of the project.
155
        :param keep_original_models: boolean, do not delete the original models generated by TEASER
156
        :return: None
157
        """
158

159
        teaser_building = self.template_env.get_template("TeaserBuilding.mot")
2✔
160
        teaser_coupling = self.template_env.get_template("TeaserCouplingBuilding.mot")
2✔
161
        run_coupling_template = self.template_env.get_template("RunTeaserCouplingBuilding.most")
2✔
162

163
        # This for loop does *a lot* of work to make the models compatible for the project structure.
164
        # Need to investigate moving this into a more testable location.
165
        # create a list of strings that we need to replace in all the file as we go along
166
        mos_weather_filename = self.system_parameters.get_param("$.weather")
2✔
167
        # create a new modelica based path for the buildings # TODO: make this work at the toplevel, somehow.
168
        b_modelica_path = ModelicaPath(self.building_name, scaffold.loads_path.files_dir, True)
2✔
169

170
        # copy over the entire model to the new location
171
        copytree(
2✔
172
            os.path.join(scaffold.loads_path.files_dir, f"Project/{self.building_name}/{self.building_name}_Models"),
173
            b_modelica_path.files_dir,
174
        )
175

176
        # read in the package to apply the changes as they other files are processed
177
        # TODO: these should be linked, so a rename method should act across the model and the package.order
178
        package = PackageParser(os.path.join(scaffold.loads_path.files_dir, self.building_name))
2✔
179

180
        # move the internal gains files to a new resources folder
181
        mat_files = glob.glob(os.path.join(scaffold.loads_path.files_dir, f"{self.building_name}/*.txt"))
2✔
182
        for f in mat_files:
2✔
183
            new_file_name = os.path.basename(f).replace(self.building_name, "")
2✔
184
            os.rename(f, f"{b_modelica_path.resources_dir}/{new_file_name}")
2✔
185

186
            # The main branch of teaser has yet to merge in the changes to support the fixes to the
187
            # internal gain files. The next method can be removed once the TEASER development branch is
188
            # merged into master/main and released.
189
            self.fix_gains_file(f"{b_modelica_path.resources_dir}/{new_file_name}")
2✔
190
            self.internal_gains_file = (
2✔
191
                f"{scaffold.project_name}/Loads/{b_modelica_path.resources_relative_dir}/{new_file_name}"
192
            )
193

194
        # process each of the thermal zones
195
        thermal_zone_files = []
2✔
196
        mo_files = glob.glob(os.path.join(scaffold.loads_path.files_dir, f"{self.building_name}/*.mo"))
2✔
197
        for f in mo_files:
2✔
198
            # ignore the package.mo file
199
            if os.path.basename(f) in ["package.mo"]:
2✔
200
                continue
2✔
201

202
            mofile = Model(f)
2✔
203

204
            # previous paths and replace with the new one.
205
            # Make sure to update the names of any resources as well.
206
            mofile.set_within_statement(f"{scaffold.project_name}.Loads.{self.building_name}")
2✔
207

208
            # remove ReaderTMY3
209
            mofile.remove_component("Buildings.BoundaryConditions.WeatherData.ReaderTMY3", "weaDat")
2✔
210

211
            # Remove `lat` from diffuse & direct solar gains (removed from MBL in version 9)
212
            mofile.remove_component_argument(
2✔
213
                "Buildings.BoundaryConditions.SolarIrradiation.DiffusePerez", "HDifTil", "lat"
214
            )
215
            mofile.remove_component_argument(
2✔
216
                "Buildings.BoundaryConditions.SolarIrradiation.DiffusePerez", "HDifTilRoof", "lat"
217
            )
218
            mofile.remove_component_argument(
2✔
219
                "Buildings.BoundaryConditions.SolarIrradiation.DirectTiltedSurface", "HDirTil", "lat"
220
            )
221
            mofile.remove_component_argument(
2✔
222
                "Buildings.BoundaryConditions.SolarIrradiation.DirectTiltedSurface", "HDirTilRoof", "lat"
223
            )
224

225
            # updating path to internal loads
226

227
            mofile.update_component_modification(
2✔
228
                "Modelica.Blocks.Sources.CombiTimeTable",
229
                "internalGains",
230
                "fileName",
231
                "Modelica.Utilities.Files.loadResource(filNam)",
232
            )
233

234
            # add heat port convective heat flow.
235
            mofile.insert_component(
2✔
236
                "Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a",
237
                "port_a",
238
                string_comment="Heat port for convective heat flow.",
239
                annotations=[
240
                    "Placement(transformation(extent={{-10,90},{10,110}}), "
241
                    "iconTransformation(extent={{-10,90},{10,110}}))"
242
                ],
243
            )
244
            # add heat port radiative heat flow.
245
            mofile.insert_component(
2✔
246
                "Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a",
247
                "port_b",
248
                string_comment="Heat port for radiative heat flow.",
249
                annotations=[
250
                    "Placement(transformation(extent={{30,-110},{50,-90}}, "
251
                    "iconTransformation(extent={{40,-112},{60,-92}})))"
252
                ],
253
            )
254
            # add fluid ports for the indoor air volume.
255
            mofile.insert_component(
2✔
256
                "Modelica.Fluid.Vessels.BaseClasses.VesselFluidPorts_b",
257
                "ports[nPorts]",
258
                string_comment="Auxiliary fluid inlets and outlets to indoor air volume.",
259
                modifications={"redeclare each final package Medium": "Buildings.Media.Air"},
260
                annotations=[
261
                    "Placement(transformation(extent={{-30, -8}, {30, 8}},origin={0, -100}), "
262
                    "iconTransformation(extent={{-23.25, -7.25}, {23.25, 7.25}},"
263
                    "origin={-0.75, -98.75}))"
264
                ],
265
            )
266

267
            fraction_latent_person = self.system_parameters.get_param(
2✔
268
                "buildings.load_model_parameters.rc.fraction_latent_person", default=1.25
269
            )
270

271
            use_moisture_balance = self.system_parameters.get_param(
2✔
272
                "buildings.load_model_parameters.rc.use_moisture_balance", default="false"
273
            )
274

275
            n_ports = self.system_parameters.get_param("buildings.load_model_parameters.rc.nPorts", default=0)
2✔
276

277
            # create a new parameter for fraction latent person
278
            mofile.add_parameter(
2✔
279
                "Real",
280
                "fraLat",
281
                assigned_value=fraction_latent_person,
282
                string_comment="Fraction latent of sensible persons load = 0.8 for home, 1.25 for office.",
283
            )
284
            # create a new Boolean parameter to evaluate the persons latent loads.
285
            mofile.add_parameter(
2✔
286
                "Boolean",
287
                "use_moisture_balance",
288
                assigned_value=use_moisture_balance,
289
                string_comment="If true, input connector QLat_flow is enabled and room air computes"
290
                " moisture balance.",
291
            )
292
            # create a integer parameter to evaluate number of connected ports.
293
            mofile.add_parameter(
2✔
294
                "Integer",
295
                "nPorts",
296
                assigned_value=n_ports,
297
                string_comment="Number of fluid ports.",
298
                annotations=["connectorSizing=true"],
299
            )
300
            # Add a parameter to put the load filename at the top of the model
301
            mofile.add_parameter(
2✔
302
                "String",
303
                "filNam",
304
                assigned_value=f'"modelica://{self.internal_gains_file}"',
305
                string_comment="modelica path to the internal gains file used in the model CombiTimeTable",
306
            )
307

308
            # Set the fraction latent person in the template by simply replacing the value
309
            mofile.insert_component(
2✔
310
                "Modelica.Blocks.Sources.RealExpression",
311
                "perLatLoa",
312
                modifications={
313
                    "y": "internalGains.y[2]*fraLat",
314
                },
315
                conditional="if use_moisture_balance",
316
                string_comment="Latent person loads",
317
                annotations=["Placement(transformation(extent={{-80,-60},{-60,-40}}))"],
318
            )
319

320
            # add TRad output
321
            mofile.insert_component(
2✔
322
                "Buildings.Controls.OBC.CDL.Interfaces.RealOutput",
323
                "TRad",
324
                modifications={
325
                    "quantity": '"ThermodynamicTemperature"',
326
                    "unit": '"K"',
327
                    "displayUnit": '"degC"',
328
                },
329
                string_comment="Mean indoor radiation temperature",
330
                annotations=["Placement(transformation(extent={{100,-10},{120,10}}))"],
331
            )
332

333
            # All existing weaDat.weaBus connections need to be updated to simply weaBus
334
            # (except for the connections where 'weaBus' is port_b, we will just delete these)
335
            mofile.edit_connect("weaDat.weaBus", "!weaBus", new_port_a="weaBus")
2✔
336
            # Now remove the unnecessary weaDat.weaBus -> weaBus connection
337
            mofile.remove_connect("weaDat.weaBus", "weaBus")
2✔
338

339
            # add new port connections
340
            rc_order = self.system_parameters.get_param_by_id(self.building_id, "load_model_parameters.rc.order")
2✔
341
            thermal_zone_name = None
2✔
342
            thermal_zone_type = None
2✔
343
            if rc_order == 1:
2!
344
                thermal_zone_type = "OneElement"
×
345
                thermal_zone_name = "thermalZoneOneElement"
×
346
            elif rc_order == 2:
2!
347
                thermal_zone_type = "TwoElements"
×
348
                thermal_zone_name = "thermalZoneTwoElements"
×
349
            elif rc_order == 3:
2!
350
                thermal_zone_type = "ThreeElements"
×
351
                thermal_zone_name = "thermalZoneThreeElements"
×
352
            elif rc_order == 4:
2!
353
                thermal_zone_type = "FourElements"
2✔
354
                thermal_zone_name = "thermalZoneFourElements"
2✔
355

356
            if thermal_zone_name is not None and thermal_zone_type is not None:
2!
357
                # add TAir output - This has been moved away from the other insert_component blocks
358
                # to use thermal_zone_name
359
                mofile.insert_component(
2✔
360
                    "Buildings.Controls.OBC.CDL.Interfaces.RealOutput",
361
                    "TAir",
362
                    modifications={
363
                        "quantity": '"ThermodynamicTemperature"',
364
                        "unit": '"K"',
365
                        "displayUnit": '"degC"',
366
                    },
367
                    conditional=f"if {thermal_zone_name}.VAir > 0",
368
                    string_comment="Room air temperature",
369
                    annotations=["Placement(transformation(extent={{100,38},{120,58}}))"],
370
                )
371

372
                # In TEASER 0.7.5 the hConvWinOut, hConvExt, hConvWin, hConvInt, hConvFloor, hConvRoof in various of
373
                # the ReducedOrder models should be hCon* not hConv*. This has been fixed on the development branch
374
                # of TEASER, but the team doesn't appear to be releasing nor merging the development branch (yet).
375
                mofile.rename_component_argument(
2✔
376
                    "Buildings.ThermalZones.ReducedOrder.EquivalentAirTemperature.VDI6007WithWindow",
377
                    "eqAirTemp",
378
                    "hConvWallOut",
379
                    "hConWallOut",
380
                )
381
                mofile.rename_component_argument(
2✔
382
                    "Buildings.ThermalZones.ReducedOrder.EquivalentAirTemperature.VDI6007WithWindow",
383
                    "eqAirTemp",
384
                    "hConvWinOut",
385
                    "hConWinOut",
386
                )
387

388
                mofile.rename_component_argument(
2✔
389
                    "Buildings.ThermalZones.ReducedOrder.EquivalentAirTemperature.VDI6007",
390
                    "eqAirTempVDI",
391
                    "hConvWallOut",
392
                    "hConWallOut",
393
                )
394

395
                renames = {
2✔
396
                    "hConvExt": "hConExt",
397
                    "hConvFloor": "hConFloor",
398
                    "hConvRoof": "hConRoof",
399
                    "hConvWinOut": "hConWinOut",
400
                    "hConvWin": "hConWin",
401
                    "hConvInt": "hConInt",
402
                }
403
                for from_, to_ in renames.items():
2✔
404
                    mofile.rename_component_argument(
2✔
405
                        f"Buildings.ThermalZones.ReducedOrder.RC.{thermal_zone_type}", thermal_zone_name, from_, to_
406
                    )
407

408
                mofile.update_component_modifications(
2✔
409
                    f"Buildings.ThermalZones.ReducedOrder.RC.{thermal_zone_type}",
410
                    thermal_zone_name,
411
                    {"use_moisture_balance": "use_moisture_balance"},
412
                )
413

414
                mofile.update_component_modifications(
2✔
415
                    f"Buildings.ThermalZones.ReducedOrder.RC.{thermal_zone_type}",
416
                    thermal_zone_name,
417
                    {"nPorts": "nPorts"},
418
                )
419

420
                mofile.add_connect(
2✔
421
                    "port_a",
422
                    f"{thermal_zone_name}.intGainsConv",
423
                    annotations=["Line(points={{0,100},{96,100},{96,20},{92,20}}, color={191,0,0})"],
424
                )
425

426
                mofile.add_connect(
2✔
427
                    f"{thermal_zone_name}.TAir",
428
                    "TAir",
429
                    annotations=["Line(points={{93,32},{98,32},{98,48},{110,48}}, color={0,0,127})"],
430
                )
431
                mofile.add_connect(
2✔
432
                    f"{thermal_zone_name}.TRad",
433
                    "TRad",
434
                    annotations=["Line(points={{93,28},{98,28},{98,-20},{110,-20}}, color={0,0,127})"],
435
                )
436
                mofile.add_connect(
2✔
437
                    f"{thermal_zone_name}.QLat_flow",
438
                    "perLatLoa.y",
439
                    annotations=[
440
                        "Line(points={{43,4},{40,4},{40,-28},{-40,-28},{-40,-50},{-59,-50}}, color={0, 0,127})"
441
                    ],
442
                )
443

444
                mofile.add_connect(
2✔
445
                    f"{thermal_zone_name}.intGainsRad",
446
                    "port_b",
447
                    annotations=["Line(points={{92, 24}, {98, 24}, {98, -100}, {40, -100}}, color={191, 0, 0})"],
448
                )
449
                mofile.insert_equation_for_loop(
2✔
450
                    index_identifier="i",
451
                    expression_raw="1:nPorts",
452
                    loop_body_raw_list=[
453
                        f"connect(ports[i], {thermal_zone_name}.ports[i])",
454
                        "\tannotation (Line(",
455
                        "\tpoints={{-18,-102},{-18,-84},{83,-84},{83,-1.95}},",
456
                        "\tcolor={0,127,255},",
457
                        "\tsmooth=Smooth.None));",
458
                    ],
459
                )
460

461
                # Fix the thermalZone medium model
462
                mofile.overwrite_component_redeclaration(
2✔
463
                    f"Buildings.ThermalZones.ReducedOrder.RC.{thermal_zone_type}",
464
                    thermal_zone_name,
465
                    "Medium = Buildings.Media.Air",
466
                )
467

468
            # change the name of the modelica model to remove the building id, update in package too!
469
            original_model_name = mofile.get_name()
2✔
470
            new_model_name = original_model_name.split("_")[1]
2✔
471
            thermal_zone_files.append(new_model_name)
2✔
472
            package.rename_model(original_model_name, new_model_name)
2✔
473
            mofile.set_name(new_model_name)
2✔
474

475
            # Save as the new filename (without building ID)
476
            new_filename = os.path.join(
2✔
477
                scaffold.loads_path.files_dir, f'{self.building_name}/{os.path.basename(f).split("_")[1]}'
478
            )
479
            mofile.save_as(new_filename)
2✔
480
            os.remove(f)
2✔
481

482
        # Now connect all the thermal zone files into the teaser building
483
        # 1. Need to a map of thermal zone names and instances
484
        zone_list = []
2✔
485
        for index, tz in enumerate(thermal_zone_files):
2✔
486
            # take /a/file/Meeting.mo -> zone_map["Meeting"] = "meeting"
487
            tz_process = os.path.splitext(os.path.basename(tz))[0]
2✔
488
            zone_list.append(
2✔
489
                {
490
                    "index": index,
491
                    "model_name": tz_process,
492
                    "instance_name": tz_process.lower(),
493
                    # process where this will be stored in python otherwise too many {{}}, yes ridiculous.
494
                    # This needs to result in {{a,b},{x,y}}
495
                    "placement": f"{{{{{-160 + index * 40},-20}},{{{-140 + index * 40},0}}}}",
496
                }
497
            )
498

499
        # Handle setting nominal load for IT room zone
500
        nom_cool_flow = np.array([-10000] * len(zone_list))
2✔
501
        for i, dic in enumerate(zone_list):
2✔
502
            if dic["instance_name"] == "ict":
2✔
503
                nom_cool_flow[i - 1] = -50000  # Need to offset for different indexing
2✔
504
        nom_heat_flow = np.array([10000] * len(zone_list))
2✔
505
        building_template_data = {
2✔
506
            "thermal_zones": zone_list,
507
            "nominal_heat_flow": str(repr(nom_heat_flow))[1:-1]
508
            .replace("[", "{")
509
            .replace("]", "}")
510
            .split("rray(", 1)[-1],
511
            "nominal_cool_flow": str(repr(nom_cool_flow))[1:-1]
512
            .replace("[", "{")
513
            .replace("]", "}")
514
            .split("rray(", 1)[-1],
515
            "load_resources_path": b_modelica_path.resources_relative_dir,
516
            "mos_weather": {
517
                "mos_weather_filename": mos_weather_filename,
518
                "filename": os.path.basename(mos_weather_filename),
519
                "path": os.path.dirname(mos_weather_filename),
520
            },
521
            "nominal_values": {
522
                "chw_supply_temp": convert_c_to_k(
523
                    self.system_parameters.get_param_by_id(self.building_id, "load_model_parameters.rc.temp_chw_supply")
524
                ),
525
                "chw_return_temp": convert_c_to_k(
526
                    self.system_parameters.get_param_by_id(self.building_id, "load_model_parameters.rc.temp_chw_return")
527
                ),
528
                "hhw_supply_temp": convert_c_to_k(
529
                    self.system_parameters.get_param_by_id(self.building_id, "load_model_parameters.rc.temp_hw_supply")
530
                ),
531
                "hhw_return_temp": convert_c_to_k(
532
                    self.system_parameters.get_param_by_id(self.building_id, "load_model_parameters.rc.temp_hw_return")
533
                ),
534
                "temp_setpoint_heating": convert_c_to_k(
535
                    self.system_parameters.get_param_by_id(
536
                        self.building_id, "load_model_parameters.rc.temp_setpoint_heating"
537
                    )
538
                ),
539
                "temp_setpoint_cooling": convert_c_to_k(
540
                    self.system_parameters.get_param_by_id(
541
                        self.building_id, "load_model_parameters.rc.temp_setpoint_cooling"
542
                    )
543
                ),
544
                # FIXME: pick up default value from schema if not specified in system_parameters,
545
                # FYI: Modelica insists on booleans being lowercase, so we need to explicitly set "true" and "false"
546
                "has_liquid_heating": "true"
547
                if self.system_parameters.get_param_by_id(
548
                    self.building_id,
549
                    "load_model_parameters.rc.has_liquid_heating",
550
                )
551
                else "false",
552
                "has_liquid_cooling": "true"
553
                if self.system_parameters.get_param_by_id(
554
                    self.building_id,
555
                    "load_model_parameters.rc.has_liquid_cooling",
556
                )
557
                else "false",
558
                "has_electric_heating": "true"
559
                if self.system_parameters.get_param_by_id(
560
                    self.building_id,
561
                    "load_model_parameters.rc.has_electric_heating",
562
                )
563
                else "false",
564
                "has_electric_cooling": "true"
565
                if self.system_parameters.get_param_by_id(
566
                    self.building_id,
567
                    "load_model_parameters.rc.has_electric_cooling",
568
                )
569
                else "false",
570
            },
571
        }
572

573
        # merge ets template values from load_base.py into the building nominal values
574
        # If there is no ets defined in sys-param file, use the building template data alone
575
        try:
2✔
576
            nominal_values = {**building_template_data["nominal_values"], **self.ets_template_data}
2✔
577
            combined_template_data = {**building_template_data, **nominal_values}
2✔
578
        except AttributeError:
×
579
            combined_template_data = building_template_data
×
580

581
        self.run_template(
2✔
582
            teaser_building,
583
            os.path.join(b_modelica_path.files_dir, "building.mo"),
584
            project_name=scaffold.project_name,
585
            model_name=self.building_name,
586
            data=combined_template_data,
587
        )
588

589
        self.run_template(
2✔
590
            teaser_coupling,
591
            os.path.join(os.path.join(b_modelica_path.files_dir, "coupling.mo")),
592
            project_name=scaffold.project_name,
593
            model_name=self.building_name,
594
            data=combined_template_data,
595
        )
596

597
        full_model_name = os.path.join(
2✔
598
            scaffold.project_name, scaffold.loads_path.files_relative_dir, self.building_name, "coupling"
599
        ).replace(os.path.sep, ".")
600

601
        self.run_template(
2✔
602
            run_coupling_template,
603
            os.path.join(os.path.join(b_modelica_path.scripts_dir, "RunTeaserCouplingBuilding.mos")),
604
            full_model_name=full_model_name,
605
            model_name="coupling",
606
        )
607

608
        # copy over the required mo files and add the other models to the package order
609
        mo_files = self.copy_required_mo_files(b_modelica_path.files_dir, within=f"{scaffold.project_name}.Loads")
2✔
610
        for f in mo_files:
2!
611
            package.add_model(os.path.splitext(os.path.basename(f))[0])
×
612
        package.add_model("building")
2✔
613
        package.add_model("coupling")
2✔
614

615
        # save the updated package.mo and package.order in the Loads.B{} folder
616
        new_package = PackageParser.new_from_template(
2✔
617
            package.path, self.building_name, package.order, within=f"{scaffold.project_name}.Loads"
618
        )
619
        new_package.save()
2✔
620
        # AA added this 9/24
621
        if os.path.exists(building_template_data["mos_weather"]["mos_weather_filename"]):
2!
622
            shutil.copy(
2✔
623
                building_template_data["mos_weather"]["mos_weather_filename"],
624
                os.path.join(b_modelica_path.resources_dir, building_template_data["mos_weather"]["filename"]),
625
            )
626
        else:
627
            raise Exception(
×
628
                f"Missing MOS weather file for Teaser: {building_template_data['mos_weather']['mos_weather_filename']}"
629
            )
630
        # end of what AA added 9/24
631

632
        # remaining clean up tasks across the entire exported project
633
        if not keep_original_models:
2!
634
            shutil.rmtree(os.path.join(scaffold.loads_path.files_dir, "Project"))
2✔
635

636
        # now create the Loads level package and package.order.
637
        if not os.path.exists(os.path.join(scaffold.loads_path.files_dir, "package.mo")):
2✔
638
            load_package = PackageParser.new_from_template(
2✔
639
                scaffold.loads_path.files_dir, "Loads", [self.building_name], within=f"{scaffold.project_name}"
640
            )
641
            load_package.save()
2✔
642
        else:
643
            load_package = PackageParser(os.path.join(scaffold.loads_path.files_dir))
2✔
644
            load_package.add_model(self.building_name)
2✔
645
            load_package.save()
2✔
646

647
        # now create the Package level package. This really needs to happen at the GeoJSON to modelica stage, but
648
        # do it here for now to aid in testing.
649
        package = PackageParser(scaffold.project_path)
2✔
650
        if "Loads" not in package.order:
2✔
651
            package.add_model("Loads")
2✔
652
            package.save()
2✔
653

654
    def get_modelica_type(self, scaffold):
2✔
655
        return f"Loads.{self.building_name}.building"
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc