aemo 0.7.2 → 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/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/nem12/quality_method.rb +3 -63
- data/lib/aemo/nem12/reason_codes.rb +3 -108
- data/lib/aemo/nem12.rb +68 -58
- 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
- metadata +11 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 284b65f74f29337015ac3c4072c3599068565538962eec46d07e2c3a5d37f1f3
|
|
4
|
+
data.tar.gz: 8c4142b329c6bc78f7a2407572d5adbf041659a90362ee5eb8c3595f7f9f41ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fbf2afed45b613df53d4f01d1785b9e4b504859d8002fdb1180dfd4977b638eedf4e3cb6b294bd83bc6d923e12cff6474e03a571e6d012410e4ef943f9db4a52
|
|
7
|
+
data.tar.gz: 4288d1a9c8eeccc67a1020d7d86b8377080f34a89f2c3ebeb28d8b873a6ec84ba502dad650cd798cf4d87ef6d2c9bae2ef28a129b6e18a27f8e9a0fe1b157fe8
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AEMO
|
|
4
|
+
module MeterData
|
|
5
|
+
class Flag
|
|
6
|
+
# Method flags for NEM12/NEM13 meter data
|
|
7
|
+
# @since 0.1.4
|
|
8
|
+
METHOD_FLAGS = {
|
|
9
|
+
11 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Check', description: '' },
|
|
10
|
+
12 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Calculated', description: '' },
|
|
11
|
+
13 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'SCADA', description: '' },
|
|
12
|
+
14 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Retrospective Like Day', description: 'Updated v7.8' },
|
|
13
|
+
15 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Retrospective Average Like Day', description: 'Updated v7.8' },
|
|
14
|
+
16 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Agreed', description: '[OBSOLETE] v7.8' },
|
|
15
|
+
17 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Linear', description: '' },
|
|
16
|
+
18 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Alternate', description: '' },
|
|
17
|
+
19 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Zero', description: '' },
|
|
18
|
+
20 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Prospective Like Day',
|
|
19
|
+
description: 'Updated v7.8' },
|
|
20
|
+
21 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Five-minute No Historical Data',
|
|
21
|
+
description: 'Added v7.8' },
|
|
22
|
+
22 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Prospective Ave Like Day',
|
|
23
|
+
description: 'Added v7.8' },
|
|
24
|
+
23 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Previous Year',
|
|
25
|
+
description: 'Added v7.8' },
|
|
26
|
+
24 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Data Scaling',
|
|
27
|
+
description: 'Added v7.8' },
|
|
28
|
+
25 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'ADL',
|
|
29
|
+
description: 'Added v7.8' },
|
|
30
|
+
51 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Previous Year', description: '' },
|
|
31
|
+
52 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Previous Read', description: '' },
|
|
32
|
+
53 => { type: %w[SUB], installation_type: 5, short_descriptor: 'Revision', description: '' },
|
|
33
|
+
54 => { type: %w[SUB], installation_type: 5, short_descriptor: 'Linear', description: '' },
|
|
34
|
+
55 => { type: %w[SUB], installation_type: 5, short_descriptor: 'Agreed', description: '' },
|
|
35
|
+
56 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Prior to First Read - Agreed',
|
|
36
|
+
description: '' },
|
|
37
|
+
57 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Customer Class', description: '' },
|
|
38
|
+
58 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Zero', description: '' },
|
|
39
|
+
59 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Five-minute No Historical Data',
|
|
40
|
+
description: '' },
|
|
41
|
+
61 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Previous Year', description: '' },
|
|
42
|
+
62 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Previous Read', description: '' },
|
|
43
|
+
63 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Customer Class', description: '' },
|
|
44
|
+
64 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Agreed', description: '' },
|
|
45
|
+
65 => { type: %w[EST], installation_type: 6, short_descriptor: 'ADL', description: '' },
|
|
46
|
+
66 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Revision', description: '' },
|
|
47
|
+
67 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Customer Read', description: '' },
|
|
48
|
+
68 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Zero', description: '' },
|
|
49
|
+
69 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Linear extrapolation', description: '' },
|
|
50
|
+
71 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Recalculation', description: '' },
|
|
51
|
+
72 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Revised Table', description: '' },
|
|
52
|
+
73 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Revised Algorithm', description: '' },
|
|
53
|
+
74 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Agreed', description: '' },
|
|
54
|
+
75 => { type: %w[EST], installation_type: 7, short_descriptor: 'Existing Table', description: '' }
|
|
55
|
+
}.freeze
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AEMO
|
|
4
|
+
module MeterData
|
|
5
|
+
class Flag
|
|
6
|
+
# Quality flags for NEM12/NEM13 meter data
|
|
7
|
+
# @since 0.1.4
|
|
8
|
+
QUALITY_FLAGS = {
|
|
9
|
+
'A' => 'Actual Data',
|
|
10
|
+
'E' => 'Forward Estimated Data',
|
|
11
|
+
'F' => 'Final Substituted Data',
|
|
12
|
+
'N' => 'Null Data',
|
|
13
|
+
'S' => 'Substituted Data',
|
|
14
|
+
'V' => 'Variable Data'
|
|
15
|
+
}.freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AEMO
|
|
4
|
+
module MeterData
|
|
5
|
+
class Flag
|
|
6
|
+
# Reason codes for NEM12/NEM13 meter data
|
|
7
|
+
# @since 0.1.4
|
|
8
|
+
REASON_CODES = {
|
|
9
|
+
0 => 'Free Text Description',
|
|
10
|
+
1 => 'Meter/Equipment Changed',
|
|
11
|
+
2 => 'Extreme Weather/Wet',
|
|
12
|
+
3 => 'Quarantine',
|
|
13
|
+
4 => 'Savage Dog',
|
|
14
|
+
5 => 'Meter/Equipment Changed',
|
|
15
|
+
6 => 'Extreme Weather/Wet',
|
|
16
|
+
7 => 'Unable To Locate Meter',
|
|
17
|
+
8 => 'Vacant Premise',
|
|
18
|
+
9 => 'Meter/Equipment Changed',
|
|
19
|
+
10 => 'Lock Damaged/Seized',
|
|
20
|
+
11 => 'In Wrong Walk',
|
|
21
|
+
12 => 'Locked Premises',
|
|
22
|
+
13 => 'Locked Gate',
|
|
23
|
+
14 => 'Locked Meter Box',
|
|
24
|
+
15 => 'Access - Overgrown',
|
|
25
|
+
16 => 'Noxious Weeds',
|
|
26
|
+
17 => 'Unsafe Equipment/Location',
|
|
27
|
+
18 => 'Read Below Previous',
|
|
28
|
+
19 => 'Consumer Wanted',
|
|
29
|
+
20 => 'Damaged Equipment/Panel',
|
|
30
|
+
21 => 'Switched Off',
|
|
31
|
+
22 => 'Meter/Equipment Seals Missing',
|
|
32
|
+
23 => 'Meter/Equipment Seals Missing',
|
|
33
|
+
24 => 'Meter/Equipment Seals Missing',
|
|
34
|
+
25 => 'Meter/Equipment Seals Missing',
|
|
35
|
+
26 => 'Meter/Equipment Seals Missing',
|
|
36
|
+
27 => 'Meter/Equipment Seals Missing',
|
|
37
|
+
28 => 'Damaged Equipment/Panel',
|
|
38
|
+
29 => 'Relay Faulty/Damaged',
|
|
39
|
+
30 => 'Meter Stop Switch On',
|
|
40
|
+
31 => 'Meter/Equipment Seals Missing',
|
|
41
|
+
32 => 'Damaged Equipment/Panel',
|
|
42
|
+
33 => 'Relay Faulty/Damaged',
|
|
43
|
+
34 => 'Meter Not In Handheld',
|
|
44
|
+
35 => 'Timeswitch Faulty/Reset Required',
|
|
45
|
+
36 => 'Meter High/Ladder Required',
|
|
46
|
+
37 => 'Meter High/Ladder Required',
|
|
47
|
+
38 => 'Unsafe Equipment/Location',
|
|
48
|
+
39 => 'Reverse Energy Observed',
|
|
49
|
+
40 => 'Timeswitch Faulty/Reset Required',
|
|
50
|
+
41 => 'Faulty Equipment Display/Dials',
|
|
51
|
+
42 => 'Faulty Equipment Display/Dials',
|
|
52
|
+
43 => 'Power Outage',
|
|
53
|
+
44 => 'Unsafe Equipment/Location',
|
|
54
|
+
45 => 'Readings Failed To Validate',
|
|
55
|
+
46 => 'Extreme Weather/Hot',
|
|
56
|
+
47 => 'Refused Access',
|
|
57
|
+
48 => 'Timeswitch Faulty/Reset Required',
|
|
58
|
+
49 => 'Wet Paint',
|
|
59
|
+
50 => 'Wrong Tariff',
|
|
60
|
+
51 => 'Installation Demolished',
|
|
61
|
+
52 => 'Access - Blocked',
|
|
62
|
+
53 => 'Bees/Wasp In Meter Box',
|
|
63
|
+
54 => 'Meter Box Damaged/Faulty',
|
|
64
|
+
55 => 'Faulty Equipment Display/Dials',
|
|
65
|
+
56 => 'Meter Box Damaged/Faulty',
|
|
66
|
+
57 => 'Timeswitch Faulty/Reset Required',
|
|
67
|
+
58 => 'Meter Ok - Supply Failure',
|
|
68
|
+
59 => 'Faulty Equipment Display/Dials',
|
|
69
|
+
60 => 'Illegal Connection/Equipment Tampered',
|
|
70
|
+
61 => 'Meter Box Damaged/Faulty',
|
|
71
|
+
62 => 'Damaged Equipment/Panel',
|
|
72
|
+
63 => 'Illegal Connection/Equipment Tampered',
|
|
73
|
+
64 => 'Key Required',
|
|
74
|
+
65 => 'Wrong Key Provided',
|
|
75
|
+
66 => 'Lock Damaged/Seized',
|
|
76
|
+
67 => 'Extreme Weather/Wet',
|
|
77
|
+
68 => 'Zero Consumption',
|
|
78
|
+
69 => 'Reading Exceeds Estimate',
|
|
79
|
+
70 => 'Probe Reports Tampering',
|
|
80
|
+
71 => 'Probe Read Error',
|
|
81
|
+
72 => 'Meter/Equipment Changed',
|
|
82
|
+
73 => 'Low Consumption',
|
|
83
|
+
74 => 'High Consumption',
|
|
84
|
+
75 => 'Customer Read',
|
|
85
|
+
76 => 'Communications Fault',
|
|
86
|
+
77 => 'Estimation Forecast',
|
|
87
|
+
78 => 'Null Data',
|
|
88
|
+
79 => 'Power Outage Alarm',
|
|
89
|
+
80 => 'Short Interval Alarm',
|
|
90
|
+
81 => 'Long Interval Alarm',
|
|
91
|
+
82 => 'CRC Error',
|
|
92
|
+
83 => 'RAM Checksum Error',
|
|
93
|
+
84 => 'ROM Checksum Error',
|
|
94
|
+
85 => 'Data Missing Alarm',
|
|
95
|
+
86 => 'Clock Error Alarm',
|
|
96
|
+
87 => 'Reset Occurred',
|
|
97
|
+
88 => 'Watchdog Timeout Alarm',
|
|
98
|
+
89 => 'Time Reset Occurred',
|
|
99
|
+
90 => 'Test Mode',
|
|
100
|
+
91 => 'Load Control',
|
|
101
|
+
92 => 'Added Interval (Data Correction)',
|
|
102
|
+
93 => 'Replaced Interval (Data Correction)',
|
|
103
|
+
94 => 'Estimated Interval (Data Correction)',
|
|
104
|
+
95 => 'Pulse Overflow Alarm',
|
|
105
|
+
96 => 'Data Out Of Limits',
|
|
106
|
+
97 => 'Excluded Data',
|
|
107
|
+
98 => 'Parity Error',
|
|
108
|
+
99 => 'Energy Type (Register Changed)'
|
|
109
|
+
}.freeze
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'flag/quality'
|
|
4
|
+
require_relative 'flag/method'
|
|
5
|
+
require_relative 'flag/reason_code'
|
|
6
|
+
|
|
7
|
+
module AEMO
|
|
8
|
+
module MeterData
|
|
9
|
+
# Represents a NEM12/NEM13 quality flag with validation and conversion capabilities
|
|
10
|
+
# @since 0.2.0
|
|
11
|
+
class Flag
|
|
12
|
+
attr_reader :quality_flag, :method_flag, :reason_code
|
|
13
|
+
|
|
14
|
+
# Initialize a new Flag instance
|
|
15
|
+
#
|
|
16
|
+
# @param quality_flag [String, nil] the quality flag ('A', 'E', 'F', 'N', 'S', 'V')
|
|
17
|
+
# @param method_flag [Integer, nil] the method flag (11-75)
|
|
18
|
+
# @param reason_code [Integer, nil] the reason code (0-99)
|
|
19
|
+
# @param validate [Boolean] whether to validate the flag (default: true)
|
|
20
|
+
def initialize(quality_flag: nil, method_flag: nil, reason_code: nil, validate: true)
|
|
21
|
+
@quality_flag = quality_flag
|
|
22
|
+
@method_flag = method_flag
|
|
23
|
+
@reason_code = reason_code
|
|
24
|
+
|
|
25
|
+
# Validate the flag if requested
|
|
26
|
+
self.class.validate!(self) if validate
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
# Create a Flag from a hash (backward compatibility)
|
|
31
|
+
#
|
|
32
|
+
# @param hash [Hash, Flag, nil] the hash or Flag instance
|
|
33
|
+
# @param validate [Boolean] whether to validate the flag (default: false for backward compatibility)
|
|
34
|
+
# @return [Flag, nil] a new Flag instance or nil if input is nil
|
|
35
|
+
def from_hash(hash, validate: false)
|
|
36
|
+
return nil if hash.nil?
|
|
37
|
+
return hash if hash.is_a?(Flag)
|
|
38
|
+
|
|
39
|
+
new(
|
|
40
|
+
quality_flag: hash[:quality_flag],
|
|
41
|
+
method_flag: hash[:method_flag],
|
|
42
|
+
reason_code: hash[:reason_code],
|
|
43
|
+
validate: validate
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Create a Flag from a quality method string and reason code
|
|
48
|
+
#
|
|
49
|
+
# @param quality_method [String] the quality method string (e.g., 'A', 'E52', 'F11')
|
|
50
|
+
# @param reason_code [String] the reason code ('0'-'99' or '')
|
|
51
|
+
# @param validate [Boolean] whether to validate the flag (default: false for backward compatibility)
|
|
52
|
+
# @return [Flag] a new Flag instance
|
|
53
|
+
def from_quality_method_reason_code(quality_method:, reason_code:, validate: false)
|
|
54
|
+
quality_flag = quality_method[0]
|
|
55
|
+
method_flag = quality_method[1, 2].to_i if quality_method.length == 3
|
|
56
|
+
new(
|
|
57
|
+
quality_flag:,
|
|
58
|
+
method_flag:,
|
|
59
|
+
reason_code: reason_code.blank? ? nil : reason_code.to_i,
|
|
60
|
+
validate:
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Normalize a flag (apply NEM12 specification rules)
|
|
65
|
+
#
|
|
66
|
+
# @param flag_or_hash [Flag, Hash, nil] the flag to normalize
|
|
67
|
+
# @return [Flag] normalized flag instance
|
|
68
|
+
#
|
|
69
|
+
# Normalization rules:
|
|
70
|
+
# - nil flags become 'A' (Actual Data)
|
|
71
|
+
# - 'A', 'N' flags: Remove method_flag if present
|
|
72
|
+
# - 'V' flags: Remove both method_flag and reason_code if present
|
|
73
|
+
# - Other flags: Preserve as-is
|
|
74
|
+
def normalize(flag_or_hash)
|
|
75
|
+
# Handle nil input
|
|
76
|
+
return new(quality_flag: 'A', validate: false) if flag_or_hash.nil?
|
|
77
|
+
|
|
78
|
+
# Convert to Flag if needed
|
|
79
|
+
flag = flag_or_hash.is_a?(Flag) ? flag_or_hash : from_hash(flag_or_hash)
|
|
80
|
+
|
|
81
|
+
normalized_quality = flag.quality_flag || 'A'
|
|
82
|
+
normalized_method = flag.method_flag
|
|
83
|
+
normalized_reason = flag.reason_code
|
|
84
|
+
|
|
85
|
+
# Apply normalization rules based on quality flag
|
|
86
|
+
case normalized_quality
|
|
87
|
+
when 'A', 'N'
|
|
88
|
+
# A and N: Remove method_flag (reason_code is optional)
|
|
89
|
+
normalized_method = nil
|
|
90
|
+
when 'V'
|
|
91
|
+
# V: Remove both method_flag and reason_code
|
|
92
|
+
normalized_method = nil
|
|
93
|
+
normalized_reason = nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
new(
|
|
97
|
+
quality_flag: normalized_quality,
|
|
98
|
+
method_flag: normalized_method,
|
|
99
|
+
reason_code: normalized_reason,
|
|
100
|
+
validate: false
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Validate a flag and raise an error if invalid
|
|
105
|
+
#
|
|
106
|
+
# @param flag_or_hash [Flag, Hash, nil] the flag to validate
|
|
107
|
+
# @return [void]
|
|
108
|
+
# @raise [ArgumentError] if flag combination is invalid
|
|
109
|
+
#
|
|
110
|
+
# NEM12 Specification Requirements:
|
|
111
|
+
# - 'A' (Actual Data): No method flag, optional reason code
|
|
112
|
+
# - 'N' (Null Data): No method flag, optional reason code *DEPRECATED*
|
|
113
|
+
# - 'V' (Variable Data): No method flag, no reason code
|
|
114
|
+
# - 'E' (Forward Estimated Data): Requires method flag, optional reason code
|
|
115
|
+
# - 'F' (Final Substituted Data): Requires method flag AND reason code
|
|
116
|
+
# - 'S' (Substituted Data): Requires method flag AND reason code
|
|
117
|
+
def validate!(flag_or_hash)
|
|
118
|
+
return if flag_or_hash.nil?
|
|
119
|
+
|
|
120
|
+
flag = from_hash(flag_or_hash)
|
|
121
|
+
|
|
122
|
+
quality_flag = flag.quality_flag
|
|
123
|
+
method_flag = flag.method_flag
|
|
124
|
+
reason_code = flag.reason_code
|
|
125
|
+
|
|
126
|
+
# Validate individual components
|
|
127
|
+
unless valid_quality_flag?(quality_flag)
|
|
128
|
+
raise ArgumentError,
|
|
129
|
+
"Invalid quality flag: #{quality_flag.inspect}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
unless valid_method_flag?(method_flag)
|
|
133
|
+
raise ArgumentError,
|
|
134
|
+
"Invalid method flag: #{method_flag.inspect}"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
unless valid_reason_code?(reason_code)
|
|
138
|
+
raise ArgumentError,
|
|
139
|
+
"Invalid reason code: #{reason_code.inspect}"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Validate flag combinations according to NEM12 specification
|
|
143
|
+
case quality_flag
|
|
144
|
+
when 'A', 'N'
|
|
145
|
+
# A and N: Should not have method flags, may have reason codes
|
|
146
|
+
unless method_flag.nil?
|
|
147
|
+
raise ArgumentError,
|
|
148
|
+
"Quality flag '#{quality_flag}' should not have a method flag"
|
|
149
|
+
end
|
|
150
|
+
when 'V'
|
|
151
|
+
# V: Should not have method flags or reason codes
|
|
152
|
+
unless method_flag.nil?
|
|
153
|
+
raise ArgumentError,
|
|
154
|
+
"Quality flag 'V' should not have a method flag"
|
|
155
|
+
end
|
|
156
|
+
unless reason_code.nil?
|
|
157
|
+
raise ArgumentError,
|
|
158
|
+
"Quality flag 'V' should not have a reason code"
|
|
159
|
+
end
|
|
160
|
+
when 'E'
|
|
161
|
+
# E: Requires method flag, may have reason code (optional)
|
|
162
|
+
if method_flag.nil?
|
|
163
|
+
raise ArgumentError,
|
|
164
|
+
"Quality flag 'E' requires a method flag"
|
|
165
|
+
end
|
|
166
|
+
when 'F', 'S'
|
|
167
|
+
# F and S: Require both method flag and reason code
|
|
168
|
+
if method_flag.nil?
|
|
169
|
+
raise ArgumentError,
|
|
170
|
+
"Quality flag '#{quality_flag}' requires a method flag"
|
|
171
|
+
end
|
|
172
|
+
if reason_code.nil?
|
|
173
|
+
raise ArgumentError,
|
|
174
|
+
"Quality flag '#{quality_flag}' requires a reason code"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Validate a quality flag value
|
|
180
|
+
#
|
|
181
|
+
# @param quality_flag [String, nil] the quality flag to validate
|
|
182
|
+
# @return [Boolean] true if valid
|
|
183
|
+
def valid_quality_flag?(quality_flag)
|
|
184
|
+
quality_flag.nil? || QUALITY_FLAGS.key?(quality_flag)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Validate a method flag value
|
|
188
|
+
#
|
|
189
|
+
# @param method_flag [Integer, nil] the method flag to validate
|
|
190
|
+
# @return [Boolean] true if valid
|
|
191
|
+
def valid_method_flag?(method_flag)
|
|
192
|
+
method_flag.nil? || METHOD_FLAGS.key?(method_flag)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Validate a reason code value
|
|
196
|
+
#
|
|
197
|
+
# @param reason_code [Integer, nil] the reason code to validate
|
|
198
|
+
# @return [Boolean] true if valid
|
|
199
|
+
def valid_reason_code?(reason_code)
|
|
200
|
+
reason_code.nil? || REASON_CODES.key?(reason_code)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Convert flag to quality method string
|
|
205
|
+
#
|
|
206
|
+
# @return [String] the quality method string (e.g., 'A', 'E52', 'F11')
|
|
207
|
+
def to_quality_method
|
|
208
|
+
return 'A' if quality_flag.nil?
|
|
209
|
+
|
|
210
|
+
qf = quality_flag || 'A'
|
|
211
|
+
mf = method_flag
|
|
212
|
+
|
|
213
|
+
# Build quality method string (quality flag + optional method flag)
|
|
214
|
+
if mf.nil?
|
|
215
|
+
qf
|
|
216
|
+
else
|
|
217
|
+
"#{qf}#{format('%02d', mf)}"
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Convert flag to human-readable string
|
|
222
|
+
#
|
|
223
|
+
# @return [String, nil] a hyphenated string for the flag or nil
|
|
224
|
+
def to_s
|
|
225
|
+
parts = []
|
|
226
|
+
parts << QUALITY_FLAGS[quality_flag] unless QUALITY_FLAGS[quality_flag].nil?
|
|
227
|
+
parts << METHOD_FLAGS[method_flag][:short_descriptor] unless METHOD_FLAGS[method_flag].nil?
|
|
228
|
+
parts << REASON_CODES[reason_code] unless REASON_CODES[reason_code].nil?
|
|
229
|
+
parts.empty? ? nil : parts.join(' - ')
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Convert flag to hash (backward compatibility)
|
|
233
|
+
#
|
|
234
|
+
# @return [Hash] hash representation of the flag
|
|
235
|
+
def to_h
|
|
236
|
+
{
|
|
237
|
+
quality_flag: @quality_flag,
|
|
238
|
+
method_flag: @method_flag,
|
|
239
|
+
reason_code: @reason_code
|
|
240
|
+
}
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Equality comparison
|
|
244
|
+
#
|
|
245
|
+
# @param other [Flag, Hash] the other flag to compare
|
|
246
|
+
# @return [Boolean] true if flags are equal
|
|
247
|
+
def ==(other)
|
|
248
|
+
if other.is_a?(Flag)
|
|
249
|
+
quality_flag == other.quality_flag &&
|
|
250
|
+
method_flag == other.method_flag &&
|
|
251
|
+
reason_code == other.reason_code
|
|
252
|
+
elsif other.is_a?(Hash)
|
|
253
|
+
quality_flag == other[:quality_flag] &&
|
|
254
|
+
method_flag == other[:method_flag] &&
|
|
255
|
+
reason_code == other[:reason_code]
|
|
256
|
+
else
|
|
257
|
+
false
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Hash code for use in hashes and sets
|
|
262
|
+
#
|
|
263
|
+
# @return [Integer] hash code
|
|
264
|
+
def hash
|
|
265
|
+
[quality_flag, method_flag, reason_code].hash
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Validate the flag and raise an error if invalid
|
|
269
|
+
#
|
|
270
|
+
# @return [void]
|
|
271
|
+
# @raise [ArgumentError] if flag combination is invalid
|
|
272
|
+
def validate!
|
|
273
|
+
self.class.validate!(self)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Equality for hash keys
|
|
277
|
+
alias eql? ==
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
@@ -1,65 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class NEM12
|
|
7
|
-
QUALITY_FLAGS = {
|
|
8
|
-
'A' => 'Actual Data',
|
|
9
|
-
'E' => 'Forward Estimated Data',
|
|
10
|
-
'F' => 'Final Substituted Data',
|
|
11
|
-
'N' => 'Null Data',
|
|
12
|
-
'S' => 'Substituted Data',
|
|
13
|
-
'V' => 'Variable Data'
|
|
14
|
-
}.freeze
|
|
15
|
-
|
|
16
|
-
METHOD_FLAGS = {
|
|
17
|
-
11 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Check', description: '' },
|
|
18
|
-
12 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Calculated', description: '' },
|
|
19
|
-
13 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'SCADA', description: '' },
|
|
20
|
-
14 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Retrospective Like Day', description: 'Updated v7.8' },
|
|
21
|
-
15 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Retrospective Average Like Day', description: 'Updated v7.8' },
|
|
22
|
-
16 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Agreed', description: '[OBSOLETE] v7.8' },
|
|
23
|
-
17 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Linear', description: '' },
|
|
24
|
-
18 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Alternate', description: '' },
|
|
25
|
-
19 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Zero', description: '' },
|
|
26
|
-
20 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Prospective Like Day',
|
|
27
|
-
description: 'Updated v7.8' },
|
|
28
|
-
21 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Five-minute No Historical Data',
|
|
29
|
-
description: 'Added v7.8' },
|
|
30
|
-
22 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Prospective Ave Like Day',
|
|
31
|
-
description: 'Added v7.8' },
|
|
32
|
-
23 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Previous Year',
|
|
33
|
-
description: 'Added v7.8' },
|
|
34
|
-
24 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Data Scaling',
|
|
35
|
-
description: 'Added v7.8' },
|
|
36
|
-
25 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'ADL',
|
|
37
|
-
description: 'Added v7.8' },
|
|
38
|
-
51 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Previous Year', description: '' },
|
|
39
|
-
52 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Previous Read', description: '' },
|
|
40
|
-
53 => { type: %w[SUB], installation_type: 5, short_descriptor: 'Revision', description: '' },
|
|
41
|
-
54 => { type: %w[SUB], installation_type: 5, short_descriptor: 'Linear', description: '' },
|
|
42
|
-
55 => { type: %w[SUB], installation_type: 5, short_descriptor: 'Agreed', description: '' },
|
|
43
|
-
56 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Prior to First Read - Agreed',
|
|
44
|
-
description: '' },
|
|
45
|
-
57 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Customer Class', description: '' },
|
|
46
|
-
58 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Zero', description: '' },
|
|
47
|
-
59 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Five-minute No Historical Data',
|
|
48
|
-
description: '' },
|
|
49
|
-
61 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Previous Year', description: '' },
|
|
50
|
-
62 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Previous Read', description: '' },
|
|
51
|
-
63 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Customer Class', description: '' },
|
|
52
|
-
64 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Agreed', description: '' },
|
|
53
|
-
65 => { type: %w[EST], installation_type: 6, short_descriptor: 'ADL', description: '' },
|
|
54
|
-
66 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Revision', description: '' },
|
|
55
|
-
67 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Customer Read', description: '' },
|
|
56
|
-
68 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Zero', description: '' },
|
|
57
|
-
69 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Linear extrapolation', description: '' },
|
|
58
|
-
71 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Recalculation', description: '' },
|
|
59
|
-
72 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Revised Table', description: '' },
|
|
60
|
-
73 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Revised Algorithm', description: '' },
|
|
61
|
-
74 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Agreed', description: '' },
|
|
62
|
-
75 => { type: %w[EST], installation_type: 7, short_descriptor: 'Existing Table', description: '' }
|
|
63
|
-
}.freeze
|
|
64
|
-
end
|
|
65
|
-
end
|
|
3
|
+
# This file is kept for backward compatibility
|
|
4
|
+
# Constants have been moved to lib/aemo/nem12/meter_data/flag/
|
|
5
|
+
# and are now accessible via AEMO::NEM12::QUALITY_FLAGS and AEMO::NEM12::METHOD_FLAGS
|
|
@@ -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
|