-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest2.py
More file actions
799 lines (658 loc) · 32.3 KB
/
test2.py
File metadata and controls
799 lines (658 loc) · 32.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
import os
import spacy
import pymongo
from pymongo import MongoClient
from typing import Dict, List, Optional
from dotenv import load_dotenv
import matplotlib.pyplot as plt
import io
import base64
import json
# Load environment variables
load_dotenv()
class StudentAcademicChatbot:
def __init__(self):
"""Initialize the chatbot with MongoDB connection and NLP model"""
# Get MongoDB URI from environment variables
self.mongodb_uri = os.getenv("MONGODB_URI")
self.database_name = "CampusConnect"
self.collection_name = "std_data"
# Load spaCy NLP model
self.nlp = spacy.load("en_core_web_sm")
try:
# Establish MongoDB Connection
self.client = pymongo.MongoClient(self.mongodb_uri)
self.db = self.client[self.database_name]
self.students_collection = self.db[self.collection_name]
# Verify connection
self.client.admin.command('ping')
print("Successfully connected to MongoDB!")
except pymongo.errors.ConnectionFailure as e:
print(f"Failed to connect to MongoDB: {e}")
raise
# Intent Mapping
self.intent_patterns = {
'overall_performance': [
'performance', 'gpa', 'grades', 'academic standing',
'total performance', 'cumulative performance', 'cgpa'
],
'course_details': [
'course', 'courses', 'class', 'subject',
'enrolled courses', 'course information'
],
'attendance': [
'attendance', 'classes', 'present', 'absent',
'class participation', 'how many classes', 'attendance percentage'
],
'semester_performance': [
'semester', 'semester gpa', 'last semester',
'performance this semester'
],
'current_semester': [
'current semester', 'what semester am i in', 'which semester', 'semester'
],
'credit_status': [
'credits', 'total credits', 'remaining credits', 'credit requirements',
'credits completed', 'credits left'
],
'program_details': [
'program', 'department', 'admission', 'admission year',
'enrollment details', 'department details'
],
'performance_trend': [
'trend', 'progress', 'performance trend', 'grade trend',
'improvement', 'academic progress'
],
'attendance_alert': [
'attendance risk', 'attendance warning', 'low attendance',
'attendance status', 'attendance requirement'
],
'course_load': [
'course load', 'credit load', 'workload',
'credits this semester', 'semester load'
],
'instructor_info': [
'instructor', 'professor', 'teacher', 'faculty',
'who teaches', 'who is teaching', 'course instructor'
]
}
def _instructor_response(self, student_data: Dict, entities: Dict) -> str:
"""Generate response for instructor-related queries"""
courses = student_data.get('academic_details', {}).get('courses', [])
if entities['course_code']:
# Find specific course instructor
course = next(
(c for c in courses if c['course_code'] == entities['course_code']),
None
)
if course:
return (
f"**Course Instructor Details**\n"
f"• Course: {course['course_code']} - {course['course_name']}\n"
f"• Instructor: {course['instructor']}\n"
f"• Office Hours: {course.get('office_hours', 'Not specified')}\n"
f"• Email: {course.get('instructor_email', 'Not available')}"
)
# If no specific course, list all instructors
instructor_list = []
for course in courses:
instructor_list.append(
f"• {course['course_code']}: {course['instructor']}"
)
return (
f"**Current Semester Instructors**\n"
f"{chr(10).join(instructor_list)}"
)
def get_student_details(self, student_id1, student_id2):
# Fetch data from database
student_data1 = self.retrieve_student_data(student_id1)
student_data2 = self.retrieve_student_data(student_id2)
return student_data1, student_data2
def compare_students(self, student_data1, student_data2):
# Compare GPA
gpa1 = student_data1['academic_details']['academic_performance']['cgpa']
gpa2 = student_data2['academic_details']['academic_performance']['cgpa']
# Compare Credits Completed
credits1 = student_data1['academic_details']['credits_completed']
credits2 = student_data2['academic_details']['credits_completed']
# Compare Attendance Percentage
attendance1 = student_data1['academic_details']['attendance_percentage']
attendance2 = student_data2['academic_details']['attendance_percentage']
# Prepare comparison data
comparison_data = {
'Student 1': {
'GPA': gpa1,
'Credits': credits1,
'Attendance': attendance1
},
'Student 2': {
'GPA': gpa2,
'Credits': credits2,
'Attendance': attendance2
}
}
return comparison_data
def sort_students_by(self, data, sort_key, ascending=True):
"""Sort students data by specified key"""
if not data:
return []
return sorted(data, key=lambda x: x[sort_key], reverse=not ascending)
def get_sorted_common_details(self, sort_by='cgpa', ascending=True):
"""Get sorted common details"""
data = self.get_common_details()
if data:
return self.sort_students_by(data, sort_by, ascending)
return None
def handle_common_details_query(self):
"""Comprehensive method to retrieve common student details"""
try:
# Retrieve student data from mock_dept_data.json
with open('mock_dept_data.json', 'r') as file:
data = json.load(file)
# Extract student details
students = []
for entry in data:
if 'students' in entry:
for student_id, student_info in entry['students'].items():
academic_details = student_info.get('academic_details', {})
enrollment = academic_details.get('enrollment', {})
performance = academic_details.get('academic_performance', {})
attendance = academic_details.get('attendance', {})
student_data = {
'name': f"Student {student_id.split('ST')[1]}",
'student_id': student_id,
'cgpa': performance.get('cgpa', 0),
'attendance': attendance.get('percentage', 0),
'credits_completed': len(academic_details.get('courses', [])),
'department': enrollment.get('department', 'Unknown')
}
students.append(student_data)
return students
except FileNotFoundError:
print("Error: mock_dept_data.json file not found.")
return []
except json.JSONDecodeError:
print("Error: mock_dept_data.json contains invalid JSON.")
return []
except Exception as e:
print(f"Error in handle_common_details_query: {e}")
return []
def retrieve_student_data(self, student_id: str) -> Optional[Dict]:
"""
Retrieve student data from MongoDB.
Args:
student_id (str): Student's unique identifier.
Returns:
Dict containing student's academic information or None.
"""
try:
print(f"Attempting to retrieve data for student ID: {student_id}")
# Query using _id field directly
student_data = self.students_collection.find_one({"_id": student_id})
if student_data:
print(f"Found data for student: {student_id}")
return student_data
else:
print(f"No data found for student ID: {student_id}")
# Log available student IDs for debugging
all_ids = list(self.students_collection.distinct("_id"))
print(f"Available student IDs in database: {all_ids}")
return None
except Exception as e:
print(f"Error retrieving student data: {str(e)}")
return None
def _classify_multiple_intents(self, query: str) -> List[str]:
"""
Classify multiple intents from a single query
Args:
query (str): User's natural language query
Returns:
List[str]: List of detected intents
"""
query_lower = query.lower()
detected_intents = []
for intent, patterns in self.intent_patterns.items():
if any(pattern in query_lower for pattern in patterns):
detected_intents.append(intent)
return detected_intents
def preprocess_query(self, query: str, student_id: Optional[str] = None) -> Dict:
"""Modified to handle multiple intents"""
query_lower = query.lower()
# Detect multiple intents
detected_intents = self._classify_multiple_intents(query)
# Default entities
entities = {
'student_id': student_id or 'ST788655',
'course_code': None,
'semester': None
}
# Extract semester if present
try:
potential_semester = [int(s) for s in query_lower.split() if s.isdigit()]
entities['semester'] = potential_semester[0] if potential_semester else None
except Exception:
entities['semester'] = None
# Extract course code if present
course_codes = ['BUS580', 'BUS628', 'BUS68', 'BUS135', 'BUS972']
for code in course_codes:
if code.lower() in query_lower:
entities['course_code'] = code
break
return {
'intents': detected_intents,
'entities': entities
}
def generate_response(self, query_analysis: Dict, student_data: Dict) -> str:
"""Modified to handle multiple intents"""
intents = query_analysis['intents']
if not intents:
return "I couldn't understand your specific query. Could you please rephrase?"
# Handle multiple intents
responses = []
for intent in intents:
if intent == 'instructor_info':
responses.append(self._instructor_response(student_data, query_analysis['entities']))
elif intent == 'overall_performance':
responses.append(self._performance_response(student_data))
elif intent == 'course_details':
responses.append(self._course_details_response(student_data, query_analysis['entities']))
elif intent == 'attendance':
responses.append(self._attendance_response(student_data, query_analysis['entities']))
elif intent == 'semester_performance':
responses.append(self._semester_performance_response(student_data, query_analysis['entities']))
elif intent == 'current_semester':
responses.append(self._current_semester_response(student_data))
elif intent == 'credit_status':
responses.append(self._credit_status_response(student_data))
elif intent == 'program_details':
responses.append(self._program_details_response(student_data))
elif intent == 'performance_trend':
responses.append(self._performance_trend_response(student_data))
elif intent == 'attendance_alert':
responses.append(self._attendance_alert_response(student_data))
elif intent == 'course_load':
responses.append(self._course_load_response(student_data))
# Combine responses with separators
combined_response = "\n\n".join([f"📌 {response}" for response in responses])
return combined_response
def _performance_response(self, student_data: Dict) -> str:
"""Generate response for overall academic performance"""
academic_performance = student_data.get('academic_details', {}).get('academic_performance', {})
cgpa = academic_performance.get('cgpa', 'N/A')
semester_wise_gpa = academic_performance.get('semester_wise_gpa', [])
current_semester_gpa = semester_wise_gpa[-1]['gpa'] if semester_wise_gpa else 'N/A'
return (f"Your current cumulative GPA is {cgpa}. "
f"In the current semester, you've achieved a GPA of {current_semester_gpa}. "
f"Keep up the great work!")
def _course_details_response(self, student_data: Dict, entities: Dict) -> str:
"""Generate response for course-specific queries"""
courses = student_data.get('academic_details', {}).get('courses', [])
if entities['course_code']:
# Find specific course
specific_course = next(
(course for course in courses if course['course_code'] == entities['course_code']),
None
)
if specific_course:
return (f"Course Details for {specific_course['course_code']}: "
f"{specific_course['course_name']} "
f"taught by {specific_course['instructor']}. "
f"Credits: {specific_course['credits']}. "
f"This course is in semester {specific_course['semester']}.")
# If no specific course match, provide general course list
course_list = [f"{c['course_code']}: {c['course_name']}" for c in courses]
return f"Your current courses are: {', '.join(course_list)}"
def _attendance_response(self, student_data: Dict, entities: Dict) -> str:
"""Generate response for attendance-related queries"""
attendance = student_data.get('academic_details', {}).get('attendance', {})
per_course_attendance = attendance.get('per_course_attendance', [])
if entities['course_code']:
# Specific course attendance
course_attendance = next(
(course for course in per_course_attendance if course['course_code'] == entities['course_code']),
None
)
if course_attendance:
return (f"Your attendance for {entities['course_code']} is {course_attendance['percentage']}%. "
f"You've attended {course_attendance['attended_classes']} out of "
f"{course_attendance['total_classes']} classes.")
# Overall attendance
return (f"Your total attendance is {attendance.get('percentage', 'N/A')}%. "
f"You've attended {attendance.get('attended_classes', 'N/A')} "
f"out of {attendance.get('total_classes', 'N/A')} classes.")
def _semester_performance_response(self, student_data, entities):
try:
academic_performance = student_data.get('academic_details', {}).get('academic_performance', {})
semester_wise_gpa = academic_performance.get('semester_wise_gpa', [])
# If specific semester is requested
if entities.get('semester'):
target_semester = entities['semester']
semester_data = next(
(sem for sem in semester_wise_gpa if sem['semester'] == target_semester),
None
)
if semester_data:
return (
f"**Semester {target_semester} Performance**\n"
f"• GPA: {semester_data['gpa']}\n"
f"• Courses Taken: {semester_data.get('courses_count', 'N/A')}\n"
f"• Performance Notes: {semester_data.get('performance_notes', 'No additional notes')}"
)
# If no specific semester, provide a comprehensive summary
performance_summary = "\n".join([f"Semester {sem['semester']}: GPA {sem['gpa']}" for sem in semester_wise_gpa])
# Create a bar chart
semesters = [sem['semester'] for sem in semester_wise_gpa]
gpas = [sem['gpa'] for sem in semester_wise_gpa]
plt.bar(semesters, gpas)
plt.xlabel('Semester')
plt.ylabel('GPA')
plt.title('Semester-wise Performance')
plt.xticks(rotation=90)
# Save the plot to a file
img = io.BytesIO()
plt.savefig(img, format='png')
img.seek(0)
# Convert the image to a base64-encoded string
img_url = base64.b64encode(img.getvalue()).decode()
# Return the plot as an image
return (
f"**Semester Performance Summary**\n"
f"{performance_summary}\n"
f"<img src='data:image/png;base64,{img_url}'>"
)
except Exception as e:
return f"Error retrieving semester performance: {str(e)}"
def _current_semester_response(self, student_data: Dict) -> str:
"""Generate response for the current semester"""
enrollment_data = student_data.get('academic_details', {}).get('enrollment', {})
current_semester = enrollment_data.get('current_semester', 'N/A')
if current_semester != 'N/A':
return f"Your current semester is {current_semester}."
else:
return "Sorry, I couldn't find your current semester information."
def chat(self, query: str, student_id: Optional[str] = None) -> str:
"""
Enhanced chat interface method to handle multiple intents
Args:
query (str): User's natural language query
student_id (str, optional): Specific student ID to query
Returns:
str: Generated response
"""
# Preprocess query with multiple intent detection
query_analysis = self.preprocess_query(query, student_id)
# Use student ID from query analysis
current_student_id = query_analysis['entities']['student_id']
# Retrieve student data
student_data = self.retrieve_student_data(current_student_id)
if not student_data:
return "Sorry, could not find student information. Please check the student ID."
# Generate combined response for all detected intents
return self.generate_response(query_analysis, student_data)
def _calculate_credit_status(self, student_data: Dict) -> Dict:
"""Calculate credit completion status"""
courses = student_data.get('academic_details', {}).get('courses', [])
current_semester_credits = sum(
course['credits'] for course in courses
if course['semester'] == student_data['academic_details']['enrollment']['current_semester']
)
return {
'current_semester_credits': current_semester_credits,
'credit_distribution': self._get_credit_distribution(courses)
}
def _get_credit_distribution(self, courses: List) -> Dict:
"""Get semester-wise credit distribution"""
distribution = {}
for course in courses:
semester = course['semester']
if semester not in distribution:
distribution[semester] = 0
distribution[semester] += course['credits']
return distribution
def _analyze_performance_trend(self, student_data: Dict) -> Dict:
"""Analyze semester-wise performance trends"""
semester_wise_gpa = student_data.get('academic_details', {}).get(
'academic_performance', {}).get('semester_wise_gpa', []
)
if not semester_wise_gpa:
return {'trend': 'No data available'}
gpas = [sem['gpa'] for sem in semester_wise_gpa]
trend = {
'improving': gpas[-1] > gpas[-2] if len(gpas) > 1 else None,
'highest_gpa': max(gpas),
'lowest_gpa': min(gpas),
'recent_change': gpas[-1] - gpas[-2] if len(gpas) > 1 else 0
}
return trend
def _check_attendance_risk(self, student_data: Dict) -> Dict:
"""Check for attendance risks in courses"""
attendance_data = student_data.get('academic_details', {}).get(
'attendance', {}).get('per_course_attendance', []
)
risk_threshold = 75 # Configurable threshold
at_risk_courses = [
{
'course_code': course['course_code'],
'current_percentage': course['percentage'],
'classes_needed': self._calculate_classes_needed(
course['percentage'],
course['attended_classes'],
course['total_classes'],
risk_threshold
)
}
for course in attendance_data
if course['percentage'] < risk_threshold
]
return {
'at_risk_courses': at_risk_courses,
'risk_threshold': risk_threshold
}
def _calculate_classes_needed(
self, current_percentage: float,
attended: int, total: int,
target: float
) -> int:
"""Calculate classes needed to reach target attendance"""
if current_percentage >= target:
return 0
current_ratio = attended / total
classes_needed = 0
while (attended + classes_needed) / (total + classes_needed) * 100 < target:
classes_needed += 1
return classes_needed
def _analyze_course_load(self, student_data: Dict) -> Dict:
"""Analyze current semester course load"""
courses = student_data.get('academic_details', {}).get('courses', [])
current_semester = student_data.get('academic_details', {}).get(
'enrollment', {}).get('current_semester'
)
current_courses = [
course for course in courses
if course['semester'] == current_semester
]
return {
'total_courses': len(current_courses),
'total_credits': sum(course['credits'] for course in current_courses),
'credit_distribution': {
'one_credit_courses': len([c for c in current_courses if c['credits'] == 1]),
'four_credit_courses': len([c for c in current_courses if c['credits'] == 4])
}
}
def _program_details_response(self, student_data: Dict) -> str:
"""Generate response for program and enrollment details"""
enrollment = student_data.get('academic_details', {}).get('enrollment', {})
return (
f"You are enrolled in the {enrollment.get('program', 'N/A')} program "
f"in the {enrollment.get('department', 'N/A')} department. "
f"You were admitted in {enrollment.get('admission_year', 'N/A')}."
)
def _credit_status_response(self, student_data: Dict) -> str:
"""Generate response for credit status"""
credit_status = self._calculate_credit_status(student_data)
return (
f"You are currently taking {credit_status['current_semester_credits']} "
f"credits this semester. Your semester-wise credit distribution is: "
f"{', '.join(f'Semester {sem}: {credits} credits' for sem, credits in credit_status['credit_distribution'].items())}"
)
def _performance_trend_response(self, student_data: Dict) -> str:
"""Generate response for performance trend analysis"""
trend = self._analyze_performance_trend(student_data)
if trend.get('trend') == 'No data available':
return "Sorry, not enough data to analyze performance trend."
trend_direction = "improving" if trend['improving'] else "declining"
return (
f"Your academic performance is {trend_direction}. "
f"Your GPA changed by {abs(trend['recent_change']):.2f} points last semester. "
f"Your highest GPA so far is {trend['highest_gpa']:.2f}."
)
def _attendance_alert_response(self, student_data: Dict) -> str:
"""Generate response for attendance alerts"""
risk_data = self._check_attendance_risk(student_data)
if not risk_data['at_risk_courses']:
return "Your attendance is above the minimum requirement in all courses."
risk_messages = [
f"{course['course_code']}: Currently at {course['current_percentage']}%. "
f"Need to attend {course['classes_needed']} more classes to reach {risk_data['risk_threshold']}%"
for course in risk_data['at_risk_courses']
]
return (
f"Attendance Alert: You have {len(risk_data['at_risk_courses'])} courses "
f"below {risk_data['risk_threshold']}% attendance:\n" +
"\n".join(risk_messages)
)
def _course_load_response(self, student_data: Dict) -> str:
"""Generate response for course load analysis"""
load_analysis = self._analyze_course_load(student_data)
return (
f"You are currently taking {load_analysis['total_courses']} courses "
f"with a total of {load_analysis['total_credits']} credits. "
f"This includes {load_analysis['credit_distribution']['one_credit_courses']} "
f"one-credit courses and "
f"{load_analysis['credit_distribution']['four_credit_courses']} "
f"four-credit courses."
)
def _credit_status_response(self, student_data: Dict) -> str:
"""Comprehensive credit status response"""
try:
# Retrieve credit-related information
academic_details = student_data.get('academic_details', {})
courses = academic_details.get('courses', [])
enrollment = academic_details.get('enrollment', {})
# Calculate total credits
total_credits_completed = sum(course['credits'] for course in courses)
current_semester = enrollment.get('current_semester', 'N/A')
# Current semester credits
current_semester_credits = sum(
course['credits'] for course in courses
if course.get('semester') == current_semester
)
# Credit distribution by semester
credit_distribution = {}
for course in courses:
semester = course.get('semester', 'Unknown')
credit_distribution[semester] = credit_distribution.get(semester, 0) + course['credits']
# Formatting response
distribution_str = ", ".join([f"Semester {sem}: {credits} credits" for sem, credits in credit_distribution.items()])
return (
f"*Credit Overview*\n"
f"• Total Credits Completed: {total_credits_completed}\n"
f"• Current Semester Credits: {current_semester_credits}\n"
f"• Credit Distribution:\n{distribution_str}"
)
except Exception as e:
return f"Error retrieving credit information: {str(e)}"
# New function to fetch academic details of two students
def get_student_details(student_id1, student_id2):
# Fetch data from database
student_data1 = retrieve_student_data(student_id1)
student_data2 = retrieve_student_data(student_id2)
return student_data1, student_data2
# New function to compare academic details of two students
def compare_students(student_data1, student_data2):
# Compare GPA
gpa1 = student_data1['academic_details']['academic_performance']['cgpa']
gpa2 = student_data2['academic_details']['academic_performance']['cgpa']
# Compare Credits Completed
credits1 = student_data1['academic_details']['credits_completed']
credits2 = student_data2['academic_details']['credits_completed']
# Compare Attendance Percentage
attendance1 = student_data1['academic_details']['attendance_percentage']
attendance2 = student_data2['academic_details']['attendance_percentage']
# Prepare comparison data
comparison_data = {
'Student 1': {
'GPA': gpa1,
'Credits': credits1,
'Attendance': attendance1
},
'Student 2': {
'GPA': gpa2,
'Credits': credits2,
'Attendance': attendance2
}
}
return comparison_data
def _semester_performance_response(self, student_data: Dict, entities: Dict) -> str:
"""Enhanced semester performance response"""
try:
academic_performance = student_data.get('academic_details', {}).get('academic_performance', {})
semester_wise_gpa = academic_performance.get('semester_wise_gpa', [])
# If specific semester is requested
if entities.get('semester'):
target_semester = entities['semester']
semester_data = next(
(sem for sem in semester_wise_gpa if sem['semester'] == target_semester),
None
)
if semester_data:
return (
f"**Semester {target_semester} Performance**\n"
f"• GPA: {semester_data['gpa']}\n"
f"• Courses Taken: {semester_data.get('courses_count', 'N/A')}\n"
f"• Performance Notes: {semester_data.get('performance_notes', 'No additional notes')}"
)
# If no specific semester, provide a comprehensive summary
performance_summary = "\n".join([f"Semester {sem['semester']}: GPA {sem['gpa']}" for sem in semester_wise_gpa])
return (
f"**Semester Performance Summary**\n"
f"{performance_summary}\n"
f"• Cumulative GPA: {academic_performance.get('cgpa', 'N/A')}"
)
except Exception as e:
return f"Error retrieving semester performance: {str(e)}"
def _performance_trend_response(self, student_data: Dict) -> str:
"""Detailed performance trend analysis"""
try:
academic_performance = student_data.get('academic_details', {}).get('academic_performance', {})
semester_wise_gpa = academic_performance.get('semester_wise_gpa', [])
if len(semester_wise_gpa) < 2:
return "Insufficient data to analyze performance trend."
# Calculate trend
gpas = [float(sem['gpa']) for sem in semester_wise_gpa]
trend_change = gpas[-1] - gpas[-2]
trend_direction = "improving" if trend_change > 0 else "declining"
return (
f"**Performance Trend Analysis**\n"
f"• Overall Trend: {trend_direction}\n"
f"• GPA Change: {abs(trend_change):.2f} points\n"
f"• Highest Semester GPA: {max(gpas):.2f}\n"
f"• Lowest Semester GPA: {min(gpas):.2f}"
)
except Exception as e:
return f"Error analyzing performance trend: {str(e)}"
def main():
chatbot = StudentAcademicChatbot() # Instantiate the chatbot
# Interactive chat loop
while True:
student_id = input("Enter student ID (or 'exit' to quit): ").strip()
if student_id.lower() in ['exit', 'quit']:
break
while True:
user_query = input(f"Ask about student {student_id}'s academic information (or 'back' to change student): ")
if user_query.lower() == 'back':
break
response = chatbot.chat(user_query, student_id) # Use the chatbot instance
print(response)
if __name__ == "__main__":
main() # Call the main function to start the chatbot