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(),
))
}