Functional Weave
Code in Python

energy.climate-change-levy@1.0.1

impl/rust.rs

4,614 bytes · the Rust 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.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::energy_climate_change_levy_data::{CCL_RATES, CCL_RATES_HISTORY};  ← this capability’s own data, compiled from data/ccl-rates.json into the same file by fune build
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_to_value};  ← from money.amount ^1.0.0 · built alongside by fune

// quantity (thousandths of a unit) x rate (thousandths of a penny) is in
// millionths of a penny.
const DIVISOR: i64 = 1_000_000;
const MAX_SAFE: i128 = 9_007_199_254_740_991;

fn is_iso_date(value: &str) -> bool {
    let bytes = value.as_bytes();
    bytes.len() == 10
        && bytes[4] == b'-'
        && bytes[7] == b'-'
        && bytes.iter().enumerate().all(|(i, b)| i == 4 || i == 7 || b.is_ascii_digit())
}

/// 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.
///
/// # Panics
/// Panics on a malformed date, a negative quantity, an unknown commodity, a
/// date no rate covers, or a levy too large to be exact in every language.
pub fn climate_change_levy(
    commodity: &str,
    quantity: i64,
    on_date: &str,
    qualifying_use: bool,
    mode: &str,
) -> ClimateChangeLevy {
    if !is_iso_date(on_date) {
        panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
    }
    if quantity < 0 {
        panic!(
            "quantity must be a whole number of thousandths of a unit, 0 or more, received {}",
            quantity
        );
    }
    if !CCL_RATES.iter().any(|r| r.commodity == commodity) {
        panic!("unknown commodity \"{}\": expected electricity, gas, lpg or other", commodity);
    }
    let mut best = None;
    for r in CCL_RATES {
        if r.commodity != commodity || on_date < r.valid_from {
            continue;
        }
        if let Some(to) = r.valid_to {
            if on_date > to {
                continue;
            }
        }
        if best.map_or(true, |b: &super::energy_climate_change_levy_data::CclRate| r.valid_from > b.valid_from) {
            best = Some(r);
        }
    }
    let best = match best {
        Some(r) => r,
        None => {
            // A pruned build must not answer an old date with a newer rate.
            if CCL_RATES_HISTORY != "full" {
                let mut earliest: Option<&'static str> = None;
                for r in CCL_RATES {
                    if r.commodity == commodity && earliest.map_or(true, |e| r.valid_from < e) {
                        earliest = Some(r.valid_from);
                    }
                }
                if let Some(earliest) = earliest {
                    if on_date < earliest {
                        panic!(
                            "no Climate Change Levy rate for {} on {}: this build was installed with history={}, so it only carries rates from {}. Reinstall with history=full for older supplies.",
                            commodity, on_date, CCL_RATES_HISTORY, earliest
                        );
                    }
                }
            }
            panic!("no Climate Change Levy rate for {} on {}", commodity, on_date)
        }
    };
    let mut pence = 0;
    if !qualifying_use {
        let product = quantity as i128 * best.rate as i128;
        if product > MAX_SAFE {
            panic!("levy too large to calculate exactly");
        }
        pence = round_div(product as i64, DIVISOR, mode);
    }
    ClimateChangeLevy {
        commodity: commodity.to_string(),
        unit: best.unit.to_string(),
        rate: best.rate,
        excluded: qualifying_use,
        levy: money(pence, "GBP"),
        valid_from: best.valid_from.to_string(),
        valid_to: best.valid_to.map(str::to_string),
    }
}

pub fn climate_change_levy_to_value(c: &ClimateChangeLevy) -> Value {
    Value::obj(vec![
        ("commodity", Value::str(&c.commodity)),
        ("unit", Value::str(&c.unit)),
        ("rate", Value::Int(c.rate)),
        ("excluded", Value::Bool(c.excluded)),
        ("levy", money_to_value(&c.levy)),
        ("validFrom", Value::str(&c.valid_from)),
        (
            "validTo",
            match &c.valid_to {
                Some(d) => Value::str(d),
                None => Value::Null,
            },
        ),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    if let Value::Float(f) = &args[1] {
        panic!("quantity must be a whole number of thousandths of a unit, 0 or more, received {}", f);
    }
    climate_change_levy_to_value(&climate_change_levy(
        args[0].as_str(),
        args[1].as_i64(),
        args[2].as_str(),
        args[3].as_bool(),
        args[4].as_str(),
    ))
}