urbanopt-scenario 0.3.0.pre1 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +3 -3
  3. data/.gitignore +2 -0
  4. data/CHANGELOG.md +60 -2
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +33 -17
  7. data/Jenkinsfile +1 -1
  8. data/LICENSE.md +1 -1
  9. data/RDOC_MAIN.md +2 -2
  10. data/README.md +1 -1
  11. data/Rakefile +1 -1
  12. data/docs/.vuepress/components/InnerJsonSchema.vue +7 -15
  13. data/docs/.vuepress/config.js +13 -9
  14. data/docs/.vuepress/highlight.js +1 -1
  15. data/docs/.vuepress/json-schema-deref-loader.js +22 -0
  16. data/docs/README.md +2 -2
  17. data/docs/package-lock.json +2384 -2323
  18. data/docs/package.json +12 -8
  19. data/lib/urbanopt-scenario.rb +1 -1
  20. data/lib/urbanopt/scenario.rb +2 -1
  21. data/lib/urbanopt/scenario/default_reports.rb +3 -8
  22. data/lib/urbanopt/scenario/extension.rb +6 -4
  23. data/lib/urbanopt/scenario/logger.rb +1 -1
  24. data/lib/urbanopt/scenario/scenario_base.rb +5 -5
  25. data/lib/urbanopt/scenario/scenario_csv.rb +29 -17
  26. data/lib/urbanopt/scenario/scenario_datapoint_base.rb +12 -5
  27. data/lib/urbanopt/scenario/scenario_post_processor_base.rb +3 -3
  28. data/lib/urbanopt/scenario/scenario_post_processor_default.rb +121 -11
  29. data/lib/urbanopt/scenario/scenario_post_processor_opendss.rb +13 -14
  30. data/lib/urbanopt/scenario/scenario_runner_base.rb +8 -7
  31. data/lib/urbanopt/scenario/scenario_runner_osw.rb +29 -13
  32. data/lib/urbanopt/scenario/scenario_visualization.rb +235 -0
  33. data/lib/urbanopt/scenario/simulation_dir_base.rb +4 -4
  34. data/lib/urbanopt/scenario/simulation_dir_osw.rb +6 -13
  35. data/lib/urbanopt/scenario/simulation_mapper_base.rb +4 -4
  36. data/lib/urbanopt/scenario/version.rb +2 -2
  37. data/package-lock.json +3 -0
  38. data/urbanopt-scenario-gem.gemspec +11 -10
  39. metadata +80 -57
  40. data/doc_templates/LICENSE.md +0 -27
  41. data/doc_templates/README.md.erb +0 -42
  42. data/doc_templates/copyright_erb.txt +0 -31
  43. data/doc_templates/copyright_js.txt +0 -4
  44. data/doc_templates/copyright_ruby.txt +0 -29
  45. data/docs/.vuepress/components/ScenarioSchema.vue +0 -12
  46. data/docs/schemas/scenario-schema.md +0 -3
  47. data/lib/measures/.rubocop.yml +0 -5
  48. data/lib/measures/default_feature_reports/LICENSE.md +0 -27
  49. data/lib/measures/default_feature_reports/README.md +0 -56
  50. data/lib/measures/default_feature_reports/README.md.erb +0 -42
  51. data/lib/measures/default_feature_reports/measure.rb +0 -1006
  52. data/lib/measures/default_feature_reports/measure.xml +0 -143
  53. data/lib/measures/default_feature_reports/tests/USA_CO_Golden-NREL.724666_TMY3.epw +0 -8768
  54. data/lib/measures/default_feature_reports/tests/default_feature_reports_test.rb +0 -238
  55. data/lib/measures/default_feature_reports/tests/example_model.osm +0 -4378
  56. data/lib/urbanopt/scenario/default_reports/construction_cost.rb +0 -169
  57. data/lib/urbanopt/scenario/default_reports/date.rb +0 -97
  58. data/lib/urbanopt/scenario/default_reports/distributed_generation.rb +0 -379
  59. data/lib/urbanopt/scenario/default_reports/end_use.rb +0 -159
  60. data/lib/urbanopt/scenario/default_reports/end_uses.rb +0 -140
  61. data/lib/urbanopt/scenario/default_reports/feature_report.rb +0 -267
  62. data/lib/urbanopt/scenario/default_reports/generator.rb +0 -92
  63. data/lib/urbanopt/scenario/default_reports/location.rb +0 -99
  64. data/lib/urbanopt/scenario/default_reports/logger.rb +0 -44
  65. data/lib/urbanopt/scenario/default_reports/power_distribution.rb +0 -102
  66. data/lib/urbanopt/scenario/default_reports/program.rb +0 -266
  67. data/lib/urbanopt/scenario/default_reports/reporting_period.rb +0 -305
  68. data/lib/urbanopt/scenario/default_reports/scenario_report.rb +0 -317
  69. data/lib/urbanopt/scenario/default_reports/schema/README.md +0 -33
  70. data/lib/urbanopt/scenario/default_reports/schema/scenario_csv_columns.txt +0 -32
  71. data/lib/urbanopt/scenario/default_reports/schema/scenario_schema.json +0 -857
  72. data/lib/urbanopt/scenario/default_reports/solar_pv.rb +0 -93
  73. data/lib/urbanopt/scenario/default_reports/storage.rb +0 -105
  74. data/lib/urbanopt/scenario/default_reports/timeseries_csv.rb +0 -299
  75. data/lib/urbanopt/scenario/default_reports/validator.rb +0 -97
  76. data/lib/urbanopt/scenario/default_reports/wind.rb +0 -92
@@ -1,3 +0,0 @@
1
- # Scenario Schema
2
-
3
- <ScenarioSchema />
@@ -1,5 +0,0 @@
1
- AllCops:
2
- Exclude:
3
- - 'spec/test_measures/**/*'
4
- inherit_from:
5
- - http://s3.amazonaws.com/openstudio-resources/styles/rubocop.yml
@@ -1,27 +0,0 @@
1
- URBANopt, Copyright (c) 2019-2020, 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.
@@ -1,56 +0,0 @@
1
-
2
-
3
- ###### (Automatically generated documentation)
4
-
5
- # DefaultFeatureReports
6
-
7
- ## Description
8
- Writes default_feature_reports.json and default_feature_reports.csv files 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
-
@@ -1,42 +0,0 @@
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 %>
@@ -1,1006 +0,0 @@
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/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 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
-
197
- timeseries_data.each do |ts|
198
- result << OpenStudio::IdfObject.load("Output:Variable,*,#{ts},#{reporting_frequency};").get
199
- end
200
-
201
- # use the built-in error checking
202
- if !runner.validateUserArguments(arguments, user_arguments)
203
- return result
204
- end
205
-
206
- return result
207
- end
208
-
209
- # sql_query method
210
- def sql_query(runner, sql, report_name, query)
211
- val = nil
212
- result = sql.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='#{report_name}' AND #{query}")
213
- if result.empty?
214
- runner.registerWarning("Query failed for #{report_name} and #{query}")
215
- else
216
- begin
217
- val = result.get
218
- rescue StandardError
219
- val = nil
220
- runner.registerWarning('Query result.get failed')
221
- end
222
- end
223
-
224
- val
225
- end
226
-
227
- # unit conversion method
228
- def convert_units(value, from_units, to_units)
229
- # apply unit conversion
230
- value_converted = OpenStudio.convert(value, from_units, to_units)
231
- if value_converted.is_initialized
232
- value = value_converted.get
233
- else
234
- @runner.registerError("Was not able to convert #{value} from #{from_units} to #{to_units}.")
235
- value = nil
236
- end
237
- return value
238
- end
239
-
240
- # define what happens when the measure is run
241
- # rubocop:disable Metrics/AbcSize
242
- def run(runner, user_arguments)
243
- super(runner, user_arguments)
244
-
245
- # use the built-in error checking
246
- unless runner.validateUserArguments(arguments, user_arguments)
247
- return false
248
- end
249
-
250
- # use the built-in error checking
251
- if !runner.validateUserArguments(arguments, user_arguments)
252
- return false
253
- end
254
-
255
- feature_id = runner.getStringArgumentValue('feature_id', user_arguments)
256
- feature_name = runner.getStringArgumentValue('feature_name', user_arguments)
257
- feature_type = runner.getStringArgumentValue('feature_type', user_arguments)
258
-
259
- # Assign the user inputs to variables
260
- reporting_frequency = runner.getStringArgumentValue('reporting_frequency', user_arguments)
261
-
262
- # BilingPeriod reporting frequency not implemented yet
263
- if reporting_frequency == 'BillingPeriod'
264
- @@logger.error('BillingPeriod frequency is not implemented yet')
265
- end
266
-
267
- # cache runner for this instance of the measure
268
- @runner = runner
269
-
270
- # get the WorkflowJSON object
271
- workflow = runner.workflow
272
-
273
- # get the last model and sql file
274
- model = runner.lastOpenStudioModel
275
- if model.empty?
276
- runner.registerError('Cannot find last model.')
277
- return false
278
- end
279
- model = model.get
280
-
281
- sql_file = runner.lastEnergyPlusSqlFile
282
- if sql_file.empty?
283
- runner.registerError('Cannot find last sql file.')
284
- return false
285
- end
286
- sql_file = sql_file.get
287
- model.setSqlFile(sql_file)
288
-
289
- # get building from model
290
- building = model.getBuilding
291
-
292
- # get surfaces from model
293
- surfaces = model.getSurfaces
294
-
295
- # get epw_file
296
- epw_file = runner.lastEpwFile
297
- if epw_file.empty?
298
- runner.registerError('Cannot find last epw file.')
299
- return false
300
- end
301
- epw_file = epw_file.get
302
-
303
- # create output feature_report report object
304
- feature_report = URBANopt::Scenario::DefaultReports::FeatureReport.new
305
- feature_report.id = feature_id
306
- feature_report.name = feature_name
307
- feature_report.feature_type = feature_type
308
- feature_report.directory_name = workflow.absoluteRunDir
309
-
310
- timesteps_per_hour = model.getTimestep.numberOfTimestepsPerHour
311
- feature_report.timesteps_per_hour = timesteps_per_hour
312
-
313
- feature_report.simulation_status = 'Complete'
314
-
315
- feature_report.reporting_periods << URBANopt::Scenario::DefaultReports::ReportingPeriod.new
316
-
317
- ###########################################################################
318
- ##
319
- # Get Location information and store in the feature_report
320
- ##
321
-
322
- # latitude
323
- latitude = epw_file.latitude
324
- feature_report.location.latitude = latitude
325
-
326
- # longitude
327
- longitude = epw_file.longitude
328
- feature_report.location.longitude = longitude
329
-
330
- # surface_elevation
331
- elev = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='General' AND RowName='Elevation' AND ColumnName='Value'")
332
- feature_report.location.surface_elevation = elev
333
-
334
- ##########################################################################
335
- ##
336
- # Get program information and store in the feature_report
337
- ##
338
-
339
- # floor_area
340
- floor_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Total Building Area' AND ColumnName='Area'")
341
- feature_report.program.floor_area = convert_units(floor_area, 'm^2', 'ft^2')
342
-
343
- # conditioned_area
344
- conditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Net Conditioned Building Area' AND ColumnName='Area'")
345
- feature_report.program.conditioned_area = convert_units(conditioned_area, 'm^2', 'ft^2')
346
-
347
- # unconditioned_area
348
- unconditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Unconditioned Building Area' AND ColumnName='Area'")
349
- feature_report.program.unconditioned_area = convert_units(unconditioned_area, 'm^2', 'ft^2')
350
-
351
- # footprint_area
352
- feature_report.program.footprint_area = convert_units(floor_area, 'm^2', 'ft^2')
353
-
354
- # maximum_number_of_stories
355
- number_of_stories = building.standardsNumberOfStories.get if building.standardsNumberOfStories.is_initialized
356
- number_of_stories ||= 1
357
- feature_report.program.maximum_number_of_stories = number_of_stories
358
-
359
- # maximum_roof_height
360
- floor_to_floor_height = building.nominalFloortoFloorHeight.to_f
361
- maximum_roof_height = number_of_stories * floor_to_floor_height
362
- feature_report.program.maximum_roof_height = maximum_roof_height
363
-
364
- # maximum_number_of_stories_above_ground
365
- number_of_stories_above_ground = building.standardsNumberOfAboveGroundStories.get if building.standardsNumberOfAboveGroundStories.is_initialized
366
- number_of_stories_above_ground ||= 1
367
- feature_report.program.maximum_number_of_stories_above_ground = number_of_stories_above_ground
368
-
369
- # number_of_residential_units
370
- number_of_living_units = building.standardsNumberOfLivingUnits.to_i.get if building.standardsNumberOfLivingUnits.is_initialized
371
- number_of_living_units ||= 1
372
- feature_report.program.number_of_residential_units = number_of_living_units
373
-
374
- ## building_types
375
-
376
- # get an array of the model spaces
377
- spaces = model.getSpaces
378
-
379
- # get array of model space types
380
- space_types = model.getSpaceTypes
381
-
382
- # create a hash for space_type_areas (spcace types as keys and their areas as values)
383
- space_type_areas = {}
384
- model.getSpaceTypes.each do |space_type|
385
- building_type = space_type.standardsBuildingType
386
- if building_type.empty?
387
- building_type = 'unknown'
388
- else
389
- building_type = building_type.get
390
- end
391
- space_type_areas[building_type] = 0 if space_type_areas[building_type].nil?
392
- space_type_areas[building_type] += convert_units(space_type.floorArea, 'm^2', 'ft^2')
393
- end
394
-
395
- # create a hash for space_type_occupancy (spcace types as keys and their occupancy as values)
396
- space_type_occupancy = {}
397
- spaces.each do |space|
398
- if space.spaceType.empty?
399
- raise 'space.spaceType is empty. Make sure spaces have a space type'
400
- else
401
- building_type = space.spaceType.get.standardsBuildingType
402
- end
403
- if building_type.empty?
404
- building_type = 'unknown'
405
- else
406
- building_type = building_type.get
407
- end
408
- space_type_occupancy[building_type] = 0 if space_type_occupancy[building_type].nil?
409
- space_type_occupancy[building_type] += space.numberOfPeople
410
- end
411
-
412
- # combine all in a building_types array
413
- building_types = []
414
- for i in 0..(space_type_areas.size - 1)
415
- building_types << { building_type: space_type_areas.keys[i], floor_area: space_type_areas.values[i], maximum_occupancy: space_type_occupancy.values[i] }
416
- end
417
- # add results to the feature report JSON
418
- feature_report.program.building_types = building_types
419
-
420
- ## window_area
421
- # north_window_area
422
- 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
423
- feature_report.program.window_area[:north_window_area] = convert_units(north_window_area, 'm^2', 'ft^2')
424
- # south_window_area
425
- 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
426
- feature_report.program.window_area[:south_window_area] = convert_units(south_window_area, 'm^2', 'ft^2')
427
- # east_window_area
428
- 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
429
- feature_report.program.window_area[:east_window_area] = convert_units(east_window_area, 'm^2', 'ft^2')
430
- # west_window_area
431
- 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
432
- feature_report.program.window_area[:west_window_area] = convert_units(west_window_area, 'm^2', 'ft^2')
433
- # total_window_area
434
- total_window_area = north_window_area + south_window_area + east_window_area + west_window_area
435
- feature_report.program.window_area[:total_window_area] = convert_units(total_window_area, 'm^2', 'ft^2')
436
-
437
- ## wall_area
438
- # north_wall_area
439
- 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
440
- feature_report.program.wall_area[:north_wall_area] = convert_units(north_wall_area, 'm^2', 'ft^2')
441
- # south_wall_area
442
- 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
443
- feature_report.program.wall_area[:south_wall_area] = convert_units(south_wall_area, 'm^2', 'ft^2')
444
- # east_wall_area
445
- 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
446
- feature_report.program.wall_area[:east_wall_area] = convert_units(east_wall_area, 'm^2', 'ft^2')
447
- # west_wall_area
448
- 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
449
- feature_report.program.wall_area[:west_wall_area] = convert_units(west_wall_area, 'm^2', 'ft^2')
450
- # total_wall_area
451
- total_wall_area = north_wall_area + south_wall_area + east_wall_area + west_wall_area
452
- feature_report.program.wall_area[:total_wall_area] = convert_units(total_wall_area, 'm^2', 'ft^2')
453
-
454
- # total_roof_area
455
- total_roof_area = 0.0
456
- surfaces.each do |surface|
457
- if (surface.outsideBoundaryCondition == 'Outdoors') && (surface.surfaceType == 'RoofCeiling')
458
- total_roof_area += surface.netArea
459
- end
460
- end
461
- feature_report.program.roof_area[:total_roof_area] = convert_units(total_roof_area, 'm^2', 'ft^2')
462
-
463
- # orientation
464
- # RK: a more robust method should be implemented to find orientation(finding main axis of the building using aspect ratio)
465
- building_rotation = model.getBuilding.northAxis
466
- feature_report.program.orientation = building_rotation
467
-
468
- # aspect_ratio
469
- 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)'")
470
- 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)'")
471
- aspect_ratio = north_wall_area / east_wall_area if north_wall_area != 0 && east_wall_area != 0
472
- aspect_ratio ||= nil
473
- feature_report.program.aspect_ratio = aspect_ratio
474
-
475
- # total_construction_cost
476
- 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'")
477
- feature_report.program.total_construction_cost = total_construction_cost
478
-
479
- ############################################################################
480
- ##
481
- # Get Reporting Periods information and store in the feature_report
482
- ##
483
-
484
- # start_date
485
- # month
486
- begin_month = model.getRunPeriod.getBeginMonth
487
- feature_report.reporting_periods[0].start_date.month = begin_month
488
- # day_of_month
489
- begin_day_of_month = model.getRunPeriod.getBeginDayOfMonth
490
- feature_report.reporting_periods[0].start_date.day_of_month = begin_day_of_month
491
- # year
492
- begin_year = model.getYearDescription.calendarYear
493
- feature_report.reporting_periods[0].start_date.year = begin_year
494
-
495
- # end_date
496
- # month
497
- end_month = model.getRunPeriod.getEndMonth
498
- feature_report.reporting_periods[0].end_date.month = end_month
499
- # day_of_month
500
- end_day_of_month = model.getRunPeriod.getEndDayOfMonth
501
- feature_report.reporting_periods[0].end_date.day_of_month = end_day_of_month
502
- # year
503
- end_year = model.getYearDescription.calendarYear
504
- feature_report.reporting_periods[0].end_date.year = end_year
505
-
506
- # total_site_energy
507
- total_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Site Energy' AND ColumnName='Total Energy'")
508
- feature_report.reporting_periods[0].total_site_energy = convert_units(total_site_energy, 'GJ', 'kBtu')
509
-
510
- # total_source_energy
511
- total_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Source Energy' AND ColumnName='Total Energy'")
512
- feature_report.reporting_periods[0].total_source_energy = convert_units(total_source_energy, 'GJ', 'kBtu')
513
-
514
- # net_site_energy
515
- net_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Site Energy' AND ColumnName='Total Energy'")
516
- feature_report.reporting_periods[0].net_site_energy = convert_units(net_site_energy, 'GJ', 'kBtu')
517
-
518
- # net_source_energy
519
- net_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Source Energy' AND ColumnName='Total Energy'")
520
- feature_report.reporting_periods[0].net_source_energy = convert_units(net_source_energy, 'GJ', 'kBtu')
521
-
522
- # electricity
523
- electricity = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Electricity'")
524
- feature_report.reporting_periods[0].electricity = convert_units(electricity, 'GJ', 'kBtu')
525
-
526
- # natural_gas
527
- natural_gas = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Natural Gas'")
528
- feature_report.reporting_periods[0].natural_gas = convert_units(natural_gas, 'GJ', 'kBtu')
529
-
530
- # additional_fuel
531
- additional_fuel = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Additional Fuel'")
532
- feature_report.reporting_periods[0].additional_fuel = convert_units(additional_fuel, 'GJ', 'kBtu')
533
-
534
- # district_cooling
535
- district_cooling = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Cooling'")
536
- feature_report.reporting_periods[0].district_cooling = convert_units(district_cooling, 'GJ', 'kBtu')
537
-
538
- # district_heating
539
- district_heating = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Heating'")
540
- feature_report.reporting_periods[0].district_heating = convert_units(district_heating, 'GJ', 'kBtu')
541
-
542
- # water
543
- water = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Water'")
544
- # feature_report.reporting_periods[0].water = convert_units(water, 'm3', 'ft3')
545
- feature_report.reporting_periods[0].water = water
546
-
547
- # electricity_produced
548
- electricity_produced = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Total On-Site and Utility Electric Sources' AND ColumnName='Electricity'")
549
- feature_report.reporting_periods[0].electricity_produced = convert_units(electricity_produced, 'GJ', 'kBtu')
550
-
551
- ## end_uses
552
-
553
- # get fuel type as listed in the sql file
554
- fuel_type = ['Electricity', 'Natural Gas', 'Additional Fuel', 'District Cooling', 'District Heating', 'Water']
555
-
556
- # get enduses as listed in the sql file
557
- enduses = ['Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps',
558
- 'Heat Rejection', 'Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators']
559
-
560
- # loop through fuel types and enduses to fill in sql_query method
561
- fuel_type.each do |ft|
562
- enduses.each do |eu|
563
- sql_r = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='#{eu}' AND ColumnName='#{ft}'")
564
-
565
- # report each query in its corresponding feature report obeject
566
- if ft.include? ' '
567
- x = ft.tr(' ', '_').downcase
568
- m = feature_report.reporting_periods[0].end_uses.send(x)
569
- else
570
- m = feature_report.reporting_periods[0].end_uses.send(ft.downcase)
571
-
572
- end
573
-
574
- if eu.include? ' '
575
- y = eu.tr(' ', '_').downcase
576
- m.send("#{y}=", convert_units(sql_r, 'GJ', 'kBtu'))
577
- else
578
- m.send("#{eu.downcase}=", convert_units(sql_r, 'GJ', 'kBtu'))
579
- end
580
- end
581
- end
582
-
583
- ### energy_production
584
- ## electricity_produced
585
- # photovoltaic
586
- photovoltaic_power = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Photovoltaic Power' AND ColumnName='Electricity'")
587
- feature_report.reporting_periods[0].energy_production[:electricity_produced][:photovoltaic] = convert_units(photovoltaic_power, 'GJ', 'kBtu')
588
-
589
- ## Total utility cost
590
- total_utility_cost = sql_query(runner, sql_file, 'Economics Results Summary Report', "TableName='Annual Cost' AND RowName='Cost' AND ColumnName='Total'")
591
- feature_report.reporting_periods[0].total_utility_cost = total_utility_cost
592
-
593
- ## Utility Costs
594
- # electricity utility cost
595
- elec_utility_cost = sql_query(runner, sql_file, 'Economics Results Summary Report', "TableName='Annual Cost' AND RowName='Cost' AND ColumnName='Electric'")
596
- feature_report.reporting_periods[0].utility_costs[0][:fuel_type] = 'Electricity'
597
- feature_report.reporting_periods[0].utility_costs[0][:total_cost] = elec_utility_cost
598
- # gas utility cost
599
- gas_utility_cost = sql_query(runner, sql_file, 'Economics Results Summary Report', "TableName='Annual Cost' AND RowName='Cost' AND ColumnName='Gas'")
600
- feature_report.reporting_periods[0].utility_costs << {:fuel_type => 'Natural Gas', :total_cost => gas_utility_cost}
601
-
602
- ## comfort_result
603
- # time_setpoint_not_met_during_occupied_cooling
604
- 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'")
605
- feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_cooling] = time_setpoint_not_met_during_occupied_cooling
606
-
607
- # time_setpoint_not_met_during_occupied_heating
608
- 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'")
609
- feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_heating] = time_setpoint_not_met_during_occupied_heating
610
-
611
- # time_setpoint_not_met_during_occupied_hour
612
- time_setpoint_not_met_during_occupied_hours = time_setpoint_not_met_during_occupied_heating + time_setpoint_not_met_during_occupied_cooling
613
- feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_hours] = time_setpoint_not_met_during_occupied_hours
614
-
615
- ######################################## Reporting TImeseries Results FOR CSV File ######################################
616
-
617
- # Get the weather file run period (as opposed to design day run period)
618
- ann_env_pd = nil
619
- sql_file.availableEnvPeriods.each do |env_pd|
620
- env_type = sql_file.environmentType(env_pd)
621
- if env_type.is_initialized
622
- if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
623
- ann_env_pd = env_pd
624
- end
625
- end
626
- end
627
-
628
- if ann_env_pd == false
629
- runner.registerError("Can't find a weather runperiod, make sure you ran an annual simulation, not just the design days.")
630
- return false
631
- end
632
-
633
- # timeseries we want to report
634
- requested_timeseries_names = [
635
- 'Electricity:Facility',
636
- 'ElectricityProduced:Facility',
637
- 'Gas:Facility',
638
- 'Cooling:Electricity',
639
- 'Heating:Electricity',
640
- 'InteriorLights:Electricity',
641
- 'ExteriorLights:Electricity',
642
- 'InteriorEquipment:Electricity',
643
- 'Fans:Electricity',
644
- 'Pumps:Electricity',
645
- 'WaterSystems:Electricity',
646
- 'Heating:Gas',
647
- 'WaterSystems:Gas',
648
- 'InteriorEquipment:Gas',
649
- 'DistrictCooling:Facility',
650
- 'DistrictHeating:Facility',
651
- 'District Cooling Chilled Water Rate',
652
- 'District Cooling Mass Flow Rate',
653
- 'District Cooling Inlet Temperature',
654
- 'District Cooling Outlet Temperature',
655
- 'District Heating Hot Water Rate',
656
- 'District Heating Mass Flow Rate',
657
- 'District Heating Inlet Temperature',
658
- 'District Heating Outlet Temperature',
659
- 'Cooling Coil Total Cooling Rate',
660
- 'Heating Coil Heating Rate'
661
- ]
662
-
663
- # add thermal comfort timeseries
664
- comfortTimeseries = ['Zone Thermal Comfort Fanger Model PMV', 'Zone Thermal Comfort Fanger Model PPD']
665
- requested_timeseries_names += comfortTimeseries
666
-
667
- # add additional power timeseries (for calculating transformer apparent power to compare to rating ) in VA
668
- powerTimeseries = ['Net Electric Energy', 'Electricity:Facility Power', 'ElectricityProduced:Facility Power', 'Electricity:Facility Apparent Power', 'ElectricityProduced:Facility Apparent Power', 'Net Power', 'Net Apparent Power']
669
- requested_timeseries_names += powerTimeseries
670
-
671
- # register info all timeseries
672
- runner.registerInfo("All timeseries: #{requested_timeseries_names}")
673
-
674
- # timeseries variables to keep to calculate power
675
- tsToKeep = ['Electricity:Facility', 'ElectricityProduced:Facility']
676
- tsToKeepIndexes = {}
677
-
678
- ### powerFactor ###
679
- # use power_factor default: 0.9
680
- # TODO: Set powerFactor default based on building type
681
- powerFactor = 0.9
682
-
683
- ### power_conversion ###
684
- # divide values by total_seconds to convert J to W (W = J/sec)
685
- # divide values by total_hours to convert kWh to kW (kW = kWh/hrs)
686
- total_seconds = (60 / timesteps_per_hour.to_f) * 60 # make sure timesteps_per_hour is a float in the division
687
- total_hours = 1 / timesteps_per_hour.to_f # make sure timesteps_per_hour is a float in the division
688
- # set power_conversion
689
- power_conversion = total_hours # we set the power conversio to total_hours since we want to convert lWh to kW
690
- puts "Power Converion: to convert kWh to kW values will be divided by #{power_conversion}"
691
-
692
- # number of values in each timeseries
693
- n = nil
694
- # all numeric timeseries values, transpose of CSV file (e.g. values[key_cnt] is column, values[key_cnt][i] is column and row)
695
- values = []
696
- tmpArray = []
697
- # since schedule value will have a bunch of key_values, we need to keep track of these as additional timeseries
698
- key_cnt = 0
699
- # this is recording the name of these final timeseries to write in the header of the CSV
700
- final_timeseries_names = []
701
-
702
- # loop over requested timeseries
703
- # rubocop: disable Metrics/BlockLength
704
- requested_timeseries_names.each_index do |i|
705
- timeseries_name = requested_timeseries_names[i]
706
- runner.registerInfo("TIMESERIES: #{timeseries_name}")
707
-
708
- # get all the key values that this timeseries can be reported for (e.g. if PMV is requested for each zone)
709
- key_values = sql_file.availableKeyValues('RUN PERIOD 1', 'Zone Timestep', timeseries_name)
710
- runner.registerInfo("KEY VALUES: #{key_values}")
711
- if key_values.empty?
712
- key_values = ['']
713
- end
714
-
715
- # sort keys
716
- sorted_keys = key_values.sort
717
- requested_keys = requested_timeseries_names
718
- final_keys = []
719
- # make sure aggregated timeseries are listed in sorted order before all individual feature timeseries
720
- sorted_keys.each do |k|
721
- if requested_keys.include? k
722
- final_keys << k
723
- end
724
- end
725
- sorted_keys.each do |k|
726
- if !requested_keys.include? k
727
- final_keys << k
728
- end
729
- end
730
-
731
- # loop over final keys
732
- final_keys.each_with_index do |key_value, key_i|
733
- new_timeseries_name = ''
734
-
735
- runner.registerInfo("!! TIMESERIES NAME: #{timeseries_name} AND key_value: #{key_value}")
736
-
737
- # check if we have to come up with a new name for the timeseries in our CSV header
738
- if key_values.size == 1
739
- # use timeseries name when only 1 keyvalue
740
- new_timeseries_name = timeseries_name
741
- else
742
- # use key_value name
743
- # special case for Zone Thermal Comfort: use both timeseries_name and key_value
744
- if timeseries_name.include? 'Zone Thermal Comfort'
745
- new_timeseries_name = timeseries_name + ' ' + key_value
746
- else
747
- new_timeseries_name = key_value
748
- end
749
- end
750
- # final_timeseries_names << new_timeseries_name
751
-
752
- # get the actual timeseries
753
- ts = sql_file.timeSeries(ann_env_pd.to_s, reporting_frequency.to_s, timeseries_name, key_value)
754
-
755
- if n.nil?
756
- # first timeseries should always be set
757
- runner.registerInfo('First timeseries')
758
- values[key_cnt] = ts.get.values
759
- n = values[key_cnt].size
760
- elsif ts.is_initialized
761
- runner.registerInfo('Is Initialized')
762
- values[key_cnt] = ts.get.values
763
- else
764
- runner.registerInfo('Is NOT Initialized')
765
- values[key_cnt] = Array.new(n, 0)
766
- end
767
-
768
- # unit conversion
769
- old_unit = ts.get.units if ts.is_initialized
770
-
771
- if timeseries_name.include? 'Gas'
772
- new_unit = 'kBtu'
773
- else
774
- new_unit = case old_unit.to_s
775
- when 'J'
776
- 'kWh'
777
- when 'kBtu'
778
- 'kWh'
779
- when 'gal'
780
- 'm3'
781
- when 'W'
782
- 'W'
783
- end
784
- end
785
-
786
- # loop through each value and apply unit conversion
787
- os_vec = values[key_cnt]
788
- if !timeseries_name.include? 'Zone Thermal Comfort'
789
- for i in 0..os_vec.length - 1
790
-
791
- unless new_unit == old_unit || old_unit.nil? || new_unit.nil? || !ts.is_initialized
792
- os_vec[i] = OpenStudio.convert(os_vec[i], old_unit, new_unit).get
793
- end
794
-
795
- end
796
- end
797
-
798
- # keep certain timeseries to calculate power
799
- if tsToKeep.include? timeseries_name
800
- tsToKeepIndexes[timeseries_name] = key_cnt
801
- end
802
-
803
- # special processing: power
804
- if powerTimeseries.include? timeseries_name
805
- # special case: net series (subtract generation from load)
806
- if timeseries_name.include? 'Net'
807
-
808
- newVals = Array.new(n, 0)
809
- # Apparent power calculation
810
-
811
- if timeseries_name.include?('Apparent')
812
- (0..n - 1).each do |j|
813
- newVals[j] = (values[tsToKeepIndexes['Electricity:Facility']][j].to_f - values[tsToKeepIndexes['ElectricityProduced:Facility']][j].to_f) / power_conversion / powerFactor
814
- j += 1
815
- end
816
- new_unit = 'kVA'
817
- elsif timeseries_name.include? 'Net Electric Energy'
818
- (0..n - 1).each do |j|
819
- newVals[j] = (values[tsToKeepIndexes['Electricity:Facility']][j].to_f - values[tsToKeepIndexes['ElectricityProduced:Facility']][j].to_f)
820
- j += 1
821
- end
822
- new_unit = 'kWh'
823
- else
824
- runner.registerInfo('Power calc')
825
- # Power calculation
826
- (0..n - 1).each do |j|
827
- newVals[j] = (values[tsToKeepIndexes['Electricity:Facility']][j].to_f - values[tsToKeepIndexes['ElectricityProduced:Facility']][j].to_f) / power_conversion
828
- j += 1
829
- end
830
- new_unit = 'kW'
831
- end
832
-
833
- values[key_cnt] = newVals
834
- else
835
- tsToKeepIndexes.each do |key, indexValue|
836
- if timeseries_name.include? key
837
- runner.registerInfo("timeseries_name: #{timeseries_name}, key: #{key}")
838
- # use this timeseries
839
- newVals = Array.new(n, 0)
840
- # Apparent power calculation
841
- if timeseries_name.include?('Apparent')
842
- (0..n - 1).each do |j|
843
- newVals[j] = values[indexValue][j].to_f / power_conversion / powerFactor
844
- j += 1
845
- end
846
- new_unit = 'kVA'
847
- else
848
- # Power calculation
849
- (0..n - 1).each do |j|
850
- newVals[j] = values[indexValue][j].to_f / power_conversion
851
- j += 1
852
- end
853
- new_unit = 'kW'
854
- end
855
- values[key_cnt] = newVals
856
- end
857
- end
858
- end
859
- end
860
-
861
- # append units to headers
862
- new_timeseries_name += "(#{new_unit})"
863
- final_timeseries_names << new_timeseries_name
864
-
865
- # TODO: DELETE PUTS
866
- # puts " *********timeseries_name = #{timeseries_name}******************"
867
- # if timeseries_name.include? 'Power'
868
- # puts "values = #{values[key_cnt]}"
869
- # puts "units = #{new_unit}"
870
- # end
871
-
872
- # comfort results usually have multiple timeseries (per zone), aggregate into a single series with consistent name and use worst value at each timestep
873
- if comfortTimeseries.include? timeseries_name
874
-
875
- # set up array if 1st key_value
876
- if key_i == 0
877
- runner.registerInfo("SETTING UP NEW ARRAY FOR: #{timeseries_name}")
878
- tmpArray = Array.new(n, 0)
879
- end
880
-
881
- # add to array (keep max value at each timestep)
882
- (0..(n - 1)).each do |ind|
883
- # process negative and positive values differently
884
- tVal = values[key_cnt][ind].to_f
885
- if tVal < 0
886
- tmpArray[ind] = [tVal, tmpArray[ind]].min
887
- else
888
- tmpArray[ind] = [tVal, tmpArray[ind]].max
889
- end
890
- end
891
-
892
- # aggregate and save when all keyvalues have been processed
893
- if key_i == final_keys.size - 1
894
-
895
- hrsOutOfBounds = 0
896
- if timeseries_name === 'Zone Thermal Comfort Fanger Model PMV'
897
- (0..(n - 1)).each do |ind|
898
- # -0.5 < x < 0.5 is within bounds
899
- if values[key_cnt][ind].to_f > 0.5 || values[key_cnt][ind].to_f < -0.5
900
- hrsOutOfBounds += 1
901
- end
902
- end
903
- hrsOutOfBounds = hrsOutOfBounds.to_f / timesteps_per_hour
904
- elsif timeseries_name === 'Zone Thermal Comfort Fanger Model PPD'
905
- (0..(n - 1)).each do |ind|
906
- # > 20 is outside bounds
907
- if values[key_cnt][ind].to_f > 20
908
- hrsOutOfBounds += 1
909
- end
910
- end
911
- hrsOutOfBounds = hrsOutOfBounds.to_f / timesteps_per_hour
912
- else
913
- # this one is already scaled by timestep, no need to divide total
914
- (0..(n - 1)).each do |ind|
915
- hrsOutOfBounds += values[key_cnt][ind].to_f if values[key_cnt][ind].to_f > 0
916
- end
917
- end
918
-
919
- # save variable to feature_reports hash
920
- runner.registerInfo("timeseries #{timeseries_name}: hours out of bounds: #{hrsOutOfBounds}")
921
- if timeseries_name === 'Zone Thermal Comfort Fanger Model PMV'
922
- feature_report.reporting_periods[0].comfort_result[:hours_out_of_comfort_bounds_PMV] = hrsOutOfBounds
923
- elsif timeseries_name == 'Zone Thermal Comfort Fanger Model PPD'
924
- feature_report.reporting_periods[0].comfort_result[:hours_out_of_comfort_bounds_PPD] = hrsOutOfBounds
925
- end
926
-
927
- end
928
-
929
- end
930
-
931
- # increment key_cnt in new_keys loop
932
- key_cnt += 1
933
- end
934
- end
935
-
936
- # Add datime column
937
- datetimes = []
938
- # check what timeseries is available
939
- available_ts = sql_file.availableTimeSeries
940
-
941
- # get the timeseries for any of available timeseries
942
- # RK: code enhancement needed
943
- ts_d = sql_file.timeSeries(ann_env_pd.to_s, reporting_frequency.to_s, available_ts[2], '')
944
-
945
- timeseries_d = ts_d.get
946
- # get formated datetimes
947
- timeseries_d.dateTimes.each do |datetime|
948
- datetimes << format_datetime(datetime.to_s)
949
- end
950
- # insert datetimes to values
951
- values.insert(0, datetimes)
952
- # insert datetime header to names
953
- final_timeseries_names.insert(0, 'Datetime')
954
-
955
-
956
- # rubocop: enable Metrics/BlockLength
957
- runner.registerInfo("new final_timeseries_names size: #{final_timeseries_names.size}")
958
-
959
- # Save the 'default_feature_reports.csv' file
960
- File.open('default_feature_reports.csv', 'w') do |file|
961
- file.puts(final_timeseries_names.join(','))
962
- (0...n).each do |l|
963
- line = []
964
- values.each_index do |j|
965
- line << values[j][l]
966
- end
967
- file.puts(line.join(','))
968
- end
969
- end
970
-
971
- # closing the sql file
972
- sql_file.close
973
-
974
- ############################# Adding timeseries_csv info to json report and saving CSV ################################
975
- # add csv info to feature_report
976
- feature_report.timeseries_csv.path = File.join(Dir.pwd, 'default_feature_reports.csv')
977
- feature_report.timeseries_csv.first_report_datetime = '0'
978
- feature_report.timeseries_csv.column_names = final_timeseries_names
979
-
980
- ##### Save the 'default_feature_reports.json' file
981
-
982
- feature_report_hash = feature_report.to_hash
983
-
984
- File.open('default_feature_reports.json', 'w') do |f|
985
- f.puts JSON.pretty_generate(feature_report_hash)
986
- # make sure data is written to the disk one way or the other
987
- begin
988
- f.fsync
989
- rescue StandardError
990
- f.flush
991
- end
992
- end
993
-
994
- # reporting final condition
995
- runner.registerFinalCondition('Default Feature Reports generated successfully.')
996
-
997
- true
998
- # end the run method
999
- end
1000
- # end the measure
1001
- end
1002
- # rubocop:enable Metrics/AbcSize
1003
- # rubocop:enable Naming/MethodName
1004
-
1005
- # register the measure to be used by the application
1006
- DefaultFeatureReports.new.registerWithApplication