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