mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix(core): use updated_at for recent_activity filter and ordering (#812)
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Signed-off-by: Drew Cain <groksrc@gmail.com> Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: phernandez <paul@basicmachines.co>
This commit is contained in:
@@ -774,7 +774,8 @@ class PostgresSearchRepository(SearchRepositoryBase):
|
||||
# Handle date filter
|
||||
if after_date:
|
||||
params["after_date"] = after_date
|
||||
conditions.append("search_index.created_at > :after_date")
|
||||
# Filter on updated_at so recently-edited notes are included even when created_at is old
|
||||
conditions.append("search_index.updated_at > :after_date")
|
||||
# order by most recent first
|
||||
order_by_clause = ", search_index.updated_at DESC"
|
||||
|
||||
@@ -945,7 +946,7 @@ class PostgresSearchRepository(SearchRepositoryBase):
|
||||
{score_expr} as score
|
||||
FROM {from_clause}
|
||||
WHERE {where_clause}
|
||||
ORDER BY score DESC, search_index.id ASC {order_by_clause}
|
||||
ORDER BY score DESC {order_by_clause}, search_index.id ASC
|
||||
LIMIT :limit
|
||||
OFFSET :offset
|
||||
"""
|
||||
|
||||
@@ -789,7 +789,8 @@ class SQLiteSearchRepository(SearchRepositoryBase):
|
||||
# Handle date filter using datetime() for proper comparison
|
||||
if after_date:
|
||||
params["after_date"] = after_date
|
||||
conditions.append("datetime(search_index.created_at) > datetime(:after_date)")
|
||||
# Filter on updated_at so recently-edited notes are included even when created_at is old
|
||||
conditions.append("datetime(search_index.updated_at) > datetime(:after_date)")
|
||||
|
||||
# order by most recent first
|
||||
order_by_clause = ", search_index.updated_at DESC"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Tests for search service."""
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import text
|
||||
@@ -174,12 +174,12 @@ async def test_after_date(search_service, test_graph):
|
||||
)
|
||||
for r in results:
|
||||
# Handle both string (SQLite) and datetime (Postgres) formats
|
||||
created_at = (
|
||||
r.created_at
|
||||
if isinstance(r.created_at, datetime)
|
||||
else datetime.fromisoformat(r.created_at)
|
||||
updated_at = (
|
||||
r.updated_at
|
||||
if isinstance(r.updated_at, datetime)
|
||||
else datetime.fromisoformat(r.updated_at)
|
||||
)
|
||||
assert created_at > past_date
|
||||
assert updated_at > past_date
|
||||
|
||||
# Should not find with future date
|
||||
future_date = datetime(2030, 1, 1).astimezone()
|
||||
@@ -192,6 +192,71 @@ async def test_after_date(search_service, test_graph):
|
||||
assert len(results) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_after_date_uses_updated_at(search_service):
|
||||
"""Regression: after_date should filter on updated_at, not created_at.
|
||||
|
||||
An entity created before the timeframe but updated within it must appear
|
||||
in recent-activity results. A stale entity (updated_at also old) must not.
|
||||
"""
|
||||
cutoff = datetime(2020, 1, 1, tzinfo=timezone.utc)
|
||||
old_created = datetime(2015, 6, 1, tzinfo=timezone.utc)
|
||||
recently_updated = datetime(2023, 3, 15, tzinfo=timezone.utc)
|
||||
stale_updated = datetime(2018, 6, 1, tzinfo=timezone.utc)
|
||||
|
||||
project_id = search_service.repository.project_id
|
||||
|
||||
# Leave metadata at its None default — SearchIndexRow.to_insert only
|
||||
# JSON-serializes truthy metadata, so passing {} would slip an
|
||||
# un-serialized dict into the SQLite bind and raise ProgrammingError.
|
||||
recently_updated_row = SearchIndexRow(
|
||||
project_id=project_id,
|
||||
id=99001,
|
||||
type="entity",
|
||||
file_path="test/recently_updated.md",
|
||||
title="Recently Updated Entity",
|
||||
content_snippet="recently updated content",
|
||||
permalink="test/recently-updated-entity",
|
||||
created_at=old_created,
|
||||
updated_at=recently_updated,
|
||||
)
|
||||
stale_row = SearchIndexRow(
|
||||
project_id=project_id,
|
||||
id=99002,
|
||||
type="entity",
|
||||
file_path="test/stale.md",
|
||||
title="Stale Entity",
|
||||
content_snippet="stale content",
|
||||
permalink="test/stale-entity",
|
||||
created_at=old_created,
|
||||
updated_at=stale_updated,
|
||||
)
|
||||
|
||||
await search_service.repository.index_item(recently_updated_row)
|
||||
await search_service.repository.index_item(stale_row)
|
||||
|
||||
results = await search_service.search(
|
||||
SearchQuery(after_date=cutoff.isoformat())
|
||||
)
|
||||
|
||||
permalinks = {r.permalink for r in results}
|
||||
# recently-updated entity must appear despite old created_at
|
||||
assert "test/recently-updated-entity" in permalinks
|
||||
# stale entity must not appear (updated_at is before cutoff)
|
||||
assert "test/stale-entity" not in permalinks
|
||||
|
||||
# results should be ordered newest updated_at first
|
||||
updated_ats = []
|
||||
for r in results:
|
||||
ua = (
|
||||
r.updated_at
|
||||
if isinstance(r.updated_at, datetime)
|
||||
else datetime.fromisoformat(r.updated_at)
|
||||
)
|
||||
updated_ats.append(ua.replace(tzinfo=timezone.utc) if ua.tzinfo is None else ua)
|
||||
assert updated_ats == sorted(updated_ats, reverse=True)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_type(search_service, test_graph):
|
||||
"""Test search filters."""
|
||||
|
||||
Reference in New Issue
Block a user