Dannyar608 commited on
Commit
d3a1938
·
verified ·
1 Parent(s): 058e198

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +218 -129
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
- # Extract student info
301
- student_match = re.search(
302
- 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+)",
303
- text, re.DOTALL
304
- )
 
 
 
 
 
305
 
306
- if student_match:
307
- self.student_data = {
308
- "id": student_match.group(1).strip(),
309
- "name": student_match.group(2).replace(",", ", ").strip(),
310
- "current_grade": student_match.group(3),
311
- "graduation_year": student_match.group(4),
312
- "unweighted_gpa": float(student_match.group(5)),
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
- # Extract requirements
319
- self.requirements = {}
320
- req_section = re.search(
321
- r"Code Description Required Waived Completed Status(.*?)Total\s+\d+\.\d+\s+\d+\.\d+\s+\d+\.\d+\s+\d+%",
322
- text, re.DOTALL
323
- )
324
- if req_section:
325
- req_lines = req_section.group(1).strip().splitlines()
326
- for line in req_lines:
327
- req_match = re.match(r"([A-Z]-[^\s]+)\s+(.+?)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+)%", line.strip())
328
- if req_match:
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
- # Extract course history (simplified for now)
339
- self.course_history = []
340
- course_pattern = re.compile(
341
- 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+)",
342
- re.DOTALL
343
- )
344
- for match in course_pattern.finditer(text):
345
- self.course_history.append({
346
- "requirement_category": match.group(1),
347
- "school_year": match.group(2),
348
- "grade_level": match.group(3),
349
- "course_code": match.group(4),
350
- "description": match.group(5).strip(),
351
- "term": match.group(6),
352
- "district_number": match.group(7),
353
- "grade": match.group(8),
354
- "inclusion_status": match.group(9),
355
- "credits": match.group(10)
356
- })
 
 
 
 
357
 
358
- # Extract in-progress
359
- self._extract_current_courses()
360
- self._calculate_completion()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
362
- return {
363
- "student_info": self.student_data,
364
- "requirements": self.requirements,
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
- except Exception as e:
372
- logging.error(f"Error parsing transcript: {str(e)}")
373
- raise ValueError(f"Couldn't parse transcript: {str(e)}")
 
 
 
 
 
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 if isinstance(c["credits"], str) and c["credits"].lower() == "inprogress"
 
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
- year_key = course.get("school_year", course.get("completion_date", "Unknown"))
465
- courses_by_year[year_key].append(course)
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('subject', 'N/A')} | "
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
- - Current grade level
510
- - Weighted GPA (if available)
511
- - Unweighted GPA (if available)
512
- - Total credits earned
513
- - Community service hours (if available)
514
- - List of all courses with:
515
- * Course code
516
- * Course name
517
- * Grade received
518
- * Credits earned
519
- * Year/semester taken
520
- * Grade level when taken
521
- - Graduation requirements status
522
- Return the data in JSON format.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: Extract JSON-like substring
559
- json_str = re.search(r'\{.*\}', response, re.DOTALL).group()
560
- parsed_data = json.loads(json_str)
 
 
 
 
 
 
 
 
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 "courses" not in transcript:
1007
  return "_No transcript information available_"
1008
 
1009
  display = "#### Course History\n"
1010
- courses_by_grade = transcript["courses"]
1011
-
1012
- if isinstance(courses_by_grade, dict):
1013
- for grade in sorted(courses_by_grade.keys(), key=lambda x: int(x) if x.isdigit() else x):
1014
- display += f"\n**Grade {grade}**\n"
1015
- for course in courses_by_grade[grade]:
1016
- display += f"- {course.get('code', '')} {course.get('name', 'Unnamed course')}"
 
 
 
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" | Year: {course.get('year', 'N/A')}\n"
1022
-
1023
- if 'gpa' in transcript:
1024
- gpa = transcript['gpa']
1025
- display += "\n**GPA**\n"
1026
- display += f"- Unweighted: {gpa.get('unweighted', 'N/A')}\n"
1027
- display += f"- Weighted: {gpa.get('weighted', 'N/A')}\n"
 
 
 
 
 
 
 
 
 
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("grade_level", "unknown")
1055
- gpa = profile.get("transcript", {}).get("gpa", {})
1056
  interests = profile.get("interests", "")
1057
- courses = profile.get("transcript", {}).get("courses", {})
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("gpa", {})
1178
- courses = profile.get("transcript", {}).get("courses", {})
1179
 
1180
  response = (f"Your GPA information:\n"
1181
- f"- Unweighted: {gpa.get('unweighted', 'N/A')}\n"
1182
- f"- Weighted: {gpa.get('weighted', 'N/A')}\n\n")
1183
 
1184
  # Identify any failing grades
1185
  weak_subjects = []
1186
- for grade_level, course_list in courses.items():
1187
- for course in course_list:
1188
- if course.get('grade', '').upper() in ['D', 'F']:
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("courses", {})
1219
- grade_level = profile.get("transcript", {}).get("grade_level", "unknown")
1220
-
1221
- response = "Here's a summary of your courses:\n"
1222
- for grade in sorted(courses.keys(), key=lambda x: int(x) if x.isdigit() else x):
1223
- response += f"\n**Grade {grade}**:\n"
1224
- for course in courses[grade]:
1225
- response += f"- {course.get('code', '')} {course.get('name', 'Unnamed course')}"
 
 
 
 
 
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"