nikeshn commited on
Commit
5c70b17
·
verified ·
1 Parent(s): 567fa26

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -1
app.py CHANGED
@@ -304,6 +304,136 @@ def _load_staff_directory_from_kb():
304
  def _staff_lookup_candidates():
305
  return kb_staff_directory or STAFF_DIRECTORY
306
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  def _match_staff_name(question: str):
308
  tokens = _normalize_name_query(question)
309
  if not tokens or len(tokens) > 5:
@@ -327,6 +457,12 @@ def _staff_name_answer(staff: dict, partial: str) -> str:
327
  f"<strong>{staff['full_name']}</strong> is the <strong>{staff['role']}</strong>. {staff['details']}"
328
  )
329
 
 
 
 
 
 
 
330
 
331
  GROUNDED_LIBRARY_MAP = {
332
  "ill": "interlibrary loan ILL document delivery full text unavailable article not available borrow from another library",
@@ -1357,7 +1493,7 @@ async def agent_query(req: AgentRequest):
1357
  "source_mode": "social",
1358
  }
1359
 
1360
- # ---- Partial staff-name handling ----
1361
  staff_match = _match_staff_name(question)
1362
  if staff_match:
1363
  answer = _staff_name_answer(staff_match, question)
@@ -1380,6 +1516,28 @@ async def agent_query(req: AgentRequest):
1380
  "source_mode": "staff_kb" if kb_staff_directory else "staff_directory",
1381
  }
1382
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1383
  # ---- Follow-up to the greeting menu ----
1384
  if _is_greeting_menu_followup(question, history):
1385
  answer = _greeting_menu_clarify_answer()
 
304
  def _staff_lookup_candidates():
305
  return kb_staff_directory or STAFF_DIRECTORY
306
 
307
+ def _role_key(name: str) -> str:
308
+ return re.sub(r"[^a-z0-9]+", " ", (name or "").lower()).strip()
309
+
310
+ def _build_role_aliases(staff: dict):
311
+ role = (staff.get("role") or "").strip()
312
+ details = (staff.get("details") or "").strip()
313
+ full_name = staff.get("full_name", "")
314
+ key = _role_key(full_name)
315
+
316
+ aliases = []
317
+ if role:
318
+ aliases.append(role)
319
+ role_l = role.lower()
320
+ parts = [p.strip() for p in re.split(r"[\/,]|\band\b", role_l) if p.strip()]
321
+ aliases.extend(parts)
322
+ if "manager" in role_l:
323
+ mgr_tail = re.sub(r"^manager\s*,?\s*", "", role_l).strip()
324
+ if mgr_tail:
325
+ aliases.extend([
326
+ mgr_tail,
327
+ f"{mgr_tail} librarian",
328
+ f"{mgr_tail.rstrip('s')} librarian",
329
+ f"{mgr_tail} manager",
330
+ ])
331
+ if "librarian" in role_l:
332
+ base = role_l.replace("librarian", "").replace("/", " ").strip(" ,")
333
+ if base:
334
+ aliases.extend([
335
+ base,
336
+ f"{base} librarian",
337
+ f"{base.rstrip('s')} librarian",
338
+ ])
339
+
340
+ best_for = ""
341
+ m = re.search(r"Best for:\s*([^|]+)", details, re.IGNORECASE)
342
+ if m:
343
+ best_for = m.group(1).strip()
344
+ if best_for:
345
+ aliases.extend([p.strip() for p in best_for.split(",") if p.strip()])
346
+
347
+ manual = {
348
+ "nikesh narayanan": [
349
+ "research librarian", "access services librarian", "research and access services librarian",
350
+ "research access librarian", "open access librarian", "orcid librarian",
351
+ "research impact librarian", "bibliometrics librarian", "scholarly communication librarian"
352
+ ],
353
+ "walter brian hall": [
354
+ "systems librarian", "system librarian", "library systems librarian",
355
+ "digital services librarian", "technology services librarian", "website librarian",
356
+ "technology librarian", "digital librarian"
357
+ ],
358
+ "rani anand": [
359
+ "e resources librarian", "e-resources librarian", "electronic resources librarian",
360
+ "database librarian", "database access librarian", "resource access librarian"
361
+ ],
362
+ "jason fetty": [
363
+ "medical librarian", "health sciences librarian", "clinical librarian",
364
+ "systematic review librarian", "pubmed librarian"
365
+ ],
366
+ "alia al harrasi": [
367
+ "acquisitions librarian", "acquisition librarian", "technical services librarian",
368
+ "technical service librarian", "collection development librarian", "cataloguing librarian",
369
+ "metadata librarian"
370
+ ],
371
+ "muna ahmad mohammad al blooshi": [
372
+ "public services librarian", "public service librarian", "circulation librarian",
373
+ "access services manager", "service desk librarian"
374
+ ],
375
+ "dr abdulla al hefeiti": [
376
+ "library director", "director of library", "director libraries", "assistant provost libraries"
377
+ ],
378
+ "abdulla al hefeiti": [
379
+ "library director", "director of library", "director libraries", "assistant provost libraries"
380
+ ],
381
+ }
382
+ aliases.extend(manual.get(key, []))
383
+
384
+ normalized = []
385
+ for a in aliases:
386
+ a = re.sub(r"[^a-z0-9&/ +()-]+", " ", (a or "").lower())
387
+ a = re.sub(r"\s+", " ", a).strip(" ,")
388
+ if a:
389
+ normalized.append(a)
390
+ if a.endswith(" services"):
391
+ normalized.append(a[:-1])
392
+ if a.endswith(" service"):
393
+ normalized.append(a + " librarian")
394
+ if a.endswith(" resources"):
395
+ normalized.append(a[:-1])
396
+ return _dedupe_keep_order(normalized)
397
+
398
+ def _match_staff_role(question: str):
399
+ ql = re.sub(r"[^a-z0-9 ]+", " ", (question or "").lower())
400
+ ql = re.sub(r"\s+", " ", ql).strip()
401
+ if not ql:
402
+ return None
403
+
404
+ role_trigger_re = re.compile(
405
+ r"\b(librarian|library director|director|manager|services?|service|systems?|technology|website|research|access|open access|orcid|bibliometric|bibliometrics|public services?|technical services?|acquisitions?|collection development|catalogu(?:e|ing)|metadata|database|e resources|e-resources|electronic resources|medical|clinical|systematic review|circulation)\b"
406
+ )
407
+ if not role_trigger_re.search(ql):
408
+ return None
409
+
410
+ best = None
411
+ best_score = 0
412
+ for staff in _staff_lookup_candidates():
413
+ score = 0
414
+ for alias in _build_role_aliases(staff):
415
+ if not alias:
416
+ continue
417
+ alias_words = alias.split()
418
+ if alias in ql:
419
+ score = max(score, 100 + len(alias_words))
420
+ elif len(alias_words) >= 2 and all(w in ql.split() for w in alias_words):
421
+ score = max(score, 70 + len(alias_words))
422
+ else:
423
+ overlap = sum(1 for w in alias_words if len(w) > 2 and w in ql.split())
424
+ if overlap >= 2:
425
+ score = max(score, 40 + overlap)
426
+ role_words = _normalize_name_query(staff.get("role", ""))
427
+ if role_words:
428
+ overlap = sum(1 for w in role_words if len(w) > 2 and w in ql.split())
429
+ if overlap >= 2:
430
+ score = max(score, 20 + overlap)
431
+ if score > best_score:
432
+ best_score = score
433
+ best = staff
434
+
435
+ return best if best_score >= 42 else None
436
+
437
  def _match_staff_name(question: str):
