urbanopt-reporting 0.1.0

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.rubocop.yml +10 -0
  4. data/CHANGELOG.md +7 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/Gemfile +18 -0
  7. data/Jenkinsfile +10 -0
  8. data/LICENSE.md +27 -0
  9. data/README.md +40 -0
  10. data/Rakefile +45 -0
  11. data/doc_templates/LICENSE.md +27 -0
  12. data/doc_templates/README.md.erb +42 -0
  13. data/doc_templates/copyright_erb.txt +31 -0
  14. data/doc_templates/copyright_js.txt +4 -0
  15. data/doc_templates/copyright_ruby.txt +29 -0
  16. data/lib/measures/.rubocop.yml +5 -0
  17. data/lib/measures/default_feature_reports/LICENSE.md +27 -0
  18. data/lib/measures/default_feature_reports/README.md +26 -0
  19. data/lib/measures/default_feature_reports/README.md.erb +42 -0
  20. data/lib/measures/default_feature_reports/measure.rb +1012 -0
  21. data/lib/measures/default_feature_reports/measure.xml +160 -0
  22. data/lib/urbanopt/reporting.rb +37 -0
  23. data/lib/urbanopt/reporting/default_reports.rb +44 -0
  24. data/lib/urbanopt/reporting/default_reports/construction_cost.rb +169 -0
  25. data/lib/urbanopt/reporting/default_reports/date.rb +97 -0
  26. data/lib/urbanopt/reporting/default_reports/distributed_generation.rb +379 -0
  27. data/lib/urbanopt/reporting/default_reports/end_use.rb +159 -0
  28. data/lib/urbanopt/reporting/default_reports/end_uses.rb +140 -0
  29. data/lib/urbanopt/reporting/default_reports/extension.rb +15 -0
  30. data/lib/urbanopt/reporting/default_reports/feature_report.rb +266 -0
  31. data/lib/urbanopt/reporting/default_reports/generator.rb +92 -0
  32. data/lib/urbanopt/reporting/default_reports/location.rb +99 -0
  33. data/lib/urbanopt/reporting/default_reports/logger.rb +44 -0
  34. data/lib/urbanopt/reporting/default_reports/power_distribution.rb +103 -0
  35. data/lib/urbanopt/reporting/default_reports/program.rb +265 -0
  36. data/lib/urbanopt/reporting/default_reports/reporting_period.rb +300 -0
  37. data/lib/urbanopt/reporting/default_reports/scenario_report.rb +317 -0
  38. data/lib/urbanopt/reporting/default_reports/schema/README.md +33 -0
  39. data/lib/urbanopt/reporting/default_reports/schema/scenario_csv_columns.txt +34 -0
  40. data/lib/urbanopt/reporting/default_reports/schema/scenario_schema.json +857 -0
  41. data/lib/urbanopt/reporting/default_reports/solar_pv.rb +93 -0
  42. data/lib/urbanopt/reporting/default_reports/storage.rb +105 -0
  43. data/lib/urbanopt/reporting/default_reports/timeseries_csv.rb +300 -0
  44. data/lib/urbanopt/reporting/default_reports/validator.rb +112 -0
  45. data/lib/urbanopt/reporting/default_reports/wind.rb +92 -0
  46. data/lib/urbanopt/reporting/derived_extension.rb +63 -0
  47. data/lib/urbanopt/reporting/version.rb +35 -0
  48. data/urbanopt-reporting-gem.gemspec +33 -0
  49. metadata +176 -0
