omkar-surve126 commited on
Commit
6cdfd32
Β·
verified Β·
1 Parent(s): 5e8269d

Update session_page.py

Browse files
Files changed (1) hide show
  1. session_page.py +119 -705
session_page.py CHANGED
@@ -18,10 +18,9 @@ import os
18
  from pymongo import MongoClient
19
  from gen_mcqs import generate_mcqs, save_quiz, quizzes_collection, get_student_quiz_score, submit_quiz_answers
20
  from create_course import courses_collection
21
- # from pre_class_analytics import NovaScholarAnalytics
22
  from pre_class_analytics2 import NovaScholarAnalytics
23
  import openai
24
- from openai import OpenAI
25
  import google.generativeai as genai
26
  from goals2 import GoalAnalyzer
27
  from openai import OpenAI
@@ -30,6 +29,8 @@ import numpy as np
30
  import re
31
  from analytics import derive_analytics, create_embeddings, cosine_similarity
32
  from bs4 import BeautifulSoup
 
 
33
 
34
  load_dotenv()
35
  MONGO_URI = os.getenv('MONGO_URI')
@@ -40,6 +41,7 @@ db = client["novascholar_db"]
40
  polls_collection = db["polls"]
41
  subjective_tests_collection = db["subjective_tests"]
42
  synoptic_store_collection = db["synoptic_store"]
 
43
 
44
  def get_current_user():
45
  if 'current_user' not in st.session_state:
@@ -132,66 +134,6 @@ def get_current_user():
132
  # user = get_current_user()
133
 
134
  def display_preclass_content(session, student_id, course_id):
135
- # """Display pre-class materials for a session"""
136
- # st.subheader("Pre-class Materials")
137
- # print("Session ID is: ", session['session_id'])
138
- # # Display pre-class materials
139
- # materials = resources_collection.find({"session_id": session['session_id']})
140
- # for material in materials:
141
- # with st.expander(f"{material['file_name']} ({material['material_type'].upper()})"):
142
- # file_type = material.get('file_type', 'unknown')
143
- # if file_type == 'application/pdf':
144
- # st.markdown(f"πŸ“‘ [Open PDF Document]({material['file_name']})")
145
- # if st.button("View PDF", key=f"view_pdf_{material['_id']}"):
146
- # st.text_area("PDF Content", material['text_content'], height=300)
147
- # if st.button("Download PDF", key=f"download_pdf_{material['_id']}"):
148
- # st.download_button(
149
- # label="Download PDF",
150
- # data=material['file_content'],
151
- # file_name=material['file_name'],
152
- # mime='application/pdf'
153
- # )
154
- # if st.button("Mark PDF as Read", key=f"pdf_{material['_id']}"):
155
- # create_notification("PDF marked as read!", "success")
156
- # elif file_type == 'text/plain':
157
- # st.markdown(f"πŸ“„ [Open Text Document]({material['file_name']})")
158
- # if st.button("View Text", key=f"view_text_{material['_id']}"):
159
- # st.text_area("Text Content", material['text_content'], height=300)
160
- # if st.button("Download Text", key=f"download_text_{material['_id']}"):
161
- # st.download_button(
162
- # label="Download Text",
163
- # data=material['file_content'],
164
- # file_name=material['file_name'],
165
- # mime='text/plain'
166
- # )
167
- # if st.button("Mark Text as Read", key=f"text_{material['_id']}"):
168
- # create_notification("Text marked as read!", "success")
169
- # elif file_type == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
170
- # st.markdown(f"πŸ“„ [Open Word Document]({material['file_name']})")
171
- # if st.button("View Word", key=f"view_word_{material['_id']}"):
172
- # st.text_area("Word Content", material['text_content'], height=300)
173
- # if st.button("Download Word", key=f"download_word_{material['_id']}"):
174
- # st.download_button(
175
- # label="Download Word",
176
- # data=material['file_content'],
177
- # file_name=material['file_name'],
178
- # mime='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
179
- # )
180
- # if st.button("Mark Word as Read", key=f"word_{material['_id']}"):
181
- # create_notification("Word document marked as read!", "success")
182
- # elif file_type == 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
183
- # st.markdown(f"πŸ“Š [Open PowerPoint Presentation]({material['file_name']})")
184
- # if st.button("View PowerPoint", key=f"view_pptx_{material['_id']}"):
185
- # st.text_area("PowerPoint Content", material['text_content'], height=300)
186
- # if st.button("Download PowerPoint", key=f"download_pptx_{material['_id']}"):
187
- # st.download_button(
188
- # label="Download PowerPoint",
189
- # data=material['file_content'],
190
- # file_name=material['file_name'],
191
- # mime='application/vnd.openxmlformats-officedocument.presentationml.presentation'
192
- # )
193
- # if st.button("Mark PowerPoint as Read", key=f"pptx_{material['_id']}"):
194
- # create_notification("PowerPoint presentation marked as read!", "success")
195
  """Display pre-class materials for a session including external resources"""
196
  st.subheader("Pre-class Materials")
197
  print("Session ID is: ", session['session_id'])
@@ -672,41 +614,29 @@ def display_post_class_content(session, student_id, course_id):
672
  )
673
 
674
  if questions:
675
- synoptic = generate_synoptic(
676
- questions,
677
- context if context else None,
678
- session['title'],
679
- num_subjective_questions
680
- )
681
-
682
- if synoptic:
683
- # Store in session state
684
- st.session_state.generated_questions = questions
685
- st.session_state.generated_synoptic = synoptic
686
- st.session_state.test_title = test_title
687
 
688
  # Display preview
689
- st.subheader("Preview Subjective Questions and Synoptic")
690
- for i, (q, s) in enumerate(zip(questions, synoptic), 1):
691
- st.markdown(f"**Question {i}:** {q['question']}")
692
- with st.expander(f"View Synoptic {i}"):
693
- st.markdown(s)
694
 
695
- # Save button outside the form
696
- if st.button("Save Test"):
697
- test_id = save_subjective_test(
698
- course_id,
699
- session['session_id'],
700
- test_title,
701
- questions,
702
- synoptic
703
- )
704
- if test_id:
705
- st.success("Subjective test saved successfully!")
706
- else:
707
- st.error("Error saving subjective test.")
708
- else:
709
- st.error("Error generating synoptic answers. Please try again.")
710
  else:
711
  st.error("Error generating questions. Please try again.")
712
  except Exception as e:
@@ -714,11 +644,9 @@ def display_post_class_content(session, student_id, course_id):
714
 
715
  # Display previously generated test if it exists in session state
716
  elif hasattr(st.session_state, 'generated_questions') and hasattr(st.session_state, 'generated_synoptic'):
