Functional Weave
Code in Rust

education.weighted-grade@1.0.0

impl/python.py

2,096 bytes · the Python 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.

from typing import Sequence

from .education_weighted_grade_types import GradeComponent, WeightedGrade
from .math_rational import add_rational, multiply_rational, rational, rational_to_integer  ← from math.rational ^1.0.0 · built alongside by fune
from .math_round_div import RoundingMode  ← from math.round-div ^1.0.0 · built alongside by fune


def _is_int(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool)


def weighted_grade(components: Sequence[GradeComponent], decimals: int, mode: RoundingMode) -> WeightedGrade:
    """Weighted percentage across components, summed exactly and rounded once.

    Each component adds mark x weight / (out_of x 100) percent: mark/out_of of
    the component, times weight/10000 of the whole, times 100 for a percentage.
    """
    if len(components) == 0:
        raise ValueError("components must not be empty")
    if not _is_int(decimals) or decimals < 0 or decimals > 6:
        raise ValueError("decimals must be a whole number from 0 to 6, received %s" % (decimals,))
    total = rational(0, 1)
    weights = 0
    for c in components:
        if not _is_int(c.out_of) or c.out_of < 1:
            raise ValueError("outOf must be a whole number of 1 or more, received %s for %s" % (c.out_of, c.name))
        if not _is_int(c.mark) or c.mark < 0 or c.mark > c.out_of:
            raise ValueError(
                "mark must be a whole number from 0 to outOf (%s), received %s for %s" % (c.out_of, c.mark, c.name)
            )
        if not _is_int(c.weight_basis_points) or c.weight_basis_points < 0:
            raise ValueError(
                "weightBasisPoints must be a whole number of 0 or more, received %s for %s"
                % (c.weight_basis_points, c.name)
            )
        weights += c.weight_basis_points
        total = add_rational(total, rational(c.mark * c.weight_basis_points, c.out_of * 100))
    if weights != 10000:
        raise ValueError("weights must add up to 10000 basis points (100%%), received %s" % (weights,))
    scaled = rational_to_integer(multiply_rational(total, rational(10**decimals, 1)), mode)
    return WeightedGrade(percent=total, scaled=scaled, decimals=decimals)