aemo 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -80,7 +80,8 @@ describe AEMO::NEM12 do
80
80
  '200,NEM1201002,E1E2,E1,E1,N1,01002,KWH,30,',
81
81
  '300,20050318,315.15,313.8,296.55,298.5,295.2,298.95,300.75,322.95,330.45,350.7,345.75,346.95,345.9,348.6,300.15,337.5,336.75,345.9,330.45,327.15,334.8,345.75,335.85,320.1,325.5,325.2,326.4,330.6,332.7,332.25,321.0,316.5,299.85,302.4,301.05,263.85,255.45,142.05,138.3,138.3,136.8,138.3,136.05,135.75,135.75,136.65,136.05,130.8,A,,,20050319014041,',
82
82
  '200,NEM1201002,E1E2,E2,E2,N2,01002,KWH,30,',
83
- '300,20050318,103.05,98.85,81.15,75.6,72.15,73.95,74.55,81.15,89.25,124.2,125.7,128.7,136.8,151.05,177.9,174.45,204.0,210.15,180.15,164.4,187.95,211.95,193.8,122.85,124.8,121.2,129.3,131.25,130.95,130.05,118.05,105.75,77.1,75.9,75.0,51.15,44.4,12.3,12.75,12.15,12.45,11.55,14.4,14.55,15.0,14.7,15.75,21.9,A,,,20050319014041,', '900',
83
+ '300,20050318,103.05,98.85,81.15,75.6,72.15,73.95,74.55,81.15,89.25,124.2,125.7,128.7,136.8,151.05,177.9,174.45,204.0,210.15,180.15,164.4,187.95,211.95,193.8,122.85,124.8,121.2,129.3,131.25,130.95,130.05,118.05,105.75,77.1,75.9,75.0,51.15,44.4,12.3,12.75,12.15,12.45,11.55,14.4,14.55,15.0,14.7,15.75,21.9,A,,,20050319014041,',
84
+ '900',
84
85
  ''
85
86
  ].join("\r\n")
86
87
  end
@@ -106,11 +107,9 @@ describe AEMO::NEM12 do
106
107
  '400,1,10,F52,71,',
107
108
  '400,11,48,E52,,',
108
109
  '200,NEM1204062,E1,E1,E1,N1,04062,KWH,30,20050503',
109
- '300,20040528,0.68,0.653,0.62,0.623,0.618,0.625,0.613,0.623,0.618,0.615,0.613,0.76,0.665,0.638,0.61,0.648,0.65,0.645,0.895,0.668,0.645,0.648,0.655,0.73,0.695,0.67,0.638,0.643,0.64,0.723,0.653,0.645,0.633,0.71,0.683,0.648,0.625,0.63,0.625,0.63,0.638,0.635,0.633,0.638,0.673,0.765,0.65,0.628,V,,,20040609000001,',
110
- '400,1,48,E52,,',
110
+ '300,20040528,0.68,0.653,0.62,0.623,0.618,0.625,0.613,0.623,0.618,0.615,0.613,0.76,0.665,0.638,0.61,0.648,0.65,0.645,0.895,0.668,0.645,0.648,0.655,0.73,0.695,0.67,0.638,0.643,0.64,0.723,0.653,0.645,0.633,0.71,0.683,0.648,0.625,0.63,0.625,0.63,0.638,0.635,0.633,0.638,0.673,0.765,0.65,0.628,E52,,,20040609000001,',
111
111
  '200,NEM1204062,E1,E1,E1,N1,04062,KWH,30,20050503',
112
- '300,20040529,0.633,0.613,0.628,0.618,0.625,0.623,0.623,0.613,0.655,0.663,0.645,0.708,0.608,0.618,0.63,0.625,0.62,0.635,0.63,0.638,0.693,0.71,0.683,0.645,0.638,0.653,0.653,0.648,0.655,0.745,0.69,0.695,0.68,0.643,0.645,0.635,0.628,0.625,0.635,0.628,0.673,0.688,0.685,0.66,0.638,0.718,0.638,0.63,V,,,20040609000001,',
113
- '400,1,48,E52,,',
112
+ '300,20040529,0.633,0.613,0.628,0.618,0.625,0.623,0.623,0.613,0.655,0.663,0.645,0.708,0.608,0.618,0.63,0.625,0.62,0.635,0.63,0.638,0.693,0.71,0.683,0.645,0.638,0.653,0.653,0.648,0.655,0.745,0.69,0.695,0.68,0.643,0.645,0.635,0.628,0.625,0.635,0.628,0.673,0.688,0.685,0.66,0.638,0.718,0.638,0.63,E52,,,20040609000001,',
114
113
  '900',
