Changed: Projects
This commit is contained in:
		
							
								
								
									
										185
									
								
								analyzer.py
									
									
									
									
									
								
							
							
						
						
									
										185
									
								
								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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
,,,,,,,,,,,,,,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		
		
			
  | 
										
											Binary file not shown.
										
									
								
							@@ -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),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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