""" 20-job live validation workflow (spec #10). Selects N jobs, generates resumes through the production provider FALLBACK CHAIN, exports files, re-parses them, scores internal + independent, prints a batch table, and exports a validation package per job. Usage: PYTHONPATH=. python scripts/run_20_job_validation.py # 20 JD fixtures (offline) PYTHONPATH=. python scripts/run_20_job_validation.py --n 20 --live # scrape real jobs PYTHONPATH=. python scripts/run_20_job_validation.py --no-packages """ import os import sys import io import json import argparse sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) if hasattr(sys.stdout, "buffer"): sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") from src.validation_runner import run_validation_batch, select_jobs from src.provider_eval import load_base_resume from src.llm_client import LLMClient def main(): ap = argparse.ArgumentParser() ap.add_argument("--n", type=int, default=20) ap.add_argument("--live", action="store_true", help="scrape real jobs (needs network)") ap.add_argument("--no-packages", action="store_true") args = ap.parse_args() base = load_base_resume() if base is None: print("ERROR: no base resume (data/resume/_parsed.json or resume.pdf).") sys.exit(1) llm = LLMClient.__new__(LLMClient) jobs = select_jobs(n=args.n, live=args.live, llm=llm) if not jobs: print("ERROR: could not select any jobs.") sys.exit(1) print(f"Selected {len(jobs)} jobs ({'live' if args.live else 'fixtures'}).") def _cb(done, total, row): print(f" [{done}/{total}] {row['company'][:24]:<24} " f"{row['provider_used'][:12]:<12} {row['status'][:22]:<22} " f"int={row['internal_score']} ind={row['independent_score']} " f"dl={'Y' if row['download_allowed'] else 'n'}") res = run_validation_batch(jobs, base_resume=base, llm=llm, build_packages=not args.no_packages, progress_cb=_cb) if res.get("error"): print("ERROR:", res["error"]) sys.exit(1) rows, summary = res["rows"], res["summary"] print("\n=== BATCH TABLE ===") print(f"{'Job Title':<28} {'Company':<18} {'Platform':<10} {'Provider':<12} " f"{'Status':<22} {'Int':>4} {'Ind':>4} {'Read':>4} {'Risk':<6} {'Rev':>3} " f"{'Rep':>3} {'DL':>3}") for r in rows: print(f"{r['job_title'][:28]:<28} {r['company'][:18]:<18} {r['platform'][:10]:<10} " f"{r['provider_used'][:12]:<12} {r['status'][:22]:<22} " f"{r['internal_score']:>4} {r['independent_score']:>4} {r['ats_readability']:>4} " f"{r['risk_level']:<6} {r['review_terms']:>3} {r['repair_attempts']:>3} " f"{'Y' if r['download_allowed'] else 'n':>3}") print(f"\nChain: {summary['chain']}") print(f"Ready/downloadable: {summary['ready']}/{summary['total']} " f"({summary['ready_rate']}%) | needs_user_input: {summary['needs_user_input']} " f"| blocked: {summary['blocked']}") print(f"Output: {summary['output_dir']}") if res.get("packages"): print(f"Validation packages: {len(res['packages'])} -> data/output/validation/") out_json = os.path.join(summary["output_dir"], "batch_results.json") with open(out_json, "w", encoding="utf-8") as f: json.dump(res, f, ensure_ascii=False, indent=2, default=str) print(f"Saved -> {out_json}") if "stub" in summary["chain"] and "->" not in summary["chain"]: print("\n*** NOTE: ran with StubProvider only (no provider keys). This validates") print("*** the workflow, NOT production quality. Re-run with Kimi/NVIDIA keys. ***") if __name__ == "__main__": main()