"""Helper functions for faculty record lookups used by multiple blueprints.""" from __future__ import annotations import difflib from typing import Any, Dict, List, Optional, Tuple from ..data.utils import canonicalize_name, generate_name_variants from .base import AnalysisContext def _lookup_record(context: AnalysisContext, target_name: str) -> Tuple[Any, List[str], Optional[Dict[str, Any]]]: """Return the faculty record matching ``target_name`` and any lookup notes.""" notes: List[str] = [] dataset = context.catalog.get("faculty") record = dataset.get_by_key(target_name) if record is None: target_canonical = canonicalize_name(target_name) for row in dataset.records: if canonicalize_name(row.get("Name", "")) == target_canonical: record = row break if record is None: candidates = [row.get("Name", "") for row in dataset.records if row.get("Name")] closest = difflib.get_close_matches(target_name, candidates, n=1, cutoff=0.6) if closest: record = dataset.get_by_key(closest[0]) notes.append(f"Showing results for '{closest[0]}' (closest match).") # Fallback: some names appear only in office-assignment CSVs (e.g., "Bain,Connor"). # Try to match those assignee names back into the faculty roster using # common name permutations. office_row: Optional[Dict[str, Any]] = None if record is None: offices = context.catalog.try_get("faculty_offices") if offices: for row in offices.records: assignee = row.get("Assignee Name") or row.get("Assignee") or row.get("Name") if not assignee: continue # If the assignee directly matches the target name (or its variants), # prefer returning the matched faculty roster row when available; if # no roster row matches, keep the office row as a fallback to emit # office/location facts. matched = False for variant in generate_name_variants(assignee): # If the variant matches the requested target, consider it a hit. if canonicalize_name(variant) == canonicalize_name(target_name): candidate = dataset.get_by_key(variant) if candidate is not None: record = candidate notes.append(f"Matched '{assignee}' from faculty offices to roster entry '{candidate.get('Name')}'.") matched = True break # remember office row as fallback if no roster entry exists office_row = row matched = True break if matched: break return record, notes, office_row