Skip to content

Commit 2d37cf2

Browse files
authored
Merge pull request #1 from jonaslache/dev
Merge dev to main
2 parents b38025a + 341101c commit 2d37cf2

2 files changed

Lines changed: 72 additions & 12 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
77

88
A Python tool to extract information from Moodle response files.
99

10+
## Version v0.2
11+
12+
On 12th April 2024, version v0.2 of the Response File Processor has been released. The following features have been added:
13+
14+
- Option to create a column with the random seed used in each attempt.
15+
- Option to specify a list of strings that the tool searches for in each row of the quiz data. The column will be filled with boolean values (True or False), depending on whether each strings are present in the individual rows. For example, the list of strings may include answer notes (e.g. `prt1-1-F`) or answer test results (e.g. `ATList_wrongentries`).
16+
1017
## About
1118

1219
The *STACK Response File Processor* is a Python tool with a graphical user
@@ -15,6 +22,8 @@ student responses to STACK questions within Moodle quizzes, including:
1522

1623
- Value and state (valid, invalid, score) of STACK input fields
1724
- The score of the potential response trees and whether the PRTs were active
25+
- Information about whether user-specified strings are present in each row of the quiz data (True/False).
26+
- The [random seed](https://en.wikipedia.org/wiki/Random_seed) used in the specific attempt
1827
- STACKrate evaluation results (see
1928
<https://www.ruhr-uni-bochum.de/stackrate-maths/>)
2029

response-file-processor.py

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Import necessary libraries
77
import tkinter as tk
88
from tkinter import filedialog
9-
from tkinter import ttk
9+
from tkinter import ttk, Text
1010
import pandas as pd
1111
import re
1212
import json
@@ -47,7 +47,7 @@ def get_survey_results(str):
4747
return None
4848

4949
# Function to extract either ratings or comments from STACKrate evaluation dict
50-
def extract_info(dictionary, key, attr=""):
50+
def extract_survey_info(dictionary, key, attr=""):
5151
if dictionary and key in dictionary:
5252
if attr=="":
5353
return dictionary[key]
@@ -94,6 +94,21 @@ def get_input_value(response_str, input_name, mode=""):
9494
else:
9595
return None
9696

97+
# Returns the random seed in a given attempt
98+
def get_random_seed(response_str):
99+
pattern = r"Seed:\s*(\d+)"
100+
match = re.search(pattern, response_str)
101+
if match:
102+
input_value = match.group(1)
103+
return input_value
104+
else:
105+
return None
106+
107+
# Returns True when `search_item` is included in `response_str` string
108+
def is_present(response_str, search_item):
109+
result = search_item in response_str
110+
return result
111+
97112
# Makes the radio buttons for the language selection visible in the GUI
98113
def show_language_radiobuttons():
99114
checkbox_seconds_row = checkbox_seconds.grid_info()["row"]
@@ -205,14 +220,17 @@ def submit_columns():
205220
input_names = set()
206221
global prts
207222
prts = set()
223+
# Add temporary column with identified prt and input names as entries:
208224
df[f"{selected_col.get()}: identified_placeholders"] = df[selected_col.get()].apply(identify_prts_inputs)
225+
# Create lists with input and prt names:
209226
for result in df[f"{selected_col.get()}: identified_placeholders"]:
210227
if result:
211228
input_names.update(result["inputs"])
212229
prts.update(result["prts"])
213230
i=1
214231
input_checkboxes_label.grid(row=i)
215232
i+=1
233+
# Create checkbox for each input name:
216234
global input_checkboxes
217235
input_checkboxes = []
218236
for input in input_names:
@@ -232,6 +250,7 @@ def submit_columns():
232250
i+=1
233251
prt_checkboxes_label.grid(row=i)
234252
i+=1
253+
# Create checkbox for each prt name:
235254
global prt_checkboxes
236255
prt_checkboxes = []
237256
for prt in prts:
@@ -251,11 +270,18 @@ def submit_columns():
251270
i+=1
252271
additional_options_label.grid(row=i)
253272
i+=1
273+
textarea_label.grid(row=i)
274+
i+=1
275+
textarea.grid(row=i)
276+
i+=1
254277
checkbox_seconds.grid(row=i)
255278
i=i+2+len(time_languages)
256279
checkbox_stackrate.grid(row=i)
257280
i+=1
281+
checkbox_randseed.grid(row=i)
282+
i+=1
258283
process_button.grid(row=i)
284+
# Delete temporary column:
259285
del df[f"{selected_col.get()}: identified_placeholders"]
260286

261287
# Function which is called when the clicks the "Save CSV file" button in GUI.
@@ -273,19 +299,29 @@ def export_csv_file():
273299
# Function where most of the "magic" happens. Called when clicking on "Submit".
274300
# Depending on user's wishes, all columns are created
275301
def process_input_strings():
302+
# Create "Seconds taken" column depending on user's choices:
276303
if var_checkbox_seconds.get():
277304
time_values = time_languages[selected_lang.get()][0]
278305
df_key = time_languages[selected_lang.get()][1]
279306
try:
280307
df["Seconds taken"] = df[df_key].apply(lambda x: string_time_to_sec(x,time_values))
281308
except:
282309
print("There is an error finding the column that contains the time information. Please make sure you selected the correct language.")
310+
# Create columns for selected input fields:
283311
for input_field in selected_input_checkboxes:
284312
df[f"{selected_col.get()}: {input_field} value"] = df[selected_col.get()].apply(lambda x: get_input_value(x, input_field, mode="value"))
285313
df[f"{selected_col.get()}: {input_field} state"] = df[selected_col.get()].apply(lambda x: get_input_value(x, input_field, mode="state"))
314+
# Create columns for selected prts:
286315
for prt in selected_prt_checkboxes:
287316
df[f"{selected_col.get()}: {prt} active"] = df[selected_col.get()].apply(lambda x: is_prt_active(x, prt))
288317
df[f"{selected_col.get()}: {prt} score"] = df[selected_col.get()].apply(lambda x: get_prt_score(x, prt))
318+
# Create columns for strings specified in text area
319+
textarea_content = textarea.get('1.0','end').strip()
320+
if textarea_content!="":
321+
for string in textarea_content.split(","):
322+
item = string.strip()
323+
df[f"{item} present"] = df[selected_col.get()].apply(lambda x: is_present(x, item))
324+
# Create STACKrate survey columns depending on user's choices:
289325
if var_checkbox_stackrate.get():
290326
df[f"{selected_col.get()}: Survey_Results"] = df[selected_col.get()].apply(get_survey_results)
291327
# Extract all STACKrate IDs:
@@ -295,9 +331,12 @@ def process_input_strings():
295331
all_stackrate_keys.update(survey_result.keys())
296332
# Create columns for all IDs:
297333
for key in all_stackrate_keys:
298-
df[f"{selected_col.get()}: STACKrate ratings to ID {key}"] = df[f"{selected_col.get()}: Survey_Results"].apply(lambda x: extract_info(x, key, attr="ratings"))
299-
df[f"{selected_col.get()}: STACKrate comments to ID {key}"] = df[f"{selected_col.get()}: Survey_Results"].apply(lambda x: extract_info(x, key, attr="comment"))
334+
df[f"{selected_col.get()}: STACKrate ratings to ID {key}"] = df[f"{selected_col.get()}: Survey_Results"].apply(lambda x: extract_survey_info(x, key, attr="ratings"))
335+
df[f"{selected_col.get()}: STACKrate comments to ID {key}"] = df[f"{selected_col.get()}: Survey_Results"].apply(lambda x: extract_survey_info(x, key, attr="comment"))
300336
df.drop(f"{selected_col.get()}: Survey_Results", axis=1, inplace=True)
337+
# Create colum with random seeds depending on user's choices:
338+
if var_checkbox_randseed.get():
339+
df["Random seed"] = df[selected_col.get()].apply(get_random_seed)
301340
input_checkboxes_label.grid_forget()
302341
for box in input_checkboxes:
303342
box.grid_forget()
@@ -307,32 +346,35 @@ def process_input_strings():
307346
box.grid_forget()
308347
sep2.grid_forget()
309348
additional_options_label.grid_forget()
349+
textarea_label.grid_forget()
350+
textarea.grid_forget()
310351
checkbox_seconds.grid_forget()
311352
hide_language_radiobuttons()
312353
checkbox_stackrate.grid_forget()
354+
checkbox_randseed.grid_forget()
313355
process_button.grid_forget()
314356
export_button_info.grid(row=1)
315357
export_button.grid(row=2)
316358

317-
# Closes GUI window
359+
# Closes GUI window:
318360
def close_window():
319361
root.destroy()
320362

321-
# GUI setup
363+
# GUI setup:
322364
root = tk.Tk()
323365
root.title("STACK Response File Processor")
324366

325-
# GUI elements for opening the input csv file
367+
# GUI elements for opening the input csv file:
326368
open_button_info = tk.Label(root, text="Click on the following button to select a CSV file:")
327369
open_button_info.grid(row=1)
328370
open_button = tk.Button(root, text="Open CSV file", command=open_csv_file)
329371
open_button.grid(row=2)
330372

331-
# GUI elements for selecting the columns
373+
# GUI elements for selecting the columns:
332374
spalten_label = tk.Label(root, text="Your CSV file contains the following columns. \nPlease select the one that relate to a STACK task and from which you wish to extract information about student responses.")
333375
submit_button_cols = tk.Button(root, text="Submit", command=submit_columns)
334376

335-
# GUI elements for selecting the desired input and prt names
377+
# GUI elements for selecting the desired input and prt names:
336378
input_checkboxes_label = tk.Label(root,
337379
text="The following input names have been found in your column. Please select those that you want to get a report for.")
338380
sep1 = ttk.Separator(root,orient='horizontal')
@@ -342,15 +384,19 @@ def close_window():
342384
text="In addition, you can choose the following options (optional):")
343385
sep2 = ttk.Separator(root,orient='horizontal')
344386

387+
# GUI elements for text area:
388+
textarea_label = tk.Label(root, text="Comma-separated list of strings for which the quiz data will be searched (e.g. PRT answer notes such as prt1-1-F).\nFor each string specified, a column will be created that contains True or False, depending on whether it is present in the respective row.")
389+
textarea = tk.Text(root, height=3)
390+
345391
# GUI elements for "Time to seconds" feature:
346392
var_checkbox_seconds = tk.BooleanVar()
347393
checkbox_seconds = tk.Checkbutton(root, text="Insert column for time spent in seconds (only English and German response files)",
348394
variable=var_checkbox_seconds, command=toggle_seconds_options)
349395
radio_language_label = tk.Label(root, text="Please select the language of your input file:")
350396
lang_radiobuttons = []
351397
selected_lang = tk.StringVar()
398+
# Create radiobutton for each language:
352399
for language in list(time_languages.keys()):
353-
# Create radiobutton for each langaue:
354400
radiobutton = tk.Radiobutton(
355401
root,
356402
text=language,
@@ -364,13 +410,18 @@ def close_window():
364410
var_checkbox_stackrate = tk.BooleanVar()
365411
checkbox_stackrate = tk.Checkbutton(root, text="Insert columns for STACKrate evaluation results",
366412
variable=var_checkbox_stackrate)
413+
414+
# GUI elements for random seed feature:
415+
var_checkbox_randseed = tk.BooleanVar()
416+
checkbox_randseed = tk.Checkbutton(root, text="Insert column for random seeds",
417+
variable=var_checkbox_randseed)
367418
process_button = tk.Button(root, text="Submit", command=process_input_strings)
368419

369-
# GUI elements for export
420+
# GUI elements for export:
370421
export_button_info = tk.Label(root, text="Click on the following button to select a storage location for the output file and start the export:")
371422
export_button = tk.Button(root, text="Save CSV file", command=export_csv_file)
372423

373-
# GUI elements for the close page
424+
# GUI elements for the close page:
374425
success_text = tk.Label(root, text="The file has been saved successfully!")
375426
close_button = tk.Button(root, text="Close", command=close_window)
376427

0 commit comments

Comments
 (0)