diff --git a/analyzer.py b/analyzer.py index 4a50928..badbdc8 100644 --- a/analyzer.py +++ b/analyzer.py @@ -1,6 +1,7 @@ # Custom from model import * from appstate import AppState +from grader.valuation import * # External from imgui_bundle import ( @@ -22,129 +23,56 @@ from numpy.typing import NDArray # Built In from typing import List, Any -def student_list(app_state: AppState) -> None: - statics = student_list - if not app_state.current_class_id: - imgui.text("No Class found in Database") - return +@immapp.static(inited=False) +def select_class(app_state: AppState) -> None: + statics = select_class - if not hasattr(statics, "select"): - statics.select = 0 - - statics.students = Student.select().where(Student.class_id == app_state.current_class_id) - statics.students = statics.students if statics.students else None - if not statics.students: - imgui.text(f"No Stundents found in {Class.get_by_id(app_state.current_class_id).name}") + if not app_state.current_class_id: + imgui.text("No class found in Database") return + if not statics.inited: + statics.select = 0 + statics.classes = Class.select() + statics.labels = [c.name for c in statics.classes] + statics.inited = True + + changed, statics.select = imgui.combo("Classes", statics.select, statics.labels) + if changed: + app_state.current_class_id = statics.classes[statics.select].id + +@immapp.static(inited=False) +def select_student(app_state: AppState) -> None: + statics = select_student + + if not app_state.current_class_id: + return + + if not statics.inited: + statics.select = 0 + statics.students = Student.select().where(Student.class_id == app_state.current_class_id) + statics.inited = True + + if not statics.students: + imgui.text("No Studends found") + return + for n, student in enumerate(statics.students, start=1): display = f"{n}. {student.prename} {student.surname}" _, clicked = imgui.selectable(display, statics.select == n-1) if clicked: statics.select = n-1 - - app_state.current_student_id = statics.students[statics.select].id + app_state.current_student_id = statics.students[statics.select].id -def group_list(app_state: AppState) -> None: - statics = group_list - if not app_state.current_class_id: - imgui.text("No Class found in Database") - return + +@immapp.static(inited=False) +def student_list(app_state: AppState) -> None: + statics = student_list - if not hasattr(statics, "select"): - statics.select = 0 + select_class(app_state) + imgui.separator() + select_student(app_state) - statics.groups = Group.select().where(Group.class_id == app_state.current_class_id) - statics.groups = statics.groups if statics.groups else None - if not statics.groups: - imgui.text("No Group found") - return - - for n, group in enumerate(statics.groups, start=1): - display = f"{n}. {group.name}" - _, clicked = imgui.selectable(display, statics.select == n-1) - if clicked: - statics.select = n-1 - -def lecture_list(app_state: AppState) -> None: - statics = lecture_list - - if not app_state.current_class_id: - imgui.text("No class found in Database") - return - - lectures = Lecture.select().where(Lecture.class_id == app_state.current_class_id) - - if not lectures: - imgui.text(f"No Lectures found for {Class.get_by_id(app_state.current_class_id).name}") - return - - if not hasattr(statics, "select"): - statics.select = 0 - - for n, lecture in enumerate(lectures, start=1): - display = f"{n}. {lecture.title}" - _, clicked = imgui.selectable(display, statics.select == n-1) - if clicked: - statics.select = n-1 - - app_state.current_lecture_id = lectures[statics.select].id - -def class_list(app_state: AppState) -> None: - statics = class_list - - if db.is_closed(): - imgui.text("No Database loaded") - return - - classes = Class.select() if db else None - if not classes: - imgui.text("No Classes currently in Database") - return - - if not hasattr(statics, "select"): - statics.select = 0 - - for n, clas in enumerate(classes, start=1): - display = f"{n}. {clas.name}" - _, clicked = imgui.selectable(display, statics.select == n-1) - if clicked: - statics.select = n-1 - - app_state.current_class_id = classes[statics.select].id - -def submissions_list(app_state: AppState) -> None: - statics = submissions_list - - if not app_state.current_lecture_id: - imgui.text("No Lecture found") - return - if not app_state.current_student_id: - imgui.text("No Student found") - return - - submissions = Submission.select().where(Submission.lecture_id == app_state.current_lecture_id and Submission.student_id == app_state.current_student_id) - - if not submissions: - student = Student.get_by_id(app_state.current_student_id) - lecture = Lecture.get_by_id(app_state.current_lecture_id) - imgui.text(f"{student.prename} {student.surname} didn't submitted for {lecture.title}") - return - - if not hasattr(statics, "select"): - statics.select = 0 - - for n, sub in enumerate(submissions, start=1): - lecture = Lecture.get_by_id(sub.lecture_id) - points = sub.points - if points.is_integer(): - points = int(points) - display = f"{n}. {lecture.title} {points}/{lecture.points}" - _, clicked = imgui.selectable(display, statics.select == n-1) - if clicked: - statics.select = n-1 - - app_state.current_submission_id = submissions[statics.select].id def plot_bar_line_percentage(data: np.array, labels: list, avg: float) -> None: if not data.size > 0: @@ -212,6 +140,8 @@ def student_graph(app_state: AppState) -> None: statics.points = np.sum(statics.sub_points) if statics.points.is_integer(): statics.points = int(statics.points) + statics.grader = get_grader("Oberstufe") + #statics.grader = get_grader(statics.student.grader) statics.subs_data = np.array([p/mp.points for p, mp in zip(statics.sub_points, statics.lectures)], dtype=np.float32)*100 statics.subs_labels = [f"{l.title} {int(points) if points.is_integer() else points}/{l.points}" for l, points in zip(statics.lectures, statics.sub_points)] @@ -219,7 +149,7 @@ def student_graph(app_state: AppState) -> None: w, h = imgui.get_window_size() imgui_md.render(f"# {statics.student.prename} {statics.student.surname} ({statics.group.name})") - imgui_md.render(f"### {statics.points}/{statics.max_points}") + imgui_md.render(f"### {statics.points}/{statics.max_points} ({statics.student.grader})") imgui.text(" ") imgui.progress_bar(statics.points/statics.max_points, ImVec2(w*0.9, h*0.05), f"{statics.points}/{statics.max_points} {statics.points/statics.max_points:.1%}") plot_bar_line_percentage(statics.subs_data, statics.subs_labels, statics.avg) @@ -227,8 +157,8 @@ def student_graph(app_state: AppState) -> None: imgui.text_colored(COLOR_TEXT_PROJECT, f"{statics.group.name}: {statics.group.project}") for n, data in enumerate(zip(statics.lectures, statics.sub_points), start=1): lecture, points = data - COLOR = COLOR_TEXT_PASSED if points >= lecture.points*0.3 else COLOR_TEXT_FAILED - imgui.text_colored(COLOR, f"{n}. {lecture.title}") + COLOR = statics.grader.get_grade_color(points, lecture.points) + imgui.text_colored(COLOR, f"{n}. {lecture.title} {points}/{lecture.points} ({statics.grader.get_grade(points, lecture.points)}) ") @immapp.static(inited=False) def sex_graph(app_state: AppState) -> None: @@ -415,26 +345,6 @@ def set_analyzer_layout(app_state: AppState) -> List[hello_imgui.DockableWindow] student_selector.label = "Students" student_selector.dock_space_name = "CommandSpace" student_selector.gui_function = lambda: student_list(app_state) - - group_selector = hello_imgui.DockableWindow() - group_selector.label = "Groups" - group_selector.dock_space_name = "CommandSpace" - group_selector.gui_function = lambda: group_list(app_state) - - lecture_selector = hello_imgui.DockableWindow() - lecture_selector.label = "Lectures" - lecture_selector.dock_space_name = "CommandSpace2" - lecture_selector.gui_function = lambda: lecture_list(app_state) - - class_selector = hello_imgui.DockableWindow() - class_selector.label = "Classes" - class_selector.dock_space_name = "CommandSpace2" - class_selector.gui_function = lambda: class_list(app_state) - - submission_selector = hello_imgui.DockableWindow() - submission_selector.label = "Submissions" - submission_selector.dock_space_name = "CommandSpace" - submission_selector.gui_function = lambda: submissions_list(app_state) student_info = hello_imgui.DockableWindow() student_info.label = "Student Analyzer" @@ -452,10 +362,9 @@ def set_analyzer_layout(app_state: AppState) -> List[hello_imgui.DockableWindow] student_ranking.gui_function = lambda: ranking(app_state) return [ - class_selector, student_selector, - lecture_selector, submission_selector, + student_selector, student_info, sex_info, - student_ranking, group_selector + student_ranking, ] def analyzer_layout(app_state: AppState) -> hello_imgui.DockingParams: diff --git a/assets/Student_list.csv b/assets/Student_list.csv index fa13c8e..5271b0d 100644 --- a/assets/Student_list.csv +++ b/assets/Student_list.csv @@ -1,37 +1,38 @@ -First Name,Last Name,Sex,Group,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium,Statistical Test Methods,Data Analysis -Abdalaziz,Abunjaila,Male,DiKum,30.5,15,18,28,17,17,17,22,0,18 -Marleen,Adolphi,Female,MeWi6,29.5,15,18,32,19,20,17,24,23,0 -Sarina,Apel,Female,MeWi1,28.5,15,18,32,20,20,21,24,20,0 -Skofiare,Berisha,Female,DiKum,29.5,13,18,34,20,17,20,26,16,0 -Aurela,Brahimi,Female,MeWi2,17.5,15,15.5,26,16,17,19,16,0,0 -Cam Thu,Do,Female,MeWi3,31,15,18,34,19,20,21.5,22,12,0 -Nova,Eib,Female,MeWi4,31,15,15,34,20,20,21,27,19,21 -Lena,Fricke,Female,MeWi4,0,0,0,0,0,0,0,0,0,0 -Nele,Grundke,Female,MeWi6,23.5,13,16,28,20,17,21,18,22,0 -Anna,Grünewald,Female,MeWi3,12,14,16,29,16,15,19,9,0,0 -Yannik,Haupt,Male,NoGroup,18,6,14,21,13,2,9,0,0,0 -Janna,Heiny,Female,MeWi1,30,15,18,33,18,20,22,25,24,30 -Milena,Krieger,Female,MeWi1,30,15,18,33,20,20,21.5,26,20,0 -Julia,Limbach,Female,MeWi6,27.5,12,18,29,11,19,17.5,26,24,0 -Viktoria,Litza,Female,MeWi5,21.5,15,18,27,13,20,22,21,21,0 -Leonie,Manthey,Female,MeWi1,28.5,14,18,29,20,10,18,23,16,28 -Izabel,Mike,Female,MeWi2,29.5,15,15,35,11,15,19,21,21,27 -Lea,Noglik,Female,MeWi5,22.5,15,17,34,13,10,20,21,19,0 -Donika,Nuhiu,Female,MeWi5,31,13.5,18,35,14,10,17,18,19,6 -Julia,Renner,Female,MeWi4,27.5,10,14,32,20,17,11,20,24,0 -Fabian,Rothberger,Male,MeWi3,30.5,15,18,34,17,17,19,22,18,0 -Natascha,Rott,Female,MeWi1,29.5,12,18,32,19,20,21,26,23,0 -Isabel,Rudolf,Female,MeWi4,27.5,9,17,34,16,19,19,21,16,0 -Melina,Sablotny,Female,MeWi6,31,15,18,33,20,20,21,19,11,0 -Alea,Schleier,Female,DiKum,27,14,18,34,16,18,21.5,22,15,22 -Flemming,Schur,Male,MeWi3,29.5,15,17,34,19,20,19,22,18,0 -Marie,Seeger,Female,DiKum,27.5,15,18,32,14,9,17,22,9,0 -Lucy,Thiele,Female,MeWi6,27.5,15,18,27,20,17,19,18,22,0 -Lara,Troschke,Female,MeWi2,28.5,14,17,28,13,19,21,25,12,0 -Inga-Brit,Turschner,Female,MeWi2,25.5,14,18,34,20,16,19,22,17,0 -Alea,Unger,Female,MeWi5,30,12,18,31,20,20,21,22,15,21.5 -Marie,Wallbaum,Female,MeWi5,28.5,14,18,34,17,20,19,24,12,0 -Katharina,Walz,Female,MeWi4,31,15,18,31,19,19,17,24,17,14.5 -Xiaowei,Wang,Male,NoGroup,30.5,14,18,26,19,17,0,0,0,0 -Lilly-Lu,Warnken,Female,DiKum,30,15,18,30,14,17,19,14,16,0 +First Name,Last Name,Sex,Group,Grader,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium,Statistical Test Methods,Data Analysis +Abdalaziz,Abunjaila,Male,DiKum,30 Percent,30.5,15,18,28,17,17,17,22,0,18 +Marleen,Adolphi,Female,MeWi6,30 Percent,29.5,15,18,32,19,20,17,24,23,0 +Sarina,Apel,Female,MeWi1,30 Percent,28.5,15,18,32,20,20,21,24,20,23 +Skofiare,Berisha,Female,DiKum,30 Percent,29.5,13,18,34,20,17,20,26,16,0 +Aurela,Brahimi,Female,MeWi2,30 Percent,17.5,15,15.5,26,16,17,19,16,0,0 +Cam Thu,Do,Female,MeWi3,30 Percent,31,15,18,34,19,20,21.5,22,12,0 +Nova,Eib,Female,MeWi4,30 Percent,31,15,15,34,20,20,21,27,19,21 +Lena,Fricke,Female,MeWi4,30 Percent,0,0,0,0,0,0,0,0,0,0 +Nele,Grundke,Female,MeWi6,30 Percent,23.5,13,16,28,20,17,21,18,22,11 +Anna,Grünewald,Female,MeWi3,30 Percent,12,14,16,29,16,15,19,9,0,0 +Yannik,Haupt,Male,NoGroup,30 Percent,18,6,14,21,13,2,9,0,0,0 +Janna,Heiny,Female,MeWi1,30 Percent,30,15,18,33,18,20,22,25,24,30 +Milena,Krieger,Female,MeWi1,30 Percent,30,15,18,33,20,20,21.5,26,20,22 +Julia,Limbach,Female,MeWi6,30 Percent,27.5,12,18,29,11,19,17.5,26,24,28 +Viktoria,Litza,Female,MeWi5,30 Percent,21.5,15,18,27,13,20,22,21,21,30 +Leonie,Manthey,Female,MeWi1,30 Percent,28.5,14,18,29,20,10,18,23,16,28 +Izabel,Mike,Female,MeWi2,30 Percent,29.5,15,15,35,11,15,19,21,21,27 +Lea,Noglik,Female,MeWi5,30 Percent,22.5,15,17,34,13,10,20,21,19,6 +Donika,Nuhiu,Female,MeWi5,30 Percent,31,13.5,18,35,14,10,17,18,19,8 +Julia,Renner,Female,MeWi4,30 Percent,27.5,10,14,32,20,17,11,20,24,14 +Fabian,Rothberger,Male,MeWi3,30 Percent,30.5,15,18,34,17,17,19,22,18,30 +Natascha,Rott,Female,MeWi1,30 Percent,29.5,12,18,32,19,20,21,26,23,26 +Isabel,Rudolf,Female,MeWi4,30 Percent,27.5,9,17,34,16,19,19,21,16,14 +Melina,Sablotny,Female,MeWi6,30 Percent,31,15,18,33,20,20,21,19,11,28 +Alea,Schleier,Female,DiKum,30 Percent,27,14,18,34,16,18,21.5,22,15,22 +Flemming,Schur,Male,MeWi3,30 Percent,29.5,15,17,34,19,20,19,22,18,27 +Marie,Seeger,Female,DiKum,30 Percent,27.5,15,18,32,14,9,17,22,9,25 +Lucy,Thiele,Female,MeWi6,30 Percent,27.5,15,18,27,20,17,19,18,22,25 +Lara,Troschke,Female,MeWi2,30 Percent,28.5,14,17,28,13,19,21,25,12,24 +Inga-Brit,Turschner,Female,MeWi2,30 Percent,25.5,14,18,34,20,16,19,22,17,30 +Alea,Unger,Female,MeWi5,30 Percent,30,12,18,31,20,20,21,22,15,21.5 +Marie,Wallbaum,Female,MeWi5,30 Percent,28.5,14,18,34,17,20,19,24,12,22 +Katharina,Walz,Female,MeWi4,30 Percent,31,15,18,31,19,19,17,24,17,14.5 +Xiaowei,Wang,Male,NoGroup,30 Percent,30.5,14,18,26,19,17,0,0,0,0 +Lilly-Lu,Warnken,Female,DiKum,30 Percent,30,15,18,30,14,17,19,14,16,24 +,,,,,,,,,,,,,, diff --git a/assets/WiSe_24_25.db b/assets/WiSe_24_25.db index b86dd92..8e2e875 100644 Binary files a/assets/WiSe_24_25.db and b/assets/WiSe_24_25.db differ diff --git a/assets/convert.py b/assets/convert.py index ae10dde..7c31ae1 100644 --- a/assets/convert.py +++ b/assets/convert.py @@ -54,10 +54,11 @@ for index, row in df.iterrows(): surname=row["Last Name"], sex=row["Sex"], class_id=clas.id, - group_id=Group.select().where(Group.name == row["Group"]) + group_id=Group.select().where(Group.name == row["Group"]), + grader=row["Grader"] ) - for title, points in list(row.to_dict().items())[4:]: + for title, points in list(row.to_dict().items())[5:]: Submission.create( student_id=s.id, lecture_id=Lecture.select().where(Lecture.title == title), diff --git a/grader/tests/base_grader.py b/grader/tests/base_grader.py new file mode 100644 index 0000000..94e8d85 --- /dev/null +++ b/grader/tests/base_grader.py @@ -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"") + + 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() diff --git a/grader/valuation.py b/grader/valuation.py new file mode 100644 index 0000000..ef42f74 --- /dev/null +++ b/grader/valuation.py @@ -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)) diff --git a/model.py b/model.py index 0177369..e99b546 100644 --- a/model.py +++ b/model.py @@ -27,6 +27,7 @@ class Student(BaseModel): sex = CharField() class_id = ForeignKeyField(Class, backref='class') group_id = ForeignKeyField(Group, backref='group') + grader = CharField() created_at = DateTimeField(default=datetime.now) class Lecture(BaseModel):