File size: 5,556 Bytes
4703cd3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"""
generator.py — AI Resume Improvement Generator.
 
Uses google/flan-t5-base (free, open-source) to generate:
  1. An improved version of the resume text
  2. A polished professional summary
 
FLAN-T5 is instruction-tuned, making it ideal for text rewriting tasks
without requiring fine-tuning.
 
Model size: ~250 MB. Downloaded once and cached automatically by
HuggingFace transformers.
"""
 
# ---------------------------------------------------------------------------
# Lazy model loading — keeps Streamlit startup fast
# ---------------------------------------------------------------------------
_pipeline = None
 
 
def _get_pipeline():
    """Load and cache the FLAN-T5 text2text pipeline."""
    global _pipeline
    if _pipeline is None:
        from transformers import pipeline
        _pipeline = pipeline(
            "text2text-generation",
            model="google/flan-t5-base",
            max_new_tokens=512,
        )
    return _pipeline
 
 
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
 
def generate_improved_resume(
    resume_text: str,
    job_description: str = "",
    missing_sections: list = None,
) -> str:
    """
    Generate an improved, ATS-friendly version of the resume.
 
    Rewrites the resume with:
      - Professional tone
      - Bullet-point format
      - Stronger action verbs
      - Explicit mention of any missing sections
 
    Args:
        resume_text     : original extracted resume text
        job_description : optional job description to tailor the rewrite
        missing_sections: list of sections the model should add
 
    Returns:
        Improved resume text as a string.
    """
    if not resume_text:
        return "No resume text provided for improvement."
 
    if missing_sections is None:
        missing_sections = []
 
    pipe = _get_pipeline()
 
    # Build a focused, instruction-style prompt
    # FLAN-T5 works best with clear task instructions
    truncated_resume = _truncate(resume_text, 400)
    jd_hint = f"\nThe target job requires: {_truncate(job_description, 100)}" if job_description else ""
    missing_hint = (
        f"\nMake sure to include sections for: {', '.join(missing_sections)}."
        if missing_sections
        else ""
    )
 
    prompt = (
        f"Rewrite this resume to be professional, ATS-friendly, and impactful. "
        f"Use bullet points, strong action verbs, and quantify achievements. "
        f"Keep all original facts.{jd_hint}{missing_hint}\n\n"
        f"Original Resume:\n{truncated_resume}\n\n"
        f"Improved Resume:"
    )
 
    try:
        result = pipe(prompt, max_new_tokens=512, do_sample=False)
        improved = result[0]["generated_text"].strip()
        return improved if improved else "⚠️ Could not generate improvement. Please try again."
    except Exception as e:
        return f"⚠️ Generation error: {str(e)}"
 
 
def generate_professional_summary(
    name: str,
    skills: list,
    experience_present: bool,
    job_title: str = "",
) -> str:
    """
    Generate a short professional summary / objective paragraph.
 
    Args:
        name              : candidate name (or empty string)
        skills            : list of extracted skill strings
        experience_present: whether work experience was detected
        job_title         : optional target job title
 
    Returns:
        A 2–3 sentence professional summary.
    """
    pipe = _get_pipeline()
 
    skills_str = ", ".join(skills[:8]) if skills else "various technical skills"
    exp_phrase = (
        "with hands-on professional experience"
        if experience_present
        else "seeking to start a professional career"
    )
    target = f" targeting a {job_title} role" if job_title else ""
 
    prompt = (
        f"Write a 2-3 sentence professional resume summary for a candidate "
        f"{exp_phrase} in {skills_str}{target}. "
        f"Keep it concise, first-person, and ATS-friendly."
    )
 
    try:
        result = pipe(prompt, max_new_tokens=120, do_sample=False)
        return result[0]["generated_text"].strip()
    except Exception as e:
        return f"⚠️ Summary generation error: {str(e)}"
 
 
def generate_section_content(section_name: str, context: str) -> str:
    """
    Generate content for a specific missing resume section.
 
    Args:
        section_name: e.g., 'Projects', 'Skills', 'Experience'
        context     : relevant resume text to base the generation on
 
    Returns:
        Generated section content as a string.
    """
    pipe = _get_pipeline()
 
    prompt = (
        f"Based on the following resume context, write a professional "
        f"'{section_name}' section in bullet-point format:\n\n"
        f"{_truncate(context, 200)}\n\n"
        f"{section_name} Section:"
    )
 
    try:
        result = pipe(prompt, max_new_tokens=200, do_sample=False)
        return result[0]["generated_text"].strip()
    except Exception as e:
        return f"⚠️ Section generation error: {str(e)}"
 
 
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
 
def _truncate(text: str, max_words: int) -> str:
    """Truncate text to max_words words to stay within model context limit."""
    words = text.split()
    if len(words) > max_words:
        return " ".join(words[:max_words]) + "..."
    return text