from .dates_days_between import days_between from .energy_estimate_read_types import EstimatedRead, MeterRead from .energy_meter_advance import meter_advance from .math_round_div import RoundingMode, round_div MAX_SAFE = 9007199254740991 def estimate_read( earlier_read: MeterRead, latest_read: MeterRead, on_date: str, digits: int, mode: RoundingMode ) -> EstimatedRead: """Carry the latest read forward to ``on_date`` at the average daily consumption between the two reads, rounding once, and wrap past all nines. """ consumption = meter_advance(earlier_read.value, latest_read.value, digits) history = days_between(earlier_read.date, latest_read.date) if history <= 0: raise ValueError( "the earlier read must be dated before the latest read, received %s and %s" % (earlier_read.date, latest_read.date) ) ahead = days_between(latest_read.date, on_date) if ahead < 0: raise ValueError( "onDate must not be before the latest read, received %s and %s" % (on_date, latest_read.date) ) product = consumption * ahead if product > MAX_SAFE: raise ValueError("estimate too large to calculate exactly") units = round_div(product, history, mode) span = 10**digits raw = latest_read.value + units return EstimatedRead(read=raw % span, units=units, rolled_over=raw >= span)