Harshal Vhatkar commited on
Commit
cca73d9
·
1 Parent(s): 1586102

add course creation and other new features

Browse files
Files changed (5) hide show
  1. create_course.py +272 -0
  2. file_upload_vectorize.py +2 -2
  3. main.py +334 -58
  4. pre_class_analytics.py +850 -0
  5. session_page.py +257 -16
create_course.py ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timedelta
2
+ import os
3
+ from typing import Dict, List, Any
4
+ from pymongo import MongoClient
5
+ import requests
6
+ import uuid
7
+ import openai
8
+ from openai import OpenAI
9
+ import streamlit as st
10
+ from bson import ObjectId
11
+ from dotenv import load_dotenv
12
+ import json
13
+
14
+ load_dotenv()
15
+ MONGODB_URI = os.getenv("MONGO_URI")
16
+ PERPLEXITY_API_KEY = os.getenv("PERPLEXITY_KEY")
17
+ OPENAI_API_KEY = os.getenv("OPENAI_KEY")
18
+
19
+ client = MongoClient(MONGODB_URI)
20
+ db = client['novascholar_db']
21
+ courses_collection = db['courses']
22
+
23
+ def generate_perplexity_response(api_key, course_name):
24
+ headers = {
25
+ "accept": "application/json",
26
+ "content-type": "application/json",
27
+ "authorization": f"Bearer {api_key}"
28
+ }
29
+
30
+ prompt = f"""
31
+ You are an expert educational AI assistant specializing in curriculum design and instructional planning. Your task is to generate comprehensive, academically rigorous course structures for undergraduate level education.
32
+
33
+ Please generate a detailed course structure for the course {course_name} in JSON format following these specifications:
34
+
35
+ 1. The course structure should be appropriate for a full semester (14-16 weeks)
36
+ 2. Each module should be designed for 2-4 weeks of instruction
37
+ 3. Follow standard academic practices and nomenclature
38
+ 4. Ensure progressive complexity from foundational to advanced concepts
39
+ 5. The course_title should exactly match the course name provided in the prompt. No additional information should be included in the course_title field.
40
+ 6: Ensure that the property names are enclosed in double quotes (") and followed by a colon (:), and the values are enclosed in double quotes (").
41
+ 7. **DO NOT INCLUDE THE WORD JSON IN THE OUTPUT STRING, DO NOT INCLUDE BACKTICKS (```) IN THE OUTPUT, AND DO NOT INCLUDE ANY OTHER TEXT, OTHER THAN THE ACTUAL JSON RESPONSE. START THE RESPONSE STRING WITH AN OPEN CURLY BRACE {{ AND END WITH A CLOSING CURLY BRACE }}.**
42
+
43
+
44
+ The JSON response should follow this structure:
45
+ {{
46
+ "course_title": "string",
47
+ "course_description": "string",
48
+ "modules": [
49
+ {{
50
+ "module_title": "string",
51
+ "sub_modules": [
52
+ {{
53
+ "title": "string",
54
+ "topics": [string],
55
+ }}
56
+ ]
57
+ }}
58
+ ]
59
+ }}
60
+
61
+ Example response:
62
+ {{
63
+ "course_title": "Advanced Natural Language Processing",
64
+ "course_descriptio": "An advanced course covering modern approaches to NLP using deep learning, with focus on transformer architectures and their applications.",
65
+ "modules": [
66
+ {{
67
+ "module_title": "Foundations of Modern NLP",
68
+ "sub_modules": [
69
+ {{
70
+ "title": "Attention Mechanism",
71
+ "topics": [
72
+ "Self-attention",
73
+ "Multi-head attention",
74
+ "Positional encoding"
75
+ ]
76
+ }}
77
+ ]
78
+ }}
79
+ ]
80
+ }}
81
+ """
82
+
83
+ messages = [
84
+ {
85
+ "role": "system",
86
+ "content": (
87
+ "You are an expert educational AI assistant specializing in course design and curriculum planning. "
88
+ "Your task is to generate accurate, detailed, and structured educational content for undergraduate-level and post-graduate-level courses. "
89
+ "Provide detailed and accurate information tailored to the user's prompt."
90
+ "Ensure that the responses are logical, follow standard academic practices, and include realistic concepts relevant to the course."
91
+ ),
92
+ },
93
+ {
94
+ "role": "user",
95
+ "content": prompt
96
+ },
97
+ ]
98
+ try:
99
+ client = OpenAI(api_key=api_key, base_url="https://api.perplexity.ai")
100
+ response = client.chat.completions.create(
101
+ model="llama-3.1-sonar-small-128k-online",
102
+ messages=messages
103
+ )
104
+ content = response.choices[0].message.content
105
+ return content
106
+ except Exception as e:
107
+ st.error(f"Failed to fetch data from Perplexity API: {e}")
108
+ return ""
109
+
110
+ def get_new_course_id():
111
+ """Generate a new course ID by incrementing the last course ID"""
112
+ last_course = courses_collection.find_one(sort=[("course_id", -1)])
113
+ if last_course:
114
+ last_course_id = int(last_course["course_id"][2:])
115
+ new_course_id = f"CS{last_course_id + 1}"
116
+ else:
117
+ new_course_id = "CS101"
118
+ return new_course_id
119
+
120
+
121
+ def create_course(course_name, start_date, duration_weeks):
122
+ # Generate course overview
123
+ # overview_prompt = f"""Generate an overview for the undergraduate course {course_name}
124
+ # Include all relevant concepts and key topics covered in a typical curriculum.
125
+ # The response should be concise (300-400 words). Ensure that your response is in a valid JSON format."""
126
+
127
+ # overview_prompt2 = f"""Generate an overview for the undergraduate course {course_name}.
128
+ # The overview should include:
129
+ # The course title, a detailed course description,
130
+ # a division of all relevant concepts and key topics into 4-6 logical modules,
131
+ # capturing the flow and structure of a typical curriculum.
132
+ # Ensure the response adheres to the following JSON format:
133
+ # {{
134
+ # 'overview': 'string',
135
+ # 'modules': [
136
+ # {{
137
+ # 'name': 'string',
138
+ # 'description': 'string'
139
+ # }}
140
+ # ]
141
+ # }}
142
+ # overview: A detailed description of the course.
143
+ # modules: An array of 4-6 objects, each representing a logical module with a name and a brief description
144
+ # **DO NOT INCLUDE THE WORD JSON IN THE OUTPUT STRING, DO NOT INCLUDE BACKTICKS (```) IN THE OUTPUT, AND DO NOT INCLUDE ANY OTHER TEXT, OTHER THAN THE ACTUAL JSON RESPONSE. START THE RESPONSE STRING WITH AN OPEN CURLY BRACE {{ AND END WITH A CLOSING CURLY BRACE }}"""
145
+
146
+ # course_overview = generate_perplexity_response(PERPLEXITY_API_KEY, overview_prompt2)
147
+ # # print(course_overview)
148
+ # course_overview_store = course_overview
149
+ # # print(course_overview_store)
150
+ # # Generate modules
151
+ # # modules_prompt = f"Based on this overview: {course_overview}\nCreate 4-6 logical modules for the course, each module should group related concepts and each module may include reference books if applicable"
152
+ # sub_modules_prompt = f"""Using the provided modules in the overview {course_overview_store}, generate 2-3 submodules for each module.
153
+ # Each submodule should represent a cohesive subset of the module's topics, logically organized for teaching purposes.
154
+ # Ensure the response adheres to the following JSON format:
155
+ # {
156
+ # 'modules': [
157
+ # {
158
+ # 'name': 'string',
159
+ # 'sub_modules': [
160
+ # {
161
+ # 'name': 'string',
162
+ # 'description': 'string'
163
+ # }
164
+ # ]
165
+ # }
166
+ # ]
167
+ # }
168
+ # modules: An array where each object contains the name of the module and its corresponding sub_modules.
169
+ # sub_modules: An array of 2-3 objects for each module, each having a name and a brief description."
170
+ # **DO NOT INCLUDE THE WORD JSON IN THE OUTPUT STRING, DO NOT INCLUDE BACKTICKS (```) IN THE OUTPUT, AND DO NOT INCLUDE ANY OTHER TEXT, OTHER THAN THE ACTUAL JSON RESPONSE. START THE RESPONSE STRING WITH AN OPEN CURLY BRACE {{ AND END WITH A CLOSING CURLY BRACE }}
171
+ # """
172
+ # sub_modules = generate_perplexity_response(PERPLEXITY_API_KEY, sub_modules_prompt)
173
+
174
+ # # modules_response = generate_perplexity_response(modules_prompt)
175
+ # print(sub_modules)
176
+
177
+ # total_sessions = duration_weeks * sessions_per_week
178
+
179
+ course_plan = generate_perplexity_response(PERPLEXITY_API_KEY, course_name)
180
+ course_plan_json = json.loads(course_plan)
181
+
182
+ # Generate sessions for each module
183
+ all_sessions = []
184
+ for module in course_plan_json['modules']:
185
+ for sub_module in module['sub_modules']:
186
+ for topic in sub_module['topics']:
187
+ session = create_session(
188
+ title=topic,
189
+ date=start_date,
190
+ module_name=module['module_title']
191
+ )
192
+ # print(session)
193
+ all_sessions.append(session)
194
+ start_date += timedelta(days=7) # Next session after a week
195
+
196
+ # sample_sessions = [
197
+ # {'session_id': ObjectId('6767d0bbad8316ac358def25'), 'title': 'What is Generative AI?', 'date': datetime(2024, 12, 22, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 504599), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
198
+ # {'session_id': ObjectId('6767d0bbad8316ac358def26'), 'title': 'History and Evolution of AI', 'date': datetime(2024, 12, 29, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 504599), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
199
+ # {'session_id': ObjectId('6767d0bbad8316ac358def27'), 'title': 'Types of Generative AI (e.g., GANs, VAEs, LLMs)', 'date': datetime(2025, 1, 5, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 505626), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
200
+ # {'session_id': ObjectId('6767d0bbad8316ac358def28'), 'title': 'Overview of popular GenAI tools (e.g., ChatGPT, Claude, Google Gemini)', 'date': datetime(2025, 1, 12, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 506559), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
201
+ # {'session_id': ObjectId('6767d0bbad8316ac358def29'), 'title': 'Frameworks for building GenAI models (e.g., TensorFlow, PyTorch)', 'date': datetime(2025, 1, 19, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 506559), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
202
+ # {'session_id': ObjectId('6767d0bbad8316ac358def2a'), 'title': 'Integration with other AI technologies', 'date': datetime(2025, 1, 26, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 507612), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
203
+ # {'session_id': ObjectId('6767d0bbad8316ac358def2b'), 'title': 'Text-to-text models (e.g., GPT-3, BERT)', 'date': datetime(2025, 2, 2, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 508512), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
204
+ # {'session_id': ObjectId('6767d0bbad8316ac358def2c'), 'title': 'Text generation for content creation and marketing', 'date': datetime(2025, 2, 9, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 508512), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
205
+ # {'session_id': ObjectId('6767d0bbad8316ac358def2d'), 'title': 'Chatbots and conversational interfaces', 'date': datetime(2025, 2, 16, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 509612), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
206
+ # {'session_id': ObjectId('6767d0bbad8316ac358def2e'), 'title': 'Generative Adversarial Networks (GANs)', 'date': datetime(2025, 2, 23, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 509612), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
207
+ # {'session_id': ObjectId('6767d0bbad8316ac358def2f'), 'title': 'Variational Autoencoders (VAEs)', 'date': datetime(2025, 3, 2, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 510612), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
208
+ # {'session_id': ObjectId('6767d0bbad8316ac358def30'), 'title': 'Applications in art, design, and media', 'date': datetime(2025, 3, 9, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 511497), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
209
+ # {'session_id': ObjectId('6767d0bbad8316ac358def31'), 'title': 'Understanding prompt design principles', 'date': datetime(2025, 3, 16, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 511497), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
210
+ # {'session_id': ObjectId('6767d0bbad8316ac358def33'), 'title': 'Advanced techniques for fine-tuning models', 'date': datetime(2025, 3, 30, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 512514), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
211
+ # {'session_id': ObjectId('6767d0bbad8316ac358def34'), 'title': 'Ethical implications of AI-generated content', 'date': datetime(2025, 4, 6, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 513613), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
212
+ # {'session_id': ObjectId('6767d0bbad8316ac358def35'), 'title': 'Addressing bias in AI models', 'date': datetime(2025, 4, 13, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 514639), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
213
+ # {'session_id': ObjectId('6767d0bbad8316ac358def36'), 'title': 'Regulatory frameworks and guidelines', 'date': datetime(2025, 4, 20, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 514639), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
214
+ # {'session_id': ObjectId('6767d0bbad8316ac358def37'), 'title': 'Case studies from various industries (e.g., marketing, healthcare, finance)', 'date': datetime(2025, 4, 27, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 515610), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
215
+ # {'session_id': ObjectId('6767d0bbad8316ac358def38'), 'title': 'Success stories and challenges faced by companies using GenAI', 'date': datetime(2025, 5, 4, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 515610), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
216
+ # {'session_id': ObjectId('6767d0bbad8316ac358def39'), 'title': 'Guidelines for developing a GenAI project', 'date': datetime(2025, 5, 11, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 516614), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
217
+ # {'session_id': ObjectId('6767d0bbad8316ac358def3a'), 'title': 'Tools and resources for project implementation', 'date': datetime(2025, 5, 18, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 516614), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
218
+ # {'session_id': ObjectId('6767d0bbad8316ac358def3b'), 'title': 'Best practices for testing and deployment', 'date': datetime(2025, 5, 25, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 517563), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}}
219
+ # ]
220
+
221
+ # small_sample_sessions = [
222
+ # {'session_id': ObjectId('6767d0bbad8316ac358def25'), 'title': 'What is Generative AI?', 'date': datetime(2024, 12, 22, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 504599), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
223
+ # {'session_id': ObjectId('6767d0bbad8316ac358def26'), 'title': 'History and Evolution of AI', 'date': datetime(2024, 12, 29, 14, 11, 27, 153899), 'status': 'upcoming', 'created_at': datetime(2024, 12, 22, 8, 41, 31, 504599), 'pre_class': {'resources': [], 'completion_required': True}, 'in_class': {'quiz': [], 'polls': []}, 'post_class': {'assignments': []}},
224
+ # ]
225
+
226
+
227
+ # print(all_sessions)
228
+
229
+ print("Number of sessions:", len(all_sessions))
230
+ # Create course document
231
+ # course_description = course_plan_json['course_description']
232
+ # course_doc = {
233
+ # "course_id": get_new_course_id(),
234
+ # "title": course_name,
235
+ # "description": course_description,
236
+ # "faculty": faculty_name,
237
+ # "faculty_id": faculty_id,
238
+ # "duration": f"{duration_weeks} weeks",
239
+ # "created_at": datetime.utcnow(),
240
+ # "sessions": all_sessions
241
+ # }
242
+ # try:
243
+ # courses_collection.insert_one(course_doc)
244
+ # except Exception as e:
245
+ # st.error(f"Failed to insert course data into the database: {e}")
246
+
247
+ # print(course_plan)
248
+
249
+ def create_session(title: str, date: datetime, module_name: str):
250
+ """Create a session document with pre-class, in-class, and post-class components."""
251
+ return {
252
+ "session_id": ObjectId(),
253
+ "title": title,
254
+ "date": date,
255
+ "status": "upcoming",
256
+ "created_at": datetime.utcnow(),
257
+ "pre_class": {
258
+ "resources": [],
259
+ "completion_required": True
260
+ },
261
+ "in_class": {
262
+ "quiz": [],
263
+ "polls": []
264
+ },
265
+ "post_class": {
266
+ "assignments": []
267
+ }
268
+ }
269
+
270
+ # Usage example:
271
+ if __name__ == "__main__":
272
+ create_course("Introduction to Data Analytics", datetime.now(), 2)
file_upload_vectorize.py CHANGED
@@ -124,12 +124,12 @@ def get_embedding(text):
124
  return response.data[0].embedding
