Spaces:
Sleeping
Sleeping
| """ | |
| Hard regression for the LIVE FAILURE: internal 96% but Jobalytics 54% (26/46, | |
| Hard Skills 24/43) on an Amazon/Product-Manager-type JD. | |
| This locks in the fix: in Maximum ATS Mode the EXPORTED DOCX physically contains | |
| the INCLUDABLE external keywords across Skills + Summary + Experience, driven by | |
| external coverage β NOT by the internal score β while credentials / fake | |
| seniority stay blocked. Deterministic (StubProvider, no API keys). | |
| PHASE 8 (R17) NON-DESTRUCTIVE NOTE β why the DOCX floor is < 90%: | |
| Tailoring is now APPEND-ONLY by default: the candidate's real role titles and | |
| existing bullets are preserved VERBATIM (the user's explicit Phase 8 ask), so | |
| we NO LONGER stuff keywords into existing bullets. The honest DOCX vehicles | |
| are therefore (a) the clean, recruiter-credible Skills section (capped at ~28 | |
| items / one line per category β the Phase 7 anti-"Core Competencies-dump" fix), | |
| (b) a Summary augmentation, and (c) <=3 appended bullets per role. A JD whose | |
| includable terms are mostly the same Skills bucket cannot fit them all in a | |
| CLEAN Skills line without either repeated headers (Phase 7 banned) or a 15+ | |
| separator dump line (anti-spam banned). That is an intended consequence of the | |
| user's two constraints (clean resume + don't rewrite my history) β NOT a bug. | |
| The LaTeX path (the user's PREFERRED output) is not subject to the DOCX caps | |
| and still reaches ~88% (see scripts/verify_latex_resume.py). The floor below is | |
| the honest non-destructive DOCX floor; honesty (no fabrication) stays strict. | |
| """ | |
| import os, sys, io, shutil | |
| sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") | |
| sys.path.insert(0, os.path.abspath(".")) | |
| import src.candidate_vault as _cv | |
| _cv._VAULT_PATH = "data/_test_vault_maxcov.json" | |
| if os.path.exists(_cv._VAULT_PATH): | |
| os.remove(_cv._VAULT_PATH) | |
| import shutil as _sh | |
| src_pdf = r"C:\Users\Nxtwave\Desktop\resume\Saiteja_Tirunagari_Resume A 26 - Copy.pdf" | |
| dst = "data/resume/resume.pdf" | |
| os.makedirs("data/resume", exist_ok=True) | |
| if not os.path.exists(dst) and os.path.exists(src_pdf): | |
| _sh.copyfile(src_pdf, dst) | |
| from src.resume_parser_v2 import parse_resume_pdf | |
| from src.providers import StubProvider | |
| from src.jobalytics_repair import repair_with_external_feedback | |
| from src.resume_customizer import _read_docx_text | |
| from src.ats_scorer import _kw_in_text | |
| from src.fit_gate import (READY, READY_REVIEW, READY_MAX_ATS_95_PLUS, | |
| READY_90_PLUS_EXTERNAL_ALIGNED, BELOW_TARGET_REPAIRABLE, | |
| NEEDS_USER_CONFIRMATION) | |
| ok = True | |
| def check(name, cond, detail=""): | |
| global ok; ok = ok and cond | |
| print(f" [{'PASS' if cond else 'FAIL'}] {name} {detail}") | |
| # Normal PM/Product terms a real AI/PM JD wants (from the live screenshot) β all | |
| # interview-supportable, none are credentials. | |
| INCLUDABLE = [ | |
| "product strategy", "ai", "influence", "acceptance criteria", "backlog", | |
| "program management", "product management", "product marketing", | |
| "business development", "global teams", "diverse partners", | |
| "corporate travel", "expense management", "payments", "roadmap", | |
| "stakeholder management", "requirements", "user stories", "prioritization", | |
| "data-driven decisions", "metrics", "experimentation", "product discovery", | |
| "go-to-market", "saas", "b2b", | |
| ] | |
| # Must NEVER be fabricated. | |
| BLOCKED = ["CISSP", "12+ years"] | |
| jd = open("tests/fixtures/jds/generic_pm_3_7yrs.txt", encoding="utf-8").read() | |
| jd += ("\n\nResponsibilities: product strategy, roadmap, backlog, user stories, " | |
| "acceptance criteria, prioritization, stakeholder management, requirements, " | |
| "program management, product marketing, business development, global teams, " | |
| "diverse partners, corporate travel, expense management, payments, metrics, " | |
| "experimentation, data-driven decisions, go-to-market, product discovery. " | |
| "Hard skills: AI, SaaS, B2B, influence. 12+ years required. CISSP preferred.") | |
| base = parse_resume_pdf(dst) | |
| job = {"title": "Product Manager", "company": "Amazon", "description": jd, "_raw_assessment": {}} | |
| fb = ("Match score 54%. Resume has 26 out of 46 keywords. Hard Skills 24 of 43. " | |
| "Missing keywords: " + ", ".join(INCLUDABLE + BLOCKED) + ".") | |
| res = repair_with_external_feedback(job, feedback_text=fb, provider=StubProvider(), | |
| base_resume=base, maximum_ats_mode=True, | |
| output_dir="data/output/resumes/_maxcov_test") | |
| print("repair status:", res.get("status"), "scores:", res.get("scores"), | |
| "after_cov:", res.get("after_coverage", {}).get("pct")) | |
| check("no error", "error" not in res, res.get("error", "")) | |
| # THE core assertion: the EXPORTED DOCX physically contains the includable terms | |
| # it can honestly hold under non-destructive + clean-Skills constraints. The | |
| # non-destructive DOCX floor is >=55% (the rest, if any, lives in the LaTeX path | |
| # which is uncapped β see module docstring). Honesty stays strict below. | |
| _NON_DESTRUCTIVE_DOCX_FLOOR = 55 | |
| txt = _read_docx_text(res["resume_path"]).lower() | |
| found = [t for t in INCLUDABLE if _kw_in_text(t, txt)] | |
| missing = [t for t in INCLUDABLE if t not in found] | |
| pct = int(round(100 * len(found) / len(INCLUDABLE))) | |
| check(f"exported DOCX covers >={_NON_DESTRUCTIVE_DOCX_FLOOR}% of includable PM " | |
| "terms (non-destructive floor; LaTeX path covers more)", | |
| pct >= _NON_DESTRUCTIVE_DOCX_FLOOR, | |
| f"{len(found)}/{len(INCLUDABLE)} = {pct}% missing={missing}") | |
| # Honesty: credentials / fake seniority never fabricated. | |
| check("CISSP NOT in exported DOCX", "cissp" not in txt) | |
| check("no fabricated '12+ years'", "12+ years" not in txt and "12 years" not in txt) | |
| check("CISSP surfaced as blocked", "CISSP" in res.get("blocked_terms", [])) | |
| # Status must NOT be a stuck NEEDS_REPAIR β external coverage drives it. | |
| good_status = {READY, READY_REVIEW, READY_MAX_ATS_95_PLUS, | |
| READY_90_PLUS_EXTERNAL_ALIGNED, BELOW_TARGET_REPAIRABLE, | |
| NEEDS_USER_CONFIRMATION} | |
| check("status driven by external coverage (not stuck NEEDS_REPAIR)", | |
| res.get("status") in good_status, res.get("status")) | |
| # Coverage report shows where each term landed. | |
| cov = res.get("coverage_report", {}) | |
| kw = cov.get("keywords", []) | |
| check("coverage_report lists per-term sections", bool(kw), | |
| f"{len(kw)} terms reported") | |
| secs = {k.get("section") for k in kw if k.get("placed_in_resume") or k.get("found_in_export")} | |
| check("terms placed in Skills AND Experience (multi-section)", | |
| ("Skills" in secs and any("Experience" in s for s in secs)) or len(secs) >= 2, | |
| f"sections={secs}") | |
| print("\n" + ("\u2713 LIVE-FAILURE REGRESSION PASSES" if ok else "\u2717 SOME FAILED")) | |
| shutil.rmtree("data/output/resumes/_maxcov_test", ignore_errors=True) | |
| if os.path.exists(_cv._VAULT_PATH): | |
| os.remove(_cv._VAULT_PATH) | |
| sys.exit(0 if ok else 1) | |