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::math_rational::{add_rational, multiply_rational, rational, rational_to_integer, rational_to_value}; ← from math.rational ^1.0.0 · built alongside by fune
/// Weighted percentage across components, summed exactly and rounded once.
///
/// Each component adds mark x weight / (out_of x 100) percent: mark/out_of of
/// the component, times weight/10000 of the whole, times 100 for a percentage.
///
/// # Panics
/// Panics on an empty list, a mark outside 0 to out_of, weights that do not
/// total 10000, decimals outside 0 to 6, or an unknown rounding mode.
pub fn weighted_grade(components: &[GradeComponent], decimals: i64, mode: &str) -> WeightedGrade {
if components.is_empty() {
panic!("components must not be empty");
}
if !(0..=6).contains(&decimals) {
panic!("decimals must be a whole number from 0 to 6, received {}", decimals);
}
let mut total = rational(0, 1);
let mut weights: i64 = 0;
for c in components {
if c.out_of < 1 {
panic!("outOf must be a whole number of 1 or more, received {} for {}", c.out_of, c.name);
}
if c.mark < 0 || c.mark > c.out_of {
panic!(
"mark must be a whole number from 0 to outOf ({}), received {} for {}",
c.out_of, c.mark, c.name
);
}
if c.weight_basis_points < 0 {
panic!(
"weightBasisPoints must be a whole number of 0 or more, received {} for {}",
c.weight_basis_points, c.name
);
}
weights += c.weight_basis_points;
total = add_rational(&total, &rational(c.mark * c.weight_basis_points, c.out_of * 100));
}
if weights != 10000 {
panic!("weights must add up to 10000 basis points (100%), received {}", weights);
}
let scaled = rational_to_integer(&multiply_rational(&total, &rational(10i64.pow(decimals as u32), 1)), mode);
WeightedGrade { percent: total, scaled, decimals }
}
fn whole(value: &Value, message: &str) -> i64 {
match value {
Value::Float(f) if f.fract() != 0.0 => panic!("{}, received {}", message, f),
_ => value.as_i64(),
}
}
pub fn weighted_grade_to_value(g: &WeightedGrade) -> Value {
Value::obj(vec![
("percent", rational_to_value(&g.percent)),
("scaled", Value::Int(g.scaled)),
("decimals", Value::Int(g.decimals)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let components: Vec<GradeComponent> = args[0]
.as_arr()
.iter()
.map(|v| {
let out_of = whole(v.get("outOf"), "outOf must be a whole number of 1 or more");
GradeComponent {
name: v.get("name").as_str().to_string(),
mark: whole(v.get("mark"), &format!("mark must be a whole number from 0 to outOf ({})", out_of)),
out_of,
weight_basis_points: whole(v.get("weightBasisPoints"), "weightBasisPoints must be a whole number of 0 or more"),
}
})
.collect();
let decimals = whole(&args[1], "decimals must be a whole number from 0 to 6");
weighted_grade_to_value(&weighted_grade(&components, decimals, args[2].as_str()))
}