115
114
  ''
116
115
  ].join("\r\n")
@@ -136,9 +135,9 @@ describe AEMO::NEM12 do
136
135
  Dir.entries(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'NEM12'))
137
136
  .reject { |f| %w[. .. .DS_Store].include?(f) }
138
137
  .each do |file|
139
- described_class.parse_nem12_file(fixture(File.join('NEM12', file))).each do |nem12|
140
- expect(nem12.nmi_identifier).to be_a String
141
- end
138
+ described_class.parse_nem12_file(fixture(File.join('NEM12', file))).each do |nem12|
139
+ expect(nem12.nmi_identifier).to be_a String
140
+ end
142
141
  end
143
142
  end
144
143
  end
@@ -173,6 +172,31 @@ describe AEMO::NEM12 do
173
172
  nem12_empty_cells_300_record = fixture(File.join('NEM12-Errors', 'NEM12#EmptyCells300Record#CNRGYMDP#NEMMCO.csv'))
174
173
  expect { described_class.parse_nem12_file(nem12_empty_cells_300_record) }.to raise_error(ArgumentError)
175
174
  end
175
+
176
+ it 'parses actual data flags explicitly' do
177
+ nem12_filepath = fixture(File.join('NEM12', 'NEM12#000000000000001#CNRGYMDP#NEMMCO.csv'))
178
+ nem12s = described_class.parse_nem12_file(nem12_filepath)
179
+ expect(nem12s.first.interval_data.first[:flag].quality_flag).to eq('A')
180
+ expect(nem12s.first.interval_data.first[:flag].method_flag).to be_nil
181
+ expect(nem12s.first.interval_data.first[:flag].reason_code).to be_nil
182
+ end
183
+
184
+ it 'parses substituted data flags with method flags' do
185
+ nem12_filepath = fixture(File.join('NEM12', 'NEM12#000000000000004#CNRGYMDP#NEMMCO.csv'))
186
+ nem12s = described_class.parse_nem12_file(nem12_filepath)
187
+ # First 10 intervals have F52 flag
188
+ expect(nem12s.first.interval_data.first[:flag].quality_flag).to eq('F')
189
+ expect(nem12s.first.interval_data.first[:flag].method_flag).to eq(52)
190
+ expect(nem12s.first.interval_data.first[:flag].reason_code).to eq(71)
191
+ end
192
+
193
+ it 'parses estimated data flags' do
194
+ nem12_filepath = fixture(File.join('NEM12', 'NEM12#000000000000004#CNRGYMDP#NEMMCO.csv'))
195
+ nem12s = described_class.parse_nem12_file(nem12_filepath)
196
+ # Intervals 11-48 have E52 flag
197
+ expect(nem12s.first.interval_data[10][:flag].quality_flag).to eq('E')
198
+ expect(nem12s.first.interval_data[10][:flag].method_flag).to eq(52)
199
+ end
176
200
  end
177
201
 
178
202
  describe '#parse_nem12_400' do
@@ -180,14 +204,36 @@ describe AEMO::NEM12 do
180
204
  nem12_empty_cells_400_record = fixture(File.join('NEM12-Errors', 'NEM12#EmptyCells400Record#CNRGYMDP#NEMMCO.csv'))
181
205
  expect { described_class.parse_nem12_file(nem12_empty_cells_400_record) }.to raise_error(ArgumentError)
182
206
  end
207
+
208
+ it 'parses 400 records and updates interval flags' do
209
+ nem12_filepath = fixture(File.join('NEM12', 'NEM12#000000000000004#CNRGYMDP#NEMMCO.csv'))
210
+ nem12s = described_class.parse_nem12_file(nem12_filepath)
211
+ # Verify that 400 records override the V flag from 300 record
212
+ expect(nem12s.first.interval_data.first[:flag].quality_flag).to eq('F')
213
+ expect(nem12s.first.interval_data[10][:flag].quality_flag).to eq('E')
214
+ end
183
215
  end
