Changed: Projects
This commit is contained in:
76
grader/tests/base_grader.py
Normal file
76
grader/tests/base_grader.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import sys
|
||||
sys.path.append("..")
|
||||
|
||||
from valuation import BaseGrading
|
||||
|
||||
# Testing
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
class TestBaseGrading(unittest.TestCase):
|
||||
test_schema = {"Grade1": 0.1, "Grade2": 0.3}
|
||||
|
||||
@patch.multiple(BaseGrading, __abstractmethods__=set())
|
||||
def get_base_grader(self):
|
||||
return BaseGrading(self.test_schema, "TestGrader")
|
||||
|
||||
def test_getter(self):
|
||||
grader = self.get_base_grader()
|
||||
self.assertEqual(grader.get("Grade1"), self.test_schema["Grade1"])
|
||||
self.assertEqual(grader.get("grade1"), self.test_schema["Grade1"])
|
||||
|
||||
def test_len(self):
|
||||
grader = self.get_base_grader()
|
||||
self.assertEqual(len(grader), len(self.test_schema))
|
||||
|
||||
def test_contains(self):
|
||||
grader = self.get_base_grader()
|
||||
self.assertTrue(0.1 in grader)
|
||||
self.assertTrue(0.9 in grader)
|
||||
self.assertFalse(100 in grader)
|
||||
self.assertFalse(None in grader)
|
||||
self.assertTrue("Grade1" in grader)
|
||||
self.assertTrue("gRADE2" in grader)
|
||||
|
||||
def test_iter(self):
|
||||
grader = self.get_base_grader()
|
||||
for grade, test in zip(grader, self.test_schema):
|
||||
self.assertEqual(grade, test)
|
||||
|
||||
def test_reversed(self):
|
||||
grader = self.get_base_grader()
|
||||
for grade, test in zip(reversed(grader), reversed(self.test_schema)):
|
||||
self.assertEqual(grade, test)
|
||||
|
||||
def test_str(self):
|
||||
grader = self.get_base_grader()
|
||||
self.assertEqual(str(grader), "TestGrader")
|
||||
|
||||
def test_repr(self):
|
||||
grader = self.get_base_grader()
|
||||
self.assertEqual(repr(grader), f"<TestGrader: ({str(self.test_schema)})>")
|
||||
|
||||
def test_eq(self):
|
||||
grader = self.get_base_grader()
|
||||
self.assertTrue(grader == grader)
|
||||
self.assertTrue(grader != grader)
|
||||
|
||||
def test_keys(self):
|
||||
grader = self.get_base_grader()
|
||||
for k1, t1 in zip(grader.keys(), self.test_schema.keys()):
|
||||
self.assertEqual(k1, t1)
|
||||
|
||||
def test_items(self):
|
||||
grader = self.get_base_grader()
|
||||
for v1, t1 in zip(grader.values(), self.test_schema.values()):
|
||||
self.assertEqual(v1, t1)
|
||||
|
||||
def test_items(self):
|
||||
grader = self.get_base_grader()
|
||||
for g1, t1 in zip(grader.items(), self.test_schema.items()):
|
||||
k, v = g1
|
||||
tk, tv = t1
|
||||
self.assertEqual(k, tk)
|
||||
self.assertEqual(v, tv)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
197
grader/valuation.py
Normal file
197
grader/valuation.py
Normal file
@@ -0,0 +1,197 @@
|
||||
from collections.abc import Sequence, Iterable, Mapping
|
||||
from typing import Any
|
||||
import inspect
|
||||
from abc import ABC, abstractmethod
|
||||
import weakref
|
||||
|
||||
from PIL import ImageColor
|
||||
from colour import Color
|
||||
PASSED: str = "#1AFE49"
|
||||
FAILED: str = "#FF124F"
|
||||
|
||||
def hex_to_rgba(color: str) -> tuple:
|
||||
return tuple([e/255 for e in ImageColor.getcolor(color, "RGBA")])
|
||||
|
||||
def gradient(color1: str, color2: str, num: int) -> list[tuple]:
|
||||
c1, c2 = Color(color1), Color(color2)
|
||||
colors = list(c2.range_to(c1, num))
|
||||
colors = [hex_to_rgba(str(c)) for c in colors]
|
||||
return colors
|
||||
|
||||
class BaseGrading(Mapping, ABC):
|
||||
__instances: list[Mapping] = list()
|
||||
|
||||
def __init__(self, schema: dict[str, int | float], name=None, alt_name=None):
|
||||
all_str = all(isinstance(k, str) for k in schema.keys())
|
||||
assert all_str or all(isinstance(k, int) for k in schema.keys()), "Keys must be all of type (str, int)"
|
||||
assert all(isinstance(v, float) for v in schema.values()), "All values must be floats in range(0,1)"
|
||||
assert all(v <= 1 and v >= 0 for v in schema.values()), "All values must be floats in range(0,1)"
|
||||
if all_str:
|
||||
self.schema = dict()
|
||||
for k, v in schema.items():
|
||||
self.schema[k.title()] = v
|
||||
else:
|
||||
self.schema = schema
|
||||
|
||||
self.__class__.__instances.append(weakref.proxy(self))
|
||||
self.name = name
|
||||
self.alt_name = alt_name
|
||||
|
||||
def __getitem__(self, index):
|
||||
if index >= len(self):
|
||||
raise IndexError
|
||||
return self.schema[index]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.schema)
|
||||
|
||||
def __contains__(self, item: int | str | float) -> bool:
|
||||
if isinstance(item, (int, str)):
|
||||
if isinstance(item, str):
|
||||
item = item.title()
|
||||
return item in self.schema
|
||||
if isinstance(item, float):
|
||||
return item <= 1 and item >= 0
|
||||
return False
|
||||
|
||||
def __iter__(self) -> Iterable:
|
||||
yield from self.schema
|
||||
|
||||
def __reversed__(self) -> Iterable:
|
||||
yield from reversed(self.schema)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.name}: ({str(self.schema)})>"
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if other == self:
|
||||
return True
|
||||
if isinstance(other, BaseEval):
|
||||
return self.schema == other.schema
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def get(self, key: int | str) -> float | None:
|
||||
if isinstance(key, str):
|
||||
key = key.title()
|
||||
if key in self:
|
||||
return self.schema[key]
|
||||
return None
|
||||
|
||||
def keys(self) -> tuple:
|
||||
return list(self.schema.keys())
|
||||
|
||||
def values(self) -> tuple:
|
||||
return list(self.schema.values())
|
||||
|
||||
def items(self) -> list[tuple]:
|
||||
return list(self.schema.items())
|
||||
|
||||
@abstractmethod
|
||||
def has_passed(self, value: int | float, max: int | float) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_grade(self, value: int | float, max: int | float) -> str | int:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_grade_color(self, value: int | float, max: int | float) -> tuple:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_instances(cls):
|
||||
yield from cls.__instances
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, name: str):
|
||||
for instance in cls.__instances:
|
||||
if instance.alt_name == name:
|
||||
return instance
|
||||
|
||||
get_gradings = lambda: BaseGrading.get_instances()
|
||||
get_grader = lambda name: BaseGrading.get_instance(name)
|
||||
|
||||
class StdPercentRule(BaseGrading):
|
||||
def has_passed(self, value: int | float, max: int | float) -> bool:
|
||||
return value >= max * self.schema["Passed"]
|
||||
|
||||
def get_grade(self, value: int | float, max: int | float) -> str:
|
||||
return "Passed" if self.has_passed(value, max) else "Not Passed"
|
||||
|
||||
def get_grade_color(self, value: int | float, max: int | float) -> tuple:
|
||||
if self.has_passed(value, max):
|
||||
return hex_to_rgba(PASSED)
|
||||
return hex_to_rgba(FAILED)
|
||||
|
||||
class StdGermanGrading(BaseGrading):
|
||||
def has_passed(self, value: int | float, max: int | float) -> bool:
|
||||
return value/max >= 0.45
|
||||
|
||||
def search_grade(self, value: float) -> int:
|
||||
if value <= 0:
|
||||
return min(self.schema.keys())
|
||||
|
||||
searched = max(self.schema.keys())
|
||||
found = False
|
||||
while not found:
|
||||
if self.schema[searched] <= value:
|
||||
found = True
|
||||
else:
|
||||
searched -= 1
|
||||
return searched
|
||||
|
||||
def get_grade(self, value: int | float, max: int | float) -> int:
|
||||
return self.search_grade(value/max)
|
||||
|
||||
def get_grade_color(self, value: float, max: int | float) -> tuple:
|
||||
grade = self.get_grade(value, max)
|
||||
colors = gradient(PASSED, FAILED, len(self.schema))
|
||||
return colors[grade]
|
||||
|
||||
# Definitions
|
||||
Std30PercentRule = StdPercentRule({
|
||||
"pAssed": 0.3,
|
||||
"Failed": 0.0
|
||||
}, "Std30PercentRule", "30 Percent")
|
||||
|
||||
Std50PercentRule = StdPercentRule({
|
||||
"Passed": 0.5,
|
||||
"Failed": 0.0
|
||||
}, "Std50PercentRule", "50 Percent")
|
||||
|
||||
StdGermanGradingMiddleSchool = StdGermanGrading({
|
||||
1: 0.96,
|
||||
2: 0.80,
|
||||
3: 0.60,
|
||||
4: 0.45,
|
||||
5: 0.16,
|
||||
6: 0.00
|
||||
}, "StdGermanGradingMiddleSchool", "Secondary School")
|
||||
|
||||
StdGermanGradingHighSchool = StdGermanGrading({
|
||||
15: 0.95,
|
||||
14: 0.90,
|
||||
13: 0.85,
|
||||
12: 0.80,
|
||||
11: 0.75,
|
||||
10: 0.70,
|
||||
9: 0.65,
|
||||
8: 0.60,
|
||||
7: 0.55,
|
||||
6: 0.50,
|
||||
5: 0.45,
|
||||
4: 0.40,
|
||||
3: 0.33,
|
||||
2: 0.27,
|
||||
1: 0.20,
|
||||
0: 0.00
|
||||
}, "StdGermanGradingHighSchool", "Oberstufe")
|
||||
|
||||
|
||||
#print(StdGermanGradingHighSchool.get_grade(0.0, 24))
|
||||
Reference in New Issue
Block a user