717
- st.subheader("Preview Subjective Questions and Synoptic")
718
- for i, (q, s) in enumerate(zip(st.session_state.generated_questions, st.session_state.generated_synoptic), 1):
719
- st.markdown(f"**Question {i}:** {q['question']}")
720
- with st.expander(f"View Synoptic {i}"):
721
- st.markdown(s)
722
 
723
  if st.button("Save Test"):
724
  test_id = save_subjective_test(
@@ -726,13 +654,11 @@ def display_post_class_content(session, student_id, course_id):
726
  session['session_id'],
727
  st.session_state.test_title,
728
  st.session_state.generated_questions,
729
- st.session_state.generated_synoptic
730
  )
731
  if test_id:
732
  st.success("Subjective test saved successfully!")
733
  # Clear session state after successful save
734
  del st.session_state.generated_questions
735
- del st.session_state.generated_synoptic
736
  del st.session_state.test_title
737
  else:
738
  st.error("Error saving subjective test.")
@@ -869,186 +795,7 @@ def display_post_class_content(session, student_id, course_id):
869
  st.markdown("### Feedback")
870
  st.info("Feedback will be provided here once the assignment is graded.")
871
  else:
872
- st.warning("No assignments found for this session.")
873
-
874
- # def display_preclass_analytics(session, course_id):
875
- # """Display pre-class analytics for faculty based on chat interaction metrics"""
876
- # st.subheader("Pre-class Analytics")
877
-
878
- # # Get all enrolled students
879
- # # enrolled_students = list(students_collection.find({"enrolled_courses": session['course_id']}))
880
- # enrolled_students = list(students_collection.find({
881
- # "enrolled_courses.course_id": course_id
882
- # }))
883
- # # total_students = len(enrolled_students)
884
-
885
- # total_students = students_collection.count_documents({
886
- # "enrolled_courses": {
887
- # "$elemMatch": {"course_id": course_id}
888
- # }
889
- # })
890
-
891
-
892
- # if total_students == 0:
893
- # st.warning("No students enrolled in this course.")
894
- # return
895
-
896
- # # Get chat history for all students in this session
897
- # chat_data = list(chat_history_collection.find({
898
- # "session_id": session['session_id']
899
- # }))
900
-
901
- # # Create a DataFrame to store student completion data
902
- # completion_data = []
903
- # incomplete_students = []
904
-
905
- # for student in enrolled_students:
906
- # student_id = student['_id']
907
- # student_name = student.get('full_name', 'Unknown')
908
- # student_sid = student.get('SID', 'Unknown')
909
-
910
- # # Find student's chat history
911
- # student_chat = next((chat for chat in chat_data if chat['user_id'] == student_id), None)
912
-
913
- # if student_chat:
914
- # messages = student_chat.get('messages', [])
915
- # message_count = len(messages)
916
- # status = "Completed" if message_count >= 20 else "Incomplete"
917
-
918
- # # Format chat history for display
919
- # chat_history = []
920
- # for msg in messages:
921
- # timestamp_str = msg.get('timestamp', '')
922
- # if isinstance(timestamp_str, str):
923
- # timestamp = datetime.fromisoformat(timestamp_str)
924
- # else:
925
- # timestamp = timestamp_str
926
- # # timestamp = msg.get('timestamp', '').strftime("%Y-%m-%d %H:%M:%S")
927
- # chat_history.append({
928
- # # 'timestamp': timestamp,
929
- # 'timestamp': timestamp.strftime("%Y-%m-%d %H:%M:%S"),
930
- # 'prompt': msg.get('prompt'),
931
- # 'response': msg.get('response')
932
- # })
933
-
934
- # message_count = len(student_chat.get('messages', []))
935
- # status = "Completed" if message_count >= 20 else "Incomplete"
936
- # if status == "Incomplete":
937
- # incomplete_students.append({
938
- # 'name': student_name,
939
- # 'sid': student_sid,
940
- # 'message_count': message_count
941
- # })
942
- # else:
943
- # message_count = 0
944
- # status = "Not Started"
945
- # chat_history = []
946
- # incomplete_students.append({
947
- # 'name': student_name,
948
- # 'sid': student_sid,
949
- # 'message_count': 0
950
- # })
951
-
952
- # completion_data.append({
953
- # 'Student Name': student_name,
954
- # 'SID': student_sid,
955
- # 'Messages': message_count,
956
- # 'Status': status,
957
- # 'Chat History': chat_history
958
- # })
959
-
960
- # # Create DataFrame
961
- # df = pd.DataFrame(completion_data)
962
-
963
- # # Display summary metrics
964
- # col1, col2, col3 = st.columns(3)
965
-
966
- # completed_count = len(df[df['Status'] == 'Completed'])
967
- # incomplete_count = len(df[df['Status'] == 'Incomplete'])
968
- # not_started_count = len(df[df['Status'] == 'Not Started'])
969
-
970
- # with col1:
971
- # st.metric("Completed", completed_count)
972
- # with col2:
973
- # st.metric("Incomplete", incomplete_count)
974
- # with col3:
975
- # st.metric("Not Started", not_started_count)
976
-
977
- # # Display completion rate progress bar
978
- # completion_rate = (completed_count / total_students) * 100
979
- # st.markdown("### Overall Completion Rate")
980
- # st.progress(completion_rate / 100)
981
- # st.markdown(f"**{completion_rate:.1f}%** of students have completed pre-class materials")
982
-
983
- # # Create tabs for different views
984
- # tab1, tab2 = st.tabs(["Student Overview", "Detailed Chat History"])
985
-
986
- # with tab1:
987
- # # Display completion summary table
988
- # st.markdown("### Student Completion Details")
989
- # summary_df = df[['Student Name', 'SID', 'Messages', 'Status']].copy()
990
- # st.dataframe(
991
- # summary_df.style.apply(lambda x: ['background-color: #90EE90' if v == 'Completed'
992
- # else 'background-color: #FFB6C1' if v == 'Incomplete'
993
- # else 'background-color: #FFE4B5'
994
- # for v in x],
995
- # subset=['Status'])
996
- # )
997
-
998
- # with tab2:
999
- # # Display detailed chat history
1000
- # st.markdown("### Student Chat Histories")
1001
-
1002
- # # Add student selector
1003
- # selected_student = st.selectbox(
1004
- # "Select a student to view chat history:",
1005
- # options=df['Student Name'].tolist()
1006
- # )
1007
-
1008
- # # Get selected student's data
1009
- # student_data = df[df['Student Name'] == selected_student].iloc[0]
1010
- # print(student_data)
1011
- # chat_history = student_data['Chat History']
1012
- # # Refresh chat history when a new student is selected
1013
- # if 'selected_student' not in st.session_state or st.session_state.selected_student != selected_student:
1014
- # st.session_state.selected_student = selected_student
1015
- # st.session_state.selected_student_chat_history = chat_history
1016
- # else:
1017
- # chat_history = st.session_state.selected_student_chat_history
1018
- # # Display student info and chat statistics
1019
- # st.markdown(f"**Student ID:** {student_data['SID']}")
1020
- # st.markdown(f"**Status:** {student_data['Status']}")
1021
- # st.markdown(f"**Total Messages:** {student_data['Messages']}")
1022
-
1023
- # # Display chat history in a table
1024
- # if chat_history:
1025
- # chat_df = pd.DataFrame(chat_history)
1026
- # st.dataframe(
1027
- # chat_df.style.apply(lambda x: ['background-color: #E8F0FE' if v == 'response' else 'background-color: #FFFFFF' for v in x], subset=['prompt']), use_container_width=True
1028
- # )
1029
- # else:
1030
- # st.info("No chat history available for this student.")
1031
-
1032
- # # Display students who haven't completed
1033
- # if incomplete_students:
1034
- # st.markdown("### Students Requiring Follow-up")
1035
- # incomplete_df = pd.DataFrame(incomplete_students)
1036
- # st.markdown(f"**{len(incomplete_students)} students** need to complete the pre-class materials:")
1037
-
1038
- # # Create a styled table for incomplete students
1039
- # st.table(
1040
- # incomplete_df.style.apply(lambda x: ['background-color: #FFFFFF'
1041
- # for _ in range(len(x))]))
1042
-
1043
- # # Export option for incomplete students list
1044
- # csv = incomplete_df.to_csv(index=False).encode('utf-8')
1045
- # st.download_button(
1046
- # "Download Follow-up List",
1047
- # csv,
1048
- # "incomplete_students.csv",
1049
- # "text/csv",
1050
- # key='download-csv'
1051
- # )
1052
 
