urbanopt-reporting 0.3.1 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -2
  3. data/docs/package-lock.json +3 -3
  4. data/docs/package.json +1 -1
  5. data/lib/measures/default_feature_reports/measure.rb +78 -32
  6. data/lib/measures/default_feature_reports/measure.xml +3 -3
  7. data/lib/measures/export_modelica_loads/LICENSE.md +27 -0
  8. data/lib/measures/export_modelica_loads/README.md +26 -0
  9. data/lib/measures/export_modelica_loads/README.md.erb +42 -0
  10. data/lib/measures/export_modelica_loads/docs/.gitkeep +0 -0
  11. data/lib/measures/export_modelica_loads/measure.rb +374 -0
  12. data/lib/measures/export_modelica_loads/measure.xml +92 -0
  13. data/lib/measures/export_modelica_loads/resources/report.html.in +13 -0
  14. data/lib/measures/export_time_series_modelica/LICENSE.md +1 -0
  15. data/lib/measures/export_time_series_modelica/README.md +59 -0
  16. data/lib/measures/export_time_series_modelica/README.md.erb +42 -0
  17. data/lib/measures/export_time_series_modelica/docs/.gitkeep +0 -0
  18. data/lib/measures/export_time_series_modelica/measure.rb +430 -0
  19. data/lib/measures/export_time_series_modelica/measure.xml +147 -0
  20. data/lib/measures/export_time_series_modelica/resources/os_lib_helper_methods.rb +399 -0
  21. data/lib/measures/export_time_series_modelica/resources/report.html.in +13 -0
  22. data/lib/urbanopt/reporting/default_reports/end_use.rb +38 -17
  23. data/lib/urbanopt/reporting/default_reports/feature_report.rb +52 -4
  24. data/lib/urbanopt/reporting/default_reports/reporting_period.rb +14 -2
  25. data/lib/urbanopt/reporting/default_reports/scenario_report.rb +10 -5
  26. data/lib/urbanopt/reporting/default_reports/schema/scenario_csv_columns.txt +11 -10
  27. data/lib/urbanopt/reporting/default_reports/schema/scenario_schema.json +27 -12
  28. data/lib/urbanopt/reporting/version.rb +1 -1
  29. data/urbanopt-reporting-gem.gemspec +1 -1
  30. metadata +19 -4
