NU-KIOSK-API / backend /tools /location.py
Monish BV
Add kiosk-api: stripped backend for speech integration
c2b7a7b
"""Blueprint that returns location information for faculty, staff, or students."""
from __future__ import annotations
from typing import Any, Dict, List, Optional
from .base import AnalysisContext, Blueprint, BlueprintResult, Fact
from .faculty_profile import _lookup_record
from ..data.utils import canonicalize_name
class LocationBlueprint(Blueprint):
"""Return where a person (faculty, staff, or student) can be found on campus.
Automatically detects whether the name matches a faculty member, staff member,
or student and returns the appropriate location information.
"""
name = "location"
def run(self, context: AnalysisContext, **kwargs: Any) -> BlueprintResult:
target_name = (kwargs.get("name") or kwargs.get("person") or "").strip()
if not target_name:
return BlueprintResult(self.name, kwargs, facts=[], notes=["No name provided."])
facts: List[Fact] = []
notes: List[str] = []
found_faculty = False
found_staff = False
found_student = False
# Try faculty lookup first
faculty_facts, faculty_notes, found_faculty = self._lookup_faculty_location(context, target_name)
facts.extend(faculty_facts)
notes.extend(faculty_notes)
# Try staff lookup
staff_facts, staff_notes, found_staff = self._lookup_staff_location(context, target_name)
facts.extend(staff_facts)
notes.extend(staff_notes)
# Try student lookup
student_facts, student_notes, found_student = self._lookup_student_seating(context, target_name)
facts.extend(student_facts)
notes.extend(student_notes)
# If none found, report not found
if not found_faculty and not found_staff and not found_student:
return BlueprintResult(
self.name,
kwargs,
facts=[],
notes=[f"'{target_name}' not found in faculty, staff, or student records."],
)
return BlueprintResult(self.name, kwargs, facts=facts, notes=notes)
def _lookup_faculty_location(
self, context: AnalysisContext, target_name: str
) -> tuple[List[Fact], List[str], bool]:
"""Look up faculty office location. Returns (facts, notes, found)."""
record, lookup_notes, office_row = _lookup_record(context, target_name)
if record is None and office_row is None:
return [], [], False
facts: List[Fact] = []
notes = list(lookup_notes)
# Try to resolve office through relationship
office_matches = []
if record is not None:
office_matches = context.catalog.resolve_relationship("faculty_to_office", record)
if office_matches:
office_row = office_matches[0]
office_entity = context.catalog.try_get("faculty_offices")
office_source = office_entity.origin if office_entity else None
building = (office_row.get("Building") or "").strip()
room = (office_row.get("Room") or office_row.get("Room Location") or "").strip()
location = " ".join(bit for bit in (building, room) if bit).strip()
facts.append(
Fact(
subject=record["Name"],
predicate="office",
value=location or "Office location unavailable",
source=office_source,
)
)
elif office_row is not None:
# Matched office assignment but no roster record
office_entity = context.catalog.try_get("faculty_offices")
office_source = office_entity.origin if office_entity else None
building = (office_row.get("Building") or office_row.get("Location") or "").strip()
room = (office_row.get("Room") or office_row.get("Room Location") or "").strip()
location = " ".join(bit for bit in (building, room) if bit).strip()
facts.append(
Fact(
subject=office_row.get("Assignee Name") or target_name,
predicate="office",
value=location or "Office location unavailable",
source=office_source,
)
)
elif record is not None:
notes.append(f"No office assignment found for {record['Name']}.")
return facts, notes, (record is not None or office_row is not None)
def _lookup_staff_location(
self, context: AnalysisContext, target_name: str
) -> tuple[List[Fact], List[str], bool]:
"""Look up staff office location. Returns (facts, notes, found)."""
staff = context.catalog.try_get("staff")
if not staff:
return [], [], False
record = None
canonical_target = canonicalize_name(target_name)
for row in staff.records:
if canonicalize_name(row.get("Name", "")) == canonical_target:
record = row
break
if record is None:
return [], [], False
facts: List[Fact] = []
notes: List[str] = []
location = (record.get("Room Location") or "").strip()
if location and location.lower() not in {"not listed", "n/a", "none", ""}:
facts.append(
Fact(
subject=record.get("Name", target_name),
predicate="office",
value=location,
source=staff.origin,
confidence=0.9,
)
)
else:
notes.append(f"No office location listed for {record.get('Name', target_name)}.")
return facts, notes, True
def _lookup_student_seating(
self, context: AnalysisContext, target_name: str
) -> tuple[List[Fact], List[str], bool]:
"""Look up student seating location. Returns (facts, notes, found)."""
students = context.catalog.try_get("students")
if not students:
return [], [], False
record = None
canonical_target = canonicalize_name(target_name)
for row in students.records:
if canonicalize_name(row.get("Name")) == canonical_target:
record = row
break
if record is None:
return [], [], False
seating = context.catalog.resolve_relationship("student_to_mudd_seat", record)
seating_entity = context.catalog.try_get("mudd_seating")
seating_origin = seating_entity.origin if seating_entity else None
facts: List[Fact] = []
for seat in seating:
facts.append(
Fact(
subject=record.get("Name", target_name),
predicate="seating",
value={
"room": seat.get("Room Number"),
"desk": seat.get("Desk Number"),
"track": seat.get("Track"),
"advisor": seat.get("Advocate/Advisor"),
"email": seat.get("Email address"),
},
source=seating_origin,
confidence=0.8,
)
)
notes: List[str] = []
if not facts:
notes.append(f"No seating assignment listed for {record['Name']}.")
return facts, notes, True