monish563/NU-KIOSK-API
0
1"""Blueprint that returns location information for faculty, staff, or students."""2 3from __future__ import annotations4 5from typing import Any, Dict, List, Optional6 7from .base import AnalysisContext, Blueprint, BlueprintResult, Fact8from .faculty_profile import _lookup_record9from ..data.utils import canonicalize_name10 11 12class LocationBlueprint(Blueprint):13 """Return where a person (faculty, staff, or student) can be found on campus.14 15 Automatically detects whether the name matches a faculty member, staff member,16 or student and returns the appropriate location information.17 """18 19 name = "location"20 21 def run(self, context: AnalysisContext, **kwargs: Any) -> BlueprintResult:22 target_name = (kwargs.get("name") or kwargs.get("person") or "").strip()23 if not target_name:24 return BlueprintResult(self.name, kwargs, facts=[], notes=["No name provided."])25 26 facts: List[Fact] = []27 notes: List[str] = []28 found_faculty = False29 found_staff = False30 found_student = False31 32 # Try faculty lookup first33 faculty_facts, faculty_notes, found_faculty = self._lookup_faculty_location(context, target_name)34 facts.extend(faculty_facts)35 notes.extend(faculty_notes)36 37 # Try staff lookup38 staff_facts, staff_notes, found_staff = self._lookup_staff_location(context, target_name)39 facts.extend(staff_facts)40 notes.extend(staff_notes)41 42 # Try student lookup43 student_facts, student_notes, found_student = self._lookup_student_seating(context, target_name)44 facts.extend(student_facts)45 notes.extend(student_notes)46 47 # If none found, report not found48 if not found_faculty and not found_staff and not found_student:49 return BlueprintResult(50 self.name,51 kwargs,52 facts=[],53 notes=[f"'{target_name}' not found in faculty, staff, or student records."],54 )55 56 return BlueprintResult(self.name, kwargs, facts=facts, notes=notes)57 58 def _lookup_faculty_location(59 self, context: AnalysisContext, target_name: str60 ) -> tuple[List[Fact], List[str], bool]:61 """Look up faculty office location. Returns (facts, notes, found)."""62 63 record, lookup_notes, office_row = _lookup_record(context, target_name)64 if record is None and office_row is None:65 return [], [], False66 67 facts: List[Fact] = []68 notes = list(lookup_notes)69 70 # Try to resolve office through relationship71 office_matches = []72 if record is not None:73 office_matches = context.catalog.resolve_relationship("faculty_to_office", record)74 75 if office_matches:76 office_row = office_matches[0]77 office_entity = context.catalog.try_get("faculty_offices")78 office_source = office_entity.origin if office_entity else None79 building = (office_row.get("Building") or "").strip()80 room = (office_row.get("Room") or office_row.get("Room Location") or "").strip()81 location = " ".join(bit for bit in (building, room) if bit).strip()82 facts.append(83 Fact(84 subject=record["Name"],85 predicate="office",86 value=location or "Office location unavailable",87 source=office_source,88 )89 )90 elif office_row is not None:91 # Matched office assignment but no roster record92 office_entity = context.catalog.try_get("faculty_offices")93 office_source = office_entity.origin if office_entity else None94 building = (office_row.get("Building") or office_row.get("Location") or "").strip()95 room = (office_row.get("Room") or office_row.get("Room Location") or "").strip()96 location = " ".join(bit for bit in (building, room) if bit).strip()97 facts.append(98 Fact(99 subject=office_row.get("Assignee Name") or target_name,100 predicate="office",101 value=location or "Office location unavailable",102 source=office_source,103 )104 )105 elif record is not None:106 notes.append(f"No office assignment found for {record['Name']}.")107 108 return facts, notes, (record is not None or office_row is not None)109 110 def _lookup_staff_location(111 self, context: AnalysisContext, target_name: str112 ) -> tuple[List[Fact], List[str], bool]:113 """Look up staff office location. Returns (facts, notes, found)."""114 115 staff = context.catalog.try_get("staff")116 if not staff:117 return [], [], False118 119 record = None120 canonical_target = canonicalize_name(target_name)121 for row in staff.records:122 if canonicalize_name(row.get("Name", "")) == canonical_target:123 record = row124 break125 126 if record is None:127 return [], [], False128 129 facts: List[Fact] = []130 notes: List[str] = []131 132 location = (record.get("Room Location") or "").strip()133 if location and location.lower() not in {"not listed", "n/a", "none", ""}:134 facts.append(135 Fact(136 subject=record.get("Name", target_name),137 predicate="office",138 value=location,139 source=staff.origin,140 confidence=0.9,141 )142 )143 else:144 notes.append(f"No office location listed for {record.get('Name', target_name)}.")145 146 return facts, notes, True147 148 def _lookup_student_seating(149 self, context: AnalysisContext, target_name: str150 ) -> tuple[List[Fact], List[str], bool]:151 """Look up student seating location. Returns (facts, notes, found)."""152 153 students = context.catalog.try_get("students")154 if not students:155 return [], [], False156 157 record = None158 canonical_target = canonicalize_name(target_name)159 for row in students.records:160 if canonicalize_name(row.get("Name")) == canonical_target:161 record = row162 break163 164 if record is None:165 return [], [], False166 167 seating = context.catalog.resolve_relationship("student_to_mudd_seat", record)168 seating_entity = context.catalog.try_get("mudd_seating")169 seating_origin = seating_entity.origin if seating_entity else None170 171 facts: List[Fact] = []172 for seat in seating:173 facts.append(174 Fact(175 subject=record.get("Name", target_name),176 predicate="seating",177 value={178 "room": seat.get("Room Number"),179 "desk": seat.get("Desk Number"),180 "track": seat.get("Track"),181 "advisor": seat.get("Advocate/Advisor"),182 "email": seat.get("Email address"),183 },184 source=seating_origin,185 confidence=0.8,186 )187 )188 189 notes: List[str] = []190 if not facts:191 notes.append(f"No seating assignment listed for {record['Name']}.")192 193 return facts, notes, True194 