Spaces:
Sleeping
Sleeping
| """ | |
| 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() | |