Class: AEMO::MeterData::Flag

Inherits:
Object
  • Object
show all
Defined in:
lib/aemo/meter_data/flag.rb,
lib/aemo/meter_data/flag/method.rb,
lib/aemo/meter_data/flag/quality.rb,
lib/aemo/meter_data/flag/reason_code.rb

Overview

Represents a NEM12/NEM13 quality flag with validation and conversion capabilities

Since:

  • 0.2.0

Constant Summary collapse

METHOD_FLAGS =

Method flags for NEM12/NEM13 meter data

Since:

  • 0.1.4

{
  11 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Check', description: '' },
  12 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Calculated', description: '' },
  13 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'SCADA', description: '' },
  14 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Retrospective Like Day', description: 'Updated v7.8' },
  15 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Retrospective Average Like Day', description: 'Updated v7.8' },
  16 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Agreed', description: '[OBSOLETE] v7.8' },
  17 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Linear', description: '' },
  18 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Alternate', description: '' },
  19 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Zero', description: '' },
  20 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Prospective Like Day',
          description: 'Updated v7.8' },
  21 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Five-minute No Historical Data',
          description: 'Added v7.8' },
  22 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Prospective Ave Like Day',
          description: 'Added v7.8' },
  23 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Previous Year',
          description: 'Added v7.8' },
  24 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'Data Scaling',
          description: 'Added v7.8' },
  25 => { type: %w[SUB], installation_type: [1, 2, 3, 4], short_descriptor: 'ADL',
          description: 'Added v7.8' },
  51 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Previous Year', description: '' },
  52 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Previous Read', description: '' },
  53 => { type: %w[SUB], installation_type: 5, short_descriptor: 'Revision', description: '' },
  54 => { type: %w[SUB], installation_type: 5, short_descriptor: 'Linear', description: '' },
  55 => { type: %w[SUB], installation_type: 5, short_descriptor: 'Agreed', description: '' },
  56 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Prior to First Read - Agreed',
          description: '' },
  57 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Customer Class', description: '' },
  58 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Zero', description: '' },
  59 => { type: %w[EST SUB], installation_type: 5, short_descriptor: 'Five-minute No Historical Data',
          description: '' },
  61 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Previous Year', description: '' },
  62 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Previous Read', description: '' },
  63 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Customer Class', description: '' },
  64 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Agreed', description: '' },
  65 => { type: %w[EST], installation_type: 6, short_descriptor: 'ADL', description: '' },
  66 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Revision', description: '' },
  67 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Customer Read', description: '' },
  68 => { type: %w[EST SUB], installation_type: 6, short_descriptor: 'Zero', description: '' },
  69 => { type: %w[SUB], installation_type: 6, short_descriptor: 'Linear extrapolation', description: '' },
  71 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Recalculation', description: '' },
  72 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Revised Table', description: '' },
  73 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Revised Algorithm', description: '' },
  74 => { type: %w[SUB], installation_type: 7, short_descriptor: 'Agreed', description: '' },
  75 => { type: %w[EST], installation_type: 7, short_descriptor: 'Existing Table', description: '' }
}.freeze
QUALITY_FLAGS =

Quality flags for NEM12/NEM13 meter data

Since:

  • 0.1.4

{
  'A' => 'Actual Data',
  'E' => 'Forward Estimated Data',
  'F' => 'Final Substituted Data',
  'N' => 'Null Data',
  'S' => 'Substituted Data',
  'V' => 'Variable Data'
}.freeze
REASON_CODES =

Reason codes for NEM12/NEM13 meter data

Since:

  • 0.1.4

