Spaces:
Sleeping
Sleeping
| """Verify Phase 4 canonical flow on all 4 failing JDs with handcrafted v4 LLM responses.""" | |
| import os, sys, io, shutil, pdfplumber | |
| sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') | |
| # Make sure the source PDF is in the expected place | |
| 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) | |
| from src.resume_parser_v2 import parse_resume_pdf_cached | |
| from src.resume_renderer import render_resume_docx | |
| from src.resume_model import Resume, Role, Education, Contact | |
| from src.ats_scorer import score_resume | |
| from src.resume_customizer import ResumeCustomizer, _read_docx_text | |
| # Load canonical resume | |
| base = parse_resume_pdf_cached(dst_pdf) | |
| print(f'Loaded canonical resume: {base.name}, {len(base.roles)} roles, {len(base.education)} edu entries') | |
| def make_tailored_for(jd_text: str, role: str, company: str, kw_focus: list[str]) -> Resume: | |
| """Build a handcrafted tailored Resume for a JD, weaving focus keywords.""" | |
| # Recruiter pitch + summary | |
| summary = ( | |
| f"Strong-fit candidate for {role} at {company}: 5+ years of PM experience " | |
| f"directly applicable to {kw_focus[0] if kw_focus else 'this role'}. " | |
| f"Owned end-to-end product modules from discovery through launch, authored PRDs, " | |
| f"user stories, wireframes, and acceptance criteria, and partnered with engineering, " | |
| f"design, and QA across sprints. Drove A/B testing on Mixpanel, Amplitude, and GA4 to " | |
| f"track activation, adoption, retention, funnel conversion, and revenue impact. " | |
| f"Hands-on with Jira and Figma; integrated CRM workflows via APIs and webhooks. " | |
| f"{' '.join(['Familiar with ' + k + '.' for k in kw_focus[:3]])}" | |
| ) | |
| # Tailored roles — pick 5-7 bullets per role, rewritten | |
| roles = [ | |
| Role( | |
| title=base.roles[0].title, | |
| company=base.roles[0].company, | |
| location=base.roles[0].location, | |
| dates=base.roles[0].dates, | |
| bullets=[ | |
| "Owned end-to-end product modules for the NIAT Application Portal, partnering with engineering, design, and QA across sprint planning and releases.", | |
| "Authored PRDs, user stories, wireframes, and acceptance criteria for 8 cross-functional releases tracked via Jira and Figma.", | |
| "Tracked activation, adoption, retention, funnel conversion, and revenue impact via Mixpanel, Amplitude, and GA4 dashboards integrated with CRM via APIs and webhooks.", | |
| "Drove A/B experiments lifting payment conversion from 27.37% to 63.24% (+35.87 pp), scaling to 141,269 OTP-verified leads.", | |
| "Translated business goals into roadmap items aligned with founders/leadership; established KPIs and prioritization frameworks.", | |
| "Partnered with data science on AI chatbot productization; gained exposure to MLOps workflows and model validation pipelines.", | |
| f"Worked adjacent to {', '.join(kw_focus[:2])} through cross-functional product integrations.", | |
| ], | |
| ), | |
| Role( | |
| title=base.roles[1].title, | |
| company=base.roles[1].company, | |
| location=base.roles[1].location, | |
| dates=base.roles[1].dates, | |
| bullets=[ | |
| "Managed 20 customer-success specialists covering 40,000 customers; maintained 95%+ satisfaction and <5% refund rate via SLA-driven production support.", | |
| "Coordinated UAT with business stakeholders; documented test results and obtained formal sign-off before production deployment.", | |
| "Led 0→1 product initiatives partnering with engineering on Xplore and Social Emotional Learning pilots.", | |
| ], | |
| ), | |
| Role( | |
| title=base.roles[2].title, | |
| company=base.roles[2].company, | |
| location=base.roles[2].location, | |
| dates=base.roles[2].dates, | |
| bullets=[ | |
| "Increased user retention by 8% by redesigning onboarding using UX research and customer empathy frameworks.", | |
| "Conducted extensive A/B testing and user research to identify pain points and refine features.", | |
| "Gathered and analysed user requirements through stakeholder interviews; produced FSDs and acceptance criteria.", | |
| ], | |
| ), | |
| Role( | |
| title=base.roles[3].title, | |
| company=base.roles[3].company, | |
| location=base.roles[3].location, | |
| dates=base.roles[3].dates, | |
| bullets=[ | |
| "Launched EdTech portfolio of 275 apps with 3M+ cumulative downloads; managed end-to-end IT projects from requirements gathering to production.", | |
| "Drove user acquisition via Google Ads, LinkedIn, and paid social; established strategic partnerships and end-to-end P&L.", | |
| "Built data-driven culture focused on conversion optimization, KPIs, and stakeholder alignment.", | |
| ], | |
| ), | |
| ] | |
| achievements = [ | |
| "Scaled product funnel to 141,269 verified leads with ₹1,120+ Cr annual pipeline", | |
| "Lifted A/B-tested payment conversion +35.87 pp via CRM-integrated nudges", | |
| "Reduced OCR processing cost 97.5% (₹4 → ₹0.10/page) through automation", | |
| "Generated 6,776 leads via AI-driven conversational chatbot", | |
| ] | |
| return Resume( | |
| name=base.name, | |
| contact=base.contact, | |
| summary=summary, | |
| roles=roles, | |
| achievements=achievements, | |
| education=base.education, | |
| ) | |
| # Test each of the 4 failing JDs | |
| jd_focus = { | |
| 'airtel_pm': ('Product Manager', 'Airtel', ['MLOps', 'A/B testing']), | |
| 'sumo_logic_pm': ('Product Manager', 'Sumo Logic', ['SIEM', 'SOAR', 'threat detection']), | |
| 'edgeverve_pm': ('Product Manager', 'EdgeVerve', ['MLOps', 'foundation models', 'AI-First']), | |
| 'aditya_birla_apm': ('Associate Product Manager', 'Aditya Birla Capital', ['FSD', 'UAT', 'requirements elicitation']), | |
| } | |
| out_dir = 'data/output/resumes/_phase4_canonical_test' | |
| os.makedirs(out_dir, exist_ok=True) | |
| rc = ResumeCustomizer.__new__(ResumeCustomizer) | |
| rc.resume_text = '' # not used in v4 path | |
| print() | |
| print('━' * 75) | |
| print(f'{"JD":<48} {"Before":>7} {"After":>7} {"JD-match":>10} {"Pages":>6}') | |
| print('━' * 75) | |
| for jd_file, (role, company, focus) in jd_focus.items(): | |
| with open(f'tests/fixtures/jds/{jd_file}.txt', encoding='utf-8') as f: | |
| jd = f.read() | |
| tailored = make_tailored_for(jd, role, company, focus) | |
| docx_path = os.path.join(out_dir, f'{company.replace(" ", "_")}_{jd_file}.docx') | |
| render_resume_docx(tailored, docx_path) | |
| # Score BEFORE injection | |
| r_before = score_resume(_read_docx_text(docx_path), jd) | |
| # Apply injection safety net | |
| rc._inject_missing_keywords(docx_path, jd) | |
| # Score AFTER injection | |
| text = _read_docx_text(docx_path) | |
| r = score_resume(text, jd) | |
| # Get page count | |
| from src.pdf_writer import docx_to_pdf | |
| try: | |
| pdf = docx_to_pdf(docx_path) | |
| with pdfplumber.open(pdf) as p: | |
| pages = len(p.pages) | |
| except Exception: | |
| pages = '?' | |
| print(f'{(role + " @ " + company):<48} {r_before["ats_score"]:>6} {r["ats_score"]:>6} {r["matched_count"]}/{r["total_jd_kw"]:<3} {pages}') | |
| print('━' * 75) | |
| print(f'\nOutput at: {os.path.abspath(out_dir)}') | |