murtaza-2007
Aurelius improvement pass: domain-aware recs, finance/research surfaces, 2D graph
658d200 | """Aurelius core β the GraphSource protocol and adapter registry. | |
| An adapter makes one interconnected dataset navigable. Implement this | |
| protocol (plus, for large static datasets, an ingest script that fills the | |
| store) and the whole engine β navigator, relate, discover, the UI β works | |
| on your graph with zero engine changes. That property is the product. | |
| Two adapter modes: | |
| live β neighbors/backlinks fetched from an upstream API per call | |
| (Wikipedia's links/linkshere, OpenAlex's refs/cited-by). | |
| ingested β the graph was written into core.store by an ingest run; | |
| the adapter answers from the store (biomed, news, finance). | |
| StoreBackedSource below is the shared implementation for | |
| this mode. | |
| """ | |
| from __future__ import annotations | |
| import abc | |
| from typing import Optional | |
| from .types import Edge, NodeInfo, NodeRef | |
| class GraphSource(abc.ABC): | |
| """Contract every data-source adapter implements.""" | |
| #: unique registry name, e.g. "wikipedia" | |
| name: str = "" | |
| #: human description for /api/sources and the UI picker | |
| description: str = "" | |
| #: edge types this source can emit (documentation + UI legend) | |
| edge_types: tuple[str, ...] = ("link",) | |
| #: False β back_neighbors() is unsupported or expensive; the navigator | |
| #: then skips goal-zone construction and runs forward-only. | |
| supports_backlinks: bool = True | |
| # ββ identity βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def resolve(self, query: str) -> Optional[NodeRef]: | |
| """Free-text query β a node, or None. Adapters own their notion of | |
| fuzzy matching / disambiguation (the v1 lesson: a resolved title is | |
| not necessarily a *search-worthy* target β handle stubs here).""" | |
| # ββ structure ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def neighbors(self, n: NodeRef, *, | |
| hunt_id: str | None = None, | |
| priority_ids: set[str] | None = None) -> list[Edge]: | |
| """Outbound edges of n. `hunt_id`/`priority_ids` are optional | |
| fetch hints (keep paginating until hunt_id is found; order | |
| priority_ids first) β adapters that don't paginate ignore them.""" | |
| async def back_neighbors(self, n: NodeRef, limit: int = 500) -> list[Edge]: | |
| """Inbound edges (edges whose dst is n). Only called when | |
| supports_backlinks is True.""" | |
| raise NotImplementedError | |
| # ββ content ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def node_info(self, n: NodeRef, rich: bool = False) -> NodeInfo: | |
| """Embedding text + display summary for n. rich=True may spend an | |
| extra fetch for a fuller text (used for the two search endpoints, | |
| whose embeddings anchor every score in the run).""" | |
| return NodeInfo(text=n.title) | |
| async def node_infos(self, ns: list[NodeRef]) -> list[NodeInfo]: | |
| """Batch form of node_info (rich=False). Adapters that already hold | |
| the info from a neighbors() fetch should override to answer from | |
| cache without I/O β the navigator calls this once per expansion | |
| with every candidate.""" | |
| return [await self.node_info(n) for n in ns] | |
| # ββ recommendations ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def suggest(self, query: str, limit: int = 8) -> list[dict]: | |
| """Type-ahead suggestions for the search box, drawn from THIS | |
| source's own vocabulary β the fix for the old Wikipedia-only | |
| autocomplete. Each item is | |
| {id, title, kind?, subtitle?} | |
| where `kind` groups results (company/paper/disease/β¦) and | |
| `subtitle` is a short human hint. Default: no suggestions (a live | |
| source with nothing cheap to offer simply returns []); adapters | |
| override with a domain-appropriate lookup.""" | |
| return [] | |
| # ββ niceties (optional) ββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def edge_display(self, src: NodeRef, dst: NodeRef) -> Optional[str]: | |
| """Human-facing rendering of an edge (Wikipedia: the piped link | |
| text). None = nothing special to show.""" | |
| return None | |
| async def sample_pair(self) -> Optional[tuple[str, str]]: | |
| """Two queries that make a good demo pair, or None.""" | |
| return None | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Registry | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _REGISTRY: dict[str, GraphSource] = {} | |
| def register(source: GraphSource) -> GraphSource: | |
| if not source.name: | |
| raise ValueError("GraphSource.name must be set") | |
| _REGISTRY[source.name] = source | |
| return source | |
| def get_source(name: str) -> GraphSource: | |
| try: | |
| return _REGISTRY[name] | |
| except KeyError: | |
| raise KeyError(f"Unknown source '{name}'. Registered: {sorted(_REGISTRY)}") | |
| def list_sources() -> list[GraphSource]: | |
| return list(_REGISTRY.values()) | |