PyKubeGrader 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {PyKubeGrader-0.3.1.dist-info → PyKubeGrader-0.3.2.dist-info}/METADATA +1 -1
- {PyKubeGrader-0.3.1.dist-info → PyKubeGrader-0.3.2.dist-info}/RECORD +16 -14
- pykubegrader/build/build_folder.py +1 -1
- pykubegrader/grade_reports/grade_reports.py +171 -0
- pykubegrader/grading_tester.ipynb +477 -0
- pykubegrader/utils.py +22 -6
- pykubegrader/widgets/multiple_choice.py +3 -3
- pykubegrader/widgets/question_processor.py +7 -3
- pykubegrader/widgets/select_many.py +6 -5
- pykubegrader/widgets_base/multi_select.py +6 -6
- pykubegrader/widgets_base/reading.py +3 -1
- pykubegrader/widgets_base/select.py +5 -5
- {PyKubeGrader-0.3.1.dist-info → PyKubeGrader-0.3.2.dist-info}/LICENSE.txt +0 -0
- {PyKubeGrader-0.3.1.dist-info → PyKubeGrader-0.3.2.dist-info}/WHEEL +0 -0
- {PyKubeGrader-0.3.1.dist-info → PyKubeGrader-0.3.2.dist-info}/entry_points.txt +0 -0
- {PyKubeGrader-0.3.1.dist-info → PyKubeGrader-0.3.2.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,15 @@
|
|
1
1
|
pykubegrader/__init__.py,sha256=AoAkdfIjDDZGWLlsIRENNq06L9h46kDGBIE8vRmsCfg,311
|
2
|
+
pykubegrader/grading_tester.ipynb,sha256=2c1JgnV-zgP405Q3rlEkOlMJPm_9Qga0Z0MVWpFk5No,18531
|
2
3
|
pykubegrader/initialize.py,sha256=Bwu1q18l18FB9lGppvt-L41D5gzr3S8t6zC0_UbrASw,3994
|
3
4
|
pykubegrader/telemetry.py,sha256=50Qp5WXeF7PD5FxDLFXWFAnQ2Yobj-wL3Dxh0Hz_vh0,6552
|
4
|
-
pykubegrader/utils.py,sha256=
|
5
|
+
pykubegrader/utils.py,sha256=jlJklKvRhY3O7Hz2aaU1m0y3p_n9eMAXNnAF7LUEaPY,1275
|
5
6
|
pykubegrader/validate.py,sha256=OKnItGyd-L8QPKcsE0KRuwBI_IxKiJzMLJKZiA2j3II,11184
|
6
7
|
pykubegrader/build/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
7
8
|
pykubegrader/build/api_notebook_builder.py,sha256=dlcVrGgsvxnt6GlAUN3e-FrpsPNJKXSHni1fstRCBik,20311
|
8
|
-
pykubegrader/build/build_folder.py,sha256=
|
9
|
+
pykubegrader/build/build_folder.py,sha256=Asc-VdhXgxQfOfFIWJShhXrF2EITJOIZQ5Dz_2y-P2I,85358
|
9
10
|
pykubegrader/build/clean_folder.py,sha256=8N0KyL4eXRs0DCw-V_2jR9igtFs_mOFMQufdL6tD-38,1323
|
10
11
|
pykubegrader/build/markdown_questions.py,sha256=cSh8mkHK3hh-etJdgrZu9UQi1WPrKQtofkzLCUp1Z-w,4676
|
12
|
+
pykubegrader/grade_reports/grade_reports.py,sha256=TmT_F2enezU_BIFPBQ0oZ4l5nBgPw4PSBo-8wJ_gt4w,5915
|
11
13
|
pykubegrader/graders/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
12
14
|
pykubegrader/graders/late_assignments.py,sha256=_2-rA5RqO0BWY9WAQA_mbCxxPKTOiJOl-byD2CYWaE0,1393
|
13
15
|
pykubegrader/log_parser/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
@@ -17,21 +19,21 @@ pykubegrader/submit/submit_assignment.py,sha256=UgJXKWw5b8-bRSFnba4iHAyXnujULHcW
|
|
17
19
|
pykubegrader/tokens/tokens.py,sha256=X9f3SzrGCrAJp_BXhr6VJn5f0LxtgQ7HLPBw7zEF2BY,1198
|
18
20
|
pykubegrader/tokens/validate_token.py,sha256=MQtgz_USvSZ9JahJ48ybjp74F5aYz64lhtvuwVc4kQw,2712
|
19
21
|
pykubegrader/widgets/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
20
|
-
pykubegrader/widgets/multiple_choice.py,sha256=
|
21
|
-
pykubegrader/widgets/question_processor.py,sha256=
|
22
|
+
pykubegrader/widgets/multiple_choice.py,sha256=ag6W-HN7isHkIUmB4BxtK8T1JhuV3FBLUBAhcV6rN80,2729
|
23
|
+
pykubegrader/widgets/question_processor.py,sha256=fFH2ffMPYAJHsDn1RweEBnibfoZlSvTANUxYT3EPb5w,1375
|
22
24
|
pykubegrader/widgets/reading_question.py,sha256=y30_swHwzH8LrT8deWTnxctAAmR8BSxTlXAqMgUrAT4,3031
|
23
|
-
pykubegrader/widgets/select_many.py,sha256=
|
25
|
+
pykubegrader/widgets/select_many.py,sha256=7Bq1Je1Lx8BY3c_lVZixN-Ijw67IWO6EX3qWtcJ2NmM,4625
|
24
26
|
pykubegrader/widgets/student_info.py,sha256=xhQgKehk1r5e6N_hnjAIovLdPvQju6ZqQTOiPG0aevg,3568
|
25
27
|
pykubegrader/widgets/style.py,sha256=fVBMYy_a6Yoz21avNpiORWC3f5FD-OrVpaZ3npmunvs,1656
|
26
28
|
pykubegrader/widgets/true_false.py,sha256=QllIhHuJstJft_RuShkxI_fFFTaDAlzNZOFNs00HLIM,2842
|
27
29
|
pykubegrader/widgets/types_question.py,sha256=kZdRRXyFzOtYTmGdC7XWb_2oaxqg1WSuLcQn_sTj6Qc,2300
|
28
30
|
pykubegrader/widgets_base/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
29
|
-
pykubegrader/widgets_base/multi_select.py,sha256=
|
30
|
-
pykubegrader/widgets_base/reading.py,sha256=
|
31
|
-
pykubegrader/widgets_base/select.py,sha256=
|
32
|
-
PyKubeGrader-0.3.
|
33
|
-
PyKubeGrader-0.3.
|
34
|
-
PyKubeGrader-0.3.
|
35
|
-
PyKubeGrader-0.3.
|
36
|
-
PyKubeGrader-0.3.
|
37
|
-
PyKubeGrader-0.3.
|
31
|
+
pykubegrader/widgets_base/multi_select.py,sha256=JgjhHQJL8Pf0-1T_wdZCecAK1IgVJrZBCbR6b3jvDtk,4181
|
32
|
+
pykubegrader/widgets_base/reading.py,sha256=ChUS3NOTa_HLtNpxR8hGX80LPKMvYMypnR6dFknfxus,5430
|
33
|
+
pykubegrader/widgets_base/select.py,sha256=tEDg7GEjsZnz1646YTthTeamujVRS5jDJWMsXhmOQbI,2705
|
34
|
+
PyKubeGrader-0.3.2.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
|
35
|
+
PyKubeGrader-0.3.2.dist-info/METADATA,sha256=le0n8bDcxtGgZ-XG4-2YXPVpq6nDgai_EKdeRdcDLcg,2806
|
36
|
+
PyKubeGrader-0.3.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
37
|
+
PyKubeGrader-0.3.2.dist-info/entry_points.txt,sha256=BbLXpFZObpOXA8e3p3GcFkL-sHdUnDLUcnYmc6zx3NI,201
|
38
|
+
PyKubeGrader-0.3.2.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
|
39
|
+
PyKubeGrader-0.3.2.dist-info/RECORD,,
|
@@ -0,0 +1,171 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import requests
|
3
|
+
from requests.auth import HTTPBasicAuth
|
4
|
+
|
5
|
+
from ..build.passwords import password, user
|
6
|
+
|
7
|
+
|
8
|
+
def format_assignment_table(assignments):
|
9
|
+
# Create DataFrame
|
10
|
+
df = pd.DataFrame(assignments)
|
11
|
+
|
12
|
+
# Replacements for normalization
|
13
|
+
replacements = {
|
14
|
+
"practicequiz": "practice quiz",
|
15
|
+
"practice-quiz": "practice quiz",
|
16
|
+
"attend": "attendance",
|
17
|
+
"attendance": "attendance",
|
18
|
+
}
|
19
|
+
|
20
|
+
# Remove assignments of type 'test'
|
21
|
+
remove_assignments = ["test"]
|
22
|
+
|
23
|
+
# Apply replacements
|
24
|
+
df["assignment_name"] = df["assignment_type"].replace(replacements)
|
25
|
+
|
26
|
+
# Filter out specific assignment types
|
27
|
+
df = df[~df["assignment_type"].isin(remove_assignments)]
|
28
|
+
|
29
|
+
# Sort by week number and assignment name
|
30
|
+
df = df.sort_values(by=["assignment_name", "week_number"]).reset_index(drop=True)
|
31
|
+
|
32
|
+
return df
|
33
|
+
|
34
|
+
|
35
|
+
def get_student_grades(
|
36
|
+
student_username, api_base_url="https://engr-131-api.eastus.cloudapp.azure.com/"
|
37
|
+
):
|
38
|
+
params = {"username": student_username}
|
39
|
+
res = requests.get(
|
40
|
+
url=api_base_url.rstrip("/") + "/student-grades-testing",
|
41
|
+
params=params,
|
42
|
+
auth=HTTPBasicAuth(user(), password()),
|
43
|
+
)
|
44
|
+
|
45
|
+
[assignments, sub] = res.json()
|
46
|
+
|
47
|
+
assignments_df = format_assignment_table(assignments)
|
48
|
+
|
49
|
+
return assignments_df, pd.DataFrame(sub)
|
50
|
+
|
51
|
+
|
52
|
+
def filter_assignments(df, max_week=None, exclude_types=None):
|
53
|
+
"""
|
54
|
+
Remove assignments with week_number greater than max_week
|
55
|
+
or with specific assignment types.
|
56
|
+
|
57
|
+
:param df: DataFrame containing assignments.
|
58
|
+
:param max_week: Maximum allowed week_number (int).
|
59
|
+
:param exclude_types: A single assignment type or a list of assignment types to exclude.
|
60
|
+
:return: Filtered DataFrame.
|
61
|
+
"""
|
62
|
+
if max_week is not None:
|
63
|
+
df = df[df["week_number"] <= max_week]
|
64
|
+
|
65
|
+
if exclude_types is not None:
|
66
|
+
# Ensure exclude_types is a list
|
67
|
+
if not isinstance(exclude_types, (list, tuple, set)):
|
68
|
+
exclude_types = [exclude_types]
|
69
|
+
df = df[~df["assignment_type"].isin(exclude_types)]
|
70
|
+
|
71
|
+
return df
|
72
|
+
|
73
|
+
|
74
|
+
# import os
|
75
|
+
# import numpy as np
|
76
|
+
# import pandas as pd
|
77
|
+
# import socket
|
78
|
+
# import requests
|
79
|
+
# from IPython.core.interactiveshell import ExecutionInfo
|
80
|
+
# from requests import Response
|
81
|
+
# from requests.auth import HTTPBasicAuth
|
82
|
+
# from requests.exceptions import RequestException
|
83
|
+
# from pykubegrader.graders.late_assignments import calculate_late_submission
|
84
|
+
|
85
|
+
|
86
|
+
# api_base_url = os.getenv("DB_URL")
|
87
|
+
# student_user = "admin" # os.getenv("user_name_student")
|
88
|
+
# student_pw = "TrgpUuadm2PWtdgtC7Yt" # os.getenv("keys_student")
|
89
|
+
|
90
|
+
# from_hostname = socket.gethostname().removeprefix("jupyter-")
|
91
|
+
# from_env = os.getenv("JUPYTERHUB_USER")
|
92
|
+
# params = {"username": from_env}
|
93
|
+
|
94
|
+
# letteronly = lambda s: re.sub(r'[^a-zA-Z]', '', s)
|
95
|
+
# start_date='2025-01-06'
|
96
|
+
|
97
|
+
# # get assignment information
|
98
|
+
# res = requests.get(
|
99
|
+
# url=api_base_url.rstrip("/") + "/assignments",
|
100
|
+
# auth=HTTPBasicAuth(student_user, student_pw),)
|
101
|
+
# res.raise_for_status()
|
102
|
+
# assignments = res.json()
|
103
|
+
|
104
|
+
# # get submission information
|
105
|
+
# res = requests.get(
|
106
|
+
# url=api_base_url.rstrip("/") + "/testing/get-all-assignment-subs",
|
107
|
+
# auth=HTTPBasicAuth('testing', 'Vok8WzmuCMVYULw3tqzJ'),
|
108
|
+
# )
|
109
|
+
# subs = res.json()
|
110
|
+
# student_subs = [sub for sub in subs if sub['student_email']==from_env]
|
111
|
+
|
112
|
+
# # set up new df format
|
113
|
+
# weights = {'homework':0.15, 'lab':0.15, 'lecture':0.15, 'quiz':0.15, 'readings':0.15,
|
114
|
+
# # 'midterm':0.15, 'final':0.2
|
115
|
+
# 'labattendance':0.05, 'practicequiz':0.05, }
|
116
|
+
# assignment_types = list(set([a['assignment_type'] for a in assignments]))+['Running Avg']
|
117
|
+
# inds = [f'week{i+1}' for i in range(11)]+['Running Avg']
|
118
|
+
# restruct_grades = {k: np.zeros(len(inds)) for k in assignment_types}
|
119
|
+
# restruct_grades['inds']=inds
|
120
|
+
# new_weekly_grades = pd.DataFrame(restruct_grades)
|
121
|
+
|
122
|
+
# for assignment in assignments:
|
123
|
+
# # get the assignment from all submissions
|
124
|
+
# subs = [ sub for sub in student_subs if \
|
125
|
+
# letteronly(sub['assignment_type'])==letteronly(assignment['assignment_type']) and \
|
126
|
+
# sub['week_number']==assignment['week_number'] ]
|
127
|
+
# if len(subs)==0: continue
|
128
|
+
# if len(subs)>1:
|
129
|
+
|
130
|
+
# # get due date from assignment
|
131
|
+
# due_date = datetime.datetime.strptime(assignment['due_date'], "%Y-%m-%d %H:%M:%S")
|
132
|
+
# for sub in subs:
|
133
|
+
# entry_date = datetime.strptime(sub['timestamp'], '%Y-%m-%dT%H:%M:%SZ')
|
134
|
+
# if entry_date <= due_date:
|
135
|
+
# else after_due).append(entry)
|
136
|
+
# calculate_late_submission(due = due_date, # '2025-01-21T18:59:59Z'.
|
137
|
+
# submitted = subs"%Y-%m-%d %H:%M:%S".
|
138
|
+
# - Q0 (float): Initial value (default is 100).
|
139
|
+
# - Q_min (float): Minimum value (default is 40).
|
140
|
+
# - k (float): Decay constant per minute (default is 6.88e-5).
|
141
|
+
|
142
|
+
# # get max from before due date
|
143
|
+
|
144
|
+
# # get max score from after due date and calculate
|
145
|
+
# print(sub['assignment'])
|
146
|
+
# print(subs)
|
147
|
+
# return
|
148
|
+
# # fill out grades
|
149
|
+
# new_weekly_grades.set_index('inds',inplace=True)
|
150
|
+
# splitted = [col_name.split('-')+[grades[col_name][0]] for col_name in grades.columns]
|
151
|
+
# for week,assignment,grade in splitted: new_weekly_grades.loc[week,assignment] = grade
|
152
|
+
|
153
|
+
# # Calculate the current week (1-based indexing)
|
154
|
+
# start_date = datetime.strptime(start_date, "%Y-%m-%d")
|
155
|
+
# today = datetime.now()
|
156
|
+
# days_since_start = (today - start_date).days
|
157
|
+
# current_week = days_since_start // 7 + 1
|
158
|
+
|
159
|
+
# # Get average until current week
|
160
|
+
# new_weekly_grades.iloc[-1] = new_weekly_grades.iloc[:current_week-1].mean()
|
161
|
+
|
162
|
+
# # make new dataframe with the midterm, final, and running average
|
163
|
+
# max_key_length = max(len(k) for k in weights.keys())
|
164
|
+
# total = 0
|
165
|
+
# for k, v in weights.items():
|
166
|
+
# grade = new_weekly_grades.get(k, pd.Series([0])).iloc[-1]
|
167
|
+
# total+=grade*v
|
168
|
+
# print(f'{k:<{max_key_length}}:\t {grade:.2f}')
|
169
|
+
# print(f'\nTotal: {total}') # exclude midterm and final
|
170
|
+
|
171
|
+
# return new_out
|
@@ -0,0 +1,477 @@
|
|
1
|
+
{
|
2
|
+
"cells": [
|
3
|
+
{
|
4
|
+
"cell_type": "code",
|
5
|
+
"execution_count": null,
|
6
|
+
"metadata": {},
|
7
|
+
"outputs": [],
|
8
|
+
"source": [
|
9
|
+
"from datetime import datetime\n",
|
10
|
+
"\n",
|
11
|
+
"import pandas as pd\n",
|
12
|
+
"import requests\n",
|
13
|
+
"from build.passwords import password, user\n",
|
14
|
+
"from grade_reports.grade_reports import filter_assignments, get_student_grades\n",
|
15
|
+
"from requests.auth import HTTPBasicAuth\n",
|
16
|
+
"\n",
|
17
|
+
"from pykubegrader.graders.late_assignments import calculate_late_submission\n",
|
18
|
+
"\n",
|
19
|
+
"api_base_url = \"https://engr-131-api.eastus.cloudapp.azure.com/\"\n",
|
20
|
+
"\n",
|
21
|
+
"\n",
|
22
|
+
"def get_all_students():\n",
|
23
|
+
" res = requests.get(\n",
|
24
|
+
" url=api_base_url.rstrip(\"/\") + \"/get-all-submission-emails\",\n",
|
25
|
+
" auth=HTTPBasicAuth(user(), password()),\n",
|
26
|
+
" )\n",
|
27
|
+
"\n",
|
28
|
+
" return res.json()\n",
|
29
|
+
"\n",
|
30
|
+
"\n",
|
31
|
+
"# def get_student_grades(student_id):\n",
|
32
|
+
"# params = {\"username\": student_id}\n",
|
33
|
+
"# res = requests.get(\n",
|
34
|
+
"# url=api_base_url.rstrip(\"/\") + \"/student-grades-testing\",\n",
|
35
|
+
"# params=params,\n",
|
36
|
+
"# auth=HTTPBasicAuth(user(), password()),\n",
|
37
|
+
"# )\n",
|
38
|
+
"\n",
|
39
|
+
"# [assignments, sub] = res.json()\n",
|
40
|
+
"\n",
|
41
|
+
"# return assignments, sub\n",
|
42
|
+
"\n",
|
43
|
+
"\n",
|
44
|
+
"def get_student(student):\n",
|
45
|
+
" print(student)\n",
|
46
|
+
" # Get assignments and submissions for the student (assumed functions)\n",
|
47
|
+
" assignments, submissions = get_student_grades(student)\n",
|
48
|
+
"\n",
|
49
|
+
" # Recalculate grades and get a grades dictionary\n",
|
50
|
+
" grades_dict = recalculate_best_grades(assignments, submissions)\n",
|
51
|
+
"\n",
|
52
|
+
" # Calculate averages and build a row for the student\n",
|
53
|
+
" row = calculate_averages(grades_dict, student)\n",
|
54
|
+
"\n",
|
55
|
+
" # Convert the row (a dictionary) into a DataFrame\n",
|
56
|
+
" # row_df = pd.DataFrame([row])\n",
|
57
|
+
"\n",
|
58
|
+
" return row\n",
|
59
|
+
"\n",
|
60
|
+
"\n",
|
61
|
+
"def get_all_student_grades():\n",
|
62
|
+
" # Initialize an empty DataFrame to hold all student grades\n",
|
63
|
+
" df = pd.DataFrame()\n",
|
64
|
+
"\n",
|
65
|
+
" # Get all students (assuming get_all_students() is a defined function)\n",
|
66
|
+
" students = get_all_students()\n",
|
67
|
+
"\n",
|
68
|
+
" for student in students:\n",
|
69
|
+
" row_df = get_student(student)\n",
|
70
|
+
"\n",
|
71
|
+
" # Append the row to the DataFrame\n",
|
72
|
+
" df = pd.concat([df, row_df], ignore_index=True)\n",
|
73
|
+
"\n",
|
74
|
+
" return df\n",
|
75
|
+
"\n",
|
76
|
+
"\n",
|
77
|
+
"def get_max_deadline(assignments, assignment_name, week_number):\n",
|
78
|
+
" matching_rows = assignments[\n",
|
79
|
+
" (assignments[\"week_number\"] == week_number)\n",
|
80
|
+
" & (assignments[\"assignment_name\"] == assignment_name)\n",
|
81
|
+
" ]\n",
|
82
|
+
"\n",
|
83
|
+
" max_timestamp = matching_rows[\"due_date\"].max()\n",
|
84
|
+
" return max_timestamp\n",
|
85
|
+
"\n",
|
86
|
+
"\n",
|
87
|
+
"def calculate_averages(grades_dict, student_id):\n",
|
88
|
+
" # Create a DataFrame from the dictionary\n",
|
89
|
+
" df = pd.DataFrame(\n",
|
90
|
+
" grades_dict\n",
|
91
|
+
" ).T # Transpose the dictionary to make assignments rows\n",
|
92
|
+
" df[\"score\"] = df[\"score\"].astype(float) # Ensure the scores are floats\n",
|
93
|
+
"\n",
|
94
|
+
" # Create a dictionary for the row data\n",
|
95
|
+
" row_data = {\"student_id\": student_id}\n",
|
96
|
+
"\n",
|
97
|
+
" # Add scores for each assignment as separate columns\n",
|
98
|
+
" for assignment, details in grades_dict.items():\n",
|
99
|
+
" column_name = (\n",
|
100
|
+
" f\"{details['assignment_name']}_week_{details['week_number']}\".replace(\n",
|
101
|
+
" \"_\", \" \"\n",
|
102
|
+
" )\n",
|
103
|
+
" )\n",
|
104
|
+
" row_data[column_name] = float(details[\"score\"])\n",
|
105
|
+
"\n",
|
106
|
+
" # Calculate and add average scores for each assignment category\n",
|
107
|
+
" averages = df.groupby(\"assignment_name\")[\"score\"].mean()\n",
|
108
|
+
" for assignment_name, avg_score in averages.items():\n",
|
109
|
+
" row_data[f\"{assignment_name}_average\".replace(\"_\", \" \")] = avg_score\n",
|
110
|
+
"\n",
|
111
|
+
" # # Course Percentage Performance\n",
|
112
|
+
" row_data[\"Current Course Percentage\"] = (\n",
|
113
|
+
" (row_data[\"quiz average\"] + row_data[\"practice quiz average\"] * 0.1) * 15\n",
|
114
|
+
" + (row_data[\"homework average\"]) * 15\n",
|
115
|
+
" + row_data[\"lab average\"] * 15\n",
|
116
|
+
" + row_data[\"lecture average\"] * 15\n",
|
117
|
+
" + row_data[\"readings average\"] * 15\n",
|
118
|
+
" + row_data[\"attendance average\"] * 5\n",
|
119
|
+
" ) / (15 + 15 + 15 + 15 + 15 + 5)\n",
|
120
|
+
"\n",
|
121
|
+
" # Create a single-row DataFrame\n",
|
122
|
+
" single_row_df = pd.DataFrame([row_data])\n",
|
123
|
+
" return single_row_df\n",
|
124
|
+
"\n",
|
125
|
+
"\n",
|
126
|
+
"def recalculate_best_grades(\n",
|
127
|
+
" assignments,\n",
|
128
|
+
" submissions,\n",
|
129
|
+
" custom_order=[\n",
|
130
|
+
" \"lecture\",\n",
|
131
|
+
" \"homework\",\n",
|
132
|
+
" \"lab\",\n",
|
133
|
+
" \"attendance\",\n",
|
134
|
+
" \"readings\",\n",
|
135
|
+
" \"quiz\",\n",
|
136
|
+
" \"practice quiz\",\n",
|
137
|
+
" ],\n",
|
138
|
+
"):\n",
|
139
|
+
" assignments[\"assignment_name\"] = pd.Categorical(\n",
|
140
|
+
" assignments[\"assignment_name\"], categories=custom_order, ordered=True\n",
|
141
|
+
" )\n",
|
142
|
+
"\n",
|
143
|
+
" # Sort by 'Category' (custom order) and then by 'Value'\n",
|
144
|
+
" sorted_df = assignments.sort_values(by=[\"assignment_name\", \"week_number\"])\n",
|
145
|
+
"\n",
|
146
|
+
" week_number = 3\n",
|
147
|
+
"\n",
|
148
|
+
" exclude_types = None\n",
|
149
|
+
"\n",
|
150
|
+
" sorted_df = filter_assignments(sorted_df, exclude_types=None, max_week=week_number)\n",
|
151
|
+
"\n",
|
152
|
+
" # Get unique pairs of 'week_number' and 'assignment_name'\n",
|
153
|
+
" unique_pairs = sorted_df[[\"week_number\", \"assignment_name\"]].drop_duplicates()\n",
|
154
|
+
"\n",
|
155
|
+
" # Build an iterator over the unique pairs\n",
|
156
|
+
" pair_iterator = unique_pairs.itertuples(index=False, name=None)\n",
|
157
|
+
"\n",
|
158
|
+
" grade_dictionary = {}\n",
|
159
|
+
"\n",
|
160
|
+
" # Filter rows matching each pair\n",
|
161
|
+
" for week_number, assignment_name in pair_iterator:\n",
|
162
|
+
" matching_rows = sorted_df[\n",
|
163
|
+
" (sorted_df[\"week_number\"] == week_number)\n",
|
164
|
+
" & (sorted_df[\"assignment_name\"] == assignment_name)\n",
|
165
|
+
" ]\n",
|
166
|
+
"\n",
|
167
|
+
" grade_dictionary[f\"Week {week_number} {assignment_name}\"] = {\n",
|
168
|
+
" \"week_number\": week_number,\n",
|
169
|
+
" \"assignment_name\": assignment_name,\n",
|
170
|
+
" \"score\": 0,\n",
|
171
|
+
" }\n",
|
172
|
+
"\n",
|
173
|
+
" # # extracts the names of the assignments grades which are associated with a given assignment, this helps if two assignments have the same name.\n",
|
174
|
+
" # title = matching_rows[\"title\"]\n",
|
175
|
+
"\n",
|
176
|
+
" # print(title)\n",
|
177
|
+
"\n",
|
178
|
+
" # gets the most recent assignment due date.\n",
|
179
|
+
" assignment_due_date = get_max_deadline(\n",
|
180
|
+
" assignments, assignment_name, week_number\n",
|
181
|
+
" )\n",
|
182
|
+
" max_score = matching_rows[\"max_score\"].min()\n",
|
183
|
+
"\n",
|
184
|
+
" # extracts the names of the assignments grades which are associated with a given assignment, this helps if two assignments have the same name.\n",
|
185
|
+
" for assignment_db_name in matching_rows[\"title\"]:\n",
|
186
|
+
" assignment_submission = submissions[\n",
|
187
|
+
" submissions[\"assignment\"] == assignment_db_name.replace(\" \", \"\").lower()\n",
|
188
|
+
" ]\n",
|
189
|
+
"\n",
|
190
|
+
" # Now we need to get the best score for an assignment based on the submission time.\n",
|
191
|
+
" for index, submission in assignment_submission.iterrows():\n",
|
192
|
+
" raw_score = submission[\"raw_score\"] / max_score\n",
|
193
|
+
"\n",
|
194
|
+
" assignment_due_date = datetime.fromisoformat(\n",
|
195
|
+
" assignment_due_date\n",
|
196
|
+
" ).strftime(\"%Y-%m-%d %H:%M:%S\")\n",
|
197
|
+
" submission_timestamp = datetime.fromisoformat(\n",
|
198
|
+
" submission[\"timestamp\"]\n",
|
199
|
+
" ).strftime(\"%Y-%m-%d %H:%M:%S\")\n",
|
200
|
+
"\n",
|
201
|
+
" deflation_fraction = calculate_late_submission(\n",
|
202
|
+
" assignment_due_date, submission_timestamp\n",
|
203
|
+
" )\n",
|
204
|
+
"\n",
|
205
|
+
" score = raw_score * deflation_fraction\n",
|
206
|
+
"\n",
|
207
|
+
" if (\n",
|
208
|
+
" score\n",
|
209
|
+
" > grade_dictionary[f\"Week {week_number} {assignment_name}\"][\"score\"]\n",
|
210
|
+
" ):\n",
|
211
|
+
" grade_dictionary[f\"Week {week_number} {assignment_name}\"][\n",
|
212
|
+
" \"score\"\n",
|
213
|
+
" ] = score\n",
|
214
|
+
"\n",
|
215
|
+
" return grade_dictionary"
|
216
|
+
]
|
217
|
+
},
|
218
|
+
{
|
219
|
+
"cell_type": "code",
|
220
|
+
"execution_count": null,
|
221
|
+
"metadata": {},
|
222
|
+
"outputs": [],
|
223
|
+
"source": [
|
224
|
+
"import requests\n",
|
225
|
+
"from build.passwords import password, user\n",
|
226
|
+
"from requests.auth import HTTPBasicAuth\n",
|
227
|
+
"\n",
|
228
|
+
"api_base_url = \"https://engr-131-api.eastus.cloudapp.azure.com/\"\n",
|
229
|
+
"\n",
|
230
|
+
"\n",
|
231
|
+
"def get_all_students():\n",
|
232
|
+
" res = requests.get(\n",
|
233
|
+
" url=api_base_url.rstrip(\"/\") + \"/get-all-submission-emails\",\n",
|
234
|
+
" auth=HTTPBasicAuth(user(), password()),\n",
|
235
|
+
" )\n",
|
236
|
+
"\n",
|
237
|
+
" return res.json()\n",
|
238
|
+
"\n",
|
239
|
+
"\n",
|
240
|
+
"# def get_student_grades(student_id):\n",
|
241
|
+
"# params = {\"username\": student_id}\n",
|
242
|
+
"# res = requests.get(\n",
|
243
|
+
"# url=api_base_url.rstrip(\"/\") + \"/student-grades-testing\",\n",
|
244
|
+
"# params=params,\n",
|
245
|
+
"# auth=HTTPBasicAuth(user(), password()),\n",
|
246
|
+
"# )\n",
|
247
|
+
"\n",
|
248
|
+
"# [assignments, sub] = res.json()\n",
|
249
|
+
"\n",
|
250
|
+
"# return assignments, sub\n",
|
251
|
+
"\n",
|
252
|
+
"\n",
|
253
|
+
"def get_all_student_grades():\n",
|
254
|
+
" # Initialize an empty DataFrame to hold all student grades\n",
|
255
|
+
" df = pd.DataFrame()\n",
|
256
|
+
"\n",
|
257
|
+
" # Get all students (assuming get_all_students() is a defined function)\n",
|
258
|
+
" students = get_all_students()\n",
|
259
|
+
"\n",
|
260
|
+
" for student in students:\n",
|
261
|
+
" print(student)\n",
|
262
|
+
" # Get assignments and submissions for the student (assumed functions)\n",
|
263
|
+
" assignments, submissions = get_student_grades(student)\n",
|
264
|
+
"\n",
|
265
|
+
" # Recalculate grades and get a grades dictionary\n",
|
266
|
+
" grades_dict = recalculate_best_grades(assignments, submissions)\n",
|
267
|
+
"\n",
|
268
|
+
" # Calculate averages and build a row for the student\n",
|
269
|
+
" row = calculate_averages(grades_dict, student)\n",
|
270
|
+
"\n",
|
271
|
+
" # # Convert the row (a dictionary) into a DataFrame\n",
|
272
|
+
" # row_df = pd.DataFrame([row])\n",
|
273
|
+
"\n",
|
274
|
+
" # Append the row to the DataFrame\n",
|
275
|
+
" df = pd.concat([df, row], ignore_index=True)\n",
|
276
|
+
"\n",
|
277
|
+
" return df\n",
|
278
|
+
"\n",
|
279
|
+
"\n",
|
280
|
+
"def get_max_deadline(assignments, assignment_name, week_number):\n",
|
281
|
+
" matching_rows = assignments[\n",
|
282
|
+
" (assignments[\"week_number\"] == week_number)\n",
|
283
|
+
" & (assignments[\"assignment_name\"] == assignment_name)\n",
|
284
|
+
" ]\n",
|
285
|
+
"\n",
|
286
|
+
" max_timestamp = matching_rows[\"due_date\"].max()\n",
|
287
|
+
" return max_timestamp\n",
|
288
|
+
"\n",
|
289
|
+
"\n",
|
290
|
+
"def calculate_averages(grades_dict, student_id):\n",
|
291
|
+
" # Create a DataFrame from the dictionary\n",
|
292
|
+
" df = pd.DataFrame(\n",
|
293
|
+
" grades_dict\n",
|
294
|
+
" ).T # Transpose the dictionary to make assignments rows\n",
|
295
|
+
" df[\"score\"] = df[\"score\"].astype(float) # Ensure the scores are floats\n",
|
296
|
+
"\n",
|
297
|
+
" # Create a dictionary for the row data\n",
|
298
|
+
" row_data = {\"student_id\": student_id}\n",
|
299
|
+
"\n",
|
300
|
+
" # Add scores for each assignment as separate columns\n",
|
301
|
+
" for assignment, details in grades_dict.items():\n",
|
302
|
+
" column_name = (\n",
|
303
|
+
" f\"{details['assignment_name']}_week_{details['week_number']}\".replace(\n",
|
304
|
+
" \"_\", \" \"\n",
|
305
|
+
" )\n",
|
306
|
+
" )\n",
|
307
|
+
" row_data[column_name] = float(details[\"score\"])\n",
|
308
|
+
"\n",
|
309
|
+
" # Calculate and add average scores for each assignment category\n",
|
310
|
+
" averages = df.groupby(\"assignment_name\")[\"score\"].mean()\n",
|
311
|
+
" for assignment_name, avg_score in averages.items():\n",
|
312
|
+
" row_data[f\"{assignment_name}_average\".replace(\"_\", \" \")] = avg_score\n",
|
313
|
+
"\n",
|
314
|
+
" # # Course Percentage Performance\n",
|
315
|
+
" row_data[\"Current Course Percentage\"] = (\n",
|
316
|
+
" (row_data[\"quiz average\"] + row_data[\"practice quiz average\"] * 0.1) * 15\n",
|
317
|
+
" + (row_data[\"homework average\"]) * 15\n",
|
318
|
+
" + row_data[\"lab average\"] * 15\n",
|
319
|
+
" + row_data[\"lecture average\"] * 15\n",
|
320
|
+
" + row_data[\"readings average\"] * 15\n",
|
321
|
+
" + row_data[\"attendance average\"] * 5\n",
|
322
|
+
" ) / (15 + 15 + 15 + 15 + 15 + 5)\n",
|
323
|
+
"\n",
|
324
|
+
" # Create a single-row DataFrame\n",
|
325
|
+
" single_row_df = pd.DataFrame([row_data])\n",
|
326
|
+
" return single_row_df\n",
|
327
|
+
"\n",
|
328
|
+
"\n",
|
329
|
+
"def recalculate_best_grades(\n",
|
330
|
+
" assignments,\n",
|
331
|
+
" submissions,\n",
|
332
|
+
" custom_order=[\n",
|
333
|
+
" \"lecture\",\n",
|
334
|
+
" \"homework\",\n",
|
335
|
+
" \"lab\",\n",
|
336
|
+
" \"attendance\",\n",
|
337
|
+
" \"readings\",\n",
|
338
|
+
" \"quiz\",\n",
|
339
|
+
" \"practice quiz\",\n",
|
340
|
+
" ],\n",
|
341
|
+
"):\n",
|
342
|
+
" assignments[\"assignment_name\"] = pd.Categorical(\n",
|
343
|
+
" assignments[\"assignment_name\"], categories=custom_order, ordered=True\n",
|
344
|
+
" )\n",
|
345
|
+
"\n",
|
346
|
+
" # Sort by 'Category' (custom order) and then by 'Value'\n",
|
347
|
+
" sorted_df = assignments.sort_values(by=[\"assignment_name\", \"week_number\"])\n",
|
348
|
+
"\n",
|
349
|
+
" week_number = 3\n",
|
350
|
+
"\n",
|
351
|
+
" exclude_types = None\n",
|
352
|
+
"\n",
|
353
|
+
" sorted_df = filter_assignments(sorted_df, exclude_types=None, max_week=week_number)\n",
|
354
|
+
"\n",
|
355
|
+
" # Get unique pairs of 'week_number' and 'assignment_name'\n",
|
356
|
+
" unique_pairs = sorted_df[[\"week_number\", \"assignment_name\"]].drop_duplicates()\n",
|
357
|
+
"\n",
|
358
|
+
" # Build an iterator over the unique pairs\n",
|
359
|
+
" pair_iterator = unique_pairs.itertuples(index=False, name=None)\n",
|
360
|
+
"\n",
|
361
|
+
" grade_dictionary = {}\n",
|
362
|
+
"\n",
|
363
|
+
" # Filter rows matching each pair\n",
|
364
|
+
" for week_number, assignment_name in pair_iterator:\n",
|
365
|
+
" matching_rows = sorted_df[\n",
|
366
|
+
" (sorted_df[\"week_number\"] == week_number)\n",
|
367
|
+
" & (sorted_df[\"assignment_name\"] == assignment_name)\n",
|
368
|
+
" ]\n",
|
369
|
+
"\n",
|
370
|
+
" grade_dictionary[f\"Week {week_number} {assignment_name}\"] = {\n",
|
371
|
+
" \"week_number\": week_number,\n",
|
372
|
+
" \"assignment_name\": assignment_name,\n",
|
373
|
+
" \"score\": 0,\n",
|
374
|
+
" }\n",
|
375
|
+
"\n",
|
376
|
+
" # # extracts the names of the assignments grades which are associated with a given assignment, this helps if two assignments have the same name.\n",
|
377
|
+
" # title = matching_rows[\"title\"]\n",
|
378
|
+
"\n",
|
379
|
+
" # print(title)\n",
|
380
|
+
"\n",
|
381
|
+
" # gets the most recent assignment due date.\n",
|
382
|
+
" assignment_due_date = get_max_deadline(\n",
|
383
|
+
" assignments, assignment_name, week_number\n",
|
384
|
+
" )\n",
|
385
|
+
" max_score = matching_rows[\"max_score\"].min()\n",
|
386
|
+
"\n",
|
387
|
+
" # extracts the names of the assignments grades which are associated with a given assignment, this helps if two assignments have the same name.\n",
|
388
|
+
" for assignment_db_name in matching_rows[\"title\"]:\n",
|
389
|
+
" assignment_submission = submissions[\n",
|
390
|
+
" submissions[\"assignment\"] == assignment_db_name.replace(\" \", \"\").lower()\n",
|
391
|
+
" ]\n",
|
392
|
+
"\n",
|
393
|
+
" # Now we need to get the best score for an assignment based on the submission time.\n",
|
394
|
+
" for index, submission in assignment_submission.iterrows():\n",
|
395
|
+
" raw_score = submission[\"raw_score\"] / max_score\n",
|
396
|
+
"\n",
|
397
|
+
" assignment_due_date = datetime.fromisoformat(\n",
|
398
|
+
" assignment_due_date\n",
|
399
|
+
" ).strftime(\"%Y-%m-%d %H:%M:%S\")\n",
|
400
|
+
" submission_timestamp = datetime.fromisoformat(\n",
|
401
|
+
" submission[\"timestamp\"]\n",
|
402
|
+
" ).strftime(\"%Y-%m-%d %H:%M:%S\")\n",
|
403
|
+
"\n",
|
404
|
+
" deflation_fraction = calculate_late_submission(\n",
|
405
|
+
" assignment_due_date, submission_timestamp\n",
|
406
|
+
" )\n",
|
407
|
+
"\n",
|
408
|
+
" score = raw_score * deflation_fraction\n",
|
409
|
+
"\n",
|
410
|
+
" if (\n",
|
411
|
+
" score\n",
|
412
|
+
" > grade_dictionary[f\"Week {week_number} {assignment_name}\"][\"score\"]\n",
|
413
|
+
" ):\n",
|
414
|
+
" grade_dictionary[f\"Week {week_number} {assignment_name}\"][\n",
|
415
|
+
" \"score\"\n",
|
416
|
+
" ] = score\n",
|
417
|
+
"\n",
|
418
|
+
" return grade_dictionary"
|
419
|
+
]
|
420
|
+
},
|
421
|
+
{
|
422
|
+
"cell_type": "code",
|
423
|
+
"execution_count": null,
|
424
|
+
"metadata": {},
|
425
|
+
"outputs": [],
|
426
|
+
"source": [
|
427
|
+
"get_student(\"jab864\")"
|
428
|
+
]
|
429
|
+
},
|
430
|
+
{
|
431
|
+
"cell_type": "code",
|
432
|
+
"execution_count": null,
|
433
|
+
"metadata": {},
|
434
|
+
"outputs": [],
|
435
|
+
"source": [
|
436
|
+
"df = get_all_student_grades()"
|
437
|
+
]
|
438
|
+
},
|
439
|
+
{
|
440
|
+
"cell_type": "code",
|
441
|
+
"execution_count": null,
|
442
|
+
"metadata": {},
|
443
|
+
"outputs": [],
|
444
|
+
"source": [
|
445
|
+
"df.to_csv(\"grades\")"
|
446
|
+
]
|
447
|
+
},
|
448
|
+
{
|
449
|
+
"cell_type": "code",
|
450
|
+
"execution_count": null,
|
451
|
+
"metadata": {},
|
452
|
+
"outputs": [],
|
453
|
+
"source": []
|
454
|
+
}
|
455
|
+
],
|
456
|
+
"metadata": {
|
457
|
+
"kernelspec": {
|
458
|
+
"display_name": "engr131_dev",
|
459
|
+
"language": "python",
|
460
|
+
"name": "python3"
|
461
|
+
},
|
462
|
+
"language_info": {
|
463
|
+
"codemirror_mode": {
|
464
|
+
"name": "ipython",
|
465
|
+
"version": 3
|
466
|
+
},
|
467
|
+
"file_extension": ".py",
|
468
|
+
"mimetype": "text/x-python",
|
469
|
+
"name": "python",
|
470
|
+
"nbconvert_exporter": "python",
|
471
|
+
"pygments_lexer": "ipython3",
|
472
|
+
"version": "3.12.7"
|
473
|
+
}
|
474
|
+
},
|
475
|
+
"nbformat": 4,
|
476
|
+
"nbformat_minor": 2
|
477
|
+
}
|
pykubegrader/utils.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
import random
|
3
|
-
from typing import
|
3
|
+
from typing import Any, Optional
|
4
4
|
|
5
5
|
import panel as pn
|
6
6
|
|
@@ -9,22 +9,38 @@ student_user = os.getenv("user_name_student")
|
|
9
9
|
student_pw = os.getenv("keys_student")
|
10
10
|
|
11
11
|
|
12
|
-
def list_of_lists(options: list) -> bool:
|
12
|
+
def list_of_lists(options: list[Any]) -> bool:
|
13
13
|
return all(isinstance(elem, list) for elem in options)
|
14
14
|
|
15
15
|
|
16
|
-
def shuffle_options(options: list[
|
16
|
+
def shuffle_options(options: list[Any], seed: int) -> None:
|
17
|
+
"""
|
18
|
+
Shuffle options in list[Optional[str]] or list[list[Optional[str]]].
|
19
|
+
Shuffling is done in place.
|
20
|
+
We annotate options as list[Any] just to keep Mypy happy.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
options (list[Any]): List of options to shuffle
|
24
|
+
seed (int): Seed for RNG
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
None
|
28
|
+
"""
|
17
29
|
random.seed(seed)
|
18
|
-
random.shuffle(options)
|
19
30
|
|
20
|
-
|
31
|
+
if list_of_lists(options):
|
32
|
+
for i in range(len(options)):
|
33
|
+
inner_list: list[Optional[str]] = options[i]
|
34
|
+
random.shuffle(inner_list)
|
35
|
+
else:
|
36
|
+
random.shuffle(options)
|
21
37
|
|
22
38
|
|
23
39
|
def shuffle_questions(
|
24
40
|
desc_widgets: list[pn.pane.HTML],
|
25
41
|
dropdowns: list[pn.widgets.Select] | list[pn.Column],
|
26
42
|
seed: int,
|
27
|
-
) -> list[
|
43
|
+
) -> list[tuple[pn.pane.HTML, pn.widgets.Select | pn.Column]]:
|
28
44
|
random.seed(seed)
|
29
45
|
|
30
46
|
# Combine widgets into pairs
|
@@ -15,12 +15,12 @@ def MCQ(
|
|
15
15
|
descriptions: list[str],
|
16
16
|
options: list[str] | list[list[str]],
|
17
17
|
initial_vals: list[str],
|
18
|
-
) -> Tuple[list[pn.
|
18
|
+
) -> Tuple[list[pn.Column], list[pn.widgets.RadioBoxGroup]]:
|
19
19
|
# Process descriptions through `process_questions_and_codes`
|
20
20
|
processed_titles, code_blocks = process_questions_and_codes(descriptions)
|
21
21
|
|
22
22
|
# Create rows for each description and its code block
|
23
|
-
desc_widgets = []
|
23
|
+
desc_widgets: list[pn.Column] = []
|
24
24
|
for title, code_block in zip(processed_titles, code_blocks):
|
25
25
|
# Create an HTML pane for the title
|
26
26
|
title_pane = pn.pane.HTML(
|
@@ -34,7 +34,7 @@ def MCQ(
|
|
34
34
|
else:
|
35
35
|
desc_widgets.append(pn.Column(title_pane, sizing_mode="stretch_width"))
|
36
36
|
|
37
|
-
radio_buttons = [
|
37
|
+
radio_buttons: list[pn.widgets.RadioBoxGroup] = [
|
38
38
|
pn.widgets.RadioBoxGroup(
|
39
39
|
options=option,
|
40
40
|
value=value,
|
@@ -1,13 +1,17 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
1
3
|
import panel as pn
|
2
4
|
|
3
5
|
|
4
|
-
def process_questions_and_codes(
|
6
|
+
def process_questions_and_codes(
|
7
|
+
titles: str | list[str],
|
8
|
+
) -> tuple[list[str], list[Optional[pn.pane.Markdown]]]:
|
5
9
|
# Ensure titles is a list
|
6
10
|
if isinstance(titles, str):
|
7
11
|
titles = [titles]
|
8
12
|
|
9
|
-
processed_titles = []
|
10
|
-
code_blocks = []
|
13
|
+
processed_titles: list[str] = []
|
14
|
+
code_blocks: list[Optional[pn.pane.Markdown]] = []
|
11
15
|
|
12
16
|
for title in titles:
|
13
17
|
# Split the title at the "```python" delimiter
|
@@ -30,20 +30,21 @@ def MultiSelect(
|
|
30
30
|
for question, option_set in zip(descriptions, options):
|
31
31
|
# Process descriptions through `process_questions_and_codes`
|
32
32
|
processed_titles, code_blocks = process_questions_and_codes(question)
|
33
|
-
|
33
|
+
|
34
34
|
# Create an HTML pane for the title
|
35
|
-
title_pane
|
35
|
+
title_pane = pn.pane.HTML(
|
36
36
|
f"<hr style='border:1px solid lightgray; width:100%;'>"
|
37
37
|
f"<div style='text-align: left; width: {desc_width};'><b>{processed_titles[0]}</b></div>"
|
38
38
|
)
|
39
39
|
# Add the title and code block in a row
|
40
40
|
if code_blocks[0]:
|
41
|
-
desc_widget = pn.Column(
|
41
|
+
desc_widget = pn.Column(
|
42
|
+
title_pane, code_blocks[0], sizing_mode="stretch_width"
|
43
|
+
)
|
42
44
|
else:
|
43
45
|
desc_widget = title_pane
|
44
|
-
|
46
|
+
|
45
47
|
# # Create description widget with separator
|
46
|
-
|
47
48
|
|
48
49
|
# Create checkboxes for current question
|
49
50
|
checkbox_set = [
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import time
|
2
|
-
from typing import Callable, Tuple
|
2
|
+
from typing import Callable, Optional, Tuple
|
3
3
|
|
4
4
|
import panel as pn
|
5
5
|
|
6
6
|
from ..telemetry import ensure_responses, score_question, update_responses
|
7
|
-
from ..utils import shuffle_questions
|
7
|
+
from ..utils import shuffle_options, shuffle_questions
|
8
8
|
from ..widgets.style import drexel_colors, raw_css
|
9
9
|
|
10
10
|
# Pass the custom CSS to Panel
|
@@ -19,12 +19,12 @@ class MultiSelectQuestion:
|
|
19
19
|
self,
|
20
20
|
title: str,
|
21
21
|
style: Callable[
|
22
|
-
[list[str], list[list[str]], list[bool]],
|
22
|
+
[list[str], list[list[Optional[str]]], list[bool]],
|
23
23
|
Tuple[list[pn.pane.HTML], list[pn.Column]],
|
24
24
|
],
|
25
25
|
question_number: int,
|
26
26
|
keys: list[str],
|
27
|
-
options: list[list[str]],
|
27
|
+
options: list[list[Optional[str]]],
|
28
28
|
descriptions: list[str],
|
29
29
|
points: int,
|
30
30
|
):
|
@@ -56,8 +56,8 @@ class MultiSelectQuestion:
|
|
56
56
|
|
57
57
|
self.initial_vals = [getattr(self, key) for key in self.keys]
|
58
58
|
|
59
|
-
#
|
60
|
-
|
59
|
+
# add shuffle options to multi_select.py
|
60
|
+
shuffle_options(options, seed)
|
61
61
|
|
62
62
|
description_widgets, self.widgets = style(
|
63
63
|
descriptions, options, self.initial_vals
|
@@ -53,9 +53,11 @@ class ReadingPython:
|
|
53
53
|
# Comment dropdowns
|
54
54
|
#
|
55
55
|
|
56
|
+
shuffle_options(options["comments_options"], seed)
|
57
|
+
|
56
58
|
self.dropdowns_for_comments: dict[str, pn.widgets.Select] = {
|
57
59
|
line: pn.widgets.Select(
|
58
|
-
options=
|
60
|
+
options=options["comments_options"],
|
59
61
|
name=f"Line {line}:",
|
60
62
|
value=getattr(self, f"q{question_number}_{i_comments + 1}"),
|
61
63
|
width=600,
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import time
|
2
|
-
from typing import Callable, Tuple
|
2
|
+
from typing import Callable, Optional, Tuple
|
3
3
|
|
4
4
|
import panel as pn
|
5
5
|
|
6
6
|
from ..telemetry import ensure_responses, score_question, update_responses
|
7
|
-
from ..utils import shuffle_questions
|
7
|
+
from ..utils import shuffle_options, shuffle_questions
|
8
8
|
from ..widgets.style import drexel_colors
|
9
9
|
|
10
10
|
# Pass custom CSS to Panel
|
@@ -21,7 +21,7 @@ class SelectQuestion:
|
|
21
21
|
],
|
22
22
|
question_number: int,
|
23
23
|
keys: list[str],
|
24
|
-
options: list,
|
24
|
+
options: list[Optional[str]] | list[list[Optional[str]]],
|
25
25
|
descriptions: list[str],
|
26
26
|
points: int,
|
27
27
|
shuffle_answers: bool = True,
|
@@ -46,8 +46,8 @@ class SelectQuestion:
|
|
46
46
|
|
47
47
|
self.initial_vals: list = [getattr(self, key) for key in self.keys]
|
48
48
|
|
49
|
-
|
50
|
-
|
49
|
+
if shuffle_answers:
|
50
|
+
shuffle_options(options, seed)
|
51
51
|
|
52
52
|
desc_widgets, self.widgets = style(descriptions, options, self.initial_vals)
|
53
53
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|