Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -24,6 +24,7 @@ from functools import lru_cache
|
|
| 24 |
import hashlib
|
| 25 |
from concurrent.futures import ThreadPoolExecutor
|
| 26 |
from pydantic import BaseModel
|
|
|
|
| 27 |
|
| 28 |
# ========== CONFIGURATION ==========
|
| 29 |
PROFILES_DIR = "student_profiles"
|
|
@@ -440,8 +441,171 @@ class TranscriptParser:
|
|
| 440 |
|
| 441 |
raise ValueError("Could not identify course information in transcript")
|
| 442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
def parse_transcript(file_obj, progress=gr.Progress()) -> Tuple[str, Optional[Dict]]:
|
| 444 |
-
"""Process transcript file and return
|
| 445 |
try:
|
| 446 |
if not file_obj:
|
| 447 |
raise gr.Error("Please upload a transcript file first (PDF or image)")
|
|
@@ -477,11 +641,35 @@ def parse_transcript(file_obj, progress=gr.Progress()) -> Tuple[str, Optional[Di
|
|
| 477 |
except Exception as e:
|
| 478 |
raise ValueError(f"Couldn't parse transcript content. Error: {str(e)}")
|
| 479 |
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
|
| 484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
|
| 486 |
except Exception as e:
|
| 487 |
error_msg = f"Error processing transcript: {str(e)}"
|
|
@@ -938,10 +1126,13 @@ def create_interface():
|
|
| 938 |
with gr.Column(scale=2):
|
| 939 |
transcript_output = gr.Textbox(
|
| 940 |
label="Analysis Results",
|
| 941 |
-
lines=
|
| 942 |
interactive=False,
|
| 943 |
elem_classes="transcript-results"
|
| 944 |
)
|
|
|
|
|
|
|
|
|
|
| 945 |
transcript_data = gr.State()
|
| 946 |
|
| 947 |
file_input.change(
|
|
@@ -954,14 +1145,22 @@ def create_interface():
|
|
| 954 |
outputs=[file_error, transcript_output]
|
| 955 |
)
|
| 956 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 957 |
upload_btn.click(
|
| 958 |
-
fn=
|
| 959 |
inputs=[file_input, tab_completed],
|
| 960 |
-
outputs=[transcript_output, transcript_data]
|
| 961 |
-
).then(
|
| 962 |
-
fn=lambda: {0: True},
|
| 963 |
-
inputs=None,
|
| 964 |
-
outputs=tab_completed
|
| 965 |
).then(
|
| 966 |
fn=lambda: gr.update(elem_classes="completed-tab"),
|
| 967 |
outputs=step1
|
|
|
|
| 24 |
import hashlib
|
| 25 |
from concurrent.futures import ThreadPoolExecutor
|
| 26 |
from pydantic import BaseModel
|
| 27 |
+
import plotly.express as px
|
| 28 |
|
| 29 |
# ========== CONFIGURATION ==========
|
| 30 |
PROFILES_DIR = "student_profiles"
|
|
|
|
| 441 |
|
| 442 |
raise ValueError("Could not identify course information in transcript")
|
| 443 |
|
| 444 |
+
# ========== ENHANCED ANALYSIS FUNCTIONS ==========
|
| 445 |
+
def analyze_gpa(parsed_data: Dict) -> str:
|
| 446 |
+
try:
|
| 447 |
+
gpa = float(parsed_data['student_info']['weighted_gpa'])
|
| 448 |
+
if gpa >= 4.5:
|
| 449 |
+
return "π Excellent GPA! You're in the top tier of students."
|
| 450 |
+
elif gpa >= 3.5:
|
| 451 |
+
return "π Good GPA! You're performing above average."
|
| 452 |
+
elif gpa >= 2.5:
|
| 453 |
+
return "βΉοΈ Average GPA. Consider focusing on improvement in weaker areas."
|
| 454 |
+
else:
|
| 455 |
+
return "β οΈ Below average GPA. Please consult with your academic advisor."
|
| 456 |
+
except (TypeError, ValueError, KeyError, AttributeError):
|
| 457 |
+
return "β Could not analyze GPA."
|
| 458 |
+
|
| 459 |
+
def analyze_graduation_status(parsed_data: Dict) -> str:
|
| 460 |
+
try:
|
| 461 |
+
total_required = sum(
|
| 462 |
+
float(req['required'])
|
| 463 |
+
for req in parsed_data['requirements'].values()
|
| 464 |
+
if req.get('required') and str(req['required']).replace('.', '').isdigit()
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
total_completed = sum(
|
| 468 |
+
float(req['completed'])
|
| 469 |
+
for req in parsed_data['requirements'].values()
|
| 470 |
+
if req.get('completed') and str(req['completed']).replace('.', '').isdigit()
|
| 471 |
+
)
|
| 472 |
+
|
| 473 |
+
completion_percentage = (total_completed / total_required) * 100 if total_required > 0 else 0
|
| 474 |
+
|
| 475 |
+
if completion_percentage >= 100:
|
| 476 |
+
return "π You've met all graduation requirements!"
|
| 477 |
+
elif completion_percentage >= 80:
|
| 478 |
+
return f"β
You've completed {completion_percentage:.1f}% of requirements. Almost there!"
|
| 479 |
+
elif completion_percentage >= 50:
|
| 480 |
+
return f"π You've completed {completion_percentage:.1f}% of requirements. Keep working!"
|
| 481 |
+
else:
|
| 482 |
+
return f"β οΈ You've only completed {completion_percentage:.1f}% of requirements. Please meet with your counselor."
|
| 483 |
+
except (ZeroDivisionError, TypeError, KeyError, AttributeError):
|
| 484 |
+
return "β Could not analyze graduation status."
|
| 485 |
+
|
| 486 |
+
def generate_advice(parsed_data: Dict) -> str:
|
| 487 |
+
advice = []
|
| 488 |
+
|
| 489 |
+
# GPA advice
|
| 490 |
+
try:
|
| 491 |
+
gpa = float(parsed_data['student_info']['weighted_gpa'])
|
| 492 |
+
if gpa < 3.0:
|
| 493 |
+
advice.append("π Your GPA could improve. Consider:\n- Seeking tutoring for challenging subjects\n- Meeting with teachers during office hours\n- Developing better study habits")
|
| 494 |
+
except (TypeError, ValueError, KeyError, AttributeError):
|
| 495 |
+
pass
|
| 496 |
+
|
| 497 |
+
# Community service advice
|
| 498 |
+
try:
|
| 499 |
+
service_hours = int(parsed_data['student_info']['community_service_hours'])
|
| 500 |
+
if service_hours < 100:
|
| 501 |
+
advice.append("π€ Consider more community service:\n- Many colleges value 100+ hours\n- Look for opportunities that align with your interests")
|
| 502 |
+
except (TypeError, ValueError, KeyError, AttributeError):
|
| 503 |
+
pass
|
| 504 |
+
|
| 505 |
+
# Missing requirements advice
|
| 506 |
+
try:
|
| 507 |
+
missing_reqs = [
|
| 508 |
+
req for code, req in parsed_data['requirements'].items()
|
| 509 |
+
if float(req['percent_complete']) < 100 and not code.startswith("Z-Assessment")
|
| 510 |
+
]
|
| 511 |
+
|
| 512 |
+
if missing_reqs:
|
| 513 |
+
req_list = "\n- ".join([f"{code}: {req['description']}" for code, req in missing_reqs])
|
| 514 |
+
advice.append(f"π Focus on completing these requirements:\n- {req_list}")
|
| 515 |
+
except (TypeError, ValueError, KeyError, AttributeError):
|
| 516 |
+
pass
|
| 517 |
+
|
| 518 |
+
# Course rigor advice
|
| 519 |
+
try:
|
| 520 |
+
ap_count = sum(1 for course in parsed_data['course_history'] if "Advanced Placement" in course['description'])
|
| 521 |
+
if ap_count < 3:
|
| 522 |
+
advice.append("π§ Consider taking more challenging courses:\n- AP/IB courses can strengthen college applications\n- Shows academic rigor to admissions officers")
|
| 523 |
+
except (TypeError, KeyError, AttributeError):
|
| 524 |
+
pass
|
| 525 |
+
|
| 526 |
+
return "\n\n".join(advice) if advice else "π― You're on track! Keep up the good work."
|
| 527 |
+
|
| 528 |
+
def generate_college_recommendations(parsed_data: Dict) -> str:
|
| 529 |
+
try:
|
| 530 |
+
gpa = float(parsed_data['student_info']['weighted_gpa'])
|
| 531 |
+
ap_count = sum(1 for course in parsed_data['course_history'] if "Advanced Placement" in course['description'])
|
| 532 |
+
service_hours = int(parsed_data['student_info']['community_service_hours']) if parsed_data['student_info'].get('community_service_hours') else 0
|
| 533 |
+
|
| 534 |
+
recommendations = []
|
| 535 |
+
|
| 536 |
+
if gpa >= 4.0 and ap_count >= 5:
|
| 537 |
+
recommendations.append("ποΈ Reach Schools: Ivy League, Stanford, MIT, etc.")
|
| 538 |
+
if gpa >= 3.7:
|
| 539 |
+
recommendations.append("π Competitive Schools: Top public universities, selective private colleges")
|
| 540 |
+
if gpa >= 3.0:
|
| 541 |
+
recommendations.append("π Good Match Schools: State flagship universities, many private colleges")
|
| 542 |
+
if gpa >= 2.0:
|
| 543 |
+
recommendations.append("π« Safety Schools: Community colleges, open admission universities")
|
| 544 |
+
|
| 545 |
+
# Add scholarship opportunities
|
| 546 |
+
if gpa >= 3.5:
|
| 547 |
+
recommendations.append("\nπ° Scholarship Opportunities:\n- Bright Futures (Florida)\n- National Merit Scholarship\n- College-specific merit scholarships")
|
| 548 |
+
elif gpa >= 3.0:
|
| 549 |
+
recommendations.append("\nπ° Scholarship Opportunities:\n- Local community scholarships\n- Special interest scholarships\n- First-generation student programs")
|
| 550 |
+
|
| 551 |
+
# Add extracurricular advice
|
| 552 |
+
if service_hours < 50:
|
| 553 |
+
recommendations.append("\nπ Extracurricular Advice:\n- Colleges value depth over breadth in activities\n- Consider leadership roles in 1-2 organizations")
|
| 554 |
+
|
| 555 |
+
if not recommendations:
|
| 556 |
+
return "β Not enough data to generate college recommendations"
|
| 557 |
+
|
| 558 |
+
return "Based on your academic profile:\n\n" + "\n\n".join(recommendations)
|
| 559 |
+
except:
|
| 560 |
+
return "β Could not generate college recommendations"
|
| 561 |
+
|
| 562 |
+
def create_gpa_visualization(parsed_data: Dict):
|
| 563 |
+
try:
|
| 564 |
+
gpa_data = {
|
| 565 |
+
"Type": ["Weighted GPA", "Unweighted GPA"],
|
| 566 |
+
"Value": [
|
| 567 |
+
float(parsed_data['student_info']['weighted_gpa']),
|
| 568 |
+
float(parsed_data['student_info']['unweighted_gpa'])
|
| 569 |
+
]
|
| 570 |
+
}
|
| 571 |
+
df = pd.DataFrame(gpa_data)
|
| 572 |
+
fig = px.bar(df, x="Type", y="Value", title="GPA Comparison",
|
| 573 |
+
color="Type", text="Value",
|
| 574 |
+
color_discrete_sequence=["#4C78A8", "#F58518"])
|
| 575 |
+
fig.update_traces(texttemplate='%{text:.2f}', textposition='outside')
|
| 576 |
+
fig.update_layout(yaxis_range=[0,5], uniformtext_minsize=8, uniformtext_mode='hide')
|
| 577 |
+
return fig
|
| 578 |
+
except:
|
| 579 |
+
return None
|
| 580 |
+
|
| 581 |
+
def create_requirements_visualization(parsed_data: Dict):
|
| 582 |
+
try:
|
| 583 |
+
req_data = []
|
| 584 |
+
for code, req in parsed_data['requirements'].items():
|
| 585 |
+
if req.get('percent_complete'):
|
| 586 |
+
completion = float(req['percent_complete'])
|
| 587 |
+
req_data.append({
|
| 588 |
+
"Requirement": code,
|
| 589 |
+
"Completion (%)": completion,
|
| 590 |
+
"Status": "Complete" if completion >= 100 else "Incomplete"
|
| 591 |
+
})
|
| 592 |
+
|
| 593 |
+
if not req_data:
|
| 594 |
+
return None
|
| 595 |
+
|
| 596 |
+
df = pd.DataFrame(req_data)
|
| 597 |
+
fig = px.bar(df, x="Requirement", y="Completion (%)",
|
| 598 |
+
title="Graduation Requirements Completion",
|
| 599 |
+
color="Status",
|
| 600 |
+
color_discrete_map={"Complete": "#2CA02C", "Incomplete": "#D62728"},
|
| 601 |
+
hover_data=["Requirement"])
|
| 602 |
+
fig.update_layout(xaxis={'categoryorder':'total descending'})
|
| 603 |
+
return fig
|
| 604 |
+
except:
|
| 605 |
+
return None
|
| 606 |
+
|
| 607 |
def parse_transcript(file_obj, progress=gr.Progress()) -> Tuple[str, Optional[Dict]]:
|
| 608 |
+
"""Process transcript file and return analysis results"""
|
| 609 |
try:
|
| 610 |
if not file_obj:
|
| 611 |
raise gr.Error("Please upload a transcript file first (PDF or image)")
|
|
|
|
| 641 |
except Exception as e:
|
| 642 |
raise ValueError(f"Couldn't parse transcript content. Error: {str(e)}")
|
| 643 |
|
| 644 |
+
# Perform enhanced analyses
|
| 645 |
+
gpa_analysis = analyze_gpa(parsed_data)
|
| 646 |
+
grad_status = analyze_graduation_status(parsed_data)
|
| 647 |
+
advice = generate_advice(parsed_data)
|
| 648 |
+
college_recs = generate_college_recommendations(parsed_data)
|
| 649 |
+
gpa_viz = create_gpa_visualization(parsed_data)
|
| 650 |
+
req_viz = create_requirements_visualization(parsed_data)
|
| 651 |
|
| 652 |
+
# Format results for display
|
| 653 |
+
results = [
|
| 654 |
+
f"π GPA Analysis: {gpa_analysis}",
|
| 655 |
+
f"π Graduation Status: {grad_status}",
|
| 656 |
+
f"π‘ Recommendations:\n{advice}",
|
| 657 |
+
f"π« College Recommendations:\n{college_recs}"
|
| 658 |
+
]
|
| 659 |
+
|
| 660 |
+
# Store all analysis results in the parsed_data
|
| 661 |
+
parsed_data['analysis'] = {
|
| 662 |
+
'gpa_analysis': gpa_analysis,
|
| 663 |
+
'grad_status': grad_status,
|
| 664 |
+
'advice': advice,
|
| 665 |
+
'college_recs': college_recs,
|
| 666 |
+
'visualizations': {
|
| 667 |
+
'gpa_viz': gpa_viz,
|
| 668 |
+
'req_viz': req_viz
|
| 669 |
+
}
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
+
return "\n\n".join(results), parsed_data
|
| 673 |
|
| 674 |
except Exception as e:
|
| 675 |
error_msg = f"Error processing transcript: {str(e)}"
|
|
|
|
| 1126 |
with gr.Column(scale=2):
|
| 1127 |
transcript_output = gr.Textbox(
|
| 1128 |
label="Analysis Results",
|
| 1129 |
+
lines=10,
|
| 1130 |
interactive=False,
|
| 1131 |
elem_classes="transcript-results"
|
| 1132 |
)
|
| 1133 |
+
with gr.Row():
|
| 1134 |
+
gpa_viz = gr.Plot(label="GPA Visualization", visible=False)
|
| 1135 |
+
req_viz = gr.Plot(label="Requirements Visualization", visible=False)
|
| 1136 |
transcript_data = gr.State()
|
| 1137 |
|
| 1138 |
file_input.change(
|
|
|
|
| 1145 |
outputs=[file_error, transcript_output]
|
| 1146 |
)
|
| 1147 |
|
| 1148 |
+
def process_and_visualize(file_obj, tab_status):
|
| 1149 |
+
results, data = parse_transcript(file_obj)
|
| 1150 |
+
|
| 1151 |
+
# Update visualizations
|
| 1152 |
+
gpa_viz_update = gr.update(visible=data.get('analysis', {}).get('visualizations', {}).get('gpa_viz') is not None)
|
| 1153 |
+
req_viz_update = gr.update(visible=data.get('analysis', {}).get('visualizations', {}).get('req_viz') is not None)
|
| 1154 |
+
|
| 1155 |
+
# Update tab completion status
|
| 1156 |
+
tab_status[0] = True
|
| 1157 |
+
|
| 1158 |
+
return results, data, gpa_viz_update, req_viz_update, tab_status
|
| 1159 |
+
|
| 1160 |
upload_btn.click(
|
| 1161 |
+
fn=process_and_visualize,
|
| 1162 |
inputs=[file_input, tab_completed],
|
| 1163 |
+
outputs=[transcript_output, transcript_data, gpa_viz, req_viz, tab_completed]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1164 |
).then(
|
| 1165 |
fn=lambda: gr.update(elem_classes="completed-tab"),
|
| 1166 |
outputs=step1
|