Skip to content

Commit 6163a2a

Browse files
Add interview prep, Streamlit dashboard, and expand to 20 programs
Interview Prep: - 52 questions across 7 categories (probability, stochastic calc, linear algebra, programming, brainteasers, finance, behavioral) - CLI command: quantpath interview (--category, --difficulty, --program) - Question bank with hints, solutions, difficulty levels, program tags Web Dashboard (Streamlit): - 5-page app: Profile Evaluation, Program Explorer, Program Comparison, Gap Analysis, Application Timeline - Plotly radar chart for dimension scores - Interactive program filtering and comparison Program Database: - Expanded from 10 to 20 programs - New: UCLA, GaTech, Rutgers, UMich, BU, UWash, JHU, Northwestern, USC, UToronto Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7b0de9d commit 6163a2a

17 files changed

Lines changed: 2662 additions & 3 deletions

cli/main.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212

1313
from core.data_loader import load_all_programs, load_profile
1414
from core.gap_advisor import analyze_gaps
15+
from core.interview_prep import (
16+
get_questions_by_category,
17+
get_questions_by_difficulty,
18+
get_questions_for_program,
19+
get_random_quiz,
20+
load_questions,
21+
)
1522
from core.prerequisite_matcher import match_prerequisites
1623
from core.profile_evaluator import evaluate as evaluate_profile
1724
from core.school_ranker import rank_schools
@@ -347,6 +354,147 @@ def _round_date(prog, round_num):
347354
console.print()
348355

349356

