aemo 0.5.0 → 0.6.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/dispatchable.rb +2 -2
- data/lib/aemo/exceptions/invalid_nmi_allocation_type.rb +1 -1
- data/lib/aemo/exceptions/time_error.rb +20 -0
- data/lib/aemo/market/interval.rb +4 -4
- data/lib/aemo/market/node.rb +9 -3
- data/lib/aemo/market.rb +2 -4
- data/lib/aemo/msats.rb +70 -54
- data/lib/aemo/nem12/data_stream_suffix.rb +1 -1
- data/lib/aemo/nem12/quality_method.rb +14 -10
- data/lib/aemo/nem12/unit_of_measurement.rb +42 -42
- data/lib/aemo/nem12.rb +300 -94
- data/lib/aemo/nem13.rb +1 -2
- data/lib/aemo/nmi/allocation.rb +6 -7
- data/lib/aemo/nmi.rb +48 -38
- data/lib/aemo/region.rb +4 -3
- data/lib/aemo/register.rb +7 -7
- data/lib/aemo/struct.rb +3 -0
- data/lib/aemo/time.rb +112 -0
- data/lib/aemo/version.rb +1 -2
- data/lib/aemo.rb +13 -11
- data/lib/data/xml_to_json.rb +2 -4
- data/spec/aemo_spec.rb +0 -7
- data/spec/lib/aemo/market/interval_spec.rb +30 -12
- data/spec/lib/aemo/market/node_spec.rb +11 -9
- data/spec/lib/aemo/market_spec.rb +5 -4
- data/spec/lib/aemo/meter_spec.rb +2 -2
- data/spec/lib/aemo/msats_spec.rb +68 -56
- data/spec/lib/aemo/nem12_spec.rb +154 -43
- data/spec/lib/aemo/nmi/allocation_spec.rb +23 -18
- data/spec/lib/aemo/nmi_spec.rb +98 -72
- data/spec/lib/aemo/region_spec.rb +23 -18
- data/spec/spec_helper.rb +13 -13
- metadata +21 -443
    
        data/lib/aemo/nmi.rb
    CHANGED
    
    | @@ -5,7 +5,7 @@ require 'json' | |
| 5 5 | 
             
            require 'time'
         | 
| 6 6 | 
             
            require 'ostruct'
         | 
| 7 7 |  | 
| 8 | 
            -
            require 'aemo/nmi/allocation | 
| 8 | 
            +
            require 'aemo/nmi/allocation'
         | 
| 9 9 |  | 
| 10 10 | 
             
            module AEMO
         | 
| 11 11 | 
             
              # [AEMO::NMI]
         | 
| @@ -22,11 +22,11 @@ module AEMO | |
| 22 22 | 
             
                  'ACT' => 'Australian Capital Territory',
         | 
| 23 23 | 
             
                  'NSW' => 'New South Wales',
         | 
| 24 24 | 
             
                  'QLD' => 'Queensland',
         | 
| 25 | 
            -
                  'SA' | 
| 25 | 
            +
                  'SA' => 'South Australia',
         | 
| 26 26 | 
             
                  'TAS' => 'Tasmania',
         | 
| 27 27 | 
             
                  'VIC' => 'Victoria',
         | 
| 28 | 
            -
                  'WA' | 
| 29 | 
            -
                  'NT' | 
| 28 | 
            +
                  'WA' => 'Western Australia',
         | 
| 29 | 
            +
                  'NT' => 'Northern Territory'
         | 
| 30 30 | 
             
                }.freeze
         | 
| 31 31 |  | 
| 32 32 | 
             
                # Transmission Node Identifier Codes are loaded from a json file
         | 
| @@ -79,7 +79,7 @@ module AEMO | |
| 79 79 | 
             
                  # @param [String] nmi the nmi to be checked
         | 
| 80 80 | 
             
                  # @return [Boolean] whether or not the nmi is valid
         | 
| 81 81 | 
             
                  def valid_nmi?(nmi)
         | 
