Added: Stuff
This commit is contained in:
parent
ab01d8f75e
commit
2d25d5105a
BIN
assets/Nexus/NexusSansPro-Bold.otf
Normal file
BIN
assets/Nexus/NexusSansPro-Bold.otf
Normal file
Binary file not shown.
BIN
assets/Nexus/NexusSansPro-Regular.otf
Normal file
BIN
assets/Nexus/NexusSansPro-Regular.otf
Normal file
Binary file not shown.
BIN
assets/Nexus/NexusSerifPro-Bold.otf
Normal file
BIN
assets/Nexus/NexusSerifPro-Bold.otf
Normal file
Binary file not shown.
BIN
assets/Nexus/NexusSerifPro-Regular.otf
Normal file
BIN
assets/Nexus/NexusSerifPro-Regular.otf
Normal file
Binary file not shown.
@ -1,36 +1,38 @@
|
||||
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%,30.5,15,18,28,17,17,17,22,0,18
|
||||
Marleen,Adolphi,Female,MeWi6,30%,29.5,15,18,32,19,20,17,24,23,0
|
||||
Sarina,Apel,Female,MeWi1,30%,28.5,15,18,32,20,20,21,24,20,23
|
||||
Skofiare,Berisha,Female,DiKum,30%,29.5,13,18,34,20,17,20,26,16,0
|
||||
Aurela,Brahimi,Female,MeWi2,30%,17.5,15,15.5,26,16,17,19,16,0,0
|
||||
Cam Thu,Do,Female,MeWi3,30%,31,15,18,34,19,20,21.5,22,12,0
|
||||
Nova,Eib,Female,MeWi4,30%,31,15,15,34,20,20,21,27,19,21
|
||||
Lena,Fricke,Female,MeWi4,30%,0,0,0,0,0,0,0,0,0,0
|
||||
Nele,Grundke,Female,MeWi6,30%,23.5,13,16,28,20,17,21,18,22,11
|
||||
Anna,Grünewald,Female,MeWi3,30%,12,14,16,29,16,15,19,9,0,0
|
||||
Yannik,Haupt,Male,NoGroup,30%,18,6,14,21,13,2,9,0,0,0
|
||||
Janna,Heiny,Female,MeWi1,30%,30,15,18,33,18,20,22,25,24,30
|
||||
Milena,Krieger,Female,MeWi1,30%,30,15,18,33,20,20,21.5,26,20,22
|
||||
Julia,Limbach,Female,MeWi6,30%,27.5,12,18,29,11,19,17.5,26,24,28
|
||||
Viktoria,Litza,Female,MeWi5,30%,21.5,15,18,27,13,20,22,21,21,30
|
||||
Leonie,Manthey,Female,MeWi1,30%,28.5,14,18,29,20,10,18,23,16,28
|
||||
Izabel,Mike,Female,MeWi2,30%,29.5,15,15,35,11,15,19,21,21,27
|
||||
Lea,Noglik,Female,MeWi5,30%,22.5,15,17,34,13,10,20,21,19,6
|
||||
Donika,Nuhiu,Female,MeWi5,30%,31,13.5,18,35,14,10,17,18,19,8
|
||||
Julia,Renner,Female,MeWi4,30%,27.5,10,14,32,20,17,11,20,24,14
|
||||
Fabian,Rothberger,Male,MeWi3,30%,30.5,15,18,34,17,17,19,22,18,30
|
||||
Natascha,Rott,Female,MeWi1,30%,29.5,12,18,32,19,20,21,26,23,26
|
||||
Isabel,Rudolf,Female,MeWi4,30%,27.5,9,17,34,16,19,19,21,16,14
|
||||
Melina,Sablotny,Female,MeWi6,30%,31,15,18,33,20,20,21,19,11,28
|
||||
Alea,Schleier,Female,DiKum,30%,27,14,18,34,16,18,21.5,22,15,22
|
||||
Flemming,Schur,Male,MeWi3,30%,29.5,15,17,34,19,20,19,22,18,27
|
||||
Marie,Seeger,Female,DiKum,30%,27.5,15,18,32,14,9,17,22,9,25
|
||||
Lucy,Thiele,Female,MeWi6,30%,27.5,15,18,27,20,17,19,18,22,25
|
||||
Lara,Troschke,Female,MeWi2,30%,28.5,14,17,28,13,19,21,25,12,24
|
||||
Inga-Brit,Turschner,Female,MeWi2,30%,25.5,14,18,34,20,16,19,22,17,30
|
||||
Alea,Unger,Female,MeWi5,30%,30,12,18,31,20,20,21,22,15,21.5
|
||||
Marie,Wallbaum,Female,MeWi5,30%,28.5,14,18,34,17,20,19,24,12,22
|
||||
Katharina,Walz,Female,MeWi4,30%,31,15,18,31,19,19,17,24,17,14.5
|
||||
Xiaowei,Wang,Male,NoGroup,30%,30.5,14,18,26,19,17,0,0,0,0
|
||||
Lilly-Lu,Warnken,Female,DiKum,30%,30,15,18,30,14,17,19,14,16,24
|
||||
First Name,Last Name,Sex,Group,Grader,Study,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium,Statistical Test Methods,Data Analysis
|
||||
Abdalaziz,Abunjaila,Male,DiKum,30%,Digitale Kommunikation & Medientechnik,30.5,15,18,28,17,17,17,22,0,18
|
||||
Marleen,Adolphi,Female,MeWi6,30%,Digitale Kommunikation & Medientechnik,29.5,15,18,32,19,20,17,24,23,0
|
||||
Sarina,Apel,Female,MeWi1,30%,Medienwissenschaften,28.5,15,18,32,20,20,21,24,20,23
|
||||
Skofiare,Berisha,Female,DiKum,30%,Medienwissenschaften,29.5,13,18,34,20,17,20,26,16,10
|
||||
Aurela,Brahimi,Female,MeWi2,30%,Medienwissenschaften,17.5,15,15.5,26,16,17,19,16,16,16
|
||||
Cam Thu,Do,Female,MeWi3,30%,Medienwissenschaften,31,15,18,34,19,20,21.5,22,12,15
|
||||
Nova,Eib,Female,MeWi4,30%,Medienwissenschaften,31,15,15,34,20,20,21,27,19,21
|
||||
Lena,Fricke,Female,MeWi4,30%,Medienwissenschaften,13,14,15,21,15,17,17,18,19,11
|
||||
Nele,Grundke,Female,MeWi6,30%,Medienwissenschaften,23.5,13,16,28,20,17,21,18,22,11
|
||||
Anna,Grünewald,Female,MeWi3,30%,Medienwissenschaften,12,14,16,29,16,15,19,9,16,12
|
||||
Yannik,Haupt,Male,NoGroup,30%,Unknown,18,6,14,21,13,2,9,0,0,0
|
||||
Janna,Heiny,Female,MeWi1,30%,Technologie Orientiertes Managment,30,15,18,33,18,20,22,25,24,30
|
||||
Milena,Krieger,Female,MeWi1,30%,Technologie Orientiertes Managment,30,15,18,33,20,20,21.5,26,20,22
|
||||
Julia,Limbach,Female,MeWi6,30%,Medienwissenschaften,27.5,12,18,29,11,19,17.5,26,24,28
|
||||
Viktoria,Litza,Female,MeWi5,30%,Medienwissenschaften,21.5,15,18,27,13,20,22,21,21,30
|
||||
Leonie,Manthey,Female,MeWi1,30%,Medienwissenschaften,28.5,14,18,29,20,10,18,23,16,28
|
||||
Izabel,Mike,Female,MeWi2,30%,Medienwissenschaften,29.5,15,15,35,11,15,19,21,21,27
|
||||
Lea,Noglik,Female,MeWi5,30%,Medienwissenschaften,22.5,15,17,34,13,10,20,21,19,6
|
||||
Donika,Nuhiu,Female,MeWi5,30%,Medienwissenschaften,31,13.5,18,35,14,10,17,18,19,8
|
||||
Julia,Renner,Female,MeWi4,30%,Medienwissenschaften,27.5,10,14,32,20,17,11,20,24,14
|
||||
Fabian,Rothberger,Male,MeWi3,30%,Medienwissenschaften,30.5,15,18,34,17,17,19,22,18,30
|
||||
Natascha,Rott,Female,MeWi1,30%,Medienwissenschaften,29.5,12,18,32,19,20,21,26,23,26
|
||||
Isabel,Rudolf,Female,MeWi4,30%,Medienwissenschaften,27.5,9,17,34,16,19,19,21,16,14
|
||||
Melina,Sablotny,Female,MeWi6,30%,Medienwissenschaften,31,15,18,33,20,20,21,19,11,28
|
||||
Alea,Schleier,Female,DiKum,30%,Medienwissenschaften,27,14,18,34,16,18,21.5,22,15,22
|
||||
Flemming,Schur,Male,MeWi3,30%,Medienwissenschaften,29.5,15,17,34,19,20,19,22,18,27
|
||||
Marie,Seeger,Female,DiKum,30%,Medienwissenschaften,27.5,15,18,32,14,9,17,22,9,25
|
||||
Lucy,Thiele,Female,MeWi6,30%,Medienwissenschaften,27.5,15,18,27,20,17,19,18,22,25
|
||||
Lara,Troschke,Female,MeWi2,30%,Medienwissenschaften,28.5,14,17,28,13,19,21,25,12,24
|
||||
Inga-Brit,Turschner,Female,MeWi2,30%,Medienwissenschaften,25.5,14,18,34,20,16,19,22,17,30
|
||||
Alea,Unger,Female,MeWi5,30%,Medienwissenschaften,30,12,18,31,20,20,21,22,15,21.5
|
||||
Marie,Wallbaum,Female,MeWi5,30%,Medienwissenschaften,28.5,14,18,34,17,20,19,24,12,22
|
||||
Katharina,Walz,Female,MeWi4,30%,Medienwissenschaften,31,15,18,31,19,19,17,24,17,14.5
|
||||
Xiaowei,Wang,Male,NoGroup,30%,Unknown,30.5,14,18,26,19,17,0,0,0,0
|
||||
Lilly-Lu,Warnken,Female,DiKum,30%,Medienwissenschaften,30,15,18,30,14,17,19,14,16,24
|
||||
,,,,,,,,,,,,,,,
|
||||
|
||||
|
|
Binary file not shown.
@ -44,19 +44,24 @@ for k, v in courses.items():
|
||||
#print(l.title, l.points, l.class_id, l.id)
|
||||
|
||||
for k, v in groups.items():
|
||||
Group.create(name=k, project=v, has_passed=True, class_id=clas.id)
|
||||
Group.create(name=k, project=v, has_passed=True if k != 'NoGroup' else False, class_id=clas.id)
|
||||
|
||||
for index, row in df.iterrows():
|
||||
|
||||
study, _ = Study.get_or_create(name=row['Study'])
|
||||
|
||||
|
||||
s = Student.create(
|
||||
prename=row["First Name"],
|
||||
surname=row["Last Name"],
|
||||
sex=row["Sex"],
|
||||
study_id=study.id,
|
||||
class_id=clas.id,
|
||||
group_id=Group.select().where(Group.name == row["Group"]),
|
||||
grader=row["Grader"],
|
||||
)
|
||||
|
||||
for title, points in list(row.to_dict().items())[5:]:
|
||||
for title, points in list(row.to_dict().items())[6:]:
|
||||
Submission.create(
|
||||
student_id=s.id,
|
||||
lecture_id=Lecture.select().where(Lecture.title == title),
|
||||
|
13874
assets/documents/document.pdf
Normal file
13874
assets/documents/document.pdf
Normal file
File diff suppressed because one or more lines are too long
13298
assets/documents/wise-23-24-fri-07-03-2025-01-14.pdf
Normal file
13298
assets/documents/wise-23-24-fri-07-03-2025-01-14.pdf
Normal file
File diff suppressed because one or more lines are too long
13400
assets/documents/wise-23-24-fri-07-03-2025-01-15.pdf
Normal file
13400
assets/documents/wise-23-24-fri-07-03-2025-01-15.pdf
Normal file
File diff suppressed because one or more lines are too long
13400
assets/documents/wise-23-24-fri-07-03-2025-01-15a.pdf
Normal file
13400
assets/documents/wise-23-24-fri-07-03-2025-01-15a.pdf
Normal file
File diff suppressed because one or more lines are too long
13569
assets/documents/wise-24-25-fri-07-03-2025-00-47.pdf
Normal file
13569
assets/documents/wise-24-25-fri-07-03-2025-00-47.pdf
Normal file
File diff suppressed because one or more lines are too long
13153
assets/documents/wise-24-25-thu-06-03-2025-21-33.pdf
Normal file
13153
assets/documents/wise-24-25-thu-06-03-2025-21-33.pdf
Normal file
File diff suppressed because one or more lines are too long
13615
assets/documents/wise-24-25-thu-06-03-2025-22-52.pdf
Normal file
13615
assets/documents/wise-24-25-thu-06-03-2025-22-52.pdf
Normal file
File diff suppressed because one or more lines are too long
13642
assets/documents/wise-24-25-thu-06-03-2025-22-53.pdf
Normal file
13642
assets/documents/wise-24-25-thu-06-03-2025-22-53.pdf
Normal file
File diff suppressed because one or more lines are too long
13642
assets/documents/wise-24-25-thu-06-03-2025-22-54.pdf
Normal file
13642
assets/documents/wise-24-25-thu-06-03-2025-22-54.pdf
Normal file
File diff suppressed because one or more lines are too long
13570
assets/documents/wise-24-25-thu-06-03-2025-22-55.pdf
Normal file
13570
assets/documents/wise-24-25-thu-06-03-2025-22-55.pdf
Normal file
File diff suppressed because one or more lines are too long
13570
assets/documents/wise-24-25-thu-06-03-2025-22-56.pdf
Normal file
13570
assets/documents/wise-24-25-thu-06-03-2025-22-56.pdf
Normal file
File diff suppressed because one or more lines are too long
29
assets/learnlytics.svg
Normal file
29
assets/learnlytics.svg
Normal file
@ -0,0 +1,29 @@
|
||||
<svg
|
||||
width="300" height="150" viewBox="0 0 300 150"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" stroke-linecap="round" stroke-linejoin="round"
|
||||
>
|
||||
<!-- Background Grid -->
|
||||
<rect width="100%" height="100%" fill="#0D1B2A" />
|
||||
|
||||
<!-- Bar Chart -->
|
||||
<rect x="50" y="90" width="30" height="30" fill="#2EC4B6" />
|
||||
<rect x="90" y="60" width="30" height="60" fill="#2EC4B6" />
|
||||
<rect x="130" y="80" width="30" height="40" fill="#2EC4B6" />
|
||||
<rect x="170" y="40" width="30" height="80" fill="#2EC4B6" />
|
||||
<rect x="210" y="75" width="30" height="45" fill="#2EC4B6" />
|
||||
<rect x="250" y="30" width="30" height="90" fill="#2EC4B6" />
|
||||
|
||||
<!-- Analytics Chart - Dynamic Graph Lines -->
|
||||
<polyline points="50,110 90,70 130,100 170,50 210,90 250,40" stroke="#F4A261" stroke-width="6" stroke-dasharray="8 4" />
|
||||
<circle cx="50" cy="110" r="5" fill="#F4A261" />
|
||||
<circle cx="90" cy="70" r="5" fill="#F4A261" />
|
||||
<circle cx="130" cy="100" r="5" fill="#F4A261" />
|
||||
<circle cx="170" cy="50" r="5" fill="#F4A261" />
|
||||
<circle cx="210" cy="90" r="5" fill="#F4A261" />
|
||||
<circle cx="250" cy="40" r="5" fill="#F4A261" />
|
||||
|
||||
<!-- Text: Learnlytics -->
|
||||
<text x="100" y="140" fill="#E0E1DD" font-family="Arial, sans-serif" font-size="20" font-weight="bold">Learnlytics</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/logo_IFN.png
Normal file
BIN
assets/logo_IFN.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
198
assets/logo_IFN.svg
Normal file
198
assets/logo_IFN.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 108 KiB |
@ -52,6 +52,14 @@ class Group(BaseModel):
|
||||
created_at = DateTimeField(default=datetime.now)
|
||||
|
||||
|
||||
class Study(BaseModel):
|
||||
'''
|
||||
Table for Storing a Study Program
|
||||
'''
|
||||
name = CharField()
|
||||
created_at = DateTimeField(default=datetime.now)
|
||||
|
||||
|
||||
class Student(BaseModel):
|
||||
'''
|
||||
Table for Storing a Student and linking him to appropiate Tables
|
||||
@ -59,6 +67,7 @@ class Student(BaseModel):
|
||||
prename = CharField()
|
||||
surname = CharField()
|
||||
sex = CharField()
|
||||
study = ForeignKeyField(Study, backref='study')
|
||||
class_id = ForeignKeyField(Class, backref='class')
|
||||
group_id = ForeignKeyField(Group, backref='group')
|
||||
grader = CharField()
|
||||
|
@ -3,6 +3,7 @@ from typing import Any
|
||||
import inspect
|
||||
from abc import ABC, abstractmethod
|
||||
import weakref
|
||||
from collections import Counter
|
||||
|
||||
from PIL import ImageColor
|
||||
from colour import Color
|
||||
@ -95,9 +96,17 @@ class BaseGrading(Mapping, ABC):
|
||||
@abstractmethod
|
||||
def has_passed(self, value: int | float, max: int | float) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def has_passed_course(self, values: list[int | float], maxs: list[int | float]) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_grade(self, value: int | float, max: int | float) -> str | int:
|
||||
def get_grade(self, value: int | float, max: int | float, is_html: bool) -> str | int:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_final_grade(self, values: list[int | float], maxs: list[int | float], is_html: bool) -> str | int:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@ -114,24 +123,42 @@ class BaseGrading(Mapping, ABC):
|
||||
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 has_passed_course(self, values: list[int | float], maxs: list[int | float]) -> bool:
|
||||
checks = [self.has_passed(value, max) for value, max in zip(values, maxs)]
|
||||
check = Counter(checks)
|
||||
return check[False] < 2
|
||||
|
||||
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(self, value: int | float, max: int | float, is_html=True) -> str:
|
||||
if is_html:
|
||||
return '✓' if self.has_passed(value, max) else '✗'
|
||||
return 'Passed' if self.has_passed(value, max) else 'Failed'
|
||||
|
||||
def get_final_grade(self, values: list[int | float], maxs: int | float, is_html=True) -> str:
|
||||
if is_html:
|
||||
return '✓' if self.has_passed_course(values, maxs) else '✗'
|
||||
return 'Passed' if self.has_passed_course(values, maxs) else 'Failed'
|
||||
|
||||
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):
|
||||
|
||||
class StdGermanHighSchoolGrading(BaseGrading):
|
||||
def has_passed(self, value: int | float, max: int | float) -> bool:
|
||||
return value/max >= 0.45
|
||||
|
||||
def has_passed_course(self, values: list[int | float], maxs: list[int | float]) -> bool:
|
||||
return True
|
||||
|
||||
def search_grade(self, value: float) -> int:
|
||||
if value <= 0:
|
||||
@ -146,14 +173,50 @@ class StdGermanGrading(BaseGrading):
|
||||
searched -= 1
|
||||
return searched
|
||||
|
||||
def get_grade(self, value: int | float, max: int | float) -> int:
|
||||
def get_grade(self, value: int | float, max: int | float, is_html=False) -> int:
|
||||
return self.search_grade(value/max)
|
||||
|
||||
def get_final_grade(self, values: list[int | float], maxs: list[int | float], is_html=False) -> int:
|
||||
return self.search_grade(sum(values)/sum(maxs))
|
||||
|
||||
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]
|
||||
|
||||
|
||||
class StdGermanMiddleSchoolGrading(BaseGrading):
|
||||
def has_passed(self, value: int | float, max: int | float) -> bool:
|
||||
return value/max >= 0.45
|
||||
|
||||
def has_passed_course(self, values: list[int | float], maxs: list[int | float]) -> bool:
|
||||
return True
|
||||
|
||||
def search_grade(self, value: float) -> int:
|
||||
if value <= 0:
|
||||
return max(self.schema.keys())
|
||||
|
||||
searched = min(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, is_html=False) -> int:
|
||||
return self.search_grade(value/max)
|
||||
|
||||
def get_final_grade(self, values: list[int | float], maxs: list[int | float], is_html=False) -> int:
|
||||
return self.search_grade(sum(values)/sum(maxs))
|
||||
|
||||
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,
|
||||
@ -165,7 +228,7 @@ Std50PercentRule = StdPercentRule({
|
||||
"Failed": 0.0
|
||||
}, "Std50PercentRule", "50%")
|
||||
|
||||
StdGermanGradingMiddleSchool = StdGermanGrading({
|
||||
StdGermanGradingMiddleSchool = StdGermanMiddleSchoolGrading({
|
||||
1: 0.96,
|
||||
2: 0.80,
|
||||
3: 0.60,
|
||||
@ -174,7 +237,7 @@ StdGermanGradingMiddleSchool = StdGermanGrading({
|
||||
6: 0.00
|
||||
}, "StdGermanGradingMiddleSchool", "Mittelstufe")
|
||||
|
||||
StdGermanGradingHighSchool = StdGermanGrading({
|
||||
StdGermanGradingHighSchool = StdGermanHighSchoolGrading({
|
||||
15: 0.95,
|
||||
14: 0.90,
|
||||
13: 0.85,
|
||||
@ -194,5 +257,5 @@ StdGermanGradingHighSchool = StdGermanGrading({
|
||||
}, "StdGermanGradingHighSchool", "Oberstufe")
|
||||
|
||||
|
||||
#print(StdGermanGradingHighSchool.get_grade(0.0, 24))
|
||||
#print(StdGermanGradingMiddleSchool.get_grade(189.5, 242))
|
||||
|
||||
|
@ -5,7 +5,8 @@ from .student_list import student_list
|
||||
from .student_graph import student_graph
|
||||
from .group_graph import group_graph
|
||||
from .class_graph import class_graph
|
||||
|
||||
from .submission_table import submission_table
|
||||
from .document_creator import document_creator
|
||||
|
||||
def analyzer_docking_splits() -> List[hello_imgui.DockingSplit]:
|
||||
split_main_misc = hello_imgui.DockingSplit()
|
||||
@ -51,17 +52,24 @@ def set_analyzer_layout() -> List[hello_imgui.DockableWindow]:
|
||||
class_info.label = "Class Analyzer"
|
||||
class_info.dock_space_name = "MainDockSpace"
|
||||
class_info.gui_function = lambda: class_graph()
|
||||
|
||||
#student_ranking = hello_imgui.DockableWindow()
|
||||
#student_ranking.label = "Ranking"
|
||||
#student_ranking.dock_space_name = "MainDockSpace"
|
||||
#student_ranking.gui_function = lambda: ranking()
|
||||
|
||||
submission_info = hello_imgui.DockableWindow()
|
||||
submission_info.label = "Submissions"
|
||||
submission_info.dock_space_name = "MainDockSpace"
|
||||
submission_info.gui_function = lambda: submission_table()
|
||||
|
||||
document = hello_imgui.DockableWindow()
|
||||
document.label = "Document Creator"
|
||||
document.dock_space_name = "MainDockSpace"
|
||||
document.gui_function = lambda: document_creator()
|
||||
|
||||
return [
|
||||
student_selector,
|
||||
student_info,
|
||||
group_info,
|
||||
class_info
|
||||
class_info,
|
||||
submission_info,
|
||||
document
|
||||
]
|
||||
|
||||
def analyzer_layout() -> hello_imgui.DockingParams:
|
||||
|
@ -2,15 +2,28 @@ from imgui_bundle import (
|
||||
imgui,
|
||||
immapp,
|
||||
imgui_md,
|
||||
im_file_dialog,
|
||||
immvision,
|
||||
ImVec2
|
||||
)
|
||||
|
||||
from peewee import fn
|
||||
from slugify import slugify
|
||||
import cairosvg
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess, os, platform
|
||||
from datetime import datetime
|
||||
from itertools import compress
|
||||
import io
|
||||
|
||||
from dbmodel import *
|
||||
from grader import get_grader, get_gradings
|
||||
from .analyzer_state import AnalyzerState
|
||||
from .plotter import plot_html
|
||||
from .plotter import plot_pdf
|
||||
from pdf import *
|
||||
|
||||
state = AnalyzerState()
|
||||
|
||||
@ -25,7 +38,7 @@ def ranking(class_id: int) -> None:
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
statics.data = [
|
||||
(student, Submission.select(fn.SUM(Submission.points)).where(Submission.student_id == student.id).scalar())
|
||||
(f"{student.prename} {student.surname}", Submission.select(fn.SUM(Submission.points)).where(Submission.student_id == student.id).scalar())
|
||||
for student in Student.select().where(Student.class_id == class_id)
|
||||
]
|
||||
statics.data.sort(key = lambda tup: tup[1], reverse=True)
|
||||
@ -34,20 +47,368 @@ def ranking(class_id: int) -> None:
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
imgui_md.render(f"# Ranking - {Class.get_by_id(statics.class_id).name}")
|
||||
imgui.text("")
|
||||
imgui_md.render_unindented(f"# Ranking - {Class.get_by_id(statics.class_id).name}")
|
||||
|
||||
if len(statics.data) < 1:
|
||||
return
|
||||
|
||||
if imgui.begin_table("Ranking1", len(statics.data), imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
if imgui.begin_table("Ranking", 3, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
for n, d in enumerate(statics.data, start=1):
|
||||
student, points = d
|
||||
if points.is_integer():
|
||||
points = int(points)
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
#imgui.set_next_item_width(-1)
|
||||
imgui.text(f"{n}. {student.prename} {student.surname} - {points} Points")
|
||||
imgui.text(f"{n}.")
|
||||
imgui.table_next_column()
|
||||
imgui.text(student)
|
||||
imgui.table_next_column()
|
||||
imgui.text(f"{points} Points")
|
||||
imgui.end_table()
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def lecture_list(class_id: int) -> None:
|
||||
imgui_md.render_unindented("# Lectures")
|
||||
|
||||
if class_id < 1:
|
||||
return
|
||||
|
||||
statics = lecture_list
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
statics.data = [
|
||||
(lecture, Submission.select().where(Submission.lecture_id == lecture.id).count())
|
||||
for lecture in Lecture.select().where(Lecture.class_id == class_id)
|
||||
]
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
|
||||
if imgui.button("Add Lecture"):
|
||||
imgui.open_popup("Lecture")
|
||||
|
||||
if add_lecture(statics.class_id):
|
||||
statics.inited = False
|
||||
|
||||
if len(statics.data) < 1:
|
||||
return
|
||||
|
||||
if imgui.begin_table("Lectures", 2, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
for n, d in enumerate(statics.data, start=1):
|
||||
lecture, sub_count = d
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
|
||||
if imgui.button(f"X##{lecture.title}"):
|
||||
lecture.delete_instance()
|
||||
statics.inited = False
|
||||
|
||||
imgui.same_line()
|
||||
imgui.text(lecture.title)
|
||||
imgui.table_next_column()
|
||||
imgui.text(f"{lecture.points} Points")
|
||||
imgui.end_table()
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def add_lecture(class_id: int) -> bool:
|
||||
statics = add_lecture
|
||||
|
||||
if not statics.inited:
|
||||
statics.title = str()
|
||||
statics.points = float()
|
||||
statics.inited = True
|
||||
|
||||
center = imgui.get_main_viewport().get_center()
|
||||
imgui.set_next_window_pos(center, imgui.Cond_.appearing.value, ImVec2(0.5, 0.5))
|
||||
|
||||
if imgui.begin_popup("Lecture", imgui.WindowFlags_.always_auto_resize.value):
|
||||
imgui_md.render_unindented("# New Lecture")
|
||||
|
||||
_, statics.title = imgui.input_text("Title", statics.title)
|
||||
|
||||
_, statics.points = imgui.input_float("Points", statics.points, 0.5, 2.0, "%.1f")
|
||||
|
||||
if statics.points < 0:
|
||||
statics.points = float()
|
||||
|
||||
if imgui.button("Add"):
|
||||
lecture = Lecture.create(
|
||||
title = statics.title,
|
||||
points = statics.points,
|
||||
class_id = class_id
|
||||
)
|
||||
|
||||
sub_data = [
|
||||
{"student_id": s.id, "lecture_id": lecture.id, "class_id": class_id, "points": 0.0}
|
||||
for s in Student.select().where(Student.class_id == class_id)
|
||||
]
|
||||
|
||||
Submission.insert_many(sub_data).execute()
|
||||
|
||||
statics.inited = False
|
||||
imgui.close_current_popup()
|
||||
imgui.end_popup()
|
||||
return True
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Cancel"):
|
||||
imgui.close_current_popup()
|
||||
statics.inited = False
|
||||
|
||||
imgui.end_popup()
|
||||
|
||||
return False
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def group_list(class_id: int) -> None:
|
||||
imgui_md.render_unindented('# Groups')
|
||||
|
||||
if class_id < 1:
|
||||
return
|
||||
|
||||
statics = group_list
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
statics.groups = list(Group.select().where(Group.class_id == class_id))
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
if imgui.button("Add Group"):
|
||||
imgui.open_popup("Group")
|
||||
|
||||
if add_group(statics.class_id):
|
||||
statics.inited = False
|
||||
|
||||
if imgui.begin_table("Groups", 2, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
for n, group in enumerate(statics.groups, start=1):
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
|
||||
if group.name != 'NoGroup':
|
||||
if imgui.button(f"X##{group.name}"):
|
||||
# Put Students in NoGroup
|
||||
students = Student.select().where(Student.group_id == group.id)
|
||||
nogroup = Group.select().where(Group.class_id == statics.class_id and Group.name == 'NoGroup').get()
|
||||
for student in students:
|
||||
student.group_id = nogroup.id
|
||||
with db.atomic():
|
||||
Student.bulk_update(students, fields=[Student.group_id], batch_size=50)
|
||||
group.delete_instance()
|
||||
statics.inited = False
|
||||
|
||||
imgui.same_line()
|
||||
imgui.text(group.name)
|
||||
imgui.table_next_column()
|
||||
imgui.text(f"{group.project}")
|
||||
imgui.end_table()
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def add_group(class_id: int) -> bool:
|
||||
statics = add_group
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
statics.name = str()
|
||||
statics.project = str()
|
||||
|
||||
nogroup = None
|
||||
for group in Group.select().where(Group.class_id == class_id):
|
||||
if group.name == 'NoGroup':
|
||||
nogroup = group
|
||||
break
|
||||
|
||||
statics.students = list(Student.select().where(Student.class_id == class_id and Student.group_id == nogroup.id))
|
||||
statics.selected = [False] * len(statics.students)
|
||||
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
center = imgui.get_main_viewport().get_center()
|
||||
imgui.set_next_window_pos(center, imgui.Cond_.appearing.value, ImVec2(0.5, 0.5))
|
||||
|
||||
if imgui.begin_popup("Group", imgui.WindowFlags_.always_auto_resize.value):
|
||||
imgui_md.render_unindented("# New Group")
|
||||
|
||||
_, statics.name = imgui.input_text("Name", statics.name)
|
||||
|
||||
_, statics.project = imgui.input_text("Project", statics.project)
|
||||
|
||||
for n, student in enumerate(statics.students):
|
||||
changed, _ = imgui.checkbox(f'{student.prename} {student.surname}', statics.selected[n])
|
||||
if changed:
|
||||
statics.selected[n] = not statics.selected[n]
|
||||
|
||||
if imgui.button("Add"):
|
||||
if not statics.name:
|
||||
imgui.close_current_popup()
|
||||
imgui.end_popup()
|
||||
return False
|
||||
|
||||
if not statics.project:
|
||||
statics.project = "NoProject"
|
||||
|
||||
group = Group.create(
|
||||
name = statics.name,
|
||||
project = statics.project,
|
||||
has_passed = False,
|
||||
class_id = class_id
|
||||
)
|
||||
|
||||
students = list(compress(statics.students, statics.selected))
|
||||
if students:
|
||||
for student in students:
|
||||
student.group_id = group.id
|
||||
with db.atomic():
|
||||
Student.bulk_update(students, fields=[Student.group_id], batch_size=50)
|
||||
|
||||
statics.inited = False
|
||||
imgui.close_current_popup()
|
||||
imgui.end_popup()
|
||||
return True
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Cancel"):
|
||||
imgui.close_current_popup()
|
||||
statics.inited = False
|
||||
|
||||
imgui.end_popup()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def student_list(class_id: int) -> None:
|
||||
imgui_md.render_unindented('# Students')
|
||||
|
||||
if class_id < 1:
|
||||
return
|
||||
|
||||
statics = student_list
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
statics.students = list(Student.select().where(Student.class_id == class_id))
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
if imgui.button("Add Student"):
|
||||
imgui.open_popup("Student")
|
||||
|
||||
if add_student(statics.class_id):
|
||||
statics.inited = False
|
||||
|
||||
if imgui.begin_table("Students", 2, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
for n, student in enumerate(statics.students, start=1):
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
|
||||
if imgui.button(f"X##{student.surname}{student.id}"):
|
||||
Submission.delete().where(Submission.student_id == student.id).execute()
|
||||
student.delete_instance()
|
||||
statics.inited = False
|
||||
imgui.end_table()
|
||||
return
|
||||
|
||||
imgui.same_line()
|
||||
imgui.text(f'{student.prename} {student.surname}')
|
||||
#imgui.table_next_column()
|
||||
#imgui.text(f"{group.project}")
|
||||
imgui.end_table()
|
||||
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from pprint import pprint
|
||||
@immapp.static(inited=False)
|
||||
def add_student(class_id: int) -> bool:
|
||||
statics = add_student
|
||||
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
|
||||
statics.prename = str()
|
||||
statics.surname = str()
|
||||
|
||||
statics.genders = ["Male", "Female"]
|
||||
statics.gender_select = 0
|
||||
|
||||
statics.studys = list(Study.select())
|
||||
statics.study_labels = [study.name for study in statics.studys]
|
||||
statics.study_select = 0
|
||||
|
||||
statics.groups = list(Group.select().where(Group.class_id == class_id))
|
||||
statics.group_labels = [group.name for group in statics.groups]
|
||||
statics.group_select = 0
|
||||
|
||||
statics.graders = [grader.alt_name for grader in get_gradings()]
|
||||
statics.grader_select = 0
|
||||
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id or len(statics.studys) != Study.select().count() or len(statics.groups) != Group.select().where(Group.class_id == statics.class_id).count():
|
||||
statics.inited = False
|
||||
|
||||
center = imgui.get_main_viewport().get_center()
|
||||
imgui.set_next_window_pos(center, imgui.Cond_.appearing.value, ImVec2(0.5, 0.5))
|
||||
|
||||
if imgui.begin_popup("Student", imgui.WindowFlags_.always_auto_resize.value):
|
||||
imgui_md.render_unindented("# New Student")
|
||||
|
||||
_, statics.prename = imgui.input_text("First Name", statics.prename)
|
||||
_, statics.surname = imgui.input_text("Last Name", statics.surname)
|
||||
|
||||
_, statics.gender_select = imgui.combo("Gender", statics.gender_select, statics.genders)
|
||||
|
||||
_, statics.study_select = imgui.combo("Study Progamm", statics.study_select, statics.study_labels)
|
||||
|
||||
_, statics.group_select = imgui.combo("Group", statics.group_select, statics.group_labels)
|
||||
|
||||
_, statics.grader_select = imgui.combo("Grader", statics.grader_select, statics.graders)
|
||||
|
||||
if imgui.button("Add"):
|
||||
s = Student.create(
|
||||
prename = statics.prename,
|
||||
surname = statics.surname,
|
||||
sex = statics.genders[statics.gender_select],
|
||||
study_id = statics.studys[statics.study_select].id,
|
||||
class_id = class_id,
|
||||
group_id = statics.groups[statics.group_select].id,
|
||||
grader = statics.graders[statics.grader_select]
|
||||
)
|
||||
|
||||
data = [
|
||||
{'student_id': s.id, 'lecture_id': lecture.id, 'class_id': s.class_id, 'points': 0.0}
|
||||
for lecture in Lecture.select().where(Lecture.class_id == s.class_id)
|
||||
]
|
||||
Submission.insert_many(data).execute()
|
||||
|
||||
statics.inited = False
|
||||
imgui.close_current_popup()
|
||||
imgui.end_popup()
|
||||
return True
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Cancel"):
|
||||
imgui.close_current_popup()
|
||||
statics.inited = False
|
||||
|
||||
imgui.end_popup()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def class_graph() -> None:
|
||||
if db.is_closed():
|
||||
imgui.text("No DB loaded")
|
||||
@ -55,22 +416,28 @@ def class_graph() -> None:
|
||||
|
||||
w, h = imgui.get_content_region_avail()
|
||||
|
||||
if imgui.begin_child("Ranking", ImVec2(w*0.3, h*0.5), imgui.ChildFlags_.borders.value):
|
||||
ranking(state.class_id)
|
||||
imgui.end_child()
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.begin_child("Presentations", ImVec2(w*0.7, h*0.7), imgui.ChildFlags_.borders.value):
|
||||
html = Path("/storage/programming/Learnlytics/assets/covid_faelle_MeWi_2.html")
|
||||
plot_html(html)
|
||||
if imgui.begin_child("Group1", ImVec2(w, h*0.5)):
|
||||
w1, h1 = imgui.get_content_region_avail()
|
||||
if imgui.begin_child("Groups", ImVec2(w1/3, h1), imgui.ChildFlags_.borders.value):
|
||||
group_list(state.class_id)
|
||||
imgui.end_child()
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Open in Browser"):
|
||||
# I Hate everything about it
|
||||
if platform.system() == 'Darwin': # MacOS
|
||||
subprocess.Popen(('open', html))
|
||||
elif platform.system() == 'Windows': # Windows
|
||||
os.startfile(html)
|
||||
else: # Linux & Variants
|
||||
subprocess.Popen(('xdg-open', html))
|
||||
|
||||
if imgui.begin_child("Lectures", ImVec2(w1/3, h1), imgui.ChildFlags_.borders.value):
|
||||
lecture_list(state.class_id)
|
||||
imgui.end_child()
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.begin_child("Students", ImVec2(w1*0.32, h1), imgui.ChildFlags_.borders.value):
|
||||
student_list(state.class_id)
|
||||
imgui.end_child()
|
||||
imgui.end_child()
|
||||
|
||||
if imgui.begin_child("Group2", ImVec2(w, h*0.5)):
|
||||
w1, h1 = imgui.get_content_region_avail()
|
||||
if imgui.begin_child("Ranking", ImVec2(w1/3, h1), imgui.ChildFlags_.borders.value):
|
||||
ranking(state.class_id)
|
||||
imgui.end_child()
|
||||
imgui.end_child()
|
||||
|
259
learnlytics/gui/analyzer/document_creator.py
Normal file
259
learnlytics/gui/analyzer/document_creator.py
Normal file
@ -0,0 +1,259 @@
|
||||
from imgui_bundle import (
|
||||
imgui,
|
||||
immapp,
|
||||
imgui_md,
|
||||
immvision,
|
||||
im_file_dialog,
|
||||
ImVec2
|
||||
)
|
||||
|
||||
from pathlib import Path
|
||||
from itertools import compress
|
||||
import io
|
||||
import pickle
|
||||
from dataclasses import dataclass
|
||||
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import cairosvg
|
||||
from slugify import slugify
|
||||
|
||||
from dbmodel import *
|
||||
from pdf import *
|
||||
from .plotter import plot_pdf
|
||||
from .analyzer_state import AnalyzerState
|
||||
state = AnalyzerState()
|
||||
|
||||
|
||||
@dataclass
|
||||
class DocumentProperties:
|
||||
logo: Path
|
||||
save_dir: Path
|
||||
file_name: str
|
||||
author: str
|
||||
|
||||
|
||||
def svg_to_png(svg: Path) -> Image:
|
||||
with open(svg) as f:
|
||||
svg = f.read()
|
||||
return Image.open(io.BytesIO(cairosvg.svg2png(svg))).convert("RGBA")
|
||||
|
||||
|
||||
def dump_properties(properties: DocumentProperties) -> None:
|
||||
with open('./pickles/document_properties.pkl', 'wb') as f:
|
||||
pickle.dump(properties, f)
|
||||
|
||||
|
||||
def load_properties() -> DocumentProperties:
|
||||
try:
|
||||
with open('./pickles/document_properties.pkl', 'rb') as f:
|
||||
properties = pickle.load(f)
|
||||
return properties
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def document_properties(class_id: int) -> None:
|
||||
# TO DO: Store Properties persistent
|
||||
statics = document_properties
|
||||
|
||||
if not statics.inited:
|
||||
statics.properties = load_properties()
|
||||
if not statics.properties:
|
||||
statics.properties = DocumentProperties(
|
||||
logo = Path("./assets/learnlytics.svg"),
|
||||
save_dir = Path.home(),
|
||||
file_name = "document",
|
||||
author = "Learnlytics"
|
||||
)
|
||||
statics.image = svg_to_png(statics.properties.logo) if statics.properties.logo.suffix == ".svg" else Image.open(statics.properties.logo)
|
||||
statics.inited = True
|
||||
|
||||
imgui_md.render_unindented('### Properties')
|
||||
imgui.text("")
|
||||
|
||||
if imgui.begin_table("Properties", 2, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text("Author:")
|
||||
imgui.table_next_column()
|
||||
changed, statics.properties.author = imgui.input_text("", statics.properties.author)
|
||||
if changed:
|
||||
dump_properties(statics.properties)
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
if imgui.button("Add Logo"):
|
||||
im_file_dialog.FileDialog.instance().open(
|
||||
"SelectLogo", "Open Logo", "Logo (*.png; *.jpg; *.jpeg; *.svg){.png,.jpg,.jpeg,.svg}", False, str(Path.home())
|
||||
)
|
||||
imgui.table_next_column()
|
||||
imgui.text(str(statics.properties.logo))
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
if imgui.button("Save to"):
|
||||
im_file_dialog.FileDialog.instance().open(
|
||||
"SelectSaveDir", "Select Save Directory", "", False, str(Path.home())
|
||||
)
|
||||
imgui.table_next_column()
|
||||
imgui.text(str(statics.properties.save_dir))
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
if imgui.button("Generate Name"):
|
||||
date = datetime.now()
|
||||
statics.properties.file_name = slugify(f'{Class.get_by_id(class_id).name}_{date.strftime("%a_%d.%m.%Y_%H:%M")}')
|
||||
imgui.table_next_column()
|
||||
changed, statics.properties.file_name = imgui.input_text(".pdf", statics.properties.file_name)
|
||||
if changed:
|
||||
dump_properties(statics.properties)
|
||||
imgui.end_table()
|
||||
|
||||
if statics.properties.logo:
|
||||
w, h = imgui.get_content_region_avail()
|
||||
x, y = statics.image.size
|
||||
size = (
|
||||
int(x) if x < w*0.8 else int(w*0.8),
|
||||
int(y) if y < h*0.5 else int(h*0.5)
|
||||
)
|
||||
immvision.image_display("Logo", np.array(statics.image), size, True)
|
||||
|
||||
if im_file_dialog.FileDialog.instance().is_done("SelectLogo"):
|
||||
if im_file_dialog.FileDialog.instance().has_result():
|
||||
statics.properties.logo = im_file_dialog.FileDialog.instance().get_result()
|
||||
statics.properties.logo = Path(statics.properties.logo.path()).relative_to(Path('.').resolve())
|
||||
statics.image = svg_to_png(statics.properties.logo) if statics.properties.logo.suffix == ".svg" else Image.open(statics.properties.logo)
|
||||
dump_properties(statics.properties)
|
||||
im_file_dialog.FileDialog.instance().close()
|
||||
|
||||
if im_file_dialog.FileDialog.instance().is_done("SelectSaveDir"):
|
||||
if im_file_dialog.FileDialog.instance().has_result():
|
||||
save_dir = im_file_dialog.FileDialog.instance().get_result()
|
||||
statics.properties.save_dir = Path(save_dir.path())
|
||||
dump_properties(statics.properties)
|
||||
im_file_dialog.FileDialog.instance().close()
|
||||
|
||||
return statics.properties
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def filters() -> list[Study]:
|
||||
statics = filters
|
||||
|
||||
if not statics.inited:
|
||||
statics.studys = list(Study.select())
|
||||
statics.checked = [True] * len(statics.studys)
|
||||
statics.inited = True
|
||||
|
||||
imgui_md.render_unindented("### Study Filters")
|
||||
imgui.text("")
|
||||
|
||||
if imgui.begin_table("Create", 2, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
for n, study in enumerate(statics.studys):
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
_, statics.checked[n] = imgui.checkbox(f"##{study.name}", statics.checked[n])
|
||||
imgui.table_next_column()
|
||||
imgui.text(study.name)
|
||||
imgui.end_table()
|
||||
|
||||
return list(compress(statics.studys, statics.checked))
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def order_by() -> str:
|
||||
statics = order_by
|
||||
|
||||
if not statics.inited:
|
||||
statics.order_by = ["Study Program", "Group", "Ranking"]
|
||||
statics.selected = 1
|
||||
statics.inited = True
|
||||
|
||||
imgui_md.render_unindented("### Order by")
|
||||
imgui.text("")
|
||||
if imgui.begin_table("Order", 2, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
for n, order in enumerate(statics.order_by):
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
if imgui.radio_button(f"## {order}", statics.selected == n):
|
||||
statics.selected = n
|
||||
imgui.table_next_column()
|
||||
imgui.text(order)
|
||||
imgui.end_table()
|
||||
|
||||
return statics.order_by[statics.selected]
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def create_document(class_id: int) -> None:
|
||||
imgui_md.render_unindented("# Create Documents")
|
||||
|
||||
statics = create_document
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
|
||||
try:
|
||||
with open("./pickles/last_file.txt") as f:
|
||||
statics.file = Path(f.read())
|
||||
except FileNotFoundError:
|
||||
statics.file = None
|
||||
|
||||
statics.order_by = None
|
||||
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
w, h = imgui.get_content_region_avail()
|
||||
if imgui.begin_child("PDF", ImVec2(w*0.45, h), imgui.ChildFlags_.borders.value):
|
||||
imgui_md.render_unindented('### Document')
|
||||
imgui.text("")
|
||||
if statics.file:
|
||||
plot_pdf(statics.file, "Viewer")
|
||||
else:
|
||||
imgui.text("No document created yet.")
|
||||
imgui.end_child()
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.begin_child("Filters", ImVec2(w*0.54, h), imgui.ChildFlags_.borders.value):
|
||||
w1, h1 = imgui.get_content_region_avail()
|
||||
if imgui.begin_child("Study Filters", ImVec2(w1, h1*0.2)):
|
||||
study_filters = filters()
|
||||
imgui.end_child()
|
||||
|
||||
if imgui.begin_child("Order By", ImVec2(w1, h1*0.2)):
|
||||
statics.order_by = order_by()
|
||||
imgui.end_child()
|
||||
|
||||
if imgui.begin_child("Document Properties", ImVec2(w1, h1*0.58)):
|
||||
properties = document_properties(statics.class_id)
|
||||
if imgui.button("Create"):
|
||||
pdf, css = get_pdf(statics.class_id)
|
||||
header = get_pdf_header(state.class_id, [study.name for study in study_filters], properties.author, properties.logo)
|
||||
|
||||
match statics.order_by:
|
||||
case "Group":
|
||||
sections = create_group_pdf(study_filters)
|
||||
|
||||
case "Study Program":
|
||||
sections = create_study_pdf(study_filters)
|
||||
|
||||
case 'Ranking':
|
||||
sections = create_ranking_pdf(study_filters)
|
||||
|
||||
pdf.add_section(header, user_css=css)
|
||||
for section in sections:
|
||||
pdf.add_section(section, user_css=css)
|
||||
statics.file = properties.save_dir / (properties.file_name + ".pdf")
|
||||
pdf.save(statics.file)
|
||||
with open("./pickles/last_file.txt", "w") as f:
|
||||
f.write(str(statics.file))
|
||||
|
||||
imgui.end_child()
|
||||
imgui.end_child()
|
||||
imgui.same_line()
|
||||
|
||||
|
||||
def document_creator():
|
||||
create_document(state.class_id)
|
@ -23,6 +23,7 @@ def header(group_id: int) -> None:
|
||||
statics = header
|
||||
|
||||
if group_id < 1:
|
||||
imgui_md.render_unindented("# Student Ranking")
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
@ -46,6 +47,13 @@ def header(group_id: int) -> None:
|
||||
if changed:
|
||||
statics.group.has_passed = not statics.group.has_passed
|
||||
statics.group.save()
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Edit Group"):
|
||||
imgui.open_popup("EditGroup")
|
||||
|
||||
if edit_group(statics.group_id):
|
||||
statics.inited = False
|
||||
|
||||
if imgui.begin_table("Students", len(statics.data), imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
for n, d in enumerate(statics.data, start=1):
|
||||
@ -57,7 +65,56 @@ def header(group_id: int) -> None:
|
||||
imgui.set_next_item_width(-1)
|
||||
imgui.text(f"{n}. {s.prename} {s.surname} - {points} Points")
|
||||
imgui.end_table()
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def edit_group(group_id: int) -> bool:
|
||||
statics = edit_group
|
||||
|
||||
if not statics.inited:
|
||||
statics.group_id = group_id
|
||||
statics.group = Group.get_by_id(group_id)
|
||||
|
||||
statics.name = statics.group.name
|
||||
statics.project = statics.group.project
|
||||
statics.has_passed = statics.group.has_passed
|
||||
statics.inited = True
|
||||
|
||||
if statics.group_id != group_id:
|
||||
statics.inited = False
|
||||
|
||||
center = imgui.get_main_viewport().get_center()
|
||||
imgui.set_next_window_pos(center, imgui.Cond_.appearing.value, ImVec2(0.5, 0.5))
|
||||
|
||||
if imgui.begin_popup("EditGroup", imgui.WindowFlags_.always_auto_resize.value):
|
||||
imgui_md.render_unindented(f"# Edit {statics.group.name}")
|
||||
|
||||
_, statics.name = imgui.input_text("Name", statics.name)
|
||||
|
||||
_, statics.project = imgui.input_text("Project", statics.project)
|
||||
|
||||
changed, _ = imgui.checkbox("Passed?", statics.has_passed)
|
||||
if changed:
|
||||
statics.has_passed = not statics.has_passed
|
||||
|
||||
if imgui.button("Change"):
|
||||
statics.group.name = statics.name
|
||||
statics.group.project = statics.project
|
||||
statics.group.has_passed = statics.has_passed
|
||||
statics.group.save()
|
||||
imgui.close_current_popup()
|
||||
imgui.end_popup()
|
||||
statics.inited = False
|
||||
return True
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Cancel"):
|
||||
imgui.close_current_popup()
|
||||
statics.inited = False
|
||||
|
||||
imgui.end_popup()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
@ -65,6 +122,7 @@ def plot(group_id: int) -> None:
|
||||
statics = plot
|
||||
|
||||
if group_id < 1:
|
||||
imgui_md.render_unindented("# Group Performance Graph")
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
@ -94,6 +152,7 @@ def presentation(group_id: int) -> None:
|
||||
statics = presentation
|
||||
|
||||
if group_id < 1:
|
||||
imgui_md.render_unindented("# Group Presentation")
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
@ -154,6 +213,10 @@ def group_plot(class_id: int) -> None:
|
||||
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
|
||||
if Student.select().where(Student.class_id == class_id).count() < 1:
|
||||
return
|
||||
|
||||
max_points = Lecture.select(fn.SUM(Lecture.points)).where(Lecture.class_id == class_id).scalar()
|
||||
data = {
|
||||
group.name:
|
||||
|
@ -105,7 +105,6 @@ def plot_html(html: Path, label: str = "Presentation") -> None:
|
||||
statics.quality = 100 if statics.high_quality else 10
|
||||
statics.reload = True
|
||||
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Back"):
|
||||
|
@ -10,6 +10,7 @@ import numpy as np
|
||||
from peewee import fn
|
||||
|
||||
from dbmodel import *
|
||||
from grader import get_gradings, get_grader
|
||||
|
||||
from .analyzer_state import AnalyzerState
|
||||
from .plotter import plot_bar_line_percentage
|
||||
@ -19,27 +20,41 @@ PROGRESS_BAR_COLOR = ImVec4(190, 190, 40, 255)/255
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def header(student_id: int) -> None:
|
||||
def header(student_id: int, reset: bool) -> None:
|
||||
statics = header
|
||||
|
||||
if student_id < 1:
|
||||
return
|
||||
|
||||
if reset:
|
||||
statics.inited = False
|
||||
|
||||
if not statics.inited:
|
||||
statics.student = Student.get_by_id(student_id)
|
||||
statics.study = Study.get_by_id(statics.student.study.id)
|
||||
|
||||
points = [sub.points for sub in Submission.select().where(Submission.student_id == student_id)]
|
||||
max_points = [lecture.points for lecture in Lecture.select().where(Lecture.class_id == statics.student.class_id)]
|
||||
statics.has_passed = get_grader(statics.student.grader).get_final_grade(points, max_points, False)
|
||||
statics.inited = True
|
||||
|
||||
if statics.student.id != student_id:
|
||||
statics.inited = False
|
||||
|
||||
imgui_md.render(f"# {statics.student.prename} {statics.student.surname}")
|
||||
imgui_md.render_unindented(f"# {statics.student.prename} {statics.student.surname} - {statics.has_passed}")
|
||||
imgui_md.render_unindented(f"Degree Program: **{statics.study.name}**")
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def plot(student_id: int) -> None:
|
||||
def plot(student_id: int, reset: bool) -> None:
|
||||
statics = plot
|
||||
|
||||
if student_id < 1:
|
||||
imgui_md.render_unindented("# Student Analyzer")
|
||||
return
|
||||
|
||||
if reset:
|
||||
statics.inited = False
|
||||
|
||||
if not statics.inited:
|
||||
statics.student = Student.get_by_id(student_id)
|
||||
@ -66,15 +81,144 @@ def plot(student_id: int) -> None:
|
||||
imgui.pop_style_color()
|
||||
plot_bar_line_percentage(statics.data, statics.labels, statics.avg*100)
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def dialog(student_id: int) -> None:
|
||||
imgui_md.render_unindented("# Student Attributes")
|
||||
|
||||
if student_id < 1:
|
||||
return
|
||||
|
||||
statics = dialog
|
||||
|
||||
if not statics.inited:
|
||||
statics.student_id = student_id
|
||||
statics.student = Student.get_by_id(student_id)
|
||||
|
||||
statics.prename = statics.student.prename
|
||||
statics.surname = statics.student.surname
|
||||
statics.sex = 0 if statics.student.sex == "Male" else 1
|
||||
|
||||
statics.studys = list(Study.select())
|
||||
s = Study.get_by_id(statics.student.study_id)
|
||||
statics.study = statics.studys.index(s)
|
||||
|
||||
statics.groups = list(Group.select().where(Group.class_id == statics.student.class_id))
|
||||
g = Group.get_by_id(statics.student.group_id)
|
||||
statics.group = statics.groups.index(g)
|
||||
|
||||
statics.graders = [grade.alt_name for grade in get_gradings()]
|
||||
statics.grader = statics.graders.index(statics.student.grader)
|
||||
|
||||
statics.inited = True
|
||||
|
||||
if statics.student_id != student_id:
|
||||
statics.inited = False
|
||||
|
||||
if imgui.begin_table("Attributes", 2):#, imgui.TableFlags_.sizing_fixed_fit.value):
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text("First Name")
|
||||
imgui.table_next_column()
|
||||
_, statics.prename = imgui.input_text("##First Name", statics.prename)
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text("Last Name")
|
||||
imgui.table_next_column()
|
||||
_, statics.surname = imgui.input_text("##Last Name", statics.surname)
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text("Sex")
|
||||
imgui.table_next_column()
|
||||
_, statics.sex = imgui.combo("##Sex", statics.sex, ["Male", "Female"])
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text("Study Program")
|
||||
imgui.table_next_column()
|
||||
_, statics.study = imgui.combo("##Study", statics.study, [s.name for s in statics.studys])
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text("Group")
|
||||
imgui.table_next_column()
|
||||
_, statics.group = imgui.combo("##Group", statics.group, [g.name for g in statics.groups])
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text("Grader")
|
||||
imgui.table_next_column()
|
||||
_, statics.grader = imgui.combo("##Grader", statics.grader, statics.graders)
|
||||
|
||||
imgui.end_table()
|
||||
|
||||
if imgui.button("Save"):
|
||||
statics.student.prename = statics.prename
|
||||
statics.student.surname = statics.surname
|
||||
statics.student.sex = "Male" if statics.sex == 0 else "Female"
|
||||
statics.student.study_id = statics.studys[statics.study].id
|
||||
statics.student.group_id = statics.groups[statics.group].id
|
||||
statics.student.grader = get_grader(statics.graders[statics.grader]).alt_name
|
||||
statics.student.save()
|
||||
statics.inited = False
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def lecture_lists(student_id: int) -> None:
|
||||
imgui_md.render_unindented("# Lectures")
|
||||
|
||||
if student_id < 1:
|
||||
return
|
||||
|
||||
statics = lecture_lists
|
||||
if not statics.inited:
|
||||
statics.student_id = student_id
|
||||
statics.student = Student.get_by_id(student_id)
|
||||
statics.subs = list(Submission.select().where(Submission.student_id == statics.student.id))
|
||||
statics.inited = True
|
||||
|
||||
if statics.student_id != student_id:
|
||||
statics.inited = False
|
||||
|
||||
if imgui.begin_table("Lecture List", 2, imgui.TableFlags_.none.value):
|
||||
for sub in statics.subs:
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text(sub.lecture_id.title)
|
||||
imgui.table_next_column()
|
||||
imgui.text(f'{int(sub.points) if sub.points.is_integer() else sub.points}/{sub.lecture_id.points} Points')
|
||||
imgui.end_table()
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def student_graph() -> None:
|
||||
if db.is_closed():
|
||||
imgui.text("No DB loaded")
|
||||
return
|
||||
|
||||
statics = student_graph
|
||||
if not statics.inited:
|
||||
statics.reset = False
|
||||
statics.inited = True
|
||||
|
||||
w, h = imgui.get_content_region_avail()
|
||||
|
||||
if imgui.begin_child("Header", ImVec2(w, h*0.5), imgui.ChildFlags_.borders.value):
|
||||
header(state.student_id)
|
||||
plot(state.student_id)
|
||||
if imgui.begin_child("Header", ImVec2(w, h*0.65), imgui.ChildFlags_.borders.value):
|
||||
header(state.student_id, statics.reset)
|
||||
plot(state.student_id, statics.reset)
|
||||
imgui.end_child()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
if imgui.begin_child("Dialog", ImVec2(w*0.4, h*0.34), imgui.ChildFlags_.borders.value):
|
||||
statics.reset = dialog(state.student_id)
|
||||
imgui.end_child()
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.begin_child("Lectures", ImVec2(w*0.3, h*0.34), imgui.ChildFlags_.borders.value):
|
||||
lecture_lists(state.student_id)
|
||||
imgui.end_child()
|
||||
|
@ -1,7 +1,8 @@
|
||||
from imgui_bundle import (
|
||||
imgui,
|
||||
immapp,
|
||||
imgui_md
|
||||
imgui_md,
|
||||
ImVec2
|
||||
)
|
||||
|
||||
from dbmodel import *
|
||||
@ -20,8 +21,119 @@ def class_selector() -> int:
|
||||
|
||||
labels = (c.name for c in Class.select())
|
||||
_, statics.selector = imgui.combo("##Classes", statics.selector, list(labels))
|
||||
|
||||
current = Class.select()[statics.selector].id
|
||||
|
||||
if imgui.button("New Class"):
|
||||
imgui.open_popup("NewC")
|
||||
new_class()
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Delete Class"):
|
||||
imgui.open_popup("DeleteC")
|
||||
if delete_class(current):
|
||||
statics.inited = False
|
||||
|
||||
imgui.separator()
|
||||
return Class.select()[statics.selector].id
|
||||
return current
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def new_class() -> None:
|
||||
statics = new_class
|
||||
|
||||
if not statics.inited:
|
||||
statics.name = str()
|
||||
statics.inited = True
|
||||
|
||||
center = imgui.get_main_viewport().get_center()
|
||||
imgui.set_next_window_pos(center, imgui.Cond_.appearing.value, ImVec2(0.5, 0.5))
|
||||
|
||||
if imgui.begin_popup("NewC"):
|
||||
imgui_md.render_unindented("# New Class")
|
||||
|
||||
_, statics.name = imgui.input_text("Name", statics.name)
|
||||
|
||||
if imgui.button("Add"):
|
||||
clas = Class.create(name=statics.name)
|
||||
Group.create(
|
||||
name="NoGroup",
|
||||
project="NoProject",
|
||||
has_passed=False,
|
||||
class_id = clas.id
|
||||
)
|
||||
imgui.close_current_popup()
|
||||
statics.inited = False
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Cancel"):
|
||||
imgui.close_current_popup()
|
||||
statics.inited = False
|
||||
|
||||
imgui.end_popup()
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def delete_class(class_id: int) -> bool:
|
||||
statics = delete_class
|
||||
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
statics.data = {
|
||||
"Students": Student.select().where(Student.class_id == class_id).count(),
|
||||
"Groups": Group.select().where(Group.class_id == class_id).count(),
|
||||
"Lectures": Lecture.select().where(Lecture.class_id == class_id).count(),
|
||||
"Submissions": Submission.select().where(Submission.class_id == class_id).count()
|
||||
}
|
||||
statics.inited = True
|
||||
statics.ret = False
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
center = imgui.get_main_viewport().get_center()
|
||||
imgui.set_next_window_pos(center, imgui.Cond_.appearing.value, ImVec2(0.5, 0.5))
|
||||
|
||||
size = imgui.get_main_viewport().size
|
||||
imgui.set_next_window_size(ImVec2(size.x * 0.1, size.y * 0.25))
|
||||
|
||||
if imgui.begin_popup("DeleteC"):
|
||||
imgui_md.render_unindented("# Delete Class")
|
||||
imgui_md.render_unindented("**Are you sure?**")
|
||||
imgui_md.render_unindented("This Operation deletes:")
|
||||
|
||||
if imgui.begin_table("Attributes", len(statics.data)):
|
||||
for attr, count in statics.data.items():
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text(attr)
|
||||
imgui.table_next_column()
|
||||
imgui.text(str(count))
|
||||
|
||||
imgui.end_table()
|
||||
|
||||
|
||||
if imgui.button("Delete"):
|
||||
Submission.delete().where(Submission.class_id == statics.class_id).execute()
|
||||
Group.delete().where(Group.class_id == statics.class_id).execute()
|
||||
Lecture.delete().where(Lecture.class_id == statics.class_id).execute()
|
||||
Student.delete().where(Student.class_id == statics.class_id).execute()
|
||||
Class.get_by_id(statics.class_id).delete_instance()
|
||||
imgui.close_current_popup()
|
||||
statics.inited = False
|
||||
statics.ret = True
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Cancel"):
|
||||
imgui.close_current_popup()
|
||||
statics.inited = False
|
||||
|
||||
imgui.end_popup()
|
||||
|
||||
return statics.ret
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def tree(class_id: int) -> None:
|
||||
@ -36,11 +148,19 @@ def tree(class_id: int) -> None:
|
||||
statics.selected = -1
|
||||
statics.ret = (-1, -1)
|
||||
statics.flags = imgui.TreeNodeFlags_.none.value
|
||||
statics.counter = 0
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
if statics.counter > 60:
|
||||
statics.data = {
|
||||
group: list(Student.select().where(Student.group_id == group.id))
|
||||
for group in Group.select().where(Group.class_id == statics.class_id)
|
||||
}
|
||||
statics.counter = 0
|
||||
|
||||
if imgui.button("Expand"):
|
||||
statics.flags = imgui.TreeNodeFlags_.default_open.value
|
||||
|
||||
@ -61,6 +181,7 @@ def tree(class_id: int) -> None:
|
||||
n += 1
|
||||
imgui.tree_pop()
|
||||
|
||||
statics.counter += 1
|
||||
return statics.ret
|
||||
|
||||
def student_list() -> None:
|
||||
|
94
learnlytics/gui/analyzer/submission_table.py
Normal file
94
learnlytics/gui/analyzer/submission_table.py
Normal file
@ -0,0 +1,94 @@
|
||||
from imgui_bundle import (
|
||||
imgui,
|
||||
immapp,
|
||||
imgui_md
|
||||
)
|
||||
|
||||
from dbmodel import *
|
||||
from .analyzer_state import AnalyzerState
|
||||
|
||||
state = AnalyzerState()
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def table(class_id: int) -> None:
|
||||
if class_id < 1:
|
||||
return
|
||||
|
||||
statics = table
|
||||
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
statics.table_flags = (
|
||||
imgui.TableFlags_.row_bg.value
|
||||
| imgui.TableFlags_.borders.value
|
||||
| imgui.TableFlags_.resizable.value
|
||||
| imgui.TableFlags_.sizing_stretch_same.value
|
||||
)
|
||||
|
||||
statics.students = Student.select().where(Student.class_id == statics.class_id)
|
||||
statics.lectures = Lecture.select().where(Lecture.class_id == statics.class_id)
|
||||
|
||||
statics.rows = len(statics.students)
|
||||
statics.cols = len(statics.lectures)
|
||||
statics.grid = [
|
||||
list(Submission.select().where(Submission.student_id == student.id))
|
||||
for student in statics.students
|
||||
]
|
||||
|
||||
statics.table_header = [f"{lecture.title} ({lecture.points})" for lecture in statics.lectures]
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
if statics.cols != Lecture.select().where(Lecture.class_id == statics.class_id).count():
|
||||
statics.inited = False
|
||||
|
||||
if len(statics.students) != Student.select().where(Student.class_id == statics.class_id).count():
|
||||
statics.inited = False
|
||||
|
||||
if imgui.begin_table("Student Grid", statics.cols+1, statics.table_flags):
|
||||
# Setup Header
|
||||
imgui.table_setup_column("Students")
|
||||
for header in statics.table_header:
|
||||
imgui.table_setup_column(header)
|
||||
imgui.table_headers_row()
|
||||
|
||||
# Fill Student names
|
||||
for row in range(statics.rows):
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
student = statics.students[row]
|
||||
imgui.text(f"{student.prename} {student.surname}")
|
||||
|
||||
for col in range(statics.cols):
|
||||
imgui.table_set_column_index(col+1)
|
||||
changed, value = imgui.input_float(f"##{statics.grid[row][col]}", statics.grid[row][col].points, 0.0, 0.0, "%.1f")
|
||||
|
||||
if changed:
|
||||
# Boundary Check
|
||||
if value < 0:
|
||||
value = 0
|
||||
if value > statics.lectures[col].points:
|
||||
value = statics.lectures[col].points
|
||||
|
||||
old_value = statics.grid[row][col].points
|
||||
statics.grid[row][col].points = value
|
||||
statics.grid[row][col].save()
|
||||
|
||||
student = statics.students[row]
|
||||
lecture = statics.lectures[col]
|
||||
sub = statics.grid[row][col]
|
||||
|
||||
imgui.end_table()
|
||||
|
||||
|
||||
def submission_table() -> None:
|
||||
|
||||
imgui_md.render_unindented("# Submissions")
|
||||
|
||||
if db.is_closed():
|
||||
imgui.text("No DB loaded")
|
||||
return
|
||||
|
||||
table(state.class_id)
|
@ -142,75 +142,6 @@ def select_file() -> None:
|
||||
# Update application state and reset selection result
|
||||
statics.res = None
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def table() -> None:
|
||||
statics = table
|
||||
|
||||
if db.is_closed():
|
||||
imgui.text("DB")
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.table_flags = (
|
||||
imgui.TableFlags_.row_bg.value
|
||||
| imgui.TableFlags_.borders.value
|
||||
| imgui.TableFlags_.resizable.value
|
||||
| imgui.TableFlags_.sizing_stretch_same.value
|
||||
)
|
||||
statics.class_id = None
|
||||
statics.inited = True
|
||||
|
||||
statics.students = Student.select().where(Student.class_id == statics.class_id)
|
||||
statics.lectures = Lecture.select().where(Lecture.class_id == statics.class_id)
|
||||
|
||||
statics.rows = len(statics.students)
|
||||
statics.cols = len(statics.lectures)
|
||||
statics.grid = list()
|
||||
|
||||
for student in statics.students:
|
||||
t_list = list()
|
||||
sub = Submission.select().where(Submission.student_id == student.id)
|
||||
for s in sub:
|
||||
t_list.append(s)
|
||||
statics.grid.append(t_list)
|
||||
|
||||
statics.table_header = [f"{lecture.title} ({lecture.points})" for lecture in statics.lectures]
|
||||
|
||||
if imgui.begin_table("Student Grid", statics.cols+1, statics.table_flags):
|
||||
# Setup Header
|
||||
imgui.table_setup_column("Students")
|
||||
for header in statics.table_header:
|
||||
imgui.table_setup_column(header)
|
||||
imgui.table_headers_row()
|
||||
|
||||
# Fill Student names
|
||||
for row in range(statics.rows):
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
student = statics.students[row]
|
||||
imgui.text(f"{student.prename} {student.surname}")
|
||||
|
||||
for col in range(statics.cols):
|
||||
imgui.table_set_column_index(col+1)
|
||||
changed, value = imgui.input_float(f"##{statics.grid[row][col]}", statics.grid[row][col].points, 0.0, 0.0, "%.1f")
|
||||
|
||||
if changed:
|
||||
# Boundary Check
|
||||
if value < 0:
|
||||
value = 0
|
||||
if value > statics.lectures[col].points:
|
||||
value = statics.lectures[col].points
|
||||
|
||||
old_value = statics.grid[row][col].points
|
||||
statics.grid[row][col].points = value
|
||||
statics.grid[row][col].save()
|
||||
|
||||
student = statics.students[row]
|
||||
lecture = statics.lectures[col]
|
||||
sub = statics.grid[row][col]
|
||||
|
||||
imgui.end_table()
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def editor() -> None:
|
||||
"""
|
||||
|
4
learnlytics/pdf/__init__.py
Normal file
4
learnlytics/pdf/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .utils import get_pdf, get_pdf_header
|
||||
from .ranking_pdf import create_ranking_pdf
|
||||
from .study_pdf import create_study_pdf
|
||||
from .group_pdf import create_group_pdf
|
98
learnlytics/pdf/document_style.css
Normal file
98
learnlytics/pdf/document_style.css
Normal file
@ -0,0 +1,98 @@
|
||||
/* All */
|
||||
@font-face {
|
||||
font-family: 'NexusSansPro-Regular';
|
||||
src: url('./assets/Nexus/NexusSansPro-Regular.otf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NexusSansPro-Bold';
|
||||
src: url('./assets/Nexus/NexusSansPro-Bold.otf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
* {
|
||||
font-size: 120%;
|
||||
font-family: 'NexusSansPro-Regular';
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Headers */
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
font-family: 'NexusSansPro-Bold';
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 22px;
|
||||
padding-bottom: -15px;
|
||||
font-family: 'NexusSansPro-Bold';
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
padding-bottom: -10px;
|
||||
font-family: 'NexusSansPro-Bold';
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 14px;
|
||||
padding-bottom: -15px;
|
||||
}
|
||||
|
||||
/* Image */
|
||||
img {
|
||||
padding: inherit;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
/* List */
|
||||
ol {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Miscelaneous */
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 3px solid #be1e3c;
|
||||
}
|
||||
|
||||
/* Table */
|
||||
table {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
/* Table Header */
|
||||
th {
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
font-family: 'NexusSansPro-Bold';
|
||||
}
|
||||
|
||||
/* Table Content */
|
||||
td {
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
padding-left: 10px;
|
||||
padding-top: -1px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
52
learnlytics/pdf/group_pdf.py
Normal file
52
learnlytics/pdf/group_pdf.py
Normal file
@ -0,0 +1,52 @@
|
||||
from dbmodel import *
|
||||
from grader import get_grader
|
||||
from markdown_pdf import Section
|
||||
|
||||
from functools import reduce
|
||||
import operator
|
||||
|
||||
def create_group_pdf(filter: list[Study]) -> list[Section]:
|
||||
sections = list()
|
||||
text = ''
|
||||
|
||||
filter = [(Student.study_id == study.id) for study in filter]
|
||||
expr = reduce(operator.or_, filter)
|
||||
students = list(Student.select().where(expr))
|
||||
|
||||
group_ids = set([student.group_id for student in students])
|
||||
group_filter = [(Group.id == id) for id in group_ids]
|
||||
group_expr = reduce(operator.or_, group_filter)
|
||||
|
||||
data = {
|
||||
group: [student for student in Student.select().where(Student.group_id == group.id) if student.study in filter]
|
||||
for group in Group.select().where(group_expr)
|
||||
}
|
||||
|
||||
max_points = [lecture.points for lecture in Lecture.select().where(Lecture.class_id == next(iter(data.keys())).class_id)]
|
||||
|
||||
n = 0
|
||||
for group, students in data.items():
|
||||
text += f'## {group.name}\n'
|
||||
text += f'#### Project: {group.project}\n'
|
||||
passed = '✓' if group.has_passed else '✗'
|
||||
text += f'Passed Exam {passed}\n'
|
||||
text += '|Student|Study Program|Passed?|\n'
|
||||
text += '|-|-|-|\n'
|
||||
for student in students:
|
||||
grader = get_grader(student.grader)
|
||||
overall_points = [sub.points for sub in Submission.select().where(Submission.student_id == student.id)]
|
||||
passed = grader.get_final_grade(overall_points, max_points)
|
||||
|
||||
text += f'|{student.prename} {student.surname}|{student.study.name}|{passed}|\n'
|
||||
text += '\n---\n\n'
|
||||
|
||||
n += 1
|
||||
if n == 2:
|
||||
sections.append(Section(text))
|
||||
n = 0
|
||||
text = ''
|
||||
|
||||
if text:
|
||||
sections.append(Section(text))
|
||||
|
||||
return sections
|
58
learnlytics/pdf/ranking_pdf.py
Normal file
58
learnlytics/pdf/ranking_pdf.py
Normal file
@ -0,0 +1,58 @@
|
||||
from dbmodel import *
|
||||
from peewee import fn
|
||||
from markdown_pdf import Section
|
||||
from functools import reduce
|
||||
from itertools import compress
|
||||
import operator
|
||||
from grader import get_grader
|
||||
from collections import Counter
|
||||
|
||||
|
||||
def create_header(number_of_students: int) -> str:
|
||||
text = '## Ranking\n'
|
||||
text += f'#### Number of Students: {number_of_students}\n'
|
||||
text += '|Student|Points|Percentage|Passed|\n'
|
||||
text += '|-|-|-|-|\n'
|
||||
return text
|
||||
|
||||
|
||||
def create_ranking_pdf(filter: list[int]) -> list[Section]:
|
||||
filter = [(Student.study_id == id) for id in filter]
|
||||
expr = reduce(operator.or_, filter)
|
||||
students = [
|
||||
(student, Submission.select(fn.SUM(Submission.points)).where(Submission.student_id == student.id).scalar())
|
||||
for student in Student.select().where(expr)
|
||||
]
|
||||
students.sort(key = lambda tup: tup[1], reverse=True)
|
||||
|
||||
sections = list()
|
||||
|
||||
max_points = [lecture.points for lecture in Lecture.select().where(Lecture.class_id == students[0][0].class_id)]
|
||||
max = sum(max_points)
|
||||
|
||||
# Table Header
|
||||
text = create_header(len(students))
|
||||
|
||||
limit = 0
|
||||
for n, tup in enumerate(students, start=1):
|
||||
student, points = tup
|
||||
|
||||
student_data = f'{n}. {student.prename} {student.surname}'
|
||||
points = int(points) if points.is_integer() else points
|
||||
|
||||
grader = get_grader(student.grader)
|
||||
p = [sub.points for sub in Submission.select().where(Submission.student_id == student.id)]
|
||||
passed = grader.get_final_grade(p, max_points)
|
||||
|
||||
text += f'|{student_data}|{points}/{max}|{points/max:.1%}|{passed}|\n'
|
||||
|
||||
limit += 1
|
||||
if limit > 29:
|
||||
sections.append(Section(text))
|
||||
limit = 0
|
||||
text = create_header(len(students))
|
||||
|
||||
if text:
|
||||
sections.append(Section(text))
|
||||
|
||||
return sections
|
37
learnlytics/pdf/study_pdf.py
Normal file
37
learnlytics/pdf/study_pdf.py
Normal file
@ -0,0 +1,37 @@
|
||||
from dbmodel import *
|
||||
from grader import get_grader
|
||||
from markdown_pdf import Section
|
||||
|
||||
|
||||
def create_study_pdf(filter: list[int]) -> list[Section]:
|
||||
sections = list()
|
||||
text = ''
|
||||
|
||||
data = {
|
||||
study: list(Student.select().where(Student.study_id == study.id))
|
||||
for study in filter
|
||||
}
|
||||
|
||||
max_points = [lecture.points for lecture in Lecture.select().where(Lecture.class_id == next(iter(data.values()))[0].class_id)]
|
||||
max = sum(max_points)
|
||||
|
||||
for study, students in data.items():
|
||||
text += f'## {study.name}\n'
|
||||
text += f'#### Number of Students: {Student.select().where(Student.study_id == study.id).count()}\n'
|
||||
text += '|Student|Points|Percentage|Passed?|\n'
|
||||
text += '|-|-|-|-|\n'
|
||||
|
||||
for student in students:
|
||||
|
||||
grader = get_grader(student.grader)
|
||||
overall_points = [sub.points for sub in Submission.select().where(Submission.student_id == student.id)]
|
||||
passed = grader.get_final_grade(overall_points, max_points)
|
||||
points = sum(overall_points)
|
||||
points = int(points) if points.is_integer() else points
|
||||
|
||||
text += f'|{student.prename} {student.surname}|{points}/{max}|{points/max:.1%}|{passed}|\n'
|
||||
|
||||
sections.append(Section(text))
|
||||
text = ''
|
||||
|
||||
return sections
|
47
learnlytics/pdf/utils.py
Normal file
47
learnlytics/pdf/utils.py
Normal file
@ -0,0 +1,47 @@
|
||||
from dbmodel import Class
|
||||
from markdown_pdf import MarkdownPdf, Section
|
||||
from datetime import datetime
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
|
||||
def get_pdf(class_id: int) -> tuple[MarkdownPdf, str]:
|
||||
clas = Class.get_by_id(class_id)
|
||||
|
||||
# Create PDF
|
||||
pdf = MarkdownPdf(toc_level=2, mode='gfm-like')
|
||||
pdf.meta['title'] = clas.name
|
||||
pdf.meta['author'] = 'Learnlytics by @DerGrumpf'
|
||||
pdf.meta['producer'] = 'Learnlytics'
|
||||
pdf.meta['subject'] = f'Passed List - {clas.name}'
|
||||
pdf.meta['keywords'] = f'Passed List,{clas.name}'
|
||||
|
||||
css = None
|
||||
with open('./learnlytics/pdf/document_style.css') as f:
|
||||
css = f.read()
|
||||
|
||||
return (pdf, css)
|
||||
|
||||
def get_pdf_header(class_id: int, filter: Sequence[str], author: str, logo_file: Path = None) -> Section:
|
||||
text = ''
|
||||
# Append Logo
|
||||
if logo_file:
|
||||
text += f'\n'
|
||||
|
||||
# Title
|
||||
text += f'# Passed List - {Class.get_by_id(class_id).name}\n'
|
||||
|
||||
# Filters applied
|
||||
text += '#### Study Programms:\n'
|
||||
for n, filter_el in enumerate(filter, start=1):
|
||||
text += f'{n}. {filter_el}\n'
|
||||
text += '---\n\n'
|
||||
|
||||
# Metadata
|
||||
text += f'Author: {author}\n\n'
|
||||
|
||||
date = datetime.now()
|
||||
text += f'Created: {date.strftime("%A %d.%m.%Y %H:%M")}\n\n'
|
||||
|
||||
text += 'This document is system-generated and may not require manual signatures.'
|
||||
|
||||
return Section(text)
|
BIN
pickles/document_properties.pkl
Normal file
BIN
pickles/document_properties.pkl
Normal file
Binary file not shown.
1
pickles/last_file.txt
Normal file
1
pickles/last_file.txt
Normal file
@ -0,0 +1 @@
|
||||
/storage/programming/Learnlytics/assets/documents/wise-23-24-fri-07-03-2025-01-15a.pdf
|
364
poetry.lock
generated
364
poetry.lock
generated
@ -11,6 +11,126 @@ files = [
|
||||
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cairocffi"
|
||||
version = "1.7.1"
|
||||
description = "cffi-based cairo bindings for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cairocffi-1.7.1-py3-none-any.whl", hash = "sha256:9803a0e11f6c962f3b0ae2ec8ba6ae45e957a146a004697a1ac1bbf16b073b3f"},
|
||||
{file = "cairocffi-1.7.1.tar.gz", hash = "sha256:2e48ee864884ec4a3a34bfa8c9ab9999f688286eb714a15a43ec9d068c36557b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.1.0"
|
||||
|
||||
[package.extras]
|
||||
doc = ["sphinx", "sphinx_rtd_theme"]
|
||||
test = ["numpy", "pikepdf", "pytest", "ruff"]
|
||||
xcb = ["xcffib (>=1.4.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "cairosvg"
|
||||
version = "2.7.1"
|
||||
description = "A Simple SVG Converter based on Cairo"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "CairoSVG-2.7.1-py3-none-any.whl", hash = "sha256:8a5222d4e6c3f86f1f7046b63246877a63b49923a1cd202184c3a634ef546b3b"},
|
||||
{file = "CairoSVG-2.7.1.tar.gz", hash = "sha256:432531d72347291b9a9ebfb6777026b607563fd8719c46ee742db0aef7271ba0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cairocffi = "*"
|
||||
cssselect2 = "*"
|
||||
defusedxml = "*"
|
||||
pillow = "*"
|
||||
tinycss2 = "*"
|
||||
|
||||
[package.extras]
|
||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
test = ["flake8", "isort", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.1"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
|
||||
{file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "colour"
|
||||
version = "0.1.5"
|
||||
@ -25,6 +145,36 @@ files = [
|
||||
[package.extras]
|
||||
test = ["nose"]
|
||||
|
||||
[[package]]
|
||||
name = "cssselect2"
|
||||
version = "0.8.0"
|
||||
description = "CSS selectors for Python ElementTree"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "cssselect2-0.8.0-py3-none-any.whl", hash = "sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e"},
|
||||
{file = "cssselect2-0.8.0.tar.gz", hash = "sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tinycss2 = "*"
|
||||
webencodings = "*"
|
||||
|
||||
[package.extras]
|
||||
doc = ["furo", "sphinx"]
|
||||
test = ["pytest", "ruff"]
|
||||
|
||||
[[package]]
|
||||
name = "defusedxml"
|
||||
version = "0.7.1"
|
||||
description = "XML bomb protection for Python stdlib modules"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
|
||||
{file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flatpak-pip-generator"
|
||||
version = "24.0.0"
|
||||
@ -186,6 +336,76 @@ PyOpenGL = "*"
|
||||
[package.extras]
|
||||
test = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "linkify-it-py"
|
||||
version = "2.0.3"
|
||||
description = "Links recognition library with FULL unicode support."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"},
|
||||
{file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
uc-micro-py = "*"
|
||||
|
||||
[package.extras]
|
||||
benchmark = ["pytest", "pytest-benchmark"]
|
||||
dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"]
|
||||
doc = ["myst-parser", "sphinx", "sphinx-book-theme"]
|
||||
test = ["coverage", "pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
description = "Python port of markdown-it. Markdown parsing, done right!"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
|
||||
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mdurl = ">=0.1,<1.0"
|
||||
|
||||
[package.extras]
|
||||
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
|
||||
code-style = ["pre-commit (>=3.0,<4.0)"]
|
||||
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
|
||||
linkify = ["linkify-it-py (>=1,<3)"]
|
||||
plugins = ["mdit-py-plugins"]
|
||||
profiling = ["gprof2dot"]
|
||||
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
|
||||
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-pdf"
|
||||
version = "1.3.3"
|
||||
description = "Markdown to pdf renderer"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "markdown_pdf-1.3.3-py3-none-any.whl", hash = "sha256:0b9b425030fbe4871cbb5842a30ba8d23c6ca1f5ba40a2bb3bd4f286e17d28fc"},
|
||||
{file = "markdown_pdf-1.3.3.tar.gz", hash = "sha256:5cdb054afa20de0b590a5cbffc0569545bf3681f70263d6ffdc15adf99b515aa"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
markdown-it-py = "3.0.0"
|
||||
PyMuPDF = "1.24.6"
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
description = "Markdown URL utilities"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "munch"
|
||||
version = "4.0.0"
|
||||
@ -490,6 +710,17 @@ files = [
|
||||
greenlet = ">=3.1.1,<4.0.0"
|
||||
pyee = ">=12,<13"
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
description = "C parser in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.6"
|
||||
@ -639,6 +870,66 @@ typing-extensions = "*"
|
||||
[package.extras]
|
||||
dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "sphinx", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"]
|
||||
|
||||
[[package]]
|
||||
name = "pymupdf"
|
||||
version = "1.24.6"
|
||||
description = "A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "PyMuPDF-1.24.6-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:3a3689394c7ab2851be73f3c82300747e2a089dc37d563be8bda3f71c603c5c4"},
|
||||
{file = "PyMuPDF-1.24.6-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:64fb6b2c00b3d3fa31c36d3735cb7694e5f459635883e02b086fdc44fb9398ee"},
|
||||
{file = "PyMuPDF-1.24.6-cp310-none-manylinux2014_x86_64.whl", hash = "sha256:83c2b34c7d6ee261e5b6db643947ec22e1aa85d3fa2ab826af50e9909c2b739f"},
|
||||
{file = "PyMuPDF-1.24.6-cp310-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e0ca0051f55063e366c26bf8619784c36f796114840b0506958297d58dcfda1"},
|
||||
{file = "PyMuPDF-1.24.6-cp310-none-win32.whl", hash = "sha256:d6caebcaffba5179d3ac62df29858b88dba026ea15e987b4a619ec7e72114b7c"},
|
||||
{file = "PyMuPDF-1.24.6-cp310-none-win_amd64.whl", hash = "sha256:57f49d90c546bca7ec46c89d1d6e4c3a1c861a6f04a5e038e12cf3419532fb4d"},
|
||||
{file = "PyMuPDF-1.24.6-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:9bd685fadc2e7e94af8cd0a92adf9c84dbcf246f4471d180186087d908e0f0c4"},
|
||||
{file = "PyMuPDF-1.24.6-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:cb095bae3bf9be8543128e8834d5cd9101b4ab43e3d84e58a73b8a72c93ca9a7"},
|
||||
{file = "PyMuPDF-1.24.6-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:6ce5703d30ebe8d710b58d4cfd1a8c6d0627c2f9a34bf9a9a1aef2e496a5df7b"},
|
||||
{file = "PyMuPDF-1.24.6-cp311-none-musllinux_1_2_x86_64.whl", hash = "sha256:5fdccd3fdbe61f1cc93d38d9b234880b7f9d7fc703d0221198be788a74de0a77"},
|
||||
{file = "PyMuPDF-1.24.6-cp311-none-win32.whl", hash = "sha256:90856c84c8babb5692f5d64504f371eb5f147c33cd0a51724a4e1e530b7a1e4b"},
|
||||
{file = "PyMuPDF-1.24.6-cp311-none-win_amd64.whl", hash = "sha256:b0ce01fe4a3153604ded32a78e48c647f30bad80ce0a051563f11db85980da5a"},
|
||||
{file = "PyMuPDF-1.24.6-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:aee2999e8cd042ded9ff5ba4997dfbcbbb4e4f8a09c5e95c9a3c293a651a919d"},
|
||||
{file = "PyMuPDF-1.24.6-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:c85b8c4e389a71d57aba4c8e6115d7f20ec3b5025018023f3360cf176bbd294a"},
|
||||
{file = "PyMuPDF-1.24.6-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:64cb67a3938c32614c2ba41cbd37168223aa9983bc882dc9a6c8c6bb207ade60"},
|
||||
{file = "PyMuPDF-1.24.6-cp312-none-musllinux_1_2_x86_64.whl", hash = "sha256:7e099cc4a0deca70173692fe10b08eb486ca86222377d34bfcadb3bcb2da3ff8"},
|
||||
{file = "PyMuPDF-1.24.6-cp312-none-win32.whl", hash = "sha256:24f11aa94e606f466e11163bc4fb5ab3328236549c75c26991c6269342d8dcba"},
|
||||
{file = "PyMuPDF-1.24.6-cp312-none-win_amd64.whl", hash = "sha256:10c373c9dce565779eced2a88730229e12c57d9c388cc1e184b1565e641979f7"},
|
||||
{file = "PyMuPDF-1.24.6-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:407ae5cfc32cae18fd50c47e4a40b5cee77ffc27b285f9fa28c50d088a5d9624"},
|
||||
{file = "PyMuPDF-1.24.6-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:4b62e9c088de896e962864d948da8b263f12572d89e5e65a8929bd51280a12ca"},
|
||||
{file = "PyMuPDF-1.24.6-cp38-none-manylinux2014_x86_64.whl", hash = "sha256:5073416516e97bb1323076ae295224684fb11d3ddf7d39d9c142eed824648067"},
|
||||
{file = "PyMuPDF-1.24.6-cp38-none-musllinux_1_2_x86_64.whl", hash = "sha256:71ed2e7b23d1cb50e577258e8b1dee986d8c06fca2261239c12872b6c43c40df"},
|
||||
{file = "PyMuPDF-1.24.6-cp38-none-win32.whl", hash = "sha256:c915f5b019c4fd4afa47d39ee5871440200328d11c2377a43a364f5cb70d3c0d"},
|
||||
{file = "PyMuPDF-1.24.6-cp38-none-win_amd64.whl", hash = "sha256:b9742faefcddda1ee793ef26a686370f468128ade356cd90d2ea2e01e98b07a1"},
|
||||
{file = "PyMuPDF-1.24.6-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:bb55d6ce5165c7a8eac2fdce3ba83c71a227816bbbfd3da3aa48af38ad91cced"},
|
||||
{file = "PyMuPDF-1.24.6-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:fdc464085549998a88b53d49aef44312e77e1c2a5ef2d2b7b03a623b9fff982f"},
|
||||
{file = "PyMuPDF-1.24.6-cp39-none-manylinux2014_x86_64.whl", hash = "sha256:7b74e0ae95b8449069966cbeb6be8e1536f61f388ceb74ff3924f91cd788462a"},
|
||||
{file = "PyMuPDF-1.24.6-cp39-none-musllinux_1_2_x86_64.whl", hash = "sha256:e059984723e3c64c5c49b9edf481b42c50d66b5d59f50ab86d56101d8e378a02"},
|
||||
{file = "PyMuPDF-1.24.6-cp39-none-win32.whl", hash = "sha256:9b7ce753a3e6c2625963815df171f268ad4002243d1d950b246c0181183bd919"},
|
||||
{file = "PyMuPDF-1.24.6-cp39-none-win_amd64.whl", hash = "sha256:b1879e5c175cacee0cc140a7ee19dae901310b95b066a02d6a3829ccbfc4a5fa"},
|
||||
{file = "PyMuPDF-1.24.6.tar.gz", hash = "sha256:029dd99df1cebcbcd4240940809c5e373353d12e6c8483934d42f59ceacfb037"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
PyMuPDFb = "1.24.6"
|
||||
|
||||
[[package]]
|
||||
name = "pymupdfb"
|
||||
version = "1.24.6"
|
||||
description = "MuPDF shared libraries for PyMuPDF."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "PyMuPDFb-1.24.6-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:21e3ed890f736def68b9a031122ae1fb854d5cb9a53aa144b6e2ca3092416a6b"},
|
||||
{file = "PyMuPDFb-1.24.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8704d2dfadc9448ce184597d8b0f9c30143e379ac948a517f9c4db7c0c71ed51"},
|
||||
{file = "PyMuPDFb-1.24.6-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01662584d5cfa7a91f77585f13fc23a12291cfd76a57e0a28dd5a56bf521cb2c"},
|
||||
{file = "PyMuPDFb-1.24.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e1f7657353529ae3f88575c83ee49eac9adea311a034b9c97248a65cee7df0e5"},
|
||||
{file = "PyMuPDFb-1.24.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cebc2cedb870d1e1168e2f502eb06f05938f6df69103b0853a2b329611ec19a7"},
|
||||
{file = "PyMuPDFb-1.24.6-py3-none-win32.whl", hash = "sha256:ac4b865cd1e239db04674f85e02844a0e405f8255ee7a74dfee0d86aad0d3576"},
|
||||
{file = "PyMuPDFb-1.24.6-py3-none-win_amd64.whl", hash = "sha256:9224e088a0d3c188dea03831807789e245b812fbd071c8d498da8f7cc33142b2"},
|
||||
{file = "PyMuPDFb-1.24.6.tar.gz", hash = "sha256:f5a40b1732d65a1e519916d698858b9ce7473e23edf9001ddd085c5293d59d30"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyopengl"
|
||||
version = "3.1.9"
|
||||
@ -664,6 +955,23 @@ files = [
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "python-slugify"
|
||||
version = "8.0.4"
|
||||
description = "A Python slugify application that also handles Unicode"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"},
|
||||
{file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
text-unidecode = ">=1.3"
|
||||
|
||||
[package.extras]
|
||||
unidecode = ["Unidecode (>=1.1.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.1"
|
||||
@ -763,6 +1071,35 @@ files = [
|
||||
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text-unidecode"
|
||||
version = "1.3"
|
||||
description = "The most basic Text::Unidecode port"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
|
||||
{file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinycss2"
|
||||
version = "1.4.0"
|
||||
description = "A tiny CSS parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"},
|
||||
{file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
webencodings = ">=0.4"
|
||||
|
||||
[package.extras]
|
||||
doc = ["sphinx", "sphinx_rtd_theme"]
|
||||
test = ["pytest", "ruff"]
|
||||
|
||||
[[package]]
|
||||
name = "types-setuptools"
|
||||
version = "75.8.0.20250225"
|
||||
@ -813,7 +1150,32 @@ tzdata = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
[package.extras]
|
||||
devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"]
|
||||
|
||||
[[package]]
|
||||
name = "uc-micro-py"
|
||||
version = "1.0.3"
|
||||
description = "Micro subset of unicode data files for linkify-it-py projects."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"},
|
||||
{file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["coverage", "pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "webencodings"
|
||||
version = "0.5.1"
|
||||
description = "Character encoding aliases for legacy web content"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
|
||||
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "bb8a55ca695106ae590ae540a5261bed6905ea6686e6d38a3e0487e5ebb16d1d"
|
||||
content-hash = "0f368a19ff46c53164f16b88dee00580f42b9559212a4fa7fa71b9aad0721824"
|
||||
|
@ -25,6 +25,10 @@ pytz = "^2025.1"
|
||||
tzlocal = "^5.3"
|
||||
pdf2image = "^1.17.0"
|
||||
playwright = "^1.50.0"
|
||||
markdown-pdf = "^1.3.3"
|
||||
linkify-it-py = "^2.0.3"
|
||||
python-slugify = "^8.0.4"
|
||||
cairosvg = "^2.7.1"
|
||||
|
||||
|
||||
[build-system]
|
||||
|
Loading…
Reference in New Issue
Block a user