Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 56 additions & 45 deletions generator/latex_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", ""))
Expand All @@ -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", ""))
Expand All @@ -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(
Expand All @@ -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 = []
Expand All @@ -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}}

Expand All @@ -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}}
Expand All @@ -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
Expand All @@ -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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand All @@ -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}}
"""
Expand Down
81 changes: 59 additions & 22 deletions generator/resume_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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))

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
{{
Expand Down Expand Up @@ -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))

Expand All @@ -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"],
Expand All @@ -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", ""),
Expand All @@ -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]
}