"""Answers questions from the site's own words. No model, no inference. Everything returned here is a direct quote from published page content plus the link it came from. If the site does not say it, this says so rather than producing something plausible — a school site cannot afford invented answers about pricing, dates or who is responsible for a child. """ import re import mesh STOPWORDS = { "a", "an", "the", "is", "are", "was", "were", "do", "does", "did", "i", "you", "my", "our", "we", "can", "could", "would", "should", "please", "tell", "me", "about", "for", "of", "to", "in", "on", "at", "and", "or", "it", "this", "that", "there", "here", "what", "whats", "when", "where", "who", "whos", "how", "why", "which", "have", "has", "any", "get", "find", "know", "want", "need", "am", "be", "with", "from", "if", "so", "up", "out", "will", "much", "many", "some", } # Question shape → how to phrase the reply. Order matters: first match wins. INTENTS = [ ("who", re.compile(r"\bwho(?:'s|s\b|\s+is|\s+are|\s+teaches|\s+do)?\b", re.I), "Here is what the site says about who is involved:"), ("when", re.compile(r"\bwhen\b|\bwhat time\b|\bdates?\b|\bschedule\b|\bcalendar\b|\bbreak\b|\bholiday\b", re.I), "Here are the dates and times listed:"), ("cost", re.compile(r"\bcost|price|pricing|how much|fee|fees|pay|payment|free\b", re.I), "Here is the pricing information listed:"), ("where", re.compile(r"\bwhere\b|\baddress\b|\blocation\b|\bdirections?\b", re.I), "Here is the location information listed:"), ("contact", re.compile(r"\bcontact\b|\bphone\b|\bemail\b|\bcall\b|\breach\b", re.I), "Here are the contact details listed:"), ("menu", re.compile(r"\bmenu\b|\blunch\b|\bbreakfast\b|\bmeal\b|\bcafeteria\b", re.I), "Here is what the menu information says:"), ] DEFAULT_LEAD = "Here is what the site says:" DATE_RE = re.compile( r"\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+\d{1,2}" r"|\b\d{1,2}/\d{1,2}(?:/\d{2,4})?" r"|\b(?:mon|tues|wednes|thurs|fri|satur|sun)day\b" r"|\b\d{1,2}:\d{2}\s*(?:a\.?m\.?|p\.?m\.?)?", re.I) MONEY_RE = re.compile(r"\$\s?\d[\d,]*(?:\.\d{2})?") PHONE_RE = re.compile(r"\b\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b") EMAIL_RE = re.compile(r"\b[\w.+-]+@[\w-]+\.[\w.]+\b") INTENT_PATTERNS = {"when": DATE_RE, "cost": MONEY_RE, "contact": PHONE_RE} def keywords(question): words = re.findall(r"[\w']+", (question or "").lower()) kept = [w for w in words if w not in STOPWORDS and len(w) > 2] return kept or words def intent_of(question): for name, pattern, lead in INTENTS: if pattern.search(question or ""): return name, lead return "what", DEFAULT_LEAD DANGLING = re.compile(r"\b(?:the|a|an|on|at|to|of|for|in|and|or|with|by|from|our|your)$", re.I) def clean(text): """Editors write literal ** in this content; visitors should not see it.""" text = re.sub(r"\*\*(.+?)\*\*", r"\1", text or "") text = text.replace("**", "").replace("__", "") return re.sub(r"\s+", " ", text).strip() def sentences(text): parts = re.split(r"(?<=[.!?])\s+|\s*\n+\s*|\s{2,}", text or "") return [clean(s).strip(" \t·-–—") for s in parts if len(clean(s)) > 2] def score_sentence(s, terms, intent): low = s.lower() score = sum(3 for t in terms if t in low) pattern = INTENT_PATTERNS.get(intent) if pattern and pattern.search(s): score += 4 if intent == "contact" and (EMAIL_RE.search(s) or PHONE_RE.search(s)): score += 3 # A bare heading like "Superintendent" repeats the question instead of # answering it. Reward sentences that actually carry information. if len(s) < 20: score -= 6 elif len(s) >= 45: score += 2 # "For school-specific contacts, visit the" — a fragment cut mid-thought # reads as broken. Prefer sentences that finish. if DANGLING.search(s.rstrip(" .,:;")): score -= 5 score -= abs(len(s) - 130) / 400 # prefer a readable length return score def best_sentence(text, terms, intent): """The one sentence that most answers the question, not the whole block.""" best, best_score = "", -999 for s in sentences(text): score = score_sentence(s, terms, intent) if score > best_score: best, best_score = s, score return best, best_score def ask(question, limit=3): """Answer from published content only. Returns quotes plus where to read more.""" terms = keywords(question) intent, lead = intent_of(question) if not terms: return {"ok": False, "intent": intent, "answer": "Ask me about something on the site — lunch menus, " "calendars, staff, or a school office.", "sources": []} hits = mesh.public_search(" ".join(terms)) if not hits: return {"ok": False, "intent": intent, "answer": f"I could not find anything on the site about " f"“{question.strip()}”. Try different words, or use the " f"site menu to browse.", "sources": []} # Collapse to the strongest passage per page. pages = {} for h in hits: aid = h["article_id"] quote, quality = best_sentence(h["text"], terms, intent) if not quote: continue # A headline names the topic; the body answers the question. if h["type"] == "headline": quality -= 4 page = pages.get(aid) if page is None or quality > page["quality"]: pages[aid] = {"article_id": aid, "title": h["article"], "quote": quote, "quality": quality, "coord": h["coord"], "terms": page["terms"] if page else set()} pages[aid]["terms"] |= {t for t in terms if t in (h["text"] or "").lower()} # Rank pages by how much of the question they actually cover, not by any # single passage — a page mentioning every term is the likelier answer. ranked = sorted(pages.values(), key=lambda p: (len(p["terms"]), p["quality"]), reverse=True) top = ranked[:limit] if not top: return {"ok": False, "intent": intent, "answer": "I found related pages but no clear sentence to quote. " "Try opening one of them.", "sources": []} return { "ok": True, "intent": intent, "lead": lead, "answer": top[0]["quote"], "source": {"title": top[0]["title"], "article_id": top[0]["article_id"]}, "sources": [{"title": t["title"], "article_id": t["article_id"], "quote": t["quote"]} for t in top], }