Functional Weave
Code in Python

fleet.emissions@1.0.1

impl/typescript.ts

2,804 bytes · the TypeScript 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 { GHG_FACTORS } from "./fleet_emissions_data.ts";  ← this capability’s own data, compiled from data/ghg-factors.json into the same file by fune build
import { type EmissionsUnit, type FleetEmissions } from "./fleet_emissions_types.ts";

const DECIMAL = /^(\d+)(?:\.(\d+))?$/;
const MAX_SAFE = 9007199254740991n;

function formatFactor(factor: number): string {
  const whole = Math.floor(factor / 100000);
  const fraction = String(factor % 100000).padStart(5, "0");
  return `${whole}.${fraction}`;
}

/**
 * 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.
 */
export function fleetEmissions(activity: string, quantity: string, unit: EmissionsUnit, factorYear: number): FleetEmissions {
  if (unit !== "litres" && unit !== "km" && unit !== "miles") {
    throw new RangeError(`unknown unit "${unit}": use litres, km or miles`);
  }
  if (!Number.isInteger(factorYear)) {
    throw new RangeError(`factorYear must be a whole year, received ${factorYear}`);
  }
  const match = typeof quantity === "string" ? DECIMAL.exec(quantity) : null;
  if (match === null) {
    throw new RangeError(`quantity must be plain decimal text, not negative, received "${quantity}"`);
  }
  const fraction = match[2] ?? "";
  const digits = (match[1] + fraction).replace(/^0+/, "");
  if (digits.length > 15 || fraction.length > 9) {
    throw new RangeError(`quantity must have at most 15 significant digits and 9 decimal places, received "${quantity}"`);
  }

  let found: number | null = null;
  let yearKnown = false;
  let activityKnown = false;
  for (const row of GHG_FACTORS) {
    if (row.factorYear !== factorYear) continue;
    yearKnown = true;
    if (row.activity !== activity) continue;
    activityKnown = true;
    if (row.unit === unit) found = row.factor;
  }
  if (!yearKnown) {
    throw new RangeError(`no conversion factors for ${factorYear}: this package carries 2023 to 2026`);
  }
  if (!activityKnown) {
    throw new RangeError(`unknown activity "${activity}" in the ${factorYear} factors`);
  }
  if (found === null) {
    throw new RangeError(`no conversion factor for ${activity} per ${unit} in ${factorYear}`);
  }

  // grams = quantity x factor x 1e-5 kg x 1000 g/kg = quantity x factor / 100
  const numerator = BigInt(match[1] + fraction) * BigInt(found);
  const denominator = 10n ** BigInt(fraction.length) * 100n;
  const grams = (2n * numerator + denominator) / (2n * denominator);
  if (grams > MAX_SAFE) {
    throw new RangeError("the result exceeds 2^53 - 1 grams");
  }
  return { co2eGrams: Number(grams), factor: formatFactor(found), factorYear, activity, unit };
}