mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 15:29:36 +00:00
Complete AI-powered personal brand automation for Sami Assiri.\n\n7 agents: LinkedIn, Email, Social Media, WhatsApp, CV Optimizer, Content Strategist, Opportunity Scout.\nInfra: FastAPI + APScheduler + Docker + Ollama/Groq LLM + GitHub Pages landing page.\n83 files, ~10K lines. Cost: $0-5/month.
187 lines
6.4 KiB
Python
187 lines
6.4 KiB
Python
"""CV rendering and PDF generation -- Jinja2 templates + WeasyPrint."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from jinja2 import BaseLoader, Environment, FileSystemLoader
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def render_cv_html(
|
|
brand_profile: dict,
|
|
template_path: str,
|
|
language: str = "en",
|
|
) -> str:
|
|
"""Render a CV as HTML from the Jinja2 template.
|
|
|
|
Parameters
|
|
----------
|
|
brand_profile:
|
|
Full (optionally enhanced) brand profile dict.
|
|
template_path:
|
|
Absolute path to the ``.html`` Jinja2 template file.
|
|
language:
|
|
``"en"`` or ``"ar"`` -- passed into the template context.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
Fully rendered HTML string.
|
|
"""
|
|
tpl_path = Path(template_path)
|
|
tpl_dir = str(tpl_path.parent)
|
|
tpl_name = tpl_path.name
|
|
|
|
env = Environment(
|
|
loader=FileSystemLoader(tpl_dir),
|
|
autoescape=True,
|
|
)
|
|
template = env.get_template(tpl_name)
|
|
|
|
# Build the template context from the profile
|
|
context = _build_template_context(brand_profile, language)
|
|
|
|
html = template.render(**context)
|
|
logger.info("CV HTML rendered (%s chars, lang=%s)", len(html), language)
|
|
return html
|
|
|
|
|
|
def generate_pdf(html_content: str, output_path: str) -> Path:
|
|
"""Convert rendered HTML to a PDF file using WeasyPrint.
|
|
|
|
Parameters
|
|
----------
|
|
html_content:
|
|
The full HTML string to convert.
|
|
output_path:
|
|
Destination file path for the generated PDF.
|
|
|
|
Returns
|
|
-------
|
|
Path
|
|
The path to the written PDF file.
|
|
"""
|
|
from weasyprint import HTML
|
|
|
|
out = Path(output_path)
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
HTML(string=html_content).write_pdf(str(out))
|
|
logger.info("PDF generated: %s (%.1f KB)", out, out.stat().st_size / 1024)
|
|
return out
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Private helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _build_template_context(profile: dict, language: str) -> dict:
|
|
"""Flatten the nested profile dict into a template-friendly context."""
|
|
personal = profile.get("personal", {})
|
|
employment = profile.get("employment", {})
|
|
education = profile.get("education", {})
|
|
enhanced = profile.get("enhanced", {})
|
|
lang_suffix = f"_{language}"
|
|
|
|
# Use enhanced summary if available, otherwise fall back to bio
|
|
summary = enhanced.get(f"summary_{language}", "") or personal.get(
|
|
f"bio_{language}", ""
|
|
)
|
|
|
|
# Current role bullets: prefer enhanced, fall back to raw description
|
|
current = employment.get("current", {})
|
|
current_bullets = enhanced.get("current_role_bullets", [])
|
|
if not current_bullets:
|
|
raw_desc = current.get(f"description_{language}", "")
|
|
current_bullets = [
|
|
line.strip().lstrip("- ")
|
|
for line in raw_desc.strip().splitlines()
|
|
if line.strip()
|
|
]
|
|
|
|
# Previous roles: prefer enhanced
|
|
previous_roles_enhanced = enhanced.get("previous_roles", [])
|
|
previous_roles_raw = employment.get("previous", [])
|
|
if previous_roles_enhanced:
|
|
previous_roles = previous_roles_enhanced
|
|
else:
|
|
previous_roles = [
|
|
{
|
|
"company": r.get("company", ""),
|
|
"title": r.get("title", ""),
|
|
"period": r.get("period", ""),
|
|
"bullets": r.get("highlights", []),
|
|
}
|
|
for r in previous_roles_raw
|
|
]
|
|
|
|
# Leadership: prefer enhanced
|
|
leadership_enhanced = enhanced.get("leadership", [])
|
|
leadership_raw = profile.get("leadership", [])
|
|
if leadership_enhanced:
|
|
leadership = leadership_enhanced
|
|
else:
|
|
leadership = [
|
|
{
|
|
"role": entry.get("role", ""),
|
|
"organization": entry.get("organization", ""),
|
|
"period": entry.get("period", ""),
|
|
"bullets": entry.get("highlights", []),
|
|
}
|
|
for entry in leadership_raw
|
|
]
|
|
|
|
# Skills grouped by category
|
|
skills = profile.get("skills", {})
|
|
skill_categories = []
|
|
_category_labels = {
|
|
"data_analytics": {"en": "Data Analytics", "ar": "تحليل البيانات"},
|
|
"project_management": {"en": "Project Management", "ar": "إدارة المشاريع"},
|
|
"engineering": {"en": "Engineering", "ar": "الهندسة"},
|
|
"leadership": {"en": "Leadership", "ar": "القيادة"},
|
|
"languages": {"en": "Languages", "ar": "اللغات"},
|
|
}
|
|
for cat_key, items in skills.items():
|
|
label = _category_labels.get(cat_key, {}).get(language, cat_key.replace("_", " ").title())
|
|
if cat_key == "languages":
|
|
formatted_items = [
|
|
f"{lang['name']} ({lang['level']})" for lang in items
|
|
]
|
|
else:
|
|
formatted_items = list(items)
|
|
skill_categories.append({"name": label, "items": formatted_items})
|
|
|
|
return {
|
|
"language": language,
|
|
"name": personal.get(f"name_{language}", personal.get("name_en", "")),
|
|
"title": personal.get(f"title_{language}", personal.get("title_en", "")),
|
|
"headline": personal.get(f"headline_{language}", ""),
|
|
"email": personal.get("email", ""),
|
|
"phone": personal.get("phone", ""),
|
|
"location": personal.get(f"location_{language}", ""),
|
|
"linkedin": profile.get("links", {}).get("linkedin", ""),
|
|
"summary": summary,
|
|
"current_company": current.get("company", current.get("company_ar", "")),
|
|
"current_title": current.get("title", current.get("title_ar", "")),
|
|
"current_location": current.get("location", current.get("location_ar", "")),
|
|
"current_start_date": current.get("start_date", ""),
|
|
"current_bullets": current_bullets,
|
|
"previous_roles": previous_roles,
|
|
"leadership": leadership,
|
|
"education_degree": education.get("degree", ""),
|
|
"education_institution": education.get("institution", ""),
|
|
"education_location": education.get("location", ""),
|
|
"education_period": education.get("period", ""),
|
|
"education_highlights": education.get("highlights", []),
|
|
"certifications": profile.get("certifications", []),
|
|
"awards": profile.get("awards", []),
|
|
"skill_categories": skill_categories,
|
|
"ats_keywords": enhanced.get("skills_keywords", []),
|
|
"references": profile.get("references", []),
|
|
}
|