Functional Weave
Code in Rust

energy.water-bill@1.0.0

impl/rust.rs

5,152 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::dates_days_between::days_between;  ← from dates.days-between ^1.0.0 · built alongside by fune
use super::energy_meter_advance::meter_advance;  ← from energy.meter-advance ^1.0.0 · built alongside by fune
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

const MAX_SAFE: i128 = 9_007_199_254_740_991;

fn check_rate(what: &str, value: i64) {
    if value < 0 {
        panic!("{} must be a whole number of 0 or more, received {}", what, value);
    }
}

fn exact(product: i128) -> i64 {
    if product > MAX_SAFE {
        panic!("water charge too large to calculate exactly");
    }
    product as i64
}

/// A metered water and sewerage bill: volumetric water, sewerage on the share
/// returned to the sewer, and standing and surface water charges apportioned
/// by days / 365, each line rounded once.
///
/// # Panics
/// Panics on reads that do not fit the register, reads in the wrong order, a
/// negative charge, a return-to-sewer share outside 0-100%, or a charge too
/// large to be exact.
pub fn water_bill(
    opening_read: &MeterRead,
    closing_read: &MeterRead,
    meter_digits: i64,
    tariff: &WaterTariff,
    surface_water_drains: bool,
    currency: &str,
    mode: &str,
) -> WaterBill {
    let cubic_metres = meter_advance(opening_read.value, closing_read.value, meter_digits);
    let days = days_between(&opening_read.date, &closing_read.date);
    if days < 0 {
        panic!(
            "the closing read must not be dated before the opening read, received {} and {}",
            opening_read.date, closing_read.date
        );
    }
    check_rate("waterVolumeRate", tariff.water_volume_rate);
    check_rate("sewerageVolumeRate", tariff.sewerage_volume_rate);
    check_rate("waterStandingCharge", tariff.water_standing_charge);
    check_rate("sewerageStandingCharge", tariff.sewerage_standing_charge);
    check_rate("surfaceWaterCharge", tariff.surface_water_charge);
    if tariff.return_to_sewer < 0 || tariff.return_to_sewer > 10000 {
        panic!("returnToSewer must be basis points from 0 to 10000, received {}", tariff.return_to_sewer);
    }
    let m3 = cubic_metres as i128;
    let d = days as i128;
    let water = round_div(exact(m3 * tariff.water_volume_rate as i128), 1000, mode);
    let sewerage = round_div(
        exact(m3 * tariff.sewerage_volume_rate as i128 * tariff.return_to_sewer as i128),
        10_000_000,
        mode,
    );
    let water_standing = round_div(exact(tariff.water_standing_charge as i128 * d), 365, mode);
    let sewerage_standing = round_div(exact(tariff.sewerage_standing_charge as i128 * d), 365, mode);
    let surface = if surface_water_drains {
        round_div(exact(tariff.surface_water_charge as i128 * d), 365, mode)
    } else {
        0
    };
    WaterBill {
        cubic_metres,
        days,
        water_volume_charge: money(water, currency),
        sewerage_volume_charge: money(sewerage, currency),
        water_standing_charge: money(water_standing, currency),
        sewerage_standing_charge: money(sewerage_standing, currency),
        surface_water_charge: money(surface, currency),
        total: money(water + sewerage + water_standing + sewerage_standing + surface, currency),
    }
}

fn whole(v: &Value, what: &str) -> i64 {
    if let Value::Float(f) = v {
        panic!("{} must be a whole number of 0 or more, received {}", what, f);
    }
    v.as_i64()
}

pub fn meter_read_from_value(v: &Value) -> MeterRead {
    if let Value::Float(f) = v.get("value") {
        panic!("reads must be whole numbers, received {}", f);
    }
    MeterRead {
        date: v.get("date").as_str().to_string(),
        value: v.get("value").as_i64(),
    }
}

pub fn water_tariff_from_value(v: &Value) -> WaterTariff {
    WaterTariff {
        water_volume_rate: whole(v.get("waterVolumeRate"), "waterVolumeRate"),
        sewerage_volume_rate: whole(v.get("sewerageVolumeRate"), "sewerageVolumeRate"),
        water_standing_charge: whole(v.get("waterStandingCharge"), "waterStandingCharge"),
        sewerage_standing_charge: whole(v.get("sewerageStandingCharge"), "sewerageStandingCharge"),
        surface_water_charge: whole(v.get("surfaceWaterCharge"), "surfaceWaterCharge"),
        return_to_sewer: whole(v.get("returnToSewer"), "returnToSewer"),
    }
}

pub fn water_bill_to_value(b: &WaterBill) -> Value {
    Value::obj(vec![
        ("cubicMetres", Value::Int(b.cubic_metres)),
        ("days", Value::Int(b.days)),
        ("waterVolumeCharge", money_to_value(&b.water_volume_charge)),
        ("sewerageVolumeCharge", money_to_value(&b.sewerage_volume_charge)),
        ("waterStandingCharge", money_to_value(&b.water_standing_charge)),
        ("sewerageStandingCharge", money_to_value(&b.sewerage_standing_charge)),
        ("surfaceWaterCharge", money_to_value(&b.surface_water_charge)),
        ("total", money_to_value(&b.total)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    water_bill_to_value(&water_bill(
        &meter_read_from_value(&args[0]),
        &meter_read_from_value(&args[1]),
        args[2].as_i64(),
        &water_tariff_from_value(&args[3]),
        args[4].as_bool(),
        args[5].as_str(),
        args[6].as_str(),
    ))
}