Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -297,80 +297,107 @@ class TranscriptParser:
|
|
| 297 |
def parse_transcript(self, text: str) -> Dict:
|
| 298 |
"""Parse Miami-Dade formatted transcripts with updated regex patterns."""
|
| 299 |
try:
|
| 300 |
-
#
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
"weighted_gpa": float(student_match.group(6)),
|
| 314 |
-
"total_credits": float(student_match.group(7)),
|
| 315 |
-
"community_service_hours": int(student_match.group(8))
|
| 316 |
-
}
|
| 317 |
|
| 318 |
-
|
| 319 |
-
self.
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
code = req_match.group(1).strip()
|
| 330 |
-
self.requirements[code] = {
|
| 331 |
-
"description": req_match.group(2).strip(),
|
| 332 |
-
"required": float(req_match.group(3)),
|
| 333 |
-
"waived": float(req_match.group(4)),
|
| 334 |
-
"completed": float(req_match.group(5)),
|
| 335 |
-
"status": f"{req_match.group(6)}%"
|
| 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 |
-
"current_courses": self.current_courses,
|
| 366 |
-
"course_history": self.course_history,
|
| 367 |
-
"graduation_status": self.graduation_status,
|
| 368 |
-
"format": "miami_dade"
|
| 369 |
-
}
|
| 370 |
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
|
| 375 |
def _extract_current_courses(self):
|
| 376 |
"""Identify in-progress courses."""
|
|
@@ -383,7 +410,8 @@ class TranscriptParser:
|
|
| 383 |
"credits": c["credits"],
|
| 384 |
"grade_level": c["grade_level"]
|
| 385 |
}
|
| 386 |
-
for c in self.course_history
|
|
|
|
| 387 |
]
|
| 388 |
|
| 389 |
def _calculate_completion(self):
|
|
@@ -421,8 +449,6 @@ def format_transcript_output(data: Dict) -> str:
|
|
| 421 |
output.append(f"**Total Credits Earned:** {student['total_credits']}")
|
| 422 |
if 'community_service_hours' in student:
|
| 423 |
output.append(f"**Community Service Hours:** {student['community_service_hours']}")
|
| 424 |
-
if 'parent' in student:
|
| 425 |
-
output.append(f"**Parent/Guardian:** {student['parent']}")
|
| 426 |
|
| 427 |
output.append("")
|
| 428 |
|
|
@@ -461,8 +487,8 @@ def format_transcript_output(data: Dict) -> str:
|
|
| 461 |
# Course History by Year
|
| 462 |
courses_by_year = defaultdict(list)
|
| 463 |
for course in data.get("course_history", []):
|
| 464 |
-
|
| 465 |
-
|
| 466 |
|
| 467 |
if courses_by_year:
|
| 468 |
output.append("## Course History\n" + '='*50)
|
|
@@ -471,7 +497,7 @@ def format_transcript_output(data: Dict) -> str:
|
|
| 471 |
for course in courses_by_year[year]:
|
| 472 |
output.append(
|
| 473 |
f"- **{course.get('course_code', '')} {course.get('description', 'Unnamed course')}**\n"
|
| 474 |
-
f" Subject: {course.get('
|
| 475 |
f"Grade: {course.get('grade', 'N/A')} | "
|
| 476 |
f"Credits: {course.get('credits', 'N/A')}"
|
| 477 |
)
|
|
@@ -500,26 +526,63 @@ def parse_transcript_with_ai(text: str, progress=gr.Progress()) -> Dict:
|
|
| 500 |
return parse_transcript_with_ai_fallback(text, progress)
|
| 501 |
|
| 502 |
def parse_transcript_with_ai_fallback(text: str, progress=gr.Progress()) -> Dict:
|
| 503 |
-
"""Fallback AI parsing method"""
|
| 504 |
# Pre-process the text
|
| 505 |
text = remove_sensitive_info(text[:15000]) # Limit input size
|
| 506 |
|
| 507 |
prompt = f"""
|
| 508 |
-
Analyze this academic transcript and extract structured information:
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
|
| 524 |
Transcript Text:
|
| 525 |
{text}
|
|
@@ -534,7 +597,7 @@ def parse_transcript_with_ai_fallback(text: str, progress=gr.Progress()) -> Dict
|
|
| 534 |
raise gr.Error(f"Model failed to load. {model_loader.error or 'Please try loading a model first.'}")
|
| 535 |
|
| 536 |
# Tokenize and generate response
|
| 537 |
-
inputs = tokenizer(prompt, return_tensors="pt").to(model_loader.device)
|
| 538 |
if progress:
|
| 539 |
progress(0.4)
|
| 540 |
|
|
@@ -542,7 +605,9 @@ def parse_transcript_with_ai_fallback(text: str, progress=gr.Progress()) -> Dict
|
|
| 542 |
**inputs,
|
| 543 |
max_new_tokens=2000,
|
| 544 |
temperature=0.1,
|
| 545 |
-
do_sample=True
|
|
|
|
|
|
|
| 546 |
)
|
| 547 |
if progress:
|
| 548 |
progress(0.8)
|
|
@@ -555,9 +620,17 @@ def parse_transcript_with_ai_fallback(text: str, progress=gr.Progress()) -> Dict
|
|
| 555 |
json_str = response.split('```json')[1].split('```')[0].strip()
|
| 556 |
parsed_data = json.loads(json_str)
|
| 557 |
except (IndexError, json.JSONDecodeError):
|
| 558 |
-
# Fallback:
|
| 559 |
-
|
| 560 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
|
| 562 |
if progress:
|
| 563 |
progress(1.0)
|
|
@@ -1003,28 +1076,40 @@ class ProfileManager:
|
|
| 1003 |
|
| 1004 |
def _format_transcript(self, transcript: Dict) -> str:
|
| 1005 |
"""Format transcript data for display."""
|
| 1006 |
-
if not transcript or "
|
| 1007 |
return "_No transcript information available_"
|
| 1008 |
|
| 1009 |
display = "#### Course History\n"
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
|
| 1014 |
-
|
| 1015 |
-
|
| 1016 |
-
|
|
|
|
|
|
|
|
|
|
| 1017 |
if 'grade' in course and course['grade']:
|
| 1018 |
display += f" (Grade: {course['grade']})"
|
| 1019 |
if 'credits' in course:
|
| 1020 |
display += f" | Credits: {course['credits']}"
|
| 1021 |
-
display += f" |
|
| 1022 |
-
|
| 1023 |
-
if '
|
| 1024 |
-
|
| 1025 |
-
display += "\n**
|
| 1026 |
-
display += f"- Unweighted: {
|
| 1027 |
-
display += f"- Weighted: {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1028 |
|
| 1029 |
return display
|
| 1030 |
|
|
@@ -1051,10 +1136,10 @@ class TeachingAssistant:
|
|
| 1051 |
# Extract profile information
|
| 1052 |
name = profile.get("name", "there")
|
| 1053 |
learning_style = profile.get("learning_style", "")
|
| 1054 |
-
grade_level = profile.get("transcript", {}).get("
|
| 1055 |
-
gpa = profile.get("transcript", {}).get("
|
| 1056 |
interests = profile.get("interests", "")
|
| 1057 |
-
courses = profile.get("transcript", {}).get("
|
| 1058 |
favorites = profile.get("favorites", {})
|
| 1059 |
|
| 1060 |
# Process message with context
|
|
@@ -1174,19 +1259,18 @@ class TeachingAssistant:
|
|
| 1174 |
|
| 1175 |
def _generate_grade_advice(self, profile: Dict) -> str:
|
| 1176 |
"""Generate response about grades and GPA."""
|
| 1177 |
-
gpa = profile.get("transcript", {}).get("
|
| 1178 |
-
courses = profile.get("transcript", {}).get("
|
| 1179 |
|
| 1180 |
response = (f"Your GPA information:\n"
|
| 1181 |
-
f"- Unweighted: {gpa.get('
|
| 1182 |
-
f"- Weighted: {gpa.get('
|
| 1183 |
|
| 1184 |
# Identify any failing grades
|
| 1185 |
weak_subjects = []
|
| 1186 |
-
for
|
| 1187 |
-
|
| 1188 |
-
|
| 1189 |
-
weak_subjects.append(f"{course.get('code', '')} {course.get('name', 'Unknown course')}")
|
| 1190 |
|
| 1191 |
if weak_subjects:
|
| 1192 |
response += ("**Areas for Improvement**:\n"
|
|
@@ -1215,14 +1299,19 @@ class TeachingAssistant:
|
|
| 1215 |
|
| 1216 |
def _generate_course_advice(self, profile: Dict) -> str:
|
| 1217 |
"""Generate response about courses."""
|
| 1218 |
-
courses = profile.get("transcript", {}).get("
|
| 1219 |
-
grade_level = profile.get("transcript", {}).get("
|
| 1220 |
-
|
| 1221 |
-
response = "Here's a summary of your courses:\n"
|
| 1222 |
-
|
| 1223 |
-
|
| 1224 |
-
|
| 1225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1226 |
if 'grade' in course:
|
| 1227 |
response += f" (Grade: {course['grade']})"
|
| 1228 |
response += "\n"
|
|
|
|
| 297 |
def parse_transcript(self, text: str) -> Dict:
|
| 298 |
"""Parse Miami-Dade formatted transcripts with updated regex patterns."""
|
| 299 |
try:
|
| 300 |
+
# First try structured parsing for Miami-Dade format
|
| 301 |
+
if "Graduation Progress Summary" in text and "Miami-Dade" in text:
|
| 302 |
+
return self._parse_miami_dade_format(text)
|
| 303 |
+
else:
|
| 304 |
+
# Fall back to AI parsing if not Miami-Dade format
|
| 305 |
+
return parse_transcript_with_ai_fallback(text)
|
| 306 |
+
|
| 307 |
+
except Exception as e:
|
| 308 |
+
logging.error(f"Error parsing transcript: {str(e)}")
|
| 309 |
+
raise ValueError(f"Couldn't parse transcript: {str(e)}")
|
| 310 |
|
| 311 |
+
def _parse_miami_dade_format(self, text: str) -> Dict:
|
| 312 |
+
"""Specialized parser for Miami-Dade County Public Schools transcripts."""
|
| 313 |
+
# Extract student info
|
| 314 |
+
student_match = re.search(
|
| 315 |
+
r"(\d{7})\s*-\s*([A-Z\s,]+).*?Current Grade:\s*(\d+).*?YOG\s*(\d{4}).*?Un-weighted GPA\s*([\d.]+).*?Weighted GPA\s*([\d.]+).*?Total Credits Earned\s*([\d.]+).*?Comm Serv Hours\s*(\d+)",
|
| 316 |
+
text, re.DOTALL
|
| 317 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
|
| 319 |
+
if student_match:
|
| 320 |
+
self.student_data = {
|
| 321 |
+
"id": student_match.group(1).strip(),
|
| 322 |
+
"name": student_match.group(2).replace(",", ", ").strip(),
|
| 323 |
+
"current_grade": student_match.group(3),
|
| 324 |
+
"graduation_year": student_match.group(4),
|
| 325 |
+
"unweighted_gpa": float(student_match.group(5)),
|
| 326 |
+
"weighted_gpa": float(student_match.group(6)),
|
| 327 |
+
"total_credits": float(student_match.group(7)),
|
| 328 |
+
"community_service_hours": int(student_match.group(8))
|
| 329 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
|
| 331 |
+
# Extract requirements
|
| 332 |
+
self.requirements = {}
|
| 333 |
+
req_section = re.search(
|
| 334 |
+
r"Code\s+Description\s+Required\s+Waived\s+Completed\s+Status(.*?)Total\s+\d+\.\d+\s+\d+\.\d+\s+\d+\.\d+\s+\d+%",
|
| 335 |
+
text, re.DOTALL
|
| 336 |
+
)
|
| 337 |
+
if req_section:
|
| 338 |
+
req_lines = req_section.group(1).strip().split('\n')
|
| 339 |
+
for line in req_lines:
|
| 340 |
+
line = line.strip()
|
| 341 |
+
if not line:
|
| 342 |
+
continue
|
| 343 |
+
|
| 344 |
+
req_match = re.match(r"([A-Z]-[^\s]+)\s+(.+?)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+)%", line)
|
| 345 |
+
if req_match:
|
| 346 |
+
code = req_match.group(1).strip()
|
| 347 |
+
self.requirements[code] = {
|
| 348 |
+
"description": req_match.group(2).strip(),
|
| 349 |
+
"required": float(req_match.group(3)),
|
| 350 |
+
"waived": float(req_match.group(4)),
|
| 351 |
+
"completed": float(req_match.group(5)),
|
| 352 |
+
"status": f"{req_match.group(6)}%"
|
| 353 |
+
}
|
| 354 |
|
| 355 |
+
# Extract course history
|
| 356 |
+
self.course_history = []
|
| 357 |
+
course_section = re.search(
|
| 358 |
+
r"Requirement\s+School Year\s+GradeLv1\s+CrsNum\s+Description\s+Term\s+DstNumber\s+FG\s+Incl\s+Credits(.*?)Legend for Incl",
|
| 359 |
+
text, re.DOTALL
|
| 360 |
+
)
|
| 361 |
+
|
| 362 |
+
if course_section:
|
| 363 |
+
course_lines = course_section.group(1).strip().split('\n')
|
| 364 |
+
for line in course_lines:
|
| 365 |
+
line = line.strip()
|
| 366 |
+
if not line or line.startswith('='):
|
| 367 |
+
continue
|
| 368 |
+
|
| 369 |
+
# Handle both regular and in-progress courses
|
| 370 |
+
course_match = re.match(
|
| 371 |
+
r"([A-Z]-[^\s]+)?\s*(\d{4}-\d{4}|\d{4})?\s*(\d{2})?\s*([A-Z0-9]+)?\s*(.+?)\s+([AT12]+)?\s*([A-Z0-9]+)?\s*([A-Z])?\s*([A-Z])?\s*(inProgress|\d+\.\d+)?",
|
| 372 |
+
line
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
if course_match:
|
| 376 |
+
self.course_history.append({
|
| 377 |
+
"requirement_category": course_match.group(1) if course_match.group(1) else None,
|
| 378 |
+
"school_year": course_match.group(2) if course_match.group(2) else None,
|
| 379 |
+
"grade_level": course_match.group(3) if course_match.group(3) else None,
|
| 380 |
+
"course_code": course_match.group(4) if course_match.group(4) else None,
|
| 381 |
+
"description": course_match.group(5).strip() if course_match.group(5) else None,
|
| 382 |
+
"term": course_match.group(6) if course_match.group(6) else None,
|
| 383 |
+
"district_number": course_match.group(7) if course_match.group(7) else None,
|
| 384 |
+
"grade": course_match.group(8) if course_match.group(8) else None,
|
| 385 |
+
"inclusion_status": course_match.group(9) if course_match.group(9) else None,
|
| 386 |
+
"credits": course_match.group(10) if course_match.group(10) else None
|
| 387 |
+
})
|
| 388 |
|
| 389 |
+
# Extract in-progress courses
|
| 390 |
+
self._extract_current_courses()
|
| 391 |
+
self._calculate_completion()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
|
| 393 |
+
return {
|
| 394 |
+
"student_info": self.student_data,
|
| 395 |
+
"requirements": self.requirements,
|
| 396 |
+
"current_courses": self.current_courses,
|
| 397 |
+
"course_history": self.course_history,
|
| 398 |
+
"graduation_status": self.graduation_status,
|
| 399 |
+
"format": "miami_dade"
|
| 400 |
+
}
|
| 401 |
|
| 402 |
def _extract_current_courses(self):
|
| 403 |
"""Identify in-progress courses."""
|
|
|
|
| 410 |
"credits": c["credits"],
|
| 411 |
"grade_level": c["grade_level"]
|
| 412 |
}
|
| 413 |
+
for c in self.course_history
|
| 414 |
+
if c.get("credits") and isinstance(c["credits"], str) and c["credits"].lower() == "inprogress"
|
| 415 |
]
|
| 416 |
|
| 417 |
def _calculate_completion(self):
|
|
|
|
| 449 |
output.append(f"**Total Credits Earned:** {student['total_credits']}")
|
| 450 |
if 'community_service_hours' in student:
|
| 451 |
output.append(f"**Community Service Hours:** {student['community_service_hours']}")
|
|
|
|
|
|
|
| 452 |
|
| 453 |
output.append("")
|
| 454 |
|
|
|
|
| 487 |
# Course History by Year
|
| 488 |
courses_by_year = defaultdict(list)
|
| 489 |
for course in data.get("course_history", []):
|
| 490 |
+
if course.get("school_year"):
|
| 491 |
+
courses_by_year[course["school_year"]].append(course)
|
| 492 |
|
| 493 |
if courses_by_year:
|
| 494 |
output.append("## Course History\n" + '='*50)
|
|
|
|
| 497 |
for course in courses_by_year[year]:
|
| 498 |
output.append(
|
| 499 |
f"- **{course.get('course_code', '')} {course.get('description', 'Unnamed course')}**\n"
|
| 500 |
+
f" Subject: {course.get('requirement_category', 'N/A')} | "
|
| 501 |
f"Grade: {course.get('grade', 'N/A')} | "
|
| 502 |
f"Credits: {course.get('credits', 'N/A')}"
|
| 503 |
)
|
|
|
|
| 526 |
return parse_transcript_with_ai_fallback(text, progress)
|
| 527 |
|
| 528 |
def parse_transcript_with_ai_fallback(text: str, progress=gr.Progress()) -> Dict:
|
| 529 |
+
"""Fallback AI parsing method with improved prompt engineering"""
|
| 530 |
# Pre-process the text
|
| 531 |
text = remove_sensitive_info(text[:15000]) # Limit input size
|
| 532 |
|
| 533 |
prompt = f"""
|
| 534 |
+
Analyze this academic transcript and extract structured information in JSON format. Follow this exact structure:
|
| 535 |
+
|
| 536 |
+
{{
|
| 537 |
+
"student_info": {{
|
| 538 |
+
"name": "Full Name",
|
| 539 |
+
"id": "Student ID",
|
| 540 |
+
"current_grade": "Grade Level",
|
| 541 |
+
"graduation_year": "Year of Graduation",
|
| 542 |
+
"unweighted_gpa": 0.0,
|
| 543 |
+
"weighted_gpa": 0.0,
|
| 544 |
+
"total_credits": 0.0,
|
| 545 |
+
"community_service_hours": 0
|
| 546 |
+
}},
|
| 547 |
+
"requirements": {{
|
| 548 |
+
"A-English": {{
|
| 549 |
+
"description": "English requirement description",
|
| 550 |
+
"required": 4.0,
|
| 551 |
+
"completed": 4.0,
|
| 552 |
+
"status": "100%"
|
| 553 |
+
}}
|
| 554 |
+
}},
|
| 555 |
+
"current_courses": [
|
| 556 |
+
{{
|
| 557 |
+
"course": "Course Name",
|
| 558 |
+
"code": "Course Code",
|
| 559 |
+
"category": "Requirement Category",
|
| 560 |
+
"term": "Term",
|
| 561 |
+
"credits": "inProgress or credit value",
|
| 562 |
+
"grade_level": "Grade Level"
|
| 563 |
+
}}
|
| 564 |
+
],
|
| 565 |
+
"course_history": [
|
| 566 |
+
{{
|
| 567 |
+
"requirement_category": "Category Code",
|
| 568 |
+
"school_year": "Year Taken",
|
| 569 |
+
"grade_level": "Grade Level",
|
| 570 |
+
"course_code": "Course Code",
|
| 571 |
+
"description": "Course Description",
|
| 572 |
+
"term": "Term",
|
| 573 |
+
"grade": "Grade Received",
|
| 574 |
+
"credits": "Credits Earned"
|
| 575 |
+
}}
|
| 576 |
+
],
|
| 577 |
+
"graduation_status": {{
|
| 578 |
+
"total_required_credits": 24.0,
|
| 579 |
+
"total_completed_credits": 24.0,
|
| 580 |
+
"percent_complete": 100.0,
|
| 581 |
+
"remaining_credits": 0.0,
|
| 582 |
+
"on_track": true
|
| 583 |
+
}},
|
| 584 |
+
"format": "miami_dade or standard"
|
| 585 |
+
}}
|
| 586 |
|
| 587 |
Transcript Text:
|
| 588 |
{text}
|
|
|
|
| 597 |
raise gr.Error(f"Model failed to load. {model_loader.error or 'Please try loading a model first.'}")
|
| 598 |
|
| 599 |
# Tokenize and generate response
|
| 600 |
+
inputs = tokenizer(prompt, return_tensors="pt", max_length=4096, truncation=True).to(model_loader.device)
|
| 601 |
if progress:
|
| 602 |
progress(0.4)
|
| 603 |
|
|
|
|
| 605 |
**inputs,
|
| 606 |
max_new_tokens=2000,
|
| 607 |
temperature=0.1,
|
| 608 |
+
do_sample=True,
|
| 609 |
+
top_p=0.9,
|
| 610 |
+
repetition_penalty=1.1
|
| 611 |
)
|
| 612 |
if progress:
|
| 613 |
progress(0.8)
|
|
|
|
| 620 |
json_str = response.split('```json')[1].split('```')[0].strip()
|
| 621 |
parsed_data = json.loads(json_str)
|
| 622 |
except (IndexError, json.JSONDecodeError):
|
| 623 |
+
# Fallback: Try to find JSON in the response
|
| 624 |
+
json_match = re.search(r'\{.*\}', response, re.DOTALL)
|
| 625 |
+
if json_match:
|
| 626 |
+
parsed_data = json.loads(json_match.group())
|
| 627 |
+
else:
|
| 628 |
+
raise ValueError("Could not extract JSON from AI response")
|
| 629 |
+
|
| 630 |
+
# Validate the parsed data structure
|
| 631 |
+
required_keys = ["student_info", "requirements", "course_history"]
|
| 632 |
+
if not all(key in parsed_data for key in required_keys):
|
| 633 |
+
raise ValueError("AI returned incomplete data structure")
|
| 634 |
|
| 635 |
if progress:
|
| 636 |
progress(1.0)
|
|
|
|
| 1076 |
|
| 1077 |
def _format_transcript(self, transcript: Dict) -> str:
|
| 1078 |
"""Format transcript data for display."""
|
| 1079 |
+
if not transcript or "course_history" not in transcript:
|
| 1080 |
return "_No transcript information available_"
|
| 1081 |
|
| 1082 |
display = "#### Course History\n"
|
| 1083 |
+
courses_by_year = defaultdict(list)
|
| 1084 |
+
for course in transcript.get("course_history", []):
|
| 1085 |
+
if course.get("school_year"):
|
| 1086 |
+
courses_by_year[course["school_year"]].append(course)
|
| 1087 |
+
|
| 1088 |
+
if courses_by_year:
|
| 1089 |
+
for year in sorted(courses_by_year.keys()):
|
| 1090 |
+
display += f"\n**{year}**\n"
|
| 1091 |
+
for course in courses_by_year[year]:
|
| 1092 |
+
display += f"- {course.get('course_code', '')} {course.get('description', 'Unnamed course')}"
|
| 1093 |
if 'grade' in course and course['grade']:
|
| 1094 |
display += f" (Grade: {course['grade']})"
|
| 1095 |
if 'credits' in course:
|
| 1096 |
display += f" | Credits: {course['credits']}"
|
| 1097 |
+
display += f" | Category: {course.get('requirement_category', 'N/A')}\n"
|
| 1098 |
+
|
| 1099 |
+
if 'student_info' in transcript:
|
| 1100 |
+
student = transcript['student_info']
|
| 1101 |
+
display += "\n**Academic Summary**\n"
|
| 1102 |
+
display += f"- Unweighted GPA: {student.get('unweighted_gpa', 'N/A')}\n"
|
| 1103 |
+
display += f"- Weighted GPA: {student.get('weighted_gpa', 'N/A')}\n"
|
| 1104 |
+
display += f"- Total Credits: {student.get('total_credits', 'N/A')}\n"
|
| 1105 |
+
|
| 1106 |
+
if 'graduation_status' in transcript:
|
| 1107 |
+
status = transcript['graduation_status']
|
| 1108 |
+
display += "\n**Graduation Progress**\n"
|
| 1109 |
+
display += f"- Completion: {status.get('percent_complete', 0)}%\n"
|
| 1110 |
+
display += f"- Credits Required: {status.get('total_required_credits', 0)}\n"
|
| 1111 |
+
display += f"- Credits Completed: {status.get('total_completed_credits', 0)}\n"
|
| 1112 |
+
display += f"- On Track: {'Yes' if status.get('on_track', False) else 'No'}\n"
|
| 1113 |
|
| 1114 |
return display
|
| 1115 |
|
|
|
|
| 1136 |
# Extract profile information
|
| 1137 |
name = profile.get("name", "there")
|
| 1138 |
learning_style = profile.get("learning_style", "")
|
| 1139 |
+
grade_level = profile.get("transcript", {}).get("student_info", {}).get("current_grade", "unknown")
|
| 1140 |
+
gpa = profile.get("transcript", {}).get("student_info", {})
|
| 1141 |
interests = profile.get("interests", "")
|
| 1142 |
+
courses = profile.get("transcript", {}).get("course_history", [])
|
| 1143 |
favorites = profile.get("favorites", {})
|
| 1144 |
|
| 1145 |
# Process message with context
|
|
|
|
| 1259 |
|
| 1260 |
def _generate_grade_advice(self, profile: Dict) -> str:
|
| 1261 |
"""Generate response about grades and GPA."""
|
| 1262 |
+
gpa = profile.get("transcript", {}).get("student_info", {})
|
| 1263 |
+
courses = profile.get("transcript", {}).get("course_history", [])
|
| 1264 |
|
| 1265 |
response = (f"Your GPA information:\n"
|
| 1266 |
+
f"- Unweighted: {gpa.get('unweighted_gpa', 'N/A')}\n"
|
| 1267 |
+
f"- Weighted: {gpa.get('weighted_gpa', 'N/A')}\n\n")
|
| 1268 |
|
| 1269 |
# Identify any failing grades
|
| 1270 |
weak_subjects = []
|
| 1271 |
+
for course in courses:
|
| 1272 |
+
if course.get('grade', '').upper() in ['D', 'F']:
|
| 1273 |
+
weak_subjects.append(f"{course.get('course_code', '')} {course.get('description', 'Unknown course')}")
|
|
|
|
| 1274 |
|
| 1275 |
if weak_subjects:
|
| 1276 |
response += ("**Areas for Improvement**:\n"
|
|
|
|
| 1299 |
|
| 1300 |
def _generate_course_advice(self, profile: Dict) -> str:
|
| 1301 |
"""Generate response about courses."""
|
| 1302 |
+
courses = profile.get("transcript", {}).get("course_history", [])
|
| 1303 |
+
grade_level = profile.get("transcript", {}).get("student_info", {}).get("current_grade", "unknown")
|
| 1304 |
+
|
| 1305 |
+
response = "Here's a summary of your courses by year:\n"
|
| 1306 |
+
courses_by_year = defaultdict(list)
|
| 1307 |
+
for course in courses:
|
| 1308 |
+
if course.get("school_year"):
|
| 1309 |
+
courses_by_year[course["school_year"]].append(course)
|
| 1310 |
+
|
| 1311 |
+
for year in sorted(courses_by_year.keys()):
|
| 1312 |
+
response += f"\n**{year}**:\n"
|
| 1313 |
+
for course in courses_by_year[year]:
|
| 1314 |
+
response += f"- {course.get('course_code', '')} {course.get('description', 'Unnamed course')}"
|
| 1315 |
if 'grade' in course:
|
| 1316 |
response += f" (Grade: {course['grade']})"
|
| 1317 |
response += "\n"
|