app/services/amr/n3rgy_readings_download_and_upsert.rb
module Amr
class N3rgyReadingsDownloadAndUpsert
def initialize(
meter:,
config:,
override_start_date: nil,
override_end_date: nil,
reload: false
)
@meter = meter
@config = config
@override_start_date = override_start_date
@override_end_date = override_end_date
@reload = reload
end
def perform
# if n3rgy dont provide us with dates, we can't load data
return if available_date_range.empty?
# determine start/end dates for API request, allowing for
# overrides and presence of previously loaded data
start_date = determine_start_date
end_date = determine_end_date
# can happen if available end from n3rgy is only part way through the day and
# we've then set end_date to be end of the previous day to avoid loading
# incomplete readings.
return if start_date > end_date
import_log = create_import_log(start_date, end_date)
readings = N3rgyDownloader.new(meter: @meter, start_date: start_date, end_date: end_date).readings
N3rgyReadingsUpserter.new(meter: @meter, config: @config, readings: readings, import_log: import_log).perform
rescue => e
msg = "Error downloading data for #{@meter.mpan_mprn} from #{start_date} to #{end_date} : #{e.message}"
import_log.update!(error_messages: msg) if import_log
Rails.logger.error msg
Rails.logger.error e.backtrace.join("\n")
Rollbar.error(e, job: :n3rgy_download, meter_id: @meter.mpan_mprn, start_date: start_date, end_date: end_date)
end
private
def available_date_range
@available_date_range ||= Meters::N3rgyMeteringService.new(@meter).available_data
end
def create_import_log(start_date, end_date)
AmrDataFeedImportLog.create(
amr_data_feed_config_id: @config.id,
file_name: "N3rgy API import for #{@meter.mpan_mprn} for #{start_date} - #{end_date}",
import_time: DateTime.now.utc)
end
def current_date_range_of_readings
if existing_n3rgy_readings.any?
first = DateTime.parse(existing_n3rgy_readings.minimum(:reading_date))
# Instead of using the maximum date, we now use 7 days prior to that
# so we're regularly refreshing the last 7 days of data.
last = DateTime.parse(existing_n3rgy_readings.maximum(:reading_date)) - 7.days
[n3rgy_first_reading_of_day(first), n3rgy_last_reading_of_day(last)]
end
end
def existing_n3rgy_readings
@meter.amr_data_feed_readings.where(amr_data_feed_config: AmrDataFeedConfig.n3rgy_api.first)
end
# Example available cache dates from n3rgy
#
# 202305310030 data starts with the first reading of 2023-05-31. Request from here
# 202305310330 data starts later in the day of 2024-05-31. Request from first reading
# 202305310000 data starts with final reading of 2023-05-30. So wind back a full day
def determine_start_date
return @override_start_date if @override_start_date
return nil if available_date_range.empty?
start = available_date_range.first
# if the n3rgy start date is midnight, then we should wind back a day
if start == start.at_midnight
start = n3rgy_first_reading_of_day(start - 1)
end
# if set to reload, then just use dates from n3rgy
return n3rgy_first_reading_of_day(start) if @reload
# Continue loading newer readings if there's no additional historical data
# in n3rgy.
#
# As the end of our current range includes reloading of last 7 days, then
# also check that this isn't before the available date range
# start new loading from the end of any existing readings if we have any
current_range = current_date_range_of_readings
if current_range && current_range.first <= start && current_range.last > start
start = current_range.last
end
# ensure we're requesting the first reading of the day
n3rgy_first_reading_of_day(start)
end
def determine_end_date
return @override_end_date if @override_end_date
return nil if available_date_range.empty?
end_date = available_date_range.last
# encountered a data problem at n3rgy where availableCacheRange had a future date
end_date = DateTime.now if end_date >= Time.zone.today
# force to midnight (last reading of previous day) to avoid loading partial data if
# n3rgy have a few hours for today
n3rgy_last_reading_of_day(end_date)
end
# n3rgy uses 00:30 as the first half-hourly reading for a day
# so convert the date to a date time and set the time accordingly
def n3rgy_first_reading_of_day(date_time)
date_time.change(hour: 0, min: 30, sec: 0)
end
# the last half-hourly reading for a day in the n3rgy API is
# midnight of the following day.
#
# so convert date to a date time and set time accordingly
def n3rgy_last_reading_of_day(date_time)
date_time.change(hour: 0, min: 0, sec: 0)
end
end
end