diff --git a/generator/latex_compiler.py b/generator/latex_compiler.py index 32f0a9f..b36b0e1 100644 --- a/generator/latex_compiler.py +++ b/generator/latex_compiler.py @@ -132,7 +132,7 @@ def sanitize_latex(text: str) -> str: # Build Experience items exp_latex = "" - for exp in experience[:5]: + for exp in experience[:3]: role = sanitize_latex(exp.get("role", "")) company = sanitize_latex(exp.get("company", "")) date = sanitize_latex(exp.get("date", "")) @@ -149,13 +149,13 @@ def sanitize_latex(text: str) -> str: bullets = [b.strip() for b in desc.split(". ") if b.strip()] exp_latex += f" \\resumeSubheading\n {{{company}}}{{{date}}}\n {{{role}}}{{}}\n \\resumeItemListStart\n" - for bullet in bullets[:4]: + for bullet in bullets[:5]: exp_latex += f" \\resumeItem{{{sanitize_latex(bullet)}}}\n" exp_latex += " \\resumeItemListEnd\n\n" # Build Education items edu_latex = "" - for edu in education: + for edu in education[:2]: school = sanitize_latex(edu.get("school", "")) degree = sanitize_latex(edu.get("degree", "")) date = sanitize_latex(edu.get("date", "")) @@ -167,7 +167,7 @@ def sanitize_latex(text: str) -> str: # Build Projects items proj_latex = "" - for proj in projects[:4]: + for proj in projects[:3]: p_name = sanitize_latex(proj.get("name", "Project")) p_desc = sanitize_latex(proj.get("description", "")) p_tech = ", ".join( @@ -180,12 +180,12 @@ def sanitize_latex(text: str) -> str: proj_latex += f" \\resumeProjectHeading\n {{{proj_title}}}{{}}\n \\resumeItemListStart\n" # Split description into bullets if needed bullets = [b.strip() for b in p_desc.split("\n") if b.strip()] - for bullet in bullets[:2]: + for bullet in bullets[:3]: proj_latex += f" \\resumeItem{{{sanitize_latex(bullet)}}}\n" proj_latex += " \\resumeItemListEnd\n\n" # Build Skills - skills_str = ", ".join([sanitize_latex(s) for s in skills[:20]]) + skills_str = ", ".join([sanitize_latex(s) for s in skills[:30]]) # Build Certifications cert_items = [] @@ -198,6 +198,41 @@ def sanitize_latex(text: str) -> str: # Build Languages lang_str = ", ".join([sanitize_latex(l) for l in languages]) + # Build sections conditionally + exp_section = "" + if exp_latex.strip(): + exp_section = f"\\cvsection{{Experience}}\n\\begin{{itemize}}\n{exp_latex}\\end{{itemize}}" + + proj_section = "" + if proj_latex.strip(): + proj_section = f"\\cvsection{{Notable Projects}}\n\\begin{{itemize}}\n{proj_latex}\\end{{itemize}}" + + edu_section = "" + if edu_latex.strip(): + edu_section = f"\\cvsection{{Education}}\n\\begin{{itemize}}\n{edu_latex}\\end{{itemize}}" + + skills_section = "" + if skills_str.strip(): + skills_section = f"""\\cvsection{{Technical Skills}} +\\begin{{itemize}} + \\item[] \\small{{ + \\textbf{{Skills:}} {{{skills_str}}} \\vspace{{-7pt}} + }} +\\end{{itemize}}""" + + cert_section = "" + if cert_latex.strip(): + cert_section = f"\\cvsection{{Certifications}}\n\\begin{{itemize}}\n{cert_latex}\\end{{itemize}}" + + lang_section = "" + if lang_str.strip(): + lang_section = f"""\\cvsection{{Languages}} +\\begin{{itemize}} + \\item[] \\small{{ + {lang_str} \\vspace{{-7pt}} + }} +\\end{{itemize}}""" + # Full Template - "Zero-Dependency" Professional Style latex_template = f"""\\documentclass[letterpaper,11pt]{{article}} @@ -206,7 +241,7 @@ def sanitize_latex(text: str) -> str: \\usepackage[english]{{babel}} \\usepackage{{tabularx}} \\usepackage{{geometry}} -\\geometry{{left=0.5in, top=0.5in, right=0.5in, bottom=0.5in}} +\\geometry{{left=0.5in, top=0.4in, right=0.5in, bottom=0.4in}} % Font option: Use standard Palatino if available, or just stick to default \\usepackage[T1]{{fontenc}} @@ -224,8 +259,8 @@ def sanitize_latex(text: str) -> str: % Sections formatting - Manual implementation without titlesec \\newcommand{{\\cvsection}}[1]{{ - \\vspace{{10pt}} - {{\\large \\scshape #1}} \\\\ \\hrule \\vspace{{5pt}} + \\vspace{{5pt}} + {{\\large \\scshape #1}} \\\\ \\hrule \\vspace{{3pt}} }} % Custom commands implementation using standard LaTeX @@ -234,22 +269,22 @@ def sanitize_latex(text: str) -> str: }} \\newcommand{{\\resumeSubheading}}[4]{{ - \\item + \\item[] \\begin{{tabular*}}{{0.97\\textwidth}}[t]{{l@{{\\extracolsep{{\\fill}}}}r}} \\textbf{{#1}} & #2 \\\\ \\textit{{\\small#3}} & \\textit{{\\small #4}} \\\\ - \\end{{tabular*}}\\vspace{{-7pt}} + \\end{{tabular*}}\\vspace{{-5pt}} }} \\newcommand{{\\resumeProjectHeading}}[2]{{ - \\item + \\item[] \\begin{{tabular*}}{{0.97\\textwidth}}{{l@{{\\extracolsep{{\\fill}}}}r}} \\small#1 & #2 \\\\ - \\end{{tabular*}}\\vspace{{-7pt}} + \\end{{tabular*}}\\vspace{{-5pt}} }} -\\newcommand{{\\resumeItemListStart}}{{\\begin{{itemize}}\\small}} -\\newcommand{{\\resumeItemListEnd}}{{\\end{{itemize}}\\vspace{{-5pt}}}} +\\newcommand{{\\resumeItemListStart}}{{\\begin{{itemize}}\\small\\setlength{{\\itemsep}}{{0pt}}\\setlength{{\\parsep}}{{0pt}}}} +\\newcommand{{\\resumeItemListEnd}}{{\\end{{itemize}}\\vspace{{-2pt}}}} %------------------------------------------- %%%%%% RESUME STARTS HERE %%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -266,41 +301,17 @@ def sanitize_latex(text: str) -> str: \\cvsection{{Summary}} \\small{{{summary}}} -%-----------EXPERIENCE----------- -\\cvsection{{Experience}} -\\begin{{itemize}} -{exp_latex} -\\end{{itemize}} +{exp_section} -%-----------PROJECTS----------- -\\cvsection{{Notable Projects}} -\\begin{{itemize}} -{proj_latex} -\\end{{itemize}} +{proj_section} -%-----------EDUCATION----------- -\\cvsection{{Education}} -\\begin{{itemize}} -{edu_latex} -\\end{{itemize}} +{edu_section} -%-----------SKILLS----------- -\\cvsection{{Technical Skills}} -\\begin{{itemize}} -\\item\\small{{\\textbf{{Skills:}} {skills_str}}} -\\end{{itemize}} +{skills_section} -%-----------CERTIFICATIONS----------- -\\cvsection{{Certifications}} -\\begin{{itemize}} -{cert_latex} -\\end{{itemize}} +{cert_section} -%-----------LANGUAGES----------- -\\cvsection{{Languages}} -\\begin{{itemize}} -\\item\\small{{{lang_str}}} -\\end{{itemize}} +{lang_section} \\end{{document}} """ diff --git a/generator/resume_generator.py b/generator/resume_generator.py index 9216c35..87b4b17 100644 --- a/generator/resume_generator.py +++ b/generator/resume_generator.py @@ -51,7 +51,8 @@ def generate(self, profile_data: Dict, job_requirements: Dict) -> Dict: response = self.model.generate_content(prompt) # Parse the response - resume_content = self._parse_gemini_response(response.text, profile_data) + resume_content = self._parse_gemini_response( + response.text, profile_data) return resume_content @@ -65,7 +66,8 @@ def _create_prompt(self, profile_data: Dict, job_requirements: Dict) -> str: linkedin_data = profile_data.get("linkedin", {}) # Merge skills - github_languages = [lang for lang, _ in github_data.get("languages", [])] + github_languages = [lang for lang, + _ in github_data.get("languages", [])] linkedin_skills = linkedin_data.get("skills", []) all_candidate_skills = list(set(github_languages + linkedin_skills)) @@ -112,7 +114,8 @@ def _create_prompt(self, profile_data: Dict, job_requirements: Dict) -> str: :10 ]: # Limit to 10 items per category to avoid token bloat # Clean up dictionary to string - item_str = ", ".join([f"{k}: {v}" for k, v in item.items() if v]) + item_str = ", ".join( + [f"{k}: {v}" for k, v in item.items() if v]) additional_info += f"- {item_str}\n" prompt = f"""You are an expert resume writer. Create a tailored resume based on the following information: @@ -150,11 +153,14 @@ def _create_prompt(self, profile_data: Dict, job_requirements: Dict) -> str: {json.dumps(job_requirements.get('skills', {}), indent=2)} TASK: -Generate a tailored resume that: -1. Highlights relevant skills that match the job requirements -2. Incorporates both professional experience from LinkedIn and technical projects from GitHub -3. Uses strong action words and quantifiable achievements -4. Tailors the "Summary" and "Projects" descriptions to emphasize relevance to the specific job description +Generate a detailed, tailored resume that fits perfectly on a single page. Follow these guidelines: +1. Highlights relevant skills that match the job requirements. +2. Incorporates both professional experience from LinkedIn and technical projects from GitHub. +3. Uses strong action words and quantifiable achievements (e.g., "Increased efficiency by 20%", "Managed a team of 5"). +4. Provide a compelling professional summary (3-4 sentences) that highlights your unique value proposition. +5. Provide 2-3 detailed bullet points for each significant work experience, focusing on achievements and technologies used. +6. Provide a descriptive paragraph or 2-3 bullet points for each project, explaining the problem solved and the technical implementation. +7. Ensure the layout is dense and professional to avoid empty space while strictly remaining on one page. Return the response in the following JSON format: {{ @@ -249,7 +255,8 @@ def _generate_basic_resume( linkedin_data = profile_data.get("linkedin", {}) # Extract skills (merge GitHub and LinkedIn) - github_languages = [lang for lang, _ in github_data.get("languages", [])] + github_languages = [lang for lang, + _ in github_data.get("languages", [])] linkedin_skills = linkedin_data.get("skills", []) all_candidate_skills = list(set(github_languages + linkedin_skills)) @@ -271,21 +278,27 @@ def _generate_basic_resume( + [s for s in all_candidate_skills if s not in highlighted_skills] )[:15] + # Format experience + experience = [] + for exp in linkedin_data.get("experience", []): + experience.append({ + "role": exp.get("title", ""), + "company": exp.get("company", ""), + "date": f"{exp.get('start_date', '')} - {exp.get('end_date', '')}".strip(" -"), + "description": exp.get("description", "") + }) + # Format projects (prioritize LinkedIn, then GitHub) projects = [] - - # Add LinkedIn Experience/Projects as projects in the base resume - for exp in linkedin_data.get("experience", [])[:3]: - projects.append( - { - "name": f"{exp.get('title')} at {exp.get('company')}", - "description": exp.get("description", ""), - "technologies": [], - } - ) - + # Add LinkedIn Projects + for lp in linkedin_data.get("projects", []): + projects.append({ + "name": lp.get("title", ""), + "description": lp.get("description", ""), + "technologies": [] + }) # Add GitHub projects - for proj in github_data.get("top_projects", [])[:3]: + for proj in github_data.get("top_projects", []): projects.append( { "name": proj["name"], @@ -294,6 +307,26 @@ def _generate_basic_resume( } ) + # Format Education + education = [] + for edu in linkedin_data.get("education", []): + education.append({ + "school": edu.get("school", ""), + "degree": edu.get("degree", ""), + "date": f"{edu.get('start_date', '')} - {edu.get('end_date', '')}".strip(" -"), + "details": edu.get("field_of_study", "") + }) + + # Certifications and Languages from full_data + full_data = linkedin_data.get("full_data", {}) + certifications = [] + if "certifications" in full_data: + certifications = [c.get("name", "") for c in full_data.get("certifications", []) if c.get("name")] + + languages = [] + if "languages" in full_data: + languages = [l.get("name", "") for l in full_data.get("languages", []) if l.get("name")] + return { "name": linkedin_data.get("name") or github_data.get("name", "Your Name"), "email": github_data.get("email", ""), @@ -308,5 +341,9 @@ def _generate_basic_resume( "summary": linkedin_data.get("summary") or github_data.get("bio", "Experienced software developer"), "skills": final_skills, - "projects": projects[:5], + "experience": experience[:3], + "projects": projects[:3], + "education": education[:2], + "certifications": certifications[:5], + "languages": languages[:5] }