Functional Weave
Code in TypeScript

education.gpa@1.0.0

impl/python.py

2,253 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 Dict, Optional, Sequence

from .education_gpa_data import DEFAULT_GRADE_POINTS  ← this capability’s own data, compiled from data/default-grade-points.json into the same file by fune build
from .education_gpa_types import GpaCourse, GpaResult, GradePoint
from .math_round_div import RoundingMode, round_div  ← 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 gpa(courses: Sequence[GpaCourse], grade_points: Optional[Sequence[GradePoint]], mode: RoundingMode) -> GpaResult:
    """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.
    """
    table = DEFAULT_GRADE_POINTS if grade_points is None else grade_points
    if len(table) == 0:
        raise ValueError("gradePoints must not be empty; pass null for the default table")
    points: Dict[str, Optional[int]] = {}
    for row in table:
        if row.grade in points:
            raise ValueError('grade "%s" appears twice in gradePoints' % (row.grade,))
        if row.points is not None and (not _is_int(row.points) or row.points < 0):
            raise ValueError(
                'points must be whole hundredths of 0 or more, received %s for grade "%s"' % (row.points, row.grade)
            )
        points[row.grade] = row.points

    quality_points = 0
    gpa_credits = 0
    excluded_credits = 0
    for course in courses:
        if not _is_int(course.credits) or course.credits < 0:
            raise ValueError("credits must be a whole number of 0 or more, received %s" % (course.credits,))
        if course.grade not in points:
            raise ValueError('grade "%s" is not in the grade-point table' % (course.grade,))
        p = points[course.grade]
        if p is None:
            excluded_credits += course.credits
        else:
            quality_points += p * course.credits
            gpa_credits += course.credits
    # Validate the mode even when there is nothing to divide.
    round_div(0, 1, mode)
    return GpaResult(
        gpa=None if gpa_credits == 0 else round_div(quality_points, gpa_credits, mode),
        quality_points=quality_points,
        gpa_credits=gpa_credits,
        excluded_credits=excluded_credits,
    )