use super::funejson::Value; use super::energy_price_cap_data::{PRICE_CAP, PRICE_CAP_HISTORY}; 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 Ofgem price cap unit rate and standing charge in force on `on_date`. /// /// # Panics /// Panics on a malformed date, an unknown fuel or payment method, a payment /// method with no figures, or a date outside every period carried. pub fn price_cap(fuel: &str, payment_method: &str, on_date: &str) -> PriceCap { if !is_iso_date(on_date) { panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date); } if fuel != "electricity" && fuel != "gas" { panic!("unknown fuel \"{}\": expected electricity or gas", fuel); } if !["direct-debit", "standard-credit", "prepayment"].contains(&payment_method) { panic!( "unknown payment method \"{}\": expected direct-debit, standard-credit or prepayment", payment_method ); } let rows: Vec<_> = PRICE_CAP .iter() .filter(|r| r.fuel == fuel && r.payment_method == payment_method) .collect(); if rows.is_empty() { panic!( "no price cap figures for {} paid by {}: this version carries GB-average Direct Debit figures only", fuel, payment_method ); } let row = match rows.iter().find(|r| r.valid_from <= on_date && on_date <= r.valid_to) { Some(row) => row, None => { // A pruned build must not answer an old date with a newer cap. if PRICE_CAP_HISTORY != "full" { let earliest = rows.iter().map(|r| r.valid_from).min().unwrap(); if on_date < earliest { panic!( "no price cap for {}/{} on {}: this build was installed with history={}, so it only carries periods from {}. Reinstall with history=full for older dates.", fuel, payment_method, on_date, PRICE_CAP_HISTORY, earliest ); } } panic!("no price cap for {}/{} on {}", fuel, payment_method, on_date) } }; PriceCap { fuel: fuel.to_string(), payment_method: payment_method.to_string(), region: row.region.to_string(), unit_rate: row.unit_rate, standing_charge: row.standing_charge, vat_basis_points: row.vat_basis_points, valid_from: row.valid_from.to_string(), valid_to: row.valid_to.to_string(), } } pub fn price_cap_to_value(p: &PriceCap) -> Value { Value::obj(vec![ ("fuel", Value::str(&p.fuel)), ("paymentMethod", Value::str(&p.payment_method)), ("region", Value::str(&p.region)), ("unitRate", Value::Int(p.unit_rate)), ("standingCharge", Value::Int(p.standing_charge)), ("vatBasisPoints", Value::Int(p.vat_basis_points)), ("validFrom", Value::str(&p.valid_from)), ("validTo", Value::str(&p.valid_to)), ]) } pub fn fune_vector(args: &[Value]) -> Value { price_cap_to_value(&price_cap(args[0].as_str(), args[1].as_str(), args[2].as_str())) }