Functional Weave
Code in TypeScript

education.gpa@1.0.0

impl/typescript.ts

2,133 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 { type RoundingMode, roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { DEFAULT_GRADE_POINTS } from "./education_gpa_data.ts";  ← this capability’s own data, compiled from data/default-grade-points.json into the same file by fune build
import { type GpaCourse, type GpaResult, type GradePoint } from "./education_gpa_types.ts";

/**
 * Credit-weighted grade point average in hundredths, rounded once.
 *
 * Quality points are summed exactly in integers (hundredths x credits), so the
 * rounding mode the caller names is the only rounding that happens.
 */
export function gpa(courses: readonly GpaCourse[], gradePoints: readonly GradePoint[] | null, mode: RoundingMode): GpaResult {
  const table: readonly GradePoint[] = gradePoints === null ? DEFAULT_GRADE_POINTS : gradePoints;
  if (table.length === 0) {
    throw new RangeError("gradePoints must not be empty; pass null for the default table");
  }
  const points = new Map<string, number | null>();
  for (const row of table) {
    if (points.has(row.grade)) {
      throw new RangeError(`grade "${row.grade}" appears twice in gradePoints`);
    }
    if (row.points !== null && (!Number.isInteger(row.points) || row.points < 0)) {
      throw new RangeError(`points must be whole hundredths of 0 or more, received ${row.points} for grade "${row.grade}"`);
    }
    points.set(row.grade, row.points);
  }

  let qualityPoints = 0;
  let gpaCredits = 0;
  let excludedCredits = 0;
  for (const course of courses) {
    if (!Number.isInteger(course.credits) || course.credits < 0) {
      throw new RangeError(`credits must be a whole number of 0 or more, received ${course.credits}`);
    }
    if (!points.has(course.grade)) {
      throw new RangeError(`grade "${course.grade}" is not in the grade-point table`);
    }
    const p = points.get(course.grade) as number | null;
    if (p === null) {
      excludedCredits += course.credits;
    } else {
      qualityPoints += p * course.credits;
      gpaCredits += course.credits;
    }
  }
  // Validate the mode even when there is nothing to divide.
  roundDiv(0, 1, mode);
  return {
    gpa: gpaCredits === 0 ? null : roundDiv(qualityPoints, gpaCredits, mode),
    qualityPoints,
    gpaCredits,
    excludedCredits,
  };
}