Zheng Qu
commited on
Commit
•
11c72a0
1
Parent(s):
12fd00c
working for MIDTERM2a and b
Browse files- app.py +23 -6
- app_utils.py +111 -62
- df2sqlite.py +61 -0
- dump_all.py +78 -45
- grading.py +21 -16
- grading_utils.py +3 -2
- student_tab.py +3 -1
- utils.py +28 -0
app.py
CHANGED
@@ -2,6 +2,7 @@ from app_utils import (load_problems, export_submissions,
|
|
2 |
get_all_students, get_student_submissions, check_password,
|
3 |
refresh_submissions)
|
4 |
from student_tab import create_student_tab
|
|
|
5 |
import gradio as gr
|
6 |
|
7 |
|
@@ -25,9 +26,15 @@ def create_app():
|
|
25 |
refresh_button = gr.Button("🔄 Refresh")
|
26 |
|
27 |
submissions = gr.JSON(label="Submissions")
|
28 |
-
download_btn = gr.Button("Download All Submissions")
|
29 |
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
def refresh_data():
|
32 |
return {
|
33 |
students: gr.update(choices=get_all_students()),
|
@@ -38,8 +45,6 @@ def create_app():
|
|
38 |
fn=refresh_data,
|
39 |
outputs=[students, submissions]
|
40 |
)
|
41 |
-
|
42 |
-
download_output = gr.File(label="Download")
|
43 |
|
44 |
def login(pwd):
|
45 |
if check_password(pwd):
|
@@ -48,7 +53,7 @@ def create_app():
|
|
48 |
admin_panel: gr.update(visible=True),
|
49 |
students: gr.update(choices=student_list),
|
50 |
login_status: "✅ Logged in successfully!",
|
51 |
-
submissions: refresh_submissions()
|
52 |
}
|
53 |
return {
|
54 |
admin_panel: gr.update(visible=False),
|
@@ -68,6 +73,12 @@ def create_app():
|
|
68 |
return file_path
|
69 |
return None
|
70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
# Update event handlers
|
72 |
login_button.click(
|
73 |
fn=login,
|
@@ -84,7 +95,13 @@ def create_app():
|
|
84 |
download_btn.click(
|
85 |
fn=download_all,
|
86 |
outputs=[download_output]
|
87 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
return app
|
89 |
|
90 |
if __name__ == "__main__":
|
|
|
2 |
get_all_students, get_student_submissions, check_password,
|
3 |
refresh_submissions)
|
4 |
from student_tab import create_student_tab
|
5 |
+
from dump_all import create_markdown_zip
|
6 |
import gradio as gr
|
7 |
|
8 |
|
|
|
26 |
refresh_button = gr.Button("🔄 Refresh")
|
27 |
|
28 |
submissions = gr.JSON(label="Submissions")
|
|
|
29 |
|
30 |
+
with gr.Row():
|
31 |
+
download_btn = gr.Button("Download All Submissions (JSON)")
|
32 |
+
download_markdown_btn = gr.Button("Download All Submissions (Markdown)")
|
33 |
+
|
34 |
+
with gr.Row():
|
35 |
+
download_output = gr.File(label="Download JSON")
|
36 |
+
download_markdown_output = gr.File(label="Download Markdown")
|
37 |
+
|
38 |
def refresh_data():
|
39 |
return {
|
40 |
students: gr.update(choices=get_all_students()),
|
|
|
45 |
fn=refresh_data,
|
46 |
outputs=[students, submissions]
|
47 |
)
|
|
|
|
|
48 |
|
49 |
def login(pwd):
|
50 |
if check_password(pwd):
|
|
|
53 |
admin_panel: gr.update(visible=True),
|
54 |
students: gr.update(choices=student_list),
|
55 |
login_status: "✅ Logged in successfully!",
|
56 |
+
submissions: refresh_submissions()
|
57 |
}
|
58 |
return {
|
59 |
admin_panel: gr.update(visible=False),
|
|
|
73 |
return file_path
|
74 |
return None
|
75 |
|
76 |
+
def download_markdown():
|
77 |
+
file_path = create_markdown_zip()
|
78 |
+
if file_path:
|
79 |
+
return file_path
|
80 |
+
return None
|
81 |
+
|
82 |
# Update event handlers
|
83 |
login_button.click(
|
84 |
fn=login,
|
|
|
95 |
download_btn.click(
|
96 |
fn=download_all,
|
97 |
outputs=[download_output]
|
98 |
+
)
|
99 |
+
|
100 |
+
download_markdown_btn.click(
|
101 |
+
fn=download_markdown,
|
102 |
+
outputs=[download_markdown_output]
|
103 |
+
)
|
104 |
+
|
105 |
return app
|
106 |
|
107 |
if __name__ == "__main__":
|
app_utils.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1 |
import json
|
2 |
import os
|
3 |
import datetime
|
|
|
|
|
|
|
4 |
from pathlib import Path
|
5 |
from dotenv import load_dotenv
|
6 |
from tinydb import TinyDB, Query
|
@@ -8,108 +11,154 @@ from tinydb_serialization import SerializationMiddleware
|
|
8 |
from tinydb_sqlite import SQLiteStorage
|
9 |
import tempfile
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
load_dotenv()
|
12 |
SESSION_ID = os.getenv('SESSION_ID', 'default_session')
|
13 |
-
|
|
|
14 |
ADMIN_PASSWD = os.getenv('ADMIN_PASSWD', 'default_password')
|
15 |
SUBMISSIONS_DIR = 'submissions'
|
16 |
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
-
|
|
|
|
|
33 |
|
34 |
def load_problems():
|
35 |
problems = []
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
return problems
|
44 |
|
45 |
-
|
46 |
def save_submission(session, name, email, problem_id, code, hint_requested):
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
def save_all_submissions(name, email, codes_dict, hints_dict):
|
64 |
-
db, submissions_table = get_db()
|
65 |
try:
|
66 |
for problem_id, code in codes_dict.items():
|
67 |
hint_requested = hints_dict.get(problem_id, False)
|
68 |
save_submission(SESSION_ID, name, email, problem_id, code, hint_requested)
|
69 |
-
|
70 |
return "✅ All submissions saved successfully!"
|
71 |
except Exception as e:
|
72 |
-
|
73 |
-
|
|
|
74 |
|
75 |
def check_password(password):
|
76 |
return password == ADMIN_PASSWD
|
77 |
|
78 |
def get_all_students():
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
|
|
|
|
|
|
|
|
83 |
|
84 |
def get_student_submissions(name):
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
|
|
|
|
|
|
|
|
91 |
|
92 |
def export_submissions():
|
93 |
-
db, submissions_table = get_db()
|
94 |
try:
|
|
|
95 |
submissions = submissions_table.all()
|
96 |
submissions.sort(key=lambda x: datetime.datetime.fromisoformat(x['timestamp']), reverse=True)
|
97 |
data = json.dumps(submissions, indent=2)
|
98 |
|
99 |
-
# Create a temporary file and write the data to it
|
100 |
temp_file = tempfile.NamedTemporaryFile(delete=False, mode='w', suffix='.json')
|
101 |
temp_file.write(data)
|
102 |
temp_file.close()
|
103 |
-
|
104 |
return temp_file.name
|
105 |
except Exception as e:
|
106 |
-
|
107 |
-
db.close()
|
108 |
return None
|
109 |
|
110 |
def refresh_submissions():
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
|
|
|
|
|
|
|
|
|
1 |
import json
|
2 |
import os
|
3 |
import datetime
|
4 |
+
import logging
|
5 |
+
import traceback
|
6 |
+
import sqlite3
|
7 |
from pathlib import Path
|
8 |
from dotenv import load_dotenv
|
9 |
from tinydb import TinyDB, Query
|
|
|
11 |
from tinydb_sqlite import SQLiteStorage
|
12 |
import tempfile
|
13 |
|
14 |
+
# Configure logging
|
15 |
+
logging.basicConfig(
|
16 |
+
level=logging.INFO,
|
17 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
18 |
+
handlers=[
|
19 |
+
logging.FileHandler('app.log'),
|
20 |
+
logging.StreamHandler()
|
21 |
+
]
|
22 |
+
)
|
23 |
+
logger = logging.getLogger(__name__)
|
24 |
+
|
25 |
load_dotenv()
|
26 |
SESSION_ID = os.getenv('SESSION_ID', 'default_session')
|
27 |
+
SESSION_TITLE = os.getenv('SESSION_TITLE', 'Default Session')
|
28 |
+
logger.info(f"Session ID: {SESSION_ID}")
|
29 |
ADMIN_PASSWD = os.getenv('ADMIN_PASSWD', 'default_password')
|
30 |
SUBMISSIONS_DIR = 'submissions'
|
31 |
|
32 |
+
def get_storage():
|
33 |
+
"""Create and return SQLite storage instance."""
|
34 |
+
try:
|
35 |
+
db_path = 'submissions.db'
|
36 |
+
serialization = SerializationMiddleware()
|
37 |
+
connection = sqlite3.connect(db_path, check_same_thread=False)
|
38 |
+
return SQLiteStorage(
|
39 |
+
connection=connection,
|
40 |
+
serializer=serialization
|
41 |
+
)
|
42 |
+
except Exception as e:
|
43 |
+
logger.error(f"Error creating storage: {str(e)}\n{traceback.format_exc()}")
|
44 |
+
raise
|
45 |
|
46 |
+
# Initialize TinyDB with the storage factory
|
47 |
+
try:
|
48 |
+
_db = TinyDB(storage=get_storage)
|
49 |
+
_submissions_table = _db.table('submissions')
|
50 |
+
logger.info("Database initialized successfully")
|
51 |
+
except Exception as e:
|
52 |
+
logger.error(f"Database initialization failed: {str(e)}\n{traceback.format_exc()}")
|
53 |
+
raise
|
54 |
|
55 |
+
def get_db():
|
56 |
+
"""Get the database and submissions table instances."""
|
57 |
+
return _db, _submissions_table
|
58 |
|
59 |
def load_problems():
|
60 |
problems = []
|
61 |
+
try:
|
62 |
+
db_path = Path(SESSION_ID)
|
63 |
+
for json_file in db_path.glob('*.json'):
|
64 |
+
try:
|
65 |
+
with open(json_file) as f:
|
66 |
+
problem = json.load(f)
|
67 |
+
problem['id'] = json_file.stem
|
68 |
+
problems.append(problem)
|
69 |
+
logger.info(f"Loaded problem: {json_file.stem}")
|
70 |
+
except Exception as e:
|
71 |
+
logger.error(f"Error loading problem {json_file}: {str(e)}\n{traceback.format_exc()}")
|
72 |
+
except Exception as e:
|
73 |
+
logger.error(f"Error in load_problems: {str(e)}\n{traceback.format_exc()}")
|
74 |
return problems
|
75 |
|
|
|
76 |
def save_submission(session, name, email, problem_id, code, hint_requested):
|
77 |
+
try:
|
78 |
+
_, submissions_table = get_db()
|
79 |
+
timestamp = datetime.datetime.now().isoformat()
|
80 |
+
submission = {
|
81 |
+
'session': session,
|
82 |
+
'name': name,
|
83 |
+
'email': email,
|
84 |
+
'problem_id': problem_id,
|
85 |
+
'student_code': code,
|
86 |
+
'hint_requested': hint_requested,
|
87 |
+
'timestamp': timestamp
|
88 |
+
}
|
89 |
+
query = Query()
|
90 |
+
submissions_table.upsert(
|
91 |
+
submission,
|
92 |
+
(query.session == session) &
|
93 |
+
(query.name == name) &
|
94 |
+
(query.email == email) &
|
95 |
+
(query.problem_id == problem_id)
|
96 |
+
)
|
97 |
+
logger.info(f"Saved submission for {name} - Problem: {problem_id}")
|
98 |
+
except Exception as e:
|
99 |
+
logger.error(f"Error in save_submission: {str(e)}\n{traceback.format_exc()}")
|
100 |
+
raise
|
101 |
|
102 |
def save_all_submissions(name, email, codes_dict, hints_dict):
|
|
|
103 |
try:
|
104 |
for problem_id, code in codes_dict.items():
|
105 |
hint_requested = hints_dict.get(problem_id, False)
|
106 |
save_submission(SESSION_ID, name, email, problem_id, code, hint_requested)
|
107 |
+
logger.info(f"Successfully saved all submissions for {name}")
|
108 |
return "✅ All submissions saved successfully!"
|
109 |
except Exception as e:
|
110 |
+
error_msg = f"Error saving submissions: {str(e)}"
|
111 |
+
logger.error(f"{error_msg}\n{traceback.format_exc()}")
|
112 |
+
return f"❌ {error_msg}"
|
113 |
|
114 |
def check_password(password):
|
115 |
return password == ADMIN_PASSWD
|
116 |
|
117 |
def get_all_students():
|
118 |
+
try:
|
119 |
+
_, submissions_table = get_db()
|
120 |
+
result = list(set([submission['name'] for submission in submissions_table.all()]))
|
121 |
+
logger.info(f"Retrieved {len(result)} students")
|
122 |
+
return result
|
123 |
+
except Exception as e:
|
124 |
+
logger.error(f"Error in get_all_students: {str(e)}\n{traceback.format_exc()}")
|
125 |
+
return []
|
126 |
|
127 |
def get_student_submissions(name):
|
128 |
+
try:
|
129 |
+
_, submissions_table = get_db()
|
130 |
+
query = Query()
|
131 |
+
submissions = submissions_table.search(query.name == name)
|
132 |
+
submissions.sort(key=lambda x: datetime.datetime.fromisoformat(x['timestamp']), reverse=True)
|
133 |
+
logger.info(f"Retrieved {len(submissions)} submissions for student: {name}")
|
134 |
+
return submissions
|
135 |
+
except Exception as e:
|
136 |
+
logger.error(f"Error in get_student_submissions: {str(e)}\n{traceback.format_exc()}")
|
137 |
+
return []
|
138 |
|
139 |
def export_submissions():
|
|
|
140 |
try:
|
141 |
+
_, submissions_table = get_db()
|
142 |
submissions = submissions_table.all()
|
143 |
submissions.sort(key=lambda x: datetime.datetime.fromisoformat(x['timestamp']), reverse=True)
|
144 |
data = json.dumps(submissions, indent=2)
|
145 |
|
|
|
146 |
temp_file = tempfile.NamedTemporaryFile(delete=False, mode='w', suffix='.json')
|
147 |
temp_file.write(data)
|
148 |
temp_file.close()
|
149 |
+
logger.info(f"Exported {len(submissions)} submissions to {temp_file.name}")
|
150 |
return temp_file.name
|
151 |
except Exception as e:
|
152 |
+
logger.error(f"Error in export_submissions: {str(e)}\n{traceback.format_exc()}")
|
|
|
153 |
return None
|
154 |
|
155 |
def refresh_submissions():
|
156 |
+
try:
|
157 |
+
_, submissions_table = get_db()
|
158 |
+
all_submissions = submissions_table.all()
|
159 |
+
all_submissions.sort(key=lambda x: datetime.datetime.fromisoformat(x['timestamp']), reverse=True)
|
160 |
+
logger.info(f"Refreshed {len(all_submissions)} submissions")
|
161 |
+
return all_submissions
|
162 |
+
except Exception as e:
|
163 |
+
logger.error(f"Error in refresh_submissions: {str(e)}\n{traceback.format_exc()}")
|
164 |
+
return []
|
df2sqlite.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# df2sqlite.py
|
2 |
+
|
3 |
+
import sqlite3
|
4 |
+
import pandas as pd
|
5 |
+
import logging
|
6 |
+
from create_course_dataframe import create_course_dataframe, process_xlsx
|
7 |
+
|
8 |
+
logging.basicConfig(
|
9 |
+
level=logging.INFO,
|
10 |
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
11 |
+
handlers=[logging.StreamHandler()]
|
12 |
+
)
|
13 |
+
|
14 |
+
def save_to_sqlite(df, database_name='courses.db', table_name='courses'):
|
15 |
+
"""
|
16 |
+
Save DataFrame to SQLite Database
|
17 |
+
"""
|
18 |
+
try:
|
19 |
+
logging.info(f"Connecting to database: {database_name}")
|
20 |
+
conn = sqlite3.connect(database_name)
|
21 |
+
|
22 |
+
logging.info(f"Saving DataFrame to table: {table_name}")
|
23 |
+
df.to_sql(table_name, conn, if_exists='replace', index=False)
|
24 |
+
|
25 |
+
logging.info("Data successfully saved to SQLite database")
|
26 |
+
|
27 |
+
# Verify the data
|
28 |
+
row_count = pd.read_sql(f"SELECT COUNT(*) FROM {table_name}", conn).iloc[0,0]
|
29 |
+
logging.info(f"Verified {row_count} rows in table {table_name}")
|
30 |
+
|
31 |
+
conn.close()
|
32 |
+
return True
|
33 |
+
|
34 |
+
except Exception as e:
|
35 |
+
logging.error(f"Error saving to database: {str(e)}")
|
36 |
+
return False
|
37 |
+
|
38 |
+
if __name__ == "__main__":
|
39 |
+
# Load and process XLSX file
|
40 |
+
file_path = "FTCM_Course List_Spring2025.xlsx"
|
41 |
+
result = process_xlsx(file_path)
|
42 |
+
|
43 |
+
if result:
|
44 |
+
column_names, department_program_courses = result
|
45 |
+
|
46 |
+
cleaned_column_names = ['Course Code', 'Course Title', 'Cr', 'Prereq(s)',
|
47 |
+
'Instructor', 'Major/ GE/ \nElective', 'Format',
|
48 |
+
'Mon', 'MonTo', 'Tue', 'TueTo', 'Wed', 'WedTo',
|
49 |
+
'Thu', 'ThuTo', 'Fri', 'FriTo', 'Sat', 'SatTo',
|
50 |
+
'Platform', 'New/ Repeat', 'Room']
|
51 |
+
|
52 |
+
# Create DataFrame
|
53 |
+
df = create_course_dataframe(cleaned_column_names, column_names, department_program_courses)
|
54 |
+
|
55 |
+
# Save to SQLite
|
56 |
+
if save_to_sqlite(df):
|
57 |
+
logging.info("Process completed successfully")
|
58 |
+
else:
|
59 |
+
logging.error("Failed to save data to SQLite")
|
60 |
+
else:
|
61 |
+
logging.error("Failed to process XLSX file")
|
dump_all.py
CHANGED
@@ -1,9 +1,12 @@
|
|
1 |
import os
|
|
|
|
|
|
|
2 |
from datetime import datetime
|
3 |
from pathlib import Path
|
4 |
-
from
|
5 |
-
|
6 |
-
|
7 |
|
8 |
def create_markdown_content(submissions):
|
9 |
"""Generate markdown content from a list of submissions"""
|
@@ -41,61 +44,91 @@ def create_markdown_content(submissions):
|
|
41 |
|
42 |
return content
|
43 |
|
44 |
-
def
|
45 |
-
|
46 |
-
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
|
|
|
|
|
|
|
|
|
52 |
try:
|
53 |
-
#
|
54 |
-
|
55 |
-
submissions_table = db.table('submissions')
|
56 |
-
print(f"Connected to TinyDB at {DB_PATH}")
|
57 |
-
|
58 |
-
# Get all submissions
|
59 |
all_submissions = submissions_table.all()
|
60 |
|
61 |
if not all_submissions:
|
62 |
-
|
63 |
-
return
|
64 |
-
|
65 |
-
# Group submissions by student name
|
66 |
-
student_submissions = {}
|
67 |
-
for submission in all_submissions:
|
68 |
-
name = submission['name']
|
69 |
-
if name not in student_submissions:
|
70 |
-
student_submissions[name] = []
|
71 |
-
student_submissions[name].append(submission)
|
72 |
|
73 |
-
# Create
|
74 |
-
|
75 |
-
#
|
76 |
-
|
77 |
-
|
78 |
|
79 |
-
#
|
80 |
-
|
|
|
|
|
|
|
81 |
|
82 |
-
|
83 |
-
|
84 |
-
with open(file_path, 'w', encoding='utf-8') as f:
|
85 |
-
f.write(content)
|
86 |
|
87 |
-
|
88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
except Exception as e:
|
94 |
-
|
95 |
-
|
96 |
-
finally:
|
97 |
-
# Close database connection
|
98 |
-
db.close()
|
99 |
|
100 |
if __name__ == "__main__":
|
101 |
main()
|
|
|
1 |
import os
|
2 |
+
import logging
|
3 |
+
import zipfile
|
4 |
+
import tempfile
|
5 |
from datetime import datetime
|
6 |
from pathlib import Path
|
7 |
+
from app_utils import get_db
|
8 |
+
|
9 |
+
logger = logging.getLogger(__name__)
|
10 |
|
11 |
def create_markdown_content(submissions):
|
12 |
"""Generate markdown content from a list of submissions"""
|
|
|
44 |
|
45 |
return content
|
46 |
|
47 |
+
def group_submissions_by_student(submissions):
|
48 |
+
"""Group submissions by student name"""
|
49 |
+
student_submissions = {}
|
50 |
+
for submission in submissions:
|
51 |
+
name = submission['name']
|
52 |
+
if name not in student_submissions:
|
53 |
+
student_submissions[name] = []
|
54 |
+
student_submissions[name].append(submission)
|
55 |
+
return student_submissions
|
56 |
+
|
57 |
+
def create_markdown_files(student_submissions, output_dir):
|
58 |
+
"""Create markdown files for each student in the specified directory"""
|
59 |
+
output_dir = Path(output_dir)
|
60 |
+
output_dir.mkdir(exist_ok=True)
|
61 |
+
created_files = []
|
62 |
|
63 |
+
for name, submissions in student_submissions.items():
|
64 |
+
# Create safe filename from student name
|
65 |
+
safe_filename = "".join(x for x in name if x.isalnum() or x in "- ").strip()
|
66 |
+
safe_filename = safe_filename.replace(" ", "_") + ".md"
|
67 |
+
|
68 |
+
# Generate markdown content
|
69 |
+
content = create_markdown_content(submissions)
|
70 |
+
|
71 |
+
# Write to file
|
72 |
+
file_path = output_dir / safe_filename
|
73 |
+
with open(file_path, 'w', encoding='utf-8') as f:
|
74 |
+
f.write(content)
|
75 |
+
|
76 |
+
created_files.append(file_path)
|
77 |
+
logger.info(f"Created markdown file for {name}: {file_path}")
|
78 |
|
79 |
+
return created_files
|
80 |
+
|
81 |
+
def create_markdown_zip():
|
82 |
+
"""Create a zip file containing markdown files for all submissions"""
|
83 |
try:
|
84 |
+
# Get submissions from database
|
85 |
+
_, submissions_table = get_db()
|
|
|
|
|
|
|
|
|
86 |
all_submissions = submissions_table.all()
|
87 |
|
88 |
if not all_submissions:
|
89 |
+
logger.warning("No submissions found in database")
|
90 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
|
92 |
+
# Create temporary directory for markdown files
|
93 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
94 |
+
# Group submissions and create markdown files
|
95 |
+
student_submissions = group_submissions_by_student(all_submissions)
|
96 |
+
markdown_files = create_markdown_files(student_submissions, temp_dir)
|
97 |
|
98 |
+
# Create zip file
|
99 |
+
zip_path = tempfile.mktemp(suffix='.zip')
|
100 |
+
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
101 |
+
for file_path in markdown_files:
|
102 |
+
zipf.write(file_path, file_path.name)
|
103 |
|
104 |
+
logger.info(f"Created zip file with {len(markdown_files)} markdown files")
|
105 |
+
return zip_path
|
|
|
|
|
106 |
|
107 |
+
except Exception as e:
|
108 |
+
logger.error(f"Error creating markdown zip: {str(e)}")
|
109 |
+
return None
|
110 |
+
|
111 |
+
def main():
|
112 |
+
"""Main function for command-line usage"""
|
113 |
+
output_dir = Path("db_dump")
|
114 |
+
try:
|
115 |
+
# Get submissions from database
|
116 |
+
_, submissions_table = get_db()
|
117 |
+
all_submissions = submissions_table.all()
|
118 |
|
119 |
+
if not all_submissions:
|
120 |
+
logger.warning("No submissions found in database")
|
121 |
+
return
|
122 |
+
|
123 |
+
# Group submissions and create markdown files
|
124 |
+
student_submissions = group_submissions_by_student(all_submissions)
|
125 |
+
create_markdown_files(student_submissions, output_dir)
|
126 |
+
|
127 |
+
logger.info(f"Successfully created markdown files in {output_dir}")
|
128 |
+
logger.info(f"Total number of students: {len(student_submissions)}")
|
129 |
|
130 |
except Exception as e:
|
131 |
+
logger.error(f"Error occurred: {str(e)}")
|
|
|
|
|
|
|
|
|
132 |
|
133 |
if __name__ == "__main__":
|
134 |
main()
|
grading.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
#!/usr/bin/env python
|
2 |
"""
|
3 |
-
Create a grading script based on midterm2a-submissions.json which will be filename on the command line. The script should:
|
4 |
1. input the json file for student submissions
|
5 |
2. get student student_code and problem_id, if there are multiple entry for the same student_code and problem_id, only the last entry will be graded
|
6 |
3. for each student_code , problem_id tuple, get the correct answer and problem_description for the problem_id with function get_correct_answer(problem_id)
|
@@ -34,18 +34,20 @@ from collections import defaultdict
|
|
34 |
from datetime import datetime
|
35 |
import sys, os
|
36 |
from grading_utils import grade_submission, Grading_rubric
|
37 |
-
from app_utils import load_problems
|
|
|
38 |
|
39 |
problemsDB = {problem['id']: problem for problem in load_problems()}
|
40 |
-
Grading_rubric_path = "grading_rubric.json"
|
|
|
41 |
if os.path.exists(Grading_rubric_path):
|
42 |
with open(Grading_rubric_path, 'r') as f:
|
43 |
Grading_rubric.update(json.load(f))
|
44 |
print("Grading rubric loaded successfully.")
|
45 |
|
46 |
def get_correct_answer(problem_id: str) -> Tuple[str, str]:
|
47 |
-
"""
|
48 |
-
return problemsDB[problem_id]['Solution_Code'], problemsDB[problem_id]['Problem_markdown']
|
49 |
|
50 |
def process_submissions(submissions: List[dict]) -> Dict[str, Dict[str, dict]]:
|
51 |
"""Process submissions and keep only the latest submission for each student-problem pair."""
|
@@ -53,13 +55,14 @@ def process_submissions(submissions: List[dict]) -> Dict[str, Dict[str, dict]]:
|
|
53 |
|
54 |
for submission in submissions:
|
55 |
name = submission['name'] # Using name as student identifier
|
|
|
56 |
problem_id = submission['problem_id']
|
57 |
-
|
58 |
# Check if this is a newer submission
|
59 |
-
if (problem_id not in student_submissions[name] or
|
60 |
-
submission['timestamp']['$date'] >
|
|
|
61 |
student_submissions[name][problem_id] = submission
|
62 |
-
|
63 |
return student_submissions
|
64 |
|
65 |
def generate_markdown_report(student_submissions: Dict[str, Dict[str, dict]], output_dir: str = "grading_reports"):
|
@@ -74,10 +77,9 @@ def generate_markdown_report(student_submissions: Dict[str, Dict[str, dict]], ou
|
|
74 |
filepath = os.path.join(output_dir, filename)
|
75 |
|
76 |
header = f"""---
|
77 |
-
title: "
|
78 |
subtitle: {student_name}
|
79 |
-
|
80 |
-
pdf_document
|
81 |
---
|
82 |
"""
|
83 |
|
@@ -87,8 +89,11 @@ output:
|
|
87 |
problem_count = 0
|
88 |
f.write(f"{header}\n\n")
|
89 |
for problem_id, submission in problems.items():
|
|
|
|
|
|
|
|
|
90 |
problem_count += 1
|
91 |
-
correct_answer, problem_description = get_correct_answer(problem_id)
|
92 |
score, feedback, rubric = grade_submission(
|
93 |
student_name,
|
94 |
problem_id,
|
@@ -102,7 +107,7 @@ output:
|
|
102 |
|
103 |
f.write(f"## Problem: {problem_id}\n\n")
|
104 |
f.write(f"**Description:** {problem_description}\n\n")
|
105 |
-
f.write("**
|
106 |
f.write("**Correct Solution:**\n```python\n{}\n```\n\n".format(correct_answer))
|
107 |
f.write(f"**Rubric:**\n{Grading_rubric[problem_id]}\n\n")
|
108 |
f.write(f"**Score:** {score:.2f}\n\n")
|
@@ -121,7 +126,7 @@ output:
|
|
121 |
print(f"Reports generated successfully in directory: {output_dir}")
|
122 |
|
123 |
except Exception as e:
|
124 |
-
|
125 |
sys.exit(1)
|
126 |
|
127 |
def main():
|
@@ -138,7 +143,7 @@ def main():
|
|
138 |
|
139 |
# Process submissions and generate reports
|
140 |
student_submissions = process_submissions(submissions)
|
141 |
-
generate_markdown_report(student_submissions)
|
142 |
|
143 |
|
144 |
if __name__ == "__main__":
|
|
|
1 |
#!/usr/bin/env python
|
2 |
"""
|
3 |
+
Create a grading script based on midterm2a-submissions.json or from db which will be filename on the command line. The script should:
|
4 |
1. input the json file for student submissions
|
5 |
2. get student student_code and problem_id, if there are multiple entry for the same student_code and problem_id, only the last entry will be graded
|
6 |
3. for each student_code , problem_id tuple, get the correct answer and problem_description for the problem_id with function get_correct_answer(problem_id)
|
|
|
34 |
from datetime import datetime
|
35 |
import sys, os
|
36 |
from grading_utils import grade_submission, Grading_rubric
|
37 |
+
from app_utils import (load_problems, SESSION_ID, SESSION_TITLE, logger)
|
38 |
+
from utils import get_full_traceback
|
39 |
|
40 |
problemsDB = {problem['id']: problem for problem in load_problems()}
|
41 |
+
Grading_rubric_path = os.path.join(SESSION_ID, "grading_rubric.json")
|
42 |
+
output_dir = os.path.join("grading_reports",SESSION_ID)
|
43 |
if os.path.exists(Grading_rubric_path):
|
44 |
with open(Grading_rubric_path, 'r') as f:
|
45 |
Grading_rubric.update(json.load(f))
|
46 |
print("Grading rubric loaded successfully.")
|
47 |
|
48 |
def get_correct_answer(problem_id: str) -> Tuple[str, str]:
|
49 |
+
"""To get correct answer and problem description."""
|
50 |
+
return problemsDB[problem_id]['Solution_Code'], problemsDB[problem_id]['Problem_markdown']
|
51 |
|
52 |
def process_submissions(submissions: List[dict]) -> Dict[str, Dict[str, dict]]:
|
53 |
"""Process submissions and keep only the latest submission for each student-problem pair."""
|
|
|
55 |
|
56 |
for submission in submissions:
|
57 |
name = submission['name'] # Using name as student identifier
|
58 |
+
|
59 |
problem_id = submission['problem_id']
|
|
|
60 |
# Check if this is a newer submission
|
61 |
+
if ((problem_id not in student_submissions[name]) or
|
62 |
+
(submission['timestamp']['$date'] >
|
63 |
+
student_submissions[name][problem_id]['timestamp']['$date'])):
|
64 |
student_submissions[name][problem_id] = submission
|
65 |
+
|
66 |
return student_submissions
|
67 |
|
68 |
def generate_markdown_report(student_submissions: Dict[str, Dict[str, dict]], output_dir: str = "grading_reports"):
|
|
|
77 |
filepath = os.path.join(output_dir, filename)
|
78 |
|
79 |
header = f"""---
|
80 |
+
title: "{SESSION_TITLE}"
|
81 |
subtitle: {student_name}
|
82 |
+
format: pdf
|
|
|
83 |
---
|
84 |
"""
|
85 |
|
|
|
89 |
problem_count = 0
|
90 |
f.write(f"{header}\n\n")
|
91 |
for problem_id, submission in problems.items():
|
92 |
+
if submission['session'] != SESSION_ID:
|
93 |
+
continue
|
94 |
+
correct_answer, problem_description = get_correct_answer( problem_id)
|
95 |
+
logger.info(f"Grading {student_name} for problem: {problem_id}")
|
96 |
problem_count += 1
|
|
|
97 |
score, feedback, rubric = grade_submission(
|
98 |
student_name,
|
99 |
problem_id,
|
|
|
107 |
|
108 |
f.write(f"## Problem: {problem_id}\n\n")
|
109 |
f.write(f"**Description:** {problem_description}\n\n")
|
110 |
+
f.write("**Your Answer:**\n```python\n{}\n```\n\n".format(submission['student_code']))
|
111 |
f.write("**Correct Solution:**\n```python\n{}\n```\n\n".format(correct_answer))
|
112 |
f.write(f"**Rubric:**\n{Grading_rubric[problem_id]}\n\n")
|
113 |
f.write(f"**Score:** {score:.2f}\n\n")
|
|
|
126 |
print(f"Reports generated successfully in directory: {output_dir}")
|
127 |
|
128 |
except Exception as e:
|
129 |
+
logger.error(f"Error generating reports: {e}\n{get_full_traceback(e)}")
|
130 |
sys.exit(1)
|
131 |
|
132 |
def main():
|
|
|
143 |
|
144 |
# Process submissions and generate reports
|
145 |
student_submissions = process_submissions(submissions)
|
146 |
+
generate_markdown_report(student_submissions, output_dir=output_dir)
|
147 |
|
148 |
|
149 |
if __name__ == "__main__":
|
grading_utils.py
CHANGED
@@ -113,9 +113,9 @@ def grade_submission(student_code: str, problem_id: str,
|
|
113 |
|
114 |
prompt = f"""You are a programming assignment grader.
|
115 |
|
116 |
-
Evaluate the student's answer based on the Problem description, Grading rubric and correct answer and provide a score and brief feedback.
|
117 |
|
118 |
-
If Grading rubric is not provided, you should develop your own rubric based on the problem description and correct answer. In rubric do not include readability and structure. Please be tolerant for minor syntax errors (up to 1 point deduction for less than 2 minor syntax errors). Please focus on the logic of the code.
|
119 |
|
120 |
##Problem ID: {problem_id}
|
121 |
|
@@ -144,6 +144,7 @@ def grade_submission(student_code: str, problem_id: str,
|
|
144 |
</output>
|
145 |
"""
|
146 |
logger.info(f"Problem ID: {problem_id}")
|
|
|
147 |
if DEBUG:
|
148 |
logger.debug("Prompt sent to LLM:")
|
149 |
logger.debug(prompt)
|
|
|
113 |
|
114 |
prompt = f"""You are a programming assignment grader.
|
115 |
|
116 |
+
Evaluate the student's answer based on the Problem description, Grading rubric and correct answer and provide a score and brief feedback. Note if a problem asks an essay question or short answer question, student's answer may be in comments like "# answer" or in tripple quotes.
|
117 |
|
118 |
+
If Grading rubric is not provided, you should develop your own rubric based on the problem description and correct answer. In rubric do not include readability and structure criteria. Please be tolerant for minor syntax errors (up to 1 point deduction for less than 2 minor syntax errors). Please focus on the logic of the code.
|
119 |
|
120 |
##Problem ID: {problem_id}
|
121 |
|
|
|
144 |
</output>
|
145 |
"""
|
146 |
logger.info(f"Problem ID: {problem_id}")
|
147 |
+
|
148 |
if DEBUG:
|
149 |
logger.debug("Prompt sent to LLM:")
|
150 |
logger.debug(prompt)
|
student_tab.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
# tab1.py
|
2 |
import gradio as gr
|
3 |
-
from app_utils import (save_submission, save_all_submissions,
|
|
|
4 |
|
5 |
def create_student_tab(problems):
|
6 |
|
@@ -17,6 +18,7 @@ def create_student_tab(problems):
|
|
17 |
for problem in problems:
|
18 |
with gr.Group():
|
19 |
gr.Markdown("---")
|
|
|
20 |
gr.Markdown(problem["Problem_markdown"])
|
21 |
|
22 |
hint_state = gr.State(value=False)
|
|
|
1 |
# tab1.py
|
2 |
import gradio as gr
|
3 |
+
from app_utils import (save_submission, save_all_submissions,
|
4 |
+
logger, SESSION_ID)
|
5 |
|
6 |
def create_student_tab(problems):
|
7 |
|
|
|
18 |
for problem in problems:
|
19 |
with gr.Group():
|
20 |
gr.Markdown("---")
|
21 |
+
logger.info(f"Creating problem {problem['id']}")
|
22 |
gr.Markdown(problem["Problem_markdown"])
|
23 |
|
24 |
hint_state = gr.State(value=False)
|
utils.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import traceback
|
2 |
+
|
3 |
+
def get_full_traceback(e: Exception) -> str:
|
4 |
+
"""
|
5 |
+
Returns full formatted traceback information from an exception object.
|
6 |
+
|
7 |
+
Args:
|
8 |
+
e (Exception): The exception object
|
9 |
+
|
10 |
+
Returns:
|
11 |
+
str: Formatted traceback string
|
12 |
+
"""
|
13 |
+
# Get exception info as a list of strings
|
14 |
+
tb_lines = traceback.format_exception(type(e), e, e.__traceback__)
|
15 |
+
|
16 |
+
# Join lines into single string
|
17 |
+
full_traceback = ''.join(tb_lines)
|
18 |
+
|
19 |
+
return full_traceback
|
20 |
+
|
21 |
+
if __name__ == "__main__":
|
22 |
+
# Usage example:
|
23 |
+
try:
|
24 |
+
# Some code that may raise exception
|
25 |
+
raise ValueError("Example error")
|
26 |
+
except Exception as e:
|
27 |
+
error_trace = get_full_traceback(e)
|
28 |
+
print(error_trace)
|