Functional Weave
Code in Python

fleet.emissions@1.0.1

impl/python.py

2,788 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import re
from typing import Optional

from .fleet_emissions_data import GHG_FACTORS  ← this capability’s own data, compiled from data/ghg-factors.json into the same file by fune build
from .fleet_emissions_types import EmissionsUnit, FleetEmissions

DECIMAL = re.compile(r"([0-9]+)(?:\.([0-9]+))?")
MAX_SAFE = 9007199254740991


def _format_factor(factor: int) -> str:
    return "%d.%05d" % (factor // 100000, factor % 100000)


def fleet_emissions(activity: str, quantity: str, unit: EmissionsUnit, factor_year: int) -> FleetEmissions:
    """Scope 1 emissions for fuel burned or distance driven, from the UK
    government's conversion factors for company reporting.

    The quantity is decimal text and the factor an integer in 1e-5 kg, so the
    product is exact and rounded once, to the gram. Multiplying floats instead
    drifts in the last place and can round a half-gram the wrong way.
    """
    if unit not in ("litres", "km", "miles"):
        raise ValueError('unknown unit "%s": use litres, km or miles' % (unit,))
    if isinstance(factor_year, bool) or not isinstance(factor_year, int):
        raise ValueError("factorYear must be a whole year, received %s" % (factor_year,))
    match = DECIMAL.fullmatch(quantity) if isinstance(quantity, str) else None
    if match is None:
        raise ValueError('quantity must be plain decimal text, not negative, received "%s"' % (quantity,))
    fraction = match.group(2) or ""
    digits = (match.group(1) + fraction).lstrip("0")
    if len(digits) > 15 or len(fraction) > 9:
        raise ValueError(
            'quantity must have at most 15 significant digits and 9 decimal places, received "%s"' % (quantity,)
        )

    found: Optional[int] = None
    year_known = False
    activity_known = False
    for row in GHG_FACTORS:
        if row.factor_year != factor_year:
            continue
        year_known = True
        if row.activity != activity:
            continue
        activity_known = True
        if row.unit == unit:
            found = row.factor
    if not year_known:
        raise ValueError("no conversion factors for %d: this package carries 2023 to 2026" % (factor_year,))
    if not activity_known:
        raise ValueError('unknown activity "%s" in the %d factors' % (activity, factor_year))
    if found is None:
        raise ValueError("no conversion factor for %s per %s in %d" % (activity, unit, factor_year))

    # grams = quantity x factor x 1e-5 kg x 1000 g/kg = quantity x factor / 100
    numerator = int(match.group(1) + fraction) * found
    denominator = 10 ** len(fraction) * 100
    grams = (2 * numerator + denominator) // (2 * denominator)
    if grams > MAX_SAFE:
        raise ValueError("the result exceeds 2^53 - 1 grams")
    return FleetEmissions(
        co2e_grams=grams, factor=_format_factor(found), factor_year=factor_year, activity=activity, unit=unit
    )