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
|