BathHacked/energy-sparks

View on GitHub
lib/data_feeds/n3rgy/data_api_client.rb

Summary

Maintainability
A
2 hrs
Test Coverage
module DataFeeds
  module N3rgy
    class ApiFailure < StandardError; end
    class NotFound < StandardError; end
    class NotAllowed < StandardError; end
    class NotAuthorised < StandardError; end

    class DataApiClient < BaseClient
      READING_TYPE_PRODUCTION = 'production'.freeze
      READING_TYPE_CONSUMPTION = 'consumption'.freeze
      READING_TYPE_IMPORT = 'import'.freeze
      READING_TYPE_EXPORT = 'export'.freeze
      READING_TYPE_TARIFF = 'tariff'.freeze

      def initialize(api_key: ENV['N3RGY_SANDBOX_API_KEY'],
                     base_url: ENV['N3RGY_SANDBOX_DATA_URL_V2'],
                     connection: nil)
        @api_key = api_key
        @base_url = base_url
        @connection = http_connection(connection)
      end

      def self.production_client
        DataApiClient.new(api_key: ENV['N3RGY_API_KEY'], base_url: ENV['N3RGY_DATA_URL_V2'])
      end

      # Returns a paged list of MPxNs for which we have consent to access
      # the data. Consent is granted or withdrawn via the ConsentApiClient
      def list_consented_meters(start_at: 0, max_results: 100)
        get_data('/', { 'startAt': start_at, 'maxResults': max_results })
      end

      # Checks to see if an MPxN is known to n3rgy. E.g. is it a SMETS-2
      # meter in the DCC?
      #
      # Does not require consent to return a response
      def find_mpxn(mpxn)
        get_data("/find-mpxn/#{mpxn}")
      end

      # Retrieve the list of utilities (fuel types) for the meter.
      def utilities(mpxn)
        get_data("/mpxn/#{mpxn}")
      end

      # Retrieve the list of reading types (e.g. consumption, production) for
      # a specific utility (fuel type)
      def reading_types(mpxn, fuel_type)
        get_data("/mpxn/#{mpxn}/utility/#{fuel_type}")
      end

      # Read the n3rgy consumption for a fuel type
      def consumption(mpxn, fuel_type, start_date = nil, end_date = nil)
        readings(mpxn, fuel_type, READING_TYPE_CONSUMPTION, start_date, end_date)
      end

      # Read the n3rgy production for a fuel type
      def production(mpxn, fuel_type, start_date = nil, end_date = nil)
        readings(mpxn, fuel_type, READING_TYPE_PRODUCTION, start_date, end_date)
      end

      # Read the tariff for the fuel type
      def tariff(mpxn, fuel_type)
        readings(mpxn, fuel_type, READING_TYPE_TARIFF)
      end

      # Fetch readings for a given MPxn, fuel type and reading type for a specified
      # date range
      #
      # Maximum 90 day window between start and end
      #
      # If not provided then their API will return the last 24 hours of data
      def readings(mpxn, fuel_type, reading_type, start_date = nil, end_date = nil)
        params = {
          'granularity': 'halfhour',
          'outputFormat': 'json'
        }
        params['start'] = url_date(start_date) if start_date.present?
        params['end'] = url_date(end_date) if end_date.present?
        get_data("/mpxn/#{mpxn}/utility/#{fuel_type}/readingtype/#{reading_type}", params)
      end

      # Fetch some diagnostic information about one or more meters identified by
      # their MPxN, UPRN (address) or their device id
      def read_inventory(device_type, mpxns: nil, uprns: nil, device_ids: nil)
        raise if mpxns.nil? && uprns.nil? && device_ids.nil?
        body = create_inventory_body(mpxns: mpxns, uprns: uprns, device_ids: device_ids)
        response = @connection.post('/read-inventory', { 'deviceType': device_type }) do |req|
          req.body = body.to_json
        end
        JSON.parse(response.body)
      end

      # This is intended to be used with a URI returned by the read_inventory call.
      #
      # That methods triggers a message to the actual meter to fetch some information
      # The URI in the response will be available for download a short time afterwards
      #
      # Initially the URL will return an error before returning a JSON document
      # once the background task as n3rgy is completed.
      def fetch_with_retry(url, retry_interval = 0, max_retries = 0)
        begin
          retries ||= 0
          sleep(retry_interval)
          get_data(url)
        rescue NotAllowed => e
          retry if (retries += 1) <= max_retries
          raise e
        end
      end

      private

      def url_date(date)
        date.strftime('%Y%m%d%H%M')
      end

      def get_data(url, params = {})
        response = @connection.get(url, params)
        raise NotAuthorised.new(error_message(response)) if response.status == 401
        raise NotAllowed.new(error_message(response)) if response.status == 403
        raise NotFound.new(error_message(response)) if response.status == 404
        raise ApiFailure.new(error_message(response)) unless response.success?
        JSON.parse(response.body)
      end

      def create_inventory_body(mpxns: nil, uprns: nil, device_ids: nil)
        body = {}
        unless mpxns.nil?
          body[:mpxns] = mpxns.is_a?(Array) ? mpxns : [mpxns]
        end
        unless uprns.nil?
          body[:uprns] = uprns.is_a?(Array) ? uprns : [uprns]
        end
        unless device_ids.nil?
          body[:deviceIds] = device_ids.is_a?(Array) ? device_ids : [device_ids]
        end
        body
      end
    end
  end
end