Functional Weave
Code in Python

education.attendance-rate@1.0.0

impl/rust.rs

3,537 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_add_days::parse_iso_date;  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::education_attendance_rate_data::{
    ABSENCE_THRESHOLDS, ABSENCE_THRESHOLDS_HISTORY, ABSENCE_THRESHOLDS_HORIZON,
};
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune

fn threshold_on(measure: &str, on_date: &str) -> Option<i64> {
    ABSENCE_THRESHOLDS
        .iter()
        .find(|r| r.measure == measure && on_date >= r.valid_from && r.valid_to.map_or(true, |to| on_date <= to))
        .map(|r| r.basis_points)
}

/// Attendance and absence in basis points, and the DfE absence flags.
///
/// The flags compare missed x 10000 with possible x threshold, exactly: a
/// rate of 9.996% rounds to 10.00% but is not persistent absence.
///
/// # Panics
/// Panics on no possible sessions, missed sessions outside 0 to possible, a
/// malformed date, or a date before the persistent absence definition.
pub fn attendance_rate(possible_sessions: i64, missed_sessions: i64, on_date: &str) -> AttendanceRate {
    if possible_sessions < 1 {
        panic!("possibleSessions must be a whole number of 1 or more, received {}", possible_sessions);
    }
    if missed_sessions < 0 || missed_sessions > possible_sessions {
        panic!(
            "missedSessions must be a whole number from 0 to possibleSessions ({}), received {}",
            possible_sessions, missed_sessions
        );
    }
    parse_iso_date(on_date);
    let persistent = match threshold_on("persistent", on_date) {
        Some(bp) => bp,
        None => {
            if let Some(horizon) = ABSENCE_THRESHOLDS_HORIZON {
                if ABSENCE_THRESHOLDS_HISTORY != "full" && on_date < horizon {
                    panic!(
                        "no persistent absence definition on {}: this build was installed with history={}, so it only carries rules from {}. Reinstall with history=full for earlier years.",
                        on_date, ABSENCE_THRESHOLDS_HISTORY, horizon
                    );
                }
            }
            panic!("no persistent absence definition on {}", on_date)
        }
    };
    let severe = threshold_on("severe", on_date);
    let missed = missed_sessions as i128 * 10000;
    let possible = possible_sessions as i128;
    let absence = round_div(missed_sessions * 10000, possible_sessions, "half-up");
    AttendanceRate {
        attendance_basis_points: 10000 - absence,
        absence_basis_points: absence,
        persistently_absent: missed >= possible * persistent as i128,
        severely_absent: severe.map(|bp| missed >= possible * bp as i128),
    }
}

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 attendance_rate_to_value(r: &AttendanceRate) -> Value {
    Value::obj(vec![
        ("attendanceBasisPoints", Value::Int(r.attendance_basis_points)),
        ("absenceBasisPoints", Value::Int(r.absence_basis_points)),
        ("persistentlyAbsent", Value::Bool(r.persistently_absent)),
        ("severelyAbsent", match r.severely_absent { Some(b) => Value::Bool(b), None => Value::Null }),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let possible = whole(&args[0], "possibleSessions must be a whole number of 1 or more");
    let missed = whole(
        &args[1],
        &format!("missedSessions must be a whole number from 0 to possibleSessions ({})", possible),
    );
    attendance_rate_to_value(&attendance_rate(possible, missed, args[2].as_str()))
}