-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.py
780 lines (685 loc) · 38.8 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
"""LMS grade book ripoff"""
import json
from pprint import pprint
from collections import defaultdict
grade_book_type = dict[tuple[str, str, str, str], dict[str, dict[str, float | None]]]
def get_activities(syllabus: dict[str, dict[str, float]]) -> set[tuple[str, str]]:
"""
Arranges groups of activities
:param activities: list, all activities
:return: set, groups of activities
>>> get_activities({'Лабораторні роботи': {'Лабораторна робота 1': 1.5},
... 'Семінари': {'Семінар 1': 4.5, 'Семінар 2': 6},
... 'Лабораторні роботи': {
... 'Лабораторна робота 1': 2.0,
... 'Лабораторна робота 2': 0.5
... }}) == {('Семінари', 'Семінар 1'),
... ('Лабораторні роботи', 'Лабораторна робота 2'),
... ('Семінари', 'Семінар 2'), ('Лабораторні роботи', 'Лабораторна робота 1')}
True
"""
output = []
for group in syllabus:
for activity in syllabus[group]:
output += [(group, activity)]
return set(output)
def get_proper_student_grade(data: grade_book_type, syllabus: dict[str, dict[str, float]]) \
-> dict[str, dict[str, dict[str, float | None]]]:
"""
Makes a dictionary for all types of grades for each student
:param data: dict, dictionary of all grades
:return: dict, dictionary for all types of grades for each student.
>>> get_proper_student_grade({\
('Фединяк', 'Степан', '[email protected]', 'ПКН24-Б1'): \
{\
'Лабораторні роботи': {'Лабораторна робота 1': 1.5},\
'Семінари': {'Семінар 1': 4.5, 'Семінар 2': 6}\
},\
('Фаренюк', 'Олег', '[email protected]', 'ПКН24-Б1'):\
{\
'Лабораторні роботи': {'Лабораторна робота 2': 2.0}}\
}, \
{\
'Семінари': {'Семінар 1': 6.0, 'Семінар 2': 6.0},\
'Лабораторні роботи': {'Лабораторна робота 1': 1.5, 'Лабораторна робота 2': 2.0}\
}) == \
{'Фединяк, Степан, [email protected], ПКН24-Б1': \
{'Семінари': {'Семінар 1': 4.5, 'Семінар 2': 6}, 'Лабораторні роботи': \
{'Лабораторна робота 1': 1.5, 'Лабораторна робота 2': None}}, \
'Фаренюк, Олег, [email protected], ПКН24-Б1': \
{'Семінари': {'Семінар 1': None, 'Семінар 2': None}, 'Лабораторні роботи': \
{'Лабораторна робота 1': None, 'Лабораторна робота 2': 2.0}}}
True
"""
def sort_key(x):
return x[1]
student_info, grades = list(data.keys()), list(data.values())
all_activities = get_activities(syllabus)
output = {}
for student in student_info:
grades = {}
for topic, lab in all_activities:
if topic not in grades:
grades[topic] = {}
if topic not in data[student] or lab not in data[student][topic]:
grades[topic][lab] = None
continue
grades[topic] = dict(sorted(grades[topic].items(), key=sort_key))
grades[topic][lab] = data[student][topic][lab]
output[", ".join(student)] = grades
return output
def add_activity(syllabus: dict[str, dict[str, float]], activity_group: str, activity: str, max_grade: float) -> dict[str, dict[str, float]]:
'''
Adds a new activity to the syllabus or ignores an existing activity
if the activity group and activity already exist in the syllabus.
:param syllabus: dict[str, dict[str, float]], The dictionary that contains activity groups
as keys and nested dictionaries as values.
:param activity_group: str, The name of the activity group.
:param activity: str, The name of activity.
:param max_grade: float, The maximum grade for the activity.
:return: dict[str, dict[str, float]], The result syllabus.
>>> add_activity({'ПКН23-А': {'Мідтерм 2023': 10}, \
'ПКН24-В': {'treasure': 1}}, \
'ПКН24-Б', 'Tower blocks', 1)
{'ПКН23-А': {'Мідтерм 2023': 10}, \
'ПКН24-В': {'treasure': 1}, \
'ПКН24-Б': {'Tower blocks': 1}}
>>> add_activity({'ПКН23-А': {'Мідтерм 2023': 10}, \
'ПКН24-В': {'treasure': 1}}, \
'ПКН24-В', 'Tower blocks', 1)
{'ПКН23-А': {'Мідтерм 2023': 10}, \
'ПКН24-В': {'treasure': 1, 'Tower blocks': 1}}
>>> add_activity({'ПКН23-А': {'Мідтерм 2023': 10}, \
'ПКН24-В': {'treasure': 1}}, \
'ПКН24-В', 'treasure', 0)
{'ПКН23-А': {'Мідтерм 2023': 10}, \
'ПКН24-В': {'treasure': 1}}
'''
if not activity_group in syllabus:
syllabus[activity_group] = {}
if not activity in syllabus[activity_group]:
syllabus[activity_group][activity] = max_grade
return syllabus
def write_json(filename: str, data: grade_book_type, syllabus: dict[str, dict[str, float]]):
"""
Writes the all students' grades json file.
:param filename: str, name of the file that should be written
:param data: dict, dictionary of all grades
:return: None
"""
student_grades = get_proper_student_grade(data, syllabus)
with open(filename, "w", encoding="utf-8") as file:
json.dump(student_grades, file, ensure_ascii=False)
def grade_check(mark: str, max_grade: float) -> float | None:
"""
Checks if the grade is correct.
:param mark: str
:param max_grade: float, maximum grade
:return: float if grade correct, None otherwise
"""
if not mark or float(mark) < 0 or float(mark) > max_grade:
return None
return float(mark)
def is_activity_correct(syllabus: dict[str, dict[str, float]], activity_group: str, activity: str) \
-> bool:
"""
Checks if the activity is correct.
:param syllabus: dict[str, dict[str, float]], dict of all activities
:param activity_group: str, activity group
:param activity: str, activity
:return: bool, True if activity is correct, False otherwise
"""
if syllabus.get(activity_group, -1) == -1 or syllabus[activity_group].get(activity, -1) == -1:
return False
return True
def get_grades_from_file(filename: str, grade_book: grade_book_type,
syllabus: dict[str, dict[str, float]], ) \
-> dict[(str, str, str, str), dict[str, dict[str, float]]]:
"""
Record grades from a file.
:param filename: str, file with marks
:param grade_book: dict[(str, str, str, str), dict[str, dict[str, float]]], key is student info,
:param syllabus: dict[str, dict[str, float]], dict of all activities with respective maximun
grades
value is a nested dict of grades
:return: dict[(str, str, str, str), dict[str, dict[str, float]]], grade book with inserted
values
>>> get_grades_from_file('grades.csv', { \
('Фединяк', 'Степан', '[email protected]','ПКН24-Б1'): {}, \
('Фаренюк', 'Олег', '[email protected]', 'ПКН24-Б1'): {}, \
('Степаненко', 'Степан', '[email protected]', 'ПКН24-Б2'): {}, \
('Рандомний', 'Поц', '[email protected]', 'ПКН24-Б1'): {}, \
('Норм', 'Кєнт', '[email protected]', 'ПКН24-Б1'): {}, \
('Хто', 'Це', '[email protected]', 'ПКН24-Б3'): {}}, \
{'Лабораторні роботи': {'Лабораторна робота 1': 2.0}})
{('Фединяк', 'Степан', '[email protected]', 'ПКН24-Б1'): \
{'Лабораторні роботи': {'Лабораторна робота 1': 1.5}}, \
('Фаренюк', 'Олег', '[email protected]', 'ПКН24-Б1'): \
{'Лабораторні роботи': {'Лабораторна робота 1': 2.0}}, \
('Степаненко', 'Степан', '[email protected]', 'ПКН24-Б2'): \
{'Лабораторні роботи': {'Лабораторна робота 1': 0.5}}, \
('Рандомний', 'Поц', '[email protected]', 'ПКН24-Б1'): \
{'Лабораторні роботи': {'Лабораторна робота 1': 0.0}}, \
('Норм', 'Кєнт', '[email protected]', 'ПКН24-Б1'): \
{'Лабораторні роботи': {'Лабораторна робота 1': 2.0}}, \
('Хто', 'Це', '[email protected]', 'ПКН24-Б3'): \
{'Лабораторні роботи': {'Лабораторна робота 1': None}}}
"""
with open(filename, "r", encoding="utf-8") as file_grade_book:
lines = file_grade_book.readlines()
for line in lines[1:]:
activity_group, activity, surname, name, email, student_group, mark = \
line.strip().split(",")
if not is_activity_correct(syllabus, activity_group, activity):
# print("Activity doesn't exist")
continue
mark = grade_check(mark, syllabus[activity_group][activity])
grade_book[(surname, name, email, student_group)] \
.setdefault(activity_group, {})[activity] = mark
return grade_book
def add_student(grade_book: grade_book_type, student_info: tuple[str, str, str, str]) \
-> grade_book_type:
"""
Adds a student to a gradebook if the student is not in it, else does nothing
:params: gradebook: dict - grabook with students and grades
:params: student_info: tuple - student information (last name, first name, email, group)
:Returns:
dict - updated gradebook with the added student
>>> add_student({('Стерненко', 'Сергій', '[email protected]', 'ПКН24-Б2'): {}},\
('Студент', 'Павло', '[email protected]', 'ПКН24-Б1'))
{('Стерненко', 'Сергій', '[email protected]', 'ПКН24-Б2'): {},\
('Студент', 'Павло', '[email protected]', 'ПКН24-Б1'): {}}
"""
if student_info not in grade_book:
grade_book[student_info] = {}
return grade_book
def stud_grade_book_to_json(filename: str, stud_mail: str, grade_book: grade_book_type):
"""
This function records a gradebook for one student,
whose email is recorded in stud_mail param.
in a json file.
-------------------------------------------------
param: filename(str) "your_file_name.json",
stud_mail(str) student's email adress,
grade_book(dict) grade_book to write to json file.
return: 'Done successfully' or 'The Error occured: two or more equal emails'
>>> STUD_GRB = {('Name1', 'Lastname1', '[email protected]', 'ПКН24-Б1'):\
{"Лабораторні роботи (22 бали)":\
{"Лабораторна робота 1": 1.5,"Лабораторна робота 2": 2.4, \
"Лабораторна робота 3": 5.6,"Лабораторна робота 4": 2.5,\
"Лабораторна робота 5": 8.7},"Тести по лекційним матеріалам":\
{'Тест 1': 1.4,'Тест 2': 3.5,'Тест 3': 2.5,'Тест 4': 1.3},\
"Тести на LMS": {'Тест 1': 1,'Тест 2': 1,'Тест 3': 1,'Тест 4': 0.8,\
'Тест 5': 3.2,'Тест 6': 1.4,'Тест 7': 3.6}},\
('Name2', 'Lastname2', '[email protected]', 'ПКН24-Б1'):\
{"Лабораторні роботи (22 бали)":\
{"Лабораторна робота 1": 0.5,"Лабораторна робота 2": 2.4, \
"Лабораторна робота 3": 5.8,"Лабораторна робота 4": 2.3,\
"Лабораторна робота 5": 3.7},"Тести по лекційним матеріалам":\
{'Тест 1': 1.4,'Тест 2': 2.5,'Тест 3': 2.5,'Тест 4': 1.3},\
"Тести на LMS": {'Тест 1': 1.8,'Тест 2': 2.6,'Тест 3': 0.0,'Тест 4': 0.0,\
'Тест 5': 3.2,'Тест 6': 1.4,'Тест 7': 3.6}}}
>>> import tempfile
>>> with tempfile.NamedTemporaryFile(mode= 'w+', suffix=".json", delete = False) as temp_input:
... res = stud_grade_book_to_json(temp_input.name, '[email protected]', STUD_GRB)
... with open(temp_input.name, 'r', encoding = 'utf-8') as temp_output:
... print(temp_output.read())
... print(res)
{
"Name1, Lastname1, [email protected], ПКН24-Б1": {
"Лабораторні роботи (22 бали)": {
"Лабораторна робота 1": 1.5,
"Лабораторна робота 2": 2.4,
"Лабораторна робота 3": 5.6,
"Лабораторна робота 4": 2.5,
"Лабораторна робота 5": 8.7
},
"Тести по лекційним матеріалам": {
"Тест 1": 1.4,
"Тест 2": 3.5,
"Тест 3": 2.5,
"Тест 4": 1.3
},
"Тести на LMS": {
"Тест 1": 1,
"Тест 2": 1,
"Тест 3": 1,
"Тест 4": 0.8,
"Тест 5": 3.2,
"Тест 6": 1.4,
"Тест 7": 3.6
}
}
}
Done successfully
>>> import tempfile
>>> with tempfile.NamedTemporaryFile(mode= 'w+', suffix=".json", delete = False) as temp_input:
... res = stud_grade_book_to_json(temp_input.name, '[email protected]', STUD_GRB)
... with open(temp_input.name, 'r', encoding = 'utf-8') as temp_output:
... print(temp_output.read())
... print(res)
<BLANKLINE>
The Error occured: no student with that email
"""
needed = list(filter(lambda x: stud_mail in x, grade_book.keys()))
if len(needed) == 1:
output_diction = {
", ".join(key): value for key, value in grade_book.items()
if key == needed[0]
}
with open(filename, mode="w", encoding="utf-8") as file:
json.dump(output_diction, file, ensure_ascii=False,
indent=4, separators=(",", ": "))
return 'Done successfully'
elif len(needed) >= 1:
return 'The Error occured: two or more equal emails'
else:
return 'The Error occured: no student with that email'
def del_activity(
syllabus: dict[str, dict[str, float]],
grade_book: dict[tuple[str, str, str, str], dict[str, dict[str, float | None]]],
activity_group: str,
activity: str
) -> None:
"""
Removes an activity from the syllabus and grade book for each student.
:param syllabus: syllabus that contains information on activity groups and their maximum scores.
:param grade_book: grade book with students' grades.
:param activity_group: name of the activity group from which the activity will be removed.
:param activity: name of the activity that we need to remove.
:return: None
>>> syllabus = {"Лабораторні роботи": {"Лабораторна робота 1": 3, "Лабораторна робота 2": 2}}
>>> grade_book = {("Фединяк", "Степан", "[email protected]", "ПКН24-Б1"): \
{"Лабораторні роботи":{"Лабораторна робота 1": 3, "Лабораторна робота 2": 2}}}
>>> del_activity(syllabus, grade_book, "Лабораторні роботи", "Лабораторна робота 1")
>>> syllabus
{'Лабораторні роботи': {'Лабораторна робота 2': 2}}
>>> grade_book
{('Фединяк', 'Степан', '[email protected]', 'ПКН24-Б1'): {'Лабораторні роботи':\
{'Лабораторна робота 2': 2}}}
"""
if activity_group in syllabus:
if activity in syllabus[activity_group]:
del syllabus[activity_group][activity]
if not syllabus[activity_group]:
del syllabus[activity_group]
for _, activities in grade_book.items():
if activity_group in activities:
if activity in activities[activity_group]:
del activities[activity_group][activity]
if not activities[activity_group]:
del activities[activity_group]
def mark_transform(grade_book: grade_book_type) -> dict[str, str]:
"""
Returns all students marks with letter grade system
:param grade_book: dict[(str, str, str, str), dict[str, dict[str, float]]],key is student names
:return: dict[str,str], name and surname of the student with their respective mark
>>> mark_transform({('Фединяк', 'Степан', '[email protected]', 'ПКН24-Б1'):\
{"Лабораторні роботи (22 бали)": {"Лабораторна робота 1": '1.0',"Лабораторна робота 2": '1.0',\
"Лабораторна робота 3": '1.0',"Лабораторна робота 4": '1.0',"Лабораторна робота 5": '1.0',\
"Лабораторна робота 6": '1.0',"Лабораторна робота 7": '1.0',"Лабораторна робота 8": '1.0',\
"Лабораторна робота 9": '1.0',"Лабораторна робота 10": '1.0',"Лабораторна робота 11": '1.0'},\
"Проміжний іспит (20 балів)": {'Теоретичне завдання': '1.0','Завдання на програмування': '1.0'}\
,"Тести по лекційним матеріалам (3 бали по 0.5 за кожен)": {'Тест 1': '1.0','Тест 2': '1.0',\
'Тест 3': '1.0','Тест 4': '1.0','Тест 5': '1.0','Тест 6': '1.0'},\
"Тести на LMS (3 бали по 0.3 за кожен)": {'Тест 1': '1.0','Тест 2': '1.0','Тест 3': '1.0',\
'Тест 4': '1.0','Тест 5': '1.0','Тест 6': '1.0','Тест 7': '1.0','Тест 8': '1.0','Тест 9': '1.0',\
'Тест 10': '1.0'},"Міні-проєкти (22 бали)": {'міні-проєкт 1': '1.0','міні-проєкт 2': '1.0'},\
"Фінальний іспит (30 балів)": {'Теоретичне завдання': '1.0','Завдання на програмування': '1.0',},\
"Додаткові бали (10 балів)": '1.0'}})
{'Фединяк Степан': 'F'}
"""
mark = 0
student = ""
letter_mark = ""
final = {}
for i in grade_book:
for e in grade_book.get(i):
if isinstance(grade_book.get(i).get(e), str):
mark += float(grade_book.get(i).get(e))
break
for g in grade_book.get(i).get(e):
mark += float(grade_book.get(i).get(e).get(g))
student += i[0] + " " + i[1]
if not mark:
letter_mark = "-"
if mark >= 90:
letter_mark = "A"
elif 90 > mark >= 85:
letter_mark = "B"
elif 85 > mark >= 75:
letter_mark = "C"
elif 75 > mark >= 65:
letter_mark = "D"
elif 65 > mark >= 60:
letter_mark = "E"
elif 60 > mark >= 0:
letter_mark = "F"
final.setdefault(student, letter_mark)
mark = 0
student = ""
return final
def letter_report(letter: str, gradebook: grade_book_type) -> list:
'''
Receives gradebook and letter and builds report of students with particular grade
:param letter: letter to get students with this mark
:param grade_book: grade book dict
:return: list[student1, student2] list of students with particular grade
>>> letter_report('F', {('Фединяк', 'Степан', '[email protected]', 'ПКН24-Б1'):\
{"Лабораторні роботи (22 бали)": {"Лабораторна робота 1": '1.0',"Лабораторна робота 2": '1.0',\
"Лабораторна робота 3": '1.0',"Лабораторна робота 4": '1.0',"Лабораторна робота 5": '1.0',\
"Лабораторна робота 6": '1.0',"Лабораторна робота 7": '1.0',"Лабораторна робота 8": '1.0',\
"Лабораторна робота 9": '1.0',"Лабораторна робота 10": '1.0',"Лабораторна робота 11": '1.0'},\
"Проміжний іспит (20 балів)": {'Теоретичне завдання': '1.0','Завдання на програмування': '1.0'}\
,"Тести по лекційним матеріалам (3 бали по 0.5 за кожен)": {'Тест 1': '1.0','Тест 2': '1.0',\
'Тест 3': '1.0','Тест 4': '1.0','Тест 5': '1.0','Тест 6': '1.0'},\
"Тести на LMS (3 бали по 0.3 за кожен)": {'Тест 1': '1.0','Тест 2': '1.0','Тест 3': '1.0',\
'Тест 4': '1.0','Тест 5': '1.0','Тест 6': '1.0','Тест 7': '1.0','Тест 8': '1.0','Тест 9': '1.0',\
'Тест 10': '1.0'},"Міні-проєкти (22 бали)": {'міні-проєкт 1': '1.0','міні-проєкт 2': '1.0'},\
"Фінальний іспит (30 балів)": {'Теоретичне завдання': '1.0','Завдання на програмування': '1.0',},\
"Додаткові бали (10 балів)": '1.0'}})
['Фединяк Степан']
'''
by_letter = []
transformed = mark_transform(gradebook)
for student, lettermark in transformed.items():
if lettermark == letter:
by_letter.append(student)
return by_letter
def activity_report(grade_book: dict[(str, str, str, str), dict[str, dict[str, float]]],
activity_group: str):
"""
Returns activity grades of all students
:param grade_book: dict[(str, str, str, str), dict[str, dict[str, float]]],key is student names
:param activity_group: name of activity group
:return: dict[(str, str, str, str), dict[str, float]], information of student and all
grades from activity
>>> students = {('Степан', 'Фединяк', '[email protected]', 'ПКН24-Б1'):\
{"Лабораторні роботи (22 бали)":\
{"Лабораторна робота 1": 1.5,"Лабораторна робота 2": 2.4, \
"Лабораторна робота 3": 5.6,"Лабораторна робота 4": 2.5,\
"Лабораторна робота 5": 8.7},"Тести по лекційним матеріалам":\
{'Тест 1': 1.4,'Тест 2': 3.5,'Тест 3': 2.5,'Тест 4': 1.3},\
"Тести на LMS": {'Тест 1': 1,'Тест 2': 1,'Тест 3': 1,'Тест 4': 0.8,\
'Тест 5': 3.2,'Тест 6': 1.4,'Тест 7': 3.6}}}
>>> activity_report(students, "Лабораторні роботи (22 бали)") == \
{('Степан', 'Фединяк', '[email protected]', 'ПКН24-Б1'): \
{"Лабораторна робота 1": 1.5,"Лабораторна робота 2": 2.4, \
"Лабораторна робота 3": 5.6,"Лабораторна робота 4": 2.5,\
"Лабораторна робота 5": 8.7}}
True
"""
result = {}
for student, activities in grade_book.items():
result[student] = activities.get(activity_group, {})
return result
def get_mean_by_activity_report(
grade_book: grade_book_type,
activity_groups: list[str]
) -> dict[str, float]:
"""
Receives grade book and activity group(s) and builds a report
of a mean grade of all students per an activity group
:param grade_book: grade book dict
:param activity_groups: list of activity group names
:return: dict[str, float], report of a mean grade per activity group
>>> grade_book = {('Степан', 'Фединяк', '[email protected]', 'ПКН24-Б1'):\
{"Лабораторні роботи (22 бали)":\
{"Лабораторна робота 1": 1.5,"Лабораторна робота 2": 2.4, \
"Лабораторна робота 3": 5.6,"Лабораторна робота 4": 2.5,\
"Лабораторна робота 5": 8.7},"Тести по лекційним матеріалам":\
{'Тест 1': 1.4,'Тест 2': 3.5,'Тест 3': 2.5,'Тест 4': 1.3},\
"Тести на LMS": {'Тест 1': 1,'Тест 2': 1,'Тест 3': 1,'Тест 4': 0.8,\
'Тест 5': 3.2,'Тест 6': 1.4,'Тест 7': 3.6}},\
("Юлія", "Колодій", "[email protected]", "ПКН24-Б1"): \
{"Лабораторні роботи (22 бали)":\
{"Лабораторна робота 1": 3.2,"Лабораторна робота 2": 2.2, \
"Лабораторна робота 3": 7.0,"Лабораторна робота 4": 3.3,\
"Лабораторна робота 5": 5.8},"Тести по лекційним матеріалам":\
{'Тест 1': 2.4,'Тест 2': 4.0,'Тест 3': 2.8,'Тест 4': 1.5},\
"Тести на LMS": {'Тест 1': 1,'Тест 2': 0.8,'Тест 3': 0.8,'Тест 4': 0.8,\
'Тест 5': 4.0,'Тест 6': 1,'Тест 7': 3.6}}}
>>> get_mean_by_activity_report(grade_book, ["Лабораторні роботи (22 бали)"]) == \
{"Лабораторні роботи (22 бали)": 21.1}
True
>>> get_mean_by_activity_report(grade_book, ["Тести на LMS", "Тести по лекційним матеріалам", "Wrong group"]) == \
{"Тести на LMS": 12.0, "Тести по лекційним матеріалам": 9.7}
True
>>> grade_book = {**grade_book, ("Vlad", "Bobryk", "[email protected]", "ПКН24-Б1"): {"Тести по лекційним матеріалам": {}, "Тести на LMS": {}}}
>>> get_mean_by_activity_report(grade_book, ["Тести на LMS", "Тести по лекційним матеріалам"]) == \
{"Тести на LMS": 8.0, "Тести по лекційним матеріалам": 6.47}
True
"""
mean_report = defaultdict(int)
total_report = get_total_by_activity_report(grade_book, activity_groups)
student_count = len(total_report)
for _, activities in total_report.items():
for activity, total in activities.items():
mean_report[activity] += total
return {act: round(total / student_count, 2) for act, total in mean_report.items()}
def get_total_by_activity_report(
grade_book: grade_book_type,
activity_groups: list[str] = None
) -> dict[str, dict[str, float]]:
"""
Receives grade book and activity group(s) and builds a report with
all students' total grade per activity group
:param grade_book: grade book dict
:param activity_groups: list of activity group names (optional)
:return: dict[str, dict[str, float]], report with each student and total grade per activity group
>>> grade_book = {('Степан', 'Фединяк', '[email protected]', 'ПКН24-Б1'):\
{"Лабораторні роботи (22 бали)":\
{"Лабораторна робота 1": 1.5,"Лабораторна робота 2": 2.4, \
"Лабораторна робота 3": 5.6,"Лабораторна робота 4": 2.5,\
"Лабораторна робота 5": 8.7},"Тести по лекційним матеріалам":\
{'Тест 1': 1.4,'Тест 2': 3.5,'Тест 3': 2.5,'Тест 4': 1.3},\
"Тести на LMS": {'Тест 1': 1,'Тест 2': 1,'Тест 3': 1,'Тест 4': 0.8,\
'Тест 5': 3.2,'Тест 6': 1.4,'Тест 7': 3.6}},\
("Юлія", "Колодій", "[email protected]", "ПКН24-Б1"): \
{"Лабораторні роботи (22 бали)":\
{"Лабораторна робота 1": 3.2,"Лабораторна робота 2": 2.2, \
"Лабораторна робота 3": 7.0,"Лабораторна робота 4": 3.3,\
"Лабораторна робота 5": 5.8},"Тести по лекційним матеріалам":\
{'Тест 1': 2.4,'Тест 2': 4.0,'Тест 3': 2.8,'Тест 4': 1.5},\
"Тести на LMS": {'Тест 1': 1,'Тест 2': 0.8,'Тест 3': 0.8,'Тест 4': 0.8,\
'Тест 5': 4.0,'Тест 6': 1,'Тест 7': 3.6}}}
>>> get_total_by_activity_report(grade_book, ["Лабораторні роботи (22 бали)"]) == \
{('Степан', 'Фединяк', '[email protected]', 'ПКН24-Б1'): {"Лабораторні роботи (22 бали)": 20.7},\
("Юлія", "Колодій", "[email protected]", "ПКН24-Б1"): {"Лабораторні роботи (22 бали)": 21.5},}
True
>>> get_total_by_activity_report(grade_book, ["Тести на LMS", "Тести по лекційним матеріалам", "Wrong group"]) == \
{('Степан', 'Фединяк', '[email protected]', 'ПКН24-Б1'): {"Тести на LMS": 12, "Тести по лекційним матеріалам": 8.7},\
("Юлія", "Колодій", "[email protected]", "ПКН24-Б1"): {"Тести на LMS": 12, "Тести по лекційним матеріалам": 10.7},}
True
>>> get_total_by_activity_report(grade_book) == \
{('Степан', 'Фединяк', '[email protected]', 'ПКН24-Б1'): {"Тести на LMS": 12, "Тести по лекційним матеріалам": 8.7, "Лабораторні роботи (22 бали)": 20.7},\
("Юлія", "Колодій", "[email protected]", "ПКН24-Б1"): {"Тести на LMS": 12, "Тести по лекційним матеріалам": 10.7, "Лабораторні роботи (22 бали)": 21.5},}
True
"""
report = defaultdict(dict)
for student, student_activity_groups in grade_book.items():
for group in activity_groups or student_activity_groups:
if (found := student_activity_groups.get(group)) is not None:
grades = found.values()
report[student][group] = sum(grades)
return report
def add_grade_for_student(grade_book: grade_book_type, syllabus, name: str, surname: str,
email: str, group: str, activity_group: str, activity: str, grade: str):
"""
Put grade for one student
:param group: group of student
:param email: email of student
:param surname: surname of student
:param name: name of student
:param syllabus: syllabus with activities
:param grade_book: grade book
:param activity_group: str, activity group
:param activity: str, activity
:param grade: float, grade
:return: None
"""
if not is_activity_correct(syllabus, activity_group, activity):
return
mark = grade_check(grade, syllabus[activity_group][activity])
grade_book[(surname, name, email, group)].setdefault(activity_group, {})[activity] = mark
def introduction(functions_interface: dict[str, tuple[object, int, str]]):
"""
Returns gradebook introduction
Args:
functions_interface (dict[str, tuple[object, int, str]]): functions with descriptions
Returns:
str: string introduction
>>> introduction({\
"help": (\
lambda args : print(introduction(functions_interface), end=""),\
0,\
"Виводить це повідомлення"\
),\
"get_activities": (\
lambda args : print(get_activities(activities)),\
0,\
"Виводить всі активності у форматі (група, активність)"\
),\
"write_json": (\
lambda args : write_json(args[0], gradebook),\
1,\
"Записує грейдбук у JSON файл. Аргумент - назва файлу"\
)\
})
'Вітаємо в консольному інтерфейсі Gradebook! Ось перелік доступних команд:\\n\
help (для виклику необхідно 0 додаткових аргументів): Виводить це повідомлення.\\n\
get_activities (для виклику необхідно 0 додаткових аргументів): \
Виводить всі активності у форматі (група, активність).\\n\
write_json (для виклику необхідно 1 додаткових аргументів): \
Записує грейдбук у JSON файл. Аргумент - назва файлу.\\n\
Щоб вийти, введіть Q у будь-який момент.\\n\
Аргументи та команду потрібно розділяти крапкою з комою та пробілом: "; ".\\n\
Введіть команду у форматі: назва_команди; аргумент_1; аргумент_2; аргумент_3\\n'
"""
res = "Вітаємо в консольному інтерфейсі Gradebook! Ось перелік доступних команд:\n"
for item in functions_interface.items():
res += f"{item[0]} (для виклику необхідно {item[1][1]} додаткових аргументів): \
{item[1][2] if item[1][2] else "Опис відсутній"}.\n"
res += "Щоб вийти, введіть Q у будь-який момент.\n"
res += "Аргументи та команду потрібно розділяти крапкою з комою та пробілом: \"; \".\n"
res += "Введіть команду у форматі: назва_команди; аргумент_1; аргумент_2; аргумент_3\n"
return res
def get_user_input(functions_interface:
dict[str, tuple[object, int, str]]) -> tuple[object, list[str]] | None:
"""
Gets command from user
Args:
functions_interface (dict[str, tuple[object, int, str]]): functions with description
Returns:
(tuple[object, list[str]] | None): function to call and arguments or None if should quit
"""
while True:
print(">>> ", end="")
inp = input().strip()
if inp.lower() == "q":
return None
parts = [part for part in inp.split("; ") if part]
if not parts:
print("Неправильний формат команди.")
continue
if parts[0].lower() not in functions_interface.keys():
print("Такої команди не існує.")
continue
if len(parts) - 1 != functions_interface[parts[0].lower()][1]:
print("Неправильна кількість аргументів.")
continue
return functions_interface[parts[0].lower()][0], parts[1:]
def main():
"""
Main interface function
"""
functions_interface = {
"help": (
lambda args: print(introduction(functions_interface), end=""),
0,
"Виводить це повідомлення"
),
"get_activities": (
lambda args: print(get_activities(syllabus)),
0,
"Виводить всі активності у форматі (група, активність)"
),
"write_json": (
lambda args: write_json(args[0], gradebook, syllabus),
1,
"Записує грейдбук у JSON файл. Аргумент - назва файлу"
),
"get_grades_from_file": (
lambda args: get_grades_from_file(args[0], gradebook, syllabus),
1,
"Читає оцінки за активності за файлу та записує у grade_book. Аргумент - назва файлу"
),
"add_grade_for_student": (
lambda args: add_grade_for_student(gradebook, syllabus, *args),
1,
"Виставляє оцінку студенту. Аргументи - прізвище, ім'я, пошта, група студента, "
"група активності, активність, оцінка"
),
"get_mean_by_activity_report": (
lambda args: pprint(get_mean_by_activity_report(gradebook, args[0].split(','))),
1,
"Виводить середній бал усіх студентів за обраними активностями у форматі (назва активності: середній бал).\n"
"Аргумент - список активностей (1 або більше) через кому"
),
"get_total_by_activity_report": (
lambda args: pprint(get_total_by_activity_report(
gradebook,
args[0].split(',') if args[0] != "ALL"
else [elem[0] for elem in get_activities(syllabus)]
)),
1,
"Виводить сумарний бал для кожного зі студентів згідно обраних або всіх активностей у форматі (студент: навза активності: сумарний бал).\n"
"Аргумент - список груп активностей (1 або більше) через кому або ALL для всіх активностей"
),
"letter_report": (
lambda args: print(letter_report(args[0], gradebook)),
1,
"Виводить список студентів згідно з оцінкою у вигляді літери.\n"
"Аргумент - літера згідно якої потрібно вивести всіх студентів з такою оцінкою"
),
"stud_grade_book_to_json": (
lambda args: stud_grade_book_to_json(args[0], args[1], gradebook),
2,
"Записує грейдбук для одного студента у JSON файл. Аргумент 1 - назва файлу,\
(filename.json), аргумент 2 - email студента ([email protected])."
),
"add_student": (
lambda args: add_student(gradebook, tuple(args)),
4,
"Додає студента до грейдбуку. Приймає 4 аргументи, приклад вводу, щоб додати студента:\
add_student Прізвище; Ім'я; пошта; група"
),
"del_activity": (
lambda args: del_activity(syllabus, gradebook, args[0], args[1]),
2,
"Видаляє активність з силабуса та журналу оцінок для кожного студента \
у форматі (група, активність)"
),
"add_activity": (
lambda args: add_activity(syllabus, args[0], args[1], float(args[2])),
3,
"Додає активність в силабус за групою активності, назвою та максимальним балом"
)
}
gradebook = {}
syllabus = {'Лабораторні_роботи': {'Лабораторна_робота_1': 2.0, 'Лабораторна_робота 2': 2.0},
'Тести': {'Тест_1': 1.0, 'Тест_2': 1.0},
'Мідтерм': {'Мідтерм_теорія': 5.0, 'Мідтерм_практика': 15.0}}
functions_interface["help"][0]([])
while True:
data = get_user_input(functions_interface)
if data is None:
return
try:
data[0](data[1])
except Exception:
print("Виникла неочікувана помилка. Спробуйте перевірити введені аргументи")
if __name__ == "__main__":
import doctest
doctest.testmod()
main()