""" Deterministic regression for External ATS Feedback Repair Mode. Uses StubProvider (no API keys). Pastes fake external-checker feedback that mixes: - LOW/plausible PM terms (should be woven in), - a BLOCKED credential (CISSP — must NEVER appear), - a HIGH-risk specialized term (SIEM — must NOT be auto-added; surfaced for confirmation). Asserts the honesty guarantees hold: blocked/high terms stay OUT of the resume, scores come from the re-parsed export, and the result carries the right buckets. """ import os, sys, io, shutil sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") sys.path.insert(0, os.path.abspath(".")) # Isolate the vault so prior confirmations don't change results. import src.candidate_vault as _cv _cv._VAULT_PATH = "data/_test_vault_feedback.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 parse_external_feedback, repair_with_external_feedback from src.resume_customizer import _read_docx_text ok = True def check(name, cond, detail=""): global ok; ok = ok and cond print(f" [{'PASS' if cond else 'FAIL'}] {name} {detail}") # 1. Feedback parser fb = ("Resume Match 73%. Missing keywords: roadmap, stakeholder management, " "A/B testing, CISSP, SIEM. Matched keywords: agile, jira") parsed = parse_external_feedback(fb) print("1. parse_external_feedback:") check("external_score parsed = 73", parsed["external_score"] == 73, str(parsed["external_score"])) check("missing keywords parsed", "roadmap" in [m.lower() for m in parsed["missing_keywords"]] and "cissp" in [m.lower() for m in parsed["missing_keywords"]]) check("no trailing punctuation", all(not m.endswith(".") for m in parsed["missing_keywords"])) # 2. Repair honesty base = parse_resume_pdf(dst) jd = open("tests/fixtures/jds/generic_pm_3_7yrs.txt", encoding="utf-8").read() job = {"title": "Product Manager", "company": "TestCo", "description": jd, "_raw_assessment": {}} res = repair_with_external_feedback(job, feedback_text=fb, provider=StubProvider(), base_resume=base, output_dir="data/output/resumes/_fb_test") print("2. repair_with_external_feedback:", res.get("status"), res.get("scores")) check("no error", "error" not in res, res.get("error", "")) check("CISSP is blocked", "CISSP" in res.get("blocked_terms", [])) check("SIEM is unresolved-high", "SIEM" in res.get("unresolved_high_risk_terms", [])) check("external_score carried = 73", res.get("external_score") == 73) check("scores present (internal/independent)", isinstance(res.get("scores", {}).get("internal_jd_match"), int) and isinstance(res.get("scores", {}).get("independent_jd_match"), int)) txt = _read_docx_text(res["resume_path"]).lower() check("CISSP NOT in resume (no fabricated cert)", "cissp" not in txt) check("SIEM NOT auto-added (high-risk, unconfirmed)", "siem" not in txt) # 3. No-keywords guard res2 = repair_with_external_feedback(job, feedback_text="great resume, no notes", provider=StubProvider(), base_resume=base, output_dir="data/output/resumes/_fb_test") check("empty feedback -> no_missing_keywords error", res2.get("error") == "no_missing_keywords") print("\n" + ("✓ ALL FEEDBACK-REPAIR CHECKS PASS" if ok else "✗ SOME FAILED")) shutil.rmtree("data/output/resumes/_fb_test", ignore_errors=True) if os.path.exists(_cv._VAULT_PATH): os.remove(_cv._VAULT_PATH) sys.exit(0 if ok else 1)