125
 
126
  def create_vector_store(text, resource_id):
127
- resource_object_id = ObjectId(resource_id)
128
  document = Document(text=text)
129
  embedding = get_embedding(text)
130
 
131
  vector_data = {
132
- "resource_id": resource_object_id,
133
  "vector": embedding,
134
  "text": text,
135
  "created_at": datetime.utcnow()
 
124
  return response.data[0].embedding
125
 
126
  def create_vector_store(text, resource_id):
127
+ # resource_object_id = ObjectId(resource_id)
128
  document = Document(text=text)
129
  embedding = get_embedding(text)
130
 
131
  vector_data = {
132
+ "resource_id": resource_id,
133
  "vector": embedding,
134
  "text": text,
135
  "created_at": datetime.utcnow()
main.py CHANGED
@@ -1,5 +1,5 @@
1
  import streamlit as st
2
- from datetime import datetime, date, time
3
  from pathlib import Path
4
  from utils.sample_data import SAMPLE_COURSES, SAMPLE_SESSIONS
5
  from session_page import display_session_content
@@ -14,9 +14,14 @@ from werkzeug.security import generate_password_hash, check_password_hash
14
  import os
15
  from openai import OpenAI
16
  from dotenv import load_dotenv
17
-
 
 
18
  client = OpenAI(api_key=os.getenv("OPENAI_KEY"))
 
19
 
 
 
20
 
21
  def get_research_papers(query):
22
  """Get research paper recommendations based on query"""
@@ -74,7 +79,12 @@ def init_session_state():
74
  st.session_state.selected_course = None
75
  if "show_create_course_form" not in st.session_state:
76
  st.session_state.show_create_course_form = False
77
-
 
 
 
 
 
78
 
79
  def login_user(username, password, user_type):
80
  """Login user based on credentials"""
@@ -127,7 +137,18 @@ def get_courses(username, user_type):
127
  courses = courses_collection2.find(
128
  {"course_id": {"$in": enrolled_course_ids}}
129
  )
130
- # course_titles = [course['title'] for course in courses]
 
 
 
 
 
 
 
 
 
 
 
131
  return list(courses)
132
  elif user_type == "faculty":
133
  faculty = faculty_collection.find_one({"full_name": username})
@@ -497,63 +518,181 @@ def register_page():
497
 
498
  # Create Course feature
499
  def create_course_form(faculty_name, faculty_id):
500
- """Display form to create a new course"""
501
  st.title("Create New Course")
502
- faculty = faculty_collection.find_one({"_id": faculty_id})
503
- if not faculty:
504
- st.error("Faculty not found")
505
- return
506
- faculty_str_id = faculty["TID"]
507
-
508
- with st.form("create_course_form"):
509
- course_title = st.text_input("Course Title")
510
- course_description = st.text_area("Course Description")
511
- start_date = st.date_input("Start Date")
512
- end_date = st.date_input("End Date")
513
- duration = -(
514
- -((end_date - start_date).days) // 7
515
- ) # Ceiling division to round up to the next week
516
-
517
- if st.form_submit_button("Create Course"):
518
- new_course_id = get_new_course_id()
519
- course = {
520
- "course_id": new_course_id,
521
- "title": course_title,
522
- "description": course_description,
523
- "faculty": faculty_name,
524
- "faculty_id": faculty_str_id,
525
- # "start_date": start_date.isoformat(),
526
- # "end_date": end_date.isoformat(),
527
- "start_date": datetime.combine(
528
- start_date, datetime.min.time()
529
- ), # Store as datetime
530
- "end_date": datetime.combine(
531
- end_date, datetime.min.time()
532
- ), # Store as datetime
533
- "duration": f"{duration} weeks",
534
- "created_at": datetime.utcnow(),
535
- "sessions": [],
536
- }
537
-
538
- # Insert course into courses collection
539
- courses_collection2.insert_one(course)
540
-
541
- # Update faculty's courses_taught array
542
- faculty_collection.update_one(
543
- {"_id": st.session_state.user_id},
544
- {
545
- "$push": {
546
- "courses_taught": {
547
- "course_id": new_course_id,
548
- "title": course_title,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
  }
550
- }
551
- },
552
- )
553
 
554
- st.success(f"Course created successfully with ID: {new_course_id}")
555
- st.session_state.show_create_course_form = False
556
- st.rerun()
 
 
 
 
 
 
 
 
 
557
 
558
 
559
  from research_assistant_dashboard import display_research_assistant_dashboard
@@ -561,6 +700,127 @@ from research_assistant_dashboard import display_research_assistant_dashboard
561
  from goals2 import display_analyst_dashboard
562
 
563
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  def main_dashboard():
565
  if st.session_state.user_type == "research_assistant":
566
  display_research_assistant_dashboard()
@@ -581,6 +841,20 @@ def main_dashboard():
581
  st.session_state.username, st.session_state.user_type
582
  )
583
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
  if st.session_state.user_type == "faculty":
585
  if st.button(
586
  "Create New Course", key="create_course", use_container_width=True
@@ -631,6 +905,8 @@ def main_dashboard():
631
  create_course_form(st.session_state.username, st.session_state.user_id)
632
  elif st.session_state.get("show_create_session_form"):
633
  create_session_form(selected_course_id)
 
 
634
  else:
635
  # Main content
636
  if "selected_session" in st.session_state:
 
1
  import streamlit as st
2
+ from datetime import datetime, date, time, timedelta
3
  from pathlib import Path
4
  from utils.sample_data import SAMPLE_COURSES, SAMPLE_SESSIONS
5
  from session_page import display_session_content
 
14
  import os
15
  from openai import OpenAI
16
  from dotenv import load_dotenv
17
+ from create_course import create_course, courses_collection, generate_perplexity_response, PERPLEXITY_API_KEY
18
+ import json
19
+ from bson import ObjectId
20
  client = OpenAI(api_key=os.getenv("OPENAI_KEY"))
21
+ from dotenv import load_dotenv
22
 
23
+ load_dotenv()
24
+ # PERPLEXITY_API_KEY = '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'
25
 
26
  def get_research_papers(query):
27
  """Get research paper recommendations based on query"""
 
79
  st.session_state.selected_course = None
80
  if "show_create_course_form" not in st.session_state:
81
  st.session_state.show_create_course_form = False
82
+ if "show_create_session_form" not in st.session_state:
83
+ st.session_state.show_create_session_form = False
84
+ if "show_enroll_course_page" not in st.session_state:
85
+ st.session_state.show_enroll_course_page = False
86
+ if "course_to_enroll" not in st.session_state:
87
+ st.session_state.course_to_enroll = None
88
 
89
  def login_user(username, password, user_type):
90
  """Login user based on credentials"""
 
137
  courses = courses_collection2.find(
138
  {"course_id": {"$in": enrolled_course_ids}}
139
  )
140
+ # courses += courses_collection2.find(
141
+ # {"course_id": {"$in": enrolled_course_ids}}
142
+ # )
143
+ # # course_titles = [course['title'] for course in courses]
144
+ # return list(courses)
145
+ # courses_cursor1 = courses_collection.find(
146
+ # {"course_id": {"$in": enrolled_course_ids}}
147
+ # )
148
+ # courses_cursor2 = courses_collection2.find(
149
+ # {"course_id": {"$in": enrolled_course_ids}}
150
+ # )
151
+ # courses = list(courses_cursor1) + list(courses_cursor2)
152
  return list(courses)
153
  elif user_type == "faculty":
154
  faculty = faculty_collection.find_one({"full_name": username})
 
518
 
519
  # Create Course feature
520
  def create_course_form(faculty_name, faculty_id):
521
+ """Display enhanced form to create a new course with AI-generated content"""
522
  st.title("Create New Course")
523
+
524
+ if 'course_plan' not in st.session_state:
525
+ st.session_state.course_plan = None
526
+ if 'edit_mode' not in st.session_state:
527
+ st.session_state.edit_mode = False
528
+
529
+ # Initial Course Creation Form
530
+ if not st.session_state.course_plan:
531
+ with st.form("initial_course_form"):
532
+ col1, col2 = st.columns(2)
533
+ with col1:
534
+ course_name = st.text_input("Course Name", placeholder="e.g., Introduction to Computer Science")
535
+ faculty_info = st.text_input("Faculty", value=faculty_name, disabled=True)
536
+ with col2:
537
+ duration_weeks = st.number_input("Duration (weeks)", min_value=1, max_value=16, value=12)
538
+ start_date = st.date_input("Start Date")
539
+
540
+ generate_button = st.form_submit_button("Generate Course Structure", use_container_width=True)
541
+
542
+ if generate_button and course_name:
543
+ with st.spinner("Generating course structure..."):
544
+ try:
545
+ course_plan = generate_perplexity_response(PERPLEXITY_API_KEY, course_name)
546
+ # print(course_plan)
547
+ st.session_state.course_plan = json.loads(course_plan)
548
+ st.session_state.start_date = start_date
549
+ st.session_state.duration_weeks = duration_weeks
550
+ st.rerun()
551
+ except Exception as e:
552
+ st.error(f"Error generating course structure: {e}")
553
+
554
+ # Display and Edit Generated Course Content
555
+ if st.session_state.course_plan:
556
+ with st.expander("Course Overview", expanded=True):
557
+ if not st.session_state.edit_mode:
558
+ st.subheader(st.session_state.course_plan['course_title'])
559
+ st.write(st.session_state.course_plan['course_description'])
560
+ edit_button = st.button("Edit Course Details", use_container_width=True)
561
+ if edit_button:
562
+ st.session_state.edit_mode = True
563
+ st.rerun()
564
+ else:
565
+ with st.form("edit_course_details"):
566
+ st.session_state.course_plan['course_title'] = st.text_input(
567
+ "Course Title",
568
+ value=st.session_state.course_plan['course_title']
569
+ )
570
+ st.session_state.course_plan['course_description'] = st.text_area(
571
+ "Course Description",
572
+ value=st.session_state.course_plan['course_description']
573
+ )
574
+ if st.form_submit_button("Save Course Details"):
575
+ st.session_state.edit_mode = False
576
+ st.rerun()
577
+
578
+ # Display Modules and Sessions
579
+ st.subheader("Course Modules and Sessions")
580
+
581
+ start_date = st.session_state.start_date
582
+ current_date = start_date
583
+
584
+ all_sessions = []
585
+ for module_idx, module in enumerate(st.session_state.course_plan['modules']):
586
+ with st.expander(f"📚 Module {module_idx + 1}: {module['module_title']}", expanded=True):
587
+ # Edit module title
588
+ new_module_title = st.text_input(
589
+ f"Module {module_idx + 1} Title",
590
+ value=module['module_title'],
591
+ key=f"module_{module_idx}"
592
+ )
593
+ module['module_title'] = new_module_title
594
+
595
+ for sub_idx, sub_module in enumerate(module['sub_modules']):
596
+ st.markdown(f"### 📖 {sub_module['title']}")
597
+
598
+ # Create sessions for each topic
599
+ for topic_idx, topic in enumerate(sub_module['topics']):
600
+ session_key = f"session_{module_idx}_{sub_idx}_{topic_idx}"
601
+
602
+ with st.container():
603
+ col1, col2, col3 = st.columns([3, 2, 1])
604
+ with col1:
605
+ new_topic = st.text_input(
606
+ "Topic",
607
+ value=topic,
608
+ key=f"{session_key}_topic"
609
+ )
610
+ sub_module['topics'][topic_idx] = new_topic
611
+
612
+ with col2:
613
+ session_date = st.date_input(
614
+ "Session Date",
615
+ value=current_date,
616
+ key=f"{session_key}_date"
617
+ )
618
+
619
+ with col3:
620
+ session_status = st.selectbox(
621
+ "Status",
622
+ options=["upcoming", "in-progress", "completed"],
623
+ key=f"{session_key}_status"
624
+ )
625
+
626
+ # Create session object
627
+ session = {
628
+ "session_id": str(ObjectId()),
629
+ "title": new_topic,
630
+ "date": datetime.combine(session_date, datetime.min.time()),
631
+ "status": session_status,
632
+ "module_name": module['module_title'],
633
+ "created_at": datetime.utcnow(),
634
+ "pre_class": {
635
+ "resources": [],
636
+ "completion_required": True
637
+ },
638
+ "in_class": {
639
+ "quiz": [],
640
+ "polls": []
641
+ },
642
+ "post_class": {
643
+ "assignments": []
644
+ }
645
+ }
646
+ all_sessions.append(session)
647
+ current_date = session_date + timedelta(days=7)
648
+
649
+ new_course_id = get_new_course_id()
650
+ course_title = st.session_state.course_plan['course_title']
651
+ # Final Save Button
652
+ if st.button("Save Course", type="primary", use_container_width=True):
653
+ try:
654
+ course_doc = {
655
+ "course_id": new_course_id,
656
+ "title": course_title,
657
+ "description": st.session_state.course_plan['course_description'],
658
+ "faculty": faculty_name,
659
+ "faculty_id": faculty_id,
660
+ "duration": f"{st.session_state.duration_weeks} weeks",
661
+ "start_date": datetime.combine(st.session_state.start_date, datetime.min.time()),
662
+ "created_at": datetime.utcnow(),
663
+ "sessions": all_sessions
664
+ }
665
+
666
+ # Insert into database
667
+ courses_collection.insert_one(course_doc)
668
+
669
+ st.success("Course successfully created!")
670
+
671
+ # Update faculty collection
672
+ faculty_collection.update_one(
673
+ {"_id": st.session_state.user_id},
674
+ {
675
+ "$push": {
676
+ "courses_taught": {
677
+ "course_id": new_course_id,
678
+ "title": course_title,
679
+ }
680
  }
681
+ },
682
+ )
 
683
 
684
+ # Clear session state
685
+ st.session_state.course_plan = None
686
+ st.session_state.edit_mode = False
687
+
688
+ # Optional: Add a button to view the created course
689
+ if st.button("View Course"):
690
+ # Add navigation logic here
691
+ pass
692
+
693
+ except Exception as e:
694
+ st.error(f"Error saving course: {e}")
695
+
696
 
697
 
698
  from research_assistant_dashboard import display_research_assistant_dashboard
 
700
  from goals2 import display_analyst_dashboard
701
 
702
 
703
+ def enroll_in_course(course_id, course_title, student):
704
+ """Enroll a student in a course"""
705
+ if student:
706
+ courses = student.get("enrolled_courses", [])
707
+ if course_id not in [course["course_id"] for course in courses]:
708
+ course = courses_collection.find_one({"course_id": course_id})
709
+ if course:
710
+ courses.append(
711
+ {
712
+ "course_id": course["course_id"],
713
+ "title": course["title"],
714
+ }
715
+ )
716
+ students_collection.update_one(
717
+ {"_id": st.session_state.user_id},
718
+ {"$set": {"enrolled_courses": courses}},
719
+ )
720
+ st.success(f"Enrolled in course {course_title}")
721
+ else:
722
+ st.error("Course not found")
723
+ else:
724
+ st.warning("Already enrolled in this course")
725
+
726
+ # def enroll_in_course_page(course_id):
727
+ # """Enroll a student in a course"""
728
+ # student = students_collection.find_one({"_id": st.session_state.user_id})
729
+ # course_title = courses_collection.find_one({"course_id": course_id})["title"]
730
+
731
+ # course = courses_collection.find_one({"course_id": course_id})
732
+ # if course:
733
+ # st.title(course["title"])
734
+ # st.subheader("Course Description:")
735
+ # st.write(course["description"])
736
+ # st.write(f"Faculty: {course['faculty']}")
737
+ # st.write(f"Duration: {course['duration']}")
738
+
739
+ # st.title("Course Sessions")
740
+ # for session in course["sessions"]:
741
+ # st.write(f"Session: {session['title']}")
742
+ # st.write(f"Date: {session['date']}")
743
+ # st.write(f"Status: {session['status']}")
744
+ # st.write("----")
745
+ # else:
746
+ # st.error("Course not found")
747
+
748
+ # enroll_button = st.button("Enroll in Course", key="enroll_button", use_container_width=True)
749
+ # if enroll_button:
750
+ # enroll_in_course(course_id, course_title, student)
751
+ def enroll_in_course_page(course_id):
752
+ """Display an aesthetically pleasing course enrollment page"""
753
+ student = students_collection.find_one({"_id": st.session_state.user_id})
754
+ course = courses_collection.find_one({"course_id": course_id})
755
+
756
+ if not course:
757
+ st.error("Course not found")
758
+ return
759
+
760
+ # Create two columns for layout
761
+ col1, col2 = st.columns([2, 1])
762
+
763
+ with col1:
764
+ # Course header section
765
+ st.title(course["title"])
766
+ st.markdown(f"*{course['description']}*")
767
+
768
+ # Course details in an expander
769
+ with st.expander("Course Details", expanded=True):
770
+ st.markdown(f"👨‍🏫 **Faculty:** {course['faculty']}")
771
+ st.markdown(f"⏱️ **Duration:** {course['duration']}")
772
+
773
+ # Sessions in a clean card-like format
774
+ st.subheader("📚 Course Sessions")
775
+ for idx, session in enumerate(course["sessions"], 1):
776
+ with st.container():
777
+ st.markdown(f"""
778
+ ---
779
+ ### Session {idx}: {session['title']}
780
+ 🗓️ **Date:** {session['date']}
781
+ 📌 **Status:** {session['status']}
782
+ """)
783
+
784
+ with col2:
785
+ with st.container():
786
+ st.markdown("### Ready to Learn?")
787
+ st.markdown("Click below to enroll in this course")
788
+
789
+ # Check if already enrolled
790
+ courses = student.get("enrolled_courses", [])
791
+ is_enrolled = course_id in [c["course_id"] for c in courses]
792
+
793
+ if is_enrolled:
794
+ st.info("✅ You are already enrolled in this course")
795
+ else:
796
+ enroll_button = st.button(
797
+ "🎓 Enroll Now",
798
+ key="enroll_button",
799
+ use_container_width=True
800
+ )
801
+ if enroll_button:
802
+ enroll_in_course(course_id, course["title"], student)
803
+
804
+ def show_available_courses(username, user_type, user_id):
805
+ """Display available courses for enrollment"""
806
+ st.title("Available Courses")
807
+
808
+ courses = list(courses_collection2.find({}, {"course_id": 1, "title": 1}))
809
+ course_options = [
810
+ f"{course['title']} ({course['course_id']})" for course in courses
811
+ ]
812
+
813
+ selected_course = st.selectbox("Select a Course to Enroll", course_options)
814
+ # if selected_courses:
815
+ # for course in selected_courses:
816
+ # course_id = course.split("(")[-1][:-1]
817
+ # course_title = course.split(" (")[0]
818
+ # enroll_in_course(course_id, course_title, user_id)
819
+ # st.success("Courses enrolled successfully!")
820
+ if selected_course:
821
+ course_id = selected_course.split("(")[-1][:-1]
822
+ enroll_in_course_page(course_id)
823
+
824
  def main_dashboard():
825
  if st.session_state.user_type == "research_assistant":
826
  display_research_assistant_dashboard()
 
841
  st.session_state.username, st.session_state.user_type
842
  )
843
 
844
+ # Enroll in Courses
845
+ if st.session_state.user_type == "student":
846
+ if st.button(
847
+ "Enroll in a New Course", key="enroll_course", use_container_width=True
848
+ ):
849
+ st.session_state.show_enroll_course_page = True
850
+
851
+ # if st.session_state.show_enroll_course_form:
852
+ # courses = list(courses_collection.find({}, {"course_id": 1, "title": 1}))
853
+ # courses += list(courses_collection2.find({}, {"course_id": 1, "title": 1}))
854
+ # course_options = [f"{course['title']} ({course['course_id']})" for course in courses]
855
+ # course_to_enroll = st.selectbox("Available Courses", course_options)
856
+ # st.session_state.course_to_enroll = course_to_enroll
857
+
858
  if st.session_state.user_type == "faculty":
859
  if st.button(
860
  "Create New Course", key="create_course", use_container_width=True
 
905
  create_course_form(st.session_state.username, st.session_state.user_id)
906
  elif st.session_state.get("show_create_session_form"):
907
  create_session_form(selected_course_id)
908
+ elif st.session_state.get("show_enroll_course_page"):
909
+ show_available_courses(st.session_state.username, st.session_state.user_type, st.session_state.user_id)
910
  else:
911
  # Main content
912
  if "selected_session" in st.session_state:
pre_class_analytics.py ADDED
@@ -0,0 +1,850 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from bson import ObjectId
3
+ from pymongo import MongoClient
4
+ import pandas as pd
5
+ import numpy as np
6
+ from datetime import datetime
7
+ from dotenv import load_dotenv
8
+ import os
9
+ from typing import List, Dict, Any
10
+ from transformers import pipeline
11
+ from textstat import flesch_reading_ease
12
+ from collections import Counter
13
+ import logging
14
+ import spacy
15
+ import json
16
+
17
+ # Load chat histories from JSON file
18
+ all_chat_histories = []
19
+ with open(r'D:\ML_Projects\CSR Project\NOVAScholarProject\NovaScholar\all_chat_histories2.json', 'r') as file:
20
+ all_chat_histories = json.load(file)
21
+
22
+ load_dotenv()
23
+ MONGO_URI = os.getenv("MONGO_URI")
24
+ client = MongoClient(MONGO_URI)
25
+ db = client['novascholar_db']
26
+
27
+ chat_history_collection = db['chat_history']
28
+
29
+ # def get_chat_history(user_id, session_id):
30
+ # query = {
31
+ # "user_id": ObjectId(user_id),
32
+ # "session_id": session_id,
33
+ # "timestamp": {"$lte": datetime.utcnow()}
34
+ # }
35
+ # result = chat_history_collection.find(query)
36
+ # return list(result)
37
+
38
+ # if __name__ == "__main__":
39
+ # user_ids = ["6738b70cc97dffb641c7d158", "6738b7b33f648a9224f7aa69"]
40
+ # session_ids = ["S104"]
41
+ # for user_id in user_ids:
42
+ # for session_id in session_ids:
43
+ # result = get_chat_history(user_id, session_id)
44
+ # print(result)
45
+
46
+ # Configure logging
47
+ logging.basicConfig(level=logging.INFO)
48
+ logger = logging.getLogger(__name__)
49
+
50
+ class NovaScholarAnalytics:
51
+ def __init__(self):
52
+ # Initialize NLP components
53
+ self.nlp = spacy.load("en_core_web_sm")
54
+ self.sentiment_analyzer = pipeline("sentiment-analysis", model="finiteautomata/bertweet-base-sentiment-analysis", top_k=None)
55
+
56
+ # Define question words for detecting questions
57
+ self.question_words = {"what", "why", "how", "when", "where", "which", "who", "whose", "whom"}
58
+
59
+ # Define question categories
60
+ self.question_categories = {
61
+ 'conceptual': {'what', 'define', 'describe', 'explain'},
62
+ 'procedural': {'how', 'steps', 'procedure', 'process'},
63
+ 'reasoning': {'why', 'reason', 'cause', 'effect'},
64
+ 'clarification': {'clarify', 'mean', 'difference', 'between'}
65
+ }
66
+
67
+ def _categorize_questions(self, questions_df: pd.DataFrame) -> Dict[str, int]:
68
+ """
69
+ Categorize questions into different types based on their content.
70
+
71
+ Args:
72
+ questions_df: DataFrame containing questions
73
+
74
+ Returns:
75
+ Dictionary with question categories and their counts
76
+ """
77
+ categories_count = {
78
+ 'conceptual': 0,
79
+ 'procedural': 0,
80
+ 'reasoning': 0,
81
+ 'clarification': 0,
82
+ 'other': 0
83
+ }
84
+
85
+ for _, row in questions_df.iterrows():
86
+ prompt_lower = row['prompt'].lower()
87
+ categorized = False
88
+
89
+ for category, keywords in self.question_categories.items():
90
+ if any(keyword in prompt_lower for keyword in keywords):
91
+ categories_count[category] += 1
92
+ categorized = True
93
+ break
94
+
95
+ if not categorized:
96
+ categories_count['other'] += 1
97
+
98
+ return categories_count
99
+
100
+
101
+ def _identify_frustration(self, df: pd.DataFrame) -> List[str]:
102
+ """
103
+ Identify signs of frustration in student messages.
104
+
105
+ Args:
106
+ df: DataFrame containing messages
107
+
108
+ Returns:
109
+ List of topics/areas where frustration was detected
110
+ """
111
+ frustration_indicators = [
112
+ "don't understand", "confused", "difficult", "hard to",
113
+ "not clear", "stuck", "help", "can't figure"
114
+ ]
115
+
116
+ frustrated_messages = df[
117
+ df['prompt'].str.lower().str.contains('|'.join(frustration_indicators), na=False)
118
+ ]
119
+
120
+ if len(frustrated_messages) == 0:
121
+ return []
122
+
123
+ # Extract topics from frustrated messages
124
+ frustrated_topics = self._extract_topics(frustrated_messages)
125
+ return list(set(frustrated_topics)) # Unique topic
126
+
127
+ def _calculate_resolution_times(self, df: pd.DataFrame) -> Dict[str, float]:
128
+ """
129
+ Calculate average time taken to resolve questions for different topics.
130
+
131
+ Args:
132
+ df: DataFrame containing messages
133
+
134
+ Returns:
135
+ Dictionary with topics and their average resolution times in minutes
136
+ """
137
+ resolution_times = {}
138
+
139
+ # Group messages by topic
140
+ topics = self._extract_topics(df)
141
+ for topic in set(topics):
142
+ escaped_topic = re.escape(topic)
143
+ topic_msgs = df[df['prompt'].str.contains(escaped_topic, case=False)]
144
+ if len(topic_msgs) >= 2:
145
+ # Calculate time difference between first and last message
146
+ start_time = pd.to_datetime(topic_msgs['timestamp'].iloc[0])
147
+ end_time = pd.to_datetime(topic_msgs['timestamp'].iloc[-1])
148
+ duration = (end_time - start_time).total_seconds() / 60 # Convert to minutes
149
+ resolution_times[topic] = duration
150
+
151
+ return resolution_times
152
+
153
+ def _calculate_completion_rates(self, df: pd.DataFrame) -> Dict[str, float]:
154
+ """
155
+ Calculate completion rates for different topics.
156
+
157
+ Args:
158
+ df: DataFrame containing messages
159
+
160
+ Returns:
161
+ Dictionary with topics and their completion rates
162
+ """
163
+ completion_rates = {}
164
+ topics = self._extract_topics(df)
165
+
166
+ for topic in set(topics):
167
+ escaped_topic = re.escape(topic)
168
+ topic_msgs = df[df['prompt'].str.contains(escaped_topic, case=False)]
169
+ if len(topic_msgs) > 0:
170
+ # Consider a topic completed if there are no frustrated messages in the last 2 messages
171
+ last_msgs = topic_msgs.tail(2)
172
+ frustrated = self._identify_frustration(last_msgs)
173
+ completion_rates[topic] = 0.0 if frustrated else 1.0
174
+
175
+ return completion_rates
176
+
177
+ def _analyze_time_distribution(self, df: pd.DataFrame) -> Dict[str, Dict[str, float]]:
178
+ """
179
+ Analyze time spent on different topics.
180
+
181
+ Args:
182
+ df: DataFrame containing messages
183
+
184
+ Returns:
185
+ Dictionary with time distribution statistics per topic
186
+ """
187
+ time_stats = {}
188
+ topics = self._extract_topics(df)
189
+
190
+ for topic in set(topics):
191
+ escaped_topic = re.escape(topic)
192
+ topic_msgs = df[df['prompt'].str.contains(escaped_topic, case=False)]
193
+ if len(topic_msgs) >= 2:
194
+ times = pd.to_datetime(topic_msgs['timestamp'])
195
+ duration = (times.max() - times.min()).total_seconds() / 60
196
+
197
+ time_stats[topic] = {
198
+ 'total_minutes': duration,
199
+ 'avg_minutes_per_message': duration / len(topic_msgs),
200
+ 'message_count': len(topic_msgs)
201
+ }
202
+
203
+ return time_stats
204
+
205
+ def _identify_coverage_gaps(self, df: pd.DataFrame) -> List[str]:
206
+ """
207
+ Identify topics with potential coverage gaps.
208
+
209
+ Args:
210
+ df: DataFrame containing messages
211
+
212
+ Returns:
213
+ List of topics with coverage gaps
214
+ """
215
+ gaps = []
216
+ topics = self._extract_topics(df)
217
+ topic_stats = self._analyze_time_distribution(df)
218
+
219
+ for topic in set(topics):
220
+ if topic in topic_stats:
221
+ stats = topic_stats[topic]
222
+ # Flag topics with very short interaction times or few messages
223
+ if stats['total_minutes'] < 5 or stats['message_count'] < 3:
224
+ gaps.append(topic)
225
+
226
+ return gaps
227
+
228
+ def _calculate_student_metrics(self, df: pd.DataFrame) -> Dict[str, Dict[str, float]]:
229
+ """
230
+ Calculate various metrics for each student.
231
+
232
+ Args:
233
+ df: DataFrame containing messages
234
+
235
+ Returns:
236
+ Dictionary with student metrics
237
+ """
238
+ student_metrics = {}
239
+
240
+ for user_id in df['user_id'].unique():
241
+ user_msgs = df[df['user_id'] == user_id]
242
+
243
+ metrics = {
244
+ 'message_count': len(user_msgs),
245
+ 'question_count': len(user_msgs[user_msgs['prompt'].str.contains('|'.join(self.question_words), case=False)]),
246
+ 'avg_response_length': user_msgs['response'].str.len().mean(),
247
+ 'unique_topics': len(set(self._extract_topics(user_msgs))),
248
+ 'frustration_count': len(self._identify_frustration(user_msgs))
249
+ }
250
+
251
+ student_metrics[user_id] = metrics
252
+
253
+ return student_metrics
254
+
255
+ def _determine_student_cluster(self, metrics: Dict[str, float]) -> str:
256
+ """
257
+ Determine which cluster a student belongs to based on their metrics.
258
+
259
+ Args:
260
+ metrics: Dictionary containing student metrics
261
+
262
+ Returns:
263
+ Cluster label ('confident', 'engaged', or 'struggling')
264
+ """
265
+ # Simple rule-based clustering
266
+ if metrics['frustration_count'] > 2 or metrics['question_count'] / metrics['message_count'] > 0.7:
267
+ return 'struggling'
268
+ elif metrics['message_count'] > 10 and metrics['unique_topics'] > 3:
269
+ return 'engaged'
270
+ else:
271
+ return 'confident'
272
+
273
+ def _identify_abandon_points(self, df: pd.DataFrame) -> List[Dict[str, Any]]:
274
+ """
275
+ Identify points where students abandoned topics.
276
+
277
+ Args:
278
+ df: DataFrame containing messages
279
+
280
+ Returns:
281
+ List of dictionaries containing abandon point information
282
+ """
283
+ abandon_points = []
284
+ topics = self._extract_topics(df)
285
+
286
+ for topic in set(topics):
287
+ escaped_topic = re.escape(topic)
288
+ topic_msgs = df[df['prompt'].str.contains(escaped_topic, case=False)]
289
+ if len(topic_msgs) >= 2:
290
+ # Check for large time gaps between messages
291
+ times = pd.to_datetime(topic_msgs['timestamp'])
292
+ time_gaps = times.diff()
293
+
294
+ for idx, gap in enumerate(time_gaps):
295
+ if gap and gap.total_seconds() > 600: # 10 minutes threshold
296
+ abandon_points.append({
297
+ 'topic': topic,
298
+ 'message_before': topic_msgs.iloc[idx-1]['prompt'],
299
+ 'time_gap': gap.total_seconds() / 60, # Convert to minutes
300
+ 'resumed': idx < len(topic_msgs) - 1
301
+ })
302
+
303
+ return abandon_points
304
+
305
+ def process_chat_history(self, chat_history: List[Dict[Any, Any]]) -> Dict[str, Any]:
306
+ """
307
+ Process chat history data and generate comprehensive analytics.
308
+
309
+ Args:
310
+ chat_history: List of chat history documents
311
+ session_info: Dictionary containing session metadata (topic, duration, etc.)
312
+
313
+ Returns:
314
+ Dictionary containing all analytics results
315
+ """
316
+ try:
317
+ # Convert chat history to DataFrame for easier processing
318
+ messages_data = []
319
+ for chat in chat_history:
320
+ for msg in chat['messages']:
321
+ messages_data.append({
322
+ 'user_id': chat['user_id'],
323
+ 'session_id': chat['session_id'],
324
+ 'timestamp': msg['timestamp'],
325
+ 'prompt': msg['prompt'],
326
+ 'response': msg['response']
327
+ })
328
+
329
+ df = pd.DataFrame(messages_data)
330
+
331
+ # Generate all analytics
332
+ analytics_results = {
333
+ 'topic_interaction': self._analyze_topic_interaction(df),
334
+ 'question_patterns': self._analyze_question_patterns(df),
335
+ 'sentiment_analysis': self._analyze_sentiment(df),
336
+ 'completion_trends': self._analyze_completion_trends(df),
337
+ 'student_clustering': self._cluster_students(df),
338
+ 'abandoned_conversations': self._analyze_abandoned_conversations(df)
339
+ }
340
+
341
+ return analytics_results
342
+
343
+ except Exception as e:
344
+ logger.error(f"Error processing chat history: {str(e)}")
345
+ raise
346
+
347
+ def _analyze_topic_interaction(self, df: pd.DataFrame) -> Dict[str, Any]:
348
+ """Analyze topic interaction frequency and patterns."""
349
+ topics = self._extract_topics(df)
350
+
351
+ topic_stats = {
352
+ 'interaction_counts': Counter(topics),
353
+ 'revisit_patterns': self._calculate_topic_revisits(df, topics),
354
+ 'avg_time_per_topic': self._calculate_avg_time_per_topic(df, topics)
355
+ }
356
+
357
+ return topic_stats
358
+
359
+ def _analyze_question_patterns(self, df: pd.DataFrame) -> Dict[str, Any]:
360
+ """Analyze question patterns and identify difficult topics."""
361
+ questions = df[df['prompt'].str.lower().str.split().apply(
362
+ lambda x: any(word.lower() in self.question_words for word in x)
363
+ )]
364
+
365
+ question_stats = {
366
+ 'total_questions': len(questions),
367
+ 'question_types': self._categorize_questions(questions),
368
+ 'complex_chains': self._identify_complex_chains(df)
369
+ }
370
+
371
+ return question_stats
372
+
373
+ def _analyze_sentiment(self, df: pd.DataFrame) -> Dict[str, Any]:
374
+ """Perform sentiment analysis on messages."""
375
+ sentiments = []
376
+ for prompt in df['prompt']:
377
+ try:
378
+ sentiment = self.sentiment_analyzer(prompt)[0]
379
+ sentiments.append(sentiment['label'])
380
+ except Exception as e:
381
+ logger.warning(f"Error in sentiment analysis: {str(e)}")
382
+ sentiments.append('NEUTRAL')
383
+
384
+ sentiment_stats = {
385
+ 'overall_sentiment': Counter(sentiments),
386
+ 'frustration_indicators': self._identify_frustration(df),
387
+ 'resolution_times': self._calculate_resolution_times(df)
388
+ }
389
+
390
+ return sentiment_stats
391
+
392
+ def _analyze_completion_trends(self, df: pd.DataFrame) -> Dict[str, Any]:
393
+ """Analyze topic completion trends and coverage."""
394
+ completion_stats = {
395
+ 'completion_rates': self._calculate_completion_rates(df),
396
+ 'time_distribution': self._analyze_time_distribution(df),
397
+ 'coverage_gaps': self._identify_coverage_gaps(df)
398
+ }
399
+
400
+ return completion_stats
401
+
402
+ def _cluster_students(self, df: pd.DataFrame) -> Dict[str, Any]:
403
+ """Cluster students based on interaction patterns."""
404
+ student_metrics = self._calculate_student_metrics(df)
405
+
406
+ clusters = {
407
+ 'confident': [],
408
+ 'engaged': [],
409
+ 'struggling': []
410
+ }
411
+
412
+ for student_id, metrics in student_metrics.items():
413
+ cluster = self._determine_student_cluster(metrics)
414
+ clusters[cluster].append(student_id)
415
+
416
+ return clusters
417
+
418
+ def _analyze_abandoned_conversations(self, df: pd.DataFrame) -> Dict[str, Any]:
419
+ """Identify and analyze abandoned conversations."""
420
+ abandoned_stats = {
421
+ 'abandon_points': self._identify_abandon_points(df),
422
+ 'incomplete_topics': self._identify_incomplete_topics(df),
423
+ 'dropout_patterns': self._analyze_dropout_patterns(df)
424
+ }
425
+
426
+ return abandoned_stats
427
+
428
+ def _identify_incomplete_topics(self, df: pd.DataFrame) -> List[Dict[str, Any]]:
429
+ """
430
+ Identify topics that were started but not completed by students.
431
+
432
+ Args:
433
+ df: DataFrame containing messages
434
+
435
+ Returns:
436
+ List of dictionaries containing incomplete topic information
437
+ """
438
+ incomplete_topics = []
439
+ topics = self._extract_topics(df)
440
+
441
+ for topic in set(topics):
442
+ escaped_topic = re.escape(topic)
443
+ topic_msgs = df[df['prompt'].str.contains(escaped_topic, case=False)]
444
+
445
+ if len(topic_msgs) > 0:
446
+ # Check for completion indicators
447
+ last_msgs = topic_msgs.tail(3) # Look at last 3 messages
448
+
449
+ # Consider a topic incomplete if:
450
+ # 1. There are unanswered questions
451
+ # 2. Contains frustration indicators
452
+ # 3. No positive confirmation/understanding indicators
453
+ has_questions = last_msgs['prompt'].str.contains('|'.join(self.question_words), case=False).any()
454
+ has_frustration = bool(self._identify_frustration(last_msgs))
455
+
456
+ completion_indicators = ['understand', 'got it', 'makes sense', 'thank you', 'clear now']
457
+ has_completion = last_msgs['prompt'].str.contains('|'.join(completion_indicators), case=False).any()
458
+
459
+ if (has_questions or has_frustration) and not has_completion:
460
+ incomplete_topics.append({
461
+ 'topic': topic,
462
+ 'last_interaction': topic_msgs.iloc[-1]['timestamp'],
463
+ 'message_count': len(topic_msgs),
464
+ 'has_pending_questions': has_questions,
465
+ 'shows_frustration': has_frustration
466
+ })
467
+
468
+ return incomplete_topics
469
+
470
+ def _analyze_dropout_patterns(self, df: pd.DataFrame) -> Dict[str, Any]:
471
+ """
472
+ Analyze patterns in where and why students tend to drop out of conversations.
473
+
474
+ Args:
475
+ df: DataFrame containing messages
476
+
477
+ Returns:
478
+ Dictionary containing dropout pattern analysis
479
+ """
480
+ dropout_analysis = {
481
+ 'timing_patterns': {},
482
+ 'topic_patterns': {},
483
+ 'complexity_indicators': {},
484
+ 'engagement_metrics': {}
485
+ }
486
+
487
+ # Analyze timing of dropouts
488
+ timestamps = pd.to_datetime(df['timestamp'])
489
+ time_gaps = timestamps.diff()
490
+ dropout_points = time_gaps[time_gaps > pd.Timedelta(minutes=30)].index
491
+
492
+ for point in dropout_points:
493
+ # Get context before dropout
494
+ context_msgs = df.loc[max(0, point-5):point]
495
+
496
+ # Analyze timing
497
+ time_of_day = timestamps[point].hour
498
+ dropout_analysis['timing_patterns'][time_of_day] = \
499
+ dropout_analysis['timing_patterns'].get(time_of_day, 0) + 1
500
+
501
+ # Analyze topics at dropout points
502
+ dropout_topics = self._extract_topics(context_msgs)
503
+ for topic in dropout_topics:
504
+ dropout_analysis['topic_patterns'][topic] = \
505
+ dropout_analysis['topic_patterns'].get(topic, 0) + 1
506
+
507
+ # Analyze complexity
508
+ msg_lengths = context_msgs['prompt'].str.len().mean()
509
+ question_density = len(context_msgs[context_msgs['prompt'].str.contains(
510
+ '|'.join(self.question_words), case=False)]) / len(context_msgs)
511
+
512
+ dropout_analysis['complexity_indicators'][point] = {
513
+ 'message_length': msg_lengths,
514
+ 'question_density': question_density
515
+ }
516
+
517
+ # Analyze engagement
518
+ dropout_analysis['engagement_metrics'][point] = {
519
+ 'messages_before_dropout': len(context_msgs),
520
+ 'response_times': time_gaps[max(0, point-5):point].mean().total_seconds() / 60
521
+ }
522
+
523
+ return dropout_analysis
524
+
525
+ def _rank_topics_by_difficulty(self, analytics_results: Dict[str, Any]) -> List[Dict[str, Any]]:
526
+ """
527
+ Rank topics by their difficulty based on various metrics from analytics results.
528
+
529
+ Args:
530
+ analytics_results: Dictionary containing all analytics data
531
+
532
+ Returns:
533
+ List of dictionaries containing topic difficulty rankings and scores
534
+ """
535
+ topic_difficulty = []
536
+
537
+ # Extract relevant metrics for each topic
538
+ topics = set()
539
+ for topic in analytics_results['topic_interaction']['interaction_counts'].keys():
540
+
541
+ # Calculate difficulty score based on multiple factors
542
+ difficulty_score = 0
543
+
544
+ # Factor 1: Question frequency
545
+ question_count = sum(1 for chain in analytics_results['question_patterns']['complex_chains']
546
+ if chain['topic'] == topic)
547
+ difficulty_score += question_count * 0.3
548
+
549
+ # Factor 2: Frustration indicators
550
+ frustration_count = sum(1 for indicator in analytics_results['sentiment_analysis']['frustration_indicators']
551
+ if topic.lower() in indicator.lower())
552
+ difficulty_score += frustration_count * 0.25
553
+
554
+ # Factor 3: Completion rate (inverse relationship)
555
+ completion_rate = analytics_results['completion_trends']['completion_rates'].get(topic, 1.0)
556
+ difficulty_score += (1 - completion_rate) * 0.25
557
+
558
+ # Factor 4: Time spent (normalized)
559
+ avg_time = analytics_results['topic_interaction']['avg_time_per_topic'].get(topic, 0)
560
+ max_time = max(analytics_results['topic_interaction']['avg_time_per_topic'].values())
561
+ normalized_time = avg_time / max_time if max_time > 0 else 0
562
+ difficulty_score += normalized_time * 0.2
563
+
564
+ topic_difficulty.append({
565
+ 'topic': topic,
566
+ 'difficulty_score': round(difficulty_score, 2),
567
+ 'metrics': {
568
+ 'question_frequency': question_count,
569
+ 'frustration_indicators': frustration_count,
570
+ 'completion_rate': completion_rate,
571
+ 'avg_time_spent': avg_time
572
+ }
573
+ })
574
+
575
+ # Sort topics by difficulty score
576
+ return sorted(topic_difficulty, key=lambda x: x['difficulty_score'], reverse=True)
577
+
578
+ def _identify_support_needs(self, analytics_results: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]:
579
+ """
580
+ Identify specific support needs for students based on analytics results.
581
+
582
+ Args:
583
+ analytics_results: Dictionary containing all analytics data
584
+
585
+ Returns:
586
+ Dictionary containing support needs categorized by urgency
587
+ """
588
+ support_needs = {
589
+ 'immediate_attention': [],
590
+ 'monitoring_needed': [],
591
+ 'general_support': []
592
+ }
593
+
594
+ # Analyze struggling students
595
+ for student_id in analytics_results['student_clustering']['struggling']:
596
+ # Get student-specific metrics
597
+ student_msgs = analytics_results.get('sentiment_analysis', {}).get('messages', [])
598
+ frustration_topics = [topic for topic in analytics_results['sentiment_analysis']['frustration_indicators']
599
+ if any(msg['user_id'] == student_id for msg in student_msgs)]
600
+
601
+ # Calculate engagement metrics
602
+ engagement_level = len([chain for chain in analytics_results['question_patterns']['complex_chains']
603
+ if any(msg['user_id'] == student_id for msg in chain['messages'])])
604
+
605
+ # Identify immediate attention needs
606
+ if len(frustration_topics) >= 3 or engagement_level < 2:
607
+ support_needs['immediate_attention'].append({
608
+ 'student_id': student_id,
609
+ 'issues': frustration_topics,
610
+ 'engagement_level': engagement_level,
611
+ 'recommended_actions': [
612
+ 'Schedule one-on-one session',
613
+ 'Review difficult topics',
614
+ 'Provide additional resources'
615
+ ]
616
+ })
617
+
618
+ # Identify monitoring needs
619
+ elif len(frustration_topics) >= 1 or engagement_level < 4:
620
+ support_needs['monitoring_needed'].append({
621
+ 'student_id': student_id,
622
+ 'areas_of_concern': frustration_topics,
623
+ 'engagement_level': engagement_level,
624
+ 'recommended_actions': [
625
+ 'Regular progress checks',
626
+ 'Provide supplementary materials'
627
+ ]
628
+ })
629
+
630
+ # General support needs
631
+ else:
632
+ support_needs['general_support'].append({
633
+ 'student_id': student_id,
634
+ 'areas_for_improvement': frustration_topics,
635
+ 'engagement_level': engagement_level,
636
+ 'recommended_actions': [
637
+ 'Maintain regular communication',
638
+ 'Encourage participation'
639
+ ]
640
+ })
641
+
642
+ return support_needs
643
+
644
+
645
+ def _extract_topics(self, df: pd.DataFrame) -> List[str]:
646
+ """Extract topics from messages using spaCy."""
647
+ topics = []
648
+ for doc in self.nlp.pipe(df['prompt']):
649
+ # Extract noun phrases as potential topics
650
+ noun_phrases = [chunk.text for chunk in doc.noun_chunks]
651
+ topics.extend(noun_phrases)
652
+ return topics
653
+
654
+ def _calculate_topic_revisits(self, df: pd.DataFrame, topics: List[str]) -> Dict[str, int]:
655
+ """Calculate how often topics are revisited."""
656
+ topic_visits = Counter(topics)
657
+ return {topic: count for topic, count in topic_visits.items() if count > 1}
658
+
659
+ def _calculate_avg_time_per_topic(self, df: pd.DataFrame, topics: List[str]) -> Dict[str, float]:
660
+ """Calculate average time spent per topic."""
661
+ topic_times = {}
662
+ for topic in set(topics):
663
+ escaped_topic = re.escape(topic)
664
+ topic_msgs = df[df['prompt'].str.contains(escaped_topic, case=False)]
665
+ if len(topic_msgs) > 1:
666
+ time_diffs = pd.to_datetime(topic_msgs['timestamp']).diff()
667
+ avg_time = time_diffs.mean().total_seconds() / 60 # Convert to minutes
668
+ topic_times[topic] = avg_time
669
+ return topic_times
670
+
671
+ def _identify_complex_chains(self, df: pd.DataFrame) -> List[Dict[str, Any]]:
672
+ """Identify complex conversation chains."""
673
+ chains = []
674
+ current_chain = []
675
+
676
+ for idx, row in df.iterrows():
677
+ if self._is_followup_question(row['prompt']):
678
+ current_chain.append(row)
679
+ else:
680
+ if len(current_chain) >= 3: # Consider 3+ related questions as complex chain
681
+ chains.append({
682
+ 'messages': current_chain,
683
+ 'topic': self._extract_topics([current_chain[0]['prompt']])[0],
684
+ 'length': len(current_chain)
685
+ })
686
+ current_chain = []
687
+
688
+ return chains
689
+
690
+ def _generate_topic_priority_list(self, analytics_results: Dict[str, Any]) -> List[Dict[str, Any]]:
691
+ """
692
+ Generate a prioritized list of topics for the upcoming session.
693
+
694
+ Args:
695
+ analytics_results: Dictionary containing all analytics data
696
+
697
+ Returns:
698
+ List of dictionaries containing topics and their priority scores
699
+ """
700
+ topic_priorities = []
701
+
702
+ # Get difficulty rankings
703
+ difficulty_ranking = self._rank_topics_by_difficulty(analytics_results)
704
+
705
+ for topic_data in difficulty_ranking:
706
+ topic = topic_data['topic']
707
+
708
+ # Calculate priority score based on multiple factors
709
+ priority_score = 0
710
+
711
+ # Factor 1: Difficulty score (40% weight)
712
+ priority_score += topic_data['difficulty_score'] * 0.4
713
+
714
+ # Factor 2: Student frustration (25% weight)
715
+ frustration_count = sum(1 for indicator in
716
+ analytics_results['sentiment_analysis']['frustration_indicators']
717
+ if topic.lower() in indicator.lower())
718
+ normalized_frustration = min(frustration_count / 5, 1) # Cap at 5 frustrations
719
+ priority_score += normalized_frustration * 0.25
720
+
721
+ # Factor 3: Incomplete understanding (20% weight)
722
+ incomplete_topics = analytics_results.get('abandoned_conversations', {}).get('incomplete_topics', [])
723
+ if any(t['topic'] == topic for t in incomplete_topics):
724
+ priority_score += 0.2
725
+
726
+ # Factor 4: Coverage gaps (15% weight)
727
+ if topic in analytics_results['completion_trends']['coverage_gaps']:
728
+ priority_score += 0.15
729
+
730
+ topic_priorities.append({
731
+ 'topic': topic,
732
+ 'priority_score': round(priority_score, 2),
733
+ 'reasons': {
734
+ 'difficulty_level': topic_data['difficulty_score'],
735
+ 'frustration_indicators': frustration_count,
736
+ 'has_incomplete_understanding': any(t['topic'] == topic for t in incomplete_topics),
737
+ 'has_coverage_gaps': topic in analytics_results['completion_trends']['coverage_gaps']
738
+ },
739
+ 'recommended_focus_areas': self._generate_focus_recommendations(topic_data, analytics_results)
740
+ })
741
+
742
+ # Sort by priority score
743
+ return sorted(topic_priorities, key=lambda x: x['priority_score'], reverse=True)
744
+
745
+ def _generate_focus_recommendations(self, topic_data: Dict[str, Any],
746
+ analytics_results: Dict[str, Any]) -> List[str]:
747
+ """Generate specific focus recommendations for a topic."""
748
+ recommendations = []
749
+
750
+ if topic_data['metrics']['question_frequency'] > 3:
751
+ recommendations.append("Provide more detailed explanations and examples")
752
+
753
+ if topic_data['metrics']['completion_rate'] < 0.7:
754
+ recommendations.append("Break down complex concepts into smaller segments")
755
+
756
+ if topic_data['metrics']['frustration_indicators'] > 2:
757
+ recommendations.append("Review prerequisite concepts and provide additional context")
758
+
759
+ return recommendations
760
+
761
+ def _is_followup_question(self, prompt: str) -> bool:
762
+ """Determine if a prompt is a follow-up question."""
763
+ followup_indicators = {'also', 'then', 'additionally', 'furthermore', 'related to that'}
764
+ return any(indicator in prompt.lower() for indicator in followup_indicators)
765
+
766
+ def generate_faculty_report(self, analytics_results: Dict[str, Any]) -> Dict[str, Any]:
767
+ """Generate a comprehensive report for faculty."""
768
+ report = {
769
+ 'key_findings': self._generate_key_findings(analytics_results),
770
+ 'recommended_actions': self._generate_recommendations(analytics_results),
771
+ 'topic_difficulty_ranking': self._rank_topics_by_difficulty(analytics_results),
772
+ 'student_support_needs': self._identify_support_needs(analytics_results),
773
+ 'topic_priorities': self._generate_topic_priority_list(analytics_results)
774
+ }
775
+
776
+ return report
777
+
778
+ def _generate_key_findings(self, analytics_results: Dict[str, Any]) -> List[str]:
779
+ """Generate key findings from analytics results."""
780
+ findings = []
781
+
782
+ # Analyze topic interaction patterns
783
+ topic_stats = analytics_results['topic_interaction']
784
+ low_interaction_topics = [topic for topic, count in topic_stats['interaction_counts'].items()
785
+ if count < 3] # Arbitrary threshold
786
+ if low_interaction_topics:
787
+ findings.append(f"Low engagement detected in topics: {', '.join(low_interaction_topics)}")
788
+
789
+ # Analyze sentiment patterns
790
+ sentiment_stats = analytics_results['sentiment_analysis']
791
+ if sentiment_stats['frustration_indicators']:
792
+ findings.append("Significant frustration detected in the following areas: " +
793
+ ', '.join(sentiment_stats['frustration_indicators']))
794
+
795
+ # Analyze student clustering
796
+ student_clusters = analytics_results['student_clustering']
797
+ if len(student_clusters['struggling']) > 0:
798
+ findings.append(f"{len(student_clusters['struggling'])} students showing signs of difficulty")
799
+
800
+ return findings
801
+
802
+ def _generate_recommendations(self, analytics_results: Dict[str, Any]) -> List[str]:
803
+ """Generate actionable recommendations for faculty."""
804
+ recommendations = []
805
+
806
+ # Analyze complex chains
807
+ question_patterns = analytics_results['question_patterns']
808
+ if question_patterns['complex_chains']:
809
+ topics_needing_clarity = set(chain['topic'] for chain in question_patterns['complex_chains'])
810
+ recommendations.append(f"Consider providing additional examples for: {', '.join(topics_needing_clarity)}")
811
+
812
+ # Analyze completion trends
813
+ completion_trends = analytics_results['completion_trends']
814
+ low_completion_topics = [topic for topic, rate in completion_trends['completion_rates'].items()
815
+ if rate < 0.7] # 70% threshold
816
+ if low_completion_topics:
817
+ recommendations.append(f"Review and possibly simplify material for: {', '.join(low_completion_topics)}")
818
+
819
+ return recommendations
820
+
821
+ # Example usage
822
+ if __name__ == "__main__":
823
+ # Initialize analytics engine
824
+ analytics_engine = NovaScholarAnalytics()
825
+
826
+ # Sample usage with dummy data
827
+ sample_chat_history = [
828
+ {
829
+ "user_id": "123",
830
+ "session_id": "S101",
831
+ "messages": [
832
+ {
833
+ "prompt": "What is DevOps?",
834
+ "response": "DevOps is a software engineering practice...",
835
+ "timestamp": datetime.now()
836
+ }
837
+ ]
838
+ }
839
+ ]
840
+
841
+ # Process analytics
842
+ results = analytics_engine.process_chat_history(all_chat_histories)
843
+
844
+ # Generate faculty report
845
+ faculty_report = analytics_engine.generate_faculty_report(results)
846
+ print(faculty_report)
847
+ # Print results
848
+ # logger.info("Analytics processing completed")
849
+ # logger.info(f"Key findings: {faculty_report['key_findings']}")
850
+ # logger.info(f"Recommendations: {faculty_report['recommended_actions']}")
session_page.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import random
2
  import streamlit as st
3
  from datetime import datetime
@@ -13,13 +15,21 @@ from dotenv import load_dotenv
13
  import os
14
  from pymongo import MongoClient
15
  from gen_mcqs import generate_mcqs, save_quiz, quizzes_collection, get_student_quiz_score, submit_quiz_answers
 
 
 
 
 
 
16
 
17
  load_dotenv()
18
  MONGO_URI = os.getenv('MONGO_URI')
 
19
  client = MongoClient(MONGO_URI)
20
  db = client["novascholar_db"]
21
  polls_collection = db["polls"]
22
 
 
23
  def get_current_user():
24
  if 'current_user' not in st.session_state:
25
  return None
@@ -133,14 +143,18 @@ def display_preclass_content(session, student_id, course_id):
133
  if st.button("Mark PDF as Read", key=f"pdf_{material['file_name']}"):
134
  create_notification("PDF marked as read!", "success")
135
 
 
 
 
 
136
  # Chat input
137
  # Add a check, if materials are available, only then show the chat input
138
  if(st.session_state.user_type == "student"):
139
  if materials:
140
  if prompt := st.chat_input("Ask a question about Pre-class Materials"):
141
- if len(st.session_state.messages) >= 20:
142
- st.warning("Message limit (20) reached for this session.")
143
- return
144
 
145
  st.session_state.messages.append({"role": "user", "content": prompt})
146
 
@@ -150,14 +164,20 @@ def display_preclass_content(session, student_id, course_id):
150
 
151
  # Get document context
152
  context = ""
 
153
  materials = resources_collection.find({"session_id": session['session_id']})
 
154
  context = ""
155
  vector_data = None
156
 
 
 
157
  context = ""
158
  for material in materials:
159
  resource_id = material['_id']
 
160
  vector_data = vectors_collection.find_one({"resource_id": resource_id})
 
161
  if vector_data and 'text' in vector_data:
162
  context += vector_data['text'] + "\n"
163
 
@@ -167,15 +187,33 @@ def display_preclass_content(session, student_id, course_id):
167
 
168
  try:
169
  # Generate response using Gemini
170
- context_prompt = f"""
171
- Based on the following context, answer the user's question:
172
 
 
 
 
 
 
 
 
 
 
 
173
  Context:
174
  {context}
175
-
 
 
 
 
 
 
 
 
176
  Question: {prompt}
177
-
178
- Please provide a clear and concise answer based only on the information provided in the context.
179
  """
180
 
181
  response = model.generate_content(context_prompt)
@@ -229,10 +267,10 @@ def display_preclass_content(session, student_id, course_id):
229
  if file_content:
230
  material_type = st.selectbox("Select Material Type", ["pdf", "docx", "txt"])
231
  if st.button("Upload Material"):
232
- upload_resource(course_id, session['session_id'], file_name, uploaded_file, material_type)
233
 
234
  # Search for the newly uploaded resource's _id in resources_collection
235
- resource_id = resources_collection.find_one({"file_name": file_name})["_id"]
236
  create_vector_store(file_content, resource_id)
237
  st.success("Material uploaded successfully!")
238
 
@@ -979,6 +1017,205 @@ def display_postclass_analytics(session, course_id):
979
  for student in pending_students:
980
  st.markdown(f"- {student.get('full_name', 'Unknown Student')} (SID: {student.get('SID', 'Unknown SID')})")
981
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
982
  def upload_preclass_materials(session_id, course_id):
983
  """Upload pre-class materials for a session"""
984
  st.subheader("Upload Pre-class Materials")
@@ -1069,7 +1306,7 @@ def display_quiz_tab(student_id, course_id, session_id):
1069
  st.error("Error submitting quiz. Please try again.")
1070
 
1071
  def display_session_content(student_id, course_id, session, username, user_type):
1072
- st.title(f"Session {session['session_id']}: {session['title']}")
1073
 
1074
  # Check if the date is a string or a datetime object
1075
  if isinstance(session['date'], str):
@@ -1078,21 +1315,23 @@ def display_session_content(student_id, course_id, session, username, user_type)
1078
  else:
1079
  session_date = session['date']
1080
 
1081
- st.markdown(f"**Date:** {format_datetime(session_date)}")
1082
- st.markdown(f"**Status:** {session['status'].replace('_', ' ').title()}")
1083
 
 
 
 
1084
  # Find the course_id of the session in
1085
 
1086
  if st.session_state.user_type == 'student':
1087
  tabs = (["Pre-class Work", "In-class Work", "Post-class Work"])
1088
  else:
1089
- tabs = (["Pre-class Analytics", "In-class Analytics", "Post-class Analytics"])
1090
 
1091
  if st.session_state.user_type == 'student':
1092
  pre_class_tab, in_class_tab, post_class_tab, quiz_tab = st.tabs(["Pre-class Work", "In-class Work", "Post-class Work", "Quizzes"])
1093
  else:
1094
  pre_class_work, in_class_work, post_class_work, preclass_analytics, inclass_analytics, postclass_analytics = st.tabs(["Pre-class Work", "In-class Work", "Post-class Work", "Pre-class Analytics", "In-class Analytics", "Post-class Analytics"])
1095
-
1096
  # Display pre-class materials
1097
  if st.session_state.user_type == 'student':
1098
  with pre_class_tab:
@@ -1114,8 +1353,10 @@ def display_session_content(student_id, course_id, session, username, user_type)
1114
  with post_class_work:
1115
  display_post_class_content(session, student_id, course_id)
1116
  with preclass_analytics:
1117
- display_preclass_analytics(session, course_id)
1118
  with inclass_analytics:
1119
  display_inclass_analytics(session, course_id)
1120
  with postclass_analytics:
1121
  display_postclass_analytics(session, course_id)
 
 
 
1
+ from collections import defaultdict
2
+ import json
3
  import random
4
  import streamlit as st
5
  from datetime import datetime
 
15
  import os
16
  from pymongo import MongoClient
17
  from gen_mcqs import generate_mcqs, save_quiz, quizzes_collection, get_student_quiz_score, submit_quiz_answers
18
+ from create_course import courses_collection
19
+ from pre_class_analytics import NovaScholarAnalytics
20
+ import openai
21
+ from openai import OpenAI
22
+
23
+
24
 
25
  load_dotenv()
26
  MONGO_URI = os.getenv('MONGO_URI')
27
+ OPENAI_KEY = os.getenv('OPENAI_KEY')
28
  client = MongoClient(MONGO_URI)
29
  db = client["novascholar_db"]
30
  polls_collection = db["polls"]
31
 
32
+
33
  def get_current_user():
34
  if 'current_user' not in st.session_state:
35
  return None
 
143
  if st.button("Mark PDF as Read", key=f"pdf_{material['file_name']}"):
144
  create_notification("PDF marked as read!", "success")
145
 
146
+ # Initialize 'messages' in session_state if it doesn't exist
147
+ if 'messages' not in st.session_state:
148
+ st.session_state.messages = []
149
+
150
  # Chat input
151
  # Add a check, if materials are available, only then show the chat input
152
  if(st.session_state.user_type == "student"):
153
  if materials:
154
  if prompt := st.chat_input("Ask a question about Pre-class Materials"):
155
+ # if len(st.session_state.messages) >= 20:
156
+ # st.warning("Message limit (20) reached for this session.")
157
+ # return
158
 
159
  st.session_state.messages.append({"role": "user", "content": prompt})
160
 
 
164
 
165
  # Get document context
166
  context = ""
167
+ print(session['session_id'])
168
  materials = resources_collection.find({"session_id": session['session_id']})
169
+ print(materials)
170
  context = ""
171
  vector_data = None
172
 
173
+ # for material in materials:
174
+ # print(material)
175
  context = ""
176
  for material in materials:
177
  resource_id = material['_id']
178
+ print(resource_id)
179
  vector_data = vectors_collection.find_one({"resource_id": resource_id})
180
+ # print(vector_data)
181
  if vector_data and 'text' in vector_data:
182
  context += vector_data['text'] + "\n"
183
 
 
187
 
188
  try:
189
  # Generate response using Gemini
190
+ # context_prompt = f"""
191
+ # Based on the following context, answer the user's question:
192
 
193
+ # Context:
194
+ # {context}
195
+
196
+ # Question: {prompt}
197
+
198
+ # Please provide a clear and concise answer based only on the information provided in the context.
199
+ # """
200
+ context_prompt = f"""
201
+ You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context.
202
+
203
  Context:
204
  {context}
205
+
206
+ Instructions:
207
+ 1. Base your answers primarily on the given context.
208
+ 2. If the answer to the user's question is not explicitly in the context but can be inferred or synthesized from the information provided, do so thoughtfully.
209
+ 3. Only use external knowledge or web assistance when:
210
+ - The context lacks sufficient information, and
211
+ - The question requires knowledge beyond what can be reasonably inferred from the context.
212
+ 4. Clearly state if you are relying on web assistance for any part of your answer.
213
+
214
  Question: {prompt}
215
+
216
+ Please provide a clear and comprehensive answer based on the above instructions.
217
  """
218
 
219
  response = model.generate_content(context_prompt)
 
267
  if file_content:
268
  material_type = st.selectbox("Select Material Type", ["pdf", "docx", "txt"])
269
  if st.button("Upload Material"):
270
+ resource_id = upload_resource(course_id, session['session_id'], file_name, uploaded_file, material_type)
271
 
272
  # Search for the newly uploaded resource's _id in resources_collection
273
+ # resource_id = resources_collection.find_one({"file_name": file_name})["_id"]
274
  create_vector_store(file_content, resource_id)
275
  st.success("Material uploaded successfully!")
276
 
 
1017
  for student in pending_students:
1018
  st.markdown(f"- {student.get('full_name', 'Unknown Student')} (SID: {student.get('SID', 'Unknown SID')})")
1019
 
1020
+ def get_chat_history(user_id, session_id):
1021
+ query = {
1022
+ "user_id": ObjectId(user_id),
1023
+ "session_id": session_id,
1024
+ "timestamp": {"$lte": datetime.utcnow()}
1025
+ }
1026
+ result = chat_history_collection.find(query)
1027
+ return list(result)
1028
+
1029
+ def get_response_from_llm(raw_data):
1030
+ messages = [
1031
+ {
1032
+ "role": "system",
1033
+ "content": "You are an AI that refines raw analytics data into actionable insights for faculty reports."
1034
+ },
1035
+ {
1036
+ "role": "user",
1037
+ "content": f"""
1038
+ Based on the following analytics data, refine and summarize the insights:
1039
+
1040
+ Raw Data:
1041
+ {raw_data}
1042
+
1043
+ Instructions:
1044
+ 1. Group similar topics together under appropriate categories.
1045
+ 2. Remove irrelevant or repetitive entries.
1046
+ 3. Summarize the findings into actionable insights.
1047
+ 4. Provide concise recommendations for improvement based on the findings.
1048
+
1049
+ Output:
1050
+ Provide a structured response with the following format:
1051
+ {{
1052
+ "Low Engagement Topics": ["List of Topics"],
1053
+ "Frustration Areas": ["List of areas"],
1054
+ "Recommendations": ["Actionable recommendations"],
1055
+ }}
1056
+ """
1057
+ }
1058
+ ]
1059
+ try:
1060
+ client = OpenAI(api_key=OPENAI_KEY)
1061
+ response = client.chat.completions.create(
1062
+ model="gpt-4o-mini",
1063
+ messages=messages,
1064
+ temperature=0.2
1065
+ )
1066
+ content = response.choices[0].message.content
1067
+ return json.loads(content)
1068
+
1069
+ except Exception as e:
1070
+ st.error(f"Error generating response: {str(e)}")
1071
+ return None
1072
+
1073
+ def get_preclass_analytics(session):
1074
+ """Get all user_ids from chat_history collection where session_id matches"""
1075
+ user_ids = chat_history_collection.distinct("user_id", {"session_id": session['session_id']})
1076
+ print(user_ids)
1077
+ session_id = session['session_id']
1078
+
1079
+ all_chat_histories = []
1080
+
1081
+ for user_id in user_ids:
1082
+ result = get_chat_history(user_id, session_id)
1083
+ if result:
1084
+ for record in result:
1085
+ chat_history = {
1086
+ "user_id": record["user_id"],
1087
+ "session_id": record["session_id"],
1088
+ "messages": record["messages"]
1089
+ }
1090
+ all_chat_histories.append(chat_history)
1091
+ else:
1092
+ st.warning("No chat history found for this session.")
1093
+
1094
+ # Use the analytics engine
1095
+ analytics_engine = NovaScholarAnalytics()
1096
+ results = analytics_engine.process_chat_history(all_chat_histories)
1097
+ faculty_report = analytics_engine.generate_faculty_report(results)
1098
+
1099
+ # Pass this Faculty Report to an LLM model for refinements and clarity
1100
+ refined_report = get_response_from_llm(faculty_report)
1101
+ return refined_report
1102
+
1103
+ def display_preclass_analytics2(session, course_id):
1104
+ refined_report = get_preclass_analytics(session)
1105
+ st.subheader("Pre-class Analytics")
1106
+ if refined_report:
1107
+ # Custom CSS to improve the look and feel
1108
+ st.markdown("""
1109
+ <style>
1110
+ .metric-card {
1111
+ background-color: #f8f9fa;
1112
+ border-radius: 10px;
1113
+ padding: 20px;
1114
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1115
+ }
1116
+ .header-text {
1117
+ color: #1f77b4;
1118
+ font-size: 24px;
1119
+ font-weight: bold;
1120
+ margin-bottom: 20px;
1121
+ }
1122
+ .subheader {
1123
+ color: #2c3e50;
1124
+ font-size: 17px;
1125
+ font-weight: 500;
1126
+ margin-bottom: 10px;
1127
+ }
1128
+ .insight-text {
1129
+ color: #34495e;
1130
+ font-size: 16px;
1131
+ line-height: 1.6;
1132
+ }
1133
+ .glossary-card {
1134
+ padding: 15px;
1135
+ margin-top: 40px;
1136
+ }
1137
+ </style>
1138
+ """, unsafe_allow_html=True)
1139
+
1140
+ # Header
1141
+ # st.markdown("<h1 style='text-align: center; color: #2c3e50;'>Pre-Class Analytics Dashboard</h1>", unsafe_allow_html=True)
1142
+ # st.markdown("<p style='text-align: center; color: #7f8c8d;'>Insights from Student Interactions</p>", unsafe_allow_html=True)
1143
+
1144
+ # Create three columns for metrics
1145
+ col1, col2, col3 = st.columns(3)
1146
+
1147
+ with col1:
1148
+ st.markdown("<p class='header-text'>🎯 Low Engagement Topics</p>", unsafe_allow_html=True)
1149
+
1150
+ # Group topics by category
1151
+ topics = refined_report["Low Engagement Topics"]
1152
+ # categories = defaultdict(list)
1153
+ for i, topic in enumerate(topics):
1154
+ st.markdown(f"{i + 1}. <p class='subheader'>{topic}</p>", unsafe_allow_html=True)
1155
+
1156
+ # # Categorize topics (you can modify these categories based on your needs)
1157
+ # for topic in topics:
1158
+ # if "Data" in topic and ("Type" in topic or "Structure" in topic):
1159
+ # categories["Data Types"].append(topic)
1160
+ # elif "Analytics" in topic:
1161
+ # categories["Analytics Concepts"].append(topic)
1162
+ # else:
1163
+ # categories["General Concepts"].append(topic)
1164
+
1165
+ # Display categorized topics
1166
+ # for category, items in categories.items():
1167
+ # st.markdown(f"<p class='subheader'>{category}</p>", unsafe_allow_html=True)
1168
+ # i = 0
1169
+ # for i, item in items:
1170
+ # st.markdown(f"{i + 1} {item}", unsafe_allow_html=True)
1171
+
1172
+ with col2:
1173
+ st.markdown("<p class='header-text'>⚠️ Frustration Areas</p>", unsafe_allow_html=True)
1174
+ for i, area in enumerate(refined_report["Frustration Areas"]):
1175
+ st.markdown(f"{i + 1}. <p class='subheader'>{area}</p>", unsafe_allow_html=True)
1176
+
1177
+ with col3:
1178
+ st.markdown("<p class='header-text'>💡 Recommendations</p>", unsafe_allow_html=True)
1179
+ for i, rec in enumerate(refined_report["Recommendations"]):
1180
+ st.markdown(f"{i + 1}. <p class='subheader'>{rec}</p>", unsafe_allow_html=True)
1181
+
1182
+ # Glossary section
1183
+ st.markdown("<div class='glossary-card'>", unsafe_allow_html=True)
1184
+ # st.markdown("<h3 style='color: #2c3e50;'>Understanding the Metrics</h3>", unsafe_allow_html=True)
1185
+
1186
+ explanations = {
1187
+ "Low Engagement Topics": "Topics where students showed minimal interaction or understanding during their chat sessions. These areas may require additional focus during classroom instruction.",
1188
+ "Frustration Areas": "Specific concepts or topics where students expressed difficulty or confusion during their interactions with the chatbot. These areas may need immediate attention or alternative teaching approaches.",
1189
+ "Recommendations": "AI-generated suggestions for improving student engagement and understanding, based on the analyzed chat interactions and identified patterns."
1190
+ }
1191
+
1192
+ st.subheader("Understanding the Metrics")
1193
+
1194
+ for metric, explanation in explanations.items():
1195
+ # st.markdown(f"<p class='subheader'>{metric}</p>", unsafe_allow_html=True)
1196
+ # st.markdown(f"<p class='insight-text'>{explanation}</p>", unsafe_allow_html=True)
1197
+ st.markdown(f"<span class='subheader'>**{metric}**</span>: <span class='subheader'>{explanation}</span>", unsafe_allow_html=True)
1198
+
1199
+ st.markdown("</div>", unsafe_allow_html=True)
1200
+
1201
+
1202
+ def display_session_analytics(session, course_id):
1203
+ """Display session analytics for faculty"""
1204
+ st.header("Session Analytics")
1205
+
1206
+ # Display Pre-class Analytics
1207
+ display_preclass_analytics2(session, course_id)
1208
+
1209
+ # Display In-class Analytics
1210
+ display_inclass_analytics(session, course_id)
1211
+
1212
+ # Display Post-class Analytics
1213
+ display_postclass_analytics(session, course_id)
1214
+
1215
+
1216
+
1217
+
1218
+
1219
  def upload_preclass_materials(session_id, course_id):
1220
  """Upload pre-class materials for a session"""
1221
  st.subheader("Upload Pre-class Materials")
 
1306
  st.error("Error submitting quiz. Please try again.")
1307
 
1308
  def display_session_content(student_id, course_id, session, username, user_type):
1309
+ st.title(f"{session['title']}")
1310
 
1311
  # Check if the date is a string or a datetime object
1312
  if isinstance(session['date'], str):
 
1315
  else:
1316
  session_date = session['date']
1317
 
1318
+ course_name = courses_collection2.find_one({"course_id": course_id})['title']
 
1319
 
1320
+ st.markdown(f"**Date:** {format_datetime(session_date)}")
1321
+ st.markdown(f"**Course Name:** {course_name}")
1322
+
1323
  # Find the course_id of the session in
1324
 
1325
  if st.session_state.user_type == 'student':
1326
  tabs = (["Pre-class Work", "In-class Work", "Post-class Work"])
1327
  else:
1328
+ tabs = (["Session Analytics"])
1329
 
1330
  if st.session_state.user_type == 'student':
1331
  pre_class_tab, in_class_tab, post_class_tab, quiz_tab = st.tabs(["Pre-class Work", "In-class Work", "Post-class Work", "Quizzes"])
1332
  else:
1333
  pre_class_work, in_class_work, post_class_work, preclass_analytics, inclass_analytics, postclass_analytics = st.tabs(["Pre-class Work", "In-class Work", "Post-class Work", "Pre-class Analytics", "In-class Analytics", "Post-class Analytics"])
1334
+ # pre_class_work, in_class_work, post_class_work, session_analytics = st.tabs(["Pre-class Work", "In-class Work", "Post-class Work", "Session Analytics"])
1335
  # Display pre-class materials
1336
  if st.session_state.user_type == 'student':
1337
  with pre_class_tab:
 
1353
  with post_class_work:
1354
  display_post_class_content(session, student_id, course_id)
1355
  with preclass_analytics:
1356
+ display_preclass_analytics2(session, course_id)
1357
  with inclass_analytics:
1358
  display_inclass_analytics(session, course_id)
1359
  with postclass_analytics:
1360
  display_postclass_analytics(session, course_id)
1361
+ # with session_analytics:
1362
+ # display_session_analytics(session, course_id)