Functional Weave
Code in Rust

education.attendance-rate@1.0.1

impl/typescript.ts

2,332 bytes · the TypeScript 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.

import { parseIsoDate } from "./dates_add_days.ts";  ← from dates.add-days ^1.0.0 · built alongside by fune
import { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { ABSENCE_THRESHOLDS, ABSENCE_THRESHOLDS_HISTORY, ABSENCE_THRESHOLDS_HORIZON } from "./education_attendance_rate_data.ts";  ← this capability’s own data, compiled from data/absence-thresholds.json into the same file by fune build
import { type AttendanceRate } from "./education_attendance_rate_types.ts";

function thresholdOn(measure: string, onDate: string): number | null {
  for (const r of ABSENCE_THRESHOLDS) {
    if (r.measure === measure && onDate >= r.validFrom && (r.validTo === null || onDate <= r.validTo)) return r.basisPoints;
  }
  return null;
}

/**
 * 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.
 */
export function attendanceRate(possibleSessions: number, missedSessions: number, onDate: string): AttendanceRate {
  if (!Number.isInteger(possibleSessions) || possibleSessions < 1) {
    throw new RangeError(`possibleSessions must be a whole number of 1 or more, received ${possibleSessions}`);
  }
  if (!Number.isInteger(missedSessions) || missedSessions < 0 || missedSessions > possibleSessions) {
    throw new RangeError(`missedSessions must be a whole number from 0 to possibleSessions (${possibleSessions}), received ${missedSessions}`);
  }
  parseIsoDate(onDate);
  const persistent = thresholdOn("persistent", onDate);
  if (persistent === null) {
    if (ABSENCE_THRESHOLDS_HISTORY !== "full" && ABSENCE_THRESHOLDS_HORIZON !== null && onDate < ABSENCE_THRESHOLDS_HORIZON) {
      throw new RangeError(
        `no persistent absence definition on ${onDate}: this build was installed with history=${ABSENCE_THRESHOLDS_HISTORY}, so it only carries rules from ${ABSENCE_THRESHOLDS_HORIZON}. Reinstall with history=full for earlier years.`,
      );
    }
    throw new RangeError(`no persistent absence definition on ${onDate}`);
  }
  const severe = thresholdOn("severe", onDate);
  const absence = roundDiv(missedSessions * 10000, possibleSessions, "half-up");
  return {
    attendanceBasisPoints: 10000 - absence,
    absenceBasisPoints: absence,
    persistentlyAbsent: missedSessions * 10000 >= possibleSessions * persistent,
    severelyAbsent: severe === null ? null : missedSessions * 10000 >= possibleSessions * severe,
  };
}