File size: 5,021 Bytes
daede4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Simulate the WORST production case: LLM returns only 2 roles (drops BYJU's
and ML Edutech), no recruiter pitch, 3 bullets per role. Verify the v4
backfill + pitch enforcement + weaving brings it to 90%+.
"""
import os, sys, io, shutil
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

src_pdf = r'C:\Users\Nxtwave\Desktop\resume\Saiteja_Tirunagari_Resume A 26 - Copy.pdf'
dst_pdf = 'data/resume/resume.pdf'
os.makedirs(os.path.dirname(dst_pdf), exist_ok=True)
if not os.path.exists(dst_pdf):
    shutil.copyfile(src_pdf, dst_pdf)

# Monkey-patch the LLM call to return a deliberately weak v4 response
from src.resume_customizer import ResumeCustomizer, _read_docx_text
from src.llm_client import LLMClient
from src.ats_scorer import score_resume


def make_weak_response(resume_dict, job_title, company):
    """Mimic worst-case LLM: 2 roles only, no pitch, 3 bullets each."""
    return {
        "name": resume_dict["name"],
        "contact": resume_dict["contact"],
        # NO recruiter pitch — generic 2-sentence summary
        "summary": (
            "Product Manager with 5+ years of experience driving product development "
            "in fast-paced startups. Built funnel optimization and AI-powered features "
            "that scaled to 140,000+ users with measurable business impact."
        ),
        "roles": [
            # Only 2 of the 4 original roles — LLM dropped older ones
            {
                "title": resume_dict["roles"][0]["title"],
                "company": resume_dict["roles"][0]["company"],
                "location": resume_dict["roles"][0]["location"],
                "dates": resume_dict["roles"][0]["dates"],
                "bullets": [
                    "Led NIAT Application Portal revamp scaling to 141,269 verified leads",
                    "Drove payment conversion lift of +35.87 percentage points",
                    "Built AI chatbot generating 6,776 leads",
                ],
            },
            {
                "title": resume_dict["roles"][1]["title"],
                "company": resume_dict["roles"][1]["company"],
                "location": resume_dict["roles"][1]["location"],
                "dates": resume_dict["roles"][1]["dates"],
                "bullets": [
                    "Managed 20 customer-success specialists covering 40,000 customers",
                    "Maintained refund rate below 5% and satisfaction above 95%",
                ],
            },
        ],
        "achievements": [
            "Scaled to 141,269 verified leads",
            "Lifted payment conversion +35.87 pp",
        ],
        "education": resume_dict["education"],
    }


# Patch LLMClient.tailor_resume_v4 to return our weak response
_original_v4 = LLMClient.tailor_resume_v4
def _mocked_v4(self, cfg, resume_dict, jd_text, job_title, company, assessment):
    return make_weak_response(resume_dict, job_title, company)
LLMClient.tailor_resume_v4 = _mocked_v4


from src.llm_client import LLMClient as LC
rc = ResumeCustomizer.__new__(ResumeCustomizer)
rc.llm = LC.__new__(LC)
rc.resume_text = ''
rc.output_dir = 'data/output/resumes/_v4_backfill_test'
os.makedirs(rc.output_dir, exist_ok=True)
rc.fast_model_cfg = {"model": "fake", "api_key": "fake", "base_url": "https://fake"}
rc._pending_summary_inject = []


print('━' * 78)
print(f'{"JD":<48} {"v4_only":>9} {"FinalATS":>9} {"Roles":>6} {"Words":>6}')
print('━' * 78)

jd_files = {
    'airtel_pm': ('Product Manager', 'Airtel'),
    'sumo_logic_pm': ('Product Manager', 'Sumo Logic'),
    'edgeverve_pm': ('Product Manager', 'EdgeVerve'),
    'aditya_birla_apm': ('Associate Product Manager', 'Aditya Birla Capital'),
}

for jd_file, (role_title, company) in jd_files.items():
    with open(f'tests/fixtures/jds/{jd_file}.txt', encoding='utf-8') as f:
        jd = f.read()
    job = {
        "title": role_title,
        "company": company,
        "description": jd,
        "_raw_assessment": {"ats_keywords": []},
    }
    filepath = os.path.join(rc.output_dir, f'{company.replace(" ", "_")}_{jd_file}.docx')

    # Call the actual v4 flow
    result = rc._generate_resume_v4(job, cfg=rc.fast_model_cfg, filepath=filepath)

    if not result:
        print(f'{(role_title + " @ " + company):<48} v4 FAILED')
        continue

    text = _read_docx_text(result)
    r = score_resume(text, jd)

    # Count roles + words in the actual generated DOCX
    from docx import Document
    doc = Document(result)
    role_lines = sum(
        1 for p in doc.paragraphs
        if p.text and any(co in p.text for co in ["NxtWave", "Think & Learn", "ML Edutech", "BYJU"])
    )
    print(f'{(role_title + " @ " + company):<48} {"weak":>9} {r["ats_score"]:>8}   {role_lines:>4}   {r["word_count"]:>5}')

print('━' * 78)
print('\nThis test simulates the WORST case: LLM returns 2 roles only, no')
print('pitch, 3 bullets each. v4 backfill + pitch enforcement + weaving')
print('should still produce 90%+ resumes with all 4 candidate roles preserved.')