urbanopt-reporting 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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