Functional Weave
Code in Python

education.weighted-grade@1.0.0

README.md

1,761 bytes · view raw

# education.weighted-grade

The overall percentage for a module or course made of weighted components:
coursework worth 40% marked out of 60, an exam worth 60% marked out of 120,
and so on. Each component contributes `mark / outOf x weight`, the sum is
kept as an exact fraction (`percent`), and it is rounded once, at the end, to
the places and rounding mode the caller names (`scaled`).

## Why exact, then one rounding

Component percentages are usually repeating decimals (45 out of 60 is fine;
88 out of 120 is 73.333...). Adding them as floats can land a hair under a
.5 and round the wrong way: 10/30 at 50% plus 29/300 at 50% is exactly 21.5,
which floats compute as 21.4999999..., so a half-up round to whole marks
gives 21 instead of 22. A vector pins that case. Rounding each component
before adding is a second, commoner source of drift and is not done here.

## Decisions

- **Weights are basis points and must total 10000.** A table that adds up to
  99.99% is almost always a typo, so it is refused rather than normalised.
  Equal thirds are 3333, 3333, 3334: say which component carries the extra
  basis point.
- A weight of 0 is allowed (a formative piece recorded alongside), and adds
  nothing.
- `scaled` is an integer so no float ever carries the answer: 74.3% at one
  decimal place is `743`. Grade the student with `education.grade-boundaries`
  on the scaled value if your boundaries are in percent.
- Components not yet sat, capped resits and compensation rules are the
  institution's regulations and are out of scope: pass only marks that count.

## Errors

An empty list, a mark outside 0 to `outOf`, `outOf` below 1, a negative
weight, weights not totalling 10000, `decimals` outside 0 to 6, or an unknown
rounding mode all raise.