File without changes
@@ -0,0 +1,374 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ require 'erb'
37
+
38
+ # This measure is originally from https://github.com/urbanopt/DES_HVAC
39
+ # start the measure
40
+ class ExportModelicaLoads < OpenStudio::Measure::ReportingMeasure
41
+ # human readable name
42
+ def name
43
+ # Measure name should be the title case of the class name.
44
+ return 'Export Modelica Loads'
45
+ end
46
+
47
+ def description
48
+ return 'Use the results from the EnergyPlus simulation to generate a load file for use in Modelica. This will create a MOS and a CSV file of the heating, cooling, and hot water loads.'
49
+ end
50
+
51
+ def modeler_description
52
+ return ''
53
+ end
54
+
55
+ def log(str)
56
+ puts "#{Time.now}: #{str}"
57
+ end
58
+
59
+ def arguments(_model)
60
+ args = OpenStudio::Measure::OSArgumentVector.new
61
+
62
+ # this measure does not require any user arguments, return an empty list
63
+ return args
64
+ end
65
+
66
+ # return a vector of IdfObject's to request EnergyPlus objects needed by the run method
67
+ def energyPlusOutputRequests(runner, user_arguments)
68
+ super(runner, user_arguments)
69
+
70
+ result = OpenStudio::IdfObjectVector.new
71
+
72
+ # To use the built-in error checking we need the model...
73
+ # get the last model and sql file
74
+ model = runner.lastOpenStudioModel
75
+ if model.empty?
76
+ runner.registerError('Cannot find last model.')
77
+ return false
78
+ end
79
+ model = model.get
80
+
81
+ # use the built-in error checking
82
+ if !runner.validateUserArguments(arguments(model), user_arguments)
83
+ return false
84
+ end
85
+
86
+ result << OpenStudio::IdfObject.load('Output:Variable,,Site Mains Water Temperature,timestep;').get
87
+ result << OpenStudio::IdfObject.load('Output:Variable,,Site Outdoor Air Drybulb Temperature,timestep;').get
88
+ result << OpenStudio::IdfObject.load('Output:Variable,,Site Outdoor Air Relative Humidity,timestep;').get
89
+ result << OpenStudio::IdfObject.load('Output:Meter,Cooling:Electricity,timestep;').get
90
+ result << OpenStudio::IdfObject.load('Output:Meter,Heating:Electricity,timestep;').get
91
+ result << OpenStudio::IdfObject.load('Output:Meter,Heating:NaturalGas,timestep;').get
92
+ result << OpenStudio::IdfObject.load('Output:Meter,InteriorLights:Electricity,timestep;').get
93
+ result << OpenStudio::IdfObject.load('Output:Meter,Fans:Electricity,timestep;').get
94
+ result << OpenStudio::IdfObject.load('Output:Meter,InteriorEquipment:Electricity,timestep;').get # Joules
95
+ result << OpenStudio::IdfObject.load('Output:Meter,ExteriorLighting:Electricity,timestep;').get # Joules
96
+ result << OpenStudio::IdfObject.load('Output:Meter,Electricity:Facility,timestep;').get # Joules
97
+ result << OpenStudio::IdfObject.load('Output:Meter,Electricity:Facility,timestep;').get # #Using this for data at timestep interval
98
+ result << OpenStudio::IdfObject.load('Output:Meter,Gas:Facility,timestep;').get # Joules
99
+ result << OpenStudio::IdfObject.load('Output:Meter,Heating:EnergyTransfer,timestep;').get # Joules
100
+ result << OpenStudio::IdfObject.load('Output:Meter,WaterSystems:EnergyTransfer,timestep;').get # Joules
101
+ # these variables are used for the modelica export.
102
+ result << OpenStudio::IdfObject.load('Output:Variable,*,Zone Predicted Sensible Load to Setpoint Heat Transfer Rate,timestep;').get # watts according to e+
103
+ result << OpenStudio::IdfObject.load('Output:Variable,*,Water Heater Total Demand Heat Transfer Rate,timestep;').get # Watts
104
+
105
+ return result
106
+ end
107
+
108
+ def extract_timeseries_into_matrix(sqlfile, data, variable_name, key_value = nil, default_if_empty = 0, timestep)
109
+ log "Executing query for #{variable_name}"
110
+ column_name = variable_name
111
+ if key_value
112
+ # ts = sqlfile.timeSeries('RUN PERIOD 1', 'Hourly', variable_name, key_value)
113
+ ts = sqlfile.timeSeries('RUN PERIOD 1', 'Zone Timestep', variable_name, key_value)
114
+ column_name += "_#{key_value}"
115
+ else
116
+ # ts = sqlfile.timeSeries('RUN PERIOD 1', 'Hourly', variable_name)
117
+ ts = sqlfile.timeSeries('RUN PERIOD 1', 'Zone Timestep', variable_name)
118
+ end
119
+ log 'Iterating over timeseries'
120
+ column = [column_name.delete(':').delete(' ')] # Set the header of the data to the variable name, removing : and spaces
121
+
122
+ if ts.empty?
123
+ log "No time series for #{variable_name}:#{key_value}... defaulting to #{default_if_empty}"
124
+ # needs to be data.size-1 since the column name is already stored above (+=)
125
+ column += [default_if_empty] * (data.size - 1)
126
+ else
127
+ ts = ts.get if ts.respond_to?(:get)
128
+ ts = ts.first if ts.respond_to?(:first)
129
+
130
+ start = Time.now
131
+ # Iterating in OpenStudio can take up to 60 seconds with 10min data. The quick_proc takes 0.03 seconds.
132
+ # for i in 0..ts.values.size - 1
133
+ # log "... at #{i}" if i % 10000 == 0
134
+ # column << ts.values[i]
135
+ # end
136
+
137
+ quick_proc = ts.values.to_s.split(',')
138
+
139
+ # the first and last have some cleanup items because of the Vector method
140
+ quick_proc[0] = quick_proc[0].gsub(/^.*\(/, '')
141
+ quick_proc[-1] = quick_proc[-1].delete(')')
142
+ column += quick_proc
143
+
144
+ log "Took #{Time.now - start} to iterate"
145
+ end
146
+
147
+ log 'Appending column to data'
148
+
149
+ # append the data to the end of the rows
150
+ if column.size == data.size
151
+ data.each_index do |index|
152
+ data[index] << column[index]
153
+ end
154
+ end
155
+
156
+ log "Finished extracting #{variable_name}"
157
+ end
158
+
159
+ def create_new_variable_sum(data, new_var_name, include_str, options = nil)
160
+ var_info = {
161
+ name: new_var_name,
162
+ var_indexes: []
163
+ }
164
+ data.each_with_index do |row, index|
165
+ if index.zero?
166
+ # Get the index of the columns to add
167
+ row.each do |c|
168
+ if c.include? include_str
169
+ var_info[:var_indexes] << row.index(c)
170
+ end
171
+ end
172
+
173
+ # add the new var to the header row
174
+ data[0] << var_info[:name]
175
+ else
176
+ # sum the values
177
+ sum = 0
178
+ var_info[:var_indexes].each do |var|
179
+ temp_v = row[var].to_f
180
+ if options.nil?
181
+ sum += temp_v
182
+ elsif options[:positive_only] && temp_v > 0
183
+ sum += temp_v
184
+ elsif options[:negative_only] && temp_v < 0
185
+ sum += temp_v
186
+ end
187
+ end
188
+
189
+ # Also round the data here, because we don't need 10 decimals
190
+ data[index] << sum.round(1)
191
+ end
192
+ end
193
+ end
194
+
195
+ def run(runner, user_arguments)
196
+ super(runner, user_arguments)
197
+
198
+ # get the last model and sql file
199
+ model = runner.lastOpenStudioModel
200
+ if model.empty?
201
+ runner.registerError('Cannot find last model.')
202
+ return false
203
+ end
204
+ model = model.get
205
+
206
+ # use the built-in error checking
207
+ return false unless runner.validateUserArguments(arguments(model), user_arguments)
208
+
209
+ # get the last model and sql file
210
+ model = runner.lastOpenStudioModel
211
+ if model.empty?
212
+ runner.registerError('Cannot find last model.')
213
+ return false
214
+ end
215
+ model = model.get
216
+
217
+ timesteps_per_hour = model.getTimestep.numberOfTimestepsPerHour.to_i
218
+ timestep = 60 / timesteps_per_hour # timestep in minutes
219
+
220
+ sqlFile = runner.lastEnergyPlusSqlFile
221
+ if sqlFile.empty?
222
+ runner.registerError('Cannot find last sql file.')
223
+ return false
224
+ end
225
+ sqlFile = sqlFile.get
226
+ model.setSqlFile(sqlFile)
227
+
228
+ # create a new csv with the values and save to the reports directory.
229
+ # assumptions:
230
+ # - all the variables exist
231
+ # - data are the same length
232
+
233
+ # initialize the rows with the header
234
+ log 'Starting to process Timeseries data'
235
+ # Initial header row
236
+ rows = [
237
+ ['Date Time', 'Month', 'Day', 'Day of Week', 'Hour', 'Minute', 'SecondsFromStart']
238
+ ]
239
+
240
+ # just grab one of the variables to get the date/time stamps
241
+ attribute_name = 'Electricity:Facility'
242
+ ts = sqlFile.timeSeries('RUN PERIOD 1', 'Zone Timestep', attribute_name)
243
+ if ts.empty?
244
+ runner.registerError("This feature does not have the attribute '#{attribute_name}' to enable this measure to work." \
245
+ 'To resolve, simulate a building with electricity or remove this measure from your workflow.')
246
+ else
247
+ ts = ts.first
248
+ dt_base = nil
249
+ # Save off the date time values
250
+ ts.dateTimes.each_with_index do |dt, index|
251
+ dt_base = DateTime.parse(dt.to_s) if dt_base.nil?
252
+ dt_current = DateTime.parse(dt.to_s)
253
+ rows << [
254
+ DateTime.parse(dt.to_s).strftime('%m/%d/%Y %H:%M'),
255
+ dt.date.monthOfYear.value,
256
+ dt.date.dayOfMonth,
257
+ dt.date.dayOfWeek.value,
258
+ dt.time.hours,
259
+ dt.time.minutes,
260
+ dt_current.to_time.to_i - dt_base.to_time.to_i + timestep * 60
261
+ ]
262
+ end
263
+ end
264
+
265
+ # add in the other variables by columns -- should really pull this from the report variables defined above
266
+ extract_timeseries_into_matrix(sqlFile, rows, 'Site Outdoor Air Drybulb Temperature', 'Environment', 0, timestep)
267
+ extract_timeseries_into_matrix(sqlFile, rows, 'Site Outdoor Air Relative Humidity', 'Environment', 0, timestep)
268
+ extract_timeseries_into_matrix(sqlFile, rows, 'Heating:Electricity', nil, 0, timestep)
269
+ extract_timeseries_into_matrix(sqlFile, rows, 'Heating:NaturalGas', nil, 0, timestep)
270
+ extract_timeseries_into_matrix(sqlFile, rows, 'Cooling:Electricity', nil, 0, timestep)
271
+ extract_timeseries_into_matrix(sqlFile, rows, 'Electricity:Facility', nil, 0, timestep)
272
+ extract_timeseries_into_matrix(sqlFile, rows, 'Gas:Facility', nil, 0, timestep)
273
+ extract_timeseries_into_matrix(sqlFile, rows, 'Heating:EnergyTransfer', nil, 0, timestep)
274
+ extract_timeseries_into_matrix(sqlFile, rows, 'WaterSystems:EnergyTransfer', nil, 0, timestep)
275
+
276
+ # get all zones and save the names for later use in aggregation.
277
+ tz_names = []
278
+ model.getThermalZones.each do |tz|
279
+ tz_names << tz.name.get if tz.name.is_initialized
280
+ extract_timeseries_into_matrix(sqlFile, rows, 'Zone Predicted Sensible Load to Setpoint Heat Transfer Rate', tz_names.last, 0, timestep)
281
+ extract_timeseries_into_matrix(sqlFile, rows, 'Water Heater Heating Rate', tz_names.last, 0, timestep)
282
+ end
283
+
284
+ # sum up a couple of the columns and create a new columns
285
+ create_new_variable_sum(rows, 'TotalSensibleLoad', 'ZonePredictedSensibleLoadtoSetpointHeatTransferRate')
286
+ create_new_variable_sum(rows, 'TotalCoolingSensibleLoad', 'ZonePredictedSensibleLoadtoSetpointHeatTransferRate', negative_only: true)
287
+ create_new_variable_sum(rows, 'TotalHeatingSensibleLoad', 'ZonePredictedSensibleLoadtoSetpointHeatTransferRate', positive_only: true)
288
+ create_new_variable_sum(rows, 'TotalWaterHeating', 'WaterHeaterHeatingRate')
289
+
290
+ # convert this to CSV object
291
+ File.open('./building_loads.csv', 'w') do |f|
292
+ rows.each do |row|
293
+ f << row.join(',') << "\n"
294
+ end
295
+ end
296
+
297
+ # covert the row data into the format needed by modelica
298
+ modelica_data = [['seconds', 'cooling', 'heating', 'waterheating']]
299
+ seconds_index = nil
300
+ total_water_heating_index = nil
301
+ total_cooling_sensible_index = nil
302
+ total_heating_sensible_index = nil
303
+ peak_cooling = 0
304
+ peak_heating = 0
305
+ peak_water_heating = 0
306
+ rows.each_with_index do |row, index|
307
+ if index.zero?
308
+ seconds_index = row.index('SecondsFromStart')
309
+ total_cooling_sensible_index = row.index('TotalCoolingSensibleLoad')
310
+ total_heating_sensible_index = row.index('TotalHeatingSensibleLoad')
311
+ total_water_heating_index = row.index('TotalWaterHeating')
312
+ else
313
+ new_data = [
314
+ row[seconds_index],
315
+ row[total_cooling_sensible_index],
316
+ row[total_heating_sensible_index],
317
+ row[total_water_heating_index]
318
+ ]
319
+
320
+ modelica_data << new_data
321
+
322
+ # store the peaks
323
+ peak_cooling = row[total_cooling_sensible_index] if row[total_cooling_sensible_index] < peak_cooling
324
+ peak_heating = row[total_heating_sensible_index] if row[total_heating_sensible_index] > peak_heating
325
+ peak_water_heating = row[total_water_heating_index] if row[total_water_heating_index] > peak_water_heating
326
+ end
327
+ end
328
+
329
+ File.open('./modelica.mos', 'w') do |f|
330
+ f << "#1\n"
331
+ f << "#Heating and Cooling Model loads from OpenStudio Prototype Buildings\n"
332
+ f << "# Building Type: {{BUILDINGTYPE}}\n"
333
+ f << "# Climate Zone: {{CLIMATEZONE}}\n"
334
+ f << "# Vintage: {{VINTAGE}}\n"
335
+ f << "# Simulation ID (for debugging): {{SIMID}}\n"
336
+ f << "# URL: https://github.com/urbanopt/openstudio-prototype-loads\n"
337
+ f << "\n"
338
+ f << "#First column: Seconds in the year (loads are hourly)\n"
339
+ f << "#Second column: cooling loads in Watts (as negative numbers).\n"
340
+ f << "#Third column: space heating loads in Watts\n"
341
+ f << "#Fourth column: water heating in Watts\n"
342
+ f << "\n"
343
+ f << "#Peak space cooling load = #{peak_cooling} Watts\n"
344
+ f << "#Peak space heating load = #{peak_heating} Watts\n"
345
+ f << "#Peak water heating load = #{peak_water_heating} Watts\n"
346
+ f << "double tab1(8760,4)\n"
347
+ modelica_data.each_with_index do |row, index|
348
+ next if index.zero?
349
+ f << row.join(';') << "\n"
350
+ end
351
+ end
352
+
353
+ # Find the total runtime for energyplus and save it into a registerValue
354
+ total_time = -999
355
+ location_of_file = ['../eplusout.end', './run/eplusout.end']
356
+ first_index = location_of_file.map { |f| File.exist?(f) }.index(true)
357
+ if first_index
358
+ match = File.read(location_of_file[first_index]).to_s.match(/Elapsed.Time=(.*)hr(.*)min(.*)sec/)
359
+ total_time = match[1].to_i * 3600 + match[2].to_i * 60 + match[3].to_f
360
+ end
361
+
362
+ runner.registerValue('energyplus_runtime', total_time, 'sec')
363
+ runner.registerValue('peak_cooling_load', peak_cooling, 'W')
364
+ runner.registerValue('peak_heating_load', peak_heating, 'W')
365
+ runner.registerValue('peak_water_heating', peak_water_heating, 'W')
366
+
367
+ return true
368
+ ensure
369
+ sqlFile.close if sqlFile
370
+ end
371
+ end
372
+
373
+ # register the measure to be used by the application
374
+ ExportModelicaLoads.new.registerWithApplication
@@ -0,0 +1,92 @@
1
+ <?xml version="1.0"?>
2
+ <measure>
3
+ <schema_version>3.0</schema_version>
4
+ <name>export_modelica_loads</name>
5
+ <uid>7051db01-2e55-4223-b5b5-fee615b68dd0</uid>
6
+ <version_id>7782f28c-19e6-449d-8cf3-e3f915550ebd</version_id>
7
+ <version_modified>20201203T151454Z</version_modified>
8
+ <xml_checksum>2C8A3EEF</xml_checksum>
9
+ <class_name>ExportModelicaLoads</class_name>
10
+ <display_name>Export Modelica Loads</display_name>
11
+ <description>Use the results from the EnergyPlus simulation to generate a load file for use in Modelica. This will create a MOS and a CSV file of the heating, cooling, and hot water loads.</description>
12
+ <modeler_description></modeler_description>
13
+ <arguments />
14
+ <outputs />
15
+ <provenances />
16
+ <tags>
17
+ <tag>Reporting.QAQC</tag>
18
+ </tags>
19
+ <attributes>
20
+ <attribute>
21
+ <name>Measure Type</name>
22
+ <value>ReportingMeasure</value>
23
+ <datatype>string</datatype>
24
+ </attribute>
25
+ <attribute>
26
+ <name>Uses SketchUp API</name>
27
+ <value>false</value>
28
+ <datatype>boolean</datatype>
29
+ </attribute>
30
+ </attributes>
31
+ <files>
32
+ <file>
33
+ <filename>README.md.erb</filename>
34
+ <filetype>erb</filetype>
35
+ <usage_type>readmeerb</usage_type>
36
+ <checksum>703C9964</checksum>
37
+ </file>
38
+ <file>
39
+ <filename>.gitkeep</filename>
40
+ <filetype>gitkeep</filetype>
41
+ <usage_type>doc</usage_type>
42
+ <checksum>00000000</checksum>
43
+ </file>
44
+ <file>
45
+ <filename>export_modelica_loads_test.rb</filename>
46
+ <filetype>rb</filetype>
47
+ <usage_type>test</usage_type>
48
+ <checksum>83EC81AC</checksum>
49
+ </file>
50
+ <file>
51
+ <filename>example_model.osm</filename>
52
+ <filetype>osm</filetype>
53
+ <usage_type>test</usage_type>
54
+ <checksum>39B6E26C</checksum>
55
+ </file>
56
+ <file>
57
+ <filename>USA_CO_Golden-NREL.724666_TMY3.epw</filename>
58
+ <filetype>epw</filetype>
59
+ <usage_type>test</usage_type>
60
+ <checksum>BDF687C1</checksum>
61
+ </file>
62
+ <file>
63
+ <filename>report.html.in</filename>
64
+ <filetype>in</filetype>
65
+ <usage_type>resource</usage_type>
66
+ <checksum>3F69E3FB</checksum>
67
+ </file>
68
+ <file>
69
+ <filename>LICENSE.md</filename>
70
+ <filetype>md</filetype>
71
+ <usage_type>license</usage_type>
72
+ <checksum>E0468DD6</checksum>
73
+ </file>
74
+ <file>
75
+ <filename>README.md</filename>
76
+ <filetype>md</filetype>
77
+ <usage_type>readme</usage_type>
78
+ <checksum>544E6A47</checksum>
79
+ </file>
80
+ <file>
81
+ <version>
82
+ <software_program>OpenStudio</software_program>
83
+ <identifier>3.0.1</identifier>
84
+ <min_compatible>3.0.1</min_compatible>
85
+ </version>
86
+ <filename>measure.rb</filename>
87
+ <filetype>rb</filetype>
88
+ <usage_type>script</usage_type>
89
+ <checksum>AE19F661</checksum>
90
+ </file>
91
+ </files>
92
+ </measure>