File size: 4,742 Bytes
7498f2c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
from __future__ import annotations
from typing import List, Tuple, Optional
import textwrap
import re

from .text import normalize_whitespace


ACTION_VERBS = [
    "Led", "Built", "Improved", "Optimized", "Delivered", "Designed", "Implemented", "Automated",
    "Reduced", "Increased", "Analyzed", "Developed", "Launched", "Managed", "Resolved", "Created",
    # Reed-recommended action words
    "Achieved", "Formulated", "Planned", "Generated", "Represented", "Completed",
]

# Weak openers to avoid on bullets and suggested stronger replacements
_WEAK_TO_STRONG = [
    (re.compile(r"^\s*-\s*responsible for\s+", re.IGNORECASE), "- Led "),
    (re.compile(r"^\s*-\s*tasked with\s+", re.IGNORECASE), "- Executed "),
    (re.compile(r"^\s*-\s*worked on\s+", re.IGNORECASE), "- Delivered "),
    (re.compile(r"^\s*-\s*helped\s+", re.IGNORECASE), "- Supported "),
    (re.compile(r"^\s*-\s*assisted with\s+", re.IGNORECASE), "- Supported "),
    (re.compile(r"^\s*-\s*handled\s+", re.IGNORECASE), "- Managed "),
]


def strengthen_action_verbs(text: str) -> str:
    """Promote weak bullet openers to stronger action verbs (The Muse guidance)."""
    if not text:
        return text
    lines = text.splitlines()
    out: List[str] = []
    for line in lines:
        new_line = line
        for pattern, repl in _WEAK_TO_STRONG:
            if pattern.search(new_line):
                new_line = pattern.sub(repl, new_line)
                break
        out.append(new_line)
    return "\n".join(out)


def make_bullets(lines: List[str]) -> str:
    clean_lines = [f"- {normalize_whitespace(l)}" for l in lines if l and l.strip()]
    return "\n".join(clean_lines)


def ensure_keywords(text: str, keywords: List[str], max_new: int = 30, allowed_keywords: Optional[set] = None) -> Tuple[str, List[str]]:
    used = []
    missing = []
    lower_text = text.lower()
    for k in keywords:
        if k.lower() in lower_text:
            used.append(k)
        else:
            missing.append(k)
    if missing:
        additions = []
        actually_added = []
        for k in missing:
            if len(actually_added) >= max_new:
                break
            if allowed_keywords is not None and k.lower() not in allowed_keywords:
                continue
            additions.append(f"Experience with {k}.")
            actually_added.append(k)
        if additions:
            text = text.rstrip() + "\n\nKeywords: " + ", ".join(actually_added) + "\n" + make_bullets(additions)
            used.extend(actually_added)
    return text, used


def format_resume_header(full_name: str, headline: str, email: str | None, phone: str | None, location: str | None, links: dict) -> str:
    contact_parts = [p for p in [email, phone, location] if p]
    links_str = " | ".join([f"{k}: {v}" for k, v in links.items()]) if links else ""
    top_line = f"{full_name} โ€” {headline}" if headline else full_name
    contact_line = " | ".join(filter(None, [" | ".join(contact_parts), links_str]))
    return "\n".join([top_line, contact_line]).strip() + "\n"


def format_experience_section(experiences: List[dict]) -> str:
    sections: List[str] = []
    for exp in experiences:
        header = f"{exp.get('title','')} โ€” {exp.get('company','')} ({exp.get('start_date','')} โ€“ {exp.get('end_date','Present')})"
        bullets = exp.get("achievements") or []
        if not bullets:
            bullets = [
                f"{ACTION_VERBS[0]} key outcomes relevant to the role.",
                "Collaborated cross-functionally to deliver results.",
                "Drove measurable impact with data-informed decisions.",
            ]
        sections.append("\n".join([header, make_bullets(bullets)]))
    return "\n\n".join(sections)


def format_skills_section(skills: List[str]) -> str:
    if not skills:
        return ""
    return "Skills: " + ", ".join(skills)


def basic_resume_template(header: str, summary: str | None, skills: str, experience: str, education: str | None) -> str:
    parts = [header]
    if summary:
        parts.append("\nSummary\n" + textwrap.fill(summary, width=100))
    if skills:
        parts.append("\n" + skills)
    if experience:
        parts.append("\n\nExperience\n" + experience)
    if education:
        parts.append("\n\nEducation\n" + education)
    return "\n".join(parts).strip() + "\n"


def basic_cover_letter_template(greeting: str, body_paragraphs: List[str], closing: str, signature: str) -> str:
    body = "\n\n".join(textwrap.fill(p, width=100) for p in body_paragraphs)
    return "\n".join([greeting, "", body, "", closing, "", signature]).strip() + "\n"