438
  tokens = _normalize_name_query(question)
439
  if not tokens or len(tokens) > 5:
 
457
  f"<strong>{staff['full_name']}</strong> is the <strong>{staff['role']}</strong>. {staff['details']}"
458
  )
459
 
460
+ def _staff_role_answer(staff: dict, question: str) -> str:
461
+ return (
462
+ f"For <strong>{question}</strong>, the best match is <strong>{staff['full_name']}</strong> — "
463
+ f"<strong>{staff['role']}</strong>.<br><br>{staff['details']}"
464
+ )
465
+
466
 
467
  GROUNDED_LIBRARY_MAP = {
468
  "ill": "interlibrary loan ILL document delivery full text unavailable article not available borrow from another library",
 
1493
  "source_mode": "social",
1494
  }
1495
 
1496
+ # ---- Staff direct matching (name first, then role semantics) ----
1497
  staff_match = _match_staff_name(question)
1498
  if staff_match:
1499
  answer = _staff_name_answer(staff_match, question)
 
1516
  "source_mode": "staff_kb" if kb_staff_directory else "staff_directory",
1517
  }
1518
 
1519
+ staff_role_match = _match_staff_role(question)
1520
+ if staff_role_match:
1521
+ answer = _staff_role_answer(staff_role_match, question)
1522
+ elapsed = time.time() - start
1523
+ source_title = staff_role_match.get("source_title", "")
1524
+ source_url = staff_role_match.get("source", "")
1525
+ return {
1526
+ "answer": answer,
1527
+ "intent": "library_info",
1528
+ "tools_used": ["staff_role_match"],
1529
+ "search_results": [],
1530
+ "sources": ([{"title": source_title, "source": source_url}] if source_title or source_url else []),
1531
+ "model_used": req.model,
1532
+ "response_time": round(elapsed, 2),
1533
+ "corrected_query": question,
1534
+ "natural_query": question,
1535
+ "database_query": question,
1536
+ "original_question": question,
1537
+ "is_follow_up": False,
1538
+ "source_mode": "staff_kb" if kb_staff_directory else "staff_directory",
1539
+ }
1540
+
1541
  # ---- Follow-up to the greeting menu ----
1542
  if _is_greeting_menu_followup(question, history):
1543
  answer = _greeting_menu_clarify_answer()