Spaces:
Sleeping
Sleeping
| """ | |
| Provider evaluation harness (spec #6, #7). | |
| Reusable functions to run the EXACT production generation logic | |
| (`ResumeCustomizer._generate_resume_v4`) once per provider on a shared set of | |
| JDs, then collect comparable metrics. The scripts in scripts/ format the tables. | |
| Model-independent: works with any provider (Claude / Kimi / NVIDIA) and with the | |
| deterministic StubProvider for offline smoke runs. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import re | |
| import json | |
| import time | |
| import statistics | |
| from typing import List, Optional | |
| from .resume_model import Resume | |
| # ββ loaders ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_base_resume() -> Optional[Resume]: | |
| """Load the candidate base resume from the parsed cache or the real PDF.""" | |
| cache = os.path.join("data", "resume", "_parsed.json") | |
| if os.path.exists(cache): | |
| try: | |
| with open(cache, encoding="utf-8") as f: | |
| data = json.load(f) | |
| return Resume.from_dict(data.get("resume", data)) | |
| except Exception: | |
| pass | |
| pdf = os.path.join("data", "resume", "resume.pdf") | |
| if os.path.exists(pdf): | |
| from .resume_parser_v2 import parse_resume_pdf_cached | |
| return parse_resume_pdf_cached(pdf) | |
| return None | |
| def load_jd_fixtures(limit: int = None) -> List[dict]: | |
| """Load JD fixtures as job dicts from tests/fixtures/jds/*.txt.""" | |
| jd_dir = os.path.join("tests", "fixtures", "jds") | |
| jobs: List[dict] = [] | |
| if not os.path.isdir(jd_dir): | |
| return jobs | |
| for fn in sorted(os.listdir(jd_dir)): | |
| if not fn.endswith(".txt") or fn.lower() == "readme.txt": | |
| continue | |
| path = os.path.join(jd_dir, fn) | |
| try: | |
| with open(path, encoding="utf-8") as f: | |
| text = f.read() | |
| except Exception: | |
| continue | |
| slug = os.path.splitext(fn)[0] | |
| title, company = _title_company_from_slug(slug, text) | |
| jobs.append({"title": title, "company": company, "description": text, | |
| "ats_keywords": "", "_raw_assessment": {}, "_jd_slug": slug}) | |
| if limit: | |
| jobs = jobs[:limit] | |
| return jobs | |
| def _title_company_from_slug(slug: str, text: str) -> tuple: | |
| first = (text.strip().splitlines() or [""])[0][:60] | |
| pretty = slug.replace("_", " ").title() | |
| return (f"Product Manager ({pretty})", pretty) | |
| # ββ single evaluation ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _make_customizer(llm, base_text: str, output_dir: str): | |
| from .resume_customizer import ResumeCustomizer | |
| c = ResumeCustomizer.__new__(ResumeCustomizer) | |
| c.llm = llm | |
| c.resume_text = base_text | |
| c.fast_model_cfg = None | |
| c.output_dir = output_dir | |
| os.makedirs(output_dir, exist_ok=True) | |
| return c | |
| def evaluate_provider_on_job(provider, base_resume: Resume, job: dict, | |
| output_dir: str, llm=None) -> dict: | |
| """Run one provider on one job through the production v4 path; return a row.""" | |
| c = _make_customizer(llm, base_resume.to_flat_text(), output_dir) | |
| safe = re.sub(r'[\\/*?:"<>|]', "", f"{provider.name}_{job.get('_jd_slug', job.get('company',''))}")[:90] | |
| filepath = os.path.join(output_dir, safe + ".docx") | |
| row = {"provider": getattr(provider, "name", "?"), | |
| "job": job.get("_jd_slug", job.get("company", "")), | |
| "internal": 0, "independent": 0, "readability": 0, | |
| "status": "", "repair_attempts": 0, "risk_terms": 0, | |
| "schema_errors": 0, "provider_errors": 0, "download_allowed": False, | |
| "runtime_s": 0.0} | |
| t0 = time.time() | |
| try: | |
| path = c._generate_resume_v4(job, cfg=None, filepath=filepath, | |
| provider=provider, base_resume_override=base_resume) | |
| except Exception as e: | |
| row["provider_errors"] = 1 | |
| row["status"] = f"ERROR:{str(e)[:40]}" | |
| row["runtime_s"] = round(time.time() - t0, 1) | |
| return row | |
| row["runtime_s"] = round(time.time() - t0, 1) | |
| report = job.get("_v2_report", {}) or {} | |
| est = report.get("estimated_scores", {}) or {} | |
| row["internal"] = est.get("jd_match", 0) | |
| row["independent"] = report.get("independent_jd_match", 0) | |
| row["readability"] = est.get("ats_readability", 0) | |
| row["status"] = report.get("status", job.get("_v2_status", "")) | |
| row["repair_attempts"] = len(report.get("repair_attempts", []) or []) | |
| row["risk_terms"] = (len(report.get("review_terms_for_user_review", []) or []) | |
| + len(report.get("high_risk_terms_for_confirmation", []) or [])) | |
| row["download_allowed"] = bool(report.get("download_allowed")) | |
| pq = report.get("provider_response_quality", "ok") | |
| row["schema_errors"] = 0 if pq == "ok" else 1 | |
| return row | |
| def run_provider_matrix(providers: List, base_resume: Resume, jobs: List[dict], | |
| output_root: str, llm=None) -> List[dict]: | |
| rows: List[dict] = [] | |
| for provider in providers: | |
| out = os.path.join(output_root, getattr(provider, "name", "provider")) | |
| for job in jobs: | |
| rows.append(evaluate_provider_on_job(provider, base_resume, job, out, llm)) | |
| return rows | |
| def summarize(rows: List[dict]) -> List[dict]: | |
| """Per-provider summary table (spec #6).""" | |
| by_provider: dict = {} | |
| for r in rows: | |
| by_provider.setdefault(r["provider"], []).append(r) | |
| out: List[dict] = [] | |
| for prov, rs in by_provider.items(): | |
| n = len(rs) or 1 | |
| ready = sum(1 for r in rs if r.get("download_allowed")) | |
| out.append({ | |
| "provider": prov, | |
| "ready_rate": round(100 * ready / n), | |
| "avg_independent": round(statistics.mean(r["independent"] for r in rs), 1), | |
| "avg_internal": round(statistics.mean(r["internal"] for r in rs), 1), | |
| "avg_repairs": round(statistics.mean(r["repair_attempts"] for r in rs), 1), | |
| "schema_error_rate": round(100 * sum(r["schema_errors"] for r in rs) / n), | |
| "provider_error_rate": round(100 * sum(r["provider_errors"] for r in rs) / n), | |
| "risk_overuse": round(statistics.mean(r["risk_terms"] for r in rs), 1), | |
| "avg_runtime_s": round(statistics.mean(r["runtime_s"] for r in rs), 1), | |
| "recommendation": "preferred" if (ready / n) >= 0.8 else ( | |
| "usable" if (ready / n) >= 0.5 else "deprioritize"), | |
| }) | |
| out.sort(key=lambda x: (x["ready_rate"], x["avg_independent"]), reverse=True) | |
| return out | |
| # ββ consistency (spec #7) ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def consistency_runs(provider, base_resume: Resume, job: dict, | |
| output_dir: str, n: int = 3, llm=None) -> dict: | |
| """Run the same provider on the same JD n times; report variance + stability.""" | |
| scores: List[int] = [] | |
| for i in range(n): | |
| out = os.path.join(output_dir, f"run{i+1}") | |
| row = evaluate_provider_on_job(provider, base_resume, dict(job), out, llm) | |
| scores.append(row["independent"]) | |
| variance = round(statistics.pvariance(scores), 1) if len(scores) > 1 else 0.0 | |
| spread = max(scores) - min(scores) if scores else 0 | |
| return {"provider": getattr(provider, "name", "?"), | |
| "job": job.get("_jd_slug", job.get("company", "")), | |
| "scores": scores, "variance": variance, "spread": spread, | |
| "stable": spread <= 5} | |