357+
def _difficulty_badge(difficulty: str) -> str:
358+
"""Return a Rich-formatted badge for question difficulty."""
359+
colors = {"easy": "green", "medium": "yellow", "hard": "red"}
360+
color = colors.get(difficulty, "white")
361+
label = difficulty.upper()
362+
return f"[{color}] {label} [/{color}]"
363+
364+
365+
def cmd_interview(args: argparse.Namespace) -> None:
366+
"""Display interview practice questions as a study guide."""
367+
categories = load_questions()
368+
369+
# --list-categories: just print the category table and exit.
370+
if args.list_categories:
371+
console.print()
372+
table = Table(title="Interview Question Categories", border_style="cyan")
373+
table.add_column("ID", style="bold")
374+
table.add_column("Category")
375+
table.add_column("Questions", justify="right")
376+
table.add_column("Difficulties")
377+
378+
for cat in categories:
379+
diffs = sorted({q.difficulty for q in cat.questions})
380+
diff_str = ", ".join(diffs)
381+
table.add_row(cat.id, cat.name, str(len(cat.questions)), diff_str)
382+
383+
total = sum(len(c.questions) for c in categories)
384+
console.print(table)
385+
console.print(f"\n [bold]{total}[/bold] questions across [bold]{len(categories)}[/bold] categories\n")
386+
return
387+
388+
# Build the question pool based on filters.
389+
if args.category:
390+
questions = get_questions_by_category(args.category, categories)
391+
if not questions:
392+
console.print(f"[red]Category '{args.category}' not found.[/red]")
393+
console.print("[dim]Use --list-categories to see available categories.[/dim]")
394+
return
395+
elif args.difficulty:
396+
questions = get_questions_by_difficulty(args.difficulty, categories)
397+
elif args.program:
398+
questions = get_questions_for_program(args.program, categories)
399+
if not questions:
400+
console.print(f"[red]No questions tagged for program '{args.program}'.[/red]")
401+
return
402+
else:
403+
questions = get_random_quiz(
404+
n=args.count,
405+
categories=categories,
406+
)
407+
408+
# Apply additional filters when a primary filter was already set.
409+
if args.category and args.difficulty:
410+
questions = [q for q in questions if q.difficulty == args.difficulty.lower()]
411+
if args.program and args.category:
412+
questions = [q for q in questions if args.program in q.programs]
413+
414+
# Limit to requested count (unless fewer available).
415+
if len(questions) > args.count:
416+
questions = questions[: args.count]
417+
418+
if not questions:
419+
console.print("[yellow]No questions match the given filters.[/yellow]")
420+
return
421+
422+
# Header
423+
console.print()
424+
filter_parts = []
425+
if args.category:
426+
filter_parts.append(f"Category: [bold]{args.category}[/bold]")
427+
if args.difficulty:
428+
filter_parts.append(f"Difficulty: [bold]{args.difficulty}[/bold]")
429+
if args.program:
430+
filter_parts.append(f"Program: [bold]{args.program}[/bold]")
431+
filter_desc = " | ".join(filter_parts) if filter_parts else "All categories"
432+
433+
console.print(
434+
Panel(
435+
f"[bold]Interview Practice Set[/bold] -- {len(questions)} questions\n{filter_desc}",
436+
border_style="cyan",
437+
title="QuantPath Interview Prep",
438+
)
439+
)
440+
441+
# Render each question
442+
for idx, q in enumerate(questions, 1):
443+
badge = _difficulty_badge(q.difficulty)
444+
topics_str = ", ".join(q.topics)
445+
446+
# Question header line
447+
header = Text()
448+
header.append(f"Q{idx}", style="bold cyan")
449+
header.append(f" [{q.category_name}]", style="dim")
450+
451+
# Build panel content
452+
lines = []
453+
lines.append(f"{q.question}")
454+
lines.append("")
455+
lines.append(f"Difficulty: {badge} Topics: [cyan]{topics_str}[/cyan]")
456+
457+
if q.programs:
458+
prog_str = ", ".join(q.programs)
459+
lines.append(f"Programs: [dim]{prog_str}[/dim]")
460+
461+
lines.append("")
462+
lines.append(f"[dim italic]Hint: {q.hint}[/dim italic]")
463+
lines.append("")
464+
lines.append("[bold]Solution:[/bold]")
465+
for sol_line in q.solution.strip().splitlines():
466+
lines.append(f" {sol_line}")
467+
468+
console.print(
469+
Panel(
470+
"\n".join(lines),
471+
title=str(header),
472+
border_style="blue",
473+
padding=(1, 2),
474+
)
475+
)
476+
477+
# Summary footer
478+
cats_seen = sorted({q.category_name for q in questions})
479+
diff_counts = {}
480+
for q in questions:
481+
diff_counts[q.difficulty] = diff_counts.get(q.difficulty, 0) + 1
482+
diff_summary = " ".join(
483+
f"{_difficulty_badge(d)}: {c}" for d, c in sorted(diff_counts.items())
484+
)
485+
486+
console.print(
487+
Panel(
488+
f"[bold]{len(questions)}[/bold] questions | "
489+
f"Categories: {', '.join(cats_seen)}\n"
490+
f"Difficulty breakdown: {diff_summary}",
491+
title="Practice Set Summary",
492+
border_style="green",
493+
)
494+
)
495+
console.print()
496+
497+
350498
def cmd_gaps(args: argparse.Namespace) -> None:
351499
"""Analyze profile gaps and suggest improvements."""
352500
profile = load_profile(args.profile)
@@ -451,6 +599,26 @@ def main() -> None:
451599
help="Comma-separated program IDs (e.g., cmu-mscf,baruch-mfe,berkeley-mfe)",
452600
)
453601

602+
# interview
603+
p_interview = subparsers.add_parser("interview", help="Practice MFE interview questions")
604+
p_interview.add_argument("--category", "-c", help="Filter by category ID (e.g. probability, finance)")
605+
p_interview.add_argument(
606+
"--difficulty", "-d",
607+
choices=["easy", "medium", "hard"],
608+
help="Filter by difficulty level",
609+
)
610+
p_interview.add_argument("--program", help="Show questions for a specific program (e.g. baruch-mfe)")
611+
p_interview.add_argument(
612+
"--count", "-n",
613+
type=int, default=5,
614+
help="Number of questions to display (default: 5)",
615+
)
616+
p_interview.add_argument(
617+
"--list-categories",
618+
action="store_true",
619+
help="List all available question categories and exit",
620+
)
621+
454622
# gaps
455623
p_gaps = subparsers.add_parser("gaps", help="Analyze profile gaps and suggest improvements")
456624
p_gaps.add_argument("--profile", "-p", required=True, help="Path to profile YAML")
@@ -468,6 +636,7 @@ def main() -> None:
468636
"timeline": cmd_timeline,
469637
"programs": cmd_programs,
470638
"compare": cmd_compare,
639+
"interview": cmd_interview,
471640
"gaps": cmd_gaps,
472641
}
473642
commands[args.command](args)

0 commit comments

Comments
 (0)