TalentLensAI / utils /builder.py
Johnny
feat: Update resume builder with LFS-tracked assets
79b5c9c
import logging
import os
import re
from datetime import datetime
from dateutil.parser import parse as date_parse
from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT, WD_TAB_ALIGNMENT
from docx.shared import Inches, Pt
logger = logging.getLogger(__name__)
def fmt_range(raw: str) -> str:
"""Formats a date range string nicely."""
if not raw:
return ""
parts = [p.strip() for p in re.split(r"\s*[–-]\s*", raw)]
formatted_parts = []
for part in parts:
if part.lower() == "present":
formatted_parts.append("Present")
else:
try:
date_obj = date_parse(part, fuzzy=True, default=datetime(1900, 1, 1))
if date_obj.year == 1900:
formatted_parts.append(part)
else:
formatted_parts.append(date_obj.strftime("%B %Y"))
except (ValueError, TypeError):
formatted_parts.append(part)
return " – ".join(formatted_parts)
def add_section_heading(doc, text):
"""Adds a centered section heading."""
p = doc.add_paragraph()
run = p.add_run(text.upper())
run.bold = True
font = run.font
font.size = Pt(12)
font.name = 'Arial'
p.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
p.paragraph_format.space_after = Pt(6)
def build_resume_from_data(tmpl: str, sections: dict, remove_blank_pages_enabled: bool = True) -> Document:
"""
Builds a formatted resume from structured data, inserting header/footer images and logging the process.
"""
logger.info("BUILDER: Starting image-based resume build process.")
try:
# 1. Create a new blank document, ignoring the template file
doc = Document()
logger.info("BUILDER: Successfully created a new blank document.")
# Get section and enable different first page header/footer
section = doc.sections[0]
section.different_first_page = True
# Move header and footer to the very edge of the page
section.header_distance = Pt(0)
section.footer_distance = Pt(0)
logger.info("BUILDER: Set header/footer distance to 0 to remove whitespace.")
# 2. Define image paths relative to the project root
script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(script_dir)
header_path = os.path.join(project_root, 'header.png')
footer_path = os.path.join(project_root, 'footer.png')
logger.info(f"BUILDER: Attempting to use header image from: {header_path}")
logger.info(f"BUILDER: Attempting to use footer image from: {footer_path}")
if not os.path.exists(header_path):
logger.error(f"BUILDER FATAL: Header image not found at '{header_path}'. Cannot proceed.")
return doc # Return empty doc
if not os.path.exists(footer_path):
logger.error(f"BUILDER FATAL: Footer image not found at '{footer_path}'. Cannot proceed.")
return doc # Return empty doc
# 3. Setup Headers
candidate_name = sections.get("Name", "Candidate Name Not Found")
experiences = sections.get("StructuredExperiences", [])
job_title = experiences[0].get("title", "") if experiences else ""
# -- First Page Header (Image + Name + Title) --
first_page_header = section.first_page_header
first_page_header.is_linked_to_previous = False
# Safely get or create a paragraph for the image
p_header_img_first = first_page_header.paragraphs[0] if first_page_header.paragraphs else first_page_header.add_paragraph()
p_header_img_first.clear()
p_header_img_first.paragraph_format.space_before = Pt(0)
p_header_img_first.paragraph_format.space_after = Pt(0)
p_header_img_first.paragraph_format.left_indent = -section.left_margin
p_header_img_first.add_run().add_picture(header_path, width=section.page_width)
logger.info("BUILDER: Inserted header.png into FIRST PAGE header.")
# Add Name
p_name = first_page_header.add_paragraph()
run_name = p_name.add_run(candidate_name.upper())
run_name.font.name = 'Arial'
run_name.font.size = Pt(14)
run_name.bold = True
p_name.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
p_name.paragraph_format.space_before = Pt(6)
p_name.paragraph_format.space_after = Pt(0)
logger.info(f"BUILDER: Added candidate name '{candidate_name}' to FIRST PAGE header.")
# Add Job Title
if job_title:
p_title = first_page_header.add_paragraph()
run_title = p_title.add_run(job_title)
run_title.font.name = 'Arial'
run_title.font.size = Pt(11)
p_title.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
p_title.paragraph_format.space_before = Pt(0)
logger.info(f"BUILDER: Added job title '{job_title}' to FIRST PAGE header.")
# -- Primary Header for subsequent pages (Image Only) --
primary_header = section.header
primary_header.is_linked_to_previous = False
# Safely get or create a paragraph for the image
p_header_img_primary = primary_header.paragraphs[0] if primary_header.paragraphs else primary_header.add_paragraph()
p_header_img_primary.clear()
p_header_img_primary.paragraph_format.space_before = Pt(0)
p_header_img_primary.paragraph_format.space_after = Pt(0)
p_header_img_primary.paragraph_format.left_indent = -section.left_margin
p_header_img_primary.add_run().add_picture(header_path, width=section.page_width)
logger.info("BUILDER: Inserted header.png into PRIMARY header for subsequent pages.")
# 4. Insert Footer Image (same for all pages)
footer = section.footer
footer.is_linked_to_previous = False
# Safely get or create a paragraph for the image
p_footer_img = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
p_footer_img.clear()
p_footer_img.paragraph_format.space_before = Pt(0)
p_footer_img.paragraph_format.space_after = Pt(0)
p_footer_img.paragraph_format.left_indent = -section.left_margin
p_footer_img.add_run().add_picture(footer_path, width=section.page_width)
# Link the first page footer to the primary footer so we only define it once.
section.first_page_footer.is_linked_to_previous = True
logger.info("BUILDER: Inserted footer.png and configured for all pages.")
# 5. Build Resume Body
logger.info("BUILDER: Proceeding to add structured resume content to document body.")
# --- Professional Summary ---
if sections.get("Summary"):
add_section_heading(doc, "Professional Summary")
doc.add_paragraph(sections["Summary"]).paragraph_format.space_after = Pt(12)
# --- Skills ---
if sections.get("Skills"):
add_section_heading(doc, "Skills")
skills_text = ", ".join(sections["Skills"])
p = doc.add_paragraph(skills_text)
p.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
p.paragraph_format.space_after = Pt(12)
# --- Professional Experience ---
if experiences:
add_section_heading(doc, "Professional Experience")
for exp in experiences:
if not isinstance(exp, dict):
continue
p = doc.add_paragraph()
p.add_run(exp.get("title", "N/A")).bold = True
p.add_run(" | ").bold = True
p.add_run(exp.get("company", "N/A")).italic = True
p.add_run(f'\t{fmt_range(exp.get("date_range", ""))}')
tab_stops = p.paragraph_format.tab_stops
tab_stops.add_tab_stop(Inches(6.5), WD_TAB_ALIGNMENT.RIGHT)
responsibilities = exp.get("responsibilities", [])
if responsibilities and isinstance(responsibilities, list):
for resp in responsibilities:
if resp.strip():
try:
p_resp = doc.add_paragraph(resp, style='List Bullet')
except KeyError:
p_resp = doc.add_paragraph(f"β€’ {resp}")
p_resp.paragraph_format.left_indent = Inches(0.25)
p_resp.paragraph_format.space_before = Pt(0)
p_resp.paragraph_format.space_after = Pt(3)
doc.add_paragraph().paragraph_format.space_after = Pt(6)
# --- Education ---
if sections.get("Education"):
add_section_heading(doc, "Education")
for edu in sections.get("Education", []):
if edu.strip():
try:
p_edu = doc.add_paragraph(edu, style='List Bullet')
except KeyError:
p_edu = doc.add_paragraph(f"β€’ {edu}")
p_edu.paragraph_format.left_indent = Inches(0.25)
logger.info("BUILDER: Resume build process completed successfully.")
return doc
except Exception:
logger.error("BUILDER: An unexpected error occurred during resume generation.", exc_info=True)
return Document()