ctizzzy0/COLLEGE
0
1 2# CollegeGenius: Ultimate College Apps Copilot (HF Spaces ready)3# ---------------------------------------------------------------4# Features (all-in-one, ~"holy grail" for college apps):5# - Essay Coach: structure/readability analysis, passive voice, cliché detector, thesis finder, originality heuristic, rubric scoring, outline helper6# - Resume/Activity Analyzer: bullet quality, action verbs, quantified impact check, STAR pattern hints, gap finder7# - College Matcher: reach/target/safety based on GPA/test scores, interests, location; tiny builtin dataset + CSV import8# - Spike Builder: turn interests into standout projects with timelines, tasks; export ICS calendar9# - Interview Practice: randomized behavioral & major-specific questions, WPM tracker, sentiment heuristics10# - OCR & Typed PDF: import essays/resumes from images/PDF; basic fallback if OCR libs missing11# - Portfolio Report: generate a polished PDF of insights, charts, and next-steps12# - Privacy-first: runs local in the Space; exports logs/report to your account storage13#14# Made to be "insanely impressive" for demos and practical for real use.15#16# -----------------------------17# Requirements (add to Spaces)18# -----------------------------19# gradio20# numpy21# pandas22# matplotlib23# fpdf24# pypdf25# sentence-transformers26# scikit-learn27# textstat28# easyocr (optional; will gracefully degrade if unavailable)29#30# -----------------------------31# MIT License (trimmed)32# -----------------------------33# Copyright (c) 2025 CollegeGenius34# Permission is hereby granted, free of charge, to any person obtaining a copy35# of this software and associated documentation files...36 37import os38import re39import io40import json41import math42import time43import uuid44import base6445import random46import string47import textwrap48import datetime as dt49from collections import Counter, defaultdict50 51import numpy as np52import pandas as pd53import matplotlib.pyplot as plt54 55import gradio as gr56from fpdf import FPDF57 58# PDFs59from pypdf import PdfReader60 61# NLP (semantic similarity & TF-IDF)62from sklearn.feature_extraction.text import TfidfVectorizer63from sklearn.metrics.pairwise import cosine_similarity64 65# Readability66try:67 import textstat68except Exception:69 textstat = None70 71# Embeddings72_EMBEDDER = None73try:74 from sentence_transformers import SentenceTransformer75 _EMBEDDER = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')76except Exception:77 _EMBEDDER = None78 79# OCR (optional)80_OCR = None81try:82 import easyocr83 _OCR = easyocr.Reader(['en'], gpu=False)84except Exception:85 _OCR = None86 87APP_ID = "college_genius"88DATA_DIR = "cg_data"89os.makedirs(DATA_DIR, exist_ok=True)90 91LOG_CSV = os.path.join(DATA_DIR, "runs.csv")92if not os.path.exists(LOG_CSV):93 pd.DataFrame(columns=[94 "timestamp","user_id","tool","score","meta"95 ]).to_csv(LOG_CSV, index=False)96 97ESSAY_DB = os.path.join(DATA_DIR, "essays.jsonl")98if not os.path.exists(ESSAY_DB):99 open(ESSAY_DB, "w").close()100 101RESUME_DB = os.path.join(DATA_DIR, "resumes.jsonl")102if not os.path.exists(RESUME_DB):103 open(RESUME_DB, "w").close()104 105# ----------------------------106# Tiny demo college dataset107# ----------------------------108_BUILTIN_COLLEGES = pd.DataFrame([109 # name, state, avg_gpa, sat_mid, act_mid, acceptance_rate, stem_strength(1-5), humanities_strength(1-5)110 ("Massachusetts Institute of Technology", "MA", 3.9, 1550, 35, 0.04, 5, 4),111 ("Stanford University", "CA", 3.95, 1540, 35, 0.04, 5, 5),112 ("Harvard University", "MA", 3.95, 1540, 35, 0.04, 5, 5),113 ("University of California, Berkeley", "CA", 3.85, 1470, 33, 0.15, 5, 4),114 ("University of Michigan, Ann Arbor", "MI", 3.85, 1470, 33, 0.20, 5, 4),115 ("Georgia Tech", "GA", 3.85, 1480, 34, 0.17, 5, 3),116 ("University of Texas at Austin", "TX", 3.8, 1420, 32, 0.31, 4, 4),117 ("Carnegie Mellon University", "PA", 3.9, 1530, 35, 0.14, 5, 4),118 ("Princeton University", "NJ", 3.95, 1540, 35, 0.05, 5, 5),119 ("Yale University", "CT", 3.95, 1540, 35, 0.05, 5, 5),120 ("Brown University", "RI", 3.9, 1510, 34, 0.06, 4, 5),121 ("Duke University", "NC", 3.9, 1520, 34, 0.07, 5, 5),122 ("Northwestern University", "IL", 3.9, 1500, 34, 0.07, 4, 5),123 ("New York University", "NY", 3.7, 1450, 33, 0.13, 4, 5),124 ("University of Florida", "FL", 3.8, 1400, 31, 0.31, 4, 3),125 ("Purdue University", "IN", 3.7, 1380, 30, 0.53, 4, 3),126 ("Virginia Tech", "VA", 3.7, 1360, 30, 0.57, 4, 3),127 ("University of Washington", "WA", 3.75, 1410, 32, 0.48, 4, 4),128 ("UIUC", "IL", 3.75, 1420, 32, 0.45, 5, 3),129 ("UCLA", "CA", 3.9, 1500, 34, 0.09, 4, 5)130], columns=["name","state","avg_gpa","sat_mid","act_mid","acceptance_rate","stem_strength","humanities_strength"])131 132# ----------------------------133# Heuristics / lexicons134# ----------------------------135C_LICHES = [136 "ever since I was a child", "since the dawn of time", "follow my dreams",137 "changed my life forever", "made me who I am today", "I have always loved",138 "I learned the true meaning", "I realized that anything is possible",139 "the importance of hard work", "the power of perseverance"140]141 142ACTION_VERBS = [143 "led","built","created","launched","founded","organized","architected",144 "automated","designed","initiated","streamlined","optimized","developed",145 "engineered","authored","deployed","scaled","improved","won","achieved",146 "delivered","analyzed","spearheaded","directed","mentored","taught",147 "coordinated","presented","implemented","transformed","secured"148]149 150MAJOR_QUESTIONS = {151 "Computer Science": [152 "Tell me about a time you debugged a difficult problem.",153 "What's a software project you're proud of and why?",154 "Explain an algorithm you like to a non-technical audience."155 ],156 "Business": [157 "Describe a time you influenced someone without authority.",158 "How did you analyze data to drive a decision?",159 "Pitch a product for college students."160 ],161 "Biology": [162 "What experiment taught you the most?",163 "How do you evaluate the quality of a scientific source?",164 "Explain CRISPR at a high level."165 ],166 "Humanities": [167 "How has a book changed how you see the world?",168 "Tell me about a debate that sharpened your thinking.",169 "What does good writing mean to you?"170 ]171}172 173SPIKE_IDEAS = {174 "AI/ML": [175 "Research sprint: build a model for local nonprofit (e.g., demand forecasting).",176 "Open-source contribution: new dataset or evaluation harness.",177 "Content series: write 8-part blog on ML intuition for teens."178 ],179 "Finance": [180 "Run an investing club with monthly backtests and public notes.",181 "Publish a 'teen finance' newsletter with original surveys.",182 "Build a budgeting app prototype for students."183 ],184 "Biotech": [185 "Wet-lab collaboration with a local university on a safe assay.",186 "Bioinformatics pipeline for public dataset; publish a preprint.",187 "Host a bioethics roundtable and publish proceedings."188 ]189}190 191# ----------------------------192# Utilities193# ----------------------------194def _now():195 return dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")196 197def _clean(text: str) -> str:198 if not isinstance(text, str):199 return ""200 return re.sub(r"\s+", " ", text).strip()201 202def _sentences(text: str):203 # Very light sentence splitter204 text = _clean(text)205 if not text:206 return []207 sents = re.split(r"(?<=[.!?])\s+", text)208 return [s for s in sents if s]209 210def _ngrams(words, n=2):211 return [tuple(words[i:i+n]) for i in range(len(words)-n+1)]212 213def _word_count(text: str):214 return len(re.findall(r"[A-Za-z0-9']+", text))215 216def _passive_voice_score(text: str):217 # heuristic: "was|were|is|are|be" + past participle ending with "ed"218 matches = re.findall(r"\b(was|were|is|are|be|been|being)\b\s+\w+ed\b", text, flags=re.I)219 return len(matches)220 221def _readability(text: str):222 if not textstat:223 return {"flesch_reading_ease": None, "smog_index": None, "grade_level": None}224 try:225 return {226 "flesch_reading_ease": round(textstat.flesch_reading_ease(text), 2),227 "smog_index": round(textstat.smog_index(text), 2),228 "grade_level": round(textstat.text_standard(text, float_output=True), 2)229 }230 except Exception:231 return {"flesch_reading_ease": None, "smog_index": None, "grade_level": None}232 233def _embed(texts):234 if not _EMBEDDER:235 # fallback: TF-IDF vectors236 vec = TfidfVectorizer(stop_words="english", max_features=2048)237 X = vec.fit_transform(texts).toarray().astype(np.float32)238 # L2 norm239 X = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-9)240 return X241 E = _EMBEDDER.encode(texts, normalize_embeddings=True)242 return np.array(E)243 244def _cosine(a, b):245 return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9))246 247def _save_log(tool, score, meta):248 df = pd.read_csv(LOG_CSV)249 row = {250 "timestamp": _now(),251 "user_id": "anon",252 "tool": tool,253 "score": score if score is not None else "",254 "meta": json.dumps(meta)[:1000]255 }256 df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)257 df.to_csv(LOG_CSV, index=False)258 259def _extract_text_from_pdf(path: str) -> str:260 try:261 reader_pdf = PdfReader(path)262 pages = []263 for p in reader_pdf.pages:264 pages.append(p.extract_text() or "")265 return "\n".join(pages)266 except Exception:267 return ""268 269def _ocr_any(file) -> str:270 if not file:271 return ""272 path = file if isinstance(file, str) else file.name273 if path.lower().endswith(".pdf"):274 t = _extract_text_from_pdf(path)275 if t.strip():276 return _clean(t)277 if _OCR:278 # attempt OCR on pages converted to images is non-trivial without poppler;279 # instruct user to upload as image280 return "PDF looks scanned/handwritten. Please upload a clear image (JPG/PNG) for OCR."281 return "Could not read PDF. Try exporting as text PDF or image."282 else:283 if _OCR:284 result = _OCR.readtext(path, detail=0, paragraph=True)285 return _clean(" ".join(result))286 return "OCR not available on this Space. Add `easyocr` to requirements or paste text manually."287 288# ----------------------------289# Essay Coach290# ----------------------------291def find_thesis(text: str):292 # naive: look for sentences with causal markers or stance293 candidates = []294 markers = ["because", "therefore", "although", "however", "should", "must", "argue", "believe"]295 for s in _sentences(text):296 lower = s.lower()297 if any(m in lower for m in markers) and 8 < _word_count(s) < 60:298 candidates.append(s)299 if not candidates:300 # pick the longest sentence as a fallback301 sents = sorted(_sentences(text), key=len, reverse=True)302 return sents[0] if sents else ""303 # choose the one with fewest passive patterns304 scored = sorted(candidates, key=lambda s: _passive_voice_score(s))305 return scored[0]306 307def cliché_hits(text: str):308 hits = []309 low = text.lower()310 for c in C_LICHES:311 if c in low:312 hits.append(c)313 return hits314 315def originality_heuristic(new_text: str):316 # compare against user's own past essays for self-plagiarism and repetition317 docs = []318 try:319 with open(ESSAY_DB, "r") as f:320 for line in f:321 try:322 docs.append(json.loads(line)["text"])323 except Exception:324 pass325 except Exception:326 pass327 if not docs:328 return {"max_self_similarity": 0.0, "similar_sample": ""}329 embs = _embed([_clean(new_text)] + [_clean(d) for d in docs])330 sims = cosine_similarity([embs[0]], embs[1:])[0].tolist()331 if not sims:332 return {"max_self_similarity": 0.0, "similar_sample": ""}333 k = int(np.argmax(sims))334 return {"max_self_similarity": round(float(sims[k]), 3), "similar_sample": docs[k][:300]}335 336def essay_rubric(text: str, prompt: str = ""):337 # scores 0-100 via weighted heuristics338 w = {339 "structure": 0.25,340 "clarity": 0.25,341 "style": 0.2,342 "originality": 0.15,343 "mechanics": 0.15344 }345 sents = _sentences(text)346 wc = _word_count(text)347 thesis = find_thesis(text)348 passive = _passive_voice_score(text)349 clichés = cliché_hits(text)350 351 # structure: parag lengths and presence of intro/body/conc heuristics352 paragraphs = [p for p in re.split(r"\n{2,}", text) if _clean(p)]353 avg_par = np.mean([_word_count(p) for p in paragraphs]) if paragraphs else 0354 structure_score = 60355 if 500 <= wc <= 800:356 structure_score += 10357 if 5 <= len(sents) <= 25:358 structure_score += 10359 if 80 <= avg_par <= 180:360 structure_score += 10361 if thesis:362 structure_score += 10363 structure_score = min(100, structure_score)364 365 # clarity: readability & sentence length variance366 read = _readability(text)367 clarity_score = 60368 if read["grade_level"] and 8 <= read["grade_level"] <= 12:369 clarity_score += 15370 if read["flesch_reading_ease"] and read["flesch_reading_ease"] >= 50:371 clarity_score += 10372 if np.std([_word_count(s) for s in sents]) if sents else 0 >= 5:373 clarity_score += 5374 clarity_score = min(100, clarity_score)375 376 # style: passive voice penalties + cliché penalties377 style_score = 85 - min(25, 2*passive) - 5*len(clichés)378 style_score = max(40, min(100, style_score))379 380 # originality: against own corpus381 orig = originality_heuristic(text)382 originality_score = int(100 - 100*orig["max_self_similarity"])383 originality_score = max(30, min(100, originality_score))384 385 # mechanics: naive based on punctuation balance & capitalization386 caps_issues = len(re.findall(r"\bi\b", text)) # lowercase I387 many_exclaims = text.count("!!") + text.count("???")388 mechanics_score = 90 - 5*caps_issues - 3*many_exclaims389 mechanics_score = max(40, min(100, mechanics_score))390 391 total = int(round(392 w["structure"]*structure_score +393 w["clarity"]*clarity_score +394 w["style"]*style_score +395 w["originality"]*originality_score +396 w["mechanics"]*mechanics_score397 ))398 399 feedback = {400 "structure_score": structure_score,401 "clarity_score": clarity_score,402 "style_score": style_score,403 "originality_score": originality_score,404 "mechanics_score": mechanics_score,405 "thesis_guess": thesis,406 "cliche_hits": clichés,407 "readability": read,408 "word_count": wc409 }410 _save_log("essay_coach", total, feedback)411 # persist essay412 try:413 with open(ESSAY_DB, "a") as f:414 f.write(json.dumps({"timestamp": _now(), "text": text, "prompt": prompt})+"\n")415 except Exception:416 pass417 return total, feedback418 419def outline_helper(prompt: str, interests: str, max_sections: int = 6):420 base = [421 "Hook (specific image or moment)",422 "Context (what's at stake, why it mattered)",423 "Catalyst (decision or conflict)",424 "Deepening (what you tried, obstacles, learning)",425 "Turning point (insight/change)",426 "Forward path (how this shapes what you'll do in college)"427 ]428 sections = base[:max_sections]429 # sprinkle interests430 if interests:431 for i in range(len(sections)):432 sections[i] += f" — weave in {interests}"433 return sections434 435# ----------------------------436# Resume / Activity Analyzer437# ----------------------------438def bullet_quality_metrics(bullet: str):439 # action verb, numbers, outcome, passive440 action = any(bullet.lower().startswith(v) for v in ACTION_VERBS)441 nums = bool(re.search(r"\d", bullet))442 outcome = bool(re.search(r"(result|outcome|impact|increased|decreased|grew|reduced|saved|won|revenue|users)", bullet, flags=re.I))443 passive = _passive_voice_score(bullet) > 0444 length_ok = 8 <= _word_count(bullet) <= 30445 score = 60 + 10*action + 10*nums + 10*outcome - 10*passive + 5*length_ok446 score = max(30, min(100, score))447 tags = []448 if action: tags.append("Action verb")449 if nums: tags.append("Quantified")450 if outcome: tags.append("Outcome")451 if passive: tags.append("Passive")452 if length_ok: tags.append("Concise")453 return score, tags454 455def analyze_resume(text: str):456 bullets = [b.strip("-• ") for b in text.split("\n") if b.strip()]457 rows = []458 for b in bullets:459 s, tags = bullet_quality_metrics(b)460 rows.append([b, s, ", ".join(tags)])461 avg = int(np.mean([r[1] for r in rows])) if rows else 0462 _save_log("resume_analyzer", avg, {"n_bullets": len(rows)})463 return rows, avg464 465def activities_gap_finder(activities_text: str):466 # simple coverage by STEM/humanities/leadership/service/creative467 axes = {"STEM":0, "Humanities":0, "Leadership":0, "Service":0, "Creative":0}468 low = activities_text.lower()469 axes["STEM"] += len(re.findall(r"(robot|code|math|science|hack|lab|research)", low))470 axes["Humanities"] += len(re.findall(r"(writing|debate|model un|history|philosophy|language)", low))471 axes["Leadership"] += len(re.findall(r"(president|captain|lead|founded|organized)", low))472 axes["Service"] += len(re.findall(r"(volunteer|tutor|nonprofit|soup kitchen|fundraiser)", low))473 axes["Creative"] += len(re.findall(r"(music|art|design|film|theater|dance)", low))474 return axes475 476# ----------------------------477# College Matcher478# ----------------------------479def match_colleges(gpa, sat, act, interest="STEM", state_pref=""):480 df = _BUILTIN_COLLEGES.copy()481 # score by GPA/test vs school midpoint and interest strength482 if sat and sat > 0:483 df["test_gap"] = (sat - df["sat_mid"])/200.0484 elif act and act > 0:485 df["test_gap"] = (act - df["act_mid"])/4.0486 else:487 df["test_gap"] = 0.0488 df["gpa_gap"] = (gpa - df["avg_gpa"])489 if interest.lower() in ["stem","engineering","cs","math","physics"]:490 df["fit"] = 0.5*df["gpa_gap"] + 0.5*df["test_gap"] + 0.3*(df["stem_strength"]/5.0)491 else:492 df["fit"] = 0.5*df["gpa_gap"] + 0.5*df["test_gap"] + 0.3*(df["humanities_strength"]/5.0)493 if state_pref:494 df.loc[df["state"].str.lower()==state_pref.lower(), "fit"] += 0.15495 # tiers496 tiers = []497 for _, r in df.iterrows():498 if r["fit"] >= 0.3:499 tiers.append("Target")500 elif r["fit"] >= 0.1:501 tiers.append("Reach")502 else:503 tiers.append("High Reach")504 df["tier"] = tiers505 df = df.sort_values("fit", ascending=False)506 cols = ["name","state","tier","fit","avg_gpa","sat_mid","act_mid","acceptance_rate"]507 return df[cols].head(12)508 509# ----------------------------510# Spike Builder & ICS Export511# ----------------------------512def build_spike(interest: str, weeks: int = 8):513 ideas = SPIKE_IDEAS.get(interest, SPIKE_IDEAS[random.choice(list(SPIKE_IDEAS.keys()))])514 # timeline: weekly milestones515 milestones = []516 for w in range(weeks):517 milestones.append({518 "week": w+1,519 "goal": f"Progress on {interest} project – {ideas[w%len(ideas)]}",520 "deliverable": f"Week {w+1} demo/post/writeup"521 })522 return ideas, milestones523 524def export_ics(title: str, milestones):525 # Minimal ICS builder526 now = dt.datetime.now()527 lines = ["BEGIN:VCALENDAR","VERSION:2.0","PRODID:-//CollegeGenius//EN"]528 for m in milestones:529 start = now + dt.timedelta(weeks=m["week"]-1)530 dtstamp = now.strftime("%Y%m%dT%H%M%SZ")531 dtstart = start.strftime("%Y%m%d")532 uid = str(uuid.uuid4()) + "@collegegenius"533 lines += [534 "BEGIN:VEVENT",535 f"UID:{uid}",536 f"DTSTAMP:{dtstamp}",537 f"DTSTART;VALUE=DATE:{dtstart}",538 f"SUMMARY:{title} - Week {m['week']}: {m['deliverable']}",539 f"DESCRIPTION:{m['goal']}",540 "END:VEVENT"541 ]542 lines.append("END:VCALENDAR")543 ics_path = os.path.join(DATA_DIR, "spike_plan.ics")544 with open(ics_path, "w") as f:545 f.write("\n".join(lines))546 return ics_path547 548# ----------------------------549# Interview Practice550# ----------------------------551def interview_question(major: str, behavioral: bool):552 if behavioral:553 qs = [554 "Tell me about a time you failed and what you learned.",555 "Describe a conflict you resolved.",556 "What's a project that best represents you and why?",557 "When did you change your mind about something important?"558 ]559 else:560 qs = MAJOR_QUESTIONS.get(major, MAJOR_QUESTIONS["Humanities"])561 return random.choice(qs)562 563def answer_metrics(answer: str, seconds: int = 90):564 wc = _word_count(answer)565 wpm = int(60*wc/max(1, seconds))566 pos = len(re.findall(r"\b(learn|excited|grateful|curious|proud|growth|impact|collaborate)\b", answer.lower()))567 neg = len(re.findall(r"\b(hate|can't|never|worst|failure)\b", answer.lower()))568 sentiment = "Positive" if pos >= neg else "Neutral" if pos == neg else "Cautious"569 return {"wpm_estimate": wpm, "sentiment": sentiment, "word_count": wc}570 571# ----------------------------572# Portfolio PDF573# ----------------------------574def portfolio_pdf(essay_result, resume_rows, college_df, spike_title, milestones, interview_summary):575 pdf = FPDF()576 pdf.add_page()577 pdf.set_font("Arial", "B", 16)578 pdf.cell(0, 10, "CollegeGenius – Application Portfolio", ln=True)579 580 # Essay581 pdf.set_font("Arial", "", 12)582 pdf.cell(0, 8, "Essay Coach Summary", ln=True)583 for k,v in essay_result.items():584 if isinstance(v, dict): continue585 pdf.cell(0, 6, f"- {k.replace('_',' ').title()}: {v}", ln=True)586 587 # Resume bullets588 pdf.ln(4)589 pdf.set_font("Arial", "", 12)590 pdf.cell(0, 8, "Resume Bullet Quality (top 5)", ln=True)591 for row in resume_rows[:5]:592 bullet, score, tags = row593 pdf.multi_cell(0, 6, f"• [{score}] {bullet} ({tags})")594 595 # Colleges596 pdf.ln(4)597 pdf.set_font("Arial", "", 12)598 pdf.cell(0, 8, "Top College Matches", ln=True)599 for _, r in college_df.head(6).iterrows():600 pdf.cell(0, 6, f"- {r['name']} ({r['tier']}) | Fit={round(r['fit'],3)} | SAT~{r['sat_mid']} | GPA~{r['avg_gpa']}", ln=True)601 602 # Spike603 pdf.ln(4)604 pdf.set_font("Arial", "", 12)605 pdf.cell(0, 8, f"Spike Plan: {spike_title}", ln=True)606 for m in milestones[:6]:607 pdf.cell(0, 6, f"Week {m['week']}: {m['deliverable']} – {m['goal']}", ln=True)608 609 # Interview610 pdf.ln(4)611 pdf.cell(0, 8, "Interview Practice Snapshot", ln=True)612 for k,v in interview_summary.items():613 pdf.cell(0, 6, f"- {k.replace('_',' ').title()}: {v}", ln=True)614 615 out = os.path.join(DATA_DIR, "portfolio.pdf")616 pdf.output(out)617 return out618 619# ----------------------------620# Visualization helpers621# ----------------------------622def bar_image(labels, values, title=""):623 fig, ax = plt.subplots()624 ax.bar(labels, values)625 ax.set_title(title)626 ax.set_ylim(0, max(1, max(values)))627 buf = io.BytesIO()628 plt.tight_layout()629 plt.savefig(buf, format="png")630 plt.close(fig)631 buf.seek(0)632 return buf633 634def radar_like_image(d, title=""):635 labels = list(d.keys())636 values = [d[k] for k in labels]637 return bar_image(labels, values, title)638 639# ----------------------------640# UI Pipelines641# ----------------------------642def essay_coach_pipeline(prompt, essay_text, upload):643 src = essay_text.strip()644 if upload is not None and not src:645 src = _ocr_any(upload)646 if not src.strip():647 return "No essay text detected. Paste text or upload a clear image/PDF.", None, None, None648 score, fb = essay_rubric(src, prompt)649 # visuals650 dims = {651 "Structure": fb["structure_score"],652 "Clarity": fb["clarity_score"],653 "Style": fb["style_score"],654 "Originality": fb["originality_score"],655 "Mechanics": fb["mechanics_score"]656 }657 plot_buf = radar_like_image(dims, title=f"Essay Score: {score}")658 # thesis / outline659 outline = outline_helper(prompt, interests="your central interests", max_sections=6)660 md = f"### Essay Score: **{score}/100**\n"661 md += f"- Thesis guess: `{fb['thesis_guess']}`\n"662 if fb["cliche_hits"]:663 md += f"- Clichés to trim: `{', '.join(fb['cliche_hits'])}`\n"664 md += f"- Readability: {fb['readability']}\n"665 md += f"- Word count: {fb['word_count']}\n"666 md += "\n**Pro Outline:**\n" + "\n".join([f"{i+1}. {s}" for i,s in enumerate(outline)])667 # originality details668 orig = originality_heuristic(src)669 md += f"\n\n**Self-similarity (to your saved drafts):** {orig['max_self_similarity']}"670 if orig["similar_sample"]:671 md += f"\nExample similar snippet: `{orig['similar_sample']}`"672 return md, gr.update(value=plot_buf, visible=True), src[:1000], json.dumps(fb, indent=2)[:2000]673 674def resume_pipeline(resume_text, upload):675 src = resume_text.strip()676 if upload is not None and not src:677 src = _ocr_any(upload)678 rows, avg = analyze_resume(src)679 df = pd.DataFrame(rows, columns=["Bullet","Score","Tags"])680 # plot distribution681 if rows:682 vals = [r[1] for r in rows]683 buf = bar_image(["Avg","Min","Max"], [int(np.mean(vals)), min(vals), max(vals)], title="Resume Bullet Quality")684 else:685 buf = None686 md = f"### Resume Analysis\n- Bullets: **{len(rows)}**\n- Average quality: **{avg}**/100"687 return df, gr.update(value=buf, visible=buf is not None), md688 689def college_match_pipeline(gpa, sat, act, interest, state_pref):690 gpa = float(gpa) if gpa else 0.0691 sat = int(sat) if sat else 0692 act = int(act) if act else 0693 df = match_colleges(gpa, sat, act, interest, state_pref)694 # plot top-5 fits695 top = df.head(5)696 buf = bar_image(top["name"].tolist(), [round(x,3) for x in top["fit"].tolist()], title="Top Fit Scores")697 return df, gr.update(value=buf, visible=True)698 699def spike_pipeline(interest, weeks, title_hint):700 ideas, milestones = build_spike(interest, weeks)701 title = title_hint.strip() or f"{interest} Spike"702 ics_path = export_ics(title, milestones)703 md = "### Spike Plan\n" + "\n".join([f"- Week {m['week']}: {m['deliverable']} — {m['goal']}" for m in milestones])704 return md, ideas, ics_path705 706def interview_pipeline(major, behavioral, answer_text):707 q = interview_question(major, behavioral)708 m = answer_metrics(answer_text or "", seconds=90)709 md = f"### Your Answer Metrics\n- WPM (est): **{m['wpm_estimate']}**\n- Sentiment: **{m['sentiment']}**\n- Word count: **{m['word_count']}**"710 return q, md711 712def portfolio_pipeline(essay_fb_json, resume_df, college_df, spike_title, milestones_json):713 try:714 essay_fb = json.loads(essay_fb_json)715 except Exception:716 essay_fb = {}717 try:718 rows = resume_df.values.tolist() if hasattr(resume_df, "values") else []719 except Exception:720 rows = []721 try:722 colleges = college_df if isinstance(college_df, pd.DataFrame) else _BUILTIN_COLLEGES.head(5)723 if not isinstance(colleges, pd.DataFrame):724 colleges = pd.DataFrame(colleges)725 except Exception:726 colleges = _BUILTIN_COLLEGES.head(5)727 try:728 milestones = json.loads(milestones_json)729 except Exception:730 milestones = [{"week":1,"deliverable":"Kickoff","goal":"Start"}]731 732 pdf_path = portfolio_pdf(essay_fb, rows, colleges, spike_title or "Spike", milestones, {"note":"Auto-generated"})733 return pdf_path734 735# ----------------------------736# Gradio UI737# ----------------------------738with gr.Blocks(title="CollegeGenius – Ultimate College Apps Copilot", theme=gr.themes.Soft()) as demo:739 gr.Markdown("# 🎓 CollegeGenius – Ultimate College Apps Copilot\nAll-in-one Space to **crush your college apps**: essays, activities, college list, spike, interview, and a clean portfolio PDF.")740 with gr.Tab("📝 Essay Coach"):741 with gr.Row():742 prompt = gr.Textbox(label="Prompt (optional)", lines=2, placeholder="E.g., 'Tell us about a time you challenged a belief...'")743 essay_text = gr.Textbox(label="Paste your essay draft (or upload below)", lines=14)744 upload_essay = gr.File(label="Upload essay image/PDF (OCR)", file_count="single")745 btn_essay = gr.Button("Analyze Essay", variant="primary")746 essay_md = gr.Markdown()747 essay_plot = gr.Image(label="Essay Score Breakdown", visible=False)748 essay_excerpt = gr.Textbox(label="First 1000 chars (for your records)", lines=6)749 essay_fb_json = gr.Textbox(label="Raw feedback JSON", lines=8)750 btn_essay.click(essay_coach_pipeline, inputs=[prompt, essay_text, upload_essay],751 outputs=[essay_md, essay_plot, essay_excerpt, essay_fb_json])752 753 with gr.Tab("📄 Resume & Activities"):754 with gr.Row():755 resume_text = gr.Textbox(label="Paste resume / activities section", lines=12)756 upload_resume = gr.File(label="Upload resume image/PDF (OCR)", file_count="single")757 btn_resume = gr.Button("Analyze Resume/Activities", variant="primary")758 resume_df = gr.Dataframe(headers=["Bullet","Score","Tags"], interactive=False, wrap=True)759 resume_plot = gr.Image(label="Quality Snapshot", visible=False)760 resume_md = gr.Markdown()761 btn_resume.click(resume_pipeline, inputs=[resume_text, upload_resume],762 outputs=[resume_df, resume_plot, resume_md])763 764 with gr.Tab("🏫 College Matcher"):765 with gr.Row():766 gpa = gr.Textbox(label="Unweighted GPA (e.g., 3.8)")767 sat = gr.Textbox(label="SAT (1600 scale)")768 act = gr.Textbox(label="ACT (36 scale)")769 with gr.Row():770 interest = gr.Dropdown(choices=["STEM","Humanities","Business","Biology","CS","Engineering"], value="STEM", label="Primary Interest")771 state_pref = gr.Textbox(label="State preference (optional, e.g., CA)")772 btn_match = gr.Button("Find Matches", variant="primary")773 college_df = gr.Dataframe(interactive=False, wrap=True)774 college_plot = gr.Image(label="Top Fit Scores", visible=False)775 btn_match.click(college_match_pipeline, inputs=[gpa, sat, act, interest, state_pref],776 outputs=[college_df, college_plot])777 778 with gr.Tab("🚀 Spike Builder + Calendar"):779 with gr.Row():780 spike_interest = gr.Dropdown(choices=list(SPIKE_IDEAS.keys()), value="AI/ML", label="Interest Area")781 weeks = gr.Slider(4, 16, value=8, step=1, label="Timeline (weeks)")782 spike_title = gr.Textbox(label="Spike Title (optional)", placeholder="AI for Good Project")783 btn_spike = gr.Button("Generate Spike Plan", variant="primary")784 spike_md = gr.Markdown()785 spike_ideas = gr.HighlightedText(label="Idea Starters", combine_adjacent=True)786 spike_ics = gr.File(label="Calendar (.ics)")787 btn_spike.click(spike_pipeline, inputs=[spike_interest, weeks, spike_title],788 outputs=[spike_md, spike_ideas, spike_ics])789 790 with gr.Tab("🎤 Interview Practice"):791 with gr.Row():792 major = gr.Dropdown(choices=list(MAJOR_QUESTIONS.keys()), value="Computer Science", label="Major")793 behavioral = gr.Checkbox(value=True, label="Behavioral? (uncheck for major-specific)")794 answer_text = gr.Textbox(label="Type your answer (90s)", lines=8, placeholder="Practice answering here...")795 btn_interview = gr.Button("Give me a question + score my answer", variant="primary")796 interview_q = gr.Textbox(label="Your Question", lines=2)797 interview_md = gr.Markdown()798 btn_interview.click(interview_pipeline, inputs=[major, behavioral, answer_text],799 outputs=[interview_q, interview_md])800 801 with gr.Tab("📚 Portfolio Report"):802 gr.Markdown("Combine everything into a clean PDF you can download or share.")803 with gr.Row():804 spike_title_in = gr.Textbox(label="Spike Title for Report", value="My Spike")805 milestones_json = gr.Textbox(label="Milestones JSON (optional)", lines=4, placeholder='[{"week":1,"deliverable":"Kickoff","goal":"Start"}]')806 btn_pdf = gr.Button("Build Portfolio PDF", variant="primary")807 pdf_out = gr.File()808 btn_pdf.click(portfolio_pipeline,809 inputs=[essay_fb_json, resume_df, college_df, spike_title_in, milestones_json],810 outputs=[pdf_out])811 812app = demo813 814if __name__ == "__main__":815 demo.launch()816 