aemo 0.1.14 → 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/Gemfile +19 -17
- data/Gemfile.lock +106 -0
- data/aemo.gemspec +10 -7
- data/lib/aemo/msats.rb +60 -32
- data/lib/aemo/nem12.rb +37 -36
- data/lib/aemo/nmi.rb +17 -17
- data/lib/aemo/version.rb +2 -2
- metadata +39 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bfdc14964d912d1e5fa19f8d00bbcd36968d3d65
|
4
|
+
data.tar.gz: 5864b68a25e24bdbea94fcc3eebb4bd76fccc4cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31b1ae761b24d6e72d8f0b27e5f193549810365f0e3ea71ba7c514cc200407afb818ff7dfeda14e28014eb7ef6d81cd78686b5ee65d0096a46eb1b26ccf4ecb7
|
7
|
+
data.tar.gz: 10b305008ce90bfd3080b393c80e9e5e761f008d578a7588f9bb5ff6e10958ef53546a382285e80eae87ca19e48e9b2be61464557be5ab9f443e501fe75d4815
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,19 +1,21 @@
|
|
1
|
-
source "
|
2
|
-
# Add dependencies required to use your gem here.
|
3
|
-
# Example:
|
4
|
-
# gem "activesupport", ">= 2.3.5"
|
1
|
+
source "https://rubygems.org" do
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
5
|
|
6
|
-
gem 'httparty', '
|
7
|
-
gem 'json', '
|
8
|
-
gem 'multi_xml', '>= 0.5.2'
|
9
|
-
gem 'coveralls', :require => false
|
6
|
+
gem 'httparty', '~>0.13'
|
7
|
+
gem 'json', '~>1.8'
|
8
|
+
gem 'multi_xml', '>= 0.5.2'
|
9
|
+
gem 'coveralls', :require => false
|
10
|
+
gem 'zip'
|
10
11
|
|
11
|
-
# Add dependencies to develop your gem here.
|
12
|
-
# Include everything needed to run rake, tests, features, etc.
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
# Add dependencies to develop your gem here.
|
13
|
+
# Include everything needed to run rake, tests, features, etc.
|
14
|
+
group :development do
|
15
|
+
gem "rspec", ">= 0"
|
16
|
+
gem "rdoc", "~> 3.12"
|
17
|
+
gem "bundler", ">= 1.0.0"
|
18
|
+
gem "jeweler", ">= 1.8.4"
|
19
|
+
gem "simplecov", ">= 0"
|
20
|
+
end
|
21
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,113 @@
|
|
1
1
|
GEM
|
2
|
+
remote: https://rubygems.org/
|
2
3
|
specs:
|
4
|
+
addressable (2.3.8)
|
5
|
+
builder (3.2.2)
|
6
|
+
coveralls (0.8.2)
|
7
|
+
json (~> 1.8)
|
8
|
+
rest-client (>= 1.6.8, < 2)
|
9
|
+
simplecov (~> 0.10.0)
|
10
|
+
term-ansicolor (~> 1.3)
|
11
|
+
thor (~> 0.19.1)
|
12
|
+
descendants_tracker (0.0.4)
|
13
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
14
|
+
diff-lcs (1.2.5)
|
15
|
+
docile (1.1.5)
|
16
|
+
domain_name (0.5.24)
|
17
|
+
unf (>= 0.0.5, < 1.0.0)
|
18
|
+
faraday (0.9.1)
|
19
|
+
multipart-post (>= 1.2, < 3)
|
20
|
+
git (1.2.9.1)
|
21
|
+
github_api (0.12.4)
|
22
|
+
addressable (~> 2.3)
|
23
|
+
descendants_tracker (~> 0.0.4)
|
24
|
+
faraday (~> 0.8, < 0.10)
|
25
|
+
hashie (>= 3.4)
|
26
|
+
multi_json (>= 1.7.5, < 2.0)
|
27
|
+
nokogiri (~> 1.6.6)
|
28
|
+
oauth2
|
29
|
+
hashie (3.4.2)
|
30
|
+
highline (1.7.3)
|
31
|
+
http-cookie (1.0.2)
|
32
|
+
domain_name (~> 0.5)
|
33
|
+
httparty (0.13.5)
|
34
|
+
json (~> 1.8)
|
35
|
+
multi_xml (>= 0.5.2)
|
36
|
+
jeweler (2.0.1)
|
37
|
+
builder
|
38
|
+
bundler (>= 1.0)
|
39
|
+
git (>= 1.2.5)
|
40
|
+
github_api
|
41
|
+
highline (>= 1.6.15)
|
42
|
+
nokogiri (>= 1.5.10)
|
43
|
+
rake
|
44
|
+
rdoc
|
45
|
+
json (1.8.3)
|
46
|
+
jwt (1.5.1)
|
47
|
+
mime-types (2.6.1)
|
48
|
+
mini_portile (0.6.2)
|
49
|
+
multi_json (1.11.2)
|
50
|
+
multi_xml (0.5.5)
|
51
|
+
multipart-post (2.0.0)
|
52
|
+
netrc (0.10.3)
|
53
|
+
nokogiri (1.6.6.2)
|
54
|
+
mini_portile (~> 0.6.0)
|
55
|
+
oauth2 (1.0.0)
|
56
|
+
faraday (>= 0.8, < 0.10)
|
57
|
+
jwt (~> 1.0)
|
58
|
+
multi_json (~> 1.3)
|
59
|
+
multi_xml (~> 0.5)
|
60
|
+
rack (~> 1.2)
|
61
|
+
rack (1.6.4)
|
62
|
+
rake (10.4.2)
|
63
|
+
rdoc (3.12.2)
|
64
|
+
json (~> 1.4)
|
65
|
+
rest-client (1.8.0)
|
66
|
+
http-cookie (>= 1.0.2, < 2.0)
|
67
|
+
mime-types (>= 1.16, < 3.0)
|
68
|
+
netrc (~> 0.7)
|
69
|
+
rspec (3.3.0)
|
70
|
+
rspec-core (~> 3.3.0)
|
71
|
+
rspec-expectations (~> 3.3.0)
|
72
|
+
rspec-mocks (~> 3.3.0)
|
73
|
+
rspec-core (3.3.2)
|
74
|
+
rspec-support (~> 3.3.0)
|
75
|
+
rspec-expectations (3.3.1)
|
76
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
77
|
+
rspec-support (~> 3.3.0)
|
78
|
+
rspec-mocks (3.3.2)
|
79
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
80
|
+
rspec-support (~> 3.3.0)
|
81
|
+
rspec-support (3.3.0)
|
82
|
+
simplecov (0.10.0)
|
83
|
+
docile (~> 1.1.0)
|
84
|
+
json (~> 1.8)
|
85
|
+
simplecov-html (~> 0.10.0)
|
86
|
+
simplecov-html (0.10.0)
|
87
|
+
term-ansicolor (1.3.2)
|
88
|
+
tins (~> 1.0)
|
89
|
+
thor (0.19.1)
|
90
|
+
thread_safe (0.3.5)
|
91
|
+
tins (1.5.4)
|
92
|
+
unf (0.1.4)
|
93
|
+
unf_ext
|
94
|
+
unf_ext (0.0.7.1)
|
95
|
+
zip (2.0.2)
|
3
96
|
|
4
97
|
PLATFORMS
|
5
98
|
ruby
|
6
99
|
|
7
100
|
DEPENDENCIES
|
101
|
+
bundler (>= 1.0.0)!
|
102
|
+
coveralls!
|
103
|
+
httparty (~> 0.13)!
|
104
|
+
jeweler (>= 1.8.4)!
|
105
|
+
json (~> 1.8)!
|
106
|
+
multi_xml (>= 0.5.2)!
|
107
|
+
rdoc (~> 3.12)!
|
108
|
+
rspec!
|
109
|
+
simplecov!
|
110
|
+
zip!
|
111
|
+
|
112
|
+
BUNDLED WITH
|
113
|
+
1.10.3
|
data/aemo.gemspec
CHANGED
@@ -1,26 +1,29 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'aemo/version'
|
3
4
|
|
4
5
|
Gem::Specification.new do |s|
|
5
6
|
s.name = 'aemo'
|
6
|
-
s.version =
|
7
|
+
s.version = AEMO::VERSION
|
7
8
|
s.platform = Gem::Platform::RUBY
|
8
|
-
s.date = '2015-
|
9
|
+
s.date = '2015-08-27'
|
9
10
|
s.summary = 'AEMO Gem'
|
10
11
|
s.description = 'Gem providing functionality for the Australian Energy Market Operator data'
|
11
|
-
s.authors = ['Joel Courtney']
|
12
|
-
s.email = ['jcourtney@cozero.com.au']
|
12
|
+
s.authors = ['Joel Courtney','Stuart Auld']
|
13
|
+
s.email = ['jcourtney@cozero.com.au','sauld@cozero.com.au']
|
13
14
|
s.homepage = 'https://github.com/jufemaiz/aemo'
|
14
15
|
s.license = 'MIT'
|
15
|
-
|
16
|
+
|
16
17
|
s.required_ruby_version = '>= 1.9.3'
|
17
18
|
|
18
19
|
s.add_dependency 'json', '~> 1.8'
|
20
|
+
s.add_dependency 'nokogiri', '~> 1.6', '>= 1.6.6'
|
21
|
+
s.add_dependency 'zip', '~>2.0'
|
19
22
|
s.add_runtime_dependency 'multi_xml', '~> 0.5', '>= 0.5.2'
|
20
|
-
s.add_runtime_dependency 'httparty',
|
23
|
+
s.add_runtime_dependency 'httparty', '~> 0.13', '>= 0.13.1'
|
21
24
|
|
22
25
|
s.files = `git ls-files`.split("\n")
|
23
26
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
24
27
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
28
|
s.require_paths = ['lib']
|
26
|
-
end
|
29
|
+
end
|
data/lib/aemo/msats.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'httparty'
|
2
2
|
require 'aemo'
|
3
|
-
require 'zip'
|
3
|
+
# require 'zip'
|
4
4
|
require 'nokogiri'
|
5
5
|
require 'digest/sha1'
|
6
6
|
|
@@ -18,22 +18,21 @@ module AEMO
|
|
18
18
|
include HTTParty
|
19
19
|
# We like to debug
|
20
20
|
# debug_output $stdout
|
21
|
-
|
21
|
+
|
22
22
|
# We like to SSLv3
|
23
23
|
ssl_version :SSLv3
|
24
24
|
|
25
25
|
# Where we like to party
|
26
26
|
base_uri 'https://msats.prod.nemnet.net.au/msats/ws/'
|
27
|
-
|
27
|
+
|
28
28
|
def initialize(options = {})
|
29
29
|
@@auth = {username: nil, password: nil}
|
30
|
-
@retailer = 'COZEROER'
|
31
30
|
|
32
31
|
@@auth[:username] = options[:username] if options[:username].is_a?(String)
|
33
32
|
@@auth[:password] = options[:password] if options[:password].is_a?(String)
|
34
33
|
@@participant_id = options[:participant_id] if options[:participant_id].is_a?(String)
|
35
34
|
end
|
36
|
-
|
35
|
+
|
37
36
|
# Single NMI Master (C4) Report
|
38
37
|
# /C4/PARTICIPANT_IDENTIFIER?transactionId=XXX&nmi=XXX&checksum=X&type=XXX&reason=XXX
|
39
38
|
#
|
@@ -51,10 +50,10 @@ module AEMO
|
|
51
50
|
options[:participantId] ||= nil
|
52
51
|
options[:roleId] ||= nil
|
53
52
|
options[:inittransId] ||= nil
|
54
|
-
|
53
|
+
|
55
54
|
query = {
|
56
55
|
transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
|
57
|
-
NMI: nmi.nmi, # Note: AEMO
|
56
|
+
NMI: nmi.nmi, # Note: AEMO has case sensitivity but no consistency across requests.
|
58
57
|
fromDate: from_date,
|
59
58
|
toDate: to_date,
|
60
59
|
asatDate: as_at_date,
|
@@ -62,9 +61,13 @@ module AEMO
|
|
62
61
|
roleId: options[:role_id],
|
63
62
|
inittransId: options[:init_trans_id],
|
64
63
|
}
|
65
|
-
|
66
|
-
response = self.get( "/C4/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
|
67
|
-
response.
|
64
|
+
|
65
|
+
response = self.get( "/C4/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
|
66
|
+
if response.response.code != '200'
|
67
|
+
response
|
68
|
+
else
|
69
|
+
response.parsed_response['aseXML']['Transactions']['Transaction']['ReportResponse']['ReportResults']
|
70
|
+
end
|
68
71
|
end
|
69
72
|
|
70
73
|
# MSATS Limits
|
@@ -76,27 +79,35 @@ module AEMO
|
|
76
79
|
transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
|
77
80
|
}
|
78
81
|
response = self.get( "/MSATSLimits/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
|
79
|
-
response.
|
82
|
+
if response.response.code != '200'
|
83
|
+
response
|
84
|
+
else
|
85
|
+
response.parsed_response['aseXML']['Transactions']['Transaction']['ReportResponse']['ReportResults']
|
86
|
+
end
|
80
87
|
end
|
81
|
-
|
88
|
+
|
82
89
|
# NMI Discovery - By Delivery Point Identifier
|
83
90
|
#
|
84
91
|
# @param [String] jurisdiction_code The Jurisdiction Code
|
85
|
-
# @param [Integer] delivery_point_identifier Delivery Point
|
92
|
+
# @param [Integer] delivery_point_identifier Delivery Point Identifier
|
86
93
|
# @return [Hash] The response
|
87
94
|
def self.nmi_discovery_by_delivery_point_identifier(jurisdiction_code,delivery_point_identifier)
|
88
95
|
raise ArgumentError, 'jurisdiction_code is not valid' unless %w(ACT NEM NSW QLD SA VIC TAS).include?(jurisdiction_code)
|
89
96
|
raise ArgumentError, 'delivery_point_identifier is not valid' unless delivery_point_identifier.respond_to?("to_i")
|
90
97
|
raise ArgumentError, 'delivery_point_identifier is not valid' if( delivery_point_identifier.to_i < 10000000 || delivery_point_identifier.to_i > 99999999)
|
91
|
-
|
98
|
+
|
92
99
|
query = {
|
93
100
|
transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
|
94
101
|
jurisdictionCode: jurisdiction_code,
|
95
102
|
deliveryPointIdentifier: delivery_point_identifier.to_i
|
96
103
|
}
|
97
|
-
|
104
|
+
|
98
105
|
response = self.get( "/NMIDiscovery/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
|
99
|
-
response.
|
106
|
+
if response.response.code != '200'
|
107
|
+
response
|
108
|
+
else
|
109
|
+
response.parsed_response['aseXML']['Transactions']['Transaction']['NMIDiscoveryResponse']['NMIStandingData']
|
110
|
+
end
|
100
111
|
end
|
101
112
|
|
102
113
|
# NMI Discovery - By Meter Serial Numner
|
@@ -106,15 +117,19 @@ module AEMO
|
|
106
117
|
# @return [Hash] The response
|
107
118
|
def self.nmi_discovery_by_meter_serial_number(jurisdiction_code,meter_serial_number)
|
108
119
|
raise ArgumentError, 'jurisdiction_code is not valid' unless %w(ACT NEM NSW QLD SA VIC TAS).include?(jurisdiction_code)
|
109
|
-
|
120
|
+
|
110
121
|
query = {
|
111
122
|
transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
|
112
123
|
jurisdictionCode: jurisdiction_code,
|
113
124
|
meterSerialNumber: meter_serial_number.to_i
|
114
125
|
}
|
115
|
-
|
126
|
+
|
116
127
|
response = self.get( "/NMIDiscovery/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
|
117
|
-
response.
|
128
|
+
if response.response.code != '200'
|
129
|
+
response
|
130
|
+
else
|
131
|
+
response.parsed_response['aseXML']['Transactions']['Transaction']['NMIDiscoveryResponse']['NMIStandingData']
|
132
|
+
end
|
118
133
|
end
|
119
134
|
|
120
135
|
# NMI Discovery - By Address
|
@@ -124,7 +139,7 @@ module AEMO
|
|
124
139
|
# @return [Hash] The response
|
125
140
|
def self.nmi_discovery_by_address(jurisdiction_code,options = {})
|
126
141
|
raise ArgumentError, 'jurisdiction_code is not valid' unless %w(ACT NEM NSW QLD SA VIC TAS).include?(jurisdiction_code)
|
127
|
-
|
142
|
+
|
128
143
|
options[:building_or_property_name] ||= nil
|
129
144
|
options[:location_descriptor] ||= nil
|
130
145
|
options[:lot_number] ||= nil
|
@@ -140,7 +155,7 @@ module AEMO
|
|
140
155
|
options[:suburb_or_place_or_locality] ||= nil
|
141
156
|
options[:postcode] ||= nil
|
142
157
|
options[:state_or_territory] ||= jurisdiction_code
|
143
|
-
|
158
|
+
|
144
159
|
query = {
|
145
160
|
transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
|
146
161
|
jurisdictionCode: jurisdiction_code,
|
@@ -160,9 +175,14 @@ module AEMO
|
|
160
175
|
postcode: options[:postcode],
|
161
176
|
stateOrTerritory: options[:state_or_territory]
|
162
177
|
}
|
163
|
-
|
178
|
+
|
164
179
|
response = self.get( "/NMIDiscovery/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
|
165
|
-
response.
|
180
|
+
if response.response.code != '200'
|
181
|
+
response
|
182
|
+
else
|
183
|
+
myresponse = response.parsed_response['aseXML']['Transactions']['Transaction']['NMIDiscoveryResponse']['NMIStandingData']
|
184
|
+
myresponse.is_a?(Hash)? [ myresponse ] : myresponse
|
185
|
+
end
|
166
186
|
end
|
167
187
|
|
168
188
|
# NMI Detail
|
@@ -175,7 +195,7 @@ module AEMO
|
|
175
195
|
end
|
176
196
|
options[:type] ||= nil
|
177
197
|
options[:reason] ||= nil
|
178
|
-
|
198
|
+
|
179
199
|
query = {
|
180
200
|
transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
|
181
201
|
nmi: nmi.nmi,
|
@@ -183,11 +203,15 @@ module AEMO
|
|
183
203
|
type: options[:type],
|
184
204
|
reason: options[:reason]
|
185
205
|
}
|
186
|
-
|
206
|
+
|
187
207
|
response = self.get( "/NMIDetail/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
|
188
|
-
response.
|
208
|
+
if response.response.code != '200'
|
209
|
+
response
|
210
|
+
else
|
211
|
+
response.parsed_response['aseXML']['Transactions']['Transaction']['NMIStandingDataResponse']['NMIStandingData']
|
212
|
+
end
|
189
213
|
end
|
190
|
-
|
214
|
+
|
191
215
|
# Participant System Status
|
192
216
|
# /ParticipantSystemStatus/PARTICIPANT_IDENTIFIER?transactionId=XXX
|
193
217
|
#
|
@@ -197,10 +221,14 @@ module AEMO
|
|
197
221
|
transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
|
198
222
|
}
|
199
223
|
response = self.get( "/ParticipantSystemStatus/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
|
200
|
-
response.
|
224
|
+
if response.response.code != '200'
|
225
|
+
response
|
226
|
+
else
|
227
|
+
response.parsed_response['aseXML']['Transactions']['Transaction']['ReportResponse']['ReportResults']
|
228
|
+
end
|
201
229
|
end
|
202
|
-
|
203
|
-
|
230
|
+
|
231
|
+
|
204
232
|
# Sets the authentication credentials in a class variable.
|
205
233
|
#
|
206
234
|
# @param [String] email cl.ly email
|
@@ -210,13 +238,13 @@ module AEMO
|
|
210
238
|
@@participant_id = participant_id
|
211
239
|
@@auth = {username: username, password: password}
|
212
240
|
end
|
213
|
-
|
241
|
+
|
214
242
|
# Check if credentials are available to use
|
215
243
|
#
|
216
244
|
# @return [Boolean] true/false if credentials are available
|
217
245
|
def self.can_authenticate?
|
218
246
|
!(@@participant_id.nil? || @@auth[:username].nil? || @@auth[:username].nil?)
|
219
247
|
end
|
220
|
-
|
248
|
+
|
221
249
|
end
|
222
250
|
end
|
data/lib/aemo/nem12.rb
CHANGED
@@ -12,7 +12,7 @@ module AEMO
|
|
12
12
|
500 => 'B2B Details',
|
13
13
|
900 => 'End'
|
14
14
|
}
|
15
|
-
|
15
|
+
|
16
16
|
TRANSACTION_CODE_FLAGS = {
|
17
17
|
'A' => 'Alteration',
|
18
18
|
'C' => 'Meter Reconfiguration',
|
@@ -24,7 +24,7 @@ module AEMO
|
|
24
24
|
'S' => 'Special Read',
|
25
25
|
'R' => 'Removal of Meter'
|
26
26
|
}
|
27
|
-
|
27
|
+
|
28
28
|
UOM = {
|
29
29
|
'MWh' => { :name => 'Megawatt Hour', :multiplier => 1e6 },
|
30
30
|
'kWh' => { :name => 'Kilowatt Hour', :multiplier => 1e3 },
|
@@ -50,7 +50,7 @@ module AEMO
|
|
50
50
|
'A' => { :name => 'Ampere', :multiplier => 1 },
|
51
51
|
'pf' => { :name => 'Power Factor', :multiplier => 1 }
|
52
52
|
}
|
53
|
-
|
53
|
+
|
54
54
|
UOM_NON_SPEC_MAPPING = {
|
55
55
|
'MWH' => 'MWh',
|
56
56
|
'KWH' => 'kWh',
|
@@ -76,7 +76,7 @@ module AEMO
|
|
76
76
|
'A' => 'A',
|
77
77
|
'PF' => 'pf'
|
78
78
|
}
|
79
|
-
|
79
|
+
|
80
80
|
QUALITY_FLAGS = {
|
81
81
|
'A' => 'Actual Data',
|
82
82
|
'E' => 'Forward Estimated Data',
|
@@ -85,7 +85,7 @@ module AEMO
|
|
85
85
|
'S' => 'Substituted Data',
|
86
86
|
'V' => 'Variable Data',
|
87
87
|
}
|
88
|
-
|
88
|
+
|
89
89
|
METHOD_FLAGS = {
|
90
90
|
11 => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Check", description: "" },
|
91
91
|
12 => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Calculated", description: "" },
|
@@ -118,7 +118,7 @@ module AEMO
|
|
118
118
|
74 => { type: ["SUB"], installation_type: 7, short_descriptor: "Agreed", description: "" },
|
119
119
|
75 => { type: ["EST"], installation_type: 7, short_descriptor: "Existing Table", description: "" }
|
120
120
|
}
|
121
|
-
|
121
|
+
|
122
122
|
REASON_CODES = {
|
123
123
|
0 => 'Free Text Description',
|
124
124
|
1 => 'Meter/Equipment Changed',
|
@@ -220,9 +220,9 @@ module AEMO
|
|
220
220
|
97 => 'Excluded Data',
|
221
221
|
98 => 'Parity Error',
|
222
222
|
99 => 'Energy Type (Register Changed)'
|
223
|
-
|
223
|
+
|
224
224
|
}
|
225
|
-
|
225
|
+
|
226
226
|
DATA_STREAM_SUFFIX = {
|
227
227
|
# Averaged Data Streams
|
228
228
|
'A' => { :stream => 'Average', :description => 'Import', :units => 'kWh' },
|
@@ -250,18 +250,19 @@ module AEMO
|
|
250
250
|
'W' => { :stream => 'Check', :description => 'Par Metering Path', :units => '' },
|
251
251
|
'Z' => { :stream => 'Check', :description => 'Volts or V2h or Amps or A2h', :units => '' },
|
252
252
|
# Net Meter Streams
|
253
|
-
|
254
|
-
'
|
253
|
+
# AEMO: NOTE THAT D AND J ARE PREVIOUSLY DEFINED
|
254
|
+
# 'D' => { :stream => 'Net', :description => 'Net', :units => 'kWh' },
|
255
|
+
# 'J' => { :stream => 'Net', :description => 'Net', :units => 'kVArh' }
|
255
256
|
}
|
256
|
-
|
257
|
+
|
257
258
|
@nmi = nil
|
258
259
|
@data_details = []
|
259
260
|
@interval_data = []
|
260
261
|
@interval_events = []
|
261
|
-
|
262
|
+
|
262
263
|
attr_accessor :nmi, :file_contents
|
263
264
|
attr_reader :data_details, :interval_data, :interval_events
|
264
|
-
|
265
|
+
|
265
266
|
# Initialize a NEM12 file
|
266
267
|
def initialize(nmi,options={})
|
267
268
|
@nmi = nmi
|
@@ -272,7 +273,7 @@ module AEMO
|
|
272
273
|
eval "self.#{key} = #{options[key]}"
|
273
274
|
end
|
274
275
|
end
|
275
|
-
|
276
|
+
|
276
277
|
# @return [Integer] checksum of the NMI
|
277
278
|
def nmi_checksum
|
278
279
|
summation = 0
|
@@ -283,7 +284,7 @@ module AEMO
|
|
283
284
|
end
|
284
285
|
value = value.to_s.split(//).map{|i| i.to_i}.reduce(:+)
|
285
286
|
summation += value
|
286
|
-
end
|
287
|
+
end
|
287
288
|
checksum = (10 - (summation % 10)) % 10
|
288
289
|
checksum
|
289
290
|
end
|
@@ -310,7 +311,7 @@ module AEMO
|
|
310
311
|
:to_participant => csv[4]
|
311
312
|
}
|
312
313
|
end
|
313
|
-
|
314
|
+
|
314
315
|
# Parses the NMI Data Details
|
315
316
|
# @param line [String] A single line in string format
|
316
317
|
# @return [Hash] the line parsed into a hash of information
|
@@ -334,7 +335,7 @@ module AEMO
|
|
334
335
|
raise ArgumentError, 'UOM is not valid' unless UOM.keys.map{|k| k.upcase}.include?(csv[7].upcase)
|
335
336
|
raise ArgumentError, 'IntervalLength is not valid' unless %w(1 5 10 15 30).include?(csv[8])
|
336
337
|
# raise ArgumentError, 'NextScheduledReadDate is not valid' if csv[9].match(/\d{8}/).nil? || csv[9] != Time.parse("#{csv[9]}").strftime('%Y%m%d')
|
337
|
-
|
338
|
+
|
338
339
|
@nmi = csv[1]
|
339
340
|
|
340
341
|
# Push onto the stack
|
@@ -351,7 +352,7 @@ module AEMO
|
|
351
352
|
:next_scheduled_read_date => csv[9],
|
352
353
|
}
|
353
354
|
end
|
354
|
-
|
355
|
+
|
355
356
|
# @param line [String] A single line in string format
|
356
357
|
# @return [Array of hashes] the line parsed into a hash of information
|
357
358
|
def parse_nem12_300(line,options={})
|
@@ -360,7 +361,7 @@ module AEMO
|
|
360
361
|
raise TypeError, 'Expected NMI Data Details to exist with IntervalLength specified' if @data_details.last.nil? || @data_details.last[:interval_length].nil?
|
361
362
|
number_of_intervals = 1440 / @data_details.last[:interval_length]
|
362
363
|
intervals_offset = number_of_intervals + 2
|
363
|
-
|
364
|
+
|
364
365
|
raise ArgumentError, 'RecordIndicator is not 300' if csv[0] != '300'
|
365
366
|
raise ArgumentError, 'IntervalDate is not valid' if csv[1].match(/\d{8}/).nil? || csv[1] != Time.parse("#{csv[1]}").strftime('%Y%m%d')
|
366
367
|
(2..(number_of_intervals+1)).each do |i|
|
@@ -385,7 +386,7 @@ module AEMO
|
|
385
386
|
raise ArgumentError, 'MSATSLoadDateTime is not valid' if csv[intervals_offset + 4].match(/\d{14}/).nil? || csv[intervals_offset + 4] != Time.parse("#{csv[intervals_offset + 4]}").strftime('%Y%m%d%H%M%S')
|
386
387
|
end
|
387
388
|
end
|
388
|
-
|
389
|
+
|
389
390
|
# Deal with flags if necessary
|
390
391
|
flag = nil
|
391
392
|
# Based on QualityMethod and ReasonCode
|
@@ -399,7 +400,7 @@ module AEMO
|
|
399
400
|
flag[:reason_code] = csv[intervals_offset + 1].to_i
|
400
401
|
end
|
401
402
|
end
|
402
|
-
|
403
|
+
|
403
404
|
base_interval = { data_details: @data_details.last, datetime: Time.parse("#{csv[1]}000000+1000"), value: nil, flag: flag}
|
404
405
|
|
405
406
|
intervals = []
|
@@ -412,7 +413,7 @@ module AEMO
|
|
412
413
|
@interval_data += intervals
|
413
414
|
intervals
|
414
415
|
end
|
415
|
-
|
416
|
+
|
416
417
|
# @param line [String] A single line in string format
|
417
418
|
# @return [Hash] the line parsed into a hash of information
|
418
419
|
def parse_nem12_400(line)
|
@@ -423,14 +424,14 @@ module AEMO
|
|
423
424
|
raise ArgumentError, 'QualityMethod is not valid' if csv[3].match(/^([AN]|([AEFNSV]\d{2}))$/).nil?
|
424
425
|
# raise ArgumentError, 'ReasonCode is not valid' if (csv[4].nil? && csv[3].match(/^ANE/)) || csv[4].match(/^\d{3}?$/) || csv[3].match(/^ANE/)
|
425
426
|
# raise ArgumentError, 'ReasonDescription is not valid' if (csv[4].nil? && csv[3].match(/^ANE/)) || ( csv[5].match(/^$/) && csv[4].match(/^0$/) )
|
426
|
-
|
427
|
+
|
427
428
|
interval_events = []
|
428
|
-
|
429
|
+
|
429
430
|
# Only need to update flags for EFSV
|
430
431
|
unless %w(A N).include?csv[3]
|
431
432
|
number_of_intervals = 1440 / @data_details.last[:interval_length]
|
432
433
|
interval_start_point = @interval_data.length - number_of_intervals
|
433
|
-
|
434
|
+
|
434
435
|
# For each of these
|
435
436
|
base_interval_event = { datetime: nil, quality_method: csv[3], reason_code: (csv[4].nil? ? nil : csv[4].to_i), reason_description: csv[5] }
|
436
437
|
|
@@ -455,17 +456,17 @@ module AEMO
|
|
455
456
|
end
|
456
457
|
interval_events
|
457
458
|
end
|
458
|
-
|
459
|
+
|
459
460
|
# @param line [String] A single line in string format
|
460
461
|
# @return [Hash] the line parsed into a hash of information
|
461
462
|
def parse_nem12_500(line,options={})
|
462
463
|
end
|
463
|
-
|
464
|
+
|
464
465
|
# @param line [String] A single line in string format
|
465
466
|
# @return [Hash] the line parsed into a hash of information
|
466
467
|
def parse_nem12_900(line,options={})
|
467
468
|
end
|
468
|
-
|
469
|
+
|
469
470
|
# Turns the flag to a string
|
470
471
|
#
|
471
472
|
# @param [Hash] the object of a flag
|
@@ -493,31 +494,31 @@ module AEMO
|
|
493
494
|
end
|
494
495
|
values
|
495
496
|
end
|
496
|
-
|
497
|
+
|
497
498
|
# @return [Array] CSV of a NEM12 file a given Meter + Data Stream for easy reading
|
498
499
|
def to_csv
|
499
500
|
headers = ['nmi','suffix','units','datetime','value','flags']
|
500
501
|
([headers]+self.to_a.map{|row| row[3]=row[3].strftime("%Y%m%d%H%M%S%z"); row}).map{|row| row.join(',')}.join("\n")
|
501
502
|
end
|
502
|
-
|
503
|
+
|
503
504
|
# @param nmi [String] a NMI that is to be checked for validity
|
504
505
|
# @return [Boolean] determines if the NMI is valid
|
505
506
|
def self.valid_nmi?(nmi)
|
506
507
|
(nmi.class == String && nmi.length == 10 && !nmi.match(/^[A-Z1-9][A-Z0-9]{9}$/).nil?)
|
507
508
|
end
|
508
|
-
|
509
|
+
|
509
510
|
# @param path_to_file [String] the path to a file
|
510
|
-
# @return [] NEM12 object
|
511
|
+
# @return [] NEM12 object
|
511
512
|
def self.parse_nem12_file(path_to_file, strict = false)
|
512
513
|
parse_nem12(File.read(path_to_file),strict)
|
513
514
|
end
|
514
|
-
|
515
|
+
|
515
516
|
# @param contents [String] the path to a file
|
516
517
|
# @return [Array[AEMO::NEM12]] An array of NEM12 objects
|
517
518
|
def self.parse_nem12(contents, strict=false)
|
518
519
|
file_contents = contents.gsub(/\r/,"\n").gsub(/\n\n/,"\n").split("\n").delete_if{|line| line.empty? }
|
519
520
|
raise ArgumentError, 'First row should be have a RecordIndicator of 100 and be of type Header Record' unless file_contents.first.parse_csv[0] == '100'
|
520
|
-
|
521
|
+
|
521
522
|
nem12s = []
|
522
523
|
nem12_100 = AEMO::NEM12.parse_nem12_100(file_contents.first,:strict => strict)
|
523
524
|
nem12 = nil
|
@@ -539,6 +540,6 @@ module AEMO
|
|
539
540
|
# Return the array of NEM12 groups
|
540
541
|
nem12s
|
541
542
|
end
|
542
|
-
|
543
|
+
|
543
544
|
end
|
544
|
-
end
|
545
|
+
end
|
data/lib/aemo/nmi.rb
CHANGED
@@ -161,7 +161,7 @@ module AEMO
|
|
161
161
|
excludes: [
|
162
162
|
]
|
163
163
|
},
|
164
|
-
'
|
164
|
+
'ETSATP' => {
|
165
165
|
title: 'ElectraNet SA',
|
166
166
|
friendly_title: 'ElectraNet SA',
|
167
167
|
state: 'SA',
|
@@ -398,7 +398,7 @@ module AEMO
|
|
398
398
|
}
|
399
399
|
# Transmission Node Identifier Codes are loaded from a json file
|
400
400
|
# Obtained from http://www.nemweb.com.au/
|
401
|
-
#
|
401
|
+
#
|
402
402
|
# See /lib/data for further data manipulation required
|
403
403
|
TNI_CODES = JSON.parse(File.read(File.join(File.dirname(__FILE__),'..','data','aemo-tni.json')))
|
404
404
|
# Distribution Loss Factor Codes are loaded from a json file
|
@@ -406,7 +406,7 @@ module AEMO
|
|
406
406
|
# Last accessed 2015-02-06
|
407
407
|
# See /lib/data for further data manipulation required
|
408
408
|
DLF_CODES = JSON.parse(File.read(File.join(File.dirname(__FILE__),'..','data','aemo-dlf.json')))
|
409
|
-
|
409
|
+
|
410
410
|
# [String] National Meter Identifier
|
411
411
|
@nmi = nil
|
412
412
|
@msats_detail = nil
|
@@ -421,9 +421,9 @@ module AEMO
|
|
421
421
|
@meters = nil
|
422
422
|
@roles = nil
|
423
423
|
@data_streams = nil
|
424
|
-
|
424
|
+
|
425
425
|
attr_accessor :nmi, :msats_detail, :tni, :dlf, :customer_classification_code, :customer_threshold_code, :jurisdiction_code, :classification_code, :status, :address, :meters, :roles, :data_streams
|
426
|
-
|
426
|
+
|
427
427
|
# Initialize a NEM12 file
|
428
428
|
#
|
429
429
|
# @param nmi [String] the National Meter Identifier (NMI)
|
@@ -433,7 +433,7 @@ module AEMO
|
|
433
433
|
raise ArgumentError.new("NMI is not a string") unless nmi.is_a?(String)
|
434
434
|
raise ArgumentError.new("NMI is not 10 characters") unless nmi.length == 10
|
435
435
|
raise ArgumentError.new("NMI is not constructed with valid characters") unless AEMO::NMI.valid_nmi?(nmi)
|
436
|
-
|
436
|
+
|
437
437
|
@nmi = nmi
|
438
438
|
@meters = []
|
439
439
|
@roles = {}
|
@@ -446,14 +446,14 @@ module AEMO
|
|
446
446
|
def valid_nmi?
|
447
447
|
AEMO::NMI.valid_nmi?(@nmi)
|
448
448
|
end
|
449
|
-
|
449
|
+
|
450
450
|
# Find the Network of NMI
|
451
451
|
#
|
452
452
|
# @returns [Hash] The Network information
|
453
453
|
def network
|
454
454
|
AEMO::NMI.network(@nmi)
|
455
455
|
end
|
456
|
-
|
456
|
+
|
457
457
|
# A function to calculate the checksum value for a given National Meter Identifier
|
458
458
|
#
|
459
459
|
# @param checksum_value [Integer] the checksum value to check against the current National Meter Identifier's checksum value
|
@@ -474,20 +474,20 @@ module AEMO
|
|
474
474
|
end
|
475
475
|
value = value.to_s.split(//).map{|i| i.to_i}.reduce(:+)
|
476
476
|
summation += value
|
477
|
-
end
|
477
|
+
end
|
478
478
|
checksum = (10 - (summation % 10)) % 10
|
479
479
|
checksum
|
480
480
|
end
|
481
|
-
|
481
|
+
|
482
482
|
# Provided MSATS is configured, gets the MSATS data for the NMI
|
483
483
|
#
|
484
484
|
# @return [Hash] MSATS NMI Detail data
|
485
485
|
def raw_msats_nmi_detail
|
486
486
|
raise ArgumentError, 'MSATS has no authentication credentials' unless AEMO::MSATS.can_authenticate?
|
487
|
-
|
487
|
+
|
488
488
|
AEMO::MSATS.nmi_detail(@nmi)
|
489
489
|
end
|
490
|
-
|
490
|
+
|
491
491
|
# Provided MSATS is configured, uses the raw MSATS data to augment NMI information
|
492
492
|
#
|
493
493
|
# @return [self] returns self
|
@@ -573,7 +573,7 @@ module AEMO
|
|
573
573
|
def meters_by_status(status = 'C')
|
574
574
|
@meters.select{|x| x.status == "#{status}"}
|
575
575
|
end
|
576
|
-
|
576
|
+
|
577
577
|
# Returns the data_stream OpenStructs for the requested status (A/I)
|
578
578
|
#
|
579
579
|
# @param status [String] the stateus [A|I]
|
@@ -581,14 +581,14 @@ module AEMO
|
|
581
581
|
def data_streams_by_status(status = 'A')
|
582
582
|
@data_streams.select{|x| x.status == "#{status}"}
|
583
583
|
end
|
584
|
-
|
584
|
+
|
585
585
|
# The current daily load
|
586
586
|
#
|
587
587
|
# @return [Integer] the current daily load for the meter
|
588
588
|
def current_daily_load
|
589
589
|
data_streams_by_status().inject(0) { |sum, stream| sum += stream.averaged_daily_load.to_i }
|
590
590
|
end
|
591
|
-
|
591
|
+
|
592
592
|
# A function to validate the NMI provided
|
593
593
|
#
|
594
594
|
# @param nmi [String] the nmi to be checked
|
@@ -606,7 +606,7 @@ module AEMO
|
|
606
606
|
nmi = AEMO::NMI.new(nmi)
|
607
607
|
nmi.valid_checksum?(checksum_value)
|
608
608
|
end
|
609
|
-
|
609
|
+
|
610
610
|
# Find the Network for a given NMI
|
611
611
|
#
|
612
612
|
# @param nmi [String] NMI
|
@@ -633,5 +633,5 @@ module AEMO
|
|
633
633
|
end
|
634
634
|
|
635
635
|
end
|
636
|
-
|
636
|
+
|
637
637
|
end
|
data/lib/aemo/version.rb
CHANGED
@@ -22,8 +22,8 @@
|
|
22
22
|
# @author Joel Courtney <euphemize@gmail.com>
|
23
23
|
module AEMO
|
24
24
|
# aemo version
|
25
|
-
VERSION = '0.1.
|
25
|
+
VERSION = '0.1.15'
|
26
26
|
|
27
27
|
# aemo version split amongst different revisions
|
28
28
|
MAJOR_VERSION, MINOR_VERSION, REVISION = VERSION.split('.').map(&:to_i)
|
29
|
-
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aemo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel Courtney
|
8
|
+
- Stuart Auld
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2015-
|
12
|
+
date: 2015-08-27 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: json
|
@@ -24,6 +25,40 @@ dependencies:
|
|
24
25
|
- - "~>"
|
25
26
|
- !ruby/object:Gem::Version
|
26
27
|
version: '1.8'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: nokogiri
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.6'
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.6.6
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - "~>"
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '1.6'
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.6.6
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: zip
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
27
62
|
- !ruby/object:Gem::Dependency
|
28
63
|
name: multi_xml
|
29
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,6 +103,7 @@ description: Gem providing functionality for the Australian Energy Market Operat
|
|
68
103
|
data
|
69
104
|
email:
|
70
105
|
- jcourtney@cozero.com.au
|
106
|
+
- sauld@cozero.com.au
|
71
107
|
executables: []
|
72
108
|
extensions: []
|
73
109
|
extra_rdoc_files: []
|
@@ -223,7 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
223
259
|
version: '0'
|
224
260
|
requirements: []
|
225
261
|
rubyforge_project:
|
226
|
-
rubygems_version: 2.
|
262
|
+
rubygems_version: 2.4.6
|
227
263
|
signing_key:
|
228
264
|
specification_version: 4
|
229
265
|
summary: AEMO Gem
|
@@ -337,4 +373,3 @@ test_files:
|
|
337
373
|
- spec/fixtures/nmi_checksum.json
|
338
374
|
- spec/spec.opts
|
339
375
|
- spec/spec_helper.rb
|
340
|
-
has_rdoc:
|