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