omkar-surve126 commited on
Commit
afd5874
·
verified ·
1 Parent(s): d45297d

Update session_page.py

Browse files
Files changed (1) hide show
  1. session_page.py +705 -119
session_page.py CHANGED
@@ -18,9 +18,10 @@ 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
- 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,8 +30,6 @@ import numpy as np
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,7 +40,6 @@ db = client["novascholar_db"]
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,6 +132,66 @@ def get_current_user():
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,29 +672,41 @@ def display_post_class_content(session, student_id, course_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,9 +714,11 @@ def display_post_class_content(session, student_id, course_id):
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,11 +726,13 @@ def display_post_class_content(session, student_id, course_id):
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,7 +869,186 @@ def display_post_class_content(session, student_id, course_id):
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,6 +1164,9 @@ def display_inclass_analytics(session, course_id):
911
  "_id": {"$nin": respondents}
912
  }))
913
 
 
 
 
914
  if non_participants:
915
  st.markdown("#### Students Who Haven't Participated")
916
  non_participant_data = [{
@@ -949,23 +1205,24 @@ def display_inclass_analytics(session, course_id):
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,7 +1231,7 @@ def display_postclass_analytics(session, 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,25 +1240,43 @@ def display_postclass_analytics(session, course_id):
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,6 +1933,34 @@ def display_session_analytics(session, course_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,11 +2191,75 @@ def display_quiz_tab(student_id, course_id, session_id):
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,7 +2269,10 @@ def display_session_content(student_id, course_id, session, username, user_type)
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,14 +2292,18 @@ def display_session_content(student_id, course_id, session, username, user_type)
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,9 +2311,7 @@ def display_session_content(student_id, course_id, session, username, user_type)
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,13 +2326,9 @@ def display_session_content(student_id, course_id, session, username, user_type)
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,21 +2426,124 @@ def generate_questions(context, num_questions, session_title, session_descriptio
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,6 +2552,7 @@ def save_subjective_test(course_id, session_id, title, questions):
2081
  "session_id": session_id,
2082
  "title": title,
2083
  "questions": formatted_questions,
 
2084
  "created_at": datetime.utcnow(),
2085
  "status": "active",
2086
  "submissions": []
@@ -2089,40 +2561,148 @@ def save_subjective_test(course_id, session_id, title, questions):
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,7 +2721,7 @@ def display_subjective_test_tab(student_id, course_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,19 +2729,9 @@ def display_subjective_test_tab(student_id, course_id, session_id):
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,27 +2762,43 @@ def display_subjective_test_tab(student_id, course_id, session_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
 
 
 
 
 
 
 
 
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
  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
  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
  # 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
  )
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
 
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
  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
  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
  "_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
  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
  # 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
  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
 
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
  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
  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
  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
  "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
  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
  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
  "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
  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
  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
  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
  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}")