import re from typing import Optional from .energy_climate_change_levy_data import CCL_RATES, CCL_RATES_HISTORY, CclRate from .energy_climate_change_levy_types import CclCommodity, ClimateChangeLevy from .math_round_div import RoundingMode, round_div from .money_amount import money ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}") # quantity (thousandths of a unit) x rate (thousandths of a penny) is in # millionths of a penny. DIVISOR = 1_000_000 MAX_SAFE = 9007199254740991 def climate_change_levy( commodity: CclCommodity, quantity: int, on_date: str, qualifying_use: bool, mode: RoundingMode ) -> ClimateChangeLevy: """The Climate Change Levy on one supply, at the main rate in force on ``on_date``. A qualifying-use supply (domestic, charity non-business or de minimis) is outside the levy and comes back excluded, with a zero levy. """ if not isinstance(on_date, str) or not ISO_DATE.fullmatch(on_date): raise ValueError('onDate must be an ISO date (YYYY-MM-DD), received "%s"' % (on_date,)) if not isinstance(quantity, int) or isinstance(quantity, bool) or quantity < 0: raise ValueError( "quantity must be a whole number of thousandths of a unit, 0 or more, received %s" % (quantity,) ) if not any(r.commodity == commodity for r in CCL_RATES): raise ValueError('unknown commodity "%s": expected electricity, gas, lpg or other' % (commodity,)) best: Optional[CclRate] = None for r in CCL_RATES: if r.commodity != commodity or on_date < r.valid_from: continue if r.valid_to is not None and on_date > r.valid_to: continue if best is None or r.valid_from > best.valid_from: best = r if best is None: # A pruned build must not answer an old date with a newer rate. if CCL_RATES_HISTORY != "full": earliest = None for r in CCL_RATES: if r.commodity == commodity and (earliest is None or r.valid_from < earliest): earliest = r.valid_from if earliest is not None and on_date < earliest: raise ValueError( "no Climate Change Levy rate for %s on %s: this build was installed with history=%s, " "so it only carries rates from %s. Reinstall with history=full for older supplies." % (commodity, on_date, CCL_RATES_HISTORY, earliest) ) raise ValueError("no Climate Change Levy rate for %s on %s" % (commodity, on_date)) pence = 0 if not qualifying_use: product = quantity * best.rate if product > MAX_SAFE: raise ValueError("levy too large to calculate exactly") pence = round_div(product, DIVISOR, mode) return ClimateChangeLevy( commodity=commodity, unit=best.unit, rate=best.rate, excluded=bool(qualifying_use), levy=money(pence, "GBP"), valid_from=best.valid_from, valid_to=best.valid_to, )