Functional Weave
Code in Rust

energy.estimate-read@1.0.0

impl/rust.rs

2,414 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

const MAX_SAFE: i128 = 9_007_199_254_740_991;

/// Carry the latest read forward to `on_date` at the average daily
/// consumption between the two reads, rounding once, and wrap past all nines.
///
/// # Panics
/// Panics on reads that do not fit the register, reads in the wrong order, a
/// target date before the latest read, or an estimate too large to be exact.
pub fn estimate_read(
    earlier_read: &MeterRead,
    latest_read: &MeterRead,
    on_date: &str,
    digits: i64,
    mode: &str,
) -> EstimatedRead {
    let consumption = meter_advance(earlier_read.value, latest_read.value, digits);
    let history = days_between(&earlier_read.date, &latest_read.date);
    if history <= 0 {
        panic!(
            "the earlier read must be dated before the latest read, received {} and {}",
            earlier_read.date, latest_read.date
        );
    }
    let ahead = days_between(&latest_read.date, on_date);
    if ahead < 0 {
        panic!(
            "onDate must not be before the latest read, received {} and {}",
            on_date, latest_read.date
        );
    }
    let product = consumption as i128 * ahead as i128;
    if product > MAX_SAFE {
        panic!("estimate too large to calculate exactly");
    }
    let units = round_div(product as i64, history, mode);
    let span = 10_i64.pow(digits as u32);
    let raw = latest_read.value + units;
    EstimatedRead {
        read: raw % span,
        units,
        rolled_over: raw >= span,
    }
}

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 estimated_read_to_value(e: &EstimatedRead) -> Value {
    Value::obj(vec![
        ("read", Value::Int(e.read)),
        ("units", Value::Int(e.units)),
        ("rolledOver", Value::Bool(e.rolled_over)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    estimated_read_to_value(&estimate_read(
        &meter_read_from_value(&args[0]),
        &meter_read_from_value(&args[1]),
        args[2].as_str(),
        args[3].as_i64(),
        args[4].as_str(),
    ))
}