aemo 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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