education.weighted-grade@1.0.0
impl/typescript.ts
1,967 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 Rational, addRational, multiplyRational, rational, rationalToInteger } from "./math_rational.ts"; ← from math.rational ^1.0.0 · built alongside by fune
import { type RoundingMode } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type GradeComponent, type WeightedGrade } from "./education_weighted_grade_types.ts";
/**
* Weighted percentage across components, summed exactly and rounded once.
*
* Each component adds mark x weight / (outOf x 100) percent: mark/outOf of the
* component, times weight/10000 of the whole, times 100 for a percentage.
*/
export function weightedGrade(components: readonly GradeComponent[], decimals: number, mode: RoundingMode): WeightedGrade {
if (components.length === 0) {
throw new RangeError("components must not be empty");
}
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 6) {
throw new RangeError(`decimals must be a whole number from 0 to 6, received ${decimals}`);
}
let total: Rational = rational(0, 1);
let weights = 0;
for (const c of components) {
if (!Number.isInteger(c.outOf) || c.outOf < 1) {
throw new RangeError(`outOf must be a whole number of 1 or more, received ${c.outOf} for ${c.name}`);
}
if (!Number.isInteger(c.mark) || c.mark < 0 || c.mark > c.outOf) {
throw new RangeError(`mark must be a whole number from 0 to outOf (${c.outOf}), received ${c.mark} for ${c.name}`);
}
if (!Number.isInteger(c.weightBasisPoints) || c.weightBasisPoints < 0) {
throw new RangeError(`weightBasisPoints must be a whole number of 0 or more, received ${c.weightBasisPoints} for ${c.name}`);
}
weights += c.weightBasisPoints;
total = addRational(total, rational(c.mark * c.weightBasisPoints, c.outOf * 100));
}
if (weights !== 10000) {
throw new RangeError(`weights must add up to 10000 basis points (100%), received ${weights}`);
}
const scaled = rationalToInteger(multiplyRational(total, rational(10 ** decimals, 1)), mode);
return { percent: total, scaled, decimals };
}