Spaces:
Sleeping
Sleeping
| """ | |
| Deterministic regression for the recruiter-grade SKILLS section (Phase 7, R14). | |
| Locks in the renderer fix: the DOCX SKILLS block must read like a real skills | |
| list β AT MOST one labelled line per category, NO repeated "Core Competencies:" | |
| headers, and a recruiter-credible 18-28 total de-duplicated items β instead of | |
| the ~12 identical "Core Competencies:" dump lines the old PER_LINE=12 chunking | |
| produced. No LLM, no network: builds a Resume directly and renders it. | |
| """ | |
| import os | |
| import sys | |
| import io | |
| import shutil | |
| import tempfile | |
| sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") | |
| sys.path.insert(0, os.path.abspath(".")) | |
| from docx import Document | |
| from src.resume_model import Resume, Contact, Role, Education | |
| from src.resume_renderer import render_resume_docx | |
| ok = True | |
| def check(name, cond, detail=""): | |
| global ok | |
| ok = ok and cond | |
| print(f" [{'PASS' if cond else 'FAIL'}] {name} {detail}") | |
| # Known terms that land in dedicated buckets (exercise multiple category lines). | |
| TOOLS = ["Jira", "Confluence", "Figma", "Mixpanel", "Amplitude", "SQL"] | |
| METHODS = ["Agile", "Scrum", "Kanban", "Experimentation", "Prioritization"] | |
| DOMAINS = ["FinTech", "SaaS", "B2B", "Payments"] | |
| # ~40 generic terms β none belong to Tools/Methods/Domains, so they all fall | |
| # into "Core Competencies" and would (pre-fix) overflow into repeated headers. | |
| CORE = [ | |
| "Stakeholder Communication", "Cross-functional Leadership", "Team Mentorship", | |
| "Strategic Planning", "Problem Solving", "Executive Reporting", "Vision Setting", | |
| "Customer Empathy", "Negotiation", "Presentation Skills", "Decision Making", | |
| "Conflict Resolution", "Time Management", "Budgeting", "Vendor Management", | |
| "Change Management", "Risk Management", "Quality Assurance", "Process Improvement", | |
| "Documentation", "Requirements Gathering", "Wireframing", "Storytelling", | |
| "Public Speaking", "Coaching", "Hiring", "Onboarding", "Cross-team Collaboration", | |
| "Prioritisation Frameworks", "Financial Modeling", "Forecasting", "Pricing Strategy", | |
| "Partnerships", "Customer Success", "Account Management", "Technical Writing", | |
| "Localization", "Accessibility", "Internationalization", "Compliance Reporting", | |
| ] | |
| SKILLS = TOOLS + METHODS + DOMAINS + CORE # ~55 mixed terms | |
| resume = Resume( | |
| name="Jordan Avery", | |
| contact=Contact(phone="+1 555 0100", email="jordan.avery@example.com", | |
| location="Austin, TX, USA"), | |
| summary=("Strong-fit candidate for Senior Product Manager: 6+ years shipping " | |
| "B2B SaaS products. Drives roadmap, discovery, and cross-functional " | |
| "delivery with measurable revenue impact."), | |
| skills=SKILLS, | |
| roles=[ | |
| Role(title="Senior Product Manager", company="Acme Corp", location="Austin, TX", | |
| dates="Jan 2022 β Present", | |
| bullets=["Led discovery and roadmap for a payments platform serving 2M users.", | |
| "Drove a 30% lift in activation via experimentation and analytics.", | |
| "Partnered with engineering and design across three squads."]), | |
| Role(title="Product Manager", company="Globex", location="Remote", | |
| dates="Jun 2019 β Dec 2021", | |
| bullets=["Owned the B2B onboarding funnel; cut time-to-value by 40%.", | |
| "Shipped 12 features against a quarterly OKR cadence.", | |
| "Built stakeholder alignment across sales and support."]), | |
| ], | |
| education=[Education(degree="B.S. β Computer Science", institution="UT Austin", | |
| dates="2015 β 2019")], | |
| ) | |
| CATEGORIES = ["Tools & Analytics:", "Methodologies:", "Domains:", "Core Competencies:"] | |
| tmpdir = tempfile.mkdtemp(prefix="skills_dist_") | |
| docx_path = os.path.join(tmpdir, "skills_test.docx") | |
| try: | |
| render_resume_docx(resume, docx_path) | |
| doc = Document(docx_path) | |
| paras = [p.text.strip() for p in doc.paragraphs if p.text and p.text.strip()] | |
| # Count category-label occurrences and tally rendered skill items. | |
| label_counts = {c: 0 for c in CATEGORIES} | |
| total_items = 0 | |
| for text in paras: | |
| for label in CATEGORIES: | |
| if text.startswith(label): | |
| label_counts[label] += 1 | |
| body = text[len(label):].strip() | |
| items = [x for x in (s.strip() for s in body.split(",")) if x] | |
| total_items += len(items) | |
| break | |
| check("'Core Competencies:' appears at most once (no repeated headers)", | |
| label_counts["Core Competencies:"] <= 1, | |
| f"count={label_counts['Core Competencies:']}") | |
| repeated = {c: n for c, n in label_counts.items() if n > 1} | |
| check("no category label appears more than once", | |
| not repeated, f"repeated={repeated}") | |
| check("at least one category line rendered", | |
| sum(label_counts.values()) >= 1, f"labels={label_counts}") | |
| check("total rendered skill items within 18-28 inclusive", | |
| 18 <= total_items <= 28, f"total_items={total_items}") | |
| print("\n" + ("\u2713 SKILLS-DISTRIBUTION REGRESSION PASSES" if ok | |
| else "\u2717 SOME CHECKS FAILED")) | |
| finally: | |
| shutil.rmtree(tmpdir, ignore_errors=True) | |
| sys.exit(0 if ok else 1) | |