From fb1ae915e4ef06edae6ec3dcc8a3b7360ae622d8 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 2 Jun 2026 22:29:08 -0400 Subject: [PATCH] feat(processes): expose process as a knowledge type Task 4 of #582. Add 'process' to the knowledge route _VALID_TYPES and to the get_knowledge_counts facet + total. query_knowledge/_apply_type_filter already handle arbitrary note_type, so listing by type=process works unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/fabledassistant/routes/knowledge.py | 2 +- src/fabledassistant/services/knowledge.py | 6 +-- tests/test_services_knowledge_counts.py | 45 +++++++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/test_services_knowledge_counts.py diff --git a/src/fabledassistant/routes/knowledge.py b/src/fabledassistant/routes/knowledge.py index b51ef21..7a2aa4d 100644 --- a/src/fabledassistant/routes/knowledge.py +++ b/src/fabledassistant/routes/knowledge.py @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) knowledge_bp = Blueprint("knowledge", __name__, url_prefix="/api/knowledge") -_VALID_TYPES = {"note", "person", "place", "list", "task", "plan"} +_VALID_TYPES = {"note", "person", "place", "list", "task", "plan", "process"} _VALID_SORTS = {"modified", "created", "alpha", "type"} diff --git a/src/fabledassistant/services/knowledge.py b/src/fabledassistant/services/knowledge.py index 42b1141..1da852b 100644 --- a/src/fabledassistant/services/knowledge.py +++ b/src/fabledassistant/services/knowledge.py @@ -237,7 +237,7 @@ async def get_knowledge_counts(user_id: int, tags: list[str] | None = None) -> d .where(Note.user_id == user_id) .where(Note.status.is_(None)) .where(Note.deleted_at.is_(None)) - .where(Note.note_type.in_(["note", "person", "place", "list"])) + .where(Note.note_type.in_(["note", "person", "place", "list", "process"])) .group_by(Note.note_type) ) if tags: @@ -273,9 +273,9 @@ async def get_knowledge_counts(user_id: int, tags: list[str] | None = None) -> d plan_stmt = plan_stmt.where(Note.tags.contains([tag])) counts["plan"] = (await session.execute(plan_stmt)).scalar_one() - for t in ("note", "person", "place", "list", "task", "plan"): + for t in ("note", "person", "place", "list", "task", "plan", "process"): counts.setdefault(t, 0) - counts["total"] = sum(counts[t] for t in ("note", "person", "place", "list", "task")) + counts["total"] = sum(counts[t] for t in ("note", "person", "place", "list", "task", "process")) return counts diff --git a/tests/test_services_knowledge_counts.py b/tests/test_services_knowledge_counts.py new file mode 100644 index 0000000..ae3a803 --- /dev/null +++ b/tests/test_services_knowledge_counts.py @@ -0,0 +1,45 @@ +"""get_knowledge_counts includes the 'process' type and counts it in total.""" +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + + +def _make_mock_session(): + s = AsyncMock() + s.__aenter__ = AsyncMock(return_value=s) + s.__aexit__ = AsyncMock(return_value=False) + return s + + +def _grouped(rows): + r = MagicMock() + r.all.return_value = rows + return r + + +def _scalar(n): + r = MagicMock() + r.scalar_one.return_value = n + return r + + +@pytest.mark.asyncio +async def test_counts_include_process_in_facet_and_total(): + session = _make_mock_session() + # 1) grouped non-task counts, 2) task count, 3) plan count + session.execute = AsyncMock(side_effect=[ + _grouped([("note", 3), ("process", 2)]), + _scalar(1), # tasks + _scalar(0), # plans + ]) + with patch("fabledassistant.services.knowledge.async_session") as cls: + cls.return_value = session + from fabledassistant.services.knowledge import get_knowledge_counts + counts = await get_knowledge_counts(user_id=1) + + assert counts["process"] == 2 + # facet keys all present (setdefault) + for key in ("note", "person", "place", "list", "task", "plan", "process"): + assert key in counts + # total = note(3) + person(0) + place(0) + list(0) + task(1) + process(2) + assert counts["total"] == 6