184
216
 
185
- describe '#flag_to_s' do
186
- it 'converts the flags to a string' do
187
- flag = { quality_flag: 'S', method_flag: 11, reason_code: 53 }
217
+ describe 'flag handling edge cases' do
218
+ it 'handles actual data with reason code' do
219
+ nem12_filepath = fixture(File.join('NEM12', 'NEM12.actual_with_reason_code.csv'))
220
+ nem12s = described_class.parse_nem12_file(nem12_filepath)
221
+ # Check that actual data with reason code is parsed correctly
222
+ interval_with_reason = nem12s.first.interval_data.find { |x| x[:flag].reason_code == 79 }
223
+ expect(interval_with_reason).not_to be_nil
224
+ expect(interval_with_reason[:flag].quality_flag).to eq('A')
225
+ expect(interval_with_reason[:flag].method_flag).to be_nil
226
+ expect(interval_with_reason[:flag].reason_code).to eq(79)
227
+ end
228
+
229
+ it 'maintains backward compatibility with nil flags' do
188
230
  nem12 = described_class.new('NEEE000010')
189
- expect(nem12.flag_to_s(flag))
190
- .to eq 'Substituted Data - Check - Bees/Wasp In Meter Box'
231
+ nem12.instance_variable_set(:@data_details, [{ interval_length: 30 }])
232
+ nem12.instance_variable_set(:@interval_data, [
233
+ { flag: nil, value: 1.0, datetime: Time.now, data_details: { interval_length: 30 } }
234
+ ])
235
+ # Should not raise error
236
+ expect { nem12.to_nem12_300_csv }.not_to raise_error
191
237
  end
192
238
  end
193
239
 
@@ -210,4 +256,104 @@ describe AEMO::NEM12 do
210
256
  expect(nem12.to_nem12_csv).to eq(expected)
211
257
  end
212
258
  end
259
+
260
+ describe '#to_nem12_300_csv with uniform flags' do
261
+ let(:nem12) { described_class.new('NEEE000010') }
262
+ let(:base_time) { Time.parse('2024-01-01 00:30:00 +1000') }
263
+
264
+ before do
265
+ nem12.instance_variable_set(:@data_details, [{ interval_length: 30 }])
266
+ end
267
+
268
+ it 'uses quality method from uniform actual data flags without 400 records' do
269
+ intervals = (0...48).map do |i|
270
+ {
271
+ flag: AEMO::MeterData::Flag.new(quality_flag: 'A', method_flag: nil, reason_code: nil),
272
+ value: 1.0 + i,
273
+ datetime: base_time + (i * 30 * 60),
274
+ data_details: { interval_length: 30 }
275
+ }
276
+ end
277
+ nem12.instance_variable_set(:@interval_data, intervals)
278
+
279
+ output = nem12.to_nem12_300_csv
280
+ expect(output).to include('300,20240101')
281
+ expect(output).to include(',A,,,')
282
+ expect(output).not_to include('400,')
283
+ end
284
+
285
+ it 'uses quality method from uniform estimated data flags without 400 records' do
286
+ intervals = (0...48).map do |i|
287
+ {
288
+ flag: AEMO::MeterData::Flag.new(quality_flag: 'E', method_flag: 52, reason_code: nil),
289
+ value: 1.0 + i,
290
+ datetime: base_time + (i * 30 * 60),
291
+ data_details: { interval_length: 30 }
292
+ }
293
+ end
294
+ nem12.instance_variable_set(:@interval_data, intervals)
295
+
296
+ output = nem12.to_nem12_300_csv
297
+ expect(output).to include('300,20240101')
298
+ expect(output).to include(',E52,,,')
299
+ expect(output).not_to include('400,')
300
+ end
301
+
302
+ it 'uses quality method from uniform substituted data flags with reason code' do
303
+ intervals = (0...48).map do |i|
304
+ {
305
+ flag: AEMO::MeterData::Flag.new(quality_flag: 'S', method_flag: 11, reason_code: 53),
306
+ value: 1.0 + i,
307
+ datetime: base_time + (i * 30 * 60),
308
+ data_details: { interval_length: 30 }
309
+ }
310
+ end
311
+ nem12.instance_variable_set(:@interval_data, intervals)
312
+
313
+ output = nem12.to_nem12_300_csv
314
+ expect(output).to include('300,20240101')
315
+ expect(output).to include(',S11,53,')
316
+ expect(output).not_to include('400,')
317
+ end
318
+
319
+ it 'uses V and generates 400 records when flags differ' do
320
+ intervals = (0...48).map do |i|
321
+ flag = if i < 10
322
+ AEMO::MeterData::Flag.new(quality_flag: 'F', method_flag: 52, reason_code: 71)
323
+ else
324
+ AEMO::MeterData::Flag.new(quality_flag: 'E', method_flag: 52, reason_code: nil)
325
+ end
326
+ {
327
+ flag:,
328
+ value: 1.0 + i,
329
+ datetime: base_time + (i * 30 * 60),
330
+ data_details: { interval_length: 30 }
331
+ }
332
+ end
333
+ nem12.instance_variable_set(:@interval_data, intervals)
334
+
335
+ output = nem12.to_nem12_300_csv
336
+ expect(output).to include('300,20240101')
337
+ expect(output).to include(',V,,,')
338
+ expect(output).to include('400,1,10,F52,71,')
339
+ expect(output).to include('400,11,48,E52,,')
340
+ end
341
+
342
+ it 'handles nil flags as nil data' do
343
+ intervals = (0...48).map do |i|
344
+ {
345
+ flag: nil,
346
+ value: 1.0 + i,
347
+ datetime: base_time + (i * 30 * 60),
348
+ data_details: { interval_length: 30 }
349
+ }
350
+ end
351
+ nem12.instance_variable_set(:@interval_data, intervals)
352
+
353
+ output = nem12.to_nem12_300_csv
354
+ expect(output).to include('300,20240101')
355
+ expect(output).to include(',,,,')
356
+ expect(output).not_to include('400,')
357
+ end
358
+ end
213
359
  end
data/spec/spec_helper.rb CHANGED
@@ -45,43 +45,43 @@ RSpec.configure do |config|
45
45
  .to_return(status: 200, body: File.new('spec/fixtures/Market/DATA201502_NSW1.csv'), headers: csv_headers)
46
46
 
47
47
  # MSATS
48
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/C4/ER})
48
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/C4/ER})
49
49
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
50
50
  .to_return(status: 200, body: File.new('spec/fixtures/MSATS/c4.xml'), headers: xml_headers)
51
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/MSATSLimits/ER})
51
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/MSATSLimits/ER})
52
52
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
53
53
  .to_return(status: 200, body: File.new('spec/fixtures/MSATS/msats_limits.xml'), headers: xml_headers)
54
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/NMIDetail/ER})
54
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/NMIDetail/ER})
55
55
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
56
56
  .to_return(status: 200, body: File.new('spec/fixtures/MSATS/nmi_details.xml'), headers: xml_headers)
57
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/NMIDiscovery/ER})
57
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/NMIDiscovery/ER})
58
58
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
59
59
  .to_return(status: 200, body: File.new('spec/fixtures/MSATS/nmi_discovery_by_address.xml'), headers: xml_headers)
60
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/ParticipantSystemStatus/ER})
60
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/ParticipantSystemStatus/ER})
61
61
  .with(headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml' })
62
62
  .to_return(status: 200, body: File.new('spec/fixtures/MSATS/participant_system_status.xml'), headers: xml_headers)
63
63
  # MSATS ERRORS
64
64
  # Invalid MSATS User
65
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/C4/NOTER})
65
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/C4/NOTER})
66
66
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
67
67
  .to_return(status: 404, body: '', headers: xml_headers)
68
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/MSATSLimits/NOTER})
68
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/MSATSLimits/NOTER})
69
69
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
70
70
  .to_return(status: 404, body: '', headers: xml_headers)
71
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/NMIDetail/NOTER})
71
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/NMIDetail/NOTER})
72
72
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
73
73
  .to_return(status: 404, body: '', headers: xml_headers)
74
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/NMIDiscovery/NOTER})
74
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/NMIDiscovery/NOTER})
75
75
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
76
76
  .to_return(status: 404, body: '', headers: xml_headers)