@@ -0,0 +1,42 @@
1
+ <%#= README.md.erb is used to auto-generate README.md. %>
2
+ <%#= To manually maintain README.md throw away README.md.erb and manually edit README.md %>
3
+ ###### (Automatically generated documentation)
4
+
5
+ # <%= name %>
6
+
7
+ ## Description
8
+ <%= description %>
9
+
10
+ ## Modeler Description
11
+ <%= modelerDescription %>
12
+
13
+ ## Measure Type
14
+ <%= measureType %>
15
+
16
+ ## Taxonomy
17
+ <%= taxonomy %>
18
+
19
+ ## Arguments
20
+
21
+ <% arguments.each do |argument| %>
22
+ ### <%= argument[:display_name] %>
23
+ <%= argument[:description] %>
24
+ **Name:** <%= argument[:name] %>,
25
+ **Type:** <%= argument[:type] %>,
26
+ **Units:** <%= argument[:units] %>,
27
+ **Required:** <%= argument[:required] %>,
28
+ **Model Dependent:** <%= argument[:model_dependent] %>
29
+ <% end %>
30
+
31
+ <% if arguments.size == 0 %>
32
+ <%= "This measure does not have any user arguments" %>
33
+ <% end %>
34
+
35
+ <% if outputs.size > 0 %>
36
+ ## Outputs
37
+ <% output_names = [] %>
38
+ <% outputs.each do |output| %>
39
+ <% output_names << output[:display_name] %>
40
+ <% end %>
41
+ <%= output_names.join(", ") %>
42
+ <% end %>
@@ -0,0 +1,1012 @@
1
+ # *********************************************************************************
2
+ # URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
3
+ # contributors. All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice, this list
9
+ # of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright notice, this
12
+ # list of conditions and the following disclaimer in the documentation and/or other
13
+ # materials provided with the distribution.
14
+ #
15
+ # Neither the name of the copyright holder nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without specific
17
+ # prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ # *********************************************************************************
30
+
31
+ require 'urbanopt/reporting/default_reports'
32
+ require 'csv'
33
+ require 'benchmark'
34
+ require 'logger'
35
+
36
+ @@logger = Logger.new(STDOUT)
37
+
38
+ # start the measure
39
+ class DefaultFeatureReports < OpenStudio::Measure::ReportingMeasure
40
+ # human readable name
41
+ def name
42
+ return 'DefaultFeatureReports'
43
+ end
44
+
45
+ # human readable description
46
+ def description
47
+ return 'Writes default_feature_reports.json and default_feature_reports.csv files used by URBANopt Scenario Default Post Processor'
48
+ end
49
+
50
+ # human readable description of modeling approach
51
+ def modeler_description
52
+ return 'This measure only allows for one feature_report per simulation. If multiple features are simulated in a single simulation, a new measure must be written to disaggregate simulation results to multiple features.'
53
+ end
54
+
55
+ # define the arguments that the user will input
56
+ def arguments
57
+ args = OpenStudio::Measure::OSArgumentVector.new
58
+
59
+ id = OpenStudio::Measure::OSArgument.makeStringArgument('feature_id', false)
60
+ id.setDisplayName('Feature unique identifier')
61
+ id.setDefaultValue('1')
62
+ args << id
63
+
64
+ name = OpenStudio::Measure::OSArgument.makeStringArgument('feature_name', false)
65
+ name.setDisplayName('Feature scenario specific name')
66
+ name.setDefaultValue('name')
67
+ args << name
68
+
69
+ feature_type = OpenStudio::Measure::OSArgument.makeStringArgument('feature_type', false)
70
+ feature_type.setDisplayName('URBANopt Feature Type')
71
+ feature_type.setDefaultValue('Building')
72
+ args << feature_type
73
+
74
+ # make an argument for the frequency
75
+ reporting_frequency_chs = OpenStudio::StringVector.new
76
+ reporting_frequency_chs << 'Detailed'
77
+ reporting_frequency_chs << 'Timestep'
78
+ reporting_frequency_chs << 'Hourly'
79
+ reporting_frequency_chs << 'Daily'
80
+ # reporting_frequency_chs << 'Zone Timestep'
81
+ reporting_frequency_chs << 'BillingPeriod' # match it to utility bill object
82
+ ## Utility report here to report the start and end for each fueltype
83
+ reporting_frequency_chs << 'Monthly'
84
+ reporting_frequency_chs << 'Runperiod'
85
+
86
+ reporting_frequency = OpenStudio::Measure::OSArgument.makeChoiceArgument('reporting_frequency', reporting_frequency_chs, true)
87
+ reporting_frequency.setDisplayName('Reporting Frequency')
88
+ reporting_frequency.setDescription('The frequency at which to report timeseries output data.')
89
+ reporting_frequency.setDefaultValue('Timestep')
90
+ args << reporting_frequency
91
+
92
+ return args
93
+ end
94
+
95
+ # define fuel types
96
+ def fuel_types
97
+ fuel_types = [
98
+ 'Electricity',
99
+ 'Gas',
100
+ 'AdditionalFuel',
101
+ 'DistrictCooling',
102
+ 'DistrictHeating',
103
+ 'Water'
104
+ ]
105
+
106
+ return fuel_types
107
+ end
108
+
109
+ # define enduses
110
+ def end_uses
111
+ end_uses = [
112
+ 'Heating',
113
+ 'Cooling',
114
+ 'InteriorLights',
115
+ 'ExteriorLights',
116
+ 'InteriorEquipment',
117
+ 'ExteriorEquipment',
118
+ 'Fans',
119
+ 'Pumps',
120
+ 'HeatRejection',
121
+ 'Humidifier',
122
+ 'HeatRecovery',
123
+ 'WaterSystems',
124
+ 'Refrigeration',
125
+ 'Generators',
126
+ 'Facility'
127
+ ]
128
+
129
+ return end_uses
130
+ end
131
+
132
+ # format datetime
133
+ def format_datetime(date_time)
134
+ date_time.tr!('-', '/')
135
+ date_time.gsub!('Jan', '01')
136
+ date_time.gsub!('Feb', '02')
137
+ date_time.gsub!('Mar', '03')
138
+ date_time.gsub!('Apr', '04')
139
+ date_time.gsub!('May', '05')
140
+ date_time.gsub!('Jun', '06')
141
+ date_time.gsub!('Jul', '07')
142
+ date_time.gsub!('Aug', '08')
143
+ date_time.gsub!('Sep', '09')
144
+ date_time.gsub!('Oct', '10')
145
+ date_time.gsub!('Nov', '11')
146
+ date_time.gsub!('Dec', '12')
147
+ return date_time
148
+ end
149
+
150
+ # return a vector of IdfObject's to request EnergyPlus objects needed by the run method
151
+ # rubocop:disable Naming/MethodName
152
+ def energyPlusOutputRequests(runner, user_arguments)
153
+ super(runner, user_arguments)
154
+
155
+ result = OpenStudio::IdfObjectVector.new
156
+
157
+ reporting_frequency = runner.getStringArgumentValue('reporting_frequency', user_arguments)
158
+
159
+ # Request the output for each end use/fuel type combination
160
+ end_uses.each do |end_use|
161
+ fuel_types.each do |fuel_type|
162
+ variable_name = if end_use == 'Facility'
163
+ "#{fuel_type}:#{end_use}"
164
+ else
165
+ "#{end_use}:#{fuel_type}"
166
+ end
167
+ result << OpenStudio::IdfObject.load("Output:Meter,#{variable_name},#{reporting_frequency};").get
168
+ end
169
+ end
170
+
171
+ # Request the output for each end use/fuel type combination
172
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Electricity:Facility,#{reporting_frequency};").get
173
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,ElectricityProduced:Facility,#{reporting_frequency};").get
174
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Gas:Facility,#{reporting_frequency};").get
175
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,DistrictCooling:Facility,#{reporting_frequency};").get
176
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,DistrictHeating:Facility,#{reporting_frequency};").get
177
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Cooling:Electricity,#{reporting_frequency};").get
178
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Heating:Electricity,#{reporting_frequency};").get
179
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,InteriorLights:Electricity,#{reporting_frequency};").get
180
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,ExteriorLights:Electricity,#{reporting_frequency};").get
181
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,InteriorEquipment:Electricity,#{reporting_frequency};").get
182
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Fans:Electricity,#{reporting_frequency};").get
183
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Pumps:Electricity,#{reporting_frequency};").get
184
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,WaterSystems:Electricity,#{reporting_frequency};").get
185
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Heating:Gas,#{reporting_frequency};").get
186
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,WaterSystems:Gas,#{reporting_frequency};").get
187
+ # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,InteriorEquipment:Gas,#{reporting_frequency};").get
188
+ result << OpenStudio::IdfObject.load('Output:Variable,*,Heating Coil Heating Rate,hourly; !- HVAC Average [W];').get
189
+
190
+ timeseries_data = ['District Cooling Chilled Water Rate', 'District Cooling Mass Flow Rate',
191
+ 'District Cooling Inlet Temperature', 'District Cooling Outlet Temperature',
192
+ 'District Heating Hot Water Rate', 'District Heating Mass Flow Rate',
193
+ 'District Heating Inlet Temperature', 'District Heating Outlet Temperature', 'Cooling Coil Total Cooling Rate',
194
+ 'Heating Coil Heating Rate']
195
+
196
+ timeseries_data.each do |ts|
197
+ result << OpenStudio::IdfObject.load("Output:Variable,*,#{ts},#{reporting_frequency};").get
198
+ end
199
+
200
+ # use the built-in error checking
201
+ if !runner.validateUserArguments(arguments, user_arguments)
202
+ return result
203
+ end
204
+
205
+ return result
206
+ end
207
+
208
+ # sql_query method
209
+ def sql_query(runner, sql, report_name, query)
210
+ val = nil
211
+ result = sql.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='#{report_name}' AND #{query}")
212
+ if result.empty?
213
+ runner.registerWarning("Query failed for #{report_name} and #{query}")
214
+ else
215
+ begin
216
+ val = result.get
217
+ rescue StandardError
218
+ val = nil
219
+ runner.registerWarning('Query result.get failed')
220
+ end
221
+ end
222
+
223
+ val
224
+ end
225
+
226
+ # unit conversion method
227
+ def convert_units(value, from_units, to_units)
228
+ # apply unit conversion
229
+ value_converted = OpenStudio.convert(value, from_units, to_units)
230
+ if value_converted.is_initialized
231
+ value = value_converted.get
232
+ else
233
+ @runner.registerError("Was not able to convert #{value} from #{from_units} to #{to_units}.")
234
+ value = nil
235
+ end
236
+ return value
237
+ end
238
+
239
+ # define what happens when the measure is run
240
+ # rubocop:disable Metrics/AbcSize
241
+ def run(runner, user_arguments)
242
+ super(runner, user_arguments)
243
+
244
+ # use the built-in error checking
245
+ unless runner.validateUserArguments(arguments, user_arguments)
246
+ return false
247
+ end
248
+
249
+ # use the built-in error checking
250
+ if !runner.validateUserArguments(arguments, user_arguments)
251
+ return false
252
+ end
253
+
254
+ feature_id = runner.getStringArgumentValue('feature_id', user_arguments)
255
+ feature_name = runner.getStringArgumentValue('feature_name', user_arguments)
256
+ feature_type = runner.getStringArgumentValue('feature_type', user_arguments)
257
+
258
+ # Assign the user inputs to variables
259
+ reporting_frequency = runner.getStringArgumentValue('reporting_frequency', user_arguments)
260
+
261
+ # BilingPeriod reporting frequency not implemented yet
262
+ if reporting_frequency == 'BillingPeriod'
263
+ @@logger.error('BillingPeriod frequency is not implemented yet')
264
+ end
265
+
266
+ # cache runner for this instance of the measure
267
+ @runner = runner
268
+
269
+ # get the WorkflowJSON object
270
+ workflow = runner.workflow
271
+
272
+ # get the last model and sql file
273
+ model = runner.lastOpenStudioModel
274
+ if model.empty?
275
+ runner.registerError('Cannot find last model.')
276
+ return false
277
+ end
278
+ model = model.get
279
+
280
+ sql_file = runner.lastEnergyPlusSqlFile
281
+ if sql_file.empty?
282
+ runner.registerError('Cannot find last sql file.')
283
+ return false
284
+ end
285
+ sql_file = sql_file.get
286
+ model.setSqlFile(sql_file)
287
+
288
+ # get building from model
289
+ building = model.getBuilding
290
+
291
+ # get surfaces from model
292
+ surfaces = model.getSurfaces
293
+
294
+ # get epw_file
295
+ epw_file = runner.lastEpwFile
296
+ if epw_file.empty?
297
+ runner.registerError('Cannot find last epw file.')
298
+ return false
299
+ end
300
+ epw_file = epw_file.get
301
+
302
+ # create output feature_report report object
303
+ feature_report = URBANopt::Reporting::DefaultReports::FeatureReport.new
304
+ feature_report.id = feature_id
305
+ feature_report.name = feature_name
306
+ feature_report.feature_type = feature_type
307
+ feature_report.directory_name = workflow.absoluteRunDir
308
+
309
+ timesteps_per_hour = model.getTimestep.numberOfTimestepsPerHour
310
+ feature_report.timesteps_per_hour = timesteps_per_hour
311
+
312
+ feature_report.simulation_status = 'Complete'
313
+
314
+ feature_report.reporting_periods << URBANopt::Reporting::DefaultReports::ReportingPeriod.new
315
+
316
+ ###########################################################################
317
+ ##
318
+ # Get Location information and store in the feature_report
319
+ ##
320
+
321
+ # latitude
322
+ latitude = epw_file.latitude
323
+ feature_report.location.latitude = latitude
324
+
325
+ # longitude
326
+ longitude = epw_file.longitude
327
+ feature_report.location.longitude = longitude
328
+
329
+ # surface_elevation
330
+ elev = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='General' AND RowName='Elevation' AND ColumnName='Value'")
331
+ feature_report.location.surface_elevation = elev
332
+
333
+ ##########################################################################
334
+ ##
335
+ # Get program information and store in the feature_report
336
+ ##
337
+
338
+ # floor_area
339
+ floor_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Total Building Area' AND ColumnName='Area'")
340
+ feature_report.program.floor_area = convert_units(floor_area, 'm^2', 'ft^2')
341
+
342
+ # conditioned_area
343
+ conditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Net Conditioned Building Area' AND ColumnName='Area'")
344
+ feature_report.program.conditioned_area = convert_units(conditioned_area, 'm^2', 'ft^2')
345
+
346
+ # unconditioned_area
347
+ unconditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Unconditioned Building Area' AND ColumnName='Area'")
348
+ feature_report.program.unconditioned_area = convert_units(unconditioned_area, 'm^2', 'ft^2')
349
+
350
+ # footprint_area
351
+ feature_report.program.footprint_area = convert_units(floor_area, 'm^2', 'ft^2')
352
+
353
+ # maximum_number_of_stories
354
+ number_of_stories = building.standardsNumberOfStories.get if building.standardsNumberOfStories.is_initialized
355
+ number_of_stories ||= 1
356
+ feature_report.program.maximum_number_of_stories = number_of_stories
357
+
358
+ # maximum_roof_height
359
+ floor_to_floor_height = building.nominalFloortoFloorHeight.to_f
360
+ maximum_roof_height = number_of_stories * floor_to_floor_height
361
+ feature_report.program.maximum_roof_height = maximum_roof_height
362
+
363
+ # maximum_number_of_stories_above_ground
364
+ number_of_stories_above_ground = building.standardsNumberOfAboveGroundStories.get if building.standardsNumberOfAboveGroundStories.is_initialized
365
+ number_of_stories_above_ground ||= 1
366
+ feature_report.program.maximum_number_of_stories_above_ground = number_of_stories_above_ground
367
+
368
+ # number_of_residential_units
369
+ number_of_living_units = building.standardsNumberOfLivingUnits.to_i.get if building.standardsNumberOfLivingUnits.is_initialized
370
+ number_of_living_units ||= 1
371
+ feature_report.program.number_of_residential_units = number_of_living_units
372
+
373
+ ## building_types
374
+
375
+ # get an array of the model spaces
376
+ spaces = model.getSpaces
377
+
378
+ # get array of model space types
379
+ space_types = model.getSpaceTypes
380
+
381
+ # create a hash for space_type_areas (spcace types as keys and their areas as values)
382
+ space_type_areas = {}
383
+ model.getSpaceTypes.each do |space_type|
384
+ building_type = space_type.standardsBuildingType
385
+ if building_type.empty?
386
+ building_type = 'unknown'
387
+ else
388
+ building_type = building_type.get
389
+ end
390
+ space_type_areas[building_type] = 0 if space_type_areas[building_type].nil?
391
+ space_type_areas[building_type] += convert_units(space_type.floorArea, 'm^2', 'ft^2')
392
+ end
393
+
394
+ # create a hash for space_type_occupancy (spcace types as keys and their occupancy as values)
395
+ space_type_occupancy = {}
396
+ spaces.each do |space|
397
+ if space.spaceType.empty?
398
+ raise 'space.spaceType is empty. Make sure spaces have a space type'
399
+ else
400
+ building_type = space.spaceType.get.standardsBuildingType
401
+ end
402
+ if building_type.empty?
403
+ building_type = 'unknown'
404
+ else
405
+ building_type = building_type.get
406
+ end
407
+ space_type_occupancy[building_type] = 0 if space_type_occupancy[building_type].nil?
408
+ space_type_occupancy[building_type] += space.numberOfPeople
409
+ end
410
+
411
+ # combine all in a building_types array
412
+ building_types = []
413
+ for i in 0..(space_type_areas.size - 1)
414
+ building_types << { building_type: space_type_areas.keys[i], floor_area: space_type_areas.values[i], maximum_occupancy: space_type_occupancy.values[i] }
415
+ end
416
+ # add results to the feature report JSON
417
+ feature_report.program.building_types = building_types
418
+
419
+ ## window_area
420
+ # north_window_area
421
+ north_window_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Window Opening Area' AND ColumnName='North (315 to 45 deg)'").to_f
422
+ feature_report.program.window_area[:north_window_area] = convert_units(north_window_area, 'm^2', 'ft^2')
423
+ # south_window_area
424
+ south_window_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Window Opening Area' AND ColumnName='South (135 to 225 deg)'").to_f
425
+ feature_report.program.window_area[:south_window_area] = convert_units(south_window_area, 'm^2', 'ft^2')
426
+ # east_window_area
427
+ east_window_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Window Opening Area' AND ColumnName='East (45 to 135 deg)'").to_f
428
+ feature_report.program.window_area[:east_window_area] = convert_units(east_window_area, 'm^2', 'ft^2')
429
+ # west_window_area
430
+ west_window_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Window Opening Area' AND ColumnName='West (225 to 315 deg)'").to_f
431
+ feature_report.program.window_area[:west_window_area] = convert_units(west_window_area, 'm^2', 'ft^2')
432
+ # total_window_area
433
+ total_window_area = north_window_area + south_window_area + east_window_area + west_window_area
434
+ feature_report.program.window_area[:total_window_area] = convert_units(total_window_area, 'm^2', 'ft^2')
435
+
436
+ ## wall_area
437
+ # north_wall_area
438
+ north_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='North (315 to 45 deg)'").to_f
439
+ feature_report.program.wall_area[:north_wall_area] = convert_units(north_wall_area, 'm^2', 'ft^2')
440
+ # south_wall_area
441
+ south_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='South (135 to 225 deg)'").to_f
442
+ feature_report.program.wall_area[:south_wall_area] = convert_units(south_wall_area, 'm^2', 'ft^2')
443
+ # east_wall_area
444
+ east_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='East (45 to 135 deg)'").to_f
445
+ feature_report.program.wall_area[:east_wall_area] = convert_units(east_wall_area, 'm^2', 'ft^2')
446
+ # west_wall_area
447
+ west_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='West (225 to 315 deg)'").to_f
448
+ feature_report.program.wall_area[:west_wall_area] = convert_units(west_wall_area, 'm^2', 'ft^2')
449
+ # total_wall_area
450
+ total_wall_area = north_wall_area + south_wall_area + east_wall_area + west_wall_area
451
+ feature_report.program.wall_area[:total_wall_area] = convert_units(total_wall_area, 'm^2', 'ft^2')
452
+
453
+ # total_roof_area
454
+ total_roof_area = 0.0
455
+ surfaces.each do |surface|
456
+ if (surface.outsideBoundaryCondition == 'Outdoors') && (surface.surfaceType == 'RoofCeiling')
457
+ total_roof_area += surface.netArea
458
+ end
459
+ end
460
+ feature_report.program.roof_area[:total_roof_area] = convert_units(total_roof_area, 'm^2', 'ft^2')
461
+
462
+ # orientation
463
+ # RK: a more robust method should be implemented to find orientation(finding main axis of the building using aspect ratio)
464
+ building_rotation = model.getBuilding.northAxis
465
+ feature_report.program.orientation = building_rotation
466
+
467
+ # aspect_ratio
468
+ north_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='North (315 to 45 deg)'")
469
+ east_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='East (45 to 135 deg)'")
470
+ aspect_ratio = north_wall_area / east_wall_area if north_wall_area != 0 && east_wall_area != 0
471
+ aspect_ratio ||= nil
472
+ feature_report.program.aspect_ratio = aspect_ratio
473
+
474
+ # total_construction_cost
475
+ total_construction_cost = sql_query(runner, sql_file, 'Life-Cycle Cost Report', "TableName='Present Value for Recurring, Nonrecurring and Energy Costs (Before Tax)' AND RowName='LCC_MAT - BUILDING - LIFE CYCLE COSTS' AND ColumnName='Cost'")
476
+ feature_report.program.total_construction_cost = total_construction_cost
477
+
478
+ ############################################################################
479
+ ##
480
+ # Get Reporting Periods information and store in the feature_report
481
+ ##
482
+
483
+ # start_date
484
+ # month
485
+ begin_month = model.getRunPeriod.getBeginMonth
486
+ feature_report.reporting_periods[0].start_date.month = begin_month
487
+ # day_of_month
488
+ begin_day_of_month = model.getRunPeriod.getBeginDayOfMonth
489
+ feature_report.reporting_periods[0].start_date.day_of_month = begin_day_of_month
490
+ # year
491
+ begin_year = model.getYearDescription.calendarYear
492
+ feature_report.reporting_periods[0].start_date.year = begin_year
493
+
494
+ # end_date
495
+ # month
496
+ end_month = model.getRunPeriod.getEndMonth
497
+ feature_report.reporting_periods[0].end_date.month = end_month
498
+ # day_of_month
499
+ end_day_of_month = model.getRunPeriod.getEndDayOfMonth
500
+ feature_report.reporting_periods[0].end_date.day_of_month = end_day_of_month
501
+ # year
502
+ end_year = model.getYearDescription.calendarYear
503
+ feature_report.reporting_periods[0].end_date.year = end_year
504
+
505
+ # total_site_energy
506
+ total_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Site Energy' AND ColumnName='Total Energy'")
507
+ feature_report.reporting_periods[0].total_site_energy = convert_units(total_site_energy, 'GJ', 'kBtu')
508
+
509
+ # total_source_energy
510
+ total_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Source Energy' AND ColumnName='Total Energy'")
511
+ feature_report.reporting_periods[0].total_source_energy = convert_units(total_source_energy, 'GJ', 'kBtu')
512
+
513
+ # net_site_energy
514
+ net_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Site Energy' AND ColumnName='Total Energy'")
515
+ feature_report.reporting_periods[0].net_site_energy = convert_units(net_site_energy, 'GJ', 'kBtu')
516
+
517
+ # net_source_energy
518
+ net_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Source Energy' AND ColumnName='Total Energy'")
519
+ feature_report.reporting_periods[0].net_source_energy = convert_units(net_source_energy, 'GJ', 'kBtu')
520
+
521
+ # electricity
522
+ electricity = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Electricity'")
523
+ feature_report.reporting_periods[0].electricity = convert_units(electricity, 'GJ', 'kBtu')
524
+
525
+ # natural_gas
526
+ natural_gas = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Natural Gas'")
527
+ feature_report.reporting_periods[0].natural_gas = convert_units(natural_gas, 'GJ', 'kBtu')
528
+
529
+ # additional_fuel
530
+ additional_fuel = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Additional Fuel'")
531
+ feature_report.reporting_periods[0].additional_fuel = convert_units(additional_fuel, 'GJ', 'kBtu')
532
+
533
+ # district_cooling
534
+ district_cooling = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Cooling'")
535
+ feature_report.reporting_periods[0].district_cooling = convert_units(district_cooling, 'GJ', 'kBtu')
536
+
537
+ # district_heating
538
+ district_heating = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Heating'")
539
+ feature_report.reporting_periods[0].district_heating = convert_units(district_heating, 'GJ', 'kBtu')
540
+
541
+ # water
542
+ water = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Water'")
543
+ # feature_report.reporting_periods[0].water = convert_units(water, 'm3', 'ft3')
544
+ feature_report.reporting_periods[0].water = water
545
+
546
+ # electricity_produced
547
+ electricity_produced = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Total On-Site and Utility Electric Sources' AND ColumnName='Electricity'")
548
+ feature_report.reporting_periods[0].electricity_produced = convert_units(electricity_produced, 'GJ', 'kBtu')
549
+
550
+ ## end_uses
551
+
552
+ # get fuel type as listed in the sql file
553
+ fuel_type = ['Electricity', 'Natural Gas', 'Additional Fuel', 'District Cooling', 'District Heating', 'Water']
554
+
555
+ # get enduses as listed in the sql file
556
+ enduses = ['Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps',
557
+ 'Heat Rejection', 'Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators']
558
+
559
+ # loop through fuel types and enduses to fill in sql_query method
560
+ fuel_type.each do |ft|
561
+ enduses.each do |eu|
562
+ sql_r = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='#{eu}' AND ColumnName='#{ft}'")
563
+
564
+ # report each query in its corresponding feature report obeject
565
+ if ft.include? ' '
566
+ x = ft.tr(' ', '_').downcase
567
+ m = feature_report.reporting_periods[0].end_uses.send(x)
568
+ else
569
+ m = feature_report.reporting_periods[0].end_uses.send(ft.downcase)
570
+
571
+ end
572
+
573
+ if eu.include? ' '
574
+ y = eu.tr(' ', '_').downcase
575
+ m.send("#{y}=", convert_units(sql_r, 'GJ', 'kBtu'))
576
+ else
577
+ m.send("#{eu.downcase}=", convert_units(sql_r, 'GJ', 'kBtu'))
578
+ end
579
+ end
580
+ end
581
+
582
+ ### energy_production
583
+ ## electricity_produced
584
+ # photovoltaic
585
+ photovoltaic_power = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Photovoltaic Power' AND ColumnName='Electricity'")
586
+ feature_report.reporting_periods[0].energy_production[:electricity_produced][:photovoltaic] = convert_units(photovoltaic_power, 'GJ', 'kBtu')
587
+
588
+ ## Total utility cost
589
+ total_utility_cost = sql_query(runner, sql_file, 'Economics Results Summary Report', "TableName='Annual Cost' AND RowName='Cost' AND ColumnName='Total'")
590
+ feature_report.reporting_periods[0].total_utility_cost = total_utility_cost
591
+
592
+ ## Utility Costs
593
+ # electricity utility cost
594
+ elec_utility_cost = sql_query(runner, sql_file, 'Economics Results Summary Report', "TableName='Annual Cost' AND RowName='Cost' AND ColumnName='Electric'")
595
+ feature_report.reporting_periods[0].utility_costs[0][:fuel_type] = 'Electricity'
596
+ feature_report.reporting_periods[0].utility_costs[0][:total_cost] = elec_utility_cost
597
+ # gas utility cost
598
+ gas_utility_cost = sql_query(runner, sql_file, 'Economics Results Summary Report', "TableName='Annual Cost' AND RowName='Cost' AND ColumnName='Gas'")
599
+ feature_report.reporting_periods[0].utility_costs << { fuel_type: 'Natural Gas', total_cost: gas_utility_cost }
600
+
601
+ ## comfort_result
602
+ # time_setpoint_not_met_during_occupied_cooling
603
+ time_setpoint_not_met_during_occupied_cooling = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Comfort and Setpoint Not Met Summary' AND RowName='Time Setpoint Not Met During Occupied Cooling' AND ColumnName='Facility'")
604
+ feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_cooling] = time_setpoint_not_met_during_occupied_cooling
605
+
606
+ # time_setpoint_not_met_during_occupied_heating
607
+ time_setpoint_not_met_during_occupied_heating = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Comfort and Setpoint Not Met Summary' AND RowName='Time Setpoint Not Met During Occupied Heating' AND ColumnName='Facility'")
608
+ feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_heating] = time_setpoint_not_met_during_occupied_heating
609
+
610
+ # time_setpoint_not_met_during_occupied_hour
611
+ time_setpoint_not_met_during_occupied_hours = time_setpoint_not_met_during_occupied_heating + time_setpoint_not_met_during_occupied_cooling
612
+ feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_hours] = time_setpoint_not_met_during_occupied_hours
613
+
614
+ ######################################## Reporting TImeseries Results FOR CSV File ######################################
615
+
616
+ # Get the weather file run period (as opposed to design day run period)
617
+ ann_env_pd = nil
618
+ sql_file.availableEnvPeriods.each do |env_pd|
619
+ env_type = sql_file.environmentType(env_pd)
620
+ if env_type.is_initialized
621
+ if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
622
+ ann_env_pd = env_pd
623
+ end
624
+ end
625
+ end
626
+
627
+ if ann_env_pd == false
628
+ runner.registerError("Can't find a weather runperiod, make sure you ran an annual simulation, not just the design days.")
629
+ return false
630
+ end
631
+
632
+ # timeseries we want to report
633
+ requested_timeseries_names = [
634
+ 'Electricity:Facility',
635
+ 'ElectricityProduced:Facility',
636
+ 'Gas:Facility',
637
+ 'Cooling:Electricity',
638
+ 'Heating:Electricity',
639
+ 'InteriorLights:Electricity',
640
+ 'ExteriorLights:Electricity',
641
+ 'InteriorEquipment:Electricity',
642
+ 'Fans:Electricity',
643
+ 'Pumps:Electricity',
644
+ 'WaterSystems:Electricity',
645
+ 'HeatRejection:Electricity',
646
+ 'HeatRejection:Gas',
647
+ 'Heating:Gas',
648
+ 'WaterSystems:Gas',
649
+ 'InteriorEquipment:Gas',
650
+ 'DistrictCooling:Facility',
651
+ 'DistrictHeating:Facility',
652
+ 'District Cooling Chilled Water Rate',
653
+ 'District Cooling Mass Flow Rate',
654
+ 'District Cooling Inlet Temperature',
655
+ 'District Cooling Outlet Temperature',
656
+ 'District Heating Hot Water Rate',
657
+ 'District Heating Mass Flow Rate',
658
+ 'District Heating Inlet Temperature',
659
+ 'District Heating Outlet Temperature',
660
+ 'Cooling Coil Total Cooling Rate',
661
+ 'Heating Coil Heating Rate'
662
+ ]
663
+
664
+ # add thermal comfort timeseries
665
+ comfortTimeseries = ['Zone Thermal Comfort Fanger Model PMV', 'Zone Thermal Comfort Fanger Model PPD']
666
+ requested_timeseries_names += comfortTimeseries
667
+
668
+ # add additional power timeseries (for calculating transformer apparent power to compare to rating ) in VA
669
+ powerTimeseries = ['Net Electric Energy', 'Electricity:Facility Power', 'ElectricityProduced:Facility Power', 'Electricity:Facility Apparent Power', 'ElectricityProduced:Facility Apparent Power', 'Net Power', 'Net Apparent Power']
670
+ requested_timeseries_names += powerTimeseries
671
+
672
+ # register info all timeseries
673
+ runner.registerInfo("All timeseries: #{requested_timeseries_names}")
674
+
675
+ # timeseries variables to keep to calculate power
676
+ tsToKeep = ['Electricity:Facility', 'ElectricityProduced:Facility']
677
+ tsToKeepIndexes = {}
678
+
679
+ ### powerFactor ###
680
+ # use power_factor default: 0.9
681
+ # TODO: Set powerFactor default based on building type
682
+ powerFactor = 0.9
683
+
684
+ ### power_conversion ###
685
+ # divide values by total_seconds to convert J to W (W = J/sec)
686
+ # divide values by total_hours to convert kWh to kW (kW = kWh/hrs)
687
+ total_seconds = (60 / timesteps_per_hour.to_f) * 60 # make sure timesteps_per_hour is a float in the division
688
+ total_hours = 1 / timesteps_per_hour.to_f # make sure timesteps_per_hour is a float in the division
689
+ # set power_conversion
690
+ power_conversion = total_hours # we set the power conversio to total_hours since we want to convert lWh to kW
691
+ puts "Power Converion: to convert kWh to kW values will be divided by #{power_conversion}"
692
+
693
+ # number of values in each timeseries
694
+ n = nil
695
+ # all numeric timeseries values, transpose of CSV file (e.g. values[key_cnt] is column, values[key_cnt][i] is column and row)
696
+ values = []
697
+ tmpArray = []
698
+ # since schedule value will have a bunch of key_values, we need to keep track of these as additional timeseries
699
+ key_cnt = 0
700
+ # this is recording the name of these final timeseries to write in the header of the CSV
701
+ final_timeseries_names = []
702
+
703
+ # loop over requested timeseries
704
+ requested_timeseries_names.each_index do |i|
705
+ timeseries_name = requested_timeseries_names[i]
706
+ puts " *********timeseries_name = #{timeseries_name}******************"
707
+ runner.registerInfo("TIMESERIES: #{timeseries_name}")
708
+
709
+ # get all the key values that this timeseries can be reported for (e.g. if PMV is requested for each zone)
710
+ key_values = sql_file.availableKeyValues('RUN PERIOD 1', 'Zone Timestep', timeseries_name)
711
+ runner.registerInfo("KEY VALUES: #{key_values}")
712
+ if key_values.empty?
713
+ key_values = ['']
714
+ end
715
+
716
+ # sort keys
717
+ sorted_keys = key_values.sort
718
+ requested_keys = requested_timeseries_names
719
+ final_keys = []
720
+ # make sure aggregated timeseries are listed in sorted order before all individual feature timeseries
721
+ sorted_keys.each do |k|
722
+ if requested_keys.include? k
723
+ final_keys << k
724
+ end
725
+ end
726
+ sorted_keys.each do |k|
727
+ if !requested_keys.include? k
728
+ final_keys << k
729
+ end
730
+ end
731
+
732
+ # loop over final keys
733
+ final_keys.each_with_index do |key_value, key_i|
734
+ new_timeseries_name = ''
735
+
736
+ runner.registerInfo("!! TIMESERIES NAME: #{timeseries_name} AND key_value: #{key_value}")
737
+
738
+ # check if we have to come up with a new name for the timeseries in our CSV header
739
+ if key_values.size == 1
740
+ # use timeseries name when only 1 keyvalue
741
+ new_timeseries_name = timeseries_name
742
+ else
743
+ # use key_value name
744
+ # special case for Zone Thermal Comfort: use both timeseries_name and key_value
745
+ if timeseries_name.include? 'Zone Thermal Comfort'
746
+ new_timeseries_name = timeseries_name + ' ' + key_value
747
+ else
748
+ new_timeseries_name = key_value
749
+ end
750
+ end
751
+ # final_timeseries_names << new_timeseries_name
752
+
753
+ # get the actual timeseries
754
+ ts = sql_file.timeSeries(ann_env_pd.to_s, reporting_frequency.to_s, timeseries_name, key_value)
755
+
756
+ if n.nil?
757
+ # first timeseries should always be set
758
+ runner.registerInfo('First timeseries')
759
+ values[key_cnt] = ts.get.values
760
+ n = values[key_cnt].size
761
+ elsif ts.is_initialized
762
+ runner.registerInfo('Is Initialized')
763
+ values[key_cnt] = ts.get.values
764
+ else
765
+ runner.registerInfo('Is NOT Initialized')
766
+ values[key_cnt] = Array.new(n, 0)
767
+ end
768
+
769
+ # unit conversion
770
+ old_unit = ts.get.units if ts.is_initialized
771
+
772
+ if timeseries_name.include? 'Gas'
773
+ new_unit = 'kBtu'
774
+ else
775
+ new_unit = case old_unit.to_s
776
+ when 'J'
777
+ 'kWh'
778
+ when 'kBtu'
779
+ 'kWh'
780
+ when 'gal'
781
+ 'm3'
782
+ when 'W'
783
+ 'W'
784
+ end
785
+ end
786
+
787
+ # loop through each value and apply unit conversion
788
+ os_vec = values[key_cnt]
789
+ if !timeseries_name.include? 'Zone Thermal Comfort'
790
+ for i in 0..os_vec.length - 1
791
+
792
+ unless new_unit == old_unit || old_unit.nil? || new_unit.nil? || !ts.is_initialized
793
+ os_vec[i] = OpenStudio.convert(os_vec[i], old_unit, new_unit).get
794
+ end
795
+
796
+ end
797
+ end
798
+
799
+ # keep certain timeseries to calculate power
800
+ if tsToKeep.include? timeseries_name
801
+ tsToKeepIndexes[timeseries_name] = key_cnt
802
+ end
803
+
804
+ # special processing: power
805
+ if powerTimeseries.include? timeseries_name
806
+ # special case: net series (subtract generation from load)
807
+ if timeseries_name.include? 'Net'
808
+
809
+ newVals = Array.new(n, 0)
810
+ # Apparent power calculation
811
+
812
+ if timeseries_name.include?('Apparent')
813
+ (0..n - 1).each do |j|
814
+ newVals[j] = (values[tsToKeepIndexes['Electricity:Facility']][j].to_f - values[tsToKeepIndexes['ElectricityProduced:Facility']][j].to_f) / power_conversion / powerFactor
815
+ j += 1
816
+ end
817
+ new_unit = 'kVA'
818
+ elsif timeseries_name.include? 'Net Electric Energy'
819
+ (0..n - 1).each do |j|
820
+ newVals[j] = (values[tsToKeepIndexes['Electricity:Facility']][j].to_f - values[tsToKeepIndexes['ElectricityProduced:Facility']][j].to_f)
821
+ j += 1
822
+ end
823
+ new_unit = 'kWh'
824
+ else
825
+ runner.registerInfo('Power calc')
826
+ # Power calculation
827
+ (0..n - 1).each do |j|
828
+ newVals[j] = (values[tsToKeepIndexes['Electricity:Facility']][j].to_f - values[tsToKeepIndexes['ElectricityProduced:Facility']][j].to_f) / power_conversion
829
+ j += 1
830
+ end
831
+ new_unit = 'kW'
832
+ end
833
+
834
+ values[key_cnt] = newVals
835
+ else
836
+ tsToKeepIndexes.each do |key, indexValue|
837
+ if timeseries_name.include? key
838
+ runner.registerInfo("timeseries_name: #{timeseries_name}, key: #{key}")
839
+ # use this timeseries
840
+ newVals = Array.new(n, 0)
841
+ # Apparent power calculation
842
+ if timeseries_name.include?('Apparent')
843
+ (0..n - 1).each do |j|
844
+ newVals[j] = values[indexValue][j].to_f / power_conversion / powerFactor
845
+ j += 1
846
+ end
847
+ new_unit = 'kVA'
848
+ else
849
+ # Power calculation
850
+ (0..n - 1).each do |j|
851
+ newVals[j] = values[indexValue][j].to_f / power_conversion
852
+ j += 1
853
+ end
854
+ new_unit = 'kW'
855
+ end
856
+ values[key_cnt] = newVals
857
+ end
858
+ end
859
+ end
860
+ end
861
+
862
+ # append units to headers
863
+ new_timeseries_name += "(#{new_unit})"
864
+ final_timeseries_names << new_timeseries_name
865
+
866
+ # TODO: DELETE PUTS
867
+ # puts " *********timeseries_name = #{timeseries_name}******************"
868
+ # if timeseries_name.include? 'Power'
869
+ # puts "values = #{values[key_cnt]}"
870
+ # puts "units = #{new_unit}"
871
+ # end
872
+
873
+ # comfort results usually have multiple timeseries (per zone), aggregate into a single series with consistent name and use worst value at each timestep
874
+ if comfortTimeseries.include? timeseries_name
875
+
876
+ # set up array if 1st key_value
877
+ if key_i == 0
878
+ runner.registerInfo("SETTING UP NEW ARRAY FOR: #{timeseries_name}")
879
+ tmpArray = Array.new(n, 0)
880
+ end
881
+
882
+ # add to array (keep max value at each timestep)
883
+ (0..(n - 1)).each do |ind|
884
+ # process negative and positive values differently
885
+ tVal = values[key_cnt][ind].to_f
886
+ if tVal < 0
887
+ tmpArray[ind] = [tVal, tmpArray[ind]].min
888
+ else
889
+ tmpArray[ind] = [tVal, tmpArray[ind]].max
890
+ end
891
+ end
892
+
893
+ # aggregate and save when all keyvalues have been processed
894
+ if key_i == final_keys.size - 1
895
+
896
+ hrsOutOfBounds = 0
897
+ if timeseries_name === 'Zone Thermal Comfort Fanger Model PMV'
898
+ (0..(n - 1)).each do |ind|
899
+ # -0.5 < x < 0.5 is within bounds
900
+ if values[key_cnt][ind].to_f > 0.5 || values[key_cnt][ind].to_f < -0.5
901
+ hrsOutOfBounds += 1
902
+ end
903
+ end
904
+ hrsOutOfBounds = hrsOutOfBounds.to_f / timesteps_per_hour
905
+ elsif timeseries_name === 'Zone Thermal Comfort Fanger Model PPD'
906
+ (0..(n - 1)).each do |ind|
907
+ # > 20 is outside bounds
908
+ if values[key_cnt][ind].to_f > 20
909
+ hrsOutOfBounds += 1
910
+ end
911
+ end
912
+ hrsOutOfBounds = hrsOutOfBounds.to_f / timesteps_per_hour
913
+ else
914
+ # this one is already scaled by timestep, no need to divide total
915
+ (0..(n - 1)).each do |ind|
916
+ hrsOutOfBounds += values[key_cnt][ind].to_f if values[key_cnt][ind].to_f > 0
917
+ end
918
+ end
919
+
920
+ # save variable to feature_reports hash
921
+ runner.registerInfo("timeseries #{timeseries_name}: hours out of bounds: #{hrsOutOfBounds}")
922
+ if timeseries_name === 'Zone Thermal Comfort Fanger Model PMV'
923
+ feature_report.reporting_periods[0].comfort_result[:hours_out_of_comfort_bounds_PMV] = hrsOutOfBounds
924
+ elsif timeseries_name == 'Zone Thermal Comfort Fanger Model PPD'
925
+ feature_report.reporting_periods[0].comfort_result[:hours_out_of_comfort_bounds_PPD] = hrsOutOfBounds
926
+ end
927
+
928
+ end
929
+
930
+ end
931
+
932
+ # increment key_cnt in new_keys loop
933
+ key_cnt += 1
934
+ end
935
+ end
936
+
937
+ # Add datime column
938
+ datetimes = []
939
+ # check what timeseries is available
940
+ available_ts = sql_file.availableTimeSeries
941
+ puts "####### available_ts = #{available_ts}"
942
+ # get the timeseries for any of available timeseries
943
+ # RK: code enhancement needed
944
+ ts_d_e = sql_file.timeSeries(ann_env_pd.to_s, reporting_frequency.to_s, 'Electricity:Facility', '')
945
+ ts_d_g = sql_file.timeSeries(ann_env_pd.to_s, reporting_frequency.to_s, 'Gas:Facility', '')
946
+
947
+ if ts_d_e.is_initialized
948
+ timeseries_d = ts_d_e.get
949
+ elsif ts_d_g.is_initialized
950
+ timeseries_d = ts_d_g.get
951
+ else
952
+ raise 'ELECTRICITY and GAS results are not initiaized'
953
+ end
954
+ # get formated datetimes
955
+ timeseries_d.dateTimes.each do |datetime|
956
+ datetimes << format_datetime(datetime.to_s)
957
+ end
958
+ # insert datetimes to values
959
+ values.insert(0, datetimes)
960
+ # insert datetime header to names
961
+ final_timeseries_names.insert(0, 'Datetime')
962
+
963
+ runner.registerInfo("new final_timeseries_names size: #{final_timeseries_names.size}")
964
+
965
+ # Save the 'default_feature_reports.csv' file
966
+ File.open('default_feature_reports.csv', 'w') do |file|
967
+ file.puts(final_timeseries_names.join(','))
968
+ (0...n).each do |l|
969
+ line = []
970
+ values.each_index do |j|
971
+ line << values[j][l]
972
+ end
973
+ file.puts(line.join(','))
974
+ end
975
+ end
976
+
977
+ # closing the sql file
978
+ sql_file.close
979
+
980
+ ############################# Adding timeseries_csv info to json report and saving CSV ################################
981
+ # add csv info to feature_report
982
+ feature_report.timeseries_csv.path = File.join(Dir.pwd, 'default_feature_reports.csv')
983
+ feature_report.timeseries_csv.first_report_datetime = '0'
984
+ feature_report.timeseries_csv.column_names = final_timeseries_names
985
+
986
+ ##### Save the 'default_feature_reports.json' file
987
+
988
+ feature_report_hash = feature_report.to_hash
989
+
990
+ File.open('default_feature_reports.json', 'w') do |f|
991
+ f.puts JSON.pretty_generate(feature_report_hash)
992
+ # make sure data is written to the disk one way or the other
993
+ begin
994
+ f.fsync
995
+ rescue StandardError
996
+ f.flush
997
+ end
998
+ end
999
+
1000
+ # reporting final condition
1001
+ runner.registerFinalCondition('Default Feature Reports generated successfully.')
1002
+
1003
+ true
1004
+ # end the run method
1005
+ end
1006
+ # end the measure
1007
+ end
1008
+ # rubocop:enable Metrics/AbcSize
1009
+ # rubocop:enable Naming/MethodName
1010
+
1011
+ # register the measure to be used by the application
1012
+ DefaultFeatureReports.new.registerWithApplication