from .energy_gas_kwh_types import GasEnergy, GasVolumeUnit from .math_round_div import round_div # 1.02264 / 3.6 in lowest terms, with the scales of the arguments folded in: # watt-hours = hundredths of m3 x 4261 x CV (thousandths of MJ/m3) / 1,500,000. FACTOR = 4261 DIVISOR = 1_500_000 MAX_VOLUME = 1_000_000_000_000 MAX_SAFE = 9007199254740991 def _is_int(value: object) -> bool: return isinstance(value, int) and not isinstance(value, bool) def gas_kwh(volume: int, volume_unit: GasVolumeUnit, calorific_value: int) -> GasEnergy: """Gas meter units to kWh, the way a GB bill does it: volume (x 2.83 for an imperial meter) x 1.02264 x calorific value / 3.6, rounded once, half-up, to the watt-hour. """ if not _is_int(volume) or volume < 0 or volume > MAX_VOLUME: raise ValueError("volume must be a whole number of units from 0 to %d, received %s" % (MAX_VOLUME, volume)) if not _is_int(calorific_value) or calorific_value < 1 or calorific_value > 100000: raise ValueError( "calorific value must be a whole number of thousandths of a MJ/m³ from 1 to 100000, received %s" % (calorific_value,) ) if volume_unit == "cubic-metres": hundredths = volume * 100 elif volume_unit == "hundreds-cubic-feet": hundredths = volume * 283 else: raise ValueError('unknown volume unit "%s": expected cubic-metres or hundreds-cubic-feet' % (volume_unit,)) scaled = FACTOR * calorific_value whole, rest = divmod(hundredths, DIVISOR) watt_hours = whole * scaled + round_div(rest * scaled, DIVISOR, "half-up") if watt_hours > MAX_SAFE: raise ValueError("gas energy too large to calculate exactly") return GasEnergy(cubic_metres_hundredths=hundredths, watt_hours=watt_hours)