"""The one shape every knowledge-base record becomes. Each collector emits its own field names — an AnchorLink org has `summary` and `categories`, a directory entity has `evidence_quote` and `building_slug`, a course has `credit_hours` and AXLE tags. Retrieval, filtering, and citation all want one shape, so every source gets an adapter in `normalize.py` that produces `Doc`s and stows the source-specific fields in `extra`. `id` is source-prefixed (`"anchorlink:org:428475"`) so it's stable across rebuilds and unambiguous when a tool result is cited back. """ from __future__ import annotations from dataclasses import dataclass, field # The four First View / Foresight domains, in canonical order. DOMAINS = ("strengths", "crew", "future", "vu") @dataclass(frozen=True, slots=True) class Doc: id: str source: str # anchorlink | events | academics | directory | # buildings | residential | study-abroad | # funding | immersion | yes kind: str # organization | event | course | program | # requirement | office | building | house | # award | resource | policy | page title: str text: str # the searchable blob url: str | None = None domains: tuple[str, ...] = () start: str | None = None # ISO date — events, deadlines, milestones end: str | None = None extra: dict = field(default_factory=dict, compare=False) def cite(self) -> dict: """The citation chip the UI renders for this document.""" return {"label": self.title, "url": self.url, "source": SOURCE_LABELS.get(self.source, self.source)} # Student-facing names. "anchorlink" is a thing students actually say; "study-abroad" # as a slug is not. SOURCE_LABELS = { "anchorlink": "AnchorLink", "events": "Campus events", "academics": "Course catalog", "directory": "Campus directory", "buildings": "Campus map", "residential": "Housing & residential life", "study-abroad": "Global Education Office", "funding": "Scholarships & fellowships", "immersion": "Immersion Vanderbilt", "recreation": "Recreation & sports", "yes": "YES class schedule", }