"""Blueprint that searches faculty, staff, and students for a person and merges results.""" from __future__ import annotations from typing import Any, Dict, List, Optional from .base import AnalysisContext, Blueprint, BlueprintResult, Fact from .faculty_profile import _lookup_record as _faculty_lookup from ..data.utils import canonicalize_name class PersonLookupBlueprint(Blueprint): """Search faculty, staff, and students for a given person name.""" name = "person_lookup" def run(self, context: AnalysisContext, **kwargs: Any) -> BlueprintResult: target = (kwargs.get("name") or "").strip() if not target: return BlueprintResult(self.name, kwargs, facts=[], notes=["Tell me which person you'd like to look up."]) facts: List[Fact] = [] notes: List[str] = [] detected_roles: List[str] = [] # 1) Faculty lookup (reuse existing internal helper) try: faculty_record, faculty_notes, faculty_office = _faculty_lookup(context, target) except Exception: faculty_record = None faculty_notes = ["Faculty lookup failed."] faculty_office = None if faculty_record: src = context.catalog.try_get("faculty").origin if context.catalog.try_get("faculty") else None if faculty_record.get("Title"): facts.append(Fact(subject=faculty_record.get("Name"), predicate="title", value=faculty_record.get("Title"), source=src, confidence=1.0)) if faculty_record.get("Category"): facts.append(Fact(subject=faculty_record.get("Name"), predicate="category", value=faculty_record.get("Category"), source=src, confidence=0.9)) if faculty_record.get("Research Interests"): facts.append(Fact(subject=faculty_record.get("Name"), predicate="research_focus", value=faculty_record.get("Research Interests"), source=src, confidence=0.9)) notes.extend(faculty_notes) detected_roles.append("faculty") # 2) Staff lookup staff_entity = context.catalog.try_get("staff") staff_row: Optional[Dict[str, Any]] = None if staff_entity: # exact key lookup if available if staff_entity.key_field: staff_row = staff_entity.get_by_key(target) if not staff_row: target_canon = canonicalize_name(target) for r in staff_entity.records: if canonicalize_name(r.get("Name", "")) == target_canon: staff_row = r break if staff_row: src = staff_entity.origin facts.append(Fact(subject=staff_row.get("Name"), predicate="title", value=staff_row.get("Title"), source=src, confidence=1.0)) facts.append(Fact(subject=staff_row.get("Name"), predicate="role", value=staff_row.get("Role"), source=src, confidence=0.9)) if staff_row.get("Email"): facts.append(Fact(subject=staff_row.get("Name"), predicate="email", value=staff_row.get("Email"), source=src, confidence=0.95)) if staff_row.get("Phone"): facts.append(Fact(subject=staff_row.get("Name"), predicate="phone", value=staff_row.get("Phone"), source=src, confidence=0.8)) detected_roles.append("staff") # 3) Student lookup student_entity = context.catalog.try_get("students") student_row: Optional[Dict[str, Any]] = None if student_entity: if student_entity.key_field: student_row = student_entity.get_by_key(target) if not student_row: target_canon = canonicalize_name(target) for r in student_entity.records: if canonicalize_name(r.get("Name", "")) == target_canon: student_row = r break if student_row: src = student_entity.origin profile_val: Dict[str, Any] = {} for k, v in student_row.items(): if k and k.lower() != "name" and v: profile_val[k.lower().replace(" ", "_")] = v facts.append(Fact(subject=student_row.get("Name"), predicate="student_profile", value=profile_val, source=src, confidence=0.85)) detected_roles.append("student") # Normalize detected roles and emit a role metadata fact (single or multiple) if detected_roles: # prefer faculty > staff > student ordering if multiple priority = ["faculty", "staff", "student"] for p in priority: if p in detected_roles: primary = p break else: primary = detected_roles[0] facts.insert(0, Fact(subject=target, predicate="person_type", value={"primary": primary, "all": detected_roles}, source=None, confidence=1.0)) if not facts: notes.append(f"No matching faculty, staff, or student found for '{target}'.") return BlueprintResult(self.name, kwargs, facts=facts, notes=notes)