1212
1313from core .data_loader import load_all_programs , load_profile
1414from 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+ )
1522from core .prerequisite_matcher import match_prerequisites
1623from core .profile_evaluator import evaluate as evaluate_profile
1724from 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+
350498def 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