use super::funejson::Value; use super::dates_days_between::days_between; use super::energy_meter_advance::meter_advance; use super::math_round_div::round_div; 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(), )) }