CoolFace
Apppublic

monish563/NU-KIOSK-API

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
utils.py140 linesDownload Raw Back to data
1"""Helper utilities for normalising and searching catalog data."""2 3from __future__ import annotations4 5import re6from typing import Iterable, List7 8 9def canonicalize_name(raw: str | None) -> str:10    """Lowercase and strip punctuation/spaces for stable matching."""11    if not raw:12        return ""13    lowered = raw.strip().lower()14    cleaned = "".join(ch for ch in lowered if ch.isalnum() or ch.isspace())15    # Collapse duplicate spaces16    return " ".join(part for part in cleaned.split() if part)17 18 19def tokenize_name(raw: str | None) -> set[str]:20    """Break names into normalized token sets for fuzzy comparisons."""21    if not raw:22        return set()23    lowered = raw.lower()24    return set(re.findall(r"[a-z0-9]+", lowered))25 26 27def generate_name_variants(raw: str | None) -> Iterable[str]:28    """Yield common name permutations used across CSV sources."""29    if not raw:30        return []31    cleaned = raw.strip()32    yield cleaned33    if "," in cleaned:34        last, _, first = cleaned.partition(",")35        first = first.strip()36        last = last.strip()37        if first and last:38            yield f"{first} {last}"39            yield f"{last} {first}"40            yield f"{last}, {first}"41            yield f"{last},{first}"42    else:43        parts = cleaned.split()44        if len(parts) >= 2:45            first = " ".join(parts[:-1])46            last = parts[-1]47            yield f"{last} {first}"48            yield f"{last}, {first}"49            yield f"{last},{first}"50 51 52def extract_leadership_names(raw: str | None) -> List[str]:53    """54    Parse leadership strings from centers.csv and extract individual names.55 56    Examples:57    - "Director: Kristian Hammond"58    - "Co-directors: Michael Horn, Chris Riesbeck, Uri Wilensky"59    - "Director: Diego Klabjan; Associate Director: Lauren Smith"60    """61    if not raw:62        return []63 64    text = raw.replace("\xa0", " ").strip()65    # Discard role labels (e.g., "Director:", "Co-directors:")66    if ":" in text:67        _, _, text = text.partition(":")68    # Normalize coordinators69    text = text.replace(" and ", ",")70    # Remove role labels repeated later in the string71    text = re.sub(r"\b[A-Za-z ]*Director[s]?\b", "", text, flags=re.IGNORECASE)72    text = re.sub(r"\bCo-PI\b", "", text, flags=re.IGNORECASE)73    text = re.sub(r"\bAssociate\b", "", text, flags=re.IGNORECASE)74 75    # Remove parentheses content (e.g., titles)76    text = re.sub(r"\([^)]*\)", "", text)77 78    names = []79    for chunk in re.split(r"[,/;]+", text):80        cleaned = chunk.strip()81        if not cleaned:82            continue83        # Strip lingering prefixes like "Co-" or trailing descriptors84        cleaned = re.sub(r"^(co-)?director(s)?\b", "", cleaned, flags=re.IGNORECASE).strip()85        # Collapse internal multiple spaces86        cleaned = " ".join(cleaned.split())87        if cleaned:88            names.append(cleaned)89    return names90 91 92def centers_for_faculty(source_row: dict, centers: List[dict]) -> List[dict]:93    """Find centers led by the faculty member described in ``source_row``."""94    name = source_row.get("Name")95    if not name:96        return []97    lookup = canonicalize_name(name)98    matches: List[dict] = []99    for center in centers:100        leaders = extract_leadership_names(center.get("Leadership"))101        if not leaders:102            continue103        for leader in leaders:104            if canonicalize_name(leader) == lookup:105                matches.append(center)106                break107    return matches108 109 110def extract_advisor_names(raw: str | None) -> List[str]:111    """Normalize advisor lists from students.csv."""112    if not raw:113        return []114    text = raw.strip()115    if not text or text.lower() in {"none", "na", "n/a"}:116        return []117 118    names: List[str] = []119 120    # First capture "Last, First" patterns so we can rebuild full names.121    comma_matches = re.findall(r"([A-Za-z.'\- ]+),\s*([A-Za-z.'\- ]+)", text)122    for last, first in comma_matches:123        first = first.strip()124        last = last.strip()125        if first and last:126            names.append(f"{first} {last}")127    # Remove the matched patterns to avoid double counting when splitting later.128    text = re.sub(r"([A-Za-z.'\- ]+),\s*([A-Za-z.'\- ]+)", "", text)129 130    text = text.replace(" and ", ",")131    segments = re.split(r"[,/;]+", text)132    for segment in segments:133        cleaned = segment.strip().strip('"').strip()134        if not cleaned:135            continue136        cleaned = re.sub(r"\(.*?\)$", "", cleaned).strip()137        if cleaned:138            names.append(cleaned)139    return [name for name in names if name]140