Spaces:
Sleeping
Sleeping
File size: 6,417 Bytes
6d3a6a8 cc74dfc 6d3a6a8 3cc1867 6d3a6a8 7b41b0f 6d3a6a8 7b41b0f 6d3a6a8 | 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 127 128 129 130 131 132 | """
ANTI-CHEAT regression — prove the system is not gaming its own scorer.
Runs the REAL uploaded resume through the full pipeline (stub LLM = honest floor)
and checks the INDEPENDENT validator (not candidate_fit) agrees:
1. A PM JD reaches READY_90_PLUS for the PM resume (both scores >= 90).
2. A cybersecurity PM JD is REVIEW_REQUIRED with security tools flagged.
3. A backend-engineer JD does NOT become CLEAN_90_PLUS for a PM (engineering
hard skills not auto-claimed; download blocked or quality != CLEAN).
4. A JD needing a specific cert does not get that cert invented.
5. A 12-year/Director JD is NOT matched as READY for a mid-level candidate
(independent seniority check fails).
6. Final score is from parsed EXPORTED text (independent re-parses the file).
7. If the renderer drops a keyword, the independent score reflects it.
"""
import os, sys, io, shutil
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
sys.path.insert(0, os.path.abspath("."))
from src.resume_customizer import ResumeCustomizer, _read_docx_text
from src.llm_client import LLMClient
from src.ats_validator import validate_resume
from src.resume_parser_v2 import parse_resume_pdf
# Isolate the vault so prior user confirmations don't change anti-cheat results.
import src.candidate_vault as _cv
_cv._VAULT_PATH = "data/_test_vault_anticheat.json"
if os.path.exists(_cv._VAULT_PATH):
os.remove(_cv._VAULT_PATH)
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):
shutil.copyfile(src_pdf, dst)
if os.path.exists("data/resume/_parsed.json"):
os.remove("data/resume/_parsed.json")
def stub(self, cfg, resume_dict, jd_text, job_title, company, assessment, **kwargs):
o = dict(resume_dict)
o["summary"] = (f"Strong-fit candidate for {job_title} at {company}: "
+ (resume_dict.get("summary") or "PM 5+ years."))
return o
LLMClient.tailor_resume_v4 = stub
rc = ResumeCustomizer.__new__(ResumeCustomizer)
rc.llm = LLMClient.__new__(LLMClient); rc.resume_text = ""
rc.output_dir = "data/output/resumes/_anticheat"; 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 = []
ok = True
def check(name, cond, detail=""):
global ok; ok = ok and cond
print(f" [{'PASS' if cond else 'FAIL'}] {name} {detail}")
def gen(jf, co):
jd = open(f"tests/fixtures/jds/{jf}.txt", encoding="utf-8").read()
job = {"title": "Product Manager", "company": co, "description": jd, "_raw_assessment": {}}
rc._generate_resume_v4(job, cfg=rc.fast_model_cfg,
filepath=os.path.join(rc.output_dir, f"{co}.docx"))
return jd, job, job.get("_v2_report", {})
print("1. PM JD -> READY_90_PLUS, both scores >= 90:")
jd, job, r = gen("generic_pm_3_7yrs", "PM")
internal = r.get("estimated_scores", {}).get("jd_match", 0)
indep = r.get("independent_jd_match", 0)
check("internal >= 90", internal >= 90, f"internal={internal}")
check("independent >= 90", indep >= 90, f"independent={indep}")
check("download allowed", job.get("download_allowed") is True)
print("2. Cybersecurity PM -> security tooling is HIGH-risk (confirm), not auto-faked:")
jd, job, r = gen("sumo_logic_pm", "Sec")
_sec = ("siem", "soar", "xdr", "secops", "threat intelligence",
"threat detection", "security operations")
_high = [t.lower() for t in r.get("high_risk_terms_for_confirmation", [])]
check("security terms classified HIGH-risk (need confirmation)",
any(t in _high for t in _sec), str(_high)[:90])
parsed_sec = _read_docx_text(os.path.join(rc.output_dir, "Sec.docx")).lower()
auto_claimed = [t for t in _sec if t in parsed_sec]
check("security tooling NOT auto-claimed in resume (HIGH not auto-included)",
not auto_claimed, f"auto-claimed={auto_claimed}")
print("3. Backend-engineer JD must NOT be CLEAN_90_PLUS for a PM:")
jd, job, r = gen("backend_engineer", "Backend")
q = r.get("quality_flag", "")
indep = r.get("independent_jd_match", 0)
check("quality is not CLEAN_90_PLUS", q != "CLEAN_90_PLUS", f"quality={q} independent={indep}")
parsed = _read_docx_text(os.path.join(rc.output_dir, "Backend.docx")).lower()
check("did not auto-claim 'java'/'spring boot' as evidenced",
"spring boot" not in parsed or "java" not in parsed,
"engineering hard skills not stuffed")
print("4. Specific cert not invented:")
# Backend JD has no cert; use a cert check on parsed text — no fake 'AWS Certified'
check("no invented 'aws certified'/'cissp'",
"aws certified" not in parsed and "cissp" not in parsed)
print("5. 12-year/Director JD not READY for mid-level candidate:")
jd, job, r = gen("senior_pm_10yrs", "SrPM")
indep = r.get("independent_jd_match", 0)
_base = parse_resume_pdf(dst).to_flat_text()
val = validate_resume(jd, _read_docx_text(os.path.join(rc.output_dir, "SrPM.docx")),
base_resume_text=_base)
check("independent seniority check fails (12y vs candidate)", not val.seniority_ok,
f"seniority_ok={val.seniority_ok} notes={val.notes}")
check("not downloadable as READY", job.get("download_allowed") is not True,
f"download={job.get('download_allowed')} status={job.get('_v2_status')}")
print("6. Score is from parsed EXPORTED text:")
# validate_resume re-parses the file independently; if it scores, it parsed.
check("independent validator scored parsed export", isinstance(indep, int))
print("7. Dropping a keyword lowers the independent score:")
full = _read_docx_text(os.path.join(rc.output_dir, "PM.docx"))
jd_pm = open("tests/fixtures/jds/generic_pm_3_7yrs.txt", encoding="utf-8").read()
base_score = validate_resume(jd_pm, full).independent_jd_match
# Simulate a renderer that dropped the Skills + most of Experience (keep only
# the header + summary). The independent score MUST fall.
lines = [l for l in full.splitlines() if l.strip()]
truncated = "\n".join(lines[: max(4, int(len(lines) * 0.25))])
drop_score = validate_resume(jd_pm, truncated).independent_jd_match
check("truncated resume scores lower", drop_score < base_score,
f"{base_score} -> {drop_score}")
print("\n" + ("✓ ALL ANTI-CHEAT CHECKS PASS" if ok else "✗ SOME ANTI-CHEAT CHECKS FAILED"))
sys.exit(0 if ok else 1)
|