urbanopt-scenario 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +25 -0
  3. data/.rdoc_options +36 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/.travis.yml +23 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +42 -0
  9. data/Jenkinsfile +10 -0
  10. data/LICENSE.md +27 -0
  11. data/RDOC_MAIN.md +39 -0
  12. data/README.md +39 -0
  13. data/Rakefile +51 -0
  14. data/deploy_docs.sh +5 -0
  15. data/doc_templates/LICENSE.md +27 -0
  16. data/doc_templates/README.md.erb +42 -0
  17. data/doc_templates/copyright_erb.txt +31 -0
  18. data/doc_templates/copyright_js.txt +4 -0
  19. data/doc_templates/copyright_ruby.txt +29 -0
  20. data/docs/.gitignore +3 -0
  21. data/docs/.vuepress/components/InnerJsonSchema.vue +84 -0
  22. data/docs/.vuepress/components/JsonSchema.vue +12 -0
  23. data/docs/.vuepress/components/ScenarioSchema.vue +12 -0
  24. data/docs/.vuepress/components/StaticLink.vue +8 -0
  25. data/docs/.vuepress/config.js +15 -0
  26. data/docs/.vuepress/highlight.js +8 -0
  27. data/docs/.vuepress/public/custom_rdoc_styles.css +74 -0
  28. data/docs/.vuepress/utils.js +17 -0
  29. data/docs/README.md +39 -0
  30. data/docs/package-lock.json +11791 -0
  31. data/docs/package.json +22 -0
  32. data/docs/schemas/scenario-schema.md +3 -0
  33. data/lib/measures/.rubocop.yml +5 -0
  34. data/lib/measures/default_feature_reports/LICENSE.md +27 -0
  35. data/lib/measures/default_feature_reports/README.md +56 -0
  36. data/lib/measures/default_feature_reports/README.md.erb +42 -0
  37. data/lib/measures/default_feature_reports/measure.rb +731 -0
  38. data/lib/measures/default_feature_reports/measure.xml +139 -0
  39. data/lib/measures/default_feature_reports/tests/USA_CO_Golden-NREL.724666_TMY3.epw +8768 -0
  40. data/lib/measures/default_feature_reports/tests/default_feature_reports_test.rb +238 -0
  41. data/lib/measures/default_feature_reports/tests/example_model.osm +4378 -0
  42. data/lib/urbanopt-scenario.rb +31 -0
  43. data/lib/urbanopt/scenario.rb +45 -0
  44. data/lib/urbanopt/scenario/default_reports.rb +40 -0
  45. data/lib/urbanopt/scenario/default_reports/construction_cost.rb +169 -0
  46. data/lib/urbanopt/scenario/default_reports/date.rb +97 -0
  47. data/lib/urbanopt/scenario/default_reports/end_use.rb +159 -0
  48. data/lib/urbanopt/scenario/default_reports/end_uses.rb +140 -0
  49. data/lib/urbanopt/scenario/default_reports/feature_report.rb +207 -0
  50. data/lib/urbanopt/scenario/default_reports/location.rb +99 -0
  51. data/lib/urbanopt/scenario/default_reports/logger.rb +44 -0
  52. data/lib/urbanopt/scenario/default_reports/program.rb +261 -0
  53. data/lib/urbanopt/scenario/default_reports/reporting_period.rb +298 -0
  54. data/lib/urbanopt/scenario/default_reports/scenario_report.rb +276 -0
  55. data/lib/urbanopt/scenario/default_reports/schema/README.md +33 -0
  56. data/lib/urbanopt/scenario/default_reports/schema/scenario_csv_columns.txt +13 -0
  57. data/lib/urbanopt/scenario/default_reports/schema/scenario_schema.json +742 -0
  58. data/lib/urbanopt/scenario/default_reports/timeseries_csv.rb +231 -0
  59. data/lib/urbanopt/scenario/default_reports/validator.rb +97 -0
  60. data/lib/urbanopt/scenario/extension.rb +63 -0
  61. data/lib/urbanopt/scenario/logger.rb +42 -0
  62. data/lib/urbanopt/scenario/scenario_base.rb +79 -0
  63. data/lib/urbanopt/scenario/scenario_csv.rb +122 -0
  64. data/lib/urbanopt/scenario/scenario_datapoint_base.rb +162 -0
  65. data/lib/urbanopt/scenario/scenario_post_processor_base.rb +69 -0
  66. data/lib/urbanopt/scenario/scenario_post_processor_default.rb +97 -0
  67. data/lib/urbanopt/scenario/scenario_runner_base.rb +63 -0
  68. data/lib/urbanopt/scenario/scenario_runner_osw.rb +158 -0
  69. data/lib/urbanopt/scenario/simulation_dir_base.rb +90 -0
  70. data/lib/urbanopt/scenario/simulation_dir_osw.rb +261 -0
  71. data/lib/urbanopt/scenario/simulation_mapper_base.rb +47 -0
  72. data/lib/urbanopt/scenario/version.rb +35 -0
  73. data/urbanopt-scenario-gem.gemspec +36 -0
  74. metadata +227 -0
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "urbanopt-scenario-gem-docs",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "dev": "vuepress dev",
8
+ "build": "vuepress build",
9
+ "deploy": "gh-pages -d .vuepress/dist"
10
+ },
11
+ "author": "NREL",
12
+ "dependencies": {
13
+ "highlight.js": "^9.15.6",
14
+ "json-schema-ref-parser": "^6.1.0",
15
+ "json-schema-view-js": "git+https://git@github.com/bgschiller/json-schema-view-js.git",
16
+ "vuepress": "^0.14.10",
17
+ "webpack-dev-middleware": "^3.6.0"
18
+ },
19
+ "devDependencies": {
20
+ "gh-pages": "^2.0.1"
21
+ }
22
+ }
@@ -0,0 +1,3 @@
1
+ # Scenario Schema
2
+
3
+ <ScenarioSchema />
@@ -0,0 +1,5 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'spec/test_measures/**/*'
4
+ inherit_from:
5
+ - http://s3.amazonaws.com/openstudio-resources/styles/rubocop.yml
@@ -0,0 +1,27 @@
1
+ URBANopt, Copyright (c) 2019, Alliance for Sustainable Energy, LLC, and other
2
+ contributors. All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ Redistributions of source code must retain the above copyright notice, this list
8
+ of conditions and the following disclaimer.
9
+
10
+ Redistributions in binary form must reproduce the above copyright notice, this
11
+ list of conditions and the following disclaimer in the documentation and/or other
12
+ materials provided with the distribution.
13
+
14
+ Neither the name of the copyright holder nor the names of its contributors may be
15
+ used to endorse or promote products derived from this software without specific
16
+ prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27
+ OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,56 @@
1
+
2
+
3
+ ###### (Automatically generated documentation)
4
+
5
+ # DefaultFeatureReports
6
+
7
+ ## Description
8
+ Writes default_feature_reports.json file used by URBANopt Scenario Default Post Processor
9
+
10
+ ## Modeler Description
11
+ 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.
12
+
13
+ ## Measure Type
14
+ ReportingMeasure
15
+
16
+ ## Taxonomy
17
+
18
+
19
+ ## Arguments
20
+
21
+
22
+ ### Feature unique identifier
23
+
24
+ **Name:** feature_id,
25
+ **Type:** String,
26
+ **Units:** ,
27
+ **Required:** false,
28
+ **Model Dependent:** false
29
+
30
+ ### Feature scenario specific name
31
+
32
+ **Name:** feature_name,
33
+ **Type:** String,
34
+ **Units:** ,
35
+ **Required:** false,
36
+ **Model Dependent:** false
37
+
38
+ ### URBANopt Feature Type
39
+
40
+ **Name:** feature_type,
41
+ **Type:** String,
42
+ **Units:** ,
43
+ **Required:** false,
44
+ **Model Dependent:** false
45
+
46
+ ### Reporting Frequency
47
+ The frequency at which to report timeseries output data.
48
+ **Name:** reporting_frequency,
49
+ **Type:** Choice,
50
+ **Units:** ,
51
+ **Required:** true,
52
+ **Model Dependent:** false
53
+
54
+
55
+
56
+
@@ -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,731 @@
1
+ # *********************************************************************************
2
+ # URBANopt, Copyright (c) 2019, 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/scenario/default_reports/feature_report'
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 file 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 << "BillingPeriod" # match it to utility bill object
81
+ ## Utility report here to report the start and end for each fueltype
82
+ reporting_frequency_chs << 'Monthly'
83
+ reporting_frequency_chs << 'Runperiod'
84
+
85
+ reporting_frequency = OpenStudio::Measure::OSArgument.makeChoiceArgument('reporting_frequency', reporting_frequency_chs, true)
86
+ reporting_frequency.setDisplayName('Reporting Frequency')
87
+ reporting_frequency.setDescription('The frequency at which to report timeseries output data.')
88
+ reporting_frequency.setDefaultValue('Hourly')
89
+ args << reporting_frequency
90
+
91
+ # move this in the run method
92
+ if reporting_frequency.defaultValueDisplayName == 'BillingPeriod'
93
+ @@logger.error('BillingPeriod frequency is not implemented yet')
94
+ end
95
+
96
+ return args
97
+ end
98
+
99
+ def fuel_types
100
+ fuel_types = [
101
+ 'Electricity',
102
+ 'Gas',
103
+ 'AdditionalFuel',
104
+ 'DistrictCooling',
105
+ 'DistrictHeating',
106
+ 'Water'
107
+ ]
108
+
109
+ return fuel_types
110
+ end
111
+
112
+ def end_uses
113
+ end_uses = [
114
+ 'Heating',
115
+ 'Cooling',
116
+ 'InteriorLights',
117
+ 'ExteriorLights',
118
+ 'InteriorEquipment',
119
+ 'ExteriorEquipment',
120
+ 'Fans',
121
+ 'Pumps',
122
+ 'HeatRejection',
123
+ 'Humidifier',
124
+ 'HeatRecovery',
125
+ 'WaterSystems',
126
+ 'Refrigeration',
127
+ 'Generators',
128
+ 'Facility'
129
+ ]
130
+
131
+ return end_uses
132
+ end
133
+
134
+ # return a vector of IdfObject's to request EnergyPlus objects needed by the run method
135
+ # rubocop:disable Naming/MethodName
136
+ def energyPlusOutputRequests(runner, user_arguments)
137
+ super(runner, user_arguments)
138
+
139
+ result = OpenStudio::IdfObjectVector.new
140
+
141
+ reporting_frequency = runner.getStringArgumentValue('reporting_frequency', user_arguments)
142
+
143
+ # Request the output for each end use/fuel type combination
144
+ end_uses.each do |end_use|
145
+ fuel_types.each do |fuel_type|
146
+ variable_name = if end_use == 'Facility'
147
+ "#{fuel_type}:#{end_use}"
148
+ else
149
+ "#{end_use}:#{fuel_type}"
150
+ end
151
+ result << OpenStudio::IdfObject.load("Output:Meter,#{variable_name},#{reporting_frequency};").get
152
+ end
153
+ end
154
+
155
+ ### Request the output for each end use/fuel type combination
156
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Electricity:Facility,#{reporting_frequency};").get
157
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,ElectricityProduced:Facility,#{reporting_frequency};").get
158
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Gas:Facility,#{reporting_frequency};").get
159
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,DistrictCooling:Facility,#{reporting_frequency};").get
160
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,DistrictHeating:Facility,#{reporting_frequency};").get
161
+
162
+ timeseries_data = ['District Cooling Chilled Water Rate', 'District Cooling Mass Flow Rate',
163
+ 'District Cooling Inlet Temperature', 'District Cooling Outlet Temperature',
164
+ 'District Heating Hot Water Rate', 'District Heating Mass Flow Rate',
165
+ 'District Heating Inlet Temperature', 'District Heating Outlet Temperature']
166
+
167
+ timeseries_data.each do |ts|
168
+ result << OpenStudio::IdfObject.load("Output:Variable,*,#{ts},#{reporting_frequency};").get
169
+ end
170
+
171
+ # use the built-in error checking
172
+ if !runner.validateUserArguments(arguments, user_arguments)
173
+ return result
174
+ end
175
+
176
+ return result
177
+ end
178
+
179
+ # sql_query method
180
+ def sql_query(runner, sql, report_name, query)
181
+ val = nil
182
+ result = sql.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='#{report_name}' AND #{query}")
183
+ if result.empty?
184
+ runner.registerWarning("Query failed for #{report_name} and #{query}")
185
+ else
186
+ begin
187
+ val = result.get
188
+ rescue StandardError
189
+ val = nil
190
+ runner.registerWarning('Query result.get failed')
191
+ end
192
+ end
193
+
194
+ val
195
+ end
196
+
197
+ # unit conversion method
198
+ def convert_units(value, from_units, to_units)
199
+ # apply unit conversion
200
+ value_converted = OpenStudio.convert(value, from_units, to_units)
201
+ if value_converted.is_initialized
202
+ value = value_converted.get
203
+ else
204
+ @runner.registerError("Was not able to convert #{value} from #{from_units} to #{to_units}.")
205
+ value = nil
206
+ end
207
+ return value
208
+ end
209
+
210
+ # define what happens when the measure is run
211
+ # rubocop:disable Metrics/AbcSize
212
+ def run(runner, user_arguments)
213
+ super(runner, user_arguments)
214
+
215
+ # use the built-in error checking
216
+ unless runner.validateUserArguments(arguments, user_arguments)
217
+ return false
218
+ end
219
+
220
+ # use the built-in error checking
221
+ if !runner.validateUserArguments(arguments, user_arguments)
222
+ return false
223
+ end
224
+
225
+ feature_id = runner.getStringArgumentValue('feature_id', user_arguments)
226
+ feature_name = runner.getStringArgumentValue('feature_name', user_arguments)
227
+ feature_type = runner.getStringArgumentValue('feature_type', user_arguments)
228
+
229
+ # Assign the user inputs to variables
230
+ reporting_frequency = runner.getStringArgumentValue('reporting_frequency', user_arguments)
231
+
232
+ # cache runner for this instance of the measure
233
+ @runner = runner
234
+
235
+ # get the WorkflowJSON object
236
+ workflow = runner.workflow
237
+
238
+ # get the last model and sql file
239
+ model = runner.lastOpenStudioModel
240
+ if model.empty?
241
+ runner.registerError('Cannot find last model.')
242
+ return false
243
+ end
244
+ model = model.get
245
+
246
+ sql_file = runner.lastEnergyPlusSqlFile
247
+ if sql_file.empty?
248
+ runner.registerError('Cannot find last sql file.')
249
+ return false
250
+ end
251
+ sql_file = sql_file.get
252
+ model.setSqlFile(sql_file)
253
+
254
+ # get building from model
255
+ building = model.getBuilding
256
+
257
+ # get surfaces from model
258
+ surfaces = model.getSurfaces
259
+
260
+ # get epw_file
261
+ epw_file = runner.lastEpwFile
262
+ if epw_file.empty?
263
+ runner.registerError('Cannot find last epw file.')
264
+ return false
265
+ end
266
+ epw_file = epw_file.get
267
+
268
+ # create output feature_report report object
269
+ feature_report = URBANopt::Scenario::DefaultReports::FeatureReport.new
270
+ feature_report.id = feature_id
271
+ feature_report.name = feature_name
272
+ feature_report.feature_type = feature_type
273
+ feature_report.directory_name = workflow.absoluteRunDir
274
+ feature_report.timesteps_per_hour = model.getTimestep.numberOfTimestepsPerHour
275
+ feature_report.simulation_status = 'Complete'
276
+
277
+ feature_report.reporting_periods << URBANopt::Scenario::DefaultReports::ReportingPeriod.new
278
+
279
+ ###########################################################################
280
+ ##
281
+ # Get Location information and store in the feature_report
282
+ ##
283
+
284
+ # latitude
285
+ latitude = epw_file.latitude
286
+ feature_report.location.latitude = latitude
287
+
288
+ # longitude
289
+ longitude = epw_file.longitude
290
+ feature_report.location.longitude = longitude
291
+
292
+ # surface_elevation
293
+ elev = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='General' AND RowName='Elevation' AND ColumnName='Value'")
294
+ feature_report.location.surface_elevation = elev
295
+
296
+ ##########################################################################
297
+ ##
298
+ # Get program information and store in the feature_report
299
+ ##
300
+
301
+ # floor_area
302
+ floor_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Total Building Area' AND ColumnName='Area'")
303
+ feature_report.program.floor_area = convert_units(floor_area, 'm^2', 'ft^2')
304
+
305
+ # conditioned_area
306
+ conditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Net Conditioned Building Area' AND ColumnName='Area'")
307
+ feature_report.program.conditioned_area = convert_units(conditioned_area, 'm^2', 'ft^2')
308
+
309
+ # unconditioned_area
310
+ unconditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Unconditioned Building Area' AND ColumnName='Area'")
311
+ feature_report.program.unconditioned_area = convert_units(unconditioned_area, 'm^2', 'ft^2')
312
+
313
+ # footprint_area
314
+ feature_report.program.footprint_area = convert_units(floor_area, 'm^2', 'ft^2')
315
+
316
+ # maximum_number_of_stories
317
+ number_of_stories = building.standardsNumberOfStories.get if building.standardsNumberOfStories.is_initialized
318
+ number_of_stories ||= 1
319
+ feature_report.program.maximum_number_of_stories = number_of_stories
320
+
321
+ # maximum_roof_height
322
+ floor_to_floor_height = building.nominalFloortoFloorHeight.to_f
323
+ maximum_roof_height = number_of_stories * floor_to_floor_height
324
+ feature_report.program.maximum_roof_height = maximum_roof_height
325
+
326
+ # maximum_number_of_stories_above_ground
327
+ number_of_stories_above_ground = building.standardsNumberOfAboveGroundStories.get if building.standardsNumberOfAboveGroundStories.is_initialized
328
+ number_of_stories_above_ground ||= 1
329
+ feature_report.program.maximum_number_of_stories_above_ground = number_of_stories_above_ground
330
+
331
+ # number_of_residential_units
332
+ number_of_living_units = building.standardsNumberOfLivingUnits.to_i.get if building.standardsNumberOfLivingUnits.is_initialized
333
+ number_of_living_units ||= 1
334
+ feature_report.program.number_of_residential_units = number_of_living_units
335
+
336
+ ## building_types
337
+
338
+ # get an array of the model spaces
339
+ spaces = model.getSpaces
340
+
341
+ # get array of model space types
342
+ space_types = model.getSpaceTypes
343
+
344
+ # create a hash for space_type_areas (spcace types as keys and their areas as values)
345
+ space_type_areas = {}
346
+ model.getSpaceTypes.each do |space_type|
347
+ building_type = space_type.standardsBuildingType
348
+ if building_type.empty?
349
+ building_type = 'unknown'
350
+ else
351
+ building_type = building_type.get
352
+ end
353
+ space_type_areas[building_type] = 0 if space_type_areas[building_type].nil?
354
+ space_type_areas[building_type] += convert_units(space_type.floorArea, 'm^2', 'ft^2')
355
+ end
356
+
357
+ # create a hash for space_type_occupancy (spcace types as keys and their occupancy as values)
358
+ space_type_occupancy = {}
359
+ spaces.each do |space|
360
+ if space.spaceType.empty?
361
+ raise 'space.spaceType is empty. Make sure spaces have a space type'
362
+ else
363
+ building_type = space.spaceType.get.standardsBuildingType
364
+ end
365
+ if building_type.empty?
366
+ building_type = 'unknown'
367
+ else
368
+ building_type = building_type.get
369
+ end
370
+ space_type_occupancy[building_type] = 0 if space_type_occupancy[building_type].nil?
371
+ space_type_occupancy[building_type] += space.numberOfPeople
372
+ end
373
+
374
+ # combine all in a building_types array
375
+ building_types = []
376
+ for i in 0..(space_type_areas.size - 1)
377
+ building_types << { building_type: space_type_areas.keys[i], floor_area: space_type_areas.values[i], maximum_occupancy: space_type_occupancy.values[i] }
378
+ end
379
+ # add results to the feature report JSON
380
+ feature_report.program.building_types = building_types
381
+
382
+ ## window_area
383
+ # north_window_area
384
+ 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
385
+ feature_report.program.window_area[:north_window_area] = convert_units(north_window_area, 'm^2', 'ft^2')
386
+ # south_window_area
387
+ 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
388
+ feature_report.program.window_area[:south_window_area] = convert_units(south_window_area, 'm^2', 'ft^2')
389
+ # east_window_area
390
+ 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
391
+ feature_report.program.window_area[:east_window_area] = convert_units(east_window_area, 'm^2', 'ft^2')
392
+ # west_window_area
393
+ 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
394
+ feature_report.program.window_area[:west_window_area] = convert_units(west_window_area, 'm^2', 'ft^2')
395
+ # total_window_area
396
+ total_window_area = north_window_area + south_window_area + east_window_area + west_window_area
397
+ feature_report.program.window_area[:total_window_area] = convert_units(total_window_area, 'm^2', 'ft^2')
398
+
399
+ ## wall_area
400
+ # north_wall_area
401
+ 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
402
+ feature_report.program.wall_area[:north_wall_area] = convert_units(north_wall_area, 'm^2', 'ft^2')
403
+ # south_wall_area
404
+ 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
405
+ feature_report.program.wall_area[:south_wall_area] = convert_units(south_wall_area, 'm^2', 'ft^2')
406
+ # east_wall_area
407
+ 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
408
+ feature_report.program.wall_area[:east_wall_area] = convert_units(east_wall_area, 'm^2', 'ft^2')
409
+ # west_wall_area
410
+ 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
411
+ feature_report.program.wall_area[:west_wall_area] = convert_units(west_wall_area, 'm^2', 'ft^2')
412
+ # total_wall_area
413
+ total_wall_area = north_wall_area + south_wall_area + east_wall_area + west_wall_area
414
+ feature_report.program.wall_area[:total_wall_area] = convert_units(total_wall_area, 'm^2', 'ft^2')
415
+
416
+ # total_roof_area
417
+ total_roof_area = 0.0
418
+ surfaces.each do |surface|
419
+ if (surface.outsideBoundaryCondition == 'Outdoors') && (surface.surfaceType == 'RoofCeiling')
420
+ total_roof_area += surface.netArea
421
+ end
422
+ end
423
+ feature_report.program.roof_area[:total_roof_area] = convert_units(total_roof_area, 'm^2', 'ft^2')
424
+
425
+ # orientation
426
+ # RK: a more robust method should be implemented to find orientation(finding main axis of the building using aspect ratio)
427
+ building_rotation = model.getBuilding.northAxis
428
+ feature_report.program.orientation = building_rotation
429
+
430
+ # aspect_ratio
431
+ 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)'")
432
+ 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)'")
433
+ aspect_ratio = north_wall_area / east_wall_area if north_wall_area != 0 && east_wall_area != 0
434
+ aspect_ratio ||= nil
435
+ feature_report.program.aspect_ratio = aspect_ratio
436
+
437
+ ############################################################################
438
+ ##
439
+ # Get Reporting Periods information and store in the feature_report
440
+ ##
441
+
442
+ # start_date
443
+ # month
444
+ begin_month = model.getRunPeriod.getBeginMonth
445
+ feature_report.reporting_periods[0].start_date.month = begin_month
446
+ # day_of_month
447
+ begin_day_of_month = model.getRunPeriod.getBeginDayOfMonth
448
+ feature_report.reporting_periods[0].start_date.day_of_month = begin_day_of_month
449
+ # year
450
+ begin_year = model.getYearDescription.calendarYear
451
+ feature_report.reporting_periods[0].start_date.year = begin_year
452
+
453
+ # end_date
454
+ # month
455
+ end_month = model.getRunPeriod.getEndMonth
456
+ feature_report.reporting_periods[0].end_date.month = end_month
457
+ # day_of_month
458
+ end_day_of_month = model.getRunPeriod.getEndDayOfMonth
459
+ feature_report.reporting_periods[0].end_date.day_of_month = end_day_of_month
460
+ # year
461
+ end_year = model.getYearDescription.calendarYear
462
+ feature_report.reporting_periods[0].end_date.year = end_year
463
+
464
+ # total_site_energy
465
+ total_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Site Energy' AND ColumnName='Total Energy'")
466
+ feature_report.reporting_periods[0].total_site_energy = convert_units(total_site_energy, 'GJ', 'kBtu')
467
+
468
+ # total_source_energy
469
+ total_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Source Energy' AND ColumnName='Total Energy'")
470
+ feature_report.reporting_periods[0].total_source_energy = convert_units(total_source_energy, 'GJ', 'kBtu')
471
+
472
+ # net_site_energy
473
+ net_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Site Energy' AND ColumnName='Total Energy'")
474
+ feature_report.reporting_periods[0].net_site_energy = convert_units(net_site_energy, 'GJ', 'kBtu')
475
+
476
+ # net_source_energy
477
+ net_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Source Energy' AND ColumnName='Total Energy'")
478
+ feature_report.reporting_periods[0].net_source_energy = convert_units(net_source_energy, 'GJ', 'kBtu')
479
+
480
+ # electricity
481
+ electricity = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Electricity'")
482
+ feature_report.reporting_periods[0].electricity = convert_units(electricity, 'GJ', 'kBtu')
483
+
484
+ # natural_gas
485
+ natural_gas = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Natural Gas'")
486
+ feature_report.reporting_periods[0].natural_gas = convert_units(natural_gas, 'GJ', 'kBtu')
487
+
488
+ # additional_fuel
489
+ additional_fuel = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Additional Fuel'")
490
+ feature_report.reporting_periods[0].additional_fuel = convert_units(additional_fuel, 'GJ', 'kBtu')
491
+
492
+ # district_cooling
493
+ district_cooling = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Cooling'")
494
+ feature_report.reporting_periods[0].district_cooling = convert_units(district_cooling, 'GJ', 'kBtu')
495
+
496
+ # district_heating
497
+ district_heating = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Heating'")
498
+ feature_report.reporting_periods[0].district_heating = convert_units(district_heating, 'GJ', 'kBtu')
499
+
500
+ # water
501
+ water = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Water'")
502
+ # feature_report.reporting_periods[0].water = convert_units(water, 'm3', 'ft3')
503
+ feature_report.reporting_periods[0].water = water
504
+
505
+ # electricity_produced
506
+ electricity_produced = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Total On-Site and Utility Electric Sources' AND ColumnName='Electricity'")
507
+ feature_report.reporting_periods[0].electricity_produced = convert_units(electricity_produced, 'GJ', 'kBtu')
508
+
509
+ ## end_uses
510
+
511
+ # get fuel type as listed in the sql file
512
+ fuel_type = ['Electricity', 'Natural Gas', 'Additional Fuel', 'District Cooling', 'District Heating', 'Water']
513
+
514
+ # get enduses as listed in the sql file
515
+ enduses = ['Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps',
516
+ 'Heat Rejection', 'Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators']
517
+
518
+ # loop through fuel types and enduses to fill in sql_query method
519
+ fuel_type.each do |ft|
520
+ enduses.each do |eu|
521
+ sql_r = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='#{eu}' AND ColumnName='#{ft}'")
522
+
523
+ # report each query in its corresponding feature report obeject
524
+ if ft.include? ' '
525
+ x = ft.tr(' ', '_').downcase
526
+ m = feature_report.reporting_periods[0].end_uses.send(x)
527
+ else
528
+ m = feature_report.reporting_periods[0].end_uses.send(ft.downcase)
529
+
530
+ end
531
+
532
+ if eu.include? ' '
533
+ y = eu.tr(' ', '_').downcase
534
+ m.send("#{y}=", convert_units(sql_r, 'GJ', 'kBtu'))
535
+ else
536
+ m.send("#{eu.downcase}=", convert_units(sql_r, 'GJ', 'kBtu'))
537
+ end
538
+ end
539
+ end
540
+
541
+ ### energy_production
542
+ ## electricity_produced
543
+ # photovoltaic
544
+ photovoltaic_power = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Photovoltaic Power' AND ColumnName='Electricity'")
545
+ feature_report.reporting_periods[0].energy_production[:electricity_produced][:photovoltaic] = convert_units(photovoltaic_power, 'GJ', 'kBtu')
546
+
547
+ ## comfort_result
548
+ # time_setpoint_not_met_during_occupied_cooling
549
+ 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'")
550
+ feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_cooling] = time_setpoint_not_met_during_occupied_cooling
551
+
552
+ # time_setpoint_not_met_during_occupied_heating
553
+ 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'")
554
+ feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_heating] = time_setpoint_not_met_during_occupied_heating
555
+
556
+ # time_setpoint_not_met_during_occupied_hour
557
+ time_setpoint_not_met_during_occupied_hours = time_setpoint_not_met_during_occupied_heating + time_setpoint_not_met_during_occupied_cooling
558
+ feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_hours] = time_setpoint_not_met_during_occupied_hours
559
+
560
+ ######################################## Reporting TImeseries Results FOR CSV File ######################################
561
+
562
+ # Get the weather file run period (as opposed to design day run period)
563
+ ann_env_pd = nil
564
+ sql_file.availableEnvPeriods.each do |env_pd|
565
+ env_type = sql_file.environmentType(env_pd)
566
+ if env_type.is_initialized
567
+ if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
568
+ ann_env_pd = env_pd
569
+ end
570
+ end
571
+ end
572
+
573
+ if ann_env_pd == false
574
+ runner.registerError("Can't find a weather runperiod, make sure you ran an annual simulation, not just the design days.")
575
+ return false
576
+ end
577
+
578
+ # timeseries we want to report
579
+ requested_timeseries_names = ['Electricity:Facility', 'ElectricityProduced:Facility', 'Gas:Facility',
580
+ 'DistrictCooling:Facility', 'DistrictHeating:Facility', 'District Cooling Chilled Water Rate',
581
+ 'District Cooling Mass Flow Rate', 'District Cooling Inlet Temperature', 'District Cooling Outlet Temperature',
582
+ 'District Heating Hot Water Rate', 'District Heating Mass Flow Rate', 'District Heating Inlet Temperature', 'District Heating Outlet Temperature']
583
+
584
+ # number of values in each timeseries
585
+ n = nil
586
+
587
+ # all numeric timeseries values, transpose of CSV file (e.g. values[j] is column, values[j][i] is column and row)
588
+ values = []
589
+
590
+ # Since schedule value will have a bunch of key_values, we need to keep track of these as additional timeseries
591
+ # this is recording the name of these final timeseries to write in the header of the CSV
592
+ final_timeseries_names = []
593
+
594
+ # loop over requested timeseries
595
+ # rubocop: disable Metrics/BlockLength
596
+ requested_timeseries_names.each_with_index do |timeseries_name, j|
597
+ runner.registerInfo("TIMESERIES: #{timeseries_name}")
598
+
599
+ # get all the key values that this timeseries can be reported for (e.g. if power is requested for each zone)
600
+ key_values = sql_file.availableKeyValues(ann_env_pd.to_s, reporting_frequency.to_s, timeseries_name)
601
+ runner.registerInfo("KEY VALUES: #{key_values}")
602
+ if key_values.empty?
603
+ key_values = ['']
604
+ end
605
+
606
+ # sort keys
607
+ sorted_keys = key_values.sort
608
+ requested_keys = ['SUMMED ELECTRICITY:FACILITY', 'SUMMED ELECTRICITY:FACILITY POWER', 'SUMMED ELECTRICITYPRODUCED:FACILITY', 'SUMMED ELECTRICITYPRODUCED:FACILITY POWER', 'SUMMED NET APPARENT POWER', 'SUMMED NET ELECTRIC ENERGY', 'SUMMED NET POWER', 'TRANSFORMER OUTPUT ELECTRIC ENERGY SCHEDULE']
609
+ final_keys = []
610
+ # make sure aggregated timeseries are listed in sorted order before all individual feature timeseries
611
+ sorted_keys.each do |k|
612
+ if requested_keys.include? k
613
+ final_keys << k
614
+ end
615
+ end
616
+ sorted_keys.each do |k|
617
+ if !requested_keys.include? k
618
+ final_keys << k
619
+ end
620
+ end
621
+
622
+ # loop over final keys
623
+ final_keys.each_with_index do |key_value, key_i|
624
+ new_timeseries_name = ''
625
+
626
+ runner.registerInfo("!! TIMESERIES NAME: #{timeseries_name} AND key_value: #{key_value}")
627
+
628
+ # check if we have to come up with a new name for the timeseries in our CSV header
629
+ if key_values.size == 1
630
+ # use timeseries name when only 1 keyvalue
631
+ new_timeseries_name = timeseries_name
632
+ else
633
+ # use key_value name
634
+ # special case for Zone Thermal Comfort: use both timeseries_name and key_value
635
+ if timeseries_name.include? 'Zone Thermal Comfort'
636
+ new_timeseries_name = timeseries_name + ' ' + key_value
637
+ else
638
+ new_timeseries_name = key_value
639
+ end
640
+ end
641
+ final_timeseries_names << new_timeseries_name
642
+
643
+ # get the actual timeseries
644
+ ts = sql_file.timeSeries(ann_env_pd.to_s, reporting_frequency.to_s, timeseries_name, key_value)
645
+
646
+ if n.nil?
647
+ # first timeseries should always be set
648
+ runner.registerInfo('First timeseries')
649
+ values[j] = ts.get.values
650
+ n = values[j].size
651
+ elsif ts.is_initialized
652
+ runner.registerInfo('Is Initialized')
653
+ values[j] = ts.get.values
654
+ else
655
+ runner.registerInfo('Is NOT Initialized')
656
+ values[j] = Array.new(n, 0)
657
+ end
658
+
659
+ # ##Unit conversion
660
+ old_units = ts.get.units if ts.is_initialized
661
+ new_units = case old_units.to_s
662
+ when 'J'
663
+ 'kBtu'
664
+ when 'kWh'
665
+ 'kBtu'
666
+ when 'm3'
667
+ 'gal'
668
+ end
669
+
670
+ # Unit conversion here
671
+ os_vec = values[j]
672
+
673
+ # loop through each value to retrieve it
674
+ for i in 0..os_vec.length - 1
675
+ unless new_units == old_units
676
+ os_vec[i] = OpenStudio.convert(os_vec[i], old_units, new_units).get
677
+ end
678
+ end
679
+ end
680
+ end
681
+ # rubocop: enable Metrics/BlockLength
682
+ runner.registerInfo("new final_timeseries_names size: #{final_timeseries_names.size}")
683
+
684
+ # Save the 'default_feature_reports.csv' file
685
+ File.open('default_feature_reports.csv', 'w') do |file|
686
+ file.puts(final_timeseries_names.join(','))
687
+ (0...n).each do |l|
688
+ line = []
689
+ values.each_index do |j|
690
+ line << values[j][l]
691
+ end
692
+ file.puts(line.join(','))
693
+ end
694
+ end
695
+
696
+ # closing the sql file
697
+ sql_file.close
698
+
699
+ ############################# Adding timeseries_csv info to json report and saving CSV ################################
700
+ # add csv info to feature_report
701
+ feature_report.timeseries_csv.path = File.join(Dir.pwd, 'default_feature_reports.csv')
702
+ feature_report.timeseries_csv.first_report_datetime = '0'
703
+ feature_report.timeseries_csv.column_names = final_timeseries_names
704
+
705
+ ##### Save the 'default_feature_reports.json' file
706
+
707
+ feature_report_hash = feature_report.to_hash
708
+
709
+ File.open('default_feature_reports.json', 'w') do |f|
710
+ f.puts JSON.pretty_generate(feature_report_hash)
711
+ # make sure data is written to the disk one way or the other
712
+ begin
713
+ f.fsync
714
+ rescue StandardError
715
+ f.flush
716
+ end
717
+ end
718
+
719
+ # reporting final condition
720
+ runner.registerFinalCondition('Default Feature Reports generated successfully.')
721
+
722
+ true
723
+ # end the run method
724
+ end
725
+ # end the measure
726
+ end
727
+ # rubocop:enable Metrics/AbcSize
728
+ # rubocop:enable Naming/MethodName
729
+
730
+ # register the measure to be used by the application
731
+ DefaultFeatureReports.new.registerWithApplication