wits 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: 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: