From 4becc887cd0d80b3892d026e9003dc5951dfc656 Mon Sep 17 00:00:00 2001 From: Adam Cuculich <46691282+cucupac@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:52:12 -0700 Subject: [PATCH] Reduce repeated recall database work and release 0.1.66 --- app/core/ports/db/concept_repositories.py | 1 + app/core/ports/db/retrieval_repositories.py | 6 +- .../retrieval/concept_seed_retrieval.py | 21 ++- .../retrieval/deterministic_graph_recall.py | 35 ++++- .../runtime/repos/relational/concepts_repo.py | 4 +- .../repos/relational/read_policy_repo.py | 46 ++++-- .../repos/semantic/concept_retrieval_repo.py | 26 +--- .../repos/semantic/keyword_retrieval_repo.py | 9 +- pyproject.toml | 2 +- .../execution/test_concept_show_use_case.py | 22 ++- .../concepts/test_concept_aware_read.py | 2 +- .../execution/test_retrieval_query_batches.py | 144 ++++++++++++++++++ .../test_deterministic_graph_recall.py | 32 +++- 13 files changed, 278 insertions(+), 72 deletions(-) create mode 100644 tests/operations/read/execution/test_retrieval_query_batches.py diff --git a/app/core/ports/db/concept_repositories.py b/app/core/ports/db/concept_repositories.py index 1ec17499..b21ae06b 100644 --- a/app/core/ports/db/concept_repositories.py +++ b/app/core/ports/db/concept_repositories.py @@ -99,6 +99,7 @@ def get_concept_bundles( *, repo_id: str, concept_ids: Sequence[str], + include_evidence: bool, include_lifecycle_events: bool = False, ) -> dict[str, dict[str, Any]]: """Return bundles keyed by concept ID; omit missing concepts.""" diff --git a/app/core/ports/db/retrieval_repositories.py b/app/core/ports/db/retrieval_repositories.py index 4a157b0b..5a1b65d0 100644 --- a/app/core/ports/db/retrieval_repositories.py +++ b/app/core/ports/db/retrieval_repositories.py @@ -60,8 +60,6 @@ def list_concept_keyword_corpus( self, *, repo_id: str, - query_terms: Sequence[str] | None = None, - candidate_limit: int | None = None, ) -> Sequence[dict[str, Any]]: """This method returns active concept text rows for lexical ranking.""" @@ -75,8 +73,8 @@ def list_structural_memory_relation_rows( *, repo_id: str, include_global: bool, - anchor_memory_id: str, + anchor_memory_ids: Sequence[str], kinds: Sequence[str] | None, predicates: Sequence[str], ) -> Sequence[dict[str, Any]]: - """Return structural relation rows touching an anchor plus visible participants.""" + """Return structural relation rows touching the anchors plus visible participants.""" diff --git a/app/core/use_cases/retrieval/concept_seed_retrieval.py b/app/core/use_cases/retrieval/concept_seed_retrieval.py index 7c6b2bf9..7d770044 100644 --- a/app/core/use_cases/retrieval/concept_seed_retrieval.py +++ b/app/core/use_cases/retrieval/concept_seed_retrieval.py @@ -17,7 +17,6 @@ normalize_lexical_text, ) from app.core.ports.db.retrieval_repositories import ( - IConceptKeywordRetrievalRepo, IConceptSemanticRetrievalRepo, ) @@ -25,7 +24,7 @@ def retrieve_concept_seeds( request_data: dict[str, Any], *, - concept_keyword_retrieval: IConceptKeywordRetrievalRepo | None, + concept_corpus: Sequence[dict[str, Any]], concept_semantic_retrieval: IConceptSemanticRetrievalRepo | None, query_vector: Sequence[float], query_model: str | None, @@ -46,7 +45,7 @@ def retrieve_concept_seeds( ) keyword = _keyword_concept_candidates( request_data, - concept_keyword_retrieval=concept_keyword_retrieval, + concept_corpus=concept_corpus, lexical_query=lexical_query, thresholds=thresholds, limit=limit, @@ -81,19 +80,19 @@ def _semantic_concept_candidates( def _keyword_concept_candidates( request_data: dict[str, Any], *, - concept_keyword_retrieval: IConceptKeywordRetrievalRepo | None, + concept_corpus: Sequence[dict[str, Any]], lexical_query: LexicalQuery, thresholds: ThresholdSettings, limit: int, ) -> list[dict[str, Any]]: - if concept_keyword_retrieval is None: - return [] + terms = lexical_query.terms + matching_rows = [ + row + for row in concept_corpus + if any(term in str(row["text"]).lower() for term in terms) + ] candidates = _rank_concept_keyword_candidates( - concept_keyword_retrieval.list_concept_keyword_corpus( - repo_id=str(request_data["repo_id"]), - query_terms=lexical_query.terms, - candidate_limit=max(limit * 10, 100), - ), + (matching_rows or concept_corpus)[: max(limit * 10, 100)], lexical_query=lexical_query, mode=str(request_data["mode"]), limit=limit, diff --git a/app/core/use_cases/retrieval/deterministic_graph_recall.py b/app/core/use_cases/retrieval/deterministic_graph_recall.py index ba0167ed..74d06b9d 100644 --- a/app/core/use_cases/retrieval/deterministic_graph_recall.py +++ b/app/core/use_cases/retrieval/deterministic_graph_recall.py @@ -143,6 +143,7 @@ def build_deterministic_graph_pack( concept_bundles = uow.concepts.get_concept_bundles( repo_id=request.repo_id, concept_ids=list(concept_candidates), + include_evidence=False, ) selected_concepts, concept_trace = _select_concepts( request=request, @@ -473,6 +474,22 @@ def _expand_structural_memory_relations( ) -> dict[str, Any]: """Add canonical structural memory-relation neighbors to recall candidates.""" + rows_by_anchor = defaultdict(list) + rows = uow.read_policy.list_structural_memory_relation_rows( + repo_id=request.repo_id, + include_global=request.include_global, + anchor_memory_ids=tuple(memory_candidates), + kinds=request.kinds or list(MATURE_MEMORY_KIND_VALUES), + predicates=( + *STRUCTURAL_PROBLEM_RELATION_PREDICATES, + *STRUCTURAL_FACT_UPDATE_RELATION_PREDICATES, + ), + ) + for row in rows: + for anchor_id in {row["subject_memory_id"], row["object_memory_id"]}: + if anchor_id in memory_candidates: + rows_by_anchor[anchor_id].append(row) + discovered: list[dict[str, Any]] = [] for anchor_memory_id in tuple(memory_candidates): anchor_entry = memory_candidates[anchor_memory_id] @@ -481,13 +498,11 @@ def _expand_structural_memory_relations( STRUCTURAL_PROBLEM_RELATION_PREDICATES, STRUCTURAL_FACT_UPDATE_RELATION_PREDICATES, ): - rows = uow.read_policy.list_structural_memory_relation_rows( - repo_id=request.repo_id, - include_global=request.include_global, - anchor_memory_id=anchor_memory_id, - kinds=request.kinds or list(MATURE_MEMORY_KIND_VALUES), - predicates=predicates, - ) + rows = [ + row + for row in rows_by_anchor[anchor_memory_id] + if row["predicate"] in predicates + ] for row in rows: key = ( row["subject_memory_id"], @@ -578,11 +593,14 @@ def _discover_concepts( memory_entry["concept_refs"].add(concept_id) memory_entry["link_roles"].add(role) + concept_corpus = uow.concept_keyword_retrieval.list_concept_keyword_corpus( + repo_id=request.repo_id + ) for lane in lanes: query_vector, query_model = embeddings[lane.name] seeds = retrieve_concept_seeds( _lane_request_data(request=request, query=lane.query), - concept_keyword_retrieval=uow.concept_keyword_retrieval, + concept_corpus=concept_corpus, concept_semantic_retrieval=uow.concept_semantic_retrieval, query_vector=query_vector, query_model=query_model, @@ -733,6 +751,7 @@ def _traverse_selected_concepts( uow.concepts.get_concept_bundles( repo_id=request.repo_id, concept_ids=missing_ids, + include_evidence=False, ) ) for neighbor_id in unique_neighbor_ids: diff --git a/app/infrastructure/db/runtime/repos/relational/concepts_repo.py b/app/infrastructure/db/runtime/repos/relational/concepts_repo.py index 36ebba8e..b5bc7ddd 100644 --- a/app/infrastructure/db/runtime/repos/relational/concepts_repo.py +++ b/app/infrastructure/db/runtime/repos/relational/concepts_repo.py @@ -445,6 +445,7 @@ def get_concept_bundle( return self.get_concept_bundles( repo_id=repo_id, concept_ids=[concept.id], + include_evidence=True, include_lifecycle_events=include_lifecycle_events, ).get(concept.id) @@ -453,6 +454,7 @@ def get_concept_bundles( *, repo_id: str, concept_ids: Sequence[str], + include_evidence: bool, include_lifecycle_events: bool = False, ) -> dict[str, dict[str, Any]]: """Fetch candidate bundles in a fixed number of repo-scoped queries.""" @@ -535,7 +537,7 @@ def rows(table, condition): for concept_id in concept_ids_for_event: bundles[concept_id]["lifecycle_events"].append(event) owners[("lifecycle_event", event.id)].update(concept_ids_for_event) - if owners: + if include_evidence and owners: for row in self._unified_evidence_rows( repo_id=repo_id, targets=[(f"concept_{kind}", target_id) for kind, target_id in owners], diff --git a/app/infrastructure/db/runtime/repos/relational/read_policy_repo.py b/app/infrastructure/db/runtime/repos/relational/read_policy_repo.py index 65c12d6b..bbd61cca 100644 --- a/app/infrastructure/db/runtime/repos/relational/read_policy_repo.py +++ b/app/infrastructure/db/runtime/repos/relational/read_policy_repo.py @@ -26,11 +26,14 @@ def list_structural_memory_relation_rows( *, repo_id: str, include_global: bool, - anchor_memory_id: str, + anchor_memory_ids: Sequence[str], kinds: Sequence[str] | None, predicates: Sequence[str], ) -> Sequence[dict[str, Any]]: - """Return active structural relation rows touching an anchor.""" + """Return active structural relation rows touching the anchors in stable order.""" + + if not anchor_memory_ids: + return [] rows = ( self._session.execute( @@ -41,23 +44,41 @@ def list_structural_memory_relation_rows( structural_memory_relations.c.status, structural_memory_relations.c.confidence, structural_memory_relations.c.validated_at, - ).where( + ) + .where( structural_memory_relations.c.repo_id == repo_id, structural_memory_relations.c.predicate.in_(list(predicates)), structural_memory_relations.c.status.in_( list(POSITIVE_LIFECYCLE_STATUSES) ), or_( - structural_memory_relations.c.subject_memory_id - == anchor_memory_id, - structural_memory_relations.c.object_memory_id - == anchor_memory_id, + structural_memory_relations.c.subject_memory_id.in_( + anchor_memory_ids + ), + structural_memory_relations.c.object_memory_id.in_( + anchor_memory_ids + ), ), ) + .order_by( + structural_memory_relations.c.subject_memory_id, + structural_memory_relations.c.predicate, + structural_memory_relations.c.object_memory_id, + ) ) .mappings() .all() ) + visible_ids = self._visible_memory_ids( + repo_id=repo_id, + include_global=include_global, + kinds=kinds, + memory_ids=[ + str(row[field]) + for row in rows + for field in ("subject_memory_id", "object_memory_id") + ], + ) return [ { "subject_memory_id": str(row["subject_memory_id"]), @@ -68,15 +89,8 @@ def list_structural_memory_relation_rows( "validated_at": row["validated_at"], "visible_memory_ids": tuple( sorted( - self._visible_memory_ids( - repo_id=repo_id, - include_global=include_global, - kinds=kinds, - memory_ids=( - str(row["subject_memory_id"]), - str(row["object_memory_id"]), - ), - ) + {str(row["subject_memory_id"]), str(row["object_memory_id"])} + & visible_ids ) ), } diff --git a/app/infrastructure/db/runtime/repos/semantic/concept_retrieval_repo.py b/app/infrastructure/db/runtime/repos/semantic/concept_retrieval_repo.py index 717ad56f..36efb1bf 100644 --- a/app/infrastructure/db/runtime/repos/semantic/concept_retrieval_repo.py +++ b/app/infrastructure/db/runtime/repos/semantic/concept_retrieval_repo.py @@ -33,8 +33,6 @@ def list_concept_keyword_corpus( self, *, repo_id: str, - query_terms: Sequence[str] | None = None, - candidate_limit: int | None = None, ) -> Sequence[dict[str, Any]]: """Return one aggregate lexical document per active concept.""" @@ -70,15 +68,11 @@ def list_concept_keyword_corpus( self._append_claim_parts(repo_id=repo_id, parts_by_id=parts_by_id) self._append_grounding_parts(repo_id=repo_id, parts_by_id=parts_by_id) - rows = [ + return [ {"concept_id": concept_id, "text": " ".join(parts_by_id[concept_id])} for concept_id in ordered_ids if parts_by_id[concept_id] ] - filtered_rows = _filter_candidate_rows(rows, query_terms=query_terms) - if candidate_limit is not None and candidate_limit > 0: - return filtered_rows[: int(candidate_limit)] - return filtered_rows def _append_alias_parts( self, *, repo_id: str, parts_by_id: dict[str, list[str]] @@ -96,7 +90,9 @@ def _append_alias_parts( concepts.c.repo_id == repo_id, concepts.c.status == "active", ) - .order_by(concept_aliases.c.concept_id.asc(), concept_aliases.c.alias.asc()) + .order_by( + concept_aliases.c.concept_id.asc(), concept_aliases.c.alias.asc() + ) ) .mappings() .all() @@ -243,20 +239,6 @@ def _extend_parts(parts: list[str], *values: object) -> None: parts.append(text) -def _filter_candidate_rows( - rows: list[dict[str, Any]], *, query_terms: Sequence[str] | None -) -> list[dict[str, Any]]: - if not query_terms: - return rows - terms = [str(term).strip().lower() for term in query_terms if str(term).strip()] - if not terms: - return rows - matched = [ - row for row in rows if any(term in str(row["text"]).lower() for term in terms) - ] - return matched or rows - - def _locator_scalars(value: object) -> tuple[str, ...]: scalars: list[str] = [] diff --git a/app/infrastructure/db/runtime/repos/semantic/keyword_retrieval_repo.py b/app/infrastructure/db/runtime/repos/semantic/keyword_retrieval_repo.py index ed5544f5..b2292fd2 100644 --- a/app/infrastructure/db/runtime/repos/semantic/keyword_retrieval_repo.py +++ b/app/infrastructure/db/runtime/repos/semantic/keyword_retrieval_repo.py @@ -66,6 +66,13 @@ def _ranked_fts_rows( ts_vector = func.to_tsvector(_ENGLISH_REGCONFIG, memories.c.text) ts_query = func.websearch_to_tsquery(_ENGLISH_REGCONFIG, query_string) + # Keep the broad visibility query from choosing a full text-parsing scan. + keyword_hits = ( + select(memories.c.id) + .where(ts_vector.op("@@")(ts_query)) + .cte("keyword_hits") + .prefix_with("MATERIALIZED") + ) rank = func.ts_rank_cd(ts_vector, ts_query) stmt = ( select( @@ -79,7 +86,7 @@ def _ranked_fts_rows( include_global=include_global, kinds=kinds, ), - ts_vector.op("@@")(ts_query), + memories.c.id.in_(select(keyword_hits.c.id)), ) .order_by(desc(rank), memories.c.id.asc()) ) diff --git a/pyproject.toml b/pyproject.toml index 1948a0e4..1416aa5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "shellbrain" -version = "0.1.65" +version = "0.1.66" description = "Repo-scoped Shellbrain CLI with explicit evidence-backed writes." readme = "README.md" requires-python = ">=3.11" diff --git a/tests/operations/concepts/execution/test_concept_show_use_case.py b/tests/operations/concepts/execution/test_concept_show_use_case.py index 461c5694..db5596e9 100644 --- a/tests/operations/concepts/execution/test_concept_show_use_case.py +++ b/tests/operations/concepts/execution/test_concept_show_use_case.py @@ -370,10 +370,14 @@ def count(*args): event.listen(connection, "before_cursor_execute", count) try: assert ( - uow.concepts.get_concept_bundles(repo_id="repo-a", concept_ids=[]) == {} + uow.concepts.get_concept_bundles( + include_evidence=True, repo_id="repo-a", concept_ids=[] + ) + == {} ) assert statements == [] bundles = uow.concepts.get_concept_bundles( + include_evidence=True, repo_id="repo-a", concept_ids=["third", second_id, "missing", "foreign", first_id], include_lifecycle_events=True, @@ -381,6 +385,21 @@ def count(*args): finally: event.remove(connection, "before_cursor_execute", count) assert len(statements) == 9 + statements.clear() + event.listen(connection, "before_cursor_execute", count) + try: + lean_bundles = uow.concepts.get_concept_bundles( + repo_id="repo-a", + concept_ids=list(bundles), + include_evidence=False, + include_lifecycle_events=True, + ) + finally: + event.remove(connection, "before_cursor_execute", count) + assert len(statements) == 8 + assert lean_bundles == { + key: {**bundle, "evidence": []} for key, bundle in bundles.items() + } assert set(bundles) == {first_id, second_id, "third"} assert bundles[first_id]["relations"] == bundles[second_id]["relations"] assert bundles[first_id]["lifecycle_events"] == [] @@ -401,6 +420,7 @@ def count(*args): repo_id="repo-a", concept_ref=concept_id, include_lifecycle_events=True ) assert bundles == uow.concepts.get_concept_bundles( + include_evidence=True, repo_id="repo-a", concept_ids=list(reversed(bundles)), include_lifecycle_events=True, diff --git a/tests/operations/read/execution/concepts/test_concept_aware_read.py b/tests/operations/read/execution/concepts/test_concept_aware_read.py index aa541f98..18699583 100644 --- a/tests/operations/read/execution/concepts/test_concept_aware_read.py +++ b/tests/operations/read/execution/concepts/test_concept_aware_read.py @@ -189,7 +189,7 @@ def test_concept_keyword_corpus_should_include_anchors_and_exclude_inactive_conc with uow_factory() as uow: rows = uow.concept_keyword_retrieval.list_concept_keyword_corpus( - repo_id="repo-a", query_terms=["deposit"], candidate_limit=10 + repo_id="repo-a" ) rows_by_id = {row["concept_id"]: row for row in rows} diff --git a/tests/operations/read/execution/test_retrieval_query_batches.py b/tests/operations/read/execution/test_retrieval_query_batches.py new file mode 100644 index 00000000..0e3eec1c --- /dev/null +++ b/tests/operations/read/execution/test_retrieval_query_batches.py @@ -0,0 +1,144 @@ +"""Database query reductions preserve visibility, ranking, and relation order.""" + +import pytest +from sqlalchemy import desc, event, func, select + +from app.core.policies.retrieval.expansion import ( + select_structural_memory_relation_neighbors, +) +from app.infrastructure.db.runtime.models.memories import memories +from app.infrastructure.db.runtime.repos.memory_visibility import visible_memory_filters +from app.infrastructure.db.runtime.repos.semantic.keyword_retrieval_repo import ( + _ENGLISH_REGCONFIG, + _websearch_or_query, +) + + +def test_structural_batch_has_two_queries_and_stable_visible_neighbors( + uow_factory, + seed_read_memory, + seed_structural_memory_relation, +): + for memory_id, repo_id in [("a", "repo-a"), ("b", "repo-a"), ("hidden", "repo-b")]: + seed_read_memory( + memory_id=memory_id, + repo_id=repo_id, + scope="repo", + kind="fact", + text_value="routing state", + ) + for relation_id, predicate, target, status in [ + ("r1", "superseded_by", "b", "active"), + ("r2", "explained_by_change", "b", "active"), + ("r3", "superseded_by", "hidden", "active"), + ("r4", "explained_by_change", "hidden", "archived"), + ]: + seed_structural_memory_relation( + relation_id=relation_id, + repo_id="repo-a", + subject_memory_id="a", + predicate=predicate, + object_memory_id=target, + status=status, + ) + with uow_factory() as uow: + statements = [] + connection = uow.read_policy._session.connection() + + def count(*args): + statements.append(args[2]) + + event.listen(connection, "before_cursor_execute", count) + try: + args = dict( + repo_id="repo-a", + include_global=False, + kinds=["fact"], + predicates=["explained_by_change", "superseded_by"], + ) + assert ( + uow.read_policy.list_structural_memory_relation_rows( + **args, anchor_memory_ids=[] + ) + == [] + ) + assert statements == [] + rows = uow.read_policy.list_structural_memory_relation_rows( + **args, anchor_memory_ids=["b", "a", "a"] + ) + assert len(statements) == 2 + assert [(r["predicate"], r["object_memory_id"]) for r in rows] == [ + ("explained_by_change", "b"), + ("superseded_by", "b"), + ("superseded_by", "hidden"), + ] + assert all("hidden" not in r["visible_memory_ids"] for r in rows) + assert select_structural_memory_relation_neighbors( + rows, anchor_memory_id="a" + ) == [ + { + "memory_id": "b", + "relation_type": "superseded_by", + "expansion_type": "fact_update", + } + ] + assert rows == uow.read_policy.list_structural_memory_relation_rows( + **args, anchor_memory_ids=["a", "b"] + ) + finally: + event.remove(connection, "before_cursor_execute", count) + + +@pytest.mark.parametrize("include_global", [False, True]) +@pytest.mark.parametrize( + "terms", [["router"], ["router", "discovery"], ["QZX917"], ["découverte"]] +) +def test_indexed_prefilter_preserves_full_text_rank_and_visibility( + uow_factory, + seed_read_memory, + include_global, + terms, +): + for memory_id, repo_id, scope, kind, status, text in [ + ("a", "repo-a", "repo", "fact", "active", "router discovery"), + ("b", "repo-a", "repo", "fact", "maybe_stale", "router discovery"), + ("c", "repo-b", "global", "fact", "active", "router router"), + ("d", "repo-b", "repo", "fact", "active", "router router router"), + ("e", "repo-a", "repo", "problem", "active", "router"), + ("f", "repo-a", "repo", "fact", "archived", "router"), + ("g", "repo-a", "repo", "fact", "active", "découverte du routeur"), + ]: + seed_read_memory( + memory_id=memory_id, + repo_id=repo_id, + scope=scope, + kind=kind, + status=status, + text_value=text, + ) + with uow_factory() as uow: + vector = func.to_tsvector(_ENGLISH_REGCONFIG, memories.c.text) + query = func.websearch_to_tsquery( + _ENGLISH_REGCONFIG, _websearch_or_query(terms) + ) + expected = list( + uow.keyword_retrieval._session.execute( + select(memories.c.id) + .where( + *visible_memory_filters( + repo_id="repo-a", include_global=include_global, kinds=["fact"] + ), + vector.op("@@")(query), + ) + .order_by(desc(func.ts_rank_cd(vector, query)), memories.c.id) + .limit(2) + ).scalars() + ) + actual = uow.keyword_retrieval._ranked_fts_rows( + repo_id="repo-a", + include_global=include_global, + kinds=["fact"], + query_terms=terms, + candidate_limit=2, + ) + assert [row["memory_id"] for row in actual] == expected diff --git a/tests/operations/recall/execution/test_deterministic_graph_recall.py b/tests/operations/recall/execution/test_deterministic_graph_recall.py index 79c190d5..2aefceec 100644 --- a/tests/operations/recall/execution/test_deterministic_graph_recall.py +++ b/tests/operations/recall/execution/test_deterministic_graph_recall.py @@ -195,8 +195,8 @@ def test_recall_preserves_case_pairings(monkeypatch, seed_both_endpoints, exclud ), } for row in relations - if kw["anchor_memory_id"] - in (row["subject_memory_id"], row["object_memory_id"]) + if set(kw["anchor_memory_ids"]) + & {row["subject_memory_id"], row["object_memory_id"]} and row["predicate"] in kw["predicates"] ], ) @@ -401,7 +401,7 @@ def find_concepts_for_memory_ids(self, **kwargs): del kwargs return [] - def get_concept_bundles(self, *, repo_id, concept_ids): + def get_concept_bundles(self, *, repo_id, concept_ids, include_evidence): return { key: bundle for key in concept_ids @@ -554,7 +554,7 @@ def list_structural_memory_relation_rows(self, **kwargs): class _StructuralFakeReadPolicy: def list_structural_memory_relation_rows(self, **kwargs): - if kwargs["anchor_memory_id"] != "mem-direct": + if "mem-direct" not in kwargs["anchor_memory_ids"]: return [] if "explained_by_change" not in set(kwargs["predicates"]): return [] @@ -652,7 +652,9 @@ def test_historical_facets_do_not_lower_active_concept_rank( request=request, concept_candidates=candidates, concept_bundles=uow.concepts.get_concept_bundles( - repo_id=request.repo_id, concept_ids=list(candidates) + repo_id=request.repo_id, + concept_ids=list(candidates), + include_evidence=False, ), ) get_bundle = uow.concepts.get_concept_bundle @@ -678,7 +680,9 @@ def with_history(**kwargs): request=request, concept_candidates=candidates, concept_bundles=uow.concepts.get_concept_bundles( - repo_id=request.repo_id, concept_ids=list(candidates) + repo_id=request.repo_id, + concept_ids=list(candidates), + include_evidence=False, ), ) assert [entry["bundle"]["concept"].slug for entry in after] == ["db-admin"] @@ -958,3 +962,19 @@ def test_learning_limit_bounds_all_memory_sources(limit): request = MemoryReadRequest(repo_id="repo-a", query="TimeoutError", limit=limit) pack = build_deterministic_graph_pack(request=request, uow=_FakeUow()) assert len(pack["memories"]) == limit + + +def test_concept_corpus_is_read_once_per_recall(monkeypatch): + uow = _FakeUow() + calls = [] + monkeypatch.setattr( + uow.concept_keyword_retrieval, + "list_concept_keyword_corpus", + lambda **kwargs: calls.append(kwargs) or [], + ) + request = _request(query="TimeoutError in app/core/settings.py") + first = build_deterministic_graph_pack(request=request, uow=uow) + assert len(first["query_lanes"]) > 1 + assert calls == [{"repo_id": "repo-a"}] + build_deterministic_graph_pack(request=request, uow=uow) + assert len(calls) == 2