{
  0 => 'Free Text Description',
  1 => 'Meter/Equipment Changed',
  2 => 'Extreme Weather/Wet',
  3 => 'Quarantine',
  4 => 'Savage Dog',
  5 => 'Meter/Equipment Changed',
  6 => 'Extreme Weather/Wet',
  7 => 'Unable To Locate Meter',
  8 => 'Vacant Premise',
  9 => 'Meter/Equipment Changed',
  10 => 'Lock Damaged/Seized',
  11 => 'In Wrong Walk',
  12 => 'Locked Premises',
  13 => 'Locked Gate',
  14 => 'Locked Meter Box',
  15 => 'Access - Overgrown',
  16 => 'Noxious Weeds',
  17 => 'Unsafe Equipment/Location',
  18 => 'Read Below Previous',
  19 => 'Consumer Wanted',
  20 => 'Damaged Equipment/Panel',
  21 => 'Switched Off',
  22 => 'Meter/Equipment Seals Missing',
  23 => 'Meter/Equipment Seals Missing',
  24 => 'Meter/Equipment Seals Missing',
  25 => 'Meter/Equipment Seals Missing',
  26 => 'Meter/Equipment Seals Missing',
  27 => 'Meter/Equipment Seals Missing',
  28 => 'Damaged Equipment/Panel',
  29 => 'Relay Faulty/Damaged',
  30 => 'Meter Stop Switch On',
  31 => 'Meter/Equipment Seals Missing',
  32 => 'Damaged Equipment/Panel',
  33 => 'Relay Faulty/Damaged',
  34 => 'Meter Not In Handheld',
  35 => 'Timeswitch Faulty/Reset Required',
  36 => 'Meter High/Ladder Required',
  37 => 'Meter High/Ladder Required',
  38 => 'Unsafe Equipment/Location',
  39 => 'Reverse Energy Observed',
  40 => 'Timeswitch Faulty/Reset Required',
  41 => 'Faulty Equipment Display/Dials',
  42 => 'Faulty Equipment Display/Dials',
  43 => 'Power Outage',
  44 => 'Unsafe Equipment/Location',
  45 => 'Readings Failed To Validate',
  46 => 'Extreme Weather/Hot',
  47 => 'Refused Access',
  48 => 'Timeswitch Faulty/Reset Required',
  49 => 'Wet Paint',
  50 => 'Wrong Tariff',
  51 => 'Installation Demolished',
  52 => 'Access - Blocked',
  53 => 'Bees/Wasp In Meter Box',
  54 => 'Meter Box Damaged/Faulty',
  55 => 'Faulty Equipment Display/Dials',
  56 => 'Meter Box Damaged/Faulty',
  57 => 'Timeswitch Faulty/Reset Required',
  58 => 'Meter Ok - Supply Failure',
  59 => 'Faulty Equipment Display/Dials',
  60 => 'Illegal Connection/Equipment Tampered',
  61 => 'Meter Box Damaged/Faulty',
  62 => 'Damaged Equipment/Panel',
  63 => 'Illegal Connection/Equipment Tampered',
  64 => 'Key Required',
  65 => 'Wrong Key Provided',
  66 => 'Lock Damaged/Seized',
  67 => 'Extreme Weather/Wet',
  68 => 'Zero Consumption',
  69 => 'Reading Exceeds Estimate',
  70 => 'Probe Reports Tampering',
  71 => 'Probe Read Error',
  72 => 'Meter/Equipment Changed',
  73 => 'Low Consumption',
  74 => 'High Consumption',
  75 => 'Customer Read',
  76 => 'Communications Fault',
  77 => 'Estimation Forecast',
  78 => 'Null Data',
  79 => 'Power Outage Alarm',
  80 => 'Short Interval Alarm',
  81 => 'Long Interval Alarm',
  82 => 'CRC Error',
  83 => 'RAM Checksum Error',
  84 => 'ROM Checksum Error',
  85 => 'Data Missing Alarm',
  86 => 'Clock Error Alarm',
  87 => 'Reset Occurred',
  88 => 'Watchdog Timeout Alarm',
  89 => 'Time Reset Occurred',
  90 => 'Test Mode',
  91 => 'Load Control',
  92 => 'Added Interval (Data Correction)',
  93 => 'Replaced Interval (Data Correction)',
  94 => 'Estimated Interval (Data Correction)',
  95 => 'Pulse Overflow Alarm',
  96 => 'Data Out Of Limits',
  97 => 'Excluded Data',
  98 => 'Parity Error',
  99 => 'Energy Type (Register Changed)'
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(quality_flag: nil, method_flag: nil, reason_code: nil, validate: true) ⇒ Flag

Initialize a new Flag instance

Parameters:

  • quality_flag (String, nil) (defaults to: nil)

    the quality flag (‘A’, ‘E’, ‘F’, ‘N’, ‘S’, ‘V’)

  • method_flag (Integer, nil) (defaults to: nil)

    the method flag (11-75)

  • reason_code (Integer, nil) (defaults to: nil)

    the reason code (0-99)

  • validate (Boolean) (defaults to: true)

    whether to validate the flag (default: true)

Since:

  • 0.2.0



20
21
22
23
24
25
26
27
# File 'lib/aemo/meter_data/flag.rb', line 20

def initialize(quality_flag: nil, method_flag: nil, reason_code: nil, validate: true)
  @quality_flag = quality_flag
  @method_flag = method_flag
  @reason_code = reason_code

  # Validate the flag if requested
  self.class.validate!(self) if validate
end

Instance Attribute Details

#method_flagObject (readonly)

Since:

  • 0.2.0



12
13
14
# File 'lib/aemo/meter_data/flag.rb', line 12

def method_flag
  @method_flag
end

#quality_flagObject (readonly)

Since:

  • 0.2.0



12
13
14
# File 'lib/aemo/meter_data/flag.rb', line 12

def quality_flag
  @quality_flag
end

#reason_codeObject (readonly)

Since:

  • 0.2.0



12
13
14
# File 'lib/aemo/meter_data/flag.rb', line 12

def reason_code
  @reason_code
end

Class Method Details

.from_hash(hash, validate: false) ⇒ Flag?

Create a Flag from a hash (backward compatibility)

Parameters:

  • hash (Hash, Flag, nil)

    the hash or Flag instance

  • validate (Boolean) (defaults to: false)

    whether to validate the flag (default: false for backward compatibility)

Returns:

  • (Flag, nil)

    a new Flag instance or nil if input is nil

Since:

  • 0.2.0



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/aemo/meter_data/flag.rb', line 35

def from_hash(hash, validate: false)
  return nil if hash.nil?
  return hash if hash.is_a?(Flag)

  new(
    quality_flag: hash[:quality_flag],
    method_flag: hash[:method_flag],
    reason_code: hash[:reason_code],
    validate: validate
  )
end

.from_quality_method_reason_code(quality_method:, reason_code:, validate: false) ⇒ Flag

Create a Flag from a quality method string and reason code

Parameters:

  • quality_method (String)

    the quality method string (e.g., ‘A’, ‘E52’, ‘F11’)

  • reason_code (String)

    the reason code (‘0’-‘99’ or ”)

  • validate (Boolean) (defaults to: false)

    whether to validate the flag (default: false for backward compatibility)

Returns:

  • (Flag)

    a new Flag instance

Since:

  • 0.2.0



53
54
55
56
57
58
59
60
61
62
# File 'lib/aemo/meter_data/flag.rb', line 53

def from_quality_method_reason_code(quality_method:, reason_code:, validate: false)
  quality_flag = quality_method[0]
  method_flag = quality_method[1, 2].to_i if quality_method.length == 3
  new(
    quality_flag:,
    method_flag:,
    reason_code: reason_code.blank? ? nil : reason_code.to_i,
    validate:
  )
end

.normalize(flag_or_hash) ⇒ Flag

Normalize a flag (apply NEM12 specification rules)

Normalization rules:

  • nil flags become ‘A’ (Actual Data)

  • ‘A’, ‘N’ flags: Remove method_flag if present

  • ‘V’ flags: Remove both method_flag and reason_code if present

  • Other flags: Preserve as-is

Parameters:

  • flag_or_hash (Flag, Hash, nil)

    the flag to normalize

Returns:

  • (Flag)

    normalized flag instance

Since:

  • 0.2.0



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/aemo/meter_data/flag.rb', line 74

def normalize(flag_or_hash)
  # Handle nil input
  return new(quality_flag: 'A', validate: false) if flag_or_hash.nil?

  # Convert to Flag if needed
  flag = flag_or_hash.is_a?(Flag) ? flag_or_hash : from_hash(flag_or_hash)

  normalized_quality = flag.quality_flag || 'A'
  normalized_method = flag.method_flag
  normalized_reason = flag.reason_code

  # Apply normalization rules based on quality flag
  case normalized_quality
  when 'A', 'N'
    # A and N: Remove method_flag (reason_code is optional)
    normalized_method = nil
  when 'V'
    # V: Remove both method_flag and reason_code
    normalized_method = nil
    normalized_reason = nil
  end

  new(
    quality_flag: normalized_quality,
    method_flag: normalized_method,
    reason_code: normalized_reason,
    validate: false
  )
end

.valid_method_flag?(method_flag) ⇒ Boolean

Validate a method flag value

Parameters:

  • method_flag (Integer, nil)

    the method flag to validate

Returns:

  • (Boolean)

    true if valid

Since:

  • 0.2.0



191
192
193
# File 'lib/aemo/meter_data/flag.rb', line 191

def valid_method_flag?(method_flag)
  method_flag.nil? || METHOD_FLAGS.key?(method_flag)
end

.valid_quality_flag?(quality_flag) ⇒ Boolean

Validate a quality flag value

Parameters:

  • quality_flag (String, nil)

    the quality flag to validate

Returns:

  • (Boolean)

    true if valid

Since:

  • 0.2.0



183
184
185
# File 'lib/aemo/meter_data/flag.rb', line 183

def valid_quality_flag?(quality_flag)
  quality_flag.nil? || QUALITY_FLAGS.key?(quality_flag)
end

.valid_reason_code?(reason_code) ⇒ Boolean

Validate a reason code value

Parameters:

  • reason_code (Integer, nil)

    the reason code to validate

Returns:

  • (Boolean)

    true if valid

Since:

  • 0.2.0



199
200
201
# File 'lib/aemo/meter_data/flag.rb', line 199

def valid_reason_code?(reason_code)
  reason_code.nil? || REASON_CODES.key?(reason_code)
end

.validate!(flag_or_hash) ⇒ void

This method returns an undefined value.

Validate a flag and raise an error if invalid

NEM12 Specification Requirements:

  • ‘A’ (Actual Data): No method flag, optional reason code

  • ‘N’ (Null Data): No method flag, optional reason code DEPRECATED

  • ‘V’ (Variable Data): No method flag, no reason code

  • ‘E’ (Forward Estimated Data): Requires method flag, optional reason code

  • ‘F’ (Final Substituted Data): Requires method flag AND reason code

  • ‘S’ (Substituted Data): Requires method flag AND reason code

Parameters:

  • flag_or_hash (Flag, Hash, nil)

    the flag to validate

Raises:

  • (ArgumentError)

    if flag combination is invalid

Since:

  • 0.2.0



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/aemo/meter_data/flag.rb', line 117

def validate!(flag_or_hash)
  return if flag_or_hash.nil?

  flag = from_hash(flag_or_hash)

  quality_flag = flag.quality_flag
  method_flag = flag.method_flag
  reason_code = flag.reason_code

  # Validate individual components
  unless valid_quality_flag?(quality_flag)
    raise ArgumentError,
          "Invalid quality flag: #{quality_flag.inspect}"
  end

  unless valid_method_flag?(method_flag)
    raise ArgumentError,
          "Invalid method flag: #{method_flag.inspect}"
  end

  unless valid_reason_code?(reason_code)
    raise ArgumentError,
          "Invalid reason code: #{reason_code.inspect}"
  end

  # Validate flag combinations according to NEM12 specification
  case quality_flag
  when 'A', 'N'
    # A and N: Should not have method flags, may have reason codes
    unless method_flag.nil?
      raise ArgumentError,
            "Quality flag '#{quality_flag}' should not have a method flag"
    end
  when 'V'
    # V: Should not have method flags or reason codes
    unless method_flag.nil?
      raise ArgumentError,
            "Quality flag 'V' should not have a method flag"
    end
    unless reason_code.nil?
      raise ArgumentError,
            "Quality flag 'V' should not have a reason code"
    end
  when 'E'
    # E: Requires method flag, may have reason code (optional)
    if method_flag.nil?
      raise ArgumentError,
            "Quality flag 'E' requires a method flag"
    end
  when 'F', 'S'
    # F and S: Require both method flag and reason code
    if method_flag.nil?
      raise ArgumentError,
            "Quality flag '#{quality_flag}' requires a method flag"
    end
    if reason_code.nil?
      raise ArgumentError,
            "Quality flag '#{quality_flag}' requires a reason code"
    end
  end
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Equality comparison

Parameters:

  • other (Flag, Hash)

    the other flag to compare

Returns:

  • (Boolean)

    true if flags are equal

Since:

  • 0.2.0



247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/aemo/meter_data/flag.rb', line 247

def ==(other)
  if other.is_a?(Flag)
    quality_flag == other.quality_flag &&
      method_flag == other.method_flag &&
      reason_code == other.reason_code
  elsif other.is_a?(Hash)
    quality_flag == other[:quality_flag] &&
      method_flag == other[:method_flag] &&
      reason_code == other[:reason_code]
  else
    false
  end
end

#hashInteger

Hash code for use in hashes and sets

Returns:

  • (Integer)

    hash code

Since:

  • 0.2.0



264
265
266
# File 'lib/aemo/meter_data/flag.rb', line 264

def hash
  [quality_flag, method_flag, reason_code].hash
end

#to_hHash

Convert flag to hash (backward compatibility)

Returns:

  • (Hash)

    hash representation of the flag

Since:

  • 0.2.0



235
236
237
238
239
240
241
# File 'lib/aemo/meter_data/flag.rb', line 235

def to_h
  {
    quality_flag: @quality_flag,
    method_flag: @method_flag,
    reason_code: @reason_code
  }
end

#to_quality_methodString

Convert flag to quality method string

Returns:

  • (String)

    the quality method string (e.g., ‘A’, ‘E52’, ‘F11’)

Since:

  • 0.2.0



207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/aemo/meter_data/flag.rb', line 207

def to_quality_method
  return 'A' if quality_flag.nil?

  qf = quality_flag || 'A'
  mf = method_flag

  # Build quality method string (quality flag + optional method flag)
  if mf.nil?
    qf
  else
    "#{qf}#{format('%02d', mf)}"
  end
end

#to_sString?

Convert flag to human-readable string

Returns:

  • (String, nil)

    a hyphenated string for the flag or nil

Since:

  • 0.2.0



224
225
226
227
228
229
230
# File 'lib/aemo/meter_data/flag.rb', line 224

def to_s
  parts = []
  parts << QUALITY_FLAGS[quality_flag] unless QUALITY_FLAGS[quality_flag].nil?
  parts << METHOD_FLAGS[method_flag][:short_descriptor] unless METHOD_FLAGS[method_flag].nil?
  parts << REASON_CODES[reason_code] unless REASON_CODES[reason_code].nil?
  parts.empty? ? nil : parts.join(' - ')
end

#validate!void

This method returns an undefined value.

Validate the flag and raise an error if invalid

Raises:

  • (ArgumentError)

    if flag combination is invalid

Since:

  • 0.2.0



272
273
274
# File 'lib/aemo/meter_data/flag.rb', line 272

def validate!
  self.class.validate!(self)
end