Spaces:
Sleeping
Sleeping
Download app.py from isHeSatoshi/RedrobJobRanker: direct link, hf CLI and curl.
- Browser
- Download file 12.3 kB
-
https://huggingface.co/spaces/isHeSatoshi/RedrobJobRanker/resolve/main/app.py
- Command line
-
hf download hf://spaces/isHeSatoshi/RedrobJobRanker/app.py
-
curl -L -o app.py https://huggingface.co/spaces/isHeSatoshi/RedrobJobRanker/resolve/main/app.py
12.3 kB
| """Streamlit sandbox demo for the Redrob Candidate Intelligence Engine. | |
| The sandbox runs the COMPLETE pipeline on a small input that the user | |
| uploads. It demonstrates: | |
| * The Role Blueprint and how the JD is parsed | |
| * Hybrid retrieval (BM25 + sentence-transformers + RRF) | |
| * Feature engineering | |
| * LightGBM LambdaRank (or deterministic fallback) ranking | |
| * Per-candidate reasoning | |
| * Export of the top-N as a CSV | |
| For the OFFICIAL reproduce_command see `scripts/run_ranking.py` in the | |
| repository. That command processes the full 100k candidate pool within | |
| the 5-minute / 16GB sandbox budget. | |
| This is the Stage-1 sandbox required by the hackathon spec | |
| (submission_spec.docx Section 10.5). Deploy to Streamlit Cloud or | |
| HuggingFace Spaces to get a working sandbox URL. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| PROJECT_ROOT = Path(__file__).resolve().parent.parent | |
| if str(PROJECT_ROOT) not in sys.path: | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| import pandas as pd | |
| import streamlit as st | |
| from redrob.blueprint import build_blueprint | |
| from redrob.rank.predict import rank_shortlist | |
| from redrob.reasoning.template import generate_reasoning_dataframe | |
| from redrob.submit.write import write_submission | |
| st.set_page_config(page_title="Redrob Candidate Intelligence", layout="wide") | |
| st.title("Redrob Candidate Intelligence Engine") | |
| st.caption("Senior AI Engineer ranking — sandbox demo (Stage 1 submission)") | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| def _load_uploaded_jsonl(uploaded_file) -> list: | |
| """Read an uploaded jsonl or json file as a list of dicts.""" | |
| raw = uploaded_file.getvalue().decode("utf-8", errors="ignore") | |
| rows = [] | |
| for line in raw.splitlines(): | |
| line = line.strip() | |
| if not line: | |
| continue | |
| try: | |
| rows.append(json.loads(line)) | |
| except json.JSONDecodeError: | |
| try: | |
| rows.append(json.loads(raw)) | |
| break | |
| except Exception: | |
| pass | |
| return rows | |
| def _rows_to_candidate_objects(rows: list) -> list: | |
| """Convert raw dicts to the dataclass shape used by the pipeline. | |
| The pipeline's `iter_canonical` parses one of two formats: | |
| - the bundled `sample_candidates.json` (list of records) | |
| - the streamed `candidates.jsonl` (one record per line, with the | |
| record itself containing a `profile` key). | |
| The bundled sample is already in the right shape. The streamed | |
| jsonl wraps the profile under `profile`. We support both. | |
| """ | |
| from redrob.data import CanonicalCandidate # type: ignore | |
| out = [] | |
| for r in rows: | |
| if "profile" in r and isinstance(r["profile"], dict): | |
| # Streamed jsonl format | |
| cand = CanonicalCandidate( | |
| candidate_id=r.get("candidate_id") or r.get("id", ""), | |
| name=(r.get("profile") or {}).get("name", ""), | |
| headline=(r.get("profile") or {}).get("headline", ""), | |
| summary=(r.get("profile") or {}).get("summary", ""), | |
| location=(r.get("profile") or {}).get("location", ""), | |
| country=(r.get("profile") or {}).get("country", ""), | |
| yoe=int((r.get("profile") or {}).get("years_experience", 0) or 0), | |
| current_title=(r.get("profile") or {}).get("current_title", ""), | |
| current_company=(r.get("profile") or {}).get("current_company", ""), | |
| current_company_size=(r.get("profile") or {}).get("current_company_size", ""), | |
| current_industry=(r.get("profile") or {}).get("current_industry", ""), | |
| career=(r.get("profile") or {}).get("career", []) or [], | |
| edu=(r.get("profile") or {}).get("education", []) or [], | |
| skills=(r.get("profile") or {}).get("skills", []) or [], | |
| certs=(r.get("profile") or {}).get("certifications", []) or [], | |
| languages=(r.get("profile") or {}).get("languages", []) or [], | |
| signals=r.get("signals") or {}, | |
| ) | |
| else: | |
| # Bundled sample format | |
| cand = CanonicalCandidate( | |
| candidate_id=r.get("candidate_id", ""), | |
| name=r.get("name", ""), | |
| headline=r.get("headline", ""), | |
| summary=r.get("summary", ""), | |
| location=r.get("location", ""), | |
| country=r.get("country", ""), | |
| yoe=int(r.get("yoe", 0) or 0), | |
| current_title=r.get("current_title", ""), | |
| current_company=r.get("current_company", ""), | |
| current_company_size=r.get("current_company_size", ""), | |
| current_industry=r.get("current_industry", ""), | |
| career=r.get("career", []) or [], | |
| edu=r.get("edu", []) or [], | |
| skills=r.get("skills", []) or [], | |
| certs=r.get("certs", []) or [], | |
| languages=r.get("languages", []) or [], | |
| signals=r.get("signals", {}) or {}, | |
| ) | |
| out.append(cand) | |
| return out | |
| def _candidates_to_dataframe(cands) -> pd.DataFrame: | |
| rows = [] | |
| for cc in cands: | |
| rows.append({ | |
| "candidate_id": cc.candidate_id, | |
| "name": cc.name, | |
| "headline": cc.headline, | |
| "summary": cc.summary, | |
| "location": cc.location, | |
| "country": cc.country, | |
| "yoe": cc.yoe, | |
| "current_title": cc.current_title, | |
| "current_company": cc.current_company, | |
| "current_company_size": cc.current_company_size, | |
| "current_industry": cc.current_industry, | |
| "career": cc.career, | |
| "edu": cc.edu, | |
| "skills": cc.skills, | |
| "certs": cc.certs, | |
| "languages": cc.languages, | |
| "signals": cc.signals, | |
| "text_corpus": cc.text_corpus, | |
| "career_total_months": cc.career_total_months, | |
| "career_overlap_months": cc.career_overlap_months, | |
| "career_industries": cc.career_industries, | |
| "career_titles": cc.career_titles, | |
| "career_companies": cc.career_companies, | |
| "skill_names": cc.skill_names, | |
| "skill_durations_max": cc.skill_durations_max, | |
| "has_ai_skill": cc.has_ai_skill, | |
| }) | |
| return pd.DataFrame(rows) | |
| # --------------------------------------------------------------------------- | |
| # Sidebar: inputs | |
| # --------------------------------------------------------------------------- | |
| st.sidebar.header("Inputs") | |
| uploaded = st.sidebar.file_uploader( | |
| "Upload candidates.jsonl or sample_candidates.json (max ~5,000 records)", | |
| type=["jsonl", "json"], | |
| help="Get the file from the hackathon bundle. The sandbox runs the " | |
| "complete pipeline on a small subset for demonstration.", | |
| ) | |
| sample_size = st.sidebar.slider("Sample size for sandbox run", 50, 2000, 200, step=50) | |
| use_dense = st.sidebar.checkbox( | |
| "Use sentence-transformers (slower, ~1 min extra)", | |
| value=False, | |
| ) | |
| run_btn = st.sidebar.button("Run ranking pipeline", type="primary") | |
| st.sidebar.markdown("---") | |
| st.sidebar.caption( | |
| "For the full 100k run, use `python scripts/run_ranking.py --no_dense --no_lgbm` " | |
| "from the repo root (see README)." | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # Blueprint panel | |
| # --------------------------------------------------------------------------- | |
| bp = build_blueprint() | |
| with st.expander("Role Blueprint (Senior AI Engineer, Redrob)", expanded=True): | |
| c1, c2, c3 = st.columns(3) | |
| with c1: | |
| st.markdown("**Core competencies**") | |
| st.write(", ".join(bp["core_competencies"][:20]) + "…") | |
| st.markdown(f"*{len(bp['core_competencies'])} total*") | |
| with c2: | |
| st.markdown("**Target titles**") | |
| st.write(", ".join(bp["target_titles"][:10]) + "…") | |
| st.markdown("**Experience band (yrs)**") | |
| st.write( | |
| f"min {bp['experience_band']['min']} · ideal " | |
| f"{bp['experience_band']['ideal_low']}–{bp['experience_band']['ideal_high']}" | |
| ) | |
| with c3: | |
| st.markdown("**Notice period**") | |
| st.write(f"preferred ≤ {bp['notice_period_ok_days']} days") | |
| st.markdown("**Locations**") | |
| st.write(", ".join(bp["preferred_locations"])) | |
| # --------------------------------------------------------------------------- | |
| # Pipeline run | |
| # --------------------------------------------------------------------------- | |
| if run_btn: | |
| if uploaded is None: | |
| st.error( | |
| "Please upload a candidates file. The sandbox needs your " | |
| "data — there's no bundled sample in the public repo." | |
| ) | |
| st.stop() | |
| t0 = time.time() | |
| raw_rows = _load_uploaded_jsonl(uploaded) | |
| if not raw_rows: | |
| st.error("Could not parse the uploaded file as JSON/JSONL.") | |
| st.stop() | |
| # Truncate to sample size | |
| raw_rows = raw_rows[:sample_size] | |
| cands = _rows_to_candidate_objects(raw_rows) | |
| df = _candidates_to_dataframe(cands) | |
| st.success(f"Loaded {len(df):,} candidates in {time.time()-t0:.1f}s") | |
| if not use_dense: | |
| # Disable dense retrieval for the sandbox (faster) | |
| from redrob import retrieval | |
| retrieval.dense.dense_search = lambda *a, **kw: {} | |
| t0 = time.time() | |
| # Provide placeholder retrieval scores | |
| df["sparse_score"] = 0.0 | |
| df["dense_score"] = 0.0 | |
| df["rrf_score"] = 0.0 | |
| df["structured_hit"] = 0.0 | |
| ranked = rank_shortlist(df, use_lgbm=False) | |
| ranked = ranked.head(min(100, len(ranked))) | |
| elapsed = time.time() - t0 | |
| st.success(f"Ranked in {elapsed:.1f}s — top-{len(ranked)} ready") | |
| # Reasoning | |
| reasonings = generate_reasoning_dataframe(ranked) | |
| ranked = ranked.copy() | |
| ranked["reasoning"] = reasonings | |
| # Display | |
| st.subheader("Top 25 (preview)") | |
| display_cols = [ | |
| "candidate_id", "current_title", "yoe", | |
| "title_fit_blend", "recruitability", "honeypot_penalty", | |
| "final_score", "reasoning", | |
| ] | |
| display_cols = [c for c in display_cols if c in ranked.columns] | |
| st.dataframe(ranked.head(25)[display_cols], use_container_width=True, hide_index=True) | |
| st.subheader(f"Top-{len(ranked)} full table") | |
| full_cols = ["candidate_id", "current_title", "yoe", "title_fit_blend", | |
| "recruitability", "honeypot_penalty", "final_score", "reasoning"] | |
| full_cols = [c for c in full_cols if c in ranked.columns] | |
| st.dataframe(ranked[full_cols], use_container_width=True, hide_index=True) | |
| # Charts | |
| c1, c2 = st.columns(2) | |
| with c1: | |
| if "current_title" in ranked.columns: | |
| st.markdown("**Top-100 title distribution**") | |
| st.bar_chart(ranked["current_title"].value_counts().head(10)) | |
| with c2: | |
| if "title_fit_blend" in ranked.columns: | |
| st.markdown("**Top-100 title fit histogram**") | |
| st.bar_chart(pd.cut(ranked["title_fit_blend"], bins=10).value_counts().sort_index()) | |
| # Export | |
| out_csv = PROJECT_ROOT / "submission_sandbox.csv" | |
| write_submission(ranked, out_path=out_csv, validate=False) | |
| with open(out_csv, "rb") as f: | |
| st.download_button( | |
| "Download submission CSV", | |
| data=f.read(), | |
| file_name="submission.csv", | |
| mime="text/csv", | |
| ) | |
| st.info( | |
| "The sandbox ran the COMPLETE pipeline (BM25 retrieval, 5-axis " | |
| "PageRank, 74 features, deterministic composite, reasoning) " | |
| "on your uploaded sample. For the full 100k pool, run " | |
| "`python scripts/run_ranking.py --no_dense --no_lgbm` from " | |
| "the repo root (see README)." | |
| ) | |
| else: | |
| st.info("Upload a candidates file and press 'Run ranking pipeline' in the sidebar to begin.") | |