| 82 | 
            -
                    ( | 
| 82 | 
            +
                    (nmi.length == 10) && !nmi.match(/^([A-HJ-NP-Z\d]{10})/).nil?
         | 
| 83 83 | 
             
                  end
         | 
| 84 84 |  | 
| 85 85 | 
             
                  # A function to calculate the checksum value for a given National Meter
         | 
| @@ -159,14 +159,13 @@ module AEMO | |
| 159 159 | 
             
                #   Identifier
         | 
| 160 160 | 
             
                def checksum
         | 
| 161 161 | 
             
                  summation = 0
         | 
| 162 | 
            -
                  @nmi.reverse. | 
| 162 | 
            +
                  @nmi.reverse.chars.each_index do |i|
         | 
| 163 163 | 
             
                    value = nmi[nmi.length - i - 1].ord
         | 
| 164 164 | 
             
                    value *= 2 if i.even?
         | 
| 165 | 
            -
                    value = value.to_s. | 
| 165 | 
            +
                    value = value.to_s.chars.map(&:to_i).reduce(:+)
         | 
| 166 166 | 
             
                    summation += value
         | 
| 167 167 | 
             
                  end
         | 
| 168 | 
            -
                   | 
| 169 | 
            -
                  checksum
         | 
| 168 | 
            +
                  (10 - (summation % 10)) % 10
         | 
| 170 169 | 
             
                end
         | 
| 171 170 |  | 
| 172 171 | 
             
                # Provided MSATS is configured, gets the MSATS data for the NMI
         | 
| @@ -174,6 +173,7 @@ module AEMO | |
| 174 173 | 
             
                # @return [Hash] MSATS NMI Detail data
         | 
| 175 174 | 
             
                def raw_msats_nmi_detail(options = {})
         | 
| 176 175 | 
             
                  raise ArgumentError, 'MSATS has no authentication credentials' unless AEMO::MSATS.can_authenticate?
         | 
| 176 | 
            +
             | 
| 177 177 | 
             
                  AEMO::MSATS.nmi_detail(@nmi, options)
         | 
| 178 178 | 
             
                end
         | 
| 179 179 |  | 
| @@ -233,7 +233,7 @@ module AEMO | |
| 233 233 | 
             
                    data_streams = @msats_detail['DataStreams']['DataStream']
         | 
| 234 234 | 
             
                    data_streams = [data_streams] if data_streams.is_a?(Hash) # Deal with issue of only one existing
         | 
| 235 235 | 
             
                    data_streams.each do |stream|
         | 
| 236 | 
            -
                      @data_streams <<  | 
| 236 | 
            +
                      @data_streams << Struct::DataStream.new(
         | 
| 237 237 | 
             
                        suffix: stream['Suffix'],
         | 
| 238 238 | 
             
                        profile_name: stream['ProfileName'],
         | 
| 239 239 | 
             
                        averaged_daily_load: stream['AveragedDailyLoad'],
         | 
| @@ -270,10 +270,10 @@ module AEMO | |
| 270 270 | 
             
                  @meters.select { |x| x.status == status.to_s }
         | 
| 271 271 | 
             
                end
         | 
| 272 272 |  | 
| 273 | 
            -
                # Returns the data_stream  | 
| 273 | 
            +
                # Returns the data_stream Structs for the requested status (A/I)
         | 
| 274 274 | 
             
                #
         | 
| 275 275 | 
             
                # @param [String] status the stateus [A|I]
         | 
| 276 | 
            -
                # @return [Array< | 
| 276 | 
            +
                # @return [Array<Struct>] Returns an array of Structs for the
         | 
| 277 277 | 
             
                #   current Meters
         | 
| 278 278 | 
             
                def data_streams_by_status(status = 'A')
         | 
| 279 279 | 
             
                  @data_streams.select { |x| x.status == status.to_s }
         | 
| @@ -297,19 +297,20 @@ module AEMO | |
| 297 297 |  | 
| 298 298 | 
             
                # A function to return the distribution loss factor value for a given date
         | 
| 299 299 | 
             
                #
         | 
| 300 | 
            -
                # @param [DateTime, Time] datetime the date for the distribution loss factor
         | 
| 300 | 
            +
                # @param [DateTime, ::Time] datetime the date for the distribution loss factor
         | 
| 301 301 | 
             
                #   value
         | 
| 302 302 | 
             
                # @return [nil, float] the distribution loss factor value
         | 
| 303 | 
            -
                def dlfc_value(datetime = Time.now)
         | 
| 303 | 
            +
                def dlfc_value(datetime = ::Time.now)
         | 
| 304 304 | 
             
                  if @dlf.nil?
         | 
| 305 305 | 
             
                    raise 'No DLF set, ensure that you have set the value either via the' \
         | 
| 306 306 | 
             
                          'update_from_msats! function or manually'
         | 
| 307 307 | 
             
                  end
         | 
| 308 308 | 
             
                  raise 'DLF is invalid' unless DLF_CODES.keys.include?(@dlf)
         | 
| 309 | 
            -
                  raise 'Invalid date' unless [DateTime, Time].include?(datetime.class)
         | 
| 309 | 
            +
                  raise 'Invalid date' unless [DateTime, ::Time].include?(datetime.class)
         | 
| 310 | 
            +
             | 
| 310 311 | 
             
                  possible_values = DLF_CODES[@dlf].select do |x|
         | 
| 311 | 
            -
                    Time.parse(x['FromDate']) <= datetime &&
         | 
| 312 | 
            -
                      Time.parse(x['ToDate']) >= datetime
         | 
| 312 | 
            +
                    ::Time.parse(x['FromDate']) <= datetime &&
         | 
| 313 | 
            +
                      ::Time.parse(x['ToDate']) >= datetime
         | 
| 313 314 | 
             
                  end
         | 
| 314 315 | 
             
                  if possible_values.empty?
         | 
| 315 316 | 
             
                    nil
         | 
| @@ -320,61 +321,70 @@ module AEMO | |
| 320 321 |  | 
| 321 322 | 
             
                # A function to return the distribution loss factor value for a given date
         | 
| 322 323 | 
             
                #
         | 
| 323 | 
            -
                # @param [DateTime, Time] start the date for the distribution loss factor value
         | 
| 324 | 
            -
                # @param [DateTime, Time] finish the date for the distribution loss factor value
         | 
| 324 | 
            +
                # @param [DateTime, ::Time] start the date for the distribution loss factor value
         | 
| 325 | 
            +
                # @param [DateTime, ::Time] finish the date for the distribution loss factor value
         | 
| 325 326 | 
             
                # @return [Array(Hash)] array of hashes of start, finish and value
         | 
| 326 | 
            -
                def dlfc_values(start = Time.now, finish = Time.now)
         | 
| 327 | 
            +
                def dlfc_values(start = ::Time.now, finish = ::Time.now)
         | 
| 327 328 | 
             
                  if @dlf.nil?
         | 
| 328 | 
            -
                    raise 'No DLF set, ensure that you have set the value either via the '\
         | 
| 329 | 
            +
                    raise 'No DLF set, ensure that you have set the value either via the ' \
         | 
| 329 330 | 
             
                          'update_from_msats! function or manually'
         | 
| 330 331 | 
             
                  end
         | 
| 331 332 | 
             
                  raise 'DLF is invalid' unless DLF_CODES.keys.include?(@dlf)
         | 
| 332 | 
            -
                  raise 'Invalid start' unless [DateTime, Time].include?(start.class)
         | 
| 333 | 
            -
                  raise 'Invalid finish' unless [DateTime, Time].include?(finish.class)
         | 
| 333 | 
            +
                  raise 'Invalid start' unless [DateTime, ::Time].include?(start.class)
         | 
| 334 | 
            +
                  raise 'Invalid finish' unless [DateTime, ::Time].include?(finish.class)
         | 
| 334 335 | 
             
                  raise 'start cannot be after finish' if start > finish
         | 
| 335 | 
            -
             | 
| 336 | 
            +
             | 
| 337 | 
            +
                  DLF_CODES[@dlf].reject { |x| start > ::Time.parse(x['ToDate']) || finish < ::Time.parse(x['FromDate']) }
         | 
| 336 338 | 
             
                                 .map { |x| { 'start' => x['FromDate'], 'finish' => x['ToDate'], 'value' => x['Value'].to_f } }
         | 
| 337 339 | 
             
                end
         | 
| 338 340 |  | 
| 339 341 | 
             
                # A function to return the transmission node identifier loss factor value for a given date
         | 
| 340 342 | 
             
                #
         | 
| 341 | 
            -
                # @param [DateTime, Time] datetime the date for the distribution loss factor value
         | 
| 343 | 
            +
                # @param [DateTime, ::Time] datetime the date for the distribution loss factor value
         | 
| 342 344 | 
             
                # @return [nil, float] the transmission node identifier loss factor value
         | 
| 343 | 
            -
                def tni_value(datetime = Time.now)
         | 
| 345 | 
            +
                def tni_value(datetime = ::Time.now)
         | 
| 344 346 | 
             
                  if @tni.nil?
         | 
| 345 | 
            -
                    raise 'No TNI set, ensure that you have set the value either via the '\
         | 
| 347 | 
            +
                    raise 'No TNI set, ensure that you have set the value either via the ' \
         | 
| 346 348 | 
             
                          'update_from_msats! function or manually'
         | 
| 347 349 | 
             
                  end
         | 
| 348 350 | 
             
                  raise 'TNI is invalid' unless TNI_CODES.keys.include?(@tni)
         | 
| 349 | 
            -
                  raise 'Invalid date' unless [DateTime, Time].include?(datetime.class)
         | 
| 350 | 
            -
             | 
| 351 | 
            +
                  raise 'Invalid date' unless [DateTime, ::Time].include?(datetime.class)
         | 
| 352 | 
            +
             | 
| 353 | 
            +
                  possible_values = TNI_CODES[@tni].select do |x|
         | 
| 354 | 
            +
                    ::Time.parse(x['FromDate']) <= datetime && datetime <= ::Time.parse(x['ToDate'])
         | 
| 355 | 
            +
                  end
         | 
| 351 356 | 
             
                  return nil if possible_values.empty?
         | 
| 352 | 
            -
             | 
| 357 | 
            +
             | 
| 358 | 
            +
                  possible_values = possible_values.first['mlf_data']['loss_factors'].select do |x|
         | 
| 359 | 
            +
                    ::Time.parse(x['start']) <= datetime && datetime <= ::Time.parse(x['finish'])
         | 
| 360 | 
            +
                  end
         | 
| 353 361 | 
             
                  return nil if possible_values.empty?
         | 
| 362 | 
            +
             | 
| 354 363 | 
             
                  possible_values.first['value'].to_f
         | 
| 355 364 | 
             
                end
         | 
| 356 365 |  | 
| 357 366 | 
             
                # A function to return the transmission node identifier loss factor value for a given date
         | 
| 358 367 | 
             
                #
         | 
| 359 | 
            -
                # @param [DateTime, Time] start the date for the distribution loss factor value
         | 
| 360 | 
            -
                # @param [DateTime, Time] finish the date for the distribution loss factor value
         | 
| 368 | 
            +
                # @param [DateTime, ::Time] start the date for the distribution loss factor value
         | 
| 369 | 
            +
                # @param [DateTime, ::Time] finish the date for the distribution loss factor value
         | 
| 361 370 | 
             
                # @return [Array(Hash)] array of hashes of start, finish and value
         | 
| 362 | 
            -
                def tni_values(start = Time.now, finish = Time.now)
         | 
| 371 | 
            +
                def tni_values(start = ::Time.now, finish = ::Time.now)
         | 
| 363 372 | 
             
                  if @tni.nil?
         | 
| 364 | 
            -
                    raise 'No TNI set, ensure that you have set the value either via the '\
         | 
| 373 | 
            +
                    raise 'No TNI set, ensure that you have set the value either via the ' \
         | 
| 365 374 | 
             
                          'update_from_msats! function or manually'
         | 
| 366 375 | 
             
                  end
         | 
| 367 376 | 
             
                  raise 'TNI is invalid' unless TNI_CODES.keys.include?(@tni)
         | 
| 368 | 
            -
                  raise 'Invalid start' unless [DateTime, Time].include?(start.class)
         | 
| 369 | 
            -
                  raise 'Invalid finish' unless [DateTime, Time].include?(finish.class)
         | 
| 377 | 
            +
                  raise 'Invalid start' unless [DateTime, ::Time].include?(start.class)
         | 
| 378 | 
            +
                  raise 'Invalid finish' unless [DateTime, ::Time].include?(finish.class)
         | 
| 370 379 | 
             
                  raise 'start cannot be after finish' if start > finish
         | 
| 371 380 |  | 
| 372 381 | 
             
                  possible_values = TNI_CODES[@tni].reject do |tni_code|
         | 
| 373 | 
            -
                    start > Time.parse(tni_code['ToDate']) ||
         | 
| 374 | 
            -
                      finish < Time.parse(tni_code['FromDate'])
         | 
| 382 | 
            +
                    start > ::Time.parse(tni_code['ToDate']) ||
         | 
| 383 | 
            +
                      finish < ::Time.parse(tni_code['FromDate'])
         | 
| 375 384 | 
             
                  end
         | 
| 376 385 |  | 
| 377 386 | 
             
                  return nil if possible_values.empty?
         | 
| 387 | 
            +
             | 
| 378 388 | 
             
                  possible_values.map { |x| x['mlf_data']['loss_factors'] }
         | 
| 379 389 | 
             
                end
         | 
| 380 390 | 
             
              end
         | 
    
        data/lib/aemo/region.rb
    CHANGED
    
    | @@ -12,11 +12,11 @@ module AEMO | |
| 12 12 | 
             
                  'ACT' => 'Australian Capital Territory',
         | 
| 13 13 | 
             
                  'NSW' => 'New South Wales',
         | 
| 14 14 | 
             
                  'QLD' => 'Queensland',
         | 
| 15 | 
            -
                  'SA' | 
| 15 | 
            +
                  'SA' => 'South Australia',
         | 
| 16 16 | 
             
                  'TAS' => 'Tasmania',
         | 
| 17 17 | 
             
                  'VIC' => 'Victoria',
         | 
| 18 | 
            -
                  'NT' | 
| 19 | 
            -
                  'WA' | 
| 18 | 
            +
                  'NT' => 'Northern Territory',
         | 
| 19 | 
            +
                  'WA' => 'Western Australia'
         | 
| 20 20 | 
             
                }.freeze
         | 
| 21 21 |  | 
| 22 22 | 
             
                attr_accessor :region
         | 
| @@ -34,6 +34,7 @@ module AEMO | |
| 34 34 | 
             
                # @return [self]
         | 
| 35 35 | 
             
                def initialize(region)
         | 
| 36 36 | 
             
                  raise ArgumentError, "Region '#{region}' is not valid." unless valid_region?(region)
         | 
| 37 | 
            +
             | 
| 37 38 | 
             
                  @region = region.upcase
         | 
| 38 39 | 
             
                  @full_name = REGIONS[@region]
         | 
| 39 40 | 
             
                  @current_trading = []
         | 
    
        data/lib/aemo/register.rb
    CHANGED
    
    | @@ -28,14 +28,14 @@ module AEMO | |
| 28 28 | 
             
                # @return [AEMO::Register] description of returned object
         | 
| 29 29 | 
             
                def self.from_hash(register)
         | 
| 30 30 | 
             
                  AEMO::Register.new(
         | 
| 31 | 
            -
                    controlled_load: | 
| 32 | 
            -
                    dial_format: | 
| 33 | 
            -
                    multiplier: | 
| 31 | 
            +
                    controlled_load: register['ControlledLoad'] == 'Y',
         | 
| 32 | 
            +
                    dial_format: register['DialFormat'],
         | 
| 33 | 
            +
                    multiplier: register['Multiplier'],
         | 
| 34 34 | 
             
                    network_tariff_code: register['NetworkTariffCode'],
         | 
| 35 | 
            -
                    register_id: | 
| 36 | 
            -
                    status: | 
| 37 | 
            -
                    time_of_day: | 
| 38 | 
            -
                    unit_of_measure: | 
| 35 | 
            +
                    register_id: register['RegisterID'],
         | 
| 36 | 
            +
                    status: register['Status'],
         | 
| 37 | 
            +
                    time_of_day: register['TimeOfDay'],
         | 
| 38 | 
            +
                    unit_of_measure: register['UnitOfMeasure']
         | 
| 39 39 | 
             
                  )
         | 
| 40 40 | 
             
                end
         | 
| 41 41 | 
             
              end
         | 
    
        data/lib/aemo/struct.rb
    ADDED
    
    
    
        data/lib/aemo/time.rb
    ADDED
    
    | @@ -0,0 +1,112 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'time'
         | 
| 4 | 
            +
            require 'active_support/all'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require_relative 'exceptions/time_error'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module AEMO
         | 
| 9 | 
            +
              # [AEMO::Time] provides time helpers for AEMO services.
         | 
| 10 | 
            +
              module Time
         | 
| 11 | 
            +
                NEMTIMEZONE = 'Australia/Brisbane'
         | 
| 12 | 
            +
                TIMESTAMP14 = '%Y%m%d%H%M%S'
         | 
| 13 | 
            +
                TIMESTAMP14_PATTERN = /^\d{4}\d{2}\d{2}\d{2}\d{2}\d{2}$/
         | 
| 14 | 
            +
                TIMESTAMP12 = '%Y%m%d%H%M'
         | 
| 15 | 
            +
                TIMESTAMP12_PATTERN = /^\d{4}\d{2}\d{2}\d{2}\d{2}$/
         | 
| 16 | 
            +
                TIMESTAMP8 = '%Y%m%d'
         | 
| 17 | 
            +
                TIMESTAMP8_PATTERN = /^\d{4}\d{2}\d{2}$/
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                class << self
         | 
| 20 | 
            +
                  # Format a time to a timestamp 14.
         | 
| 21 | 
            +
                  #
         | 
| 22 | 
            +
                  # @param [Time] time
         | 
| 23 | 
            +
                  # @return [String]
         | 
| 24 | 
            +
                  def format_timestamp14(time)
         | 
| 25 | 
            +
                    time.in_time_zone(NEMTIMEZONE).strftime(TIMESTAMP14)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # Format a time to a timestamp 12.
         | 
| 29 | 
            +
                  #
         | 
| 30 | 
            +
                  # @param [Time] time
         | 
| 31 | 
            +
                  # @return [String]
         | 
| 32 | 
            +
                  def format_timestamp12(time)
         | 
| 33 | 
            +
                    time.in_time_zone(NEMTIMEZONE).strftime(TIMESTAMP12)
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  # Format a time to a timestamp 8.
         | 
| 37 | 
            +
                  #
         | 
| 38 | 
            +
                  # @param [Time] time
         | 
| 39 | 
            +
                  # @return [String]
         | 
| 40 | 
            +
                  def format_timestamp8(time)
         | 
| 41 | 
            +
                    time.in_time_zone(NEMTIMEZONE).strftime(TIMESTAMP8)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  # Parse a 14 character timestamp.
         | 
| 45 | 
            +
                  #
         | 
| 46 | 
            +
                  # @param [String] string
         | 
| 47 | 
            +
                  # @raise [AEMO::TimeError]
         | 
| 48 | 
            +
                  # @return [Time]
         | 
| 49 | 
            +
                  def parse_timestamp14(string)
         | 
| 50 | 
            +
                    raise AEMO::TimeError unless string.match(TIMESTAMP14_PATTERN)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    ::Time.find_zone(NEMTIMEZONE).strptime(string, TIMESTAMP14)
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  # Parse a 12 character timestamp.
         | 
| 56 | 
            +
                  #
         | 
| 57 | 
            +
                  # @param [String] string
         | 
| 58 | 
            +
                  # @return [Time]
         | 
| 59 | 
            +
                  def parse_timestamp12(string)
         | 
| 60 | 
            +
                    raise AEMO::TimeError unless string.match(TIMESTAMP12_PATTERN)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    ::Time.find_zone(NEMTIMEZONE).strptime(string, TIMESTAMP12)
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  # Parse an 8 character date.
         | 
| 66 | 
            +
                  #
         | 
| 67 | 
            +
                  # @param [String] string
         | 
| 68 | 
            +
                  # @return [Time]
         | 
| 69 | 
            +
                  def parse_timestamp8(string)
         | 
| 70 | 
            +
                    raise AEMO::TimeError unless string.match(TIMESTAMP8_PATTERN)
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    ::Time.find_zone(NEMTIMEZONE).strptime(string, TIMESTAMP8)
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  # Check if a string is a valid timestamp 14.
         | 
| 76 | 
            +
                  #
         | 
| 77 | 
            +
                  # @param [String] string
         | 
| 78 | 
            +
                  # @return [Boolean]
         | 
| 79 | 
            +
                  def valid_timestamp14?(string)
         | 
| 80 | 
            +
                    parse_timestamp14(string)
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    true
         | 
| 83 | 
            +
                  rescue AEMO::TimeError
         | 
| 84 | 
            +
                    false
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  # Check if a string is a valid timestamp 12.
         | 
| 88 | 
            +
                  #
         | 
| 89 | 
            +
                  # @param [String] string
         | 
| 90 | 
            +
                  # @return [Boolean]
         | 
| 91 | 
            +
                  def valid_timestamp12?(string)
         | 
| 92 | 
            +
                    parse_timestamp12(string)
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    true
         | 
| 95 | 
            +
                  rescue AEMO::TimeError
         | 
| 96 | 
            +
                    false
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  # Check if a string is a valid timestamp 8.
         | 
| 100 | 
            +
                  #
         | 
| 101 | 
            +
                  # @param [String] string
         | 
| 102 | 
            +
                  # @return [Boolean]
         | 
| 103 | 
            +
                  def valid_timestamp8?(string)
         | 
| 104 | 
            +
                    parse_timestamp8(string)
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    true
         | 
| 107 | 
            +
                  rescue AEMO::TimeError
         | 
| 108 | 
            +
                    false
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
              end
         | 
| 112 | 
            +
            end
         | 
    
        data/lib/aemo/version.rb
    CHANGED
    
    | @@ -1,6 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            # -*- coding: UTF-8 -*-
         | 
| 4 3 | 
             
            #
         | 
| 5 4 | 
             
            # Copyright 2014 Joel Courtney
         | 
| 6 5 | 
             
            #
         | 
| @@ -24,7 +23,7 @@ | |
| 24 23 | 
             
            # @author Joel Courtney <euphemize@gmail.com>
         | 
| 25 24 | 
             
            module AEMO
         | 
| 26 25 | 
             
              # aemo version
         | 
| 27 | 
            -
              VERSION = '0. | 
| 26 | 
            +
              VERSION = '0.6.0'
         | 
| 28 27 |  | 
| 29 28 | 
             
              # aemo version split amongst different revisions
         | 
| 30 29 | 
             
              MAJOR_VERSION, MINOR_VERSION, REVISION = VERSION.split('.').map(&:to_i)
         | 
    
        data/lib/aemo.rb
    CHANGED
    
    | @@ -4,17 +4,19 @@ require 'active_support/all' | |
| 4 4 | 
             
            require 'httparty'
         | 
| 5 5 | 
             
            require 'csv'
         | 
| 6 6 |  | 
| 7 | 
            -
            require 'aemo/ | 
| 8 | 
            -
            require 'aemo/ | 
| 9 | 
            -
            require 'aemo/ | 
| 10 | 
            -
            require 'aemo/market | 
| 11 | 
            -
            require 'aemo/ | 
| 12 | 
            -
            require 'aemo/ | 
| 13 | 
            -
            require 'aemo/ | 
| 14 | 
            -
            require 'aemo/ | 
| 15 | 
            -
            require 'aemo/ | 
| 16 | 
            -
            require 'aemo/ | 
| 17 | 
            -
            require 'aemo/ | 
| 7 | 
            +
            require 'aemo/struct'
         | 
| 8 | 
            +
            require 'aemo/time'
         | 
| 9 | 
            +
            require 'aemo/region'
         | 
| 10 | 
            +
            require 'aemo/market'
         | 
| 11 | 
            +
            require 'aemo/market/interval'
         | 
| 12 | 
            +
            require 'aemo/market/node'
         | 
| 13 | 
            +
            require 'aemo/meter'
         | 
| 14 | 
            +
            require 'aemo/nem12'
         | 
| 15 | 
            +
            require 'aemo/nmi'
         | 
| 16 | 
            +
            require 'aemo/msats'
         | 
| 17 | 
            +
            require 'aemo/register'
         | 
| 18 | 
            +
            require 'aemo/version'
         | 
| 19 | 
            +
            require 'aemo/exceptions/invalid_nmi_allocation_type'
         | 
| 18 20 |  | 
| 19 21 | 
             
            # AEMO Module to encapsulate all AEMO classes
         | 
| 20 22 | 
             
            module AEMO
         | 
    
        data/lib/data/xml_to_json.rb
    CHANGED
    
    | @@ -21,7 +21,7 @@ CSV.parse(file_contents, headers: true, converters: :numeric).each do |row| | |
| 21 21 | 
             
              @mlf_data[row['TNI']] ||= { location: row['Location'],
         | 
| 22 22 | 
             
                                          voltage: row['Voltage'],
         | 
| 23 23 | 
             
                                          loss_factors: [] }
         | 
| 24 | 
            -
              row.headers. | 
| 24 | 
            +
              row.headers.grep(/^FY\d{2}$/).sort.reverse.each do |fin_year|
         | 
| 25 25 | 
             
                year = "20#{fin_year.match(/FY(\d{2})/)[1]}".to_i
         | 
| 26 26 | 
             
                @mlf_data[row['TNI']][:loss_factors] << {
         | 
| 27 27 | 
             
                  start: Time.parse("#{year - 1}-07-01T00:00:00+1000"),
         | 
| @@ -74,7 +74,5 @@ end | |
| 74 74 | 
             
                output_data[code] << output_data_instance
         | 
| 75 75 | 
             
              end
         | 
| 76 76 |  | 
| 77 | 
            -
              File. | 
| 78 | 
            -
                write_file.write(output_data.to_json)
         | 
| 79 | 
            -
              end
         | 
| 77 | 
            +
              File.write(File.join(@path, output_file), output_data.to_json)
         | 
| 80 78 | 
             
            end
         | 
    
        data/spec/aemo_spec.rb
    CHANGED
    
    
| @@ -8,42 +8,60 @@ describe AEMO::Market::Interval do | |
| 8 8 | 
             
                  expect(AEMO::Market::Interval::INTERVALS).to eq(trading: 'Trading', dispatch: 'Dispatch')
         | 
| 9 9 | 
             
                end
         | 
| 10 10 | 
             
              end
         | 
| 11 | 
            +
             | 
| 11 12 | 
             
              describe 'AEMO::Market::Interval instance methods' do
         | 
| 12 | 
            -
                before | 
| 13 | 
            -
                  @interval =  | 
| 13 | 
            +
                before do
         | 
| 14 | 
            +
                  @interval = described_class.new('2016-03-01T00:30:00', 'REGION' => 'NSW', 'TOTALDEMAND' => 1000.23,
         | 
| 15 | 
            +
                                                                         'RRP' => 76.54, 'PERIODTYPE' => 'TRADING')
         | 
| 14 16 | 
             
                end
         | 
| 17 | 
            +
             | 
| 15 18 | 
             
                it 'creates a valid interval' do
         | 
| 16 | 
            -
                  expect  | 
| 19 | 
            +
                  expect do
         | 
| 20 | 
            +
                    described_class.new('2016-03-01T00:30:00', 'REGION' => 'NSW', 'TOTALDEMAND' => 1000.23, 'RRP' => 76.54,
         | 
| 21 | 
            +
                                                               'PERIODTYPE' => 'TRADING')
         | 
| 22 | 
            +
                  end.not_to raise_error
         | 
| 17 23 | 
             
                end
         | 
| 24 | 
            +
             | 
| 18 25 | 
             
                it 'has a trailing datetime' do
         | 
| 19 26 | 
             
                  expect(@interval.datetime).to eq(Time.parse('2016-03-01T00:30:00+1000'))
         | 
| 20 27 | 
             
                end
         | 
| 28 | 
            +
             | 
| 21 29 | 
             
                it 'has a leading datetime' do
         | 
| 22 | 
            -
                  expect(@interval.datetime(false)).to eq(Time.parse('2016-03-01T00:00:00+1000'))
         | 
| 30 | 
            +
                  expect(@interval.datetime(trailing_edge: false)).to eq(Time.parse('2016-03-01T00:00:00+1000'))
         | 
| 23 31 | 
             
                end
         | 
| 32 | 
            +
             | 
| 24 33 | 
             
                it 'has a leading datetime for dispatch' do
         | 
| 25 | 
            -
                  @interval =  | 
| 26 | 
            -
             | 
| 34 | 
            +
                  @interval = described_class.new('2016-03-01T00:30:00', 'REGION' => 'NSW', 'TOTALDEMAND' => 1000.23,
         | 
| 35 | 
            +
                                                                         'RRP' => 76.54, 'PERIODTYPE' => '')
         | 
| 36 | 
            +
                  expect(@interval.datetime(trailing_edge: false)).to eq(Time.parse('2016-03-01T00:25:00+1000'))
         | 
| 27 37 | 
             
                end
         | 
| 38 | 
            +
             | 
| 28 39 | 
             
                it 'has an interval length' do
         | 
| 29 40 | 
             
                  expect(@interval.interval_length).to eq(Time.at(300))
         | 
| 30 41 | 
             
                end
         | 
| 42 | 
            +
             | 
| 31 43 | 
             
                it 'is a trading interval' do
         | 
| 32 44 | 
             
                  expect(@interval.interval_type).to eq(:trading)
         | 
| 33 45 | 
             
                end
         | 
| 46 | 
            +
             | 
| 34 47 | 
             
                it 'is a trading interval' do
         | 
| 35 | 
            -
                  expect(@interval.trading?).to  | 
| 36 | 
            -
                  expect(@interval.dispatch?).to  | 
| 48 | 
            +
                  expect(@interval.trading?).to be(true)
         | 
| 49 | 
            +
                  expect(@interval.dispatch?).to be(false)
         | 
| 37 50 | 
             
                end
         | 
| 51 | 
            +
             | 
| 38 52 | 
             
                it 'is a dispatch interval' do
         | 
| 39 | 
            -
                  @interval =  | 
| 53 | 
            +
                  @interval = described_class.new('2016-03-01T00:30:00', 'REGION' => 'NSW', 'TOTALDEMAND' => 1000.23,
         | 
| 54 | 
            +
                                                                         'RRP' => 76.54, 'PERIODTYPE' => '')
         | 
| 40 55 | 
             
                  expect(@interval.interval_type).to eq(:dispatch)
         | 
| 41 56 | 
             
                end
         | 
| 57 | 
            +
             | 
| 42 58 | 
             
                it 'is a dispatch interval' do
         | 
| 43 | 
            -
                  @interval =  | 
| 44 | 
            -
             | 
| 45 | 
            -
                  expect(@interval. | 
| 59 | 
            +
                  @interval = described_class.new('2016-03-01T00:30:00', 'REGION' => 'NSW', 'TOTALDEMAND' => 1000.23,
         | 
| 60 | 
            +
                                                                         'RRP' => 76.54, 'PERIODTYPE' => '')
         | 
| 61 | 
            +
                  expect(@interval.trading?).to be(false)
         | 
| 62 | 
            +
                  expect(@interval.dispatch?).to be(true)
         | 
| 46 63 | 
             
                end
         | 
| 64 | 
            +
             | 
| 47 65 | 
             
                it 'has a valid value' do
         | 
| 48 66 | 
             
                  expect(@interval.value).to eq((@interval.total_demand * @interval.rrp).round(2))
         | 
| 49 67 | 
             
                end
         | 
| @@ -4,30 +4,32 @@ require 'spec_helper' | |
| 4 4 |  | 
| 5 5 | 
             
            describe AEMO::Market::Node do
         | 
| 6 6 | 
             
              describe '.IDENTIFIERS' do
         | 
| 7 | 
            -
                it ' | 
| 7 | 
            +
                it 'is an array' do
         | 
| 8 8 | 
             
                  expect(AEMO::Market::Node::IDENTIFIERS).to be_instance_of(Array)
         | 
| 9 9 | 
             
                end
         | 
| 10 10 | 
             
              end
         | 
| 11 11 |  | 
| 12 12 | 
             
              describe 'creating a node' do
         | 
| 13 | 
            -
                it ' | 
| 14 | 
            -
                  expect {  | 
| 13 | 
            +
                it 'raises an error if invalid region' do
         | 
| 14 | 
            +
                  expect { described_class.new('BOTTOMS') }.to raise_error(ArgumentError)
         | 
| 15 15 | 
             
                end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 16 | 
            +
             | 
| 17 | 
            +
                it 'creates if node valid' do
         | 
| 18 | 
            +
                  expect { described_class.new('NSW') }.not_to raise_error
         | 
| 18 19 | 
             
                end
         | 
| 19 20 | 
             
              end
         | 
| 20 21 |  | 
| 21 22 | 
             
              describe 'AEMO::Region instance methods' do
         | 
| 22 | 
            -
                before | 
| 23 | 
            -
                  @nsw =  | 
| 23 | 
            +
                before do
         | 
| 24 | 
            +
                  @nsw = described_class.new('NSW')
         | 
| 24 25 | 
             
                end
         | 
| 25 26 |  | 
| 26 27 | 
             
                describe 'AEMO::Region dispatch information' do
         | 
| 27 | 
            -
                  it ' | 
| 28 | 
            +
                  it 'returns current dispatch data' do
         | 
| 28 29 | 
             
                    expect(@nsw.current_dispatch.count).to eq(AEMO::Market.current_dispatch(@nsw.identifier).count)
         | 
| 29 30 | 
             
                  end
         | 
| 30 | 
            -
             | 
| 31 | 
            +
             | 
| 32 | 
            +
                  it 'returns current trading data' do
         | 
| 31 33 | 
             
                    expect(@nsw.current_trading.count).to eq(AEMO::Market.current_trading(@nsw.identifier).count)
         | 
| 32 34 | 
             
                  end
         | 
| 33 35 | 
             
                end
         | 
| @@ -14,25 +14,26 @@ describe AEMO::Market do | |
| 14 14 |  | 
| 15 15 | 
             
              describe '.current_dispatch' do
         | 
| 16 16 | 
             
                it 'has an array of data' do
         | 
| 17 | 
            -
                  expect( | 
| 17 | 
            +
                  expect(described_class.current_dispatch('NSW').class).to eq(Array)
         | 
| 18 18 | 
             
                end
         | 
| 19 19 | 
             
              end
         | 
| 20 20 |  | 
| 21 21 | 
             
              describe '.current_trading' do
         | 
| 22 22 | 
             
                it 'has an array of data' do
         | 
| 23 | 
            -
                  expect( | 
| 23 | 
            +
                  expect(described_class.current_trading('NSW').class).to eq(Array)
         | 
| 24 24 | 
             
                end
         | 
| 25 25 | 
             
              end
         | 
| 26 26 |  | 
| 27 27 | 
             
              describe '.historic_trading_by_range' do
         | 
| 28 28 | 
             
                it 'has an array of data' do
         | 
| 29 | 
            -
                  expect( | 
| 29 | 
            +
                  expect(described_class.historic_trading_by_range('NSW', Date.parse('2015-01-01'),
         | 
| 30 | 
            +
                                                                   Date.parse('2015-02-28')).class).to eq(Array)
         | 
| 30 31 | 
             
                end
         | 
| 31 32 | 
             
              end
         | 
| 32 33 |  | 
| 33 34 | 
             
              describe '.historic_trading' do
         | 
| 34 35 | 
             
                it 'has an array of data' do
         | 
| 35 | 
            -
                  expect( | 
| 36 | 
            +
                  expect(described_class.historic_trading('NSW', 2015, 1).class).to eq(Array)
         | 
| 36 37 | 
             
                end
         | 
| 37 38 | 
             
              end
         | 
| 38 39 | 
             
            end
         | 
    
        data/spec/lib/aemo/meter_spec.rb
    CHANGED
    
    | @@ -5,14 +5,14 @@ require 'spec_helper' | |
| 5 5 | 
             
            describe AEMO::Meter do
         | 
| 6 6 | 
             
              describe 'instance methods' do
         | 
| 7 7 | 
             
                it 'creates a new instance' do
         | 
| 8 | 
            -
                  expect( | 
| 8 | 
            +
                  expect(described_class.new).to be_a described_class
         | 
| 9 9 | 
             
                end
         | 
| 10 10 |  | 
| 11 11 | 
             
                it 'can be initialized from MSATS mumbo jumbo' do
         | 
| 12 12 | 
             
                  AEMO::MSATS.authorize('ER', 'ER', 'ER')
         | 
| 13 13 | 
             
                  nmi_detail_query = AEMO::MSATS.nmi_detail('4001234567')
         | 
| 14 14 | 
             
                  meter = nmi_detail_query['MeterRegister']['Meter'].first
         | 
| 15 | 
            -
                  expect( | 
| 15 | 
            +
                  expect(described_class.from_hash(meter)).to be_a described_class
         | 
| 16 16 | 
             
                end
         | 
| 17 17 | 
             
              end
         | 
| 18 18 | 
             
            end
         |