wits 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e619dbc0e8e27a5dc2b90905bdb7e2d79d6df257
4
+ data.tar.gz: 274e2cac8a4ad516ef112edcd763cb5acf1bc030
5
+ SHA512:
6
+ metadata.gz: 4559b72384b3e94e4233503e6edcbfa6c343bd70a176094b83bef7fd14e5d43155b9e8eb4ef80b6eb3e5a47dd5610620563319b46a88fcbd668f772e900d8e41
7
+ data.tar.gz: 40ef592b050dd51d134df1d55823dabd95c671d0c9c4ce3c43e412fd20748cb78efaf5df8b6999febe9dc16736dcef4a231ea8888204c4bb0812a81d84cc63b1
@@ -0,0 +1,52 @@
1
+ require 'wits/error'
2
+
3
+ #
4
+ # A wrapper for the chosen HTTP library that can be easily replaced
5
+ # while keeping the Wits API and error classes consistent
6
+ #
7
+ module Wits
8
+ module Client
9
+ def self.get_csv(*args, &block)
10
+ response = get(*args, &block)
11
+
12
+ check_for_data(response)
13
+
14
+ response
15
+ end
16
+
17
+ def self.get(*args, &block)
18
+ client.get(*args, &block)
19
+ rescue Faraday::ClientError => error
20
+ handle_error(error)
21
+ end
22
+
23
+ def self.client
24
+ @client ||= Faraday.new(url: 'http://www.electricityinfo.co.nz')
25
+ end
26
+
27
+ private
28
+
29
+ # WITS is buggy and doesn't use status codes.
30
+ # This is the most reliable method of determining whether
31
+ # we actually have a meaningful CSV file
32
+ def self.check_for_data(response)
33
+ return unless response.body =~ /html/i ||
34
+ response.body.length < 300
35
+
36
+ fail Wits::Error::ResourceNotFound
37
+ end
38
+
39
+ def self.handle_error(error)
40
+ case error
41
+ when Faraday::ConnectionFailed
42
+ raise Wits::Error::ConnectionFailed.new error
43
+ when Faraday::ResourceNotFound
44
+ raise Wits::Error::ResourceNotFound.new error
45
+ when Faraday::TimeoutError
46
+ raise Wits::Error::TimeoutError.new error
47
+ else # Faraday::ClientError
48
+ raise Wits::Error::ClientError.new error
49
+ end
50
+ end
51
+ end
52
+ end
data/lib/wits/error.rb ADDED
@@ -0,0 +1,18 @@
1
+ module Wits
2
+ class Error < StandardError
3
+ # Generic Client Error
4
+ ClientError = Class.new(Error)
5
+
6
+ # ConnectionFailed from Faraday
7
+ ConnectionFailed = Class.new(ClientError)
8
+
9
+ # TimeoutError from Faraday
10
+ TimeoutError = Class.new(ClientError)
11
+
12
+ # No data available from Wits
13
+ ResourceNotFound = Class.new(ClientError)
14
+
15
+ # Unexpected error when parsing data
16
+ ParsingError = Class.new(ClientError)
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ require 'wits/nodes'
2
+
3
+ module Wits
4
+ module FinalInterimPrices
5
+ module ConvenienceMethods
6
+ Wits::Nodes.nodes.each do |node, name|
7
+ # def ben2201
8
+ define_method node.downcase do |*args|
9
+ prices(node, *args)
10
+ end
11
+
12
+ # def hay
13
+ define_method node.downcase[0..2] do |*args|
14
+ prices(node, *args)
15
+ end
16
+
17
+ # def halfway_bush
18
+ define_method name.downcase.tr(' ', '_') do |*args|
19
+ prices(node, *args)
20
+ end
21
+
22
+
23
+ # def ben2201_prices
24
+ define_method "#{node.downcase}_prices" do |*args|
25
+ prices(node, *args)
26
+ end
27
+
28
+ # def hay_prices
29
+ define_method "#{node.downcase[0..2]}_prices" do |*args|
30
+ prices(node, *args)
31
+ end
32
+
33
+ # def halfway_bush_prices
34
+ define_method "#{name.downcase.tr(' ', '_')}_prices" do |*args|
35
+ prices(node, *args)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,70 @@
1
+ require 'csv'
2
+ require 'wits/nodes'
3
+ require 'wits/helpers'
4
+ require 'wits/client'
5
+ require 'wits/error'
6
+ require 'wits/final_interim_prices/convenience_methods'
7
+
8
+ module Wits
9
+ module FinalInterimPrices
10
+ extend self
11
+ extend Helpers
12
+ extend ConvenienceMethods
13
+
14
+ def self.extended(base)
15
+ base.extend Helpers
16
+ base.extend ConvenienceMethods
17
+ end
18
+
19
+ def prices(node, date = Date.today - 2)
20
+ node = format_node(node)
21
+ date = format_date(date)
22
+
23
+ request_prices(node, date)
24
+ end
25
+
26
+ private
27
+
28
+ def request_prices(node, date)
29
+ response = Wits::Client.get_csv do |request|
30
+ request.url(
31
+ 'comitFta/ftaPage.download',
32
+ pNode: node,
33
+ pDate: date
34
+ )
35
+ end
36
+
37
+ parse_prices_csv(response.body)
38
+ end
39
+
40
+ def parse_prices_csv(csv)
41
+ csv = CSV.parse(csv)
42
+
43
+ price_type, node, date = read_prices_header(csv)
44
+ prices = process_prices(csv, date)
45
+
46
+ format_prices(node, date, prices, price_type)
47
+ rescue StandardError => error
48
+ raise Wits::Error::ParsingError.new(error)
49
+ end
50
+
51
+ def read_prices_header(csv)
52
+ header_regexp = %r{(.*) Prices for Node (.{3}\d{4}) on (\d{2}\/\d{2}\/\d{4})}
53
+
54
+ header = csv.shift.shift
55
+ price_type, node, date = header.match(header_regexp).captures
56
+
57
+ date = Date.parse date
58
+
59
+ csv.shift # discard second header
60
+
61
+ [price_type, node, date]
62
+ end
63
+
64
+ def process_prices(csv, date)
65
+ csv.map do |time, trading_period, price|
66
+ format_price(date, time, trading_period, price)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,40 @@
1
+ require 'wits/nodes'
2
+
3
+ module Wits
4
+ module FiveMinutePrices
5
+ module ConvenienceMethods
6
+ Wits::Nodes.nodes.each do |node, name|
7
+ # def ben2201_five_minute_prices
8
+ define_method "#{node.downcase}_five_minute_prices" do |*args|
9
+ five_minute_prices(node, *args)
10
+ end
11
+
12
+ # def hay_five_minute_prices
13
+ define_method "#{node.downcase[0..2]}_five_minute_prices" do |*args|
14
+ five_minute_prices(node, *args)
15
+ end
16
+
17
+ # def halfway_bush_five_minute_prices
18
+ define_method "#{name.downcase.tr(' ', '_')}_five_minute_prices" do |*args|
19
+ five_minute_prices(node, *args)
20
+ end
21
+
22
+
23
+ # def ben2201_avgerage_five_minute_prices
24
+ define_method "#{node.downcase}_average_five_minute_prices" do |*args|
25
+ average_five_minute_prices(node, *args)
26
+ end
27
+
28
+ # def hay_avgerage_five_minute_prices
29
+ define_method "#{node.downcase[0..2]}_average_five_minute_prices" do |*args|
30
+ average_five_minute_prices(node, *args)
31
+ end
32
+
33
+ # def halfway_bush_avgerage_five_minute_prices
34
+ define_method "#{name.downcase.tr(' ', '_')}_average_five_minute_prices" do |*args|
35
+ average_five_minute_prices(node, *args)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,86 @@
1
+ require 'csv'
2
+ require 'wits/nodes'
3
+ require 'wits/helpers'
4
+ require 'wits/client'
5
+ require 'wits/error'
6
+ require 'wits/five_minute_prices/convenience_methods'
7
+
8
+ module Wits
9
+ module FiveMinutePrices
10
+ extend self
11
+ extend Helpers
12
+ extend ConvenienceMethods
13
+
14
+ def self.extended(base)
15
+ base.extend Helpers
16
+ base.extend ConvenienceMethods
17
+ end
18
+
19
+ def average_five_minute_prices(node, date = Date.today - 2)
20
+ five_minute_prices(node, date, :average)
21
+ end
22
+
23
+ def five_minute_prices(node, date = Date.today - 2, type = :price )
24
+ node = format_node(node)
25
+ date = format_date(date)
26
+ type = format_price_type(type)
27
+
28
+ request_five_min_prices(node, date, type)
29
+ end
30
+
31
+ private
32
+
33
+ def format_price_type(type)
34
+ if type == :price
35
+ 'Price'
36
+ else
37
+ 'Average'
38
+ end
39
+ end
40
+
41
+ def request_five_min_prices(node, date, type)
42
+ response = Wits::Client.get_csv do |request|
43
+ request.url(
44
+ 'comitFta/five_min_prices.download',
45
+ INchoice: 'SEL', # Mandatory
46
+ INdate: date,
47
+ INgip: node,
48
+ INperiodfrom: 1,
49
+ INperiodto: 50,
50
+ INtype: type
51
+ )
52
+ end
53
+
54
+ parse_five_min_csv(response.body, type)
55
+ end
56
+
57
+ def parse_five_min_csv(csv, type)
58
+ csv = CSV.parse(csv.sub("\r\n", "\n"))
59
+
60
+ node, date = process_five_min_metadata(csv)
61
+ prices = process_five_min_prices(csv)
62
+ price_type = 'Five Minute'
63
+
64
+ price_type = "Average #{price_type}" if type == 'Average'
65
+
66
+ format_prices(node, date, prices, price_type)
67
+ rescue StandardError => error
68
+ raise Wits::Error::ParsingError.new(error)
69
+ end
70
+
71
+ def process_five_min_metadata(csv)
72
+ csv.shift # discard header
73
+
74
+ node = csv.first[0]
75
+ date = Date.parse csv.first[1]
76
+
77
+ [node, date]
78
+ end
79
+
80
+ def process_five_min_prices(csv)
81
+ csv.map do |_node, date, trading_period, time, price, *_|
82
+ format_price(date, time, trading_period, price)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,55 @@
1
+ require 'tzinfo'
2
+ require 'wits/nodes'
3
+
4
+ module Wits
5
+ module Helpers
6
+ private
7
+
8
+ def format_node(node)
9
+ node = node.to_s.upcase
10
+
11
+ if node.length == 3
12
+ Wits::Nodes.node_short_codes[node.to_sym]
13
+ else
14
+ node
15
+ end
16
+ end
17
+
18
+ def format_date(date)
19
+ if date.respond_to?(:strftime)
20
+ date.strftime('%d/%m/%Y')
21
+ else
22
+ date
23
+ end
24
+ end
25
+
26
+ def format_price(date, time, trading_period, price)
27
+ {
28
+ time: parse_nz_time(date, time),
29
+ trading_period: trading_period.to_i,
30
+ price: price.to_f
31
+ }
32
+ end
33
+
34
+ def format_prices(node, date, prices, price_type)
35
+ {
36
+ node_code: node,
37
+ node_short_code: node[0..2],
38
+ node_name: Wits::Nodes.nodes[node.to_sym],
39
+ price_type: price_type,
40
+ date: date,
41
+ prices: prices.sort_by { |price| price[:time] }
42
+ }
43
+ end
44
+
45
+ def parse_nz_time(date, time)
46
+ tz = TZInfo::Timezone.get('Pacific/Auckland')
47
+
48
+ # Timezone information of the Time object is ignored by TZInfo
49
+ time = Time.parse("#{date} #{time}")
50
+
51
+ # Output in local time
52
+ tz.local_to_utc(time).localtime
53
+ end
54
+ end
55
+ end
data/lib/wits/nodes.rb ADDED
@@ -0,0 +1,47 @@
1
+ module Wits
2
+ module Nodes
3
+ extend self
4
+
5
+ NODES = {
6
+ BEN2201: 'Benmore',
7
+ HWB2201: 'Halfway Bush',
8
+ HAY2201: 'Haywards',
9
+ HLY2201: 'Huntly',
10
+ INV2201: 'Invercargill',
11
+ ISL2201: 'Islington',
12
+ OTA2201: 'Otahuhu',
13
+ STK2201: 'Stoke',
14
+ SFD2201: 'Stratford',
15
+ TUI1101: 'Tuai',
16
+ WKM2201: 'Whakamaru'
17
+ }.freeze
18
+
19
+ NODE_NAMES = NODES.invert.freeze
20
+
21
+ SHORT_CODES = {
22
+ BEN: 'BEN2201',
23
+ HWB: 'HWB2201',
24
+ HAY: 'HAY2201',
25
+ HLY: 'HLY2201',
26
+ INV: 'INV2201',
27
+ ISL: 'ISL2201',
28
+ OTA: 'OTA2201',
29
+ STK: 'STK2201',
30
+ SFD: 'SFD2201',
31
+ TUI: 'TUI1101',
32
+ WKM: 'WKM2201'
33
+ }.freeze
34
+
35
+ def nodes
36
+ NODES
37
+ end
38
+
39
+ def node_names
40
+ NODE_NAMES
41
+ end
42
+
43
+ def node_short_codes
44
+ SHORT_CODES
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module Wits
2
+ VERSION = "0.1.0"
3
+ end
data/lib/wits.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'faraday'
2
+
3
+ require 'wits/version'
4
+ require 'wits/nodes'
5
+ require 'wits/final_interim_prices'
6
+ require 'wits/five_minute_prices'
7
+
8
+ module Wits
9
+ extend Wits::Nodes
10
+ extend Wits::FinalInterimPrices
11
+ extend Wits::FiveMinutePrices
12
+ end
metadata ADDED
@@ -0,0 +1,254 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wits
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gordon Chan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: tzinfo
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.2.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.10.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.10.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 4.5.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 4.5.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 3.2.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 3.2.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: vcr
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 2.9.3
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 2.9.3
125
+ - !ruby/object:Gem::Dependency
126
+ name: webmock
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 1.21.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 1.21.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: multi_json
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 1.11.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 1.11.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: timecop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 0.7.3
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 0.7.3
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 0.9.2
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 0.9.2
181
+ - !ruby/object:Gem::Dependency
182
+ name: rubocop
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.29.1
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.29.1
195
+ - !ruby/object:Gem::Dependency
196
+ name: coveralls
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 0.8.0
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 0.8.0
209
+ description: |-
210
+ This client library interfaces with WITS (Wholesale and
211
+ information trading system) to fetch New Zealand electricity data, preparing it
212
+ to be consumed by Ruby code.
213
+ email:
214
+ - developer.gordon@gmail.com
215
+ executables: []
216
+ extensions: []
217
+ extra_rdoc_files: []
218
+ files:
219
+ - lib/wits.rb
220
+ - lib/wits/client.rb
221
+ - lib/wits/error.rb
222
+ - lib/wits/final_interim_prices.rb
223
+ - lib/wits/final_interim_prices/convenience_methods.rb
224
+ - lib/wits/five_minute_prices.rb
225
+ - lib/wits/five_minute_prices/convenience_methods.rb
226
+ - lib/wits/helpers.rb
227
+ - lib/wits/nodes.rb
228
+ - lib/wits/version.rb
229
+ homepage: https://www.github.com/gchan/wits
230
+ licenses:
231
+ - MIT
232
+ metadata: {}
233
+ post_install_message:
234
+ rdoc_options: []
235
+ require_paths:
236
+ - lib
237
+ required_ruby_version: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - ">="
240
+ - !ruby/object:Gem::Version
241
+ version: '0'
242
+ required_rubygems_version: !ruby/object:Gem::Requirement
243
+ requirements:
244
+ - - ">="
245
+ - !ruby/object:Gem::Version
246
+ version: '0'
247
+ requirements: []
248
+ rubyforge_project:
249
+ rubygems_version: 2.4.5
250
+ signing_key:
251
+ specification_version: 4
252
+ summary: Retrieves electricity data from WITS Free to Air
253
+ test_files: []
254
+ has_rdoc: