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.
- checksums.yaml +4 -4
- data/lib/aemo/market.rb +1 -0
- data/lib/aemo/meter_data/flag/method.rb +58 -0
- data/lib/aemo/meter_data/flag/quality.rb +18 -0
- data/lib/aemo/meter_data/flag/reason_code.rb +112 -0
- data/lib/aemo/meter_data/flag.rb +280 -0
- data/lib/aemo/msats.rb +1 -0
- data/lib/aemo/nem12/quality_method.rb +3 -63
- data/lib/aemo/nem12/reason_codes.rb +3 -108
- data/lib/aemo/nem12.rb +70 -60
- data/lib/aemo/time.rb +3 -3
- data/lib/aemo/version.rb +1 -1
- data/lib/data/xml_to_json.rb +32 -32
- data/spec/fixtures/NEM12/NEM12.actual_with_reason_code.csv +14 -0
- data/spec/lib/aemo/meter_data/flag_spec.rb +142 -0
- data/spec/lib/aemo/nem12_spec.rb +159 -13
- data/spec/spec_helper.rb +12 -12
- metadata +12 -6
|
@@ -1,110 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class NEM12
|
|
7
|
-
REASON_CODES = {
|
|
8
|
-
0 => 'Free Text Description',
|
|
9
|
-
1 => 'Meter/Equipment Changed',
|
|
10
|
-
2 => 'Extreme Weather/Wet',
|
|
11
|
-
3 => 'Quarantine',
|
|
12
|
-
4 => 'Savage Dog',
|
|
13
|
-
5 => 'Meter/Equipment Changed',
|
|
14
|
-
6 => 'Extreme Weather/Wet',
|
|
15
|
-
7 => 'Unable To Locate Meter',
|
|
16
|
-
8 => 'Vacant Premise',
|
|
17
|
-
9 => 'Meter/Equipment Changed',
|
|
18
|
-
10 => 'Lock Damaged/Seized',
|
|
19
|
-
11 => 'In Wrong Walk',
|
|
20
|
-
12 => 'Locked Premises',
|
|
21
|
-
13 => 'Locked Gate',
|
|
22
|
-
14 => 'Locked Meter Box',
|
|
23
|
-
15 => 'Access - Overgrown',
|
|
24
|
-
16 => 'Noxious Weeds',
|
|
25
|
-
17 => 'Unsafe Equipment/Location',
|
|
26
|
-
18 => 'Read Below Previous',
|
|
27
|
-
19 => 'Consumer Wanted',
|
|
28
|
-
20 => 'Damaged Equipment/Panel',
|
|
29
|
-
21 => 'Switched Off',
|
|
30
|
-
22 => 'Meter/Equipment Seals Missing',
|
|
31
|
-
23 => 'Meter/Equipment Seals Missing',
|
|
32
|
-
24 => 'Meter/Equipment Seals Missing',
|
|
33
|
-
25 => 'Meter/Equipment Seals Missing',
|
|
34
|
-
26 => 'Meter/Equipment Seals Missing',
|
|
35
|
-
27 => 'Meter/Equipment Seals Missing',
|
|
36
|
-
28 => 'Damaged Equipment/Panel',
|
|
37
|
-
29 => 'Relay Faulty/Damaged',
|
|
38
|
-
30 => 'Meter Stop Switch On',
|
|
39
|
-
31 => 'Meter/Equipment Seals Missing',
|
|
40
|
-
32 => 'Damaged Equipment/Panel',
|
|
41
|
-
33 => 'Relay Faulty/Damaged',
|
|
42
|
-
34 => 'Meter Not In Handheld',
|
|
43
|
-
35 => 'Timeswitch Faulty/Reset Required',
|
|
44
|
-
36 => 'Meter High/Ladder Required',
|
|
45
|
-
37 => 'Meter High/Ladder Required',
|
|
46
|
-
38 => 'Unsafe Equipment/Location',
|
|
47
|
-
39 => 'Reverse Energy Observed',
|
|
48
|
-
40 => 'Timeswitch Faulty/Reset Required',
|
|
49
|
-
41 => 'Faulty Equipment Display/Dials',
|
|
50
|
-
42 => 'Faulty Equipment Display/Dials',
|
|
51
|
-
43 => 'Power Outage',
|
|
52
|
-
44 => 'Unsafe Equipment/Location',
|
|
53
|
-
45 => 'Readings Failed To Validate',
|
|
54
|
-
46 => 'Extreme Weather/Hot',
|
|
55
|
-
47 => 'Refused Access',
|
|
56
|
-
48 => 'Timeswitch Faulty/Reset Required',
|
|
57
|
-
49 => 'Wet Paint',
|
|
58
|
-
50 => 'Wrong Tariff',
|
|
59
|
-
51 => 'Installation Demolished',
|
|
60
|
-
52 => 'Access - Blocked',
|
|
61
|
-
53 => 'Bees/Wasp In Meter Box',
|
|
62
|
-
54 => 'Meter Box Damaged/Faulty',
|
|
63
|
-
55 => 'Faulty Equipment Display/Dials',
|
|
64
|
-
56 => 'Meter Box Damaged/Faulty',
|
|
65
|
-
57 => 'Timeswitch Faulty/Reset Required',
|
|
66
|
-
58 => 'Meter Ok - Supply Failure',
|
|
67
|
-
59 => 'Faulty Equipment Display/Dials',
|
|
68
|
-
60 => 'Illegal Connection/Equipment Tampered',
|
|
69
|
-
61 => 'Meter Box Damaged/Faulty',
|
|
70
|
-
62 => 'Damaged Equipment/Panel',
|
|
71
|
-
63 => 'Illegal Connection/Equipment Tampered',
|
|
72
|
-
64 => 'Key Required',
|
|
73
|
-
65 => 'Wrong Key Provided',
|
|
74
|
-
66 => 'Lock Damaged/Seized',
|
|
75
|
-
67 => 'Extreme Weather/Wet',
|
|
76
|
-
68 => 'Zero Consumption',
|
|
77
|
-
69 => 'Reading Exceeds Estimate',
|
|
78
|
-
70 => 'Probe Reports Tampering',
|
|
79
|
-
71 => 'Probe Read Error',
|
|
80
|
-
72 => 'Meter/Equipment Changed',
|
|
81
|
-
73 => 'Low Consumption',
|
|
82
|
-
74 => 'High Consumption',
|
|
83
|
-
75 => 'Customer Read',
|
|
84
|
-
76 => 'Communications Fault',
|
|
85
|
-
77 => 'Estimation Forecast',
|
|
86
|
-
78 => 'Null Data',
|
|
87
|
-
79 => 'Power Outage Alarm',
|
|
88
|
-
80 => 'Short Interval Alarm',
|
|
89
|
-
81 => 'Long Interval Alarm',
|
|
90
|
-
82 => 'CRC Error',
|
|
91
|
-
83 => 'RAM Checksum Error',
|
|
92
|
-
84 => 'ROM Checksum Error',
|
|
93
|
-
85 => 'Data Missing Alarm',
|
|
94
|
-
86 => 'Clock Error Alarm',
|
|
95
|
-
87 => 'Reset Occurred',
|
|
96
|
-
88 => 'Watchdog Timeout Alarm',
|
|
97
|
-
89 => 'Time Reset Occurred',
|
|
98
|
-
90 => 'Test Mode',
|
|
99
|
-
91 => 'Load Control',
|
|
100
|
-
92 => 'Added Interval (Data Correction)',
|
|
101
|
-
93 => 'Replaced Interval (Data Correction)',
|
|
102
|
-
94 => 'Estimated Interval (Data Correction)',
|
|
103
|
-
95 => 'Pulse Overflow Alarm',
|
|
104
|
-
96 => 'Data Out Of Limits',
|
|
105
|
-
97 => 'Excluded Data',
|
|
106
|
-
98 => 'Parity Error',
|
|
107
|
-
99 => 'Energy Type (Register Changed)'
|
|
108
|
-
}.freeze
|
|
109
|
-
end
|
|
110
|
-
end
|
|
3
|
+
# This file is kept for backward compatibility
|
|
4
|
+
# Constants have been moved to lib/aemo/nem12/meter_data/flag/reason_code.rb
|
|
5
|
+
# and are now accessible via AEMO::NEM12::REASON_CODES
|
data/lib/aemo/nem12.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'csv'
|
|
4
4
|
require 'time'
|
|
5
5
|
|
|
6
|
+
require 'aemo/meter_data/flag'
|
|
6
7
|
require 'aemo/nem12/data_stream_suffix'
|
|
7
8
|
require 'aemo/nem12/quality_method'
|
|
8
9
|
require 'aemo/nem12/reason_codes'
|
|
@@ -17,6 +18,11 @@ module AEMO
|
|
|
17
18
|
CRLF = "\r\n"
|
|
18
19
|
CSV_SEPARATOR = ','
|
|
19
20
|
|
|
21
|
+
# Backward compatibility: delegate constants to Flag class
|
|
22
|
+
QUALITY_FLAGS = ::AEMO::MeterData::Flag::QUALITY_FLAGS
|
|
23
|
+
METHOD_FLAGS = ::AEMO::MeterData::Flag::METHOD_FLAGS
|
|
24
|
+
REASON_CODES = ::AEMO::MeterData::Flag::REASON_CODES
|
|
25
|
+
|
|
20
26
|
@file_contents = nil
|
|
21
27
|
@header = nil
|
|
22
28
|
@nmi_data_details = []
|
|
@@ -123,7 +129,7 @@ module AEMO
|
|
|
123
129
|
default_nem12_100,
|
|
124
130
|
nem12s.map(&:to_nem12_200_csv),
|
|
125
131
|
default_nem12_900
|
|
126
|
-
].
|
|
132
|
+
].join
|
|
127
133
|
end
|
|
128
134
|
end
|
|
129
135
|
|
|
@@ -258,17 +264,8 @@ module AEMO
|
|
|
258
264
|
end
|
|
259
265
|
end
|
|
260
266
|
|
|
261
|
-
# Deal with flags
|
|
262
|
-
flag =
|
|
263
|
-
# Based on QualityMethod and ReasonCode
|
|
264
|
-
if csv[intervals_offset + 0].length == 3 || !csv[intervals_offset + 1].nil?
|
|
265
|
-
flag ||= { quality_flag: nil, method_flag: nil, reason_code: nil }
|
|
266
|
-
if csv[intervals_offset + 0].length == 3
|
|
267
|
-
flag[:quality_flag] = csv[intervals_offset + 0][0]
|
|
268
|
-
flag[:method_flag] = csv[intervals_offset + 0][1, 2].to_i
|
|
269
|
-
end
|
|
270
|
-
flag[:reason_code] = csv[intervals_offset + 1].to_i unless csv[intervals_offset + 1].nil?
|
|
271
|
-
end
|
|
267
|
+
# Deal with flags - explicitly set all flag values
|
|
268
|
+
flag = AEMO::MeterData::Flag.from_quality_method_reason_code(quality_method: csv[intervals_offset + 0], reason_code: csv[intervals_offset + 1], validate: strict)
|
|
272
269
|
|
|
273
270
|
# Deal with updated_at & msats_load_at
|
|
274
271
|
updated_at = nil
|
|
@@ -302,7 +299,7 @@ module AEMO
|
|
|
302
299
|
# @param [String] line A single line in string format
|
|
303
300
|
# @param [Boolean] strict
|
|
304
301
|
# @return [Hash] the line parsed into a hash of information
|
|
305
|
-
def parse_nem12_400(line, strict: true) # rubocop:disable
|
|
302
|
+
def parse_nem12_400(line, strict: true) # rubocop:disable Naming/VariableNumber
|
|
306
303
|
csv = line.parse_csv
|
|
307
304
|
raise ArgumentError, 'RecordIndicator is not 400' if csv[0] != '400'
|
|
308
305
|
raise ArgumentError, 'StartInterval is not valid' if csv[1].nil? || csv[1].match(/^\d+$/).nil?
|
|
@@ -318,32 +315,30 @@ module AEMO
|
|
|
318
315
|
|
|
319
316
|
interval_events = []
|
|
320
317
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
# Update with flag details
|
|
343
|
-
@interval_data[interval_start_point + (i - 1)][:flag] = flag
|
|
344
|
-
end
|
|
345
|
-
@interval_events += interval_events
|
|
318
|
+
number_of_intervals = 1440 / @data_details.last[:interval_length]
|
|
319
|
+
interval_start_point = @interval_data.length - number_of_intervals
|
|
320
|
+
|
|
321
|
+
# For each of these
|
|
322
|
+
# Parse reason code - only set if present and not empty
|
|
323
|
+
parsed_reason_code = nil
|
|
324
|
+
parsed_reason_code = csv[4].to_i unless csv[4].nil? || csv[4].empty?
|
|
325
|
+
|
|
326
|
+
base_interval_event = { datetime: nil, quality_method: csv[3], reason_code: parsed_reason_code,
|
|
327
|
+
reason_description: csv[5] }
|
|
328
|
+
|
|
329
|
+
# Interval Numbers are 1-indexed
|
|
330
|
+
((csv[1].to_i)..(csv[2].to_i)).each do |i|
|
|
331
|
+
interval_event = base_interval_event.dup
|
|
332
|
+
interval_event[:datetime] = @interval_data[interval_start_point + (i - 1)][:datetime]
|
|
333
|
+
interval_events << interval_event
|
|
334
|
+
|
|
335
|
+
flag = AEMO::MeterData::Flag.from_quality_method_reason_code(quality_method: interval_event[:quality_method], reason_code: interval_event[:reason_code], validate: strict)
|
|
336
|
+
|
|
337
|
+
# Update with flag details
|
|
338
|
+
@interval_data[interval_start_point + (i - 1)][:flag] = flag
|
|
346
339
|
end
|
|
340
|
+
@interval_events += interval_events
|
|
341
|
+
|
|
347
342
|
interval_events
|
|
348
343
|
end
|
|
349
344
|
|
|
@@ -361,20 +356,6 @@ module AEMO
|
|
|
361
356
|
# @return [Hash] the line parsed into a hash of information
|
|
362
357
|
def parse_nem12_900(_line, strict: true); end # rubocop:disable Naming/VariableNumber
|
|
363
358
|
|
|
364
|
-
# Turns the flag to a string
|
|
365
|
-
#
|
|
366
|
-
# @param [Hash] flag the object of a flag
|
|
367
|
-
# @return [nil, String] a hyphenated string for the flag or nil
|
|
368
|
-
def flag_to_s(flag)
|
|
369
|
-
flag_to_s = []
|
|
370
|
-
unless flag.nil?
|
|
371
|
-
flag_to_s << QUALITY_FLAGS[flag[:quality_flag]] unless QUALITY_FLAGS[flag[:quality_flag]].nil?
|
|
372
|
-
flag_to_s << METHOD_FLAGS[flag[:method_flag]][:short_descriptor] unless METHOD_FLAGS[flag[:method_flag]].nil?
|
|
373
|
-
flag_to_s << REASON_CODES[flag[:reason_code]] unless REASON_CODES[flag[:reason_code]].nil?
|
|
374
|
-
end
|
|
375
|
-
flag_to_s.empty? ? nil : flag_to_s.join(' - ')
|
|
376
|
-
end
|
|
377
|
-
|
|
378
359
|
# @return [Array] array of a NEM12 file a given Meter + Data Stream for easy reading
|
|
379
360
|
def to_a
|
|
380
361
|
@interval_data.map do |d|
|
|
@@ -384,7 +365,7 @@ module AEMO
|
|
|
384
365
|
d[:data_details][:uom],
|
|
385
366
|
d[:datetime],
|
|
386
367
|
d[:value],
|
|
387
|
-
|
|
368
|
+
d[:flag]&.to_s
|
|
388
369
|
]
|
|
389
370
|
end
|
|
390
371
|
end
|
|
@@ -408,7 +389,7 @@ module AEMO
|
|
|
408
389
|
to_nem12_100_csv,
|
|
409
390
|
to_nem12_200_csv,
|
|
410
391
|
to_nem12_900_csv
|
|
411
|
-
].
|
|
392
|
+
].join
|
|
412
393
|
end
|
|
413
394
|
|
|
414
395
|
# Output the AEMO::NEM12 to a valid NEM12 100 row CSV string.
|
|
@@ -462,20 +443,36 @@ module AEMO
|
|
|
462
443
|
end
|
|
463
444
|
daily_datas.keys.sort.each do |key|
|
|
464
445
|
daily_data = daily_datas[key].sort_by { |x| x[:datetime] }
|
|
465
|
-
|
|
446
|
+
|
|
447
|
+
# Check if all intervals have identical flags
|
|
448
|
+
# If so, use that flag's quality method in the 300 record
|
|
449
|
+
# Otherwise use 'V' (variable data) and generate 400 records
|
|
450
|
+
first_flag = daily_data.first[:flag]
|
|
451
|
+
all_same_flag = daily_data.all? { |x| x[:flag] == first_flag }
|
|
452
|
+
|
|
453
|
+
if all_same_flag
|
|
454
|
+
# All intervals have the same flag - use it in the 300 record
|
|
455
|
+
quality_method = first_flag&.to_quality_method || ''
|
|
456
|
+
reason_code = first_flag&.reason_code || ''
|
|
457
|
+
else
|
|
458
|
+
# Intervals have different flags - use 'V' and generate 400 records
|
|
459
|
+
quality_method = 'V'
|
|
460
|
+
reason_code = ''
|
|
461
|
+
end
|
|
466
462
|
|
|
467
463
|
lines << [
|
|
468
464
|
'300',
|
|
469
465
|
key,
|
|
470
466
|
daily_data.map { |x| x[:value] },
|
|
471
|
-
|
|
472
|
-
|
|
467
|
+
quality_method,
|
|
468
|
+
reason_code,
|
|
473
469
|
'',
|
|
474
470
|
daily_data.first[:updated_at] ? AEMO::Time.format_timestamp14(daily_data.first[:updated_at]) : nil,
|
|
475
471
|
daily_data.first[:msats_load_at] ? AEMO::Time.format_timestamp14(daily_data.first[:msats_load_at]) : nil
|
|
476
472
|
].flatten.join(CSV_SEPARATOR)
|
|
477
473
|
|
|
478
|
-
|
|
474
|
+
# Generate 400 records only if we have variable data
|
|
475
|
+
next if all_same_flag
|
|
479
476
|
|
|
480
477
|
lines << to_nem12_400_csv(daily_data:)
|
|
481
478
|
end
|
|
@@ -504,12 +501,25 @@ module AEMO
|
|
|
504
501
|
end
|
|
505
502
|
|
|
506
503
|
nem12_400_rows.map do |row|
|
|
504
|
+
flag = row[:flag]
|
|
505
|
+
# Default to 'A' if flag is nil, otherwise use explicit quality flag
|
|
506
|
+
quality_flag = flag.nil? ? 'A' : flag.quality_flag
|
|
507
|
+
method_flag = flag&.method_flag
|
|
508
|
+
reason_code = flag&.reason_code
|
|
509
|
+
|
|
510
|
+
# Build quality method string (quality flag + optional method flag)
|
|
511
|
+
quality_method = if method_flag.nil?
|
|
512
|
+
quality_flag
|
|
513
|
+
else
|
|
514
|
+
"#{quality_flag}#{format('%02d', method_flag)}"
|
|
515
|
+
end
|
|
516
|
+
|
|
507
517
|
[
|
|
508
518
|
'400',
|
|
509
519
|
row[:start_index],
|
|
510
520
|
row[:finish_index],
|
|
511
|
-
|
|
512
|
-
|
|
521
|
+
quality_method,
|
|
522
|
+
reason_code || '',
|
|
513
523
|
''
|
|
514
524
|
].join(CSV_SEPARATOR)
|
|
515
525
|
end.join(CRLF)
|
data/lib/aemo/time.rb
CHANGED
|
@@ -10,11 +10,11 @@ module AEMO
|
|
|
10
10
|
module Time
|
|
11
11
|
NEMTIMEZONE = 'Australia/Brisbane'
|
|
12
12
|
TIMESTAMP14 = '%Y%m%d%H%M%S'
|
|
13
|
-
TIMESTAMP14_PATTERN =
|
|
13
|
+
TIMESTAMP14_PATTERN = /\A\d{4}\d{2}\d{2}\d{2}\d{2}\d{2}\z/
|
|
14
14
|
TIMESTAMP12 = '%Y%m%d%H%M'
|
|
15
|
-
TIMESTAMP12_PATTERN =
|
|
15
|
+
TIMESTAMP12_PATTERN = /\A\d{4}\d{2}\d{2}\d{2}\d{2}\z/
|
|
16
16
|
TIMESTAMP8 = '%Y%m%d'
|
|
17
|
-
TIMESTAMP8_PATTERN =
|
|
17
|
+
TIMESTAMP8_PATTERN = /\A\d{4}\d{2}\d{2}\z/
|
|
18
18
|
|
|
19
19
|
class << self
|
|
20
20
|
# Format a time to a timestamp 14.
|
data/lib/aemo/version.rb
CHANGED
data/lib/data/xml_to_json.rb
CHANGED
|
@@ -40,39 +40,39 @@ end
|
|
|
40
40
|
# Now to create the DLF and TNI output JSON files for use
|
|
41
41
|
@files.select { |x| ['aemo-tni.xml', 'aemo-dlf.xml'].include?(x) }
|
|
42
42
|
.each do |file|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
output_file = file.gsub('.xml', '.json')
|
|
44
|
+
output_data = {}
|
|
45
|
+
open_file = File.open(File.join(@path, file))
|
|
46
|
+
xml = Nokogiri::XML(open_file) do |c|
|
|
47
|
+
c.options = Nokogiri::XML::ParseOptions::NOBLANKS
|
|
48
|
+
end
|
|
49
|
+
open_file.close
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
xml.xpath('//Row').each do |row|
|
|
52
|
+
row_children = row.children
|
|
53
|
+
code = row_children.find { |x| x.name == 'Code' }.children.first.text
|
|
54
|
+
output_data[code] ||= []
|
|
55
|
+
output_data_instance = {}
|
|
56
|
+
row_children.each do |row_child|
|
|
57
|
+
output_data_instance[row_child.name] = row_child.children.first.text
|
|
58
|
+
end
|
|
59
|
+
if file =~ /tni/
|
|
60
|
+
puts "output_data_instance: #{output_data_instance.inspect}"
|
|
61
|
+
output_data_instance[:mlf_data] = {}
|
|
62
|
+
unless @mlf_data[code].nil?
|
|
63
|
+
output_data_instance[:mlf_data] = @mlf_data[code].deep_dup
|
|
64
|
+
output_data_instance[:mlf_data][:loss_factors].reject! do |x|
|
|
65
|
+
Time.parse(output_data_instance['ToDate']) < x[:start] ||
|
|
66
|
+
Time.parse(output_data_instance['FromDate']) >= x[:finish]
|
|
67
|
+
end
|
|
68
|
+
puts 'output_data_instance[:mlf_data][:loss_factors]: ' \
|
|
69
|
+
"#{output_data_instance[:mlf_data][:loss_factors].inspect}"
|
|
70
|
+
end
|
|
71
|
+
elsif file =~ /dlf/
|
|
72
|
+
output_data_instance[:nsp_code] = @dlf_data[code]
|
|
73
|
+
end
|
|
74
|
+
output_data[code] << output_data_instance
|
|
67
75
|
end
|
|
68
|
-
puts 'output_data_instance[:mlf_data][:loss_factors]: ' \
|
|
69
|
-
"#{output_data_instance[:mlf_data][:loss_factors].inspect}"
|
|
70
|
-
end
|
|
71
|
-
elsif file =~ /dlf/
|
|
72
|
-
output_data_instance[:nsp_code] = @dlf_data[code]
|
|
73
|
-
end
|
|
74
|
-
output_data[code] << output_data_instance
|
|
75
|
-
end
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
File.write(File.join(@path, output_file), output_data.to_json)
|
|
78
78
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
100,NEM12,200505181432,ENOSI,NEMMCO
|
|
2
|
+
200,4123456789,E1B1,E1,E1,E1,B1234567,KWH,5,20260211
|
|
3
|
+
300,20251227,0.5120,0.4930,0.4170,0.3730,0.3850,0.4170,0.3900,0.4020,0.3910,0.3730,0.4830,0.5080,0.4060,0.4040,0.3640,0.3780,0.3070,0.2620,0.3280,0.2940,0.2930,0.3320,0.2960,0.3290,0.3060,0.2550,0.2920,0.2890,0.4040,0.4200,0.4030,0.4130,0.3780,0.3430,0.4050,0.3440,0.3590,0.3320,0.3050,0.3180,0.2850,0.2770,0.3250,0.2960,0.3140,0.3390,0.2970,0.3240,0.3060,0.3010,0.3900,0.3250,0.3110,0.3310,0.3200,0.3810,0.3060,0.2720,0.3150,0.2980,0.2760,0.2470,0.2160,0.3190,0.3180,0.3060,0.3310,0.2980,0.3140,0.3180,0.2420,0.2760,0.2570,0.2330,0.2700,0.2430,0.2530,0.2610,0.2200,0.3910,0.3660,0.3130,0.2800,0.2280,0.2330,0.2430,0.1850,0.2040,0.2230,0.1830,0.1930,0.1150,0.0730,0.0940,0.0230,0.0070,0.0040,0.0040,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0320,0.0090,0.1130,0.0280,0.0230,0.0000,0.0000,0.0040,0.0010,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.1710,0.0000,0.0000,0.1780,0.0000,0.0000,0.1930,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0190,0.0020,0.0000,0.0000,0.0000,0.0050,0.0140,0.0220,0.0130,0.0640,0.0440,0.0700,0.2380,0.1860,0.1960,0.2000,0.1410,0.0580,0.0170,0.0030,0.0170,0.0750,0.0370,0.0820,0.1060,0.1000,0.1240,0.0900,0.1430,0.1460,0.1510,0.2060,0.1830,0.2180,0.2280,0.1970,0.2380,0.2190,0.1940,0.2420,0.2130,0.2200,0.2300,0.1940,0.2660,0.4730,0.4500,0.5530,0.5600,0.5680,0.6040,0.5620,0.5870,0.6150,0.5770,0.6100,0.6000,0.5710,0.6140,0.5810,0.5820,0.6090,0.5720,0.6590,0.6520,0.7140,0.8750,0.7740,0.8690,0.9730,0.9240,1.0040,0.9260,0.9540,0.9420,0.9540,0.9040,0.9130,0.8790,0.9200,0.8610,0.8650,0.9030,0.8840,0.9090,0.8910,0.7720,0.7800,0.7550,0.7470,0.8830,0.8760,0.8310,0.7710,0.7040,0.7240,0.7540,0.8530,0.8490,0.7540,0.7180,V,,,20251228011206,
|
|
4
|
+
400,1,60,A,,
|
|
5
|
+
400,61,61,A,79,
|
|
6
|
+
400,62,288,A,,
|
|
7
|
+
500,O,,20251228000000,079499.641
|
|
8
|
+
200,4123456789,E1B1,B1,B1,B1,B1234567,KWH,5,20260211
|
|
9
|
+
300,20251227,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0170,0.0400,0.0540,0.1690,0.1730,0.2440,0.2320,0.2400,0.3070,0.2730,0.0240,0.0480,0.0170,0.0340,0.1590,0.3290,0.4210,0.1430,0.1240,0.1670,0.2100,0.3860,0.3710,0.2060,0.1680,0.3940,0.4270,0.3920,0.4480,0.4470,0.4280,0.4480,0.4390,0.4310,0.0640,0.3180,0.4450,0.0430,0.3560,0.4490,0.0590,0.3600,0.5000,0.4820,0.5650,0.5720,0.5390,0.5650,0.5430,0.5470,0.5780,0.5330,0.5690,0.5760,0.5530,0.6200,0.5850,0.5880,0.5870,0.5780,0.6130,0.6050,0.5490,0.6050,0.5570,0.4580,0.4770,0.5630,0.5390,0.5510,0.5830,0.4430,0.5850,0.5460,0.5410,0.5600,0.4840,0.4970,0.4790,0.3430,0.1860,0.2020,0.1000,0.0410,0.1300,0.1250,0.1820,0.1490,0.2100,0.1510,0.1440,0.1530,0.0960,0.0860,0.0860,0.0540,0.0810,0.0150,0.0500,0.0150,0.0180,0.0140,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0210,0.0150,0.0410,0.0290,0.0220,0.0000,0.0040,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,V,,,20251228011206,
|
|
10
|
+
400,1,60,A,,
|
|
11
|
+
400,61,61,A,79,
|
|
12
|
+
400,62,288,A,,
|
|
13
|
+
500,O,,20251228000000,027575.424
|
|
14
|
+
900
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe AEMO::MeterData::Flag do
|
|
6
|
+
describe '.new' do
|
|
7
|
+
it 'creates a flag with all attributes' do
|
|
8
|
+
flag = described_class.new(quality_flag: 'E', method_flag: 52, reason_code: 1)
|
|
9
|
+
expect(flag.quality_flag).to eq('E')
|
|
10
|
+
expect(flag.method_flag).to eq(52)
|
|
11
|
+
expect(flag.reason_code).to eq(1)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'creates a flag with nil attributes' do
|
|
15
|
+
flag = described_class.new
|
|
16
|
+
expect(flag.quality_flag).to be_nil
|
|
17
|
+
expect(flag.method_flag).to be_nil
|
|
18
|
+
expect(flag.reason_code).to be_nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'validates quality flag' do
|
|
22
|
+
expect { described_class.new(quality_flag: 'X') }.to raise_error(ArgumentError, /Invalid quality flag/)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'validates method flag' do
|
|
26
|
+
expect { described_class.new(method_flag: 999) }.to raise_error(ArgumentError, /Invalid method flag/)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'validates reason code' do
|
|
30
|
+
expect { described_class.new(reason_code: 999) }.to raise_error(ArgumentError, /Invalid reason code/)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'validates flag combinations' do
|
|
34
|
+
expect { described_class.new(quality_flag: 'E') }.to raise_error(ArgumentError, /requires a method flag/)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '.from_hash' do
|
|
39
|
+
it 'creates a flag from a hash' do
|
|
40
|
+
flag = described_class.from_hash({ quality_flag: 'E', method_flag: 52, reason_code: 1 })
|
|
41
|
+
expect(flag.quality_flag).to eq('E')
|
|
42
|
+
expect(flag.method_flag).to eq(52)
|
|
43
|
+
expect(flag.reason_code).to eq(1)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns nil for nil input' do
|
|
47
|
+
expect(described_class.from_hash(nil)).to be_nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'returns the flag if already a Flag instance' do
|
|
51
|
+
flag = described_class.new(quality_flag: 'A')
|
|
52
|
+
expect(described_class.from_hash(flag)).to eq(flag)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe '.normalize' do
|
|
57
|
+
it 'normalizes nil to actual data flag' do
|
|
58
|
+
flag = described_class.normalize(nil)
|
|
59
|
+
expect(flag.quality_flag).to eq('A')
|
|
60
|
+
expect(flag.method_flag).to be_nil
|
|
61
|
+
expect(flag.reason_code).to be_nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'removes method flag from A quality flag' do
|
|
65
|
+
flag = described_class.normalize({ quality_flag: 'A', method_flag: 52 })
|
|
66
|
+
expect(flag.quality_flag).to eq('A')
|
|
67
|
+
expect(flag.method_flag).to be_nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'removes method flag and reason code from V quality flag' do
|
|
71
|
+
flag = described_class.normalize({ quality_flag: 'V', method_flag: 52, reason_code: 1 })
|
|
72
|
+
expect(flag.quality_flag).to eq('V')
|
|
73
|
+
expect(flag.method_flag).to be_nil
|
|
74
|
+
expect(flag.reason_code).to be_nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'preserves E quality flag with method flag' do
|
|
78
|
+
flag = described_class.normalize({ quality_flag: 'E', method_flag: 52 })
|
|
79
|
+
expect(flag.quality_flag).to eq('E')
|
|
80
|
+
expect(flag.method_flag).to eq(52)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe '#to_quality_method' do
|
|
85
|
+
it 'returns A for nil quality flag' do
|
|
86
|
+
flag = described_class.new
|
|
87
|
+
expect(flag.to_quality_method).to eq('A')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'returns quality flag when method flag is nil' do
|
|
91
|
+
flag = described_class.new(quality_flag: 'A')
|
|
92
|
+
expect(flag.to_quality_method).to eq('A')
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'returns quality flag with formatted method flag' do
|
|
96
|
+
flag = described_class.new(quality_flag: 'E', method_flag: 52)
|
|
97
|
+
expect(flag.to_quality_method).to eq('E52')
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe '#to_s' do
|
|
102
|
+
it 'returns nil for nil flag' do
|
|
103
|
+
flag = described_class.new
|
|
104
|
+
expect(flag.to_s).to be_nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'returns quality flag description' do
|
|
108
|
+
flag = described_class.new(quality_flag: 'A')
|
|
109
|
+
expect(flag.to_s).to eq('Actual Data')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'returns quality flag and method flag description' do
|
|
113
|
+
flag = described_class.new(quality_flag: 'E', method_flag: 52)
|
|
114
|
+
expect(flag.to_s).to eq('Forward Estimated Data - Previous Read')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'returns quality flag, method flag, and reason code description' do
|
|
118
|
+
flag = described_class.new(quality_flag: 'E', method_flag: 52, reason_code: 1)
|
|
119
|
+
expect(flag.to_s).to eq('Forward Estimated Data - Previous Read - Meter/Equipment Changed')
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe '#to_h' do
|
|
124
|
+
it 'returns a hash representation' do
|
|
125
|
+
flag = described_class.new(quality_flag: 'E', method_flag: 52, reason_code: 1)
|
|
126
|
+
expect(flag.to_h).to eq({ quality_flag: 'E', method_flag: 52, reason_code: 1 })
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe '#==' do
|
|
131
|
+
it 'compares two flags' do
|
|
132
|
+
flag1 = described_class.new(quality_flag: 'E', method_flag: 52, reason_code: 1)
|
|
133
|
+
flag2 = described_class.new(quality_flag: 'E', method_flag: 52, reason_code: 1)
|
|
134
|
+
expect(flag1).to eq(flag2)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'compares flag with hash' do
|
|
138
|
+
flag = described_class.new(quality_flag: 'E', method_flag: 52, reason_code: 1)
|
|
139
|
+
expect(flag).to eq({ quality_flag: 'E', method_flag: 52, reason_code: 1 })
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|