aemo 0.1.4 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4cc289d4d7970c23318336e42aac4062aeba374
4
- data.tar.gz: 7f07cf1aa6fd5e47f23a035a82aaa51ee332fd84
3
+ metadata.gz: a401245a1f7b25723a1ae670079c4d58a444f8f6
4
+ data.tar.gz: c48680094298ec88e7cfe69d2e849c061177a2ec
5
5
  SHA512:
6
- metadata.gz: 16a93457ddbf13d0eca18f0be91e8de731c9f41a7511f528a410bedbfcb37f144d7225a4148e6456c17d4cbcf9eebce6df0b05199d23402ab231509d838e9392
7
- data.tar.gz: 288b14ba40832243066d5c15bf11dcc9189d2bb1926092dfc7117fef853d7c89ce505a5349bf9c9e9b597791d75ea42e716bf36d2b2e1a9f0dcdf0093d4b1885
6
+ metadata.gz: cc19ba3f91983da75207c8d6328ab4ecce573b849bbaddfa4720b73ce05e4db00a1f8604ad3adebe18b88755d9801894055820339d18890bc820eef6f9315e71
7
+ data.tar.gz: 3151c7067018f9d059ddfe27d361172aef4a2a66ad301a25a0690d336e7a95c606ff6df699434c02d1cbc325a311fb2dfadbc490682b5139eb9f808124b12941
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ # Ignore Stuff
2
+
3
+ # Don't include gems in the repository
4
+ *.gem
5
+ # Or documentation
6
+ .yardoc/
data/aemo.gemspec CHANGED
@@ -3,16 +3,15 @@ $:.push File.expand_path('../lib', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'aemo'
6
- s.version = '0.1.4'
6
+ s.version = '0.1.5'
7
7
  s.platform = Gem::Platform::RUBY
8
- s.date = '2014-12-05'
8
+ s.date = '2015-02-07'
9
9
  s.summary = 'AEMO Gem'
10
10
  s.description = 'Gem providing functionality for the Australian Energy Market Operator data'
11
11
  s.authors = ['Joel Courtney']
12
12
  s.email = ['jcourtney@cozero.com.au']
13
- s.homepage =
14
- 'https://bitbucket.org/jufemaiz/aemo-gem'
15
- s.license = 'MIT'
13
+ s.homepage = 'https://github.com/jufemaiz/aemo'
14
+ s.license = 'MIT'
16
15
 
17
16
  s.required_ruby_version = '>= 1.9.3'
18
17
 
data/lib/aemo.rb CHANGED
@@ -5,6 +5,8 @@ require 'aemo/market/interval.rb'
5
5
  require 'aemo/region.rb'
6
6
  require 'aemo/nem12.rb'
7
7
  require 'aemo/nmi.rb'
8
+ require 'aemo/msats.rb'
8
9
 
10
+ # AEMO Module to encapsulate all AEMO classes
9
11
  module AEMO
10
12
  end
data/lib/aemo/msats.rb ADDED
@@ -0,0 +1,222 @@
1
+ require 'httparty'
2
+ require 'aemo'
3
+ require 'zip'
4
+ require 'nokogiri'
5
+ require 'digest/sha1'
6
+
7
+ module AEMO
8
+
9
+ class MSATS
10
+ # Globally set request headers
11
+ HEADERS = {
12
+ 'User-Agent' => "Ruby.AEMO.MSATS.Api",
13
+ 'Accept' => 'text/xml',
14
+ 'Content-Type' => 'text/xml'
15
+ }
16
+
17
+ # We like to party
18
+ include HTTParty
19
+ # We like to debug
20
+ # debug_output $stdout
21
+
22
+ # We like to SSLv3
23
+ ssl_version :SSLv3
24
+
25
+ # Where we like to party
26
+ base_uri 'https://msats.prod.nemnet.net.au/msats/ws/'
27
+
28
+ def initialize(options = {})
29
+ @@auth = {username: nil, password: nil}
30
+ @retailer = 'COZEROER'
31
+
32
+ @@auth[:username] = options[:username] if options[:username].is_a?(String)
33
+ @@auth[:password] = options[:password] if options[:password].is_a?(String)
34
+ @@participant_id = options[:participant_id] if options[:participant_id].is_a?(String)
35
+ end
36
+
37
+ # Single NMI Master (C4) Report
38
+ # /C4/PARTICIPANT_IDENTIFIER?transactionId=XXX&nmi=XXX&checksum=X&type=XXX&reason=XXX
39
+ #
40
+ # @param [String,AEMO::NMI] nmi the NMI to run the master report against
41
+ # @param [Date,String] from_date the from date for the master report
42
+ # @param [Date,String] to_date the from date for the master report
43
+ # @return [Hash] A hashed view of the NMI Standing Data for a given NMI
44
+ def self.c4(nmi, from_date,to_date,as_at_date, options = {})
45
+
46
+ nmi = AEMO::NMI.new(nmi) if nmi.is_a?(String)
47
+ from_date = Date.parse(from_date) if from_date.is_a?(String)
48
+ to_date = Date.parse(to_date) if to_date.is_a?(String)
49
+ as_at_date = Date.parse(as_at_date) if as_at_date.is_a?(String)
50
+
51
+ options[:participantId] ||= nil
52
+ options[:roleId] ||= nil
53
+ options[:inittransId] ||= nil
54
+
55
+ query = {
56
+ transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
57
+ NMI: nmi.nmi, # Note: AEMO is a bunch of idiots, has case sensitivity but no consistency across requests. Muppets.
58
+ fromDate: from_date,
59
+ toDate: to_date,
60
+ asatDate: as_at_date,
61
+ participantId: @@participant_id,
62
+ roleId: options[:role_id],
63
+ inittransId: options[:init_trans_id],
64
+ }
65
+
66
+ response = self.get( "/C4/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
67
+ response.parsed_response['aseXML']['Transactions']['Transaction']['ReportResponse']['ReportResults']
68
+ end
69
+
70
+ # MSATS Limits
71
+ # /MSATSLimits/PARTICIPANT_IDENTIFIER?transactionId=XXX
72
+ #
73
+ # @return [Hash] The report results from the MSATS Limits web service query
74
+ def self.msats_limits
75
+ query = {
76
+ transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
77
+ }
78
+ response = self.get( "/MSATSLimits/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
79
+ response.parsed_response['aseXML']['Transactions']['Transaction']['ReportResponse']['ReportResults']
80
+ end
81
+
82
+ # NMI Discovery - By Delivery Point Identifier
83
+ #
84
+ # @param [String] jurisdiction_code The Jurisdiction Code
85
+ # @param [Integer] delivery_point_identifier Delivery Point Iddentifier
86
+ # @return [Hash] The response
87
+ def self.nmi_discovery_by_delivery_point_identifier(jurisdiction_code,delivery_point_identifier)
88
+ raise ArgumentError, 'jurisdiction_code is not valid' unless %w(ACT NEM NSW QLD SA VIC TAS).include?(jurisdiction_code)
89
+ raise ArgumentError, 'delivery_point_identifier is not valid' unless delivery_point_identifier.respond_to?("to_i")
90
+ raise ArgumentError, 'delivery_point_identifier is not valid' if( delivery_point_identifier.to_i < 10000000 || delivery_point_identifier.to_i > 99999999)
91
+
92
+ query = {
93
+ transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
94
+ jurisdictionCode: jurisdiction_code,
95
+ deliveryPointIdentifier: delivery_point_identifier.to_i
96
+ }
97
+
98
+ response = self.get( "/NMIDiscovery/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
99
+ response.parsed_response['aseXML']['Transactions']['Transaction']['NMIDiscoveryResponse']['NMIStandingData']
100
+ end
101
+
102
+ # NMI Discovery - By Meter Serial Numner
103
+ #
104
+ # @param [String] jurisdiction_code The Jurisdiction Code
105
+ # @param [Integer] meter_serial_number The meter's serial number
106
+ # @return [Hash] The response
107
+ def self.nmi_discovery_by_meter_serial_number(jurisdiction_code,meter_serial_number)
108
+ raise ArgumentError, 'jurisdiction_code is not valid' unless %w(ACT NEM NSW QLD SA VIC TAS).include?(jurisdiction_code)
109
+
110
+ query = {
111
+ transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
112
+ jurisdictionCode: jurisdiction_code,
113
+ meterSerialNumber: meter_serial_number.to_i
114
+ }
115
+
116
+ response = self.get( "/NMIDiscovery/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
117
+ response.parsed_response['aseXML']['Transactions']['Transaction']['NMIDiscoveryResponse']['NMIStandingData']
118
+ end
119
+
120
+ # NMI Discovery - By Address
121
+ #
122
+ # @param [String] jurisdiction_code The Jurisdiction Code
123
+ # @param [Integer] meter_serial_number The meter's serial number
124
+ # @return [Hash] The response
125
+ def self.nmi_discovery_by_address(jurisdiction_code,options = {})
126
+ raise ArgumentError, 'jurisdiction_code is not valid' unless %w(ACT NEM NSW QLD SA VIC TAS).include?(jurisdiction_code)
127
+
128
+ options[:building_or_property_name] ||= nil
129
+ options[:location_descriptor] ||= nil
130
+ options[:lot_number] ||= nil
131
+ options[:flat_or_unit_number] ||= nil
132
+ options[:flat_or_unit_type] ||= nil
133
+ options[:floor_or_level_number] ||= nil
134
+ options[:floor_or_level_type] ||= nil
135
+ options[:house_number] ||= nil
136
+ options[:house_number_suffix] ||= nil
137
+ options[:street_name] ||= nil
138
+ options[:street_suffix] ||= nil
139
+ options[:street_type] ||= nil
140
+ options[:suburb_or_place_or_locality] ||= nil
141
+ options[:postcode] ||= nil
142
+ options[:state_or_territory] ||= jurisdiction_code
143
+
144
+ query = {
145
+ transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
146
+ jurisdictionCode: jurisdiction_code,
147
+ buildingOrPropertyName: options[:building_or_property_name],
148
+ locationDescriptor: options[:location_descriptor],
149
+ lotNumber: options[:lot_number],
150
+ flatOrUnitNumber: options[:flat_or_unit_number],
151
+ flatOrUnitType: options[:flat_or_unit_type],
152
+ floorOrLevelNumber: options[:floor_or_level_number],
153
+ floorOrLevelType: options[:floor_or_level_type],
154
+ houseNumber: options[:house_number],
155
+ houseNumberSuffix: options[:house_number_suffix],
156
+ streetName: options[:street_name],
157
+ streetSuffix: options[:street_suffix],
158
+ streetType: options[:street_type],
159
+ suburbOrPlaceOrLocality: options[:suburb_or_place_or_locality],
160
+ postcode: options[:postcode],
161
+ stateOrTerritory: options[:state_or_territory]
162
+ }
163
+
164
+ response = self.get( "/NMIDiscovery/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
165
+ response.parsed_response['aseXML']['Transactions']['Transaction']['NMIDiscoveryResponse']['NMIStandingData']
166
+ end
167
+
168
+ # NMI Detail
169
+ # /NMIDetail/PARTICIPANT_IDENTIFIER?transactionId=XXX&nmi=XXX&checksum=X&type=XXX&reason=XXX
170
+ #
171
+ # @return [Hash] A hashed view of the NMI Standing Data for a given NMI
172
+ def self.nmi_detail(nmi, options = {})
173
+ if nmi.is_a?(String)
174
+ nmi = AEMO::NMI.new(nmi)
175
+ end
176
+ options[:type] ||= nil
177
+ options[:reason] ||= nil
178
+
179
+ query = {
180
+ transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
181
+ nmi: nmi.nmi,
182
+ checksum: nmi.checksum,
183
+ type: options[:type],
184
+ reason: options[:reason]
185
+ }
186
+
187
+ response = self.get( "/NMIDetail/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
188
+ response.parsed_response['aseXML']['Transactions']['Transaction']['NMIStandingDataResponse']['NMIStandingData']
189
+ end
190
+
191
+ # Participant System Status
192
+ # /ParticipantSystemStatus/PARTICIPANT_IDENTIFIER?transactionId=XXX
193
+ #
194
+ # @return [Hash] The report results from the Participant System Status web service query
195
+ def self.system_status
196
+ query = {
197
+ transactionId: Digest::SHA1.hexdigest(Time.now.to_s)[0..35],
198
+ }
199
+ response = self.get( "/ParticipantSystemStatus/#{@@participant_id}", basic_auth: @@auth, headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml'}, query: query )
200
+ response.parsed_response['aseXML']['Transactions']['Transaction']['ReportResponse']['ReportResults']
201
+ end
202
+
203
+
204
+ # Sets the authentication credentials in a class variable.
205
+ #
206
+ # @param [String] email cl.ly email
207
+ # @param [String] password cl.ly password
208
+ # @return [Hash] authentication credentials
209
+ def self.authorize(participant_id,username,password)
210
+ @@participant_id = participant_id
211
+ @@auth = {username: username, password: password}
212
+ end
213
+
214
+ # Check if credentials are available to use
215
+ #
216
+ # @return [Boolean] true/false if credentials are available
217
+ def self.can_authenticate?
218
+ !(@@participant_id.nil? || @@auth[:username].nil? || @@auth[:username].nil?)
219
+ end
220
+
221
+ end
222
+ end
data/lib/aemo/nem12.rb CHANGED
@@ -385,7 +385,21 @@ module AEMO
385
385
  end
386
386
  end
387
387
 
388
- base_interval = { :data_details => @data_details.last, :datetime => Time.parse("#{csv[1]}000000+1000"), :value => nil, :flag => nil}
388
+ # Deal with flags if necessary
389
+ flag = nil
390
+ # Based on QualityMethod and ReasonCode
391
+ if csv[intervals_offset + 0].length == 3 || !csv[intervals_offset + 1].nil?
392
+ flag ||= { quality_flag: nil, method_flag: nil, reason_code: nil }
393
+ if csv[intervals_offset + 0].length == 3
394
+ flag[:quality_flag] = csv[intervals_offset + 0][0]
395
+ flag[:method_flag] = csv[intervals_offset + 0][1,2]
396
+ end
397
+ unless csv[intervals_offset + 1].nil?
398
+ flag[:reason_code] = csv[intervals_offset + 1]
399
+ end
400
+ end
401
+
402
+ base_interval = { data_details: @data_details.last, datetime: Time.parse("#{csv[1]}000000+1000"), value: nil, flag: flag}
389
403
 
390
404
  intervals = []
391
405
  (2..(number_of_intervals+1)).each do |i|
@@ -458,34 +472,52 @@ module AEMO
458
472
  def parse_nem12_900(line,options={})
459
473
  end
460
474
 
461
- # @param nmi [String] a NMI that is to be checked for validity
462
- # @return [Boolean] determines if the NMI is valid
463
- def self.valid_nmi?(nmi)
464
- (nmi.class == String && nmi.length == 10 && !nmi.match(/^[A-Z1-9][A-Z0-9]{9}$/).nil?)
465
- end
466
-
467
- # @param path_to_file [String] the path to a file
468
- # @return [] NEM12 object
469
- def self.parse_nem12_file(path_to_file, strict = false)
470
- parse_nem12(File.read(path_to_file),strict)
475
+ # Turns the flag to a string
476
+ #
477
+ # @param [Hash] the object of a flag
478
+ # @return [String] a hyphenated string for the flag
479
+ def flag_to_s(flag)
480
+ flag_to_s = []
481
+ unless flag.nil?
482
+ flag_to_s << QUALITY_FLAGS[flag[:quality_flag]]
483
+ flag_to_s << METHOD_FLAGS[flag[:method_flag]][:short_descriptor]
484
+ flag_to_s << REASON_CODES[flag[:reason_code]]
485
+ end
486
+ flag_to_s.join(" - ")
471
487
  end
472
-
488
+
473
489
  # @return [Array] array of a NEM12 file a given Meter + Data Stream for easy reading
474
490
  def to_a
475
491
  values = @interval_data.map do |d|
476
492
  uom = UOM[UOM_NON_SPEC_MAPPING[d[:data_details][:uom].upcase]]
477
-
478
- [d[:data_details][:nmi],d[:data_details][:nmi_suffix].upcase,d[:data_details][:uom],d[:datetime],d[:value],d[:flag]]
493
+ [ d[:data_details][:nmi],
494
+ d[:data_details][:nmi_suffix].upcase,
495
+ d[:data_details][:uom],
496
+ d[:datetime],
497
+ d[:value],
498
+ flag_to_s(d[:flag])]
479
499
  end
480
500
  values
481
501
  end
482
502
 
483
503
  # @return [Array] CSV of a NEM12 file a given Meter + Data Stream for easy reading
484
504
  def to_csv
485
- headers = ['nmi','suffix','units','datetime','value']
505
+ headers = ['nmi','suffix','units','datetime','value','flags']
486
506
  ([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")
487
507
  end
488
508
 
509
+ # @param nmi [String] a NMI that is to be checked for validity
510
+ # @return [Boolean] determines if the NMI is valid
511
+ def self.valid_nmi?(nmi)
512
+ (nmi.class == String && nmi.length == 10 && !nmi.match(/^[A-Z1-9][A-Z0-9]{9}$/).nil?)
513
+ end
514
+
515
+ # @param path_to_file [String] the path to a file
516
+ # @return [] NEM12 object
517
+ def self.parse_nem12_file(path_to_file, strict = false)
518
+ parse_nem12(File.read(path_to_file),strict)
519
+ end
520
+
489
521
  # @param contents [String] the path to a file
490
522
  # @return [Array[AEMO::NEM12]] An array of NEM12 objects
491
523
  def self.parse_nem12(contents, strict=false)
data/lib/aemo/nmi.rb CHANGED
@@ -1,5 +1,11 @@
1
+ require 'csv'
2
+ require 'json'
3
+ require 'time'
4
+ require 'ostruct'
1
5
  module AEMO
6
+ # AEMO::NMI acts as an object to simplify access to data and information about a NMI and provide verification of the NMI value
2
7
  class NMI
8
+ # Operational Regions for the NMI
3
9
  REGIONS = {
4
10
  'ACT' => 'Australian Capital Territory',
5
11
  'NSW' => 'New South Wales',
@@ -10,366 +16,572 @@ module AEMO
10
16
  'WA' => 'Western Australia',
11
17
  'NT' => 'Northern Territory'
12
18
  }
13
- NETWORK_SERVICE_PROVIDERS = [
14
- # ACT
15
- {
16
- type: 'electrical',
17
- key: 'ACTEWP',
18
- states: ['ACT'],
19
- names: ['Actew Distribution Ltd','Jemena Networks (ACT) Pty Ltd'],
20
- nmi_blocks: [
21
- /^NGGG[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
22
- /^7001[0-9]{6}$/
19
+ # NMI_ALLOCATIONS as per AEMO Documentation at http://aemo.com.au/Electricity/Policies-and-Procedures/Retail-and-Metering/~/media/Files/Other/Retail%20and%20Metering/NMI_Allocation_List_v7_June_2012.ashx
20
+ # Last accessed 2015-02-04
21
+ NMI_ALLOCATIONS = {
22
+ 'ACTEWP' => {
23
+ title: 'Actew Distribution Ltd and Jemena Networks (ACT) Pty Ltd',
24
+ friendly_title: 'ACTEWAgl',
25
+ state: 'ACT',
26
+ type: 'electricity',
27
+ includes: [
28
+ (/^(NGGG[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
29
+ (/^(7001\d{6})$/)
30
+ ],
31
+ excludes: [
23
32
  ]
24
33
  },
25
- # NSW
26
- {
27
- type: 'electrical',
28
- key: 'ENERGYAP',
29
- states: ['NSW'],
30
- names: ['Ausgrid'],
31
- nmi_blocks: [
32
- /^NTTT[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
33
- /^410[2-4][0-9]{6}$/
34
+ 'CNRGYP' => {
35
+ title: 'Essential Energy',
36
+ friendly_title: 'Essential Energy',
37
+ state: 'NSW',
38
+ type: 'electricity',
39
+ includes: [
40
+ (/^(NAAA[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
41
+ (/^(NBBB[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
42
+ (/^(NDDD[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
43
+ (/^(NFFF[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
44
+ (/^(4001\d{6})$/),
45
+ (/^(4508\d{6})$/),
46
+ (/^(4204\d{6})$/),
47
+ (/^(4407\d{6})$/)
48
+ ],
49
+ excludes: [
34
50
  ]
35
51
  },
36
- {
37
- type: 'electrical',
38
- key: 'INTEGP',
39
- states: ['NSW'],
40
- names: ['Endeavour Energy'],
41
- nmi_blocks: [
42
- /^NEEE[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
43
- /^431[0-9][0-9]{6}$/
52
+ 'ENERGYAP' => {
53
+ title: 'Ausgrid',
54
+ friendly_title: 'Ausgrid',
55
+ state: 'NSW',
56
+ type: 'electricity',
57
+ includes: [
58
+ (/^(NCCC[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
59
+ (/^(410[234]\d{6})$/)
60
+ ],
61
+ excludes: [
44
62
  ]
45
63
  },
46
- {
47
- type: 'electrical',
48
- key: 'CNRGYP',
49
- states: ['NSW'],
50
- names: ['Essential Energy'],
51
- nmi_blocks: [
52
- /^NAAA[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
53
- /^NBBB[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
54
- /^NDDD[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
55
- /^NFFF[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
56
- /^4001[0-9]{6}$/,
57
- /^4508[0-9]{6}$/,
58
- /^4204[0-9]{6}$/,
59
- /^4407[0-9]{6}$/
64
+ 'INTEGP' => {
65
+ title: 'Endeavour Energy',
66
+ friendly_title: 'Endeavour Energy',
67
+ state: 'NSW',
68
+ type: 'electricity',
69
+ includes: [
70
+ (/^(NEEE[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
71
+ (/^(431\d{7})$/)
72
+ ],
73
+ excludes: [
60
74
  ]
61
75
  },
62
- {
63
- type: 'electrical',
64
- key: 'TRANSGP',
65
- states: ['NSW'],
66
- names: ['TransGrid'],
67
- nmi_blocks: [
68
- /^NCCC[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
69
- /^410[2-4][0-9]{6}$/
76
+ 'TRANSGP' => {
77
+ title: 'TransGrid',
78
+ friendly_title: 'TransGrid',
79
+ state: 'NSW',
80
+ type: 'electricity',
81
+ includes: [
82
+ (/^(NTTT[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
83
+ (/^(460810[0-8]\d{3})$/)
84
+ ],
85
+ excludes: [
70
86
  ]
71
87
  },
72
- {
73
- type: 'electrical',
74
- key: 'SNOWY',
75
- states: ['NSW'],
76
- names: ['Snowy Hydro'],
77
- nmi_blocks: [
78
- /^4708109[0-9]{3}$/
88
+ 'SNOWY' => {
89
+ title: 'Snowy Hydro Ltd',
90
+ friendly_title: 'Snowy Hydro',
91
+ state: 'NSW',
92
+ type: 'electricity',
93
+ includes: [
94
+ (/^(4708109\d{3})$/)
95
+ ],
96
+ excludes: [
79
97
  ]
80
98
  },
81
- # NT
82
- {
83
- type: 'electrical',
84
- key: 'RESERVED_NT',
85
- states: ['NT'],
86
- names: [],
87
- nmi_blocks: [
88
- /^250[0-9]{7}$/
99
+ 'NT_RESERVED' => {
100
+ title: 'Northern Territory Reserved Block',
101
+ friendly_title: 'Northern Territory Reserved Block',
102
+ state: 'NT',
103
+ type: 'electricity',
104
+ includes: [
105
+ (/^(250\d{7})$/)
106
+ ],
107
+ excludes: [
89
108
  ]
90
109
  },
91
- # QUEENSLAND
92
- {
93
- type: 'electrical',
94
- key: 'ERGONETP',
95
- states: ['QLD'],
96
- names: ['Ergon Energy'],
97
- nmi_blocks: [
98
- /^QAAA[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
99
- /^QCCC[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
100
- /^QDDD[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
101
- /^QEEE[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
102
- /^QFFF[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
103
- /^QGGG[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
104
- /^30[0-9]{8}$/
110
+ 'ERGONETP' => {
111
+ title: 'Ergon Energy Corporation',
112
+ friendly_title: 'Ergon Energy',
113
+ state: 'QLD',
114
+ type: 'electricity',
115
+ includes: [
116
+ (/^(QAAA[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
117
+ (/^(QCCC[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
118
+ (/^(QDDD[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
119
+ (/^(QEEE[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
120
+ (/^(QFFF[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
121
+ (/^(QGGG[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
122
+ (/^(30\d{8})$/)
123
+ ],
124
+ excludes: [
105
125
  ]
106
126
  },
107
- {
108
- type: 'electrical',
109
- key: 'ENERGEXP',
110
- states: ['QLD'],
111
- names: ['ENERGEX'],
112
- nmi_blocks: [
113
- /^QB[0-9]{2}[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
114
- /^31[0-9]{8}$/
127
+ 'ENERGEXP' => {
128
+ title: 'ENERGEX Limited',
129
+ friendly_title: 'Energex',
130
+ state: 'QLD',
131
+ type: 'electricity',
132
+ includes: [
133
+ (/^(QBBB[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
134
+ (/^(31\d{8})$/)
135
+ ],
136
+ excludes: [
115
137
  ]
116
138
  },
117
- {
118
- type: 'electrical',
119
- key: 'PLINKP',
120
- states: ['QLD'],
121
- names: ['QLD Electricity Transmission Corp (Powerlink)'],
122
- nmi_blocks: [
123
- /^QtniW[A-HJ-NP-Z0-9]{5}$/,
124
- /^320200[0-9]{4}$/
139
+ 'PLINKP' => {
140
+ title: 'Qld Electricity Transmission Corp (Powerlink)',
141
+ friendly_title: 'Powerlink',
142
+ state: 'QLD',
143
+ type: 'electricity',
144
+ includes: [
145
+ (/^(Q[A-HJ-NP-Z\d]{3}W[A-HJ-NP-Z\d]{5})$/),
146
+ (/^(320200\d{4})$/)
147
+ ],
148
+ excludes: [
125
149
  ]
126
150
  },
127
- # SA
128
- {
129
- type: 'electrical',
130
- key: 'UMPLP',
131
- states: ['SA'],
132
- names: ['SA Power Networks'],
133
- nmi_blocks: [
134
- /^SAAA[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
135
- /^SASMPL[0-9]{4}$/,
136
- /^200[1-2][0-9]{6}$/
151
+ 'UMPLP' => {
152
+ title: 'SA Power Networks',
153
+ friendly_title: 'SA Power Networks',
154
+ state: 'SA',
155
+ type: 'electricity',
156
+ includes: [
157
+ (/^(SAAA[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
158
+ (/^(SASMPL[\d]{4})$/),
159
+ (/^(200[12]\d{6})$/)
160
+ ],
161
+ excludes: [
137
162
  ]
138
163
  },
139
- {
140
- type: 'electrical',
141
- key: 'ETSATP',
142
- states: ['SA'],
143
- names: ['ElectraNet'],
144
- nmi_blocks: [
145
- /^StniW[A-HJ-NP-Z0-9]{5}$/,
146
- /^310200[0-9]{4}$/
164
+ 'TRANSEND' => {
165
+ title: 'ElectraNet SA',
166
+ friendly_title: 'ElectraNet SA',
167
+ state: 'SA',
168
+ type: 'electricity',
169
+ includes: [
170
+ (/^(S[A-HJ-NP-Z\d]{3}W[A-HJ-NP-Z\d]{5})$/),
171
+ (/^(210200\d{4})$/)
172
+ ],
173
+ excludes: [
147
174
  ]
148
175
  },
149
- # TASMANIA
150
- {
151
- type: 'electrical',
152
- key: 'AURORAP',
153
- states: ['TAS'],
154
- names: ['Aurora Energy'],
155
- nmi_blocks: [
156
- /^T00000(([0-4][0-9]{3})|(5001))$/,
157
- /^8000[0-9]{6}$/,
158
- /^8590[2-3][0-9]{5}$/
176
+ 'AURORAP' => {
177
+ title: 'TasNetworks',
178
+ friendly_title: 'TasNetworks',
179
+ state: 'TAS',
180
+ type: 'electricity',
181
+ includes: [
182
+ (/^(T000000(([0-4]\d{3})|(500[01])))$/),
183
+ (/^(8000\d{6})$/),
184
+ (/^(8590[23]\d{5})$/)
185
+ ],
186
+ excludes: [
159
187
  ]
160
188
  },
161
- {
162
- type: 'electrical',
163
- key: 'TRANSEND',
164
- states: ['TAS'],
165
- names: ['Transend Networks'],
166
- nmi_blocks: [
167
- /^TtniW[A-HJ-NP-Z0-9]{5}$/
189
+ 'TRANSEND' => {
190
+ title: 'TasNetworks',
191
+ friendly_title: 'TasNetworks',
192
+ state: 'TAS',
193
+ type: 'electricity',
194
+ includes: [
195
+ (/^(T[A-HJ-NP-Z\d]{3}W[A-HJ-NP-Z\d]{5})$/),
196
+ ],
197
+ excludes: [
168
198
  ]
169
199
  },
170
- # VICTORIA
171
- {
172
- type: 'electrical',
173
- key: 'CITIPP',
174
- states: ['VIC'],
175
- names: ['CitiPower'],
176
- nmi_blocks: [
177
- /^VAAA[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
178
- /^610[2-3][0-9]{6}$/
200
+ 'CITIPP' => {
201
+ title: 'CitiPower',
202
+ friendly_title: 'CitiPower',
203
+ state: 'VIC',
204
+ type: 'electricity',
205
+ includes: [
206
+ (/^(VAAA[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
207
+ (/^(610[23]\d{6})$/)
208
+ ],
209
+ excludes: [
179
210
  ]
180
211
  },
181
- {
182
- type: 'electrical',
183
- key: 'EASTERN',
184
- states: ['VIC'],
185
- names: ['AusNet'],
186
- nmi_blocks: [
187
- /^VBBB[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
188
- /^630[5-6][0-9]{6}$/
212
+ 'EASTERN' => {
213
+ title: 'SP AusNet',
214
+ friendly_title: 'SP AusNet DNSP',
215
+ state: 'VIC',
216
+ type: 'electricity',
217
+ includes: [
218
+ (/^(VBBB[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
219
+ (/^(630[56]\d{6})$/)
220
+ ],
221
+ excludes: [
189
222
  ]
190
223
  },
191
- {
192
- type: 'electrical',
193
- key: 'POWCP',
194
- states: ['VIC'],
195
- names: ['Powercor'],
196
- nmi_blocks: [
197
- /^VCCC[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
198
- /^620[3-4][0-9]{6}$/
224
+ 'POWCP' => {
225
+ title: 'PowerCor Australia',
226
+ friendly_title: 'PowerCor',
227
+ state: 'VIC',
228
+ type: 'electricity',
229
+ includes: [
230
+ (/^(VCCC[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
231
+ (/^(620[34]\d{6})$/)
232
+ ],
233
+ excludes: [
199
234
  ]
200
235
  },
201
- {
202
- type: 'electrical',
203
- key: 'Jemena Electricity Networks',
204
- states: ['VIC'],
205
- names: ['SOLARISP'],
206
- nmi_blocks: [
207
- /^VDDD[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
208
- /^6001[0-9]{6}$/
236
+ 'SOLARISP' => {
237
+ title: 'Jemena Electricity Networks (VIC)',
238
+ friendly_title: 'Jemena',
239
+ state: 'VIC',
240
+ type: 'electricity',
241
+ includes: [
242
+ (/^(VDDD[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
243
+ (/^(6001\d{6})$/)
244
+ ],
245
+ excludes: [
209
246
  ]
210
247
  },
211
- {
212
- type: 'electrical',
213
- key: 'UNITED',
214
- states: ['VIC'],
215
- names: ['United Energy Distribution'],
216
- nmi_blocks: [
217
- /^VEEE[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
218
- /^640[7-8][0-9]{6}$/
248
+ 'UNITED' => {
249
+ title: 'United Energy Distribution',
250
+ friendly_title: 'United Energy',
251
+ state: 'VIC',
252
+ type: 'electricity',
253
+ includes: [
254
+ (/^(VEEE[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
255
+ (/^(640[78]\d{6})$/)
256
+ ],
257
+ excludes: [
219
258
  ]
220
259
  },
221
- {
222
- type: 'electrical',
223
- key: 'GPUPP',
224
- states: ['SA'],
225
- names: ['AusNet'],
226
- nmi_blocks: [
227
- /^VtniW[A-HJ-NP-Z0-9]{5}$/,
228
- /^650900[0-9]{4}$/
260
+ 'GPUPP' => {
261
+ title: 'SP AusNet TNSP',
262
+ friendly_title: 'SP AusNet TNSP',
263
+ state: 'VIC',
264
+ type: 'electricity',
265
+ includes: [
266
+ (/^(V[A-HJ-NP-Z\d]{3}W[A-HJ-NP-Z\d]{5})$/),
267
+ (/^(650900\d{4})$/)
268
+ ],
269
+ excludes: [
229
270
  ]
230
271
  },
231
- # WA
232
- {
233
- type: 'electrical',
234
- key: 'WESTERNPOWER',
235
- states: ['WA'],
236
- names: ['Western Power'],
237
- nmi_blocks: [
238
- /^WAAA[A-HJ-NP-VX-Z0-9][A-HJ-NP-Z0-9]{5}$/,
239
- /^801[0-9]{7}$/,
240
- /^8020[0-9]{6}$/
272
+ 'WESTERNPOWER' => {
273
+ title: 'Western Power',
274
+ friendly_title: 'Western Power',
275
+ state: 'WA',
276
+ type: 'electricity',
277
+ includes: [
278
+ (/^(WAAA[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
279
+ (/^(800[1-9]\d{6})$/),
280
+ (/^(801\d{7})$/),
281
+ (/^(8020\d{6})$/)
282
+ ],
283
+ excludes: [
241
284
  ]
242
285
  },
243
- {
244
- type: 'electrical',
245
- key: 'GPUPP',
246
- states: ['SA'],
247
- names: ['AusNet'],
248
- nmi_blocks: [
249
- /^8021[0-9]{6}$/
286
+ 'HORIZONPOWER' => {
287
+ title: 'Horizon Power',
288
+ friendly_title: 'Horizon Power',
289
+ state: 'WA',
290
+ type: 'electricity',
291
+ includes: [
292
+ (/^(8021\d{6})$/)
293
+ ],
294
+ excludes: [
250
295
  ]
251
296
  },
252
- # GAS
253
- {
297
+ 'GAS_NSW' => {
298
+ title: 'GAS NSW',
299
+ friendly_title: 'GAS NSW',
300
+ state: 'NSW',
254
301
  type: 'gas',
255
- key: nil,
256
- states: ['NSW','ACT'],
257
- names: [],
258
- nmi_blocks: [
259
- /^52[0-9]{8}$/
302
+ includes: [
303
+ (/^(52\d{8})$/)
304
+ ],
305
+ excludes: [
260
306
  ]
261
307
  },
262
- {
308
+ 'GAS_VIC' => {
309
+ title: 'GAS VIC',
310
+ friendly_title: 'GAS VIC',
311
+ state: 'VIC',
263
312
  type: 'gas',
264
- key: nil,
265
- states: ['VIC'],
266
- names: [],
267
- nmi_blocks: [
268
- /^53[0-9]{8}$/
313
+ includes: [
314
+ (/^(53\d{8})$/)
315
+ ],
316
+ excludes: [
269
317
  ]
270
318
  },
271
- {
319
+ 'GAS_QLD' => {
320
+ title: 'GAS QLD',
321
+ friendly_title: 'GAS QLD',
322
+ state: 'QLD',
272
323
  type: 'gas',
273
- key: nil,
274
- states: ['QLD'],
275
- names: [],
276
- nmi_blocks: [
277
- /^54[0-9]{8}$/
324
+ includes: [
325
+ (/^(54\d{8})$/)
326
+ ],
327
+ excludes: [
278
328
  ]
279
329
  },
280
- {
330
+ 'GAS_SA' => {
331
+ title: 'GAS SA',
332
+ friendly_title: 'GAS SA',
333
+ state: 'SA',
281
334
  type: 'gas',
282
- key: nil,
283
- states: ['SA'],
284
- names: [],
285
- nmi_blocks: [
286
- /^55[0-9]{8}$/
335
+ includes: [
336
+ (/^(55\d{8})$/)
337
+ ],
338
+ excludes: [
287
339
  ]
288
340
  },
289
- {
341
+ 'GAS_WA' => {
342
+ title: 'GAS WA',
343
+ friendly_title: 'GAS WA',
344
+ state: 'WA',
290
345
  type: 'gas',
291
- key: nil,
292
- states: ['WA'],
293
- names: [],
294
- nmi_blocks: [
295
- /^56[0-9]{8}$/
346
+ includes: [
347
+ (/^(56\d{8})$/)
348
+ ],
349
+ excludes: [
296
350
  ]
297
351
  },
298
- {
352
+ 'GAS_TAS' => {
353
+ title: 'GAS TAS',
354
+ friendly_title: 'GAS TAS',
355
+ state: 'TAS',
299
356
  type: 'gas',
300
- key: nil,
301
- states: ['TAS'],
302
- names: [],
303
- nmi_blocks: [
304
- /^57[0-9]{8}$/
357
+ includes: [
358
+ (/^(57\d{8})$/)
359
+ ],
360
+ excludes: [
305
361
  ]
306
362
  },
307
- # MISC
308
- {
309
- type: 'electrical',
310
- key: nil,
311
- states: [],
312
- names: ['Federal Airports Corporation (Sydney Airport)'],
313
- nmi_blocks: [
314
- /^NJJJNR[A-HJ-NP-Z0-9]{4}$/
363
+ 'FEDAIRPORTS' => {
364
+ title: 'Federal Airports Corporation (Sydney Airport)',
365
+ friendly_title: 'Sydney Airport',
366
+ state: 'NSW',
367
+ type: 'electricity',
368
+ includes: [
369
+ (/^(NJJJNR[A-HJ-NP-Z\d]{4})$/)
370
+ ],
371
+ excludes: [
315
372
  ]
316
373
  },
317
- {
318
- type: 'electrical',
319
- key: nil,
320
- states: [],
321
- names: ['Exempt Networks - various'],
322
- nmi_blocks: [
323
- /^NKKKNR[A-HJ-NP-Z0-9]{4}$/,
324
- /^7102[0-9]{6}$/
374
+ 'EXEMPTNETWORKS' => {
375
+ title: 'Exempt Networks - various',
376
+ friendly_title: 'Exempt Networks - various',
377
+ state: '',
378
+ type: 'electricity',
379
+ includes: [
380
+ (/^(NKKK[A-HJ-NP-VX-Z\d][A-HJ-NP-Z\d]{5})$/),
381
+ (/^(7102\d{6})$/)
382
+ ],
383
+ excludes: [
325
384
  ]
326
385
  },
327
- {
328
- type: 'electrical',
329
- key: nil,
330
- states: [],
331
- names: ['AEMO Reserved Block 1'],
332
- nmi_blocks: [
333
- /^880[1-5][0-9]{6}$/
334
- ]
335
- },
336
- {
337
- type: 'electrical',
338
- key: nil,
339
- states: [],
340
- names: ['AEMO Reserved Block 1'],
341
- nmi_blocks: [
342
- /^9[0-9]{9}$/
386
+ 'AEMORESERVED' => {
387
+ title: 'AEMO Reserved',
388
+ friendly_title: 'AEMO Reserved',
389
+ state: '',
390
+ type: 'electricity',
391
+ includes: [
392
+ (/^(880[1-5]\d{6})$/),
393
+ (/^(9\d{9})$/)
394
+ ],
395
+ excludes: [
343
396
  ]
344
397
  }
345
- ]
398
+ }
399
+ # Transmission Node Identifier Codes are loaded from a json file
400
+ # Obtained from http://www.nemweb.com.au/
401
+ #
402
+ # See /lib/data for further data manipulation required
403
+ TNI_CODES = JSON.parse(File.read(File.join(File.dirname(__FILE__),'..','data','aemo-tni.json')))
404
+ # Distribution Loss Factor Codes are loaded from a json file
405
+ # Obtained from MSATS, matching to DNSP from file http://www.aemo.com.au/Electricity/Market-Operations/Loss-Factors-and-Regional-Boundaries/~/media/Files/Other/loss%20factors/DLF_FINAL_V2_2014_2015.ashx
406
+ # Last accessed 2015-02-06
407
+ # See /lib/data for further data manipulation required
408
+ DLF_CODES = JSON.parse(File.read(File.join(File.dirname(__FILE__),'..','data','aemo-dlf.json')))
409
+
410
+ # [String] National Meter Identifier
411
+ @nmi = nil
412
+ @msats_detail = nil
413
+ @tni = nil
414
+ @dlf = nil
415
+ @customer_classification_code = nil
416
+ @customer_threshold_code = nil
417
+ @jurisdiction_code = nil
418
+ @classification_code = nil
419
+ @status = nil
420
+ @address = nil
421
+ @meters = nil
422
+ @roles = nil
423
+
424
+ attr_accessor :nmi, :msats_detail, :tni, :dlf, :customer_classification_code, :customer_threshold_code, :jurisdiction_code, :classification_code, :status, :address, :meters, :roles
346
425
 
347
- attr_accessor :region
426
+ # Initialize a NEM12 file
427
+ #
428
+ # @param nmi [String] the National Meter Identifier (NMI)
429
+ # @param options [Hash] a hash of options
430
+ # @return [AEMO::NMI] an instance of AEMO::NMI is returned
431
+ def initialize(nmi,options={})
432
+ raise ArgumentError.new("NMI is not a string") unless nmi.is_a?(String)
433
+ raise ArgumentError.new("NMI is not 10 characters") unless nmi.length == 10
434
+ raise ArgumentError.new("NMI is not constructed with valid characters") unless AEMO::NMI.valid_nmi?(nmi)
435
+
436
+ @nmi = nmi
437
+ @meters = {}
438
+ @roles = {}
439
+ end
348
440
 
349
- def initialize(nmi)
350
- @nmi = nmi
351
- @network = NMI.find_network(nmi)
441
+ # A function to validate the instance's nmi value
442
+ #
443
+ # @return [Boolean] whether or not the nmi is valid
444
+ def valid_nmi?
445
+ AEMO::NMI.valid_nmi?(@nmi)
446
+ end
447
+
448
+ # Find the Network of NMI
449
+ #
450
+ # @returns [Hash] The Network information
451
+ def network
452
+ AEMO::NMI.network(@nmi)
453
+ end
454
+
455
+ # A function to calculate the checksum value for a given National Meter Identifier
456
+ #
457
+ # @param checksum_value [Integer] the checksum value to check against the current National Meter Identifier's checksum value
458
+ # @return [Boolean] whether or not the checksum is valid
459
+ def valid_checksum?(checksum_value)
460
+ checksum_value == self.checksum
461
+ end
462
+
463
+ # Checksum is a function to calculate the checksum value for a given National Meter Identifier
464
+ #
465
+ # @return [Integer] the checksum value for the current National Meter Identifier
466
+ def checksum
467
+ summation = 0
468
+ @nmi.reverse.split(//).each_index do |i|
469
+ value = nmi[nmi.length - i - 1].ord
470
+ if(i % 2 == 0)
471
+ value = value * 2
472
+ end
473
+ value = value.to_s.split(//).map{|i| i.to_i}.reduce(:+)
474
+ summation += value
475
+ end
476
+ checksum = (10 - (summation % 10)) % 10
477
+ checksum
352
478
  end
353
479
 
480
+ # Provided MSATS is configured, gets the MSATS data for the NMI
481
+ #
482
+ # @return [Hash] MSATS NMI Detail data
483
+ def raw_msats_nmi_detail
484
+ raise ArgumentError, 'MSATS has no authentication credentials' unless AEMO::MSATS.can_authenticate?
485
+
486
+ AEMO::MSATS.nmi_detail(@nmi)
487
+ end
354
488
 
355
- def self.find_network(nmi)
356
- nmi_network = nil
357
- NETWORK_SERVICE_PROVIDERS.each do |network|
358
- network[:nmi_blocks].each do |nmi_block|
359
- nmi_network = network if nmi.match(nmi_block)
489
+ # Provided MSATS is configured, uses the raw MSATS data to augment NMI information
490
+ #
491
+ # @return [self] returns self
492
+ def update_from_msats!
493
+ # Update local cache
494
+ @msats_detail = raw_msats_nmi_detail
495
+ # Set the details
496
+ @tni = @msats_detail['MasterData']['TransmissionNodeIdentifier']
497
+ @dlf = @msats_detail['MasterData']['DistributionLossFactorCode']
498
+ @customer_classification_code = @msats_detail['MasterData']['CustomerClassificationCode']
499
+ @customer_threshold_code = @msats_detail['MasterData']['CustomerThresholdCode']
500
+ @jurisdiction_code = @msats_detail['MasterData']['JurisdictionCode']
501
+ @classification_code = @msats_detail['MasterData']['NMIClassificationCode']
502
+ @status = @msats_detail['MasterData']['Status']
503
+ @address = @msats_detail['MasterData']['Address']
504
+ @meters ||= {}
505
+ # Meters
506
+ unless @msats_detail['MeterRegister'].nil?
507
+ @msats_detail['MeterRegister']['Meter'].select{|x| !x['Status'].nil? }.each do |meter|
508
+ m = OpenStruct.new(
509
+ status: meter['Status'],
510
+ installation_type_code: meter['InstallationTypeCode'],
511
+ next_scheduled_read_date: meter['NextScheduledReadDate'],
512
+ read_type_code: meter['ReadTypeCode'],
513
+ registers: []
514
+ )
515
+ puts "@meters.inspect: #{@meters.inspect}"
516
+ @meters[meter['SerialNumber']] = m
517
+ end
518
+ @msats_detail['MeterRegister']['Meter'].select{|x| x['Status'].nil? }.each do |registers|
519
+ register = OpenStruct.new(
520
+ controlled_load: (registers['RegisterConfiguration']['Register']['ControlledLoad'] == 'Y'),
521
+ dial_format: registers['RegisterConfiguration']['Register']['DialFormat'],
522
+ multiplier: registers['RegisterConfiguration']['Register']['Multiplier'],
523
+ network_tariff_code: registers['RegisterConfiguration']['Register']['NetworkTariffCode'],
524
+ register_id: registers['RegisterConfiguration']['Register']['RegisterID'],
525
+ status: registers['RegisterConfiguration']['Register']['Status'],
526
+ time_of_day: registers['RegisterConfiguration']['Register']['TimeOfDay'],
527
+ unit_of_measure: registers['RegisterConfiguration']['Register']['UnitOfMeasure']
528
+ )
529
+ @meters[registers['SerialNumber']].registers << register
530
+ end
531
+ end
532
+ # Roles
533
+ unless @msats_detail['RoleAssignments'].nil?
534
+ @msats_detail['RoleAssignments']['RoleAssignment'].each do |role|
535
+ @roles[role['Role']] = role['Party']
360
536
  end
361
- break unless nmi_network.nil?
362
537
  end
363
- nmi_network
538
+
539
+ self
364
540
  end
365
-
541
+
542
+ # A function to validate the NMI provided
543
+ #
544
+ # @param nmi [String] the nmi to be checked
545
+ # @return [Boolean] whether or not the nmi is valid
546
+ def self.valid_nmi?(nmi)
547
+ ((nmi.length == 10) && !nmi.match(/^([A-HJ-NP-Z\d]{10})/).nil?)
548
+ end
549
+
550
+ # A function to calculate the checksum value for a given National Meter Identifier
551
+ #
552
+ # @param nmi [String] the NMI to check the checksum against
553
+ # @param checksum_value [Integer] the checksum value to check against the current National Meter Identifier's checksum value
554
+ # @return [Boolean] whether or not the checksum is valid
555
+ def self.valid_checksum?(nmi,checksum_value)
556
+ nmi = AEMO::NMI.new(nmi)
557
+ nmi.valid_checksum?(checksum_value)
558
+ end
559
+
560
+ # Find the Network for a given NMI
561
+ #
562
+ # @param nmi [String] NMI
563
+ # @returns [Hash] The Network information
564
+ def self.network(nmi)
565
+ network = nil
566
+ AEMO::NMI::NMI_ALLOCATIONS.each_pair do |identifier, details|
567
+ details[:includes].each do |pattern|
568
+ if nmi.match(pattern)
569
+ network = { identifier => details }
570
+ break
571
+ end
572
+ end
573
+ end
574
+ network
575
+ end
576
+
366
577
  # ######### #
367
578
  protected
368
579
  # ######### #
369
-
580
+
370
581
  def is_valid_region?(region)
371
582
  REGIONS.keys.include?(region)
372
583
  end
373
-
584
+
374
585
  end
375
- end
586
+
587
+ end