Zheng Qu commited on
Commit
11c72a0
1 Parent(s): 12fd00c

working for MIDTERM2a and b

Browse files
Files changed (8) hide show
  1. app.py +23 -6
  2. app_utils.py +111 -62
  3. df2sqlite.py +61 -0
  4. dump_all.py +78 -45
  5. grading.py +21 -16
  6. grading_utils.py +3 -2
  7. student_tab.py +3 -1
  8. 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
- # Move this inside admin_panel to ensure it's only accessible after login
 
 
 
 
 
 
 
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() # Load initial 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
- print(f"Session ID: {SESSION_ID}")
 
14
  ADMIN_PASSWD = os.getenv('ADMIN_PASSWD', 'default_password')
15
  SUBMISSIONS_DIR = 'submissions'
16
 
17
- print("Using TinyDB with SQLiteStorage for data storage")
18
-
19
- def get_db():
20
- """Initialize and return a TinyDB instance with SQLiteStorage."""
21
- serialization = SerializationMiddleware()
22
- storage = SQLiteStorage(
23
- 'submissions.db',
24
- serialization=serialization,
25
- connection_kwargs={'check_same_thread': False}
26
- )
27
- db = TinyDB(storage=storage)
28
- submissions_table = db.table('submissions')
29
- return db, submissions_table
30
 
 
 
 
 
 
 
 
 
31
 
32
- print("Using TinyDB for data storage")
 
 
33
 
34
  def load_problems():
35
  problems = []
36
- db_path = Path(SESSION_ID)
37
- for json_file in db_path.glob('*.json'):
38
- with open(json_file) as f:
39
- problem = json.load(f)
40
- problem['id'] = json_file.stem
41
- problems.append(problem)
42
- print(f"Loaded problem: {json_file.stem}")
 
 
 
 
 
 
43
  return problems
44
 
45
-
46
  def save_submission(session, name, email, problem_id, code, hint_requested):
47
- db, submissions_table = get_db()
48
- timestamp = datetime.datetime.now().isoformat()
49
- submission = {
50
- 'session': session,
51
- 'name': name,
52
- 'email': email,
53
- 'problem_id': problem_id,
54
- 'student_code': code,
55
- 'hint_requested': hint_requested,
56
- 'timestamp': timestamp
57
- }
58
- query = Query()
59
- submissions_table.upsert(submission, (query.session == session) & (query.name == name) & (query.email == email) & (query.problem_id == problem_id))
60
- db.close()
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
- db.close()
70
  return "✅ All submissions saved successfully!"
71
  except Exception as e:
72
- db.close()
73
- return f"❌ Error saving submissions: {str(e)}"
 
74
 
75
  def check_password(password):
76
  return password == ADMIN_PASSWD
77
 
78
  def get_all_students():
79
- db, submissions_table = get_db()
80
- result = list(set([submission['name'] for submission in submissions_table.all()]))
81
- db.close()
82
- return result
 
 
 
 
83
 
84
  def get_student_submissions(name):
85
- db, submissions_table = get_db()
86
- query = Query()
87
- submissions = submissions_table.search(query.name == name)
88
- submissions.sort(key=lambda x: datetime.datetime.fromisoformat(x['timestamp']), reverse=True)
89
- db.close()
90
- return submissions
 
 
 
 
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
- db.close()
104
  return temp_file.name
105
  except Exception as e:
106
- print(f"Error in export_submissions: {str(e)}")
107
- db.close()
108
  return None
109
 
110
  def refresh_submissions():
111
- db, submissions_table = get_db()
112
- all_submissions = submissions_table.all()
113
- all_submissions.sort(key=lambda x: datetime.datetime.fromisoformat(x['timestamp']), reverse=True)
114
- db.close()
115
- return all_submissions
 
 
 
 
 
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 tinydb import TinyDB, Query
5
- from tinydb.storages import JSONStorage
6
- from tinydb.middlewares import CachingMiddleware
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 main():
45
- # Configuration
46
- DB_PATH = 'submissions.json'
47
- OUTPUT_DIR = Path("db_dump")
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- # Create output directory if it doesn't exist
50
- OUTPUT_DIR.mkdir(exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
 
 
 
 
52
  try:
53
- # Connect to TinyDB
54
- db = TinyDB(DB_PATH, storage=CachingMiddleware(JSONStorage))
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
- print("No submissions found in database")
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 markdown file for each student
74
- for name, submissions in student_submissions.items():
75
- # Create safe filename from student name
76
- safe_filename = "".join(x for x in name if x.isalnum() or x in "- ").strip()
77
- safe_filename = safe_filename.replace(" ", "_") + ".md"
78
 
79
- # Generate markdown content
80
- content = create_markdown_content(submissions)
 
 
 
81
 
82
- # Write to file
83
- file_path = OUTPUT_DIR / safe_filename
84
- with open(file_path, 'w', encoding='utf-8') as f:
85
- f.write(content)
86
 
87
- print(f"Created markdown file for {name}: {file_path}")
88
- print(f"Number of submissions: {len(submissions)}")
 
 
 
 
 
 
 
 
 
89
 
90
- print(f"\nSuccessfully created markdown files in {OUTPUT_DIR}")
91
- print(f"Total number of students: {len(student_submissions)}")
 
 
 
 
 
 
 
 
92
 
93
  except Exception as e:
94
- print(f"Error occurred: {str(e)}")
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
- """Mock function to get correct answer and problem description."""
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'] > student_submissions[name][problem_id]['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: "Midterm 2a"
78
  subtitle: {student_name}
79
- output:
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("**Student Solution:**\n```python\n{}\n```\n\n".format(submission['student_code']))
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
- print(f"Error generating reports: {e}")
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, SESSION_ID)
 
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)