feat(evidence): contribute to semantic stages (#42)
This commit is contained in:
@@ -8,6 +8,7 @@ import pytest
|
||||
|
||||
from tht.decisions import DecisionRecord
|
||||
from tht.evidence import (
|
||||
EvidenceSearchContext,
|
||||
acquire,
|
||||
active_searcher,
|
||||
build_preprocessing_pipeline,
|
||||
@@ -16,6 +17,7 @@ from tht.evidence import (
|
||||
discover,
|
||||
project_session,
|
||||
resolve_citation,
|
||||
search_evidence,
|
||||
)
|
||||
from tht.evidence.contracts import (
|
||||
AcquiredDocument,
|
||||
@@ -25,6 +27,7 @@ from tht.evidence.contracts import (
|
||||
)
|
||||
from tht.evidence.corpus.models import CanonicalDocument, CorpusManifest
|
||||
from tht.evidence.corpus.store import CorpusStore
|
||||
from tht.ports.vector import VectorReadUnavailable
|
||||
from tht.session.models import Candidate, SchemaLinking
|
||||
|
||||
|
||||
@@ -186,13 +189,141 @@ def test_retrieval_entries_preserve_hit_order_and_existing_projection_shape():
|
||||
SimpleNamespace(label="Second", status="reviewed", content="abcdefgh"),
|
||||
SimpleNamespace(label="First", status=None, content="12345678"),
|
||||
]
|
||||
|
||||
assert build_retrieval_entries(hits, excerpt_chars=5) == [
|
||||
{"title": "Second", "status": "reviewed", "excerpt": "abcde"},
|
||||
{"title": "First", "status": None, "excerpt": "12345"},
|
||||
]
|
||||
|
||||
|
||||
def test_typed_search_renders_one_stable_query_and_groups_fragments_by_evidence_unit():
|
||||
"""Removing context rendering, hard filters, or grouping changes this public result."""
|
||||
class Searcher:
|
||||
vector_generation = "gen:" + "a" * 32
|
||||
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
def search(self, embedding, **kwargs):
|
||||
self.calls.append((embedding, kwargs))
|
||||
return [
|
||||
SimpleNamespace(
|
||||
id="fragment:second", similarity=0.7, content="second excerpt",
|
||||
title="Pediatric range", metadata={
|
||||
"evidence_id": "evidence:pediatric-range", "evidence_kind": "formula",
|
||||
"document_id": "doc:range", "ordinal": 1,
|
||||
"source_uri": "file:///curated/pediatric-range.md",
|
||||
"provenance": {"source_file": "source/range.md"},
|
||||
},
|
||||
),
|
||||
SimpleNamespace(
|
||||
id="fragment:first", similarity=0.9, content="first excerpt",
|
||||
title="Pediatric range", metadata={
|
||||
"evidence_id": "evidence:pediatric-range", "evidence_kind": "formula",
|
||||
"document_id": "doc:range", "ordinal": 0,
|
||||
"source_uri": "file:///curated/pediatric-range.md",
|
||||
"provenance": {"source_file": "source/range.md"},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
class Embedder:
|
||||
def __init__(self):
|
||||
self.queries = []
|
||||
|
||||
def embed_query(self, query):
|
||||
self.queries.append(query)
|
||||
return [0.25]
|
||||
|
||||
searcher = Searcher()
|
||||
embedder = Embedder()
|
||||
outcome = search_evidence(
|
||||
" Pazienti \"Età" + "\r\n" + " pediatrica ",
|
||||
"schema_linking",
|
||||
EvidenceSearchContext(
|
||||
concepts=("pediatrica", "pediatrica", " Età "),
|
||||
tables=("clinical.patient",),
|
||||
columns=("clinical.patient.Age",),
|
||||
required_kinds=("formula",),
|
||||
required_concepts=("Età",),
|
||||
required_tables=("clinical.patient",),
|
||||
required_columns=("clinical.patient.Age",),
|
||||
),
|
||||
searcher=searcher,
|
||||
embedder=embedder,
|
||||
)
|
||||
|
||||
rendered = (
|
||||
"Domanda: Pazienti \"Età\n pediatrica\n"
|
||||
"Concetti: Età, pediatrica\n"
|
||||
"Tabelle: clinical.patient\n"
|
||||
"Colonne: clinical.patient.Age"
|
||||
)
|
||||
assert embedder.queries == [rendered]
|
||||
assert searcher.calls == [([0.25], {
|
||||
"top_n": 10,
|
||||
"kinds": ["evidence"],
|
||||
"query_text": rendered,
|
||||
"metadata_filter": {
|
||||
"purpose": "schema_linking",
|
||||
"required_kinds": ["formula"],
|
||||
"required_concepts": ["Età"],
|
||||
"required_tables": ["clinical.patient"],
|
||||
"required_columns": ["clinical.patient.Age"],
|
||||
},
|
||||
})]
|
||||
assert outcome.status == "available"
|
||||
assert outcome.vector_generation == "gen:" + "a" * 32
|
||||
assert [(item.evidence_id, item.excerpts, item.provenance, item.citation) for item in outcome.results] == [
|
||||
("evidence:pediatric-range", ("first excerpt", "second excerpt"),
|
||||
{"source_file": "source/range.md"}, "file:///curated/pediatric-range.md"),
|
||||
]
|
||||
|
||||
|
||||
def test_typed_search_reports_vector_errors_as_unavailable_not_empty_results():
|
||||
class UnavailableSearcher:
|
||||
vector_generation = "gen:" + "a" * 32
|
||||
|
||||
def search(self, _embedding, **_kwargs):
|
||||
raise VectorReadUnavailable("reader unavailable")
|
||||
|
||||
outcome = search_evidence(
|
||||
"question", "rewriting", EvidenceSearchContext(),
|
||||
searcher=UnavailableSearcher(), embedder=SimpleNamespace(embed_query=lambda _query: [0.25]),
|
||||
)
|
||||
|
||||
assert outcome.status == "unavailable"
|
||||
assert outcome.code == "vector_unavailable"
|
||||
assert outcome.results == ()
|
||||
|
||||
|
||||
def test_typed_search_without_an_active_generation_is_unavailable_not_an_empty_search():
|
||||
outcome = search_evidence(
|
||||
"question", "rewriting", EvidenceSearchContext(),
|
||||
searcher=SimpleNamespace(), embedder=SimpleNamespace(embed_query=lambda _query: [0.25]),
|
||||
)
|
||||
|
||||
assert outcome.status == "unavailable"
|
||||
assert outcome.code == "active_corpus_unavailable"
|
||||
|
||||
|
||||
def test_typed_search_reports_a_malformed_fragment_payload_as_unavailable():
|
||||
class Searcher:
|
||||
vector_generation = "gen:" + "a" * 32
|
||||
|
||||
def search(self, _embedding, **_kwargs):
|
||||
return [SimpleNamespace(
|
||||
id="fragment:bad", similarity=0.5, title="Bad", content="bad",
|
||||
metadata={"evidence_id": "evidence:bad"},
|
||||
)]
|
||||
|
||||
outcome = search_evidence(
|
||||
"question", "rewriting", EvidenceSearchContext(),
|
||||
searcher=Searcher(), embedder=SimpleNamespace(embed_query=lambda _query: [0.25]),
|
||||
)
|
||||
|
||||
assert outcome.status == "unavailable"
|
||||
assert outcome.code == "evidence_search_unavailable"
|
||||
|
||||
def _canonical_store(root, evidence_id):
|
||||
content = f"# {evidence_id}\n"
|
||||
digest = hashlib.sha256(content.encode()).hexdigest()
|
||||
|
||||
Reference in New Issue
Block a user