77
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/ParticipantSystemStatus/NOTER})
77
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/ParticipantSystemStatus/NOTER})
78
78
  .with(headers: { 'Accept' => 'text/xml', 'Content-Type' => 'text/xml' })
79
79
  .to_return(status: 404, body: '', headers: xml_headers)
80
80
  # Data errors
81
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/C4/ER\?.+?NMI=4001234566.+?})
81
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/C4/ER\?.+?NMI=4001234566.+?})
82
82
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
83
83
  .to_return(status: 404, body: '', headers: xml_headers)
84
- stub_request(:get, %r{msats.prod.nemnet.net.au/msats/ws/NMIDetail/ER\?.+?nmi=4001234566.+?})
84
+ stub_request(:get, %r{msats\.prod\.nemnet\.net\.au/msats/ws/NMIDetail/ER\?.+?nmi=4001234566.+?})
85
85
  .with(headers: { 'Accept' => ['text/xml'], 'Content-Type' => 'text/xml' })
86
86
  .to_return(status: 404, body: '', headers: xml_headers)
87
87
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aemo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Courtney
@@ -98,20 +98,20 @@ dependencies:
98
98
  requirements:
99
99
  - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: '1.14'
101
+ version: '1.19'
102
102
  - - ">="
103
103
  - !ruby/object:Gem::Version
104
- version: 1.14.3
104
+ version: 1.19.0
105
105
  type: :runtime
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
109
  - - "~>"
110
110
  - !ruby/object:Gem::Version
111
- version: '1.14'
111
+ version: '1.19'
112
112
  - - ">="
113
113
  - !ruby/object:Gem::Version
114
- version: 1.14.3
114
+ version: 1.19.0
115
115
  - !ruby/object:Gem::Dependency
116
116
  name: rexml
117
117
  requirement: !ruby/object:Gem::Requirement
@@ -151,6 +151,10 @@ files:
151
151
  - lib/aemo/market/interval.rb
152
152
  - lib/aemo/market/node.rb
153
153
  - lib/aemo/meter.rb
154
+ - lib/aemo/meter_data/flag.rb
155
+ - lib/aemo/meter_data/flag/method.rb
156
+ - lib/aemo/meter_data/flag/quality.rb
157
+ - lib/aemo/meter_data/flag/reason_code.rb
154
158
  - lib/aemo/msats.rb
155
159
  - lib/aemo/nem12.rb
156
160
  - lib/aemo/nem12/data_stream_suffix.rb
@@ -260,6 +264,7 @@ files:
260
264
  - spec/fixtures/NEM12/NEM12#mdffl0000000001#ACTEWM#NEMMCO.mdff
261
265
  - spec/fixtures/NEM12/NEM12#mdffl0000000004#ACTEWM#NEMMCO.txt
262
266
  - spec/fixtures/NEM12/NEM12#mdffl0000000008#ACTEWM#NEMMCO.txt
267
+ - spec/fixtures/NEM12/NEM12.actual_with_reason_code.csv
263
268
  - spec/fixtures/NEM12/nem12#S01#INTEGM#NEMMCO
264
269
  - spec/fixtures/NEM12/nem12#S02#INTEGM#NEMMCO
265
270
  - spec/fixtures/NEM12/nem12#S03#INTEGM#NEMMCO
@@ -292,6 +297,7 @@ files:
292
297
  - spec/lib/aemo/market/interval_spec.rb
293
298
  - spec/lib/aemo/market/node_spec.rb
294
299
  - spec/lib/aemo/market_spec.rb
300
+ - spec/lib/aemo/meter_data/flag_spec.rb
295
301
  - spec/lib/aemo/meter_spec.rb
296
302
  - spec/lib/aemo/msats_spec.rb
297
303
  - spec/lib/aemo/nem12_spec.rb
@@ -319,7 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
319
325
  - !ruby/object:Gem::Version
320
326
  version: '0'
321
327
  requirements: []
322
- rubygems_version: 3.6.7
328
+ rubygems_version: 3.6.9
323
329
  specification_version: 4
324
330
  summary: Gem providing functionality for the Australian Energy Market Operator data
325
331
  test_files: []