""" loader.py — Load and parse candidate profiles from candidates.jsonl.gz Handles both: - candidates.jsonl.gz (compressed, ~52 MB) - candidates.jsonl (uncompressed, ~465 MB) Returns a list of dicts with the full candidate schema. """ import gzip import json import os from typing import List, Dict, Any from tqdm import tqdm def load_candidates(path: str, limit: int = None) -> List[Dict[str, Any]]: """ Load all candidates from a .jsonl or .jsonl.gz file. Args: path: Path to candidates.jsonl or candidates.jsonl.gz limit: If set, only load the first N candidates (useful for testing) Returns: List of candidate dicts matching the Redrob candidate schema. """ if not os.path.exists(path): raise FileNotFoundError( f"Cannot find candidates file at: {path}\n" f"Make sure candidates.jsonl.gz is in your project root." ) candidates = [] open_fn = gzip.open if path.endswith(".gz") else open mode = "rt" if path.endswith(".gz") else "r" print(f"Loading candidates from: {path}") with open_fn(path, mode, encoding="utf-8") as f: for i, line in enumerate(tqdm(f, desc="Loading", unit=" candidates")): line = line.strip() if not line: continue try: candidate = json.loads(line) candidates.append(candidate) except json.JSONDecodeError as e: print(f"Warning: Skipping malformed line {i+1}: {e}") continue if limit and len(candidates) >= limit: break print(f"Loaded {len(candidates):,} candidates.") return candidates def load_sample(sample_path: str) -> List[Dict[str, Any]]: """ Load sample_candidates.json (pretty-printed JSON array, not JSONL). Useful for rapid development and testing without the full 465 MB file. """ with open(sample_path, "r", encoding="utf-8") as f: candidates = json.load(f) print(f"Loaded {len(candidates)} sample candidates.") return candidates def get_candidate_ids(candidates: List[Dict[str, Any]]) -> List[str]: """Extract ordered list of candidate_ids.""" return [c["candidate_id"] for c in candidates] def build_id_index(candidates: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: """ Build a dict of candidate_id -> candidate for O(1) lookup. Used during scoring to fetch full profiles by ID. """ return {c["candidate_id"]: c for c in candidates}