ce-bucketize 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aa75c7b3b340eff4180b1eeeb39713e7924bd1d3
4
+ data.tar.gz: 8274b993868aa63cf4d383f78f740aaa9105e8dc
5
+ SHA512:
6
+ metadata.gz: 42c8b54e45ce7f49090bccb799ed7bab95630b9105001b9f72d121f0acde4d6d5777640251f3ad93f93bbff0da9a68cf2f035078805643dfb1960aff04bb57a6
7
+ data.tar.gz: 8794ff5e9c7a2342e28862ed9b4809673f45771f775452d3e205940df3fb3f7c1797b136236b21f043107df00616152df4e35ed08e007054c8ca354168a679af
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ce-bucketize.gemspec
4
+ gemspec
5
+
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 TopCoder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # Bucketize
2
+
3
+ This gem will aggregate consumption data with heterogeneous granularity across time into systematic intervals.
4
+ These intervals are then either multiplied with a relevant tariff value then summed into daily totals, or directly summed into daily totals.
5
+ The simplest example would be to sum hourly data into daily totals, subject to data completeness checks and multiply them with a single tariff number.
6
+
7
+ ## Prerequisites
8
+ Ruby ~> 2.0.0
9
+ Bundler
10
+ gem install bundler
11
+
12
+ ## Installation
13
+ ### GreenButton gem
14
+ Follow installation instructions in the README file of
15
+ the GreenButton Library module
16
+ ### Bucketize gem
17
+ Using Bundler, add the following line to your Gemfile
18
+
19
+ ```ruby
20
+ gem 'bucketize'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem build bucketize.gemspec
30
+ $ gem install bucketize-0.1.0.gem
31
+
32
+ ## Usage
33
+
34
+ ```ruby
35
+ require 'ce-greenbutton'
36
+ require 'ce-bucketize'
37
+
38
+ reg_access_token = 'd89bb056-0f02-4d47-9fd2-ec6a19ba8d0c'
39
+ app_inf_url = 'https://services.greenbuttondata.org:443/DataCustodian/espi/1_1/resource/ApplicationInformation/2'
40
+ subscription_url = 'https://services.greenbuttondata.org:443/DataCustodian/espi/1_1/resource/Batch/RetailCustomer/5/UsagePoint'
41
+ access_token = '688b026c-665f-4994-9139-6b21b13fbeee'
42
+
43
+ # FTP related values
44
+ ftp_host = 'us1.hostedftp.com'
45
+ ftp_user = 'clearlyenergy'
46
+ ftp_password = 'store_clearly'
47
+ ftp_path = 'SDGETest'
48
+ utility_name = 'CLEARLY'
49
+ application_id = '10066_CONSUMPTION'
50
+ ftp_time = Time.new(2015, 3, 19, 1, 1, 1, 0)
51
+
52
+ download_options = {access_token: access_token, subscription_url: subscription_url}
53
+ ftp_download_options = {
54
+ use_ftp: true,
55
+ application_id: application_id,
56
+ utility_name: utility_name,
57
+ time: ftp_time
58
+ }
59
+
60
+
61
+ # configure the GreenButton Library
62
+ Bucketize.config(
63
+ reg_access_token: reg_access_token,
64
+ application_information_url: app_inf_url,
65
+ ftp_host: ftp_host,
66
+ ftp_user: ftp_user,
67
+ ftp_password: ftp_password,
68
+ ftp_path: ftp_path
69
+ )
70
+
71
+ # get consumptions from month start
72
+ daily_data = Bucketize.consumption_from_month_start(Time.new(2011, 2, 1, 0, 0, 0, -28800), download_options)
73
+ puts 'Consumption From Month Start 2012/02'
74
+ puts JSON.pretty_generate(daily_data.to_a)
75
+
76
+ daily_data = Bucketize.consumption_from_month_start(Time.new(2013, 1, 1, 0, 0, 0, -14400), ftp_download_options)
77
+ puts 'Consumption From Month Start 2013/01 Using FTP'
78
+ puts JSON.pretty_generate(daily_data.to_a)
79
+
80
+
81
+
82
+ # get cost from month start
83
+ tariff = '[{
84
+ "utility_id": 1,
85
+ "rate_class": "R",
86
+ "day_of_week": [1,2,3,4,5,6,7],
87
+ "kwh_low": 0,
88
+ "kwh_high":1000,
89
+ "tariff":10
90
+ },
91
+ {
92
+ "utility_id": 1,
93
+ "rate_class": "R",
94
+ "day_of_week": [1,2,3,4,5,6,7],
95
+ "kwh_low": 1000,
96
+ "tariff":20
97
+ }]'
98
+ daily_data = Bucketize.cost_from_month_start(Time.new(2011, 2, 1, 0, 0, 0, -28800), tariff, download_options)
99
+ puts 'Cost From Month Start 2012/02:'
100
+ puts JSON.pretty_generate(daily_data.to_a)
101
+
102
+ daily_data = Bucketize.cost_from_month_start(Time.new(2013, 1, 1, 0, 0, 0, -14400), tariff, ftp_download_options)
103
+ puts 'Cost From Month Start 2013/01 Using FTP'
104
+ puts JSON.pretty_generate(daily_data.to_a)
105
+
106
+
107
+
108
+ # get consumption by date
109
+ daily_data = Bucketize.consumption_by_date(Time.new(2011, 2, 5, 0, 0, 0, -28800), download_options)
110
+ puts 'Consumption For Date 2011/02/05'
111
+ puts JSON.pretty_generate(daily_data.to_a)
112
+
113
+ daily_data = Bucketize.consumption_by_date(Time.new(2013, 1, 5, 0, 0, 0, -14400), ftp_download_options)
114
+ puts 'Consumption For Date 2013/01/05 Using FTP'
115
+ puts JSON.pretty_generate(daily_data.to_a)
116
+
117
+
118
+ # get cost by date
119
+ tariff = '[{
120
+ "utility_id": 1,
121
+ "rate_class": "R",
122
+ "time_start": 1,
123
+ "time_end": 3,
124
+ "tariff":10
125
+ },
126
+ {
127
+ "utility_id": 1,
128
+ "rate_class": "R",
129
+ "time_start": 4,
130
+ "time_end": 24,
131
+ "tariff":20
132
+ }]'
133
+ daily_data = Bucketize.cost_by_date(Time.new(2011, 2, 5, 0, 0, 0, -28800), tariff,
134
+ download_options)
135
+ puts 'Consumption For Date 2011/02/05'
136
+ puts JSON.pretty_generate(daily_data.to_a)
137
+
138
+ daily_data = Bucketize.cost_by_date(Time.new(2013, 1, 5, 0, 0, 0, -14400), tariff,
139
+ ftp_download_options)
140
+ puts 'Consumption For Date 2013/01/05 Using FTP'
141
+ puts JSON.pretty_generate(daily_data.to_a)
142
+
143
+
144
+
145
+ # get consumption by date range.
146
+ daily_data = Bucketize.consumption_date_range(Time.new(2011, 2, 5, 0, 0, 0, -28800),
147
+ Time.new(2011, 2, 10, 23, 59, 59, -28800),
148
+ download_options)
149
+ puts 'Consumption For Dates 2011/02/05 to 2011/02/10 :'
150
+ puts JSON.pretty_generate(daily_data.to_a)
151
+
152
+ daily_data = Bucketize.consumption_date_range(Time.new(2013, 1, 5, 0, 0, 0, -14400),
153
+ Time.new(2013, 1, 10, 23, 59, 59, -14400),
154
+ ftp_download_options)
155
+ puts 'Consumption For Dates 2013/01/05 to 2013/01/10 :'
156
+ puts JSON.pretty_generate(daily_data.to_a)
157
+
158
+ # get cost by date range.
159
+ tariff = '[{
160
+ "utility_id": 1,
161
+ "rate_class": "R",
162
+ "time_start": 1,
163
+ "time_end": 12,
164
+ "day_of_week": [1,2,3,4],
165
+ "tariff":5
166
+ },
167
+ {
168
+ "utility_id": 1,
169
+ "rate_class": "R",
170
+ "time_start": 1,
171
+ "time_end": 12,
172
+ "day_of_week": [5,6,7],
173
+ "tariff":7
174
+ },
175
+ {
176
+ "utility_id": 1,
177
+ "rate_class": "R",
178
+ "time_start": 13,
179
+ "time_end": 24,
180
+ "day_of_week": [1,2,3,4],
181
+ "tariff":10
182
+ },
183
+ {
184
+ "utility_id": 1,
185
+ "rate_class": "R",
186
+ "time_start": 13,
187
+ "time_end": 24,
188
+ "day_of_week": [5,6,7],
189
+ "tariff":12
190
+ }]'
191
+ daily_data = Bucketize.cost_date_range(Time.new(2011, 2, 5, 0, 0, 0, -28800),
192
+ Time.new(2011, 2, 10, 23, 59, 59, -28800),
193
+ tariff,
194
+ download_options)
195
+ puts 'Cost For Dates 2011/02/05 to 2011/02/10'
196
+ puts JSON.pretty_generate(daily_data.to_a)
197
+
198
+ daily_data = Bucketize.cost_date_range(Time.new(2013, 1, 5, 0, 0, 0, -14400),
199
+ Time.new(2013, 1, 10, 23, 59, 59, -14400),
200
+ tariff,
201
+ ftp_download_options)
202
+ puts 'Cost For Dates 2013/01/05 to 2013/01/10 Using FTP'
203
+ puts JSON.pretty_generate(daily_data.to_a)
204
+ ```
205
+ Other aggregation methods can be called in the manner like above.
206
+
207
+ ## Development
208
+ This gem was designed in a modular way to allow further modification. An expected
209
+ scenario is to adapt a MapReduce solution to help analyze the data more efficiently.
210
+
211
+ The aggregation was separated to phases; each was encapsulated in a custom Enumerable
212
+ that operates on the input data and produces new data.
213
+
214
+ API ==> GreenButton ==> HourlyValues ==> TariffedHourlyValues/Costs ==> DailyData ==> output data
215
+
216
+ 1. API: the GreenButton API (REST endpoint)
217
+ 2. GreenButton: The GreenButton Library that maps the API response to set of Ruby Objects
218
+ 3. HourlyValues: a custom Enumerable that takes the generated GbDataDescription and produces an hourly data.
219
+ input data gaps are detected and interpolated in this stage.
220
+ 4. TariffedHourlyValues/TariffedHourlyCosts: custom Enumerables that takes the HourlyData and applies the input
221
+ tariff rules on them. The output will be the values after applying the matched tariff.
222
+ 5. DailyData: a custom Enumerable that aggregates a day data from the tariffed hourly data.
223
+
224
+ ## Test
225
+ From the base directory of the module (the directory containing the .gemspec file.
226
+
227
+ $ bundle install
228
+ $ bundle exec rspec
229
+
230
+ Coverage reports will be found under the `coverage` subfolder.
231
+
232
+
233
+ ## GreenButton API reference
234
+ - [Green Button Home](http://www.greenbuttondata.org)
235
+ - [Green Button API sandbox](http://energyos.github.io/OpenESPI-GreenButton-API-Documentation/API/)
236
+ - [GReen Button Concept overview](http://www.greenbuttondata.org/developers/)
237
+
238
+
239
+ ### Reference this Ruby Gem Documentation
240
+ `tomdoc` was used to generate documentation files for this gem. The generated files are included in `doc` directory.
241
+ documentation is generated by [Yard.](https://github.com/rubyworks/yard-tomdoc)
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ce-bucketize/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ce-bucketize'
8
+ spec.version = Bucketize::VERSION
9
+ spec.authors = ['TCSASSEMBLER']
10
+ spec.email = ['TCSASSEMBLER@topcoder.com']
11
+
12
+ spec.summary = %q{Aggregates consumption data and calculates costs based on predefined tariffs.}
13
+
14
+ spec.description = %q{aggregate consumption data with heterogeneous granularity across time into systematic intervals.
15
+ These intervals are then either multiplied with a relevant tariff value then summed into daily totals,
16
+ or directly summed into daily totals. The simplest example would be to sum hourly data into daily totals,
17
+ subject to data completeness checks and multiply them with a single tariff number.}
18
+ spec.homepage = 'http://greenbuttondata.org'
19
+ spec.license = 'MIT'
20
+
21
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ spec.bindir = 'exe'
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.8'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'rspec', '~> 3.2'
29
+ spec.add_development_dependency 'simplecov', '~> 0.9.2'
30
+ spec.add_development_dependency 'yard-tomdoc', '~> 0.7.1'
31
+
32
+ spec.add_runtime_dependency 'interpolate', '~> 0.3.0'
33
+ spec.add_runtime_dependency 'ce-greenbutton', '~> 0.1.0'
34
+
35
+ end
@@ -0,0 +1,134 @@
1
+ # Copyright (C) 2015 TopCoder Inc., All Rights Reserved.
2
+
3
+ require 'ce-bucketize/hourly_values'
4
+ require 'ce-bucketize/tariffed_hourly_values'
5
+ require 'ce-bucketize/tariffed_hourly_costs'
6
+ require 'ce-bucketize/daily_data'
7
+
8
+ module Bucketize
9
+ # This class is responsible for executing an aggregation method for the
10
+ # Bucketize module. It orchestras some other helper classes to perform the
11
+ # required aggregation.
12
+ #
13
+ # Author: ahmed.seddiq
14
+ # Version: 1.0
15
+ class Bucketizer
16
+ # Initializes the Bucketizer with the given arguments.
17
+ #
18
+ # download_options - options used to download GreenButton data
19
+ # from - represents the start Date for required aggregation
20
+ # to - represents the end date for required aggregation
21
+ #
22
+ # supported download_options
23
+ # use_ftp - a Boolean flag to switch between ftp and API modes.
24
+ # API (HTTP) options
25
+ # subscription_url - the GreenButton subscription url.
26
+ # access_token - the GreenButton OAuth2 access_token.
27
+ # FTP options
28
+ # application_id - represents the GreenButton 3rd party application id.
29
+ # time - used to construct file name. (optional, defaults
30
+ # to current time)
31
+ # utility_name - represents the utility name, used to construct the
32
+ # XML file name.
33
+ # Raises ArgumentError if download_options are not valid
34
+ #
35
+ def initialize(download_options, from=nil, to=nil)
36
+ @download_options = download_options
37
+ check_download_options
38
+ @from = from
39
+ @to = to
40
+ end
41
+
42
+ # Fetches and aggregates the consumption data to daily usage values
43
+ #
44
+ # Returns an array in the form [{date1 => value, date2 => value}]
45
+ def daily_consumption_values
46
+ gb = download_data(@from)
47
+ gb.map do |gb_data_description|
48
+ hourly_values = Bucketize::HourlyValues.new(gb_data_description, @from, @to)
49
+ daily_data(hourly_values, :value)
50
+ end
51
+
52
+ end
53
+
54
+
55
+
56
+ # Fetches and aggregates the data based on the given array of TariffRules
57
+ #
58
+ # tariff_rules - an Array of TariffRule to be applied to the GreenButton
59
+ # data for this Bucketizer.
60
+ # Returns a collection of [gb_data_description, date, cost]
61
+ def daily_consumption_costs(tariff_rules)
62
+ # Validate tariff rules
63
+ tariff_rules.each { |rule| rule.validate }
64
+ gb = download_data(@from)
65
+ gb.map do |gb_data_description|
66
+ hourly_values = Bucketize::HourlyValues.new(gb_data_description, @from, @to)
67
+ tariffed_hourly_costs =
68
+ Bucketize::TariffedHourlyCosts.new(hourly_values, tariff_rules)
69
+ daily_data(tariffed_hourly_costs, :cost)
70
+ end
71
+ end
72
+
73
+ # Internal: aggregates the given hourly data to a daily data.
74
+ #
75
+ # hourly_data - the hourly data, an Enumerable
76
+ # aggregation_method - the getter method to be called for elements in the
77
+ # hourly_data. this value will be summed for each day.
78
+ # Returns a DailyData enumerable.
79
+ def daily_data(hourly_data, aggregation_method )
80
+ daily_data = Bucketize::DailyData.new(hourly_data, aggregation_method)
81
+ # convert the daily_data to hash date => value
82
+ daily_data.reduce({}) do |all, entry|
83
+ all[entry[0]] = entry[1]
84
+ all
85
+ end
86
+ end
87
+
88
+ private
89
+ # Internal: checks the validity of the download options
90
+ #
91
+ # Raises ArgumentError if options are invalid
92
+ def check_download_options
93
+ raise ArgumentError, 'No download options provided'if @download_options.nil?
94
+ if @download_options[:use_ftp]
95
+ if @download_options[:application_id].nil? or
96
+ @download_options[:utility_name].nil?
97
+ raise ArgumentError, 'application_id and utility_name are mandatory
98
+ options for FTP download'
99
+ end
100
+ else
101
+ if @download_options[:access_token].nil? or
102
+ @download_options[:subscription_url].nil?
103
+ raise ArgumentError, 'access_token and subscription_url are mandatory
104
+ options for API(HTTP) download'
105
+ end
106
+ end
107
+ end
108
+
109
+
110
+ # Internal: downloads data using GreenButton gem according to the configured
111
+ # downlaod_options
112
+ #
113
+ # from - the Time object representing the start date of required data, used
114
+ # only in the API(HTTP) download.
115
+ #
116
+ # Returns array of GbDataDescription
117
+ def download_data(from)
118
+ if @download_options[:use_ftp]
119
+ GreenButton.download_data_ftp(
120
+ @download_options[:application_id],
121
+ @download_options[:time] || Time.now,
122
+ @download_options[:utility_name]
123
+ )
124
+ else
125
+ GreenButton.download_data(
126
+ @download_options[:access_token],
127
+ @download_options[:subscription_url],
128
+ from
129
+ )
130
+ end
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,41 @@
1
+ # Copyright (C) 2015 TopCoder Inc., All Rights Reserved.
2
+
3
+ module Bucketize
4
+ # An Enumerable that will aggregate hourly data to daily data.
5
+ #
6
+ # Author: ahmed.seddiq
7
+ # Version: 1.0
8
+ class DailyData
9
+ include Enumerable
10
+ # Creates a new instance of this Enumerable
11
+ #
12
+ # hourly_data - the hourly data source. (an Enumerable)
13
+ # aggregated_method - the method that will be called on the items from the
14
+ # source and summed up in the result value.
15
+ def initialize (hourly_data, aggregate_method)
16
+ @hourly_data = hourly_data
17
+ @aggregate_method = aggregate_method
18
+ end
19
+
20
+ def each(&block)
21
+ return enum_for(:each) if block.nil?
22
+
23
+ date = @hourly_data.first.hour_start
24
+ day_value = 0
25
+ day = date.day
26
+ @hourly_data.each do |h_data|
27
+ value = h_data.public_send(@aggregate_method)
28
+ if h_data.hour_start.day == day
29
+ day_value += value
30
+ else
31
+ yield date, day_value
32
+ date = h_data.hour_start
33
+ day_value = value
34
+ day = date.day
35
+ end
36
+ end
37
+ # the last
38
+ yield date, day_value
39
+ end
40
+ end
41
+ end