1053
  def display_inclass_analytics(session, course_id):
1054
  """Display in-class analytics for faculty"""
@@ -1164,9 +911,6 @@ def display_inclass_analytics(session, course_id):
1164
  "_id": {"$nin": respondents}
1165
  }))
1166
 
1167
-
1168
-
1169
-
1170
  if non_participants:
1171
  st.markdown("#### Students Who Haven't Participated")
1172
  non_participant_data = [{
@@ -1205,24 +949,23 @@ def display_inclass_analytics(session, course_id):
1205
  def display_postclass_analytics(session, course_id):
1206
  """Display post-class analytics for faculty"""
1207
  st.subheader("Post-class Analytics")
1208
-
1209
  # Get all assignments for this session
1210
  session_data = courses_collection2.find_one(
1211
  {"sessions.session_id": session['session_id']},
1212
  {"sessions.$": 1}
1213
  )
1214
-
1215
  if not session_data or 'sessions' not in session_data:
1216
  st.warning("No assignments found for this session.")
1217
  return
1218
-
1219
  assignments = session_data['sessions'][0].get('post_class', {}).get('assignments', [])
1220
-
1221
  for assignment in assignments:
1222
  with st.expander(f"πŸ“ Assignment: {assignment.get('title', 'Untitled')}"):
1223
  # Get submission analytics
1224
  submissions = assignment.get('submissions', [])
1225
- # total_students = students_collection.count_documents({"courses": session['course_id']})
1226
  total_students = students_collection.count_documents({
1227
  "enrolled_courses": {
1228
  "$elemMatch": {"course_id": course_id}
@@ -1231,7 +974,7 @@ def display_postclass_analytics(session, course_id):
1231
  # Calculate submission metrics
1232
  submitted_count = len(submissions)
1233
  submission_rate = (submitted_count / total_students) * 100 if total_students > 0 else 0
1234
-
1235
  # Display metrics
1236
  col1, col2, col3 = st.columns(3)
1237
  with col1:
@@ -1240,43 +983,25 @@ def display_postclass_analytics(session, course_id):
1240
  st.metric("Submission Rate", f"{submission_rate:.1f}%")
1241
  with col3:
1242
  st.metric("Pending Submissions", total_students - submitted_count)
1243
-
1244
  # Display submission timeline
1245
  if submissions:
1246
  submission_dates = [sub.get('submitted_at') for sub in submissions if 'submitted_at' in sub]
1247
  if submission_dates:
1248
  df = pd.DataFrame(submission_dates, columns=['Submission Date'])
1249
- fig = px.histogram(df, x='Submission Date',
1250
- title='Submission Timeline',
1251
- labels={'Submission Date': 'Date', 'count': 'Number of Submissions'})
1252
  st.plotly_chart(fig)
1253
-
1254
- # Display submission status breakdown
1255
- status_counts = {
1256
- 'pending': total_students - submitted_count,
1257
- 'submitted': submitted_count,
1258
- 'late': len([sub for sub in submissions if sub.get('is_late', False)])
1259
- }
1260
-
1261
- st.markdown("### Submission Status Breakdown")
1262
- status_df = pd.DataFrame(list(status_counts.items()),
1263
- columns=['Status', 'Count'])
1264
- st.bar_chart(status_df.set_index('Status'))
1265
-
1266
- # List of students who haven't submitted
1267
- if status_counts['pending'] > 0:
1268
- st.markdown("### Students with Pending Submissions")
1269
- # submitted_ids = [sub.get('student_id') for sub in submissions]
1270
- submitted_ids = [ObjectId(sub.get('student_id')) for sub in submissions]
1271
- print(submitted_ids)
1272
- pending_students = students_collection.find({
1273
- "enrolled_courses.course_id": course_id,
1274
- "_id": {"$nin": submitted_ids}
1275
- })
1276
- print(pending_students)
1277
- for student in pending_students:
1278
- st.markdown(f"- {student.get('full_name', 'Unknown Student')} (SID: {student.get('SID', 'Unknown SID')})")
1279
 
 
 
 
 
 
 
 
 
1280
  def get_chat_history(user_id, session_id):
1281
  query = {
1282
  "user_id": ObjectId(user_id),
@@ -1933,34 +1658,6 @@ def display_session_analytics(session, course_id):
1933
 
1934
  # Display Post-class Analytics
1935
  display_postclass_analytics(session, course_id)
1936
-
1937
- # def upload_preclass_materials(session_id, course_id):
1938
- # """Upload pre-class materials for a session"""
1939
- # st.subheader("Upload Pre-class Materials")
1940
-
1941
- # # File upload section
1942
- # uploaded_file = st.file_uploader("Upload Material", type=['txt', 'pdf', 'docx'])
1943
- # if uploaded_file is not None:
1944
- # with st.spinner("Processing document..."):
1945
- # file_name = uploaded_file.name
1946
- # file_content = extract_text_from_file(uploaded_file)
1947
- # if file_content:
1948
- # material_type = st.selectbox("Select Material Type", ["pdf", "docx", "txt"])
1949
- # if st.button("Upload Material"):
1950
- # upload_resource(course_id, session_id, file_name, uploaded_file, material_type)
1951
-
1952
- # # Search for the newly uploaded resource's _id in resources_collection
1953
- # resource_id = resources_collection.find_one({"file_name": file_name})["_id"]
1954
- # create_vector_store(file_content, resource_id)
1955
- # st.success("Material uploaded successfully!")
1956
-
1957
- # # Display existing materials
1958
- # materials = resources_collection.find({"course_id": course_id, "session_id": session_id})
1959
- # for material in materials:
1960
- # st.markdown(f"""
1961
- # * **{material['file_name']}** ({material['material_type']})
1962
- # Uploaded on: {material['uploaded_at'].strftime('%Y-%m-%d %H:%M')}
1963
- # """)
1964
 
1965
  def upload_preclass_materials(session_id, course_id):
1966
  """Upload pre-class materials and manage external resources for a session"""
@@ -2191,75 +1888,11 @@ def display_quiz_tab(student_id, course_id, session_id):
2191
  else:
2192
  st.error("Error submitting quiz. Please try again.")
2193
 
2194
- def display_subjective_test_tab(student_id, course_id, session_id):
2195
- """Display subjective tests for students"""
2196
- st.header("Subjective Tests")
2197
-
2198
- try:
2199
- subjective_tests = list(subjective_tests_collection.find({
2200
- "course_id": course_id,
2201
- "session_id": session_id,
2202
- "status": "active"
2203
- }))
2204
-
2205
- if not subjective_tests:
2206
- st.info("No subjective tests available for this session.")
2207
- return
2208
-
2209
- for test in subjective_tests:
2210
- with st.expander(f"πŸ“ {test['title']}", expanded=True):
2211
- # Check for existing submission
2212
- existing_submission = next(
2213
- (sub for sub in test.get('submissions', [])
2214
- if sub['student_id'] == str(student_id)),
2215
- None
2216
- )
2217
-
2218
- if existing_submission:
2219
- st.success("Test completed! Your answers have been submitted.")
2220
- st.subheader("Your Answers")
2221
- for i, ans in enumerate(existing_submission['answers']):
2222
- st.markdown(f"**Question {i+1}:** {test['questions'][i]['question']}")
2223
- st.markdown(f"**Your Answer:** {ans}")
2224
- st.markdown("---")
2225
- else:
2226
- st.write("Please write your answers:")
2227
- with st.form(key=f"subjective_test_form_{test['_id']}"):
2228
- student_answers = []
2229
- for i, question in enumerate(test['questions']):
2230
- st.markdown(f"**Question {i+1}:** {question['question']}")
2231
- answer = st.text_area(
2232
- "Your answer:",
2233
- key=f"q_{test['_id']}_{i}",
2234
- height=200
2235
- )
2236
- student_answers.append(answer)
2237
-
2238
- if st.form_submit_button("Submit Test"):
2239
- if all(answer.strip() for answer in student_answers):
2240
- success = submit_subjective_test(
2241
- test['_id'],
2242
- str(student_id),
2243
- student_answers
2244
- )
2245
- if success:
2246
- st.success("Test submitted successfully!")
2247
- st.rerun()
2248
- else:
2249
- st.error("Error submitting test. Please try again.")
2250
- else:
2251
- st.error("Please answer all questions before submitting.")
2252
-
2253
- except Exception as e:
2254
- st.error(f"An error occurred while loading the tests. Please try again later.")
2255
- print(f"Error in display_subjective_test_tab: {str(e)}", flush=True)
2256
-
2257
  def display_session_content(student_id, course_id, session, username, user_type):
2258
  st.title(f"{session['title']}")
2259
 
2260
  # Check if the date is a string or a datetime object
2261
  if isinstance(session['date'], str):
2262
- # Convert date string to datetime object
2263
  session_date = datetime.fromisoformat(session['date'])
2264
  else:
2265
  session_date = session['date']
@@ -2269,10 +1902,7 @@ def display_session_content(student_id, course_id, session, username, user_type)
2269
  st.markdown(f"**Date:** {format_datetime(session_date)}")
2270
  st.markdown(f"**Course Name:** {course_name}")
2271
 
2272
- # Find the course_id of the session in
2273
-
2274
  if user_type == 'student':
2275
- # Create all tabs at once for students
2276
  tabs = st.tabs([
2277
  "Pre-class Work",
2278
  "In-class Work",
@@ -2292,18 +1922,14 @@ def display_session_content(student_id, course_id, session, username, user_type)
2292
  with tabs[3]:
2293
  display_quiz_tab(student_id, course_id, session['session_id'])
2294
  with tabs[4]:
2295
- display_subjective_test_tab(student_id, course_id, session['session_id'])
2296
  with tabs[5]:
2297
- st.subheader("Group Work")
2298
- st.info("Group work content will be available soon.")
2299
  with tabs[6]:
2300
  st.subheader("End Terms")
2301
  st.info("End term content will be available soon.")
2302
- else:
2303
- st.error("Error creating tabs. Please try again.")
2304
-
2305
  else: # faculty user
2306
- # Create all tabs at once for faculty
2307
  tabs = st.tabs([
2308
  "Pre-class Work",
2309
  "In-class Work",
@@ -2311,7 +1937,9 @@ def display_session_content(student_id, course_id, session, username, user_type)
2311
  "Pre-class Analytics",
2312
  "In-class Analytics",
2313
  "Post-class Analytics",
2314
- "End Terms"
 
 
2315
  ])
2316
  with tabs[0]:
2317
  upload_preclass_materials(session['session_id'], course_id)
@@ -2326,9 +1954,13 @@ def display_session_content(student_id, course_id, session, username, user_type)
2326
  with tabs[5]:
2327
  display_postclass_analytics(session, course_id)
2328
  with tabs[6]:
 
 
2329
  st.subheader("End Terms")
2330
  st.info("End term content will be available soon.")
2331
-
 
 
2332
  def parse_model_response(response_text):
2333
  """Enhanced parser for model responses with better error handling.
2334
 
@@ -2426,124 +2058,21 @@ def generate_questions(context, num_questions, session_title, session_descriptio
2426
  print(f"Error generating questions: {str(e)}")
2427
  return None
2428
 
2429
- def generate_synoptic(questions, context, session_title, num_questions):
2430
- """Generate synoptic answers for the questions with improved error handling and response validation.
2431
-
2432
- Args:
2433
- questions (list): List of question dictionaries
2434
- context (str): Additional context for answer generation
2435
- session_title (str): Title of the session
2436
- num_questions (int): Expected number of questions
2437
-
2438
- Returns:
2439
- list: List of synoptic answers or None if generation fails
2440
- """
2441
  try:
2442
- # First, let's validate our input
2443
- if not questions or not isinstance(questions, list):
2444
- raise ValueError("Questions must be provided as a non-empty list")
2445
-
2446
- # Format questions for better prompt clarity
2447
- formatted_questions = "\n".join(
2448
- f"{i+1}. {q['question']}"
2449
- for i, q in enumerate(questions)
2450
- )
2451
 
2452
- # Construct a more structured prompt
2453
- prompt = f"""You are a subject matter expert creating detailed model answers for {num_questions} questions about {session_title}.
2454
-
2455
- Here are the questions:
2456
- {formatted_questions}
2457
- {f'Additional context: {context}' if context else ''}
2458
-
2459
- Please provide {num_questions} comprehensive answers following this JSON format:
2460
- {{
2461
- "answers": [
2462
- {{
2463
- "answer": "Your detailed answer for question 1...",
2464
- "key_points": ["Point 1", "Point 2", "Point 3"]
2465
- }},
2466
- {{
2467
- "answer": "Your detailed answer for question 2...",
2468
- "key_points": ["Point 1", "Point 2", "Point 3"]
2469
- }}
2470
- ]
2471
- }}
2472
-
2473
- Requirements for each answer:
2474
- 1. Minimum 150 words
2475
- 2. Include specific examples and evidence
2476
- 3. Reference key concepts and terminology
2477
- 4. Demonstrate critical analysis
2478
- 5. Structure with clear introduction, body, and conclusion
2479
-
2480
- IMPORTANT: Return ONLY the JSON object with the answers array. No additional text.
2481
- """
2482
-
2483
- # Generate response
2484
- response = model.generate_content(prompt)
2485
-
2486
- # Parse and validate the response
2487
- parsed_response = parse_model_response(response.text)
2488
-
2489
- # Additional validation of parsed response
2490
- if not isinstance(parsed_response, (dict, list)):
2491
- raise ValueError("Response must be a dictionary or list")
2492
-
2493
- # Handle both possible valid response formats
2494
- if isinstance(parsed_response, dict):
2495
- answers = parsed_response.get('answers', [])
2496
- else: # If it's a list
2497
- answers = parsed_response
2498
-
2499
- # Validate answer count
2500
- if len(answers) != num_questions:
2501
- raise ValueError(f"Expected {num_questions} answers, got {len(answers)}")
2502
-
2503
- # Extract just the answer texts for consistency with existing code
2504
- final_answers = []
2505
- for ans in answers:
2506
- if isinstance(ans, dict):
2507
- answer_text = ans.get('answer', '')
2508
- key_points = ans.get('key_points', [])
2509
- formatted_answer = f"{answer_text}\n\nKey Points:\n" + "\n".join(f"β€’ {point}" for point in key_points)
2510
- final_answers.append(formatted_answer)
2511
- else:
2512
- final_answers.append(str(ans))
2513
-
2514
- # Final validation of the answers
2515
- for i, answer in enumerate(final_answers):
2516
- if not answer or len(answer.split()) < 50: # Basic length check
2517
- raise ValueError(f"Answer {i+1} is too short or empty")
2518
-
2519
- # Save the synoptic to the synoptic_store collection
2520
- synoptic_data = {
2521
- "session_title": session_title,
2522
- "questions": questions,
2523
- "synoptic": final_answers,
2524
- "created_at": datetime.utcnow()
2525
- }
2526
- synoptic_store_collection.insert_one(synoptic_data)
2527
-
2528
- return final_answers
2529
-
2530
- except Exception as e:
2531
- # Log the error for debugging
2532
- print(f"Error in generate_synoptic: {str(e)}")
2533
- print(f"Response text: {response.text if 'response' in locals() else 'No response generated'}")
2534
- return None
2535
-
2536
- def save_subjective_test(course_id, session_id, title, questions, synoptic):
2537
- """Save subjective test to database"""
2538
- try:
2539
- # Format questions to include metadata
2540
  formatted_questions = []
2541
  for q in questions:
2542
  formatted_question = {
2543
  "question": q["question"],
2544
- "expected_points": q.get("expected_points", []),
2545
- "difficulty_level": q.get("difficulty_level", "medium"),
2546
- "suggested_time": q.get("suggested_time", "5 minutes")
2547
  }
2548
  formatted_questions.append(formatted_question)
2549
 
@@ -2552,7 +2081,6 @@ def save_subjective_test(course_id, session_id, title, questions, synoptic):
2552
  "session_id": session_id,
2553
  "title": title,
2554
  "questions": formatted_questions,
2555
- "synoptic": synoptic,
2556
  "created_at": datetime.utcnow(),
2557
  "status": "active",
2558
  "submissions": []
@@ -2561,148 +2089,40 @@ def save_subjective_test(course_id, session_id, title, questions, synoptic):
2561
  result = subjective_tests_collection.insert_one(test_data)
2562
  return result.inserted_id
2563
  except Exception as e:
2564
- print(f"Error saving subjective test: {e}")
2565
  return None
2566
 
2567
- def submit_subjective_test(test_id, student_id, student_answers):
2568
- """Submit subjective test answers and trigger analysis"""
2569
  try:
2570
- submission_data = {
 
 
 
 
 
2571
  "student_id": student_id,
2572
- "answers": student_answers,
2573
- "submitted_at": datetime.utcnow()
 
2574
  }
2575
 
 
2576
  result = subjective_tests_collection.update_one(
2577
- {"_id": test_id},
2578
- {
2579
- "$push": {
2580
- "submissions": submission_data
2581
- }
2582
- }
2583
  )
2584
 
2585
- if result.modified_count > 0:
2586
- try:
2587
- # Trigger grading and analysis
2588
- analysis = analyze_subjective_answers(test_id, student_id)
2589
- if analysis:
2590
- # Update the submission with the analysis and score
2591
- subjective_tests_collection.update_one(
2592
- {"_id": test_id, "submissions.student_id": student_id},
2593
- {
2594
- "$set": {
2595
- "submissions.$.analysis": analysis,
2596
- "submissions.$.score": analysis.get('correctness_score')
2597
- }
2598
- }
2599
- )
2600
- return True
2601
- else:
2602
- print("Error: Analysis failed")
2603
- return False
2604
- except Exception as e:
2605
- print(f"Warning: Grading failed but submission was saved: {e}")
2606
- return True # We still return True since the submission itself was successful
2607
-
2608
- print("Error: No document was modified")
2609
- return False
2610
-
2611
  except Exception as e:
2612
- print(f"Error submitting subjective test: {str(e)}")
2613
  return False
2614
 
2615
- def analyze_subjective_answers(test_id, student_id):
2616
- """Analyze subjective test answers for correctness and improvements"""
2617
- try:
2618
- # Get test and submission details
2619
- test_doc = subjective_tests_collection.find_one({"_id": test_id})
2620
- if not test_doc:
2621
- print(f"Test document not found for test_id: {test_id}")
2622
- return None
2623
-
2624
- submission = next(
2625
- (sub for sub in test_doc.get('submissions', []) if sub['student_id'] == student_id),
2626
- None
2627
- )
2628
-
2629
- if not submission:
2630
- print(f"No submission found for student_id: {student_id}")
2631
- return None
2632
-
2633
- # Get questions and answers
2634
- questions = test_doc.get('questions', [])
2635
- student_answers = submission.get('answers', [])
2636
-
2637
- if not questions or not student_answers:
2638
- print("No questions or answers found")
2639
- return None
2640
-
2641
- # Retrieve the synoptic from the synoptic_store collection
2642
- synoptic_doc = synoptic_store_collection.find_one({"session_title": test_doc.get('title')})
2643
- synoptic = synoptic_doc.get('synoptic', '') if synoptic_doc else ''
2644
-
2645
- # Analyze each question separately
2646
- all_analyses = []
2647
- total_score = 0
2648
-
2649
- for i, (question, answer) in enumerate(zip(questions, student_answers), 1):
2650
- # Format content for individual question
2651
- analysis_content = f"Question {i}: {question['question']}\nAnswer: {answer}\n\n"
2652
-
2653
- # Get analysis for this question
2654
- individual_analysis = derive_analytics(
2655
- goal="Analyze and Grade",
2656
- reference_text=analysis_content,
2657
- openai_api_key=OPENAI_API_KEY,
2658
- context=test_doc.get('context', ''),
2659
- synoptic=synoptic[i-1] if isinstance(synoptic, list) else synoptic
2660
- )
2661
-
2662
- if individual_analysis:
2663
- # Extract score for this question
2664
- try:
2665
- score_match = re.search(r'(\d+)(?:/10)?', individual_analysis)
2666
- if score_match:
2667
- question_score = int(score_match.group(1))
2668
- if 1 <= question_score <= 10:
2669
- total_score += question_score
2670
- except:
2671
- question_score = 0
2672
-
2673
- # Format individual analysis
2674
- formatted_analysis = f"\n\n## Question {i} Analysis\n\n{individual_analysis}"
2675
- all_analyses.append(formatted_analysis)
2676
-
2677
- if not all_analyses:
2678
- print("Error: No analyses generated")
2679
- return None
2680
-
2681
- # Calculate average score
2682
- average_score = round(total_score / len(questions)) if questions else 0
2683
-
2684
- # Combine all analyses
2685
- combined_analysis = "\n".join(all_analyses)
2686
-
2687
- # Format final results
2688
- analysis_results = {
2689
- "content_analysis": combined_analysis,
2690
- "analyzed_at": datetime.utcnow(),
2691
- "correctness_score": average_score
2692
- }
2693
-
2694
- return analysis_results
2695
-
2696
- except Exception as e:
2697
- print(f"Error in analyze_subjective_answers: {str(e)}")
2698
- return None
2699
-
2700
  def display_subjective_test_tab(student_id, course_id, session_id):
2701
  """Display subjective tests for students"""
2702
  st.header("Subjective Tests")
2703
-
2704
  try:
2705
- # Query for active tests
2706
  subjective_tests = list(subjective_tests_collection.find({
2707
  "course_id": course_id,
2708
  "session_id": session_id,
@@ -2721,7 +2141,7 @@ def display_subjective_test_tab(student_id, course_id, session_id):
2721
  if sub['student_id'] == str(student_id)),
2722
  None
2723
  )
2724
-
2725
  if existing_submission:
2726
  st.success("Test completed! Your answers have been submitted.")
2727
  st.subheader("Your Answers")
@@ -2729,9 +2149,19 @@ def display_subjective_test_tab(student_id, course_id, session_id):
2729
  st.markdown(f"**Question {i+1}:** {test['questions'][i]['question']}")
2730
  st.markdown(f"**Your Answer:** {ans}")
2731
  st.markdown("---")
2732
-
2733
  # Display analysis
2734
- display_subjective_analysis(test['_id'], str(student_id), test.get('context', ''))
 
 
 
 
 
 
 
 
 
 
2735
  else:
2736
  st.write("Please write your answers:")
2737
  with st.form(key=f"subjective_test_form_{test['_id']}"):
@@ -2762,43 +2192,27 @@ def display_subjective_test_tab(student_id, course_id, session_id):
2762
  except Exception as e:
2763
  print(f"Error in display_subjective_test_tab: {str(e)}", flush=True)
2764
  st.error("An error occurred while loading the tests. Please try again later.")
2765
-
2766
- def display_subjective_analysis(test_id, student_id, context):
2767
- """Display subjective test analysis to students and faculty"""
2768
- try:
2769
- test_doc = subjective_tests_collection.find_one({"_id": test_id})
2770
- submission = next(
2771
- (sub for sub in test_doc.get('submissions', []) if sub['student_id'] == student_id),
2772
- None
2773
- )
2774
 
2775
- if not submission:
2776
- st.warning("No submission found for analysis.")
2777
- return
2778
-
2779
- # Get or generate analysis
2780
- analysis = submission.get('analysis')
2781
- if not analysis:
2782
- analysis = analyze_subjective_answers(test_id, student_id, context)
2783
- if not analysis:
2784
- st.error("Could not generate analysis.")
2785
- return
2786
-
2787
- # Display analysis results
2788
- st.subheader("Answer Analysis")
2789
-
2790
- # Content analysis
2791
- st.markdown("### Evidence-Based Feedback")
2792
- st.markdown(analysis.get('content_analysis', 'No analysis available'))
2793
-
2794
- # Improvement suggestions
2795
- # st.markdown("### Suggested Improvements")
2796
- # st.markdown(analysis.get('suggested_improvements', 'No suggestions available'))
2797
-
2798
- # Analysis timestamp
2799
- analyzed_at = analysis.get('analyzed_at')
2800
- if analyzed_at:
2801
- st.caption(f"Analysis performed at: {analyzed_at.strftime('%Y-%m-%d %H:%M:%S UTC')}")
2802
-
2803
- except Exception as e:
2804
- st.error(f"Error displaying analysis: {e}")
 
18
  from pymongo import MongoClient
19
  from gen_mcqs import generate_mcqs, save_quiz, quizzes_collection, get_student_quiz_score, submit_quiz_answers
20
  from create_course import courses_collection
21
+ import traceback
22
  from pre_class_analytics2 import NovaScholarAnalytics
23
  import openai
 
24
  import google.generativeai as genai
25
  from goals2 import GoalAnalyzer
26
  from openai import OpenAI
 
29
  import re
30
  from analytics import derive_analytics, create_embeddings, cosine_similarity
31
  from bs4 import BeautifulSoup
32
+ from rubrics import display_rubrics_tab
33
+ from subjective_test_evaluation import evaluate_subjective_answers, display_evaluation_to_faculty
34
 
35
  load_dotenv()
36
  MONGO_URI = os.getenv('MONGO_URI')
 
41
  polls_collection = db["polls"]
42
  subjective_tests_collection = db["subjective_tests"]
43
  synoptic_store_collection = db["synoptic_store"]
44
+ subjective_test_analysis_collection = db['subjective_test_analysis']
45
 
46
  def get_current_user():
47
  if 'current_user' not in st.session_state:
 
134
  # user = get_current_user()
135
 
136
  def display_preclass_content(session, student_id, course_id):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  """Display pre-class materials for a session including external resources"""
138
  st.subheader("Pre-class Materials")
139
  print("Session ID is: ", session['session_id'])
 
614
  )
615
 
616
  if questions:
617
+ # Store in session state
618
+ st.session_state.generated_questions = questions
619
+ st.session_state.test_title = test_title
 
 
 
 
 
 
 
 
 
620
 
621
  # Display preview
622
+ st.subheader("Preview Subjective Questions and Synoptic")
623
+ for i, q in enumerate(questions, 1):
624
+ st.markdown(f"**Question {i}:** {q['question']}")
625
+ # with st.expander(f"View Synoptic {i}"):
626
+ # st.markdown(s)
627
 
628
+ # Save button outside the form
629
+ if st.button("Save Test"):
630
+ test_id = save_subjective_test(
631
+ course_id,
632
+ session['session_id'],
633
+ test_title,
634
+ questions
635
+ )
636
+ if test_id:
637
+ st.success("Subjective test saved successfully!")
638
+ else:
639
+ st.error("Error saving subjective test.")
 
 
 
640
  else:
641
  st.error("Error generating questions. Please try again.")
642
  except Exception as e:
 
644
 
645
  # Display previously generated test if it exists in session state
646
  elif hasattr(st.session_state, 'generated_questions') and hasattr(st.session_state, 'generated_synoptic'):
647
+ st.subheader("Preview Subjective Questions")
648
+ for i, q in enumerate(st.session_state.generated_questions, 1):
649
+ st.markdown(f"**Questison {i}:** {q['question']}")
 
 
650
 
651
  if st.button("Save Test"):
652
  test_id = save_subjective_test(
 
654
  session['session_id'],
655
  st.session_state.test_title,
656
  st.session_state.generated_questions,
 
657
  )
658
  if test_id:
659
  st.success("Subjective test saved successfully!")
660
  # Clear session state after successful save
661
  del st.session_state.generated_questions
 
662
  del st.session_state.test_title
663
  else:
664
  st.error("Error saving subjective test.")
 
795
  st.markdown("### Feedback")
796
  st.info("Feedback will be provided here once the assignment is graded.")
797
  else:
798
+ st.warning("No assignments found for this session.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
799
 
800
  def display_inclass_analytics(session, course_id):
801
  """Display in-class analytics for faculty"""
 
911
  "_id": {"$nin": respondents}
912
  }))
913
 
 
 
 
914
  if non_participants:
915
  st.markdown("#### Students Who Haven't Participated")
916
  non_participant_data = [{
 
949
  def display_postclass_analytics(session, course_id):
950
  """Display post-class analytics for faculty"""
951
  st.subheader("Post-class Analytics")
952
+
953
  # Get all assignments for this session
954
  session_data = courses_collection2.find_one(
955
  {"sessions.session_id": session['session_id']},
956
  {"sessions.$": 1}
957
  )
958
+
959
  if not session_data or 'sessions' not in session_data:
960
  st.warning("No assignments found for this session.")
961
  return
962
+
963
  assignments = session_data['sessions'][0].get('post_class', {}).get('assignments', [])
964
+
965
  for assignment in assignments:
966
  with st.expander(f"πŸ“ Assignment: {assignment.get('title', 'Untitled')}"):
967
  # Get submission analytics
968
  submissions = assignment.get('submissions', [])
 
969
  total_students = students_collection.count_documents({
970
  "enrolled_courses": {
971
  "$elemMatch": {"course_id": course_id}
 
974
  # Calculate submission metrics
975
  submitted_count = len(submissions)
976
  submission_rate = (submitted_count / total_students) * 100 if total_students > 0 else 0
977
+
978
  # Display metrics
979
  col1, col2, col3 = st.columns(3)
980
  with col1:
 
983
  st.metric("Submission Rate", f"{submission_rate:.1f}%")
984
  with col3:
985
  st.metric("Pending Submissions", total_students - submitted_count)
986
+
987
  # Display submission timeline
988
  if submissions:
989
  submission_dates = [sub.get('submitted_at') for sub in submissions if 'submitted_at' in sub]
990
  if submission_dates:
991
  df = pd.DataFrame(submission_dates, columns=['Submission Date'])
992
+ fig = px.histogram(df, x='Submission Date',
993
+ title='Submission Timeline',
994
+ labels={'Submission Date': 'Date', 'count': 'Number of Submissions'})
995
  st.plotly_chart(fig)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
996
 
997
+ # Display subjective test analysis
998
+ st.subheader("Subjective Test Analysis")
999
+ for submission in submissions:
1000
+ if 'analysis' in submission:
1001
+ st.markdown(f"**Student ID:** {submission['student_id']}")
1002
+ st.markdown(submission['analysis']['content_analysis'])
1003
+ st.markdown("---")
1004
+
1005
  def get_chat_history(user_id, session_id):
1006
  query = {
1007
  "user_id": ObjectId(user_id),
 
1658
 
1659
  # Display Post-class Analytics
1660
  display_postclass_analytics(session, course_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1661
 
1662
  def upload_preclass_materials(session_id, course_id):
1663
  """Upload pre-class materials and manage external resources for a session"""
 
1888
  else:
1889
  st.error("Error submitting quiz. Please try again.")
1890
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1891
  def display_session_content(student_id, course_id, session, username, user_type):
1892
  st.title(f"{session['title']}")
1893
 
1894
  # Check if the date is a string or a datetime object
1895
  if isinstance(session['date'], str):
 
1896
  session_date = datetime.fromisoformat(session['date'])
1897
  else:
1898
  session_date = session['date']
 
1902
  st.markdown(f"**Date:** {format_datetime(session_date)}")
1903
  st.markdown(f"**Course Name:** {course_name}")
1904
 
 
 
1905
  if user_type == 'student':
 
1906
  tabs = st.tabs([
1907
  "Pre-class Work",
1908
  "In-class Work",
 
1922
  with tabs[3]:
1923
  display_quiz_tab(student_id, course_id, session['session_id'])
1924
  with tabs[4]:
1925
+ display_subjective_test_tab(student_id, course_id, session['session_id']) # Added this line
1926
  with tabs[5]:
1927
+ #display_group_work_tab(session, student_id)
1928
+ st.info("End term content will be available soon.")
1929
  with tabs[6]:
1930
  st.subheader("End Terms")
1931
  st.info("End term content will be available soon.")
 
 
 
1932
  else: # faculty user
 
1933
  tabs = st.tabs([
1934
  "Pre-class Work",
1935
  "In-class Work",
 
1937
  "Pre-class Analytics",
1938
  "In-class Analytics",
1939
  "Post-class Analytics",
1940
+ "Rubrics",
1941
+ "End Terms",
1942
+ "Evaluate Subjective Tests" # New tab for evaluation
1943
  ])
1944
  with tabs[0]:
1945
  upload_preclass_materials(session['session_id'], course_id)
 
1954
  with tabs[5]:
1955
  display_postclass_analytics(session, course_id)
1956
  with tabs[6]:
1957
+ display_rubrics_tab(session, course_id)
1958
+ with tabs[7]:
1959
  st.subheader("End Terms")
1960
  st.info("End term content will be available soon.")
1961
+ with tabs[8]: # New tab for evaluation
1962
+ display_evaluation_to_faculty(session['session_id'], student_id, course_id)
1963
+
1964
  def parse_model_response(response_text):
1965
  """Enhanced parser for model responses with better error handling.
1966
 
 
2058
  print(f"Error generating questions: {str(e)}")
2059
  return None
2060
 
2061
+ def save_subjective_test(course_id, session_id, title, questions):
2062
+ """Save subjective test to database with proper ID handling"""
 
 
 
 
 
 
 
 
 
 
2063
  try:
2064
+ # Ensure proper string format for IDs
2065
+ # course_id = course_id
2066
+ # session_id = session_id
 
 
 
 
 
 
2067
 
2068
+ # Format questions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2069
  formatted_questions = []
2070
  for q in questions:
2071
  formatted_question = {
2072
  "question": q["question"],
2073
+ "expected_points": [],
2074
+ "difficulty_level": "medium",
2075
+ "suggested_time": "5 minutes"
2076
  }
2077
  formatted_questions.append(formatted_question)
2078
 
 
2081
  "session_id": session_id,
2082
  "title": title,
2083
  "questions": formatted_questions,
 
2084
  "created_at": datetime.utcnow(),
2085
  "status": "active",
2086
  "submissions": []
 
2089
  result = subjective_tests_collection.insert_one(test_data)
2090
  return result.inserted_id
2091
  except Exception as e:
2092
+ print(f"Error saving test: {e}")
2093
  return None
2094
 
2095
+ def submit_subjective_test(test_id, student_id, answers):
2096
+ """Submit test answers with proper ID handling"""
2097
  try:
2098
+ # Ensure IDs are strings
2099
+ test_id = str(test_id)
2100
+ student_id = str(student_id)
2101
+
2102
+ # Create submission document
2103
+ submission = {
2104
  "student_id": student_id,
2105
+ "answers": answers,
2106
+ "submitted_at": datetime.utcnow(),
2107
+ "status": "submitted"
2108
  }
2109
 
2110
+ # Update test document with new submission
2111
  result = subjective_tests_collection.update_one(
2112
+ {"_id": ObjectId(test_id)},
2113
+ {"$push": {"submissions": submission}}
 
 
 
 
2114
  )
2115
 
2116
+ return result.modified_count > 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2117
  except Exception as e:
2118
+ print(f"Error submitting test: {e}")
2119
  return False
2120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2121
  def display_subjective_test_tab(student_id, course_id, session_id):
2122
  """Display subjective tests for students"""
2123
  st.header("Subjective Tests")
2124
+
2125
  try:
 
2126
  subjective_tests = list(subjective_tests_collection.find({
2127
  "course_id": course_id,
2128
  "session_id": session_id,
 
2141
  if sub['student_id'] == str(student_id)),
2142
  None
2143
  )
2144
+
2145
  if existing_submission:
2146
  st.success("Test completed! Your answers have been submitted.")
2147
  st.subheader("Your Answers")
 
2149
  st.markdown(f"**Question {i+1}:** {test['questions'][i]['question']}")
2150
  st.markdown(f"**Your Answer:** {ans}")
2151
  st.markdown("---")
2152
+
2153
  # Display analysis
2154
+ analysis = subjective_test_analysis_collection.find_one({
2155
+ "test_id": test['_id'],
2156
+ "student_id": str(student_id)
2157
+ })
2158
+ if analysis:
2159
+ st.subheader("Answer Analysis")
2160
+ st.markdown("### Evidence-Based Feedback")
2161
+ st.markdown(analysis.get('content_analysis', 'No analysis available'))
2162
+ analyzed_at = analysis.get('analyzed_at')
2163
+ if analyzed_at:
2164
+ st.caption(f"Analysis performed at: {analyzed_at.strftime('%Y-%m-%d %H:%M:%S UTC')}")
2165
  else:
2166
  st.write("Please write your answers:")
2167
  with st.form(key=f"subjective_test_form_{test['_id']}"):
 
2192
  except Exception as e:
2193
  print(f"Error in display_subjective_test_tab: {str(e)}", flush=True)
2194
  st.error("An error occurred while loading the tests. Please try again later.")
 
 
 
 
 
 
 
 
 
2195
 
2196
+ def review_and_edit_analysis(test_id, student_id):
2197
+ """Allow faculty to review and edit the generated analysis before storing it"""
2198
+ analysis = st.session_state.get('llm_analysis')
2199
+ if not analysis:
2200
+ st.error("No analysis available for review.")
2201
+ return
2202
+
2203
+ st.header("Review and Edit Analysis")
2204
+ content_analysis = st.text_area("Content Analysis", value=analysis['content_analysis'], height=300)
2205
+ correctness_score = st.number_input("Correctness Score", value=analysis['correctness_score'], min_value=0, max_value=10)
2206
+
2207
+ if st.button("Save Analysis"):
2208
+ # Store the edited analysis in MongoDB
2209
+ analysis_results = {
2210
+ "test_id": test_id,
2211
+ "student_id": student_id,
2212
+ "content_analysis": content_analysis,
2213
+ "analyzed_at": datetime.utcnow(),
2214
+ "correctness_score": correctness_score
2215
+ }
2216
+ subjective_test_analysis_collection.insert_one(analysis_results)
2217
+ st.success("Analysis saved successfully!")
2218
+ del st.session_state['llm_analysis'] # Clear the temporary analysis