2,369 bytes · the Python implementation · view raw
from typing import Sequence
from .education_grade_boundaries_types import GradeBoundary, GradeResult
def _is_int(value: object) -> bool:
return isinstance(value, int) andnot isinstance(value, bool)
def grade_for_mark(mark: int, max_mark: int, boundaries: Sequence[GradeBoundary]) -> GradeResult:
"""The grade a mark earns from a boundary table, and the gap to the next grade. Rows are sorted by min_mark here because published tables run highest grade first, and a lookup that assumes ascending order hands out the wrong grade. """ifnot _is_int(max_mark) or max_mark < 1:
raise ValueError("maxMark must be a positive whole number, received %s" % (max_mark,))
ifnot _is_int(mark) or mark < 0or mark > max_mark:
raise ValueError("mark must be a whole number from 0 to maxMark (%s), received %s" % (max_mark, mark))
if len(boundaries) == 0:
raise ValueError("boundaries must not be empty")
seen = set()
for b in boundaries:
ifnot isinstance(b.grade, str) or len(b.grade) == 0:
raise ValueError("every boundary needs a non-empty grade")
ifnot _is_int(b.min_mark) or b.min_mark < 0or b.min_mark > max_mark:
raise ValueError(
"boundary minMark must be a whole number from 0 to maxMark (%s), received %s for grade %s"
% (max_mark, b.min_mark, b.grade)
)
if b.grade in seen:
raise ValueError("grade %s appears twice in boundaries" % (b.grade,))
seen.add(b.grade)
ordered = sorted(boundaries, key=lambda b: b.min_mark)
for i in range(1, len(ordered)):
if ordered[i].min_mark == ordered[i - 1].min_mark:
raise ValueError(
"grades %s and %s share the boundary %s" % (ordered[i - 1].grade, ordered[i].grade, ordered[i].min_mark)
)
index = -1for i, b in enumerate(ordered):
if b.min_mark <= mark:
index = i
awarded = ordered[index] if index >= 0elseNone
nxt = ordered[index + 1] if index + 1 < len(ordered) elseNonereturn GradeResult(
grade=Noneif awarded isNoneelse awarded.grade,
min_mark=Noneif awarded isNoneelse awarded.min_mark,
next_grade=Noneif nxt isNoneelse nxt.grade,
marks_to_next=Noneif nxt isNoneelse nxt.min_mark - mark,
)