Spaces:
Sleeping
Sleeping
| """ | |
| Run history — saves and loads completed pipeline runs as JSON. | |
| Each run is stored at data/output/run_history/run_YYYY-MM-DD_HH-MM-SS.json. | |
| Keeps the full job list so results can be restored in the UI without rerunning. | |
| """ | |
| import os | |
| import json | |
| import logging | |
| from pathlib import Path | |
| from datetime import datetime | |
| log = logging.getLogger("run_history") | |
| HISTORY_DIR = Path("data/output/run_history") | |
| def save_run(jobs: list, run_meta: dict) -> str: | |
| """ | |
| Save a completed run to history. | |
| run_meta should include: run_id, excel_path, platforms, roles. | |
| Returns the history file path, or '' on failure. | |
| """ | |
| HISTORY_DIR.mkdir(parents=True, exist_ok=True) | |
| run_id = run_meta.get("run_id") or datetime.now().strftime("%Y-%m-%d_%H-%M-%S") | |
| high = sum(1 for j in jobs if j.get("relevance_score", 0) >= 8) | |
| med = sum(1 for j in jobs if 6 <= j.get("relevance_score", 0) <= 7) | |
| resumes = sum(1 for j in jobs if j.get("resume_path")) | |
| pdfs = sum(1 for j in jobs if j.get("resume_pdf_path")) | |
| ats_before = [j["ats_score_before"] for j in jobs | |
| if j.get("ats_score_before") not in (None, "", 0)] | |
| ats_after = [j["ats_score_after"] for j in jobs | |
| if j.get("ats_score_after") not in (None, "", 0)] | |
| record = { | |
| "run_id": run_id, | |
| "date": datetime.now().strftime("%Y-%m-%d %H:%M"), | |
| "total_jobs": len(jobs), | |
| "high_priority": high, | |
| "med_priority": med, | |
| "resumes": resumes, | |
| "pdfs": pdfs, | |
| "avg_ats_before": int(sum(ats_before) / len(ats_before)) if ats_before else 0, | |
| "avg_ats_after": int(sum(ats_after) / len(ats_after)) if ats_after else 0, | |
| "platforms": list(set(j.get("platform", "") for j in jobs if j.get("platform"))), | |
| "roles": run_meta.get("roles", []), | |
| "excel_path": run_meta.get("excel_path", ""), | |
| "jobs": jobs, | |
| } | |
| path = HISTORY_DIR / f"run_{run_id}.json" | |
| try: | |
| with open(path, "w", encoding="utf-8") as f: | |
| json.dump(record, f, ensure_ascii=False, default=str) | |
| log.info(f"Run saved to history: {path}") | |
| # Best-effort: mirror to a private HF Dataset so history + resumes | |
| # survive HF Spaces restarts (no-ops when no HF token is configured). | |
| try: | |
| from .hf_storage import push_run, is_enabled | |
| if is_enabled(): | |
| resume_files = [ | |
| j[k] for j in jobs for k in ("resume_path", "resume_pdf_path") | |
| if j.get(k) | |
| ] | |
| # Also persist the generated report (Excel + its CSV sibling). | |
| xp = run_meta.get("excel_path", "") | |
| if xp: | |
| resume_files.append(xp) | |
| resume_files.append(os.path.splitext(xp)[0] + ".csv") | |
| push_run(str(path), resume_files) | |
| except Exception as _e: | |
| log.warning(f"HF dataset push skipped: {_e}") | |
| return str(path) | |
| except Exception as e: | |
| log.error(f"Failed to save run history: {e}") | |
| return "" | |
| def list_runs() -> list: | |
| """ | |
| List all saved runs, newest first. | |
| Returns list of summary dicts (no jobs key — kept light for UI listing). | |
| """ | |
| if not HISTORY_DIR.exists(): | |
| return [] | |
| runs = [] | |
| for p in sorted(HISTORY_DIR.glob("run_*.json"), reverse=True): | |
| try: | |
| with open(p, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| summary = {k: v for k, v in data.items() if k != "jobs"} | |
| summary["_path"] = str(p) | |
| runs.append(summary) | |
| except Exception: | |
| pass | |
| return runs | |
| def load_run(path: str) -> dict: | |
| """Load a full run (including jobs list) from a history JSON file.""" | |
| try: | |
| with open(path, "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except Exception as e: | |
| log.error(f"Failed to load run {path}: {e}") | |
| return {} | |