class AEMO::NMI
AEMO::NMI
-
AEMO::NMI
acts as an object to simplify access to data and informationabout a NMI and provide verification of the NMI value
@author Joel Courtney @abstract Model for a National Metering Identifier. @since 2014-12-05
Constants
- DLF_CODES
Distribution Loss Factor Codes are loaded from a json file
Obtained from MSATS, matching to DNSP from file
www.aemo.com.au/-/media/Files/Electricity/NEM/
Security_and_Reliability/Loss_Factors_and_Regional_Boundaries/ 2016/DLF_V3_2016_2017.pdf Last accessed 2017-08-01 See /lib/data for further data manipulation required
- REGIONS
Operational Regions for the
NMI
- TNI_CODES
Transmission Node Identifier Codes are loaded from a json file
Obtained from http://www.nemweb.com.au/ See /lib/data for further data manipulation required
Attributes
Public Class Methods
Find the Network for a given NMI
@param [String] nmi NMI
@returns [AEMO::NMI::Allocation] The Network information
# File lib/aemo/nmi.rb, line 99 def network(nmi) AEMO::NMI::Allocation.find_by_nmi(nmi) end
Initialize a NMI
file
@param [String] nmi the National Meter
Identifier (NMI
) @param [Hash] options a hash of options @option options [Hash] :msats_detail MSATS
details as per
#parse_msats_detail requirements
@return [AEMO::NMI] an instance of AEMO::NMI
is returned
# File lib/aemo/nmi.rb, line 113 def initialize(nmi, options = {}) raise ArgumentError, 'NMI is not a string' unless nmi.is_a?(String) raise ArgumentError, 'NMI is not 10 characters' unless nmi.length == 10 raise ArgumentError, 'NMI is not constructed with valid characters' unless AEMO::NMI.valid_nmi?(nmi) @nmi = nmi @meters = [] @roles = {} @data_streams = [] @msats_detail = options[:msats_detail] parse_msats_detail unless @msats_detail.nil? end
A function to calculate the checksum value for a given National Meter
Identifier
@param [String] nmi the NMI
to check the checksum against @param [Integer] checksum_value the checksum value to check against the
current National Meter Identifier's checksum value
@return [Boolean] whether or not the checksum is valid
# File lib/aemo/nmi.rb, line 90 def valid_checksum?(nmi, checksum_value) nmi = AEMO::NMI.new(nmi) nmi.valid_checksum?(checksum_value) end
A function to validate the NMI
provided
@param [String] nmi the nmi to be checked @return [Boolean] whether or not the nmi is valid
# File lib/aemo/nmi.rb, line 79 def valid_nmi?(nmi) ((nmi.length == 10) && !nmi.match(/^([A-HJ-NP-Z\d]{10})/).nil?) end
Public Instance Methods
Checksum is a function to calculate the checksum value for a given National Meter
Identifier
@return [Integer] the checksum value for the current National Meter
Identifier
# File lib/aemo/nmi.rb, line 158 def checksum summation = 0 @nmi.reverse.split(//).each_index do |i| value = nmi[nmi.length - i - 1].ord value *= 2 if i.even? value = value.to_s.split(//).map(&:to_i).reduce(:+) summation += value end checksum = (10 - (summation % 10)) % 10 checksum end
The current annual load in MWh
@todo Use TimeDifference for more accurate annualised load @return [Integer] the current annual load for the meter in MWh
# File lib/aemo/nmi.rb, line 292 def current_annual_load (current_daily_load * 365.2425 / 1000).to_i end
The current daily load in kWh
@return [Integer] the current daily load for the meter in kWh
# File lib/aemo/nmi.rb, line 283 def current_daily_load data_streams_by_status.map { |x| x.averaged_daily_load.to_i } .inject(0, :+) end
Returns the data_stream OpenStructs for the requested status (A/I)
@param [String] status the stateus [A|I] @return [Array<OpenStruct>] Returns an array of OpenStructs for the
current Meters
# File lib/aemo/nmi.rb, line 276 def data_streams_by_status(status = 'A') @data_streams.select { |x| x.status == status.to_s } end
A function to return the distribution loss factor value for a given date
@param [DateTime, Time] datetime the date for the distribution loss factor
value
@return [nil, float] the distribution loss factor value
# File lib/aemo/nmi.rb, line 301 def dlfc_value(datetime = Time.now) if @dlf.nil? raise 'No DLF set, ensure that you have set the value either via the' \ 'update_from_msats! function or manually' end raise 'DLF is invalid' unless DLF_CODES.keys.include?(@dlf) raise 'Invalid date' unless [DateTime, Time].include?(datetime.class) possible_values = DLF_CODES[@dlf].select do |x| Time.parse(x['FromDate']) <= datetime && Time.parse(x['ToDate']) >= datetime end if possible_values.empty? nil else possible_values.first['Value'].to_f end end
A function to return the distribution loss factor value for a given date
@param [DateTime, Time] start the date for the distribution loss factor value @param [DateTime, Time] finish the date for the distribution loss factor value @return [Array(Hash)] array of hashes of start, finish and value
# File lib/aemo/nmi.rb, line 324 def dlfc_values(start = Time.now, finish = Time.now) if @dlf.nil? raise 'No DLF set, ensure that you have set the value either via the '\ 'update_from_msats! function or manually' end raise 'DLF is invalid' unless DLF_CODES.keys.include?(@dlf) raise 'Invalid start' unless [DateTime, Time].include?(start.class) raise 'Invalid finish' unless [DateTime, Time].include?(finish.class) raise 'start cannot be after finish' if start > finish DLF_CODES[@dlf].reject { |x| start > Time.parse(x['ToDate']) || finish < Time.parse(x['FromDate']) } .map { |x| { 'start' => x['FromDate'], 'finish' => x['ToDate'], 'value' => x['Value'].to_f } } end
Returns a nice address from the structured one AEMO
sends us
@return [String]
# File lib/aemo/nmi.rb, line 249 def friendly_address friendly_address = '' if @address.is_a?(Hash) friendly_address = @address.values.map do |x| if x.is_a?(Hash) x = x.values.map { |y| y.is_a?(Hash) ? y.values.join(' ') : y }.join(' ') end x end.join(', ') end friendly_address end
Returns the meters for the requested status (C/R)
@param [String] status the stateus [C|R] @return [Array<AEMO::Meter>] Returns an array of AEMO::Meters with the
status provided
# File lib/aemo/nmi.rb, line 267 def meters_by_status(status = 'C') @meters.select { |x| x.status == status.to_s } end
Find the Network of NMI
@returns [Hash] The Network information
# File lib/aemo/nmi.rb, line 137 def network AEMO::NMI.network(@nmi) end
Turns raw MSATS
junk into useful things
@return [self] returns self
# File lib/aemo/nmi.rb, line 192 def parse_msats_detail # Set the details if there are any unless @msats_detail['MasterData'].nil? @tni = @msats_detail['MasterData']['TransmissionNodeIdentifier'] @dlf = @msats_detail['MasterData']['DistributionLossFactorCode'] @customer_classification_code = @msats_detail['MasterData']['CustomerClassificationCode'] @customer_threshold_code = @msats_detail['MasterData']['CustomerThresholdCode'] @jurisdiction_code = @msats_detail['MasterData']['JurisdictionCode'] @classification_code = @msats_detail['MasterData']['NMIClassificationCode'] @status = @msats_detail['MasterData']['Status'] @address = @msats_detail['MasterData']['Address'] end @meters ||= [] @roles ||= {} @data_streams ||= [] # Meters unless @msats_detail['MeterRegister'].nil? meters = @msats_detail['MeterRegister']['Meter'] meters = [meters] if meters.is_a?(Hash) meters.reject { |x| x['Status'].nil? }.each do |meter| @meters << AEMO::Meter.from_hash(meter) end meters.select { |x| x['Status'].nil? }.each do |registers| m = @meters.find { |x| x.serial_number == registers['SerialNumber'] } m.registers << AEMO::Register.from_hash( registers['RegisterConfiguration']['Register'] ) end end # Roles unless @msats_detail['RoleAssignments'].nil? role_assignments = @msats_detail['RoleAssignments']['RoleAssignment'] role_assignments = [role_assignments] if role_assignments.is_a?(Hash) role_assignments.each do |role| @roles[role['Role']] = role['Party'] end end # DataStreams unless @msats_detail['DataStreams'].nil? data_streams = @msats_detail['DataStreams']['DataStream'] data_streams = [data_streams] if data_streams.is_a?(Hash) # Deal with issue of only one existing data_streams.each do |stream| @data_streams << OpenStruct.new( suffix: stream['Suffix'], profile_name: stream['ProfileName'], averaged_daily_load: stream['AveragedDailyLoad'], data_stream_type: stream['DataStreamType'], status: stream['Status'] ) end end self end
Provided MSATS
is configured, gets the MSATS
data for the NMI
@return [Hash] MSATS
NMI
Detail data
# File lib/aemo/nmi.rb, line 173 def raw_msats_nmi_detail(options = {}) raise ArgumentError, 'MSATS has no authentication credentials' unless AEMO::MSATS.can_authenticate? AEMO::MSATS.nmi_detail(@nmi, options) end
A function to return the transmission node identifier loss factor value for a given date
@param [DateTime, Time] datetime the date for the distribution loss factor value @return [nil, float] the transmission node identifier loss factor value
# File lib/aemo/nmi.rb, line 341 def tni_value(datetime = Time.now) if @tni.nil? raise 'No TNI set, ensure that you have set the value either via the '\ 'update_from_msats! function or manually' end raise 'TNI is invalid' unless TNI_CODES.keys.include?(@tni) raise 'Invalid date' unless [DateTime, Time].include?(datetime.class) possible_values = TNI_CODES[@tni].select { |x| Time.parse(x['FromDate']) <= datetime && datetime <= Time.parse(x['ToDate']) } return nil if possible_values.empty? possible_values = possible_values.first['mlf_data']['loss_factors'].select { |x| Time.parse(x['start']) <= datetime && datetime <= Time.parse(x['finish']) } return nil if possible_values.empty? possible_values.first['value'].to_f end
A function to return the transmission node identifier loss factor value for a given date
@param [DateTime, Time] start the date for the distribution loss factor value @param [DateTime, Time] finish the date for the distribution loss factor value @return [Array(Hash)] array of hashes of start, finish and value
# File lib/aemo/nmi.rb, line 360 def tni_values(start = Time.now, finish = Time.now) if @tni.nil? raise 'No TNI set, ensure that you have set the value either via the '\ 'update_from_msats! function or manually' end raise 'TNI is invalid' unless TNI_CODES.keys.include?(@tni) raise 'Invalid start' unless [DateTime, Time].include?(start.class) raise 'Invalid finish' unless [DateTime, Time].include?(finish.class) raise 'start cannot be after finish' if start > finish possible_values = TNI_CODES[@tni].reject do |tni_code| start > Time.parse(tni_code['ToDate']) || finish < Time.parse(tni_code['FromDate']) end return nil if possible_values.empty? possible_values.map { |x| x['mlf_data']['loss_factors'] } end
A function to calculate the checksum value for a given National Meter
Identifier
@param [Integer] checksum_value the checksum value to check against the
current National Meter Identifier's checksum value
@return [Boolean] whether or not the checksum is valid
# File lib/aemo/nmi.rb, line 149 def valid_checksum?(checksum_value) checksum_value == checksum end
A function to validate the instance's nmi value
@return [Boolean] whether or not the nmi is valid
# File lib/aemo/nmi.rb, line 130 def valid_nmi? AEMO::NMI.valid_nmi?(@nmi) end