1"""Relevance ranker — re-ranks memory search results by combined score."""
2
3from __future__ import annotations
4
5from datetime import UTC, datetime
6import math
7
8from lexigram.contracts.ai.memory import MemoryQuery, MemorySearchResult
9from lexigram.logging import (
10 get_logger,
11)
12
13logger = get_logger(__name__)
14
15
16class RelevanceRanker:
17 """Re-ranks a list of MemorySearchResult by a multi-factor score.
18
19 Combines the raw retrieval score with recency and importance using
20 the weights from the originating MemoryQuery.
21 """
22
23 def rank(
24 self,
25 results: list[MemorySearchResult],
26 query: MemoryQuery,
27 ) -> list[MemorySearchResult]:
28 """Re-rank *results* and return a new sorted list.
29
30 Args:
31 results: Raw search results to re-rank.
32 query: Original query with weighting parameters.
33
34 Returns:
35 Results sorted by descending combined score.
36 """
37 reranked: list[MemorySearchResult] = []
38 for r in results:
39 age_s = (datetime.now(UTC) - r.entry.timestamp).total_seconds()
40 recency = math.exp(-age_s / 86400.0)
41 combined = (
42 query.relevance_weight * r.score
43 + query.recency_weight * recency
44 + query.importance_weight * r.entry.importance
45 )
46 reranked.append(
47 MemorySearchResult(entry=r.entry, score=combined, source=r.source)
48 )
49 reranked.sort(key=lambda x: x.score, reverse=True)
50 return reranked
51
52 def top_k(
53 self,
54 results: list[MemorySearchResult],
55 query: MemoryQuery,
56 k: int | None = None,
57 ) -> list[MemorySearchResult]:
58 """Re-rank and return the top *k* results.
59
60 Args:
61 results: Raw results to rank.
62 query: Query parameters.
63 k: Maximum results to return. Defaults to ``query.top_k``.
64
65 Returns:
66 Top-k results after re-ranking.
67 """
68 ranked = self.rank(results, query)
69 return ranked[: k or query.top_k]
70
71
72__all__ = ["RelevanceRanker"]