urbanopt-scenario 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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +25 -0
  3. data/.rdoc_options +36 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/.travis.yml +23 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +42 -0
  9. data/Jenkinsfile +10 -0
  10. data/LICENSE.md +27 -0
  11. data/RDOC_MAIN.md +39 -0
  12. data/README.md +39 -0
  13. data/Rakefile +51 -0
  14. data/deploy_docs.sh +5 -0
  15. data/doc_templates/LICENSE.md +27 -0
  16. data/doc_templates/README.md.erb +42 -0
  17. data/doc_templates/copyright_erb.txt +31 -0
  18. data/doc_templates/copyright_js.txt +4 -0
  19. data/doc_templates/copyright_ruby.txt +29 -0
  20. data/docs/.gitignore +3 -0
  21. data/docs/.vuepress/components/InnerJsonSchema.vue +84 -0
  22. data/docs/.vuepress/components/JsonSchema.vue +12 -0
  23. data/docs/.vuepress/components/ScenarioSchema.vue +12 -0
  24. data/docs/.vuepress/components/StaticLink.vue +8 -0
  25. data/docs/.vuepress/config.js +15 -0
  26. data/docs/.vuepress/highlight.js +8 -0
  27. data/docs/.vuepress/public/custom_rdoc_styles.css +74 -0
  28. data/docs/.vuepress/utils.js +17 -0
  29. data/docs/README.md +39 -0
  30. data/docs/package-lock.json +11791 -0
  31. data/docs/package.json +22 -0
  32. data/docs/schemas/scenario-schema.md +3 -0
  33. data/lib/measures/.rubocop.yml +5 -0
  34. data/lib/measures/default_feature_reports/LICENSE.md +27 -0
  35. data/lib/measures/default_feature_reports/README.md +56 -0
  36. data/lib/measures/default_feature_reports/README.md.erb +42 -0
  37. data/lib/measures/default_feature_reports/measure.rb +731 -0
  38. data/lib/measures/default_feature_reports/measure.xml +139 -0
  39. data/lib/measures/default_feature_reports/tests/USA_CO_Golden-NREL.724666_TMY3.epw +8768 -0
  40. data/lib/measures/default_feature_reports/tests/default_feature_reports_test.rb +238 -0
  41. data/lib/measures/default_feature_reports/tests/example_model.osm +4378 -0
  42. data/lib/urbanopt-scenario.rb +31 -0
  43. data/lib/urbanopt/scenario.rb +45 -0
  44. data/lib/urbanopt/scenario/default_reports.rb +40 -0
  45. data/lib/urbanopt/scenario/default_reports/construction_cost.rb +169 -0
  46. data/lib/urbanopt/scenario/default_reports/date.rb +97 -0
  47. data/lib/urbanopt/scenario/default_reports/end_use.rb +159 -0
  48. data/lib/urbanopt/scenario/default_reports/end_uses.rb +140 -0
  49. data/lib/urbanopt/scenario/default_reports/feature_report.rb +207 -0
  50. data/lib/urbanopt/scenario/default_reports/location.rb +99 -0
  51. data/lib/urbanopt/scenario/default_reports/logger.rb +44 -0
  52. data/lib/urbanopt/scenario/default_reports/program.rb +261 -0
  53. data/lib/urbanopt/scenario/default_reports/reporting_period.rb +298 -0
  54. data/lib/urbanopt/scenario/default_reports/scenario_report.rb +276 -0
  55. data/lib/urbanopt/scenario/default_reports/schema/README.md +33 -0
  56. data/lib/urbanopt/scenario/default_reports/schema/scenario_csv_columns.txt +13 -0
  57. data/lib/urbanopt/scenario/default_reports/schema/scenario_schema.json +742 -0
  58. data/lib/urbanopt/scenario/default_reports/timeseries_csv.rb +231 -0
  59. data/lib/urbanopt/scenario/default_reports/validator.rb +97 -0
  60. data/lib/urbanopt/scenario/extension.rb +63 -0
  61. data/lib/urbanopt/scenario/logger.rb +42 -0
  62. data/lib/urbanopt/scenario/scenario_base.rb +79 -0
  63. data/lib/urbanopt/scenario/scenario_csv.rb +122 -0
  64. data/lib/urbanopt/scenario/scenario_datapoint_base.rb +162 -0
  65. data/lib/urbanopt/scenario/scenario_post_processor_base.rb +69 -0
  66. data/lib/urbanopt/scenario/scenario_post_processor_default.rb +97 -0
  67. data/lib/urbanopt/scenario/scenario_runner_base.rb +63 -0
  68. data/lib/urbanopt/scenario/scenario_runner_osw.rb +158 -0
  69. data/lib/urbanopt/scenario/simulation_dir_base.rb +90 -0
  70. data/lib/urbanopt/scenario/simulation_dir_osw.rb +261 -0
  71. data/lib/urbanopt/scenario/simulation_mapper_base.rb +47 -0
  72. data/lib/urbanopt/scenario/version.rb +35 -0
  73. data/urbanopt-scenario-gem.gemspec +36 -0
  74. metadata +227 -0
@@ -0,0 +1,261 @@
1
+ # *********************************************************************************
2
+ # URBANopt, Copyright (c) 2019, Alliance for Sustainable Energy, LLC, and other
3
+ # contributors. All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice, this list
9
+ # of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright notice, this
12
+ # list of conditions and the following disclaimer in the documentation and/or other
13
+ # materials provided with the distribution.
14
+ #
15
+ # Neither the name of the copyright holder nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without specific
17
+ # prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ # *********************************************************************************
30
+
31
+ require 'urbanopt/scenario/default_reports/validator'
32
+ require 'json-schema'
33
+ require 'json'
34
+
35
+ module URBANopt
36
+ module Scenario
37
+ module DefaultReports
38
+ ##
39
+ # Program includes all building program related information.
40
+ ##
41
+ class Program
42
+ attr_accessor :site_area, :floor_area, :conditioned_area, :unconditioned_area, :footprint_area, :maximum_roof_height,
43
+ :maximum_number_of_stories, :maximum_number_of_stories_above_ground, :parking_area, :number_of_parking_spaces,
44
+ :number_of_parking_spaces_charging, :parking_footprint_area, :maximum_parking_height, :maximum_number_of_parking_stories,
45
+ :maximum_number_of_parking_stories_above_ground, :number_of_residential_units, :building_types, :building_type, :maximum_occupancy,
46
+ :area, :window_area, :north_window_area, :south_window_area, :east_window_area, :west_window_area, :wall_area, :roof_area, :equipment_roof_area,
47
+ :photovoltaic_roof_area, :available_roof_area, :total_roof_area, :orientation, :aspect_ratio # :nodoc:
48
+ # Program class intialize building program attributes: +:site_area+ , +:floor_area+ , +:conditioned_area+ , +:unconditioned_area+ ,
49
+ # +:footprint_area+ , +:maximum_roof_height, +:maximum_number_of_stories+ , +:maximum_number_of_stories_above_ground+ , +:parking_area+ ,
50
+ # +:number_of_parking_spaces+ , +:number_of_parking_spaces_charging+ , +:parking_footprint_area+ , +:maximum_parking_height+ , +:maximum_number_of_parking_stories+ ,
51
+ # +:maximum_number_of_parking_stories_above_ground+ , +:number_of_residential_units+ , +:building_types+ , +:building_type+ , +:maximum_occupancy+ ,
52
+ # +:area+ , +:window_area+ , +:north_window_area+ , +:south_window_area+ , +:east_window_area+ , +:west_window_area+ , +:wall_area+ , +:roof_area+ ,
53
+ # +:equipment_roof_area+ , +:photovoltaic_roof_area+ , +:available_roof_area+ , +:total_roof_area+ , +:orientation+ , +:aspect_ratio+
54
+ ##
55
+ # [parameters:]
56
+ # +hash+ - _Hash_ - A hash which may contain a deserialized program.
57
+ ##
58
+ def initialize(hash = {})
59
+ hash.delete_if { |k, v| v.nil? }
60
+ hash = defaults.merge(hash)
61
+
62
+ @site_area = hash[:site_area]
63
+ @floor_area = hash[:floor_area]
64
+ @conditioned_area = hash[:conditioned_area]
65
+ @unconditioned_area = hash[:unconditioned_area]
66
+ @footprint_area = hash[:footprint_area]
67
+ @maximum_roof_height = hash[:maximum_roof_height]
68
+ @maximum_number_of_stories = hash[:maximum_number_of_stories]
69
+ @maximum_number_of_stories_above_ground = hash[:maximum_number_of_stories_above_ground]
70
+ @parking_area = hash[:parking_area]
71
+ @number_of_parking_spaces = hash[:number_of_parking_spaces]
72
+ @number_of_parking_spaces_charging = hash[:number_of_parking_spaces_charging]
73
+ @parking_footprint_area = hash[:parking_footprint_area]
74
+ @maximum_parking_height = hash[:maximum_parking_height]
75
+ @maximum_number_of_parking_stories = hash[:maximum_number_of_parking_stories]
76
+ @maximum_number_of_parking_stories_above_ground = hash[:maximum_number_of_parking_stories_above_ground]
77
+ @number_of_residential_units = hash[:number_of_residential_units]
78
+ @building_types = hash[:building_types]
79
+ @window_area = hash[:window_area]
80
+ @wall_area = hash[:wall_area]
81
+ @roof_area = hash[:roof_area]
82
+ @orientation = hash[:orientation]
83
+ @aspect_ratio = hash[:aspect_ratio]
84
+
85
+ # initialize class variables @@validator and @@schema
86
+ @@validator ||= Validator.new
87
+ @@schema ||= @@validator.schema
88
+ end
89
+
90
+ ##
91
+ # Assigns default values if values do not exist.
92
+ ##
93
+ def defaults
94
+ hash = {}
95
+ hash[:site_area] = nil
96
+ hash[:floor_area] = nil
97
+ hash[:conditioned_area] = nil
98
+ hash[:unconditioned_area] = nil
99
+ hash[:footprint_area] = nil
100
+ hash[:maximum_roof_height] = nil
101
+ hash[:maximum_number_of_stories] = nil
102
+ hash[:maximum_number_of_stories_above_ground] = nil
103
+ hash[:parking_area] = nil
104
+ hash[:number_of_parking_spaces] = nil
105
+ hash[:number_of_parking_spaces_charging] = nil
106
+ hash[:parking_footprint_area] = nil
107
+ hash[:maximum_parking_height] = nil
108
+ hash[:maximum_number_of_parking_stories] = nil
109
+ hash[:maximum_number_of_parking_stories_above_ground] = nil
110
+ hash[:number_of_residential_units] = nil
111
+ hash[:building_types] = [{ building_type: nil, maximum_occupancy: nil, floor_area: nil }]
112
+ hash[:window_area] = { north_window_area: nil, south_window_area: nil, east_window_area: nil, west_window_area: nil, total_window_area: nil }
113
+ hash[:wall_area] = { north_wall_area: nil, south_wall_area: nil, east_wall_area: nil, west_wall_area: nil, total_wall_area: nil }
114
+ hash[:roof_area] = { equipment_roof_area: nil, photovoltaic_roof_area: nil, available_roof_area: nil, total_roof_area: nil }
115
+ hash[:orientation] = nil
116
+ hash[:aspect_ratio] = nil
117
+ return hash
118
+ end
119
+
120
+ ##
121
+ # Convert to a Hash equivalent for JSON serialization.
122
+ ##
123
+ # - Exclude attributes with nil values.
124
+ # - Validate program hash properties against schema.
125
+ ##
126
+ def to_hash
127
+ result = {}
128
+ result[:site_area] = @site_area if @site_area
129
+ result[:floor_area] = @floor_area if @floor_area
130
+ result[:conditioned_area] = @conditioned_area if @conditioned_area
131
+ result[:unconditioned_area] = @unconditioned_area if @unconditioned_area
132
+ result[:footprint_area] = @footprint_area if @footprint_area
133
+ result[:maximum_roof_height] = @maximum_roof_height if @maximum_roof_height
134
+ result[:maximum_number_of_stories] = @maximum_number_of_stories if @maximum_number_of_stories
135
+ result[:maximum_number_of_stories_above_ground] = @maximum_number_of_stories_above_ground if @maximum_number_of_parking_stories_above_ground
136
+ result[:parking_area] = @parking_area if @parking_area
137
+ result[:number_of_parking_spaces] = @number_of_parking_spaces if @number_of_parking_spaces
138
+ result[:number_of_parking_spaces_charging] = @number_of_parking_spaces_charging if @number_of_parking_spaces_charging
139
+ result[:parking_footprint_area] = @parking_footprint_area if @parking_footprint_area
140
+ result[:maximum_parking_height] = @maximum_parking_height if @maximum_parking_height
141
+ result[:maximum_number_of_parking_stories] = @maximum_number_of_parking_stories if @maximum_number_of_parking_stories
142
+ result[:maximum_number_of_parking_stories_above_ground] = @maximum_number_of_parking_stories_above_ground if @maximum_number_of_parking_stories_above_ground
143
+ result[:number_of_residential_units] = @number_of_residential_units if @number_of_residential_units
144
+
145
+ if @building_types.any?
146
+ result[:building_types] = @building_types
147
+ @building_types.each do |bt|
148
+ bt.delete_if { |k, v| v.nil? } if bt
149
+ end
150
+ end
151
+
152
+ # result[:window_area] = @window_area if @window_area
153
+ window_area_hash = @window_area if @window_area
154
+ window_area_hash.delete_if { |k, v| v.nil? }
155
+ result[:window_area] = window_area_hash if @window_area
156
+
157
+ # result[:wall_area] = @wall_area if @wall_area
158
+ wall_area_hash = @wall_area if @wall_area
159
+ wall_area_hash.delete_if { |k, v| v.nil? }
160
+ result[:wall_area] = wall_area_hash if @wall_area
161
+
162
+ # result[:roof_area] = @roof_area if @roof_area
163
+ roof_area_hash = @roof_area if @roof_area
164
+ roof_area_hash.delete_if { |k, v| v.nil? }
165
+ result[:roof_area] = roof_area_hash if @roof_area
166
+
167
+ result[:orientation] = @orientation if @orientation
168
+ result[:aspect_ratio] = @aspect_ratio if @aspect_ratio
169
+
170
+ # validate program properties against schema
171
+ if @@validator.validate(@@schema[:definitions][:Program][:properties], result).any?
172
+ raise "program properties does not match schema: #{@@validator.validate(@@schema[:definitions][:Program][:properties], result)}"
173
+ end
174
+
175
+ return result
176
+ end
177
+
178
+ ##
179
+ # Return the maximum value from +existing_value+ and +new_value+.
180
+ ##
181
+ # [parameters:]
182
+ # +existing_value+ - _Float_ - A value corresponding to a Program attribute.
183
+ ##
184
+ # +new_value+ - _Float_ - A value corresponding to a Program attribute.
185
+ ##
186
+ def max_value(existing_value, new_value)
187
+ if existing_value && new_value
188
+ [existing_value, new_value].max
189
+ elsif new_value
190
+ existing_value = new_value
191
+ end
192
+ return existing_value
193
+ end
194
+
195
+ ##
196
+ # Adds up +existing_value+ and +new_values+ if not nill.
197
+ ##
198
+ # [parameters:]
199
+ # +existing_value+ - _Float_ - A value corresponding to a Program attribute.
200
+ ##
201
+ # +new_value+ - _Float_ - A value corresponding to a Program attribute.
202
+ ##
203
+ def add_values(existing_value, new_value)
204
+ if existing_value && new_value
205
+ existing_value += new_value
206
+ elsif new_value
207
+ existing_value = new_value
208
+ end
209
+ return existing_value
210
+ end
211
+
212
+ ##
213
+ # Merges program objects to each other by summing up values or taking the maximum value of the attributes.
214
+ ##
215
+ # [parameters:]
216
+ # +other+ - _Program_ - An object of Program class.
217
+ ##
218
+ # rubocop:disable Metrics/AbcSize # :nodoc:
219
+ def add_program(other)
220
+ @site_area = add_values(@site_area, other.site_area)
221
+
222
+ @floor_area = add_values(@floor_area, other.floor_area)
223
+ @conditioned_area = add_values(@conditioned_area, other.conditioned_area)
224
+ @unconditioned_area = add_values(@unconditioned_area, other.unconditioned_area)
225
+ @footprint_area = add_values(@footprint_area, other.footprint_area)
226
+ @maximum_roof_height = max_value(@maximum_roof_height, other.maximum_roof_height)
227
+ @maximum_number_of_stories = max_value(@maximum_number_of_stories, other.maximum_number_of_stories)
228
+ @maximum_number_of_stories_above_ground = max_value(@maximum_number_of_stories_above_ground, other.maximum_number_of_stories_above_ground)
229
+ @parking_area = add_values(@parking_area, other.parking_area)
230
+ @number_of_parking_spaces = add_values(@number_of_parking_spaces, other.number_of_parking_spaces)
231
+ @number_of_parking_spaces_charging = add_values(@number_of_parking_spaces_charging, other.number_of_parking_spaces_charging)
232
+ @parking_footprint_area = add_values(@parkig_footprint_area, other.parking_footprint_area)
233
+ @maximum_parking_height = max_value(@maximum_parking_height, other.maximum_parking_height)
234
+ @maximum_number_of_parking_stories = max_value(@maximum_number_of_parking_stories, other.maximum_number_of_parking_stories)
235
+ @maximum_number_of_parking_stories_above_ground = max_value(maximum_number_of_parking_stories_above_ground, other.maximum_number_of_parking_stories_above_ground)
236
+ @number_of_residential_units = add_values(@number_of_residential_units, other.number_of_residential_units)
237
+
238
+ @building_types = other.building_types
239
+
240
+ @window_area[:north_window_area] = add_values(@window_area[:north_window_area], other.window_area[:north_window_area])
241
+ @window_area[:south_window_area] = add_values(@window_area[:south_window_area], other.window_area[:south_window_area])
242
+ @window_area[:east_window_area] = add_values(@window_area[:east_window_area], other.window_area[:east_window_area])
243
+ @window_area[:west_window_area] = add_values(@window_area[:west_window_area], other.window_area[:west_window_area])
244
+ @window_area[:total_window_area] = add_values(@window_area[:total_window_area], other.window_area[:total_window_area])
245
+
246
+ @wall_area[:north_wall_area] = add_values(@wall_area[:north_wall_area], other.wall_area[:north_wall_area])
247
+ @wall_area[:south_wall_area] = add_values(@wall_area[:south_wall_area], other.wall_area[:south_wall_area])
248
+ @wall_area[:east_wall_area] = add_values(@wall_area[:east_wall_area], other.wall_area[:east_wall_area])
249
+ @wall_area[:west_wall_area] = add_values(@wall_area[:west_wall_area], other.wall_area[:west_wall_area])
250
+ @wall_area[:total_wall_area] = add_values(@wall_area[:total_wall_area], other.wall_area[:total_wall_area])
251
+
252
+ @roof_area[:equipment_roof_area] = add_values(@roof_area[:equipment_roof_area], other.roof_area[:equipment_roof_area])
253
+ @roof_area[:photovoltaic_roof_area] = add_values(@roof_area[:photovoltaic_roof_area], other.roof_area[:photovoltaic_roof_area])
254
+ @roof_area[:available_roof_area] = add_values(@roof_area[:available_roof_area], other.roof_area[:available_roof_area])
255
+ @roof_area[:total_roof_area] = add_values(@roof_area[:total_roof_area], other.roof_area[:total_roof_area])
256
+ end
257
+ # rubocop:enable Metrics/AbcSize
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,298 @@
1
+ # *********************************************************************************
2
+ # URBANopt, Copyright (c) 2019, Alliance for Sustainable Energy, LLC, and other
3
+ # contributors. All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice, this list
9
+ # of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright notice, this
12
+ # list of conditions and the following disclaimer in the documentation and/or other
13
+ # materials provided with the distribution.
14
+ #
15
+ # Neither the name of the copyright holder nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without specific
17
+ # prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ # *********************************************************************************
30
+
31
+ require 'json'
32
+ require 'urbanopt/scenario/default_reports/end_uses'
33
+ require 'urbanopt/scenario/default_reports/end_use'
34
+ require 'urbanopt/scenario/default_reports/date'
35
+ require 'urbanopt/scenario/default_reports/validator'
36
+ require 'json-schema'
37
+
38
+ module URBANopt
39
+ module Scenario
40
+ module DefaultReports
41
+ ##
42
+ # ReportingPeriod includes all the results of a specific reporting period.
43
+ ##
44
+ class ReportingPeriod
45
+ attr_accessor :id, :name, :multiplier, :start_date, :end_date, :month, :day_of_month, :year, :total_site_energy, :total_source_energy,
46
+ :net_site_energy, :net_source_energy, :net_utility_cost, :electricity, :natural_gas, :additional_fuel, :district_cooling,
47
+ :district_heating, :water, :electricity_produced, :end_uses, :energy_production, :photovoltaic, :utility_costs,
48
+ :fuel_type, :total_cost, :usage_cost, :demand_cost, :comfort_result, :time_setpoint_not_met_during_occupied_cooling,
49
+ :time_setpoint_not_met_during_occupied_heating, :time_setpoint_not_met_during_occupied_hours #:nodoc:
50
+ # ReportingPeriod class intializes the reporting period attributes:
51
+ # +:id+ , +:name+ , +:multiplier+ , +:start_date+ , +:end_date+ , +:month+ , +:day_of_month+ , +:year+ , +:total_site_energy+ , +:total_source_energy+ ,
52
+ # +:net_site_energy+ , +:net_source_energy+ , +:net_utility_cost+ , +:electricity+ , +:natural_gas+ , +:additional_fuel+ , +:district_cooling+ ,
53
+ # +:district_heating+ , +:water+ , +:electricity_produced+ , +:end_uses+ , +:energy_production+ , +:photovoltaic+ , +:utility_costs+ ,
54
+ # +:fuel_type+ , +:total_cost+ , +:usage_cost+ , +:demand_cost+ , +:comfort_result+ , +:time_setpoint_not_met_during_occupied_cooling+ ,
55
+ # +:time_setpoint_not_met_during_occupied_heating+ , +:time_setpoint_not_met_during_occupied_hours+
56
+ ##
57
+ # [parameters:]
58
+ # +hash+ - _Hash_ - A hash which may contain a deserialized reporting_period.
59
+ ##
60
+ def initialize(hash = {})
61
+ hash.delete_if { |k, v| v.nil? }
62
+ hash = defaults.merge(hash)
63
+
64
+ @id = hash[:id]
65
+ @name = hash[:name]
66
+ @multiplier = hash[:multiplier]
67
+ @start_date = Date.new(hash[:start_date])
68
+ @end_date = Date.new(hash[:end_date])
69
+
70
+ @total_site_energy = hash[:total_site_energy]
71
+ @total_source_energy = hash[:total_source_energy]
72
+ @net_site_energy = hash [:net_site_energy]
73
+ @net_source_energy = hash [:net_source_energy]
74
+ @net_utility_cost = hash [:net_utility_cost]
75
+ @electricity = hash [:electricity]
76
+ @natural_gas = hash [:natural_gas]
77
+ @additional_fuel = hash [:additional_fuel]
78
+ @district_cooling = hash [:district_cooling]
79
+ @district_heating = hash[:district_heating]
80
+ @water = hash[:water]
81
+ @electricity_produced = hash[:electricity_produced]
82
+ @end_uses = EndUses.new(hash[:end_uses])
83
+
84
+ @energy_production = hash[:energy_production]
85
+
86
+ @utility_costs = hash[:utility_costs]
87
+
88
+ @comfort_result = hash[:comfort_result]
89
+
90
+ # initialize class variables @@validator and @@schema
91
+ @@validator ||= Validator.new
92
+ @@schema ||= @@validator.schema
93
+ end
94
+
95
+ ##
96
+ # Assigns default values if values do not exist.
97
+ ##
98
+ def defaults
99
+ hash = {}
100
+
101
+ hash[:id] = nil
102
+ hash[:name] = nil
103
+ hash[:multiplier] = nil
104
+ hash[:start_date] = Date.new.to_hash
105
+ hash[:end_date] = Date.new.to_hash
106
+
107
+ hash[:total_site_energy] = nil
108
+ hash[:total_source_energy] = nil
109
+ hash [:net_site_energy] = nil
110
+ hash [:net_source_energy] = nil
111
+ hash [:net_utility_cost] = nil
112
+ hash [:electricity] = nil
113
+ hash [:natural_gas] = nil
114
+ hash [:additional_fuel] = nil
115
+ hash [:district_cooling] = nil
116
+ hash[:district_heating] = nil
117
+
118
+ hash[:electricity_produced] = nil
119
+ hash[:end_uses] = EndUses.new.to_hash
120
+ hash[:energy_production] = { electricity_produced: { photovoltaic: nil } }
121
+ hash[:utility_costs] = [{ fuel_type: nil, total_cost: nil, usage_cost: nil, demand_cost: nil }]
122
+ hash[:comfort_result] = { time_setpoint_not_met_during_occupied_cooling: nil, time_setpoint_not_met_during_occupied_heating: nil, time_setpoint_not_met_during_occupied_hours: nil }
123
+
124
+ return hash
125
+ end
126
+
127
+ ##
128
+ # Converts to a Hash equivalent for JSON serialization.
129
+ ##
130
+ # - Exclude attributes with nil values.
131
+ # - Validate reporting_period hash properties against schema.
132
+ #
133
+ def to_hash
134
+ result = {}
135
+
136
+ result[:id] = @id if @id
137
+ result[:name] = @name if @name
138
+ result[:multiplier] = @multiplier if @multiplier
139
+ result[:start_date] = @start_date.to_hash if @start_date
140
+ result[:end_date] = @end_date.to_hash if @end_date
141
+ result[:total_site_energy] = @total_site_energy if @total_site_energy
142
+ result[:total_source_energy] = @total_source_energy if @total_source_energy
143
+ result[:net_site_energy] = @net_site_energy if @net_site_energy
144
+ result[:net_source_energy] = @net_source_energy if @net_source_energy
145
+ result[:net_utility_cost] = @net_utility_cost if @net_utility_cost
146
+ result[:electricity] = @electricity if @electricity
147
+ result[:natural_gas] = @natural_gas if @natural_gas
148
+ result[:additional_fuel] = @additional_fuel if @additional_fuel
149
+ result[:district_cooling] = @district_cooling if @district_cooling
150
+ result[:district_heating] = @district_heating if @district_heating
151
+ result[:water] = @water if @water
152
+ result[:electricity_produced] = @electricity_produced if @electricity_produced
153
+ result[:end_uses] = @end_uses.to_hash if @end_uses
154
+
155
+ energy_production_hash = @energy_production if @energy_production
156
+ energy_production_hash.delete_if { |k, v| v.nil? }
157
+ energy_production_hash.each do |eph|
158
+ eph.delete_if { |k, v| v.nil? }
159
+ end
160
+
161
+ result[:energy_production] = energy_production_hash if @energy_production
162
+
163
+ if @utility_costs.any?
164
+ result[:utility_costs] = @utility_costs
165
+ @utility_costs.each do |uc|
166
+ uc.delete_if { |k, v| v.nil? } if uc
167
+ end
168
+ end
169
+
170
+ comfort_result_hash = @comfort_result if @comfort_result
171
+ comfort_result_hash.delete_if { |k, v| v.nil? }
172
+ result[:comfort_result] = comfort_result_hash if @comfort_result
173
+
174
+ # validates +reporting_period+ properties against schema for reporting period.
175
+ if @@validator.validate(@@schema[:definitions][:ReportingPeriod][:properties], result).any?
176
+ raise "feature_report properties does not match schema: #{@@validator.validate(@@schema[:definitions][:ReportingPeriod][:properties], result)}"
177
+ end
178
+
179
+ return result
180
+ end
181
+
182
+ ##
183
+ # Adds up +existing_value+ and +new_values+ if not nill.
184
+ ##
185
+ # [parameter:]
186
+ # +existing_value+ - _Float_ - A value corresponding to a ReportingPeriod attribute.
187
+ ##
188
+ # +new_value+ - _Float_ - A value corresponding to a ReportingPeriod attribute.
189
+ ##
190
+ def self.add_values(existing_value, new_value)
191
+ if existing_value && new_value
192
+ existing_value += new_value
193
+ elsif new_value
194
+ existing_value = new_value
195
+ end
196
+ return existing_value
197
+ end
198
+
199
+ ##
200
+ # Merges an +existing_period+ with a +new_period+ if not nil.
201
+ ##
202
+ # [Parameters:]
203
+ # +existing_period+ - _ReportingPeriod_ - An object of ReportingPeriod class.
204
+ ##
205
+ # +new_period+ - _ReportingPeriod_ - An object of ReportingPeriod class.
206
+ ##
207
+ # rubocop: disable Metrics/AbcSize
208
+ def self.merge_reporting_period(existing_period, new_period)
209
+ # modify the existing_period by summing up the results
210
+ existing_period.total_site_energy = add_values(existing_period.total_site_energy, new_period.total_site_energy)
211
+ existing_period.total_source_energy = add_values(existing_period.total_source_energy, new_period.total_source_energy)
212
+ existing_period.net_source_energy = add_values(existing_period.net_source_energy, new_period.net_source_energy)
213
+ existing_period.net_utility_cost = add_values(existing_period.net_utility_cost, new_period.net_utility_cost)
214
+ existing_period.electricity = add_values(existing_period.electricity, new_period.electricity)
215
+ existing_period.natural_gas = add_values(existing_period.natural_gas, new_period.natural_gas)
216
+ existing_period.additional_fuel = add_values(existing_period.additional_fuel, new_period.additional_fuel)
217
+ existing_period.district_cooling = add_values(existing_period.district_cooling, new_period.district_cooling)
218
+ existing_period.district_heating = add_values(existing_period.district_heating, new_period.district_heating)
219
+ existing_period.water = add_values(existing_period.water, new_period.water)
220
+ existing_period.electricity_produced = add_values(existing_period.electricity_produced, new_period.electricity_produced)
221
+
222
+ # merge end uses
223
+ new_end_uses = new_period.end_uses
224
+ existing_period.end_uses.merge_end_uses!(new_end_uses) if existing_period.end_uses
225
+
226
+ if existing_period.energy_production
227
+ if existing_period.energy_production[:electricity_produced]
228
+ existing_period.energy_production[:electricity_produced][:photovoltaic] = add_values(existing_period.energy_production[:electricity_produced][:photovoltaic], new_period.energy_production[:electricity_produced][:photovoltaic])
229
+ end
230
+ end
231
+
232
+ if existing_period.utility_costs
233
+
234
+ existing_period.utility_costs.each_with_index do |item, i|
235
+ existing_period.utility_costs[i][:fuel_type] = add_values(existing_period.utility_costs[i][:fuel_type], new_period.utility_costs[i][:fuel_type])
236
+ existing_period.utility_costs[i][:total_cost] = add_values(existing_period.utility_costs[i][:total_cost], new_period.utility_costs[i][:total_cost])
237
+ existing_period.utility_costs[i][:usage_cost] = add_values(existing_period.utility_costs[i][:usage_cost], new_period.utility_costs[i][:usage_cost])
238
+ existing_period.utility_costs[i][:demand_cost] = add_values(existing_period.utility_costs[i][:demand_cost], new_period.utility_costs[i][:demand_cost])
239
+ end
240
+
241
+ end
242
+
243
+ if existing_period.comfort_result
244
+ existing_period.comfort_result[:time_setpoint_not_met_during_occupied_cooling] = add_values(existing_period.comfort_result[:time_setpoint_not_met_during_occupied_cooling], new_period.comfort_result[:time_setpoint_not_met_during_occupied_cooling])
245
+ existing_period.comfort_result[:time_setpoint_not_met_during_occupied_heating] = add_values(existing_period.comfort_result[:time_setpoint_not_met_during_occupied_heating], new_period.comfort_result[:time_setpoint_not_met_during_occupied_heating])
246
+ existing_period.comfort_result[:time_setpoint_not_met_during_occupied_hours] = add_values(existing_period.comfort_result[:time_setpoint_not_met_during_occupied_hours], new_period.comfort_result[:time_setpoint_not_met_during_occupied_hours])
247
+ end
248
+
249
+ return existing_period
250
+ end
251
+ # rubocop: enable Metrics/AbcSize # :nodoc:
252
+
253
+ ##
254
+ # Merges multiple reporting periods together.
255
+ # - If +existing_periods+ and +new_periods+ ids are equal,
256
+ # modify the existing_periods by merging the new periods results
257
+ # - If existing periods are empty, initialize with new_periods.
258
+ # - Raise an error if the existing periods are not identical with new periods (cannot have different reporting period ids).
259
+ ##
260
+ # [parameters:]
261
+ ##
262
+ # +existing_periods+ - _Array_ - An array of ReportingPeriod objects.
263
+ ##
264
+ # +new_periods+ - _Array_ - An array of ReportingPeriod objects.
265
+ ##
266
+ def self.merge_reporting_periods(existing_periods, new_periods)
267
+ id_list_existing = []
268
+ id_list_new = []
269
+ id_list_existing = existing_periods.collect(&:id)
270
+ id_list_new = new_periods.collect(&:id)
271
+
272
+ if id_list_existing == id_list_new
273
+
274
+ existing_periods.each_index do |index|
275
+ # if +existing_periods+ and +new_periods+ ids are equal,
276
+ # modify the existing_periods by merging the new periods results
277
+ existing_periods[index] = merge_reporting_period(existing_periods[index], new_periods[index])
278
+ end
279
+
280
+ elsif existing_periods.empty?
281
+
282
+ # if existing periods are empty, initialize with new_periods
283
+ # the = operator would link existing_periods and new_periods to the same object in memory
284
+ # we want to initialize with a deep clone of new_periods
285
+ existing_periods = Marshal.load(Marshal.dump(new_periods))
286
+
287
+ else
288
+ # raise an error if the existing periods are not identical with new periods (cannot have different reporting period ids)
289
+ raise 'cannot merge different reporting periods'
290
+
291
+ end
292
+
293
+ return existing_periods
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end