Spaces:
Sleeping
Sleeping
Download redrob/graph/propagate.py from isHeSatoshi/RedrobJobRanker: direct link, hf CLI and curl.
- Browser
- Download file 4.16 kB
-
https://huggingface.co/spaces/isHeSatoshi/RedrobJobRanker/resolve/main/redrob/graph/propagate.py
- Command line
-
hf download hf://spaces/isHeSatoshi/RedrobJobRanker/redrob/graph/propagate.py
-
curl -L -o propagate.py https://huggingface.co/spaces/isHeSatoshi/RedrobJobRanker/resolve/main/redrob/graph/propagate.py
4.16 kB
| """Personalised PageRank over the heterogeneous graph. | |
| Supports both the original single-axis PPR and a multi-axis PPR | |
| (5 JD axes: applied_ml, retrieval_rank, nlp_llm, production_eng, product_company). | |
| Multi-axis results are cached on disk at artifacts/ppr_axes.pkl. | |
| """ | |
| from __future__ import annotations | |
| import pickle | |
| from pathlib import Path | |
| from typing import Dict, Iterable, List | |
| import networkx as nx | |
| from .. import config | |
| from .build import build_graph | |
| def _seed_nodes(jd_terms: Iterable[str], prefix: str) -> List[str]: | |
| """Map JD terms to graph node labels with the given prefix.""" | |
| out: List[str] = [] | |
| for term in jd_terms: | |
| key = f"{prefix}::{term.strip()}" | |
| out.append(key) | |
| return out | |
| def personalised_pagerank( | |
| jd_terms: Iterable[str] | None = None, | |
| alpha: float = 0.85, | |
| max_iter: int = 60, | |
| ) -> Dict[str, float]: | |
| """Return a dict of {candidate_node: ppr_score}.""" | |
| G = build_graph() | |
| seeds = _seed_nodes(jd_terms or config.CORE_COMPETENCIES, "skill") | |
| seeds = [s for s in seeds if s in G] | |
| if not seeds: | |
| return {} | |
| personalization = {n: 0.0 for n in G.nodes()} | |
| for s in seeds: | |
| personalization[s] = 1.0 / len(seeds) | |
| pr = nx.pagerank( | |
| G, | |
| alpha=alpha, | |
| personalization=personalization, | |
| max_iter=max_iter, | |
| tol=1e-6, | |
| weight="weight", | |
| ) | |
| return {n: float(v) for n, v in pr.items() | |
| if isinstance(n, str) and n.startswith("cand::")} | |
| # Disk cache path for multi-axis PPR | |
| _PPR_AXES_CACHE = config.ARTIFACTS_DIR / "ppr_axes.pkl" | |
| def ppr_axes( | |
| axes: Dict[str, List[str]] | None = None, | |
| alpha: float = 0.85, | |
| max_iter: int = 60, | |
| ) -> Dict[str, Dict[str, float]]: | |
| """Multi-axis PPR: one PPR per JD axis. | |
| Returns Dict[axis_name, Dict[cand_node, ppr_score]]. | |
| Results are cached at artifacts/ppr_axes.pkl. | |
| Axes: | |
| applied_ml -> seeds from config.JD_AXES["applied_ml"] on skill:: nodes | |
| retrieval_rank -> seeds from config.JD_AXES["retrieval_rank"] on skill:: nodes | |
| nlp_llm -> seeds from config.JD_AXES["nlp_llm"] on skill:: nodes | |
| production_eng -> seeds from config.JD_AXES["production_eng"] on skill:: nodes | |
| product_company -> seeds from config.JD_AXES["product_company"] on company:: nodes | |
| """ | |
| if axes is None: | |
| axes = config.JD_AXES | |
| # Load cache if present | |
| if _PPR_AXES_CACHE.exists(): | |
| with open(_PPR_AXES_CACHE, "rb") as f: | |
| cached: Dict[str, Dict[str, float]] = pickle.load(f) | |
| # Validate all axes are present | |
| if all(ax in cached for ax in axes): | |
| return cached | |
| G = build_graph() | |
| result: Dict[str, Dict[str, float]] = {} | |
| for axis_name, terms in axes.items(): | |
| # product_company axis uses company:: nodes; all others use skill:: | |
| prefix = "company" if axis_name == "product_company" else "skill" | |
| seeds = _seed_nodes(terms, prefix) | |
| seeds = [s for s in seeds if s in G] | |
| if not seeds: | |
| result[axis_name] = {} | |
| print(f" ppr_axes[{axis_name}]: 0 seeds found in graph") | |
| continue | |
| personalization = {n: 0.0 for n in G.nodes()} | |
| for s in seeds: | |
| personalization[s] = 1.0 / len(seeds) | |
| pr = nx.pagerank( | |
| G, | |
| alpha=alpha, | |
| personalization=personalization, | |
| max_iter=max_iter, | |
| tol=1e-6, | |
| weight="weight", | |
| ) | |
| result[axis_name] = { | |
| n: float(v) | |
| for n, v in pr.items() | |
| if isinstance(n, str) and n.startswith("cand::") | |
| } | |
| top_score = max(result[axis_name].values()) if result[axis_name] else 0.0 | |
| print(f" ppr_axes[{axis_name}]: {len(seeds)} seeds, top_score={top_score:.6f}") | |
| # Persist to disk | |
| with open(_PPR_AXES_CACHE, "wb") as f: | |
| pickle.dump(result, f, protocol=pickle.HIGHEST_PROTOCOL) | |
| return result | |
| if __name__ == "__main__": | |
| axes = ppr_axes() | |
| for name, scores in axes.items(): | |
| top = sorted(scores.items(), key=lambda x: -x[1])[:3] | |
| print(f"{name}: {top}") | |