This commit is contained in:
1 parent
0e1a2ad008
commit
34f4923566
28 files changed
+3932
-81
No files matched your search
@@ -0,0 +1,96 @@
|
||||
from gewu.adapters.sqlite_ingest_reviews import SQLiteIngestReviewStore
|
||||
from gewu.domain.contracts import (
|
||||
IngestReviewCreate,
|
||||
JevFacetOptionScore,
|
||||
JevFacetResolution,
|
||||
JevFacetScore,
|
||||
Locator,
|
||||
)
|
||||
|
||||
|
||||
def test_review_store_round_trips_jev_facet_arrays_and_idempotent_create(tmp_path) -> None:
|
||||
store = SQLiteIngestReviewStore(tmp_path / "reviews.sqlite3")
|
||||
item = IngestReviewCreate(
|
||||
review_id="review-satie-label-1",
|
||||
project_id="dome-performance",
|
||||
run_id="run-satie-label-1",
|
||||
source_revision_id="source-revision-satie-1",
|
||||
extraction_id="a" * 64,
|
||||
locator=Locator(
|
||||
kind="pdf_page_span",
|
||||
block_id="block-satie-1",
|
||||
start=0,
|
||||
end=5,
|
||||
page=1,
|
||||
),
|
||||
excerpt="quote",
|
||||
lens="technical",
|
||||
summary="Normalize a SATIE spatial-audio tag",
|
||||
relevance_rationale="The candidate term should reuse the project vocabulary.",
|
||||
candidate_facet_labels=(
|
||||
{
|
||||
"facet": "tags",
|
||||
"value": "spatial sound",
|
||||
"definition": "Audio techniques that place sound in immersive spatial configurations.",
|
||||
},
|
||||
),
|
||||
)
|
||||
scores = (JevFacetScore(facet="tags", value="spatial sound", probability=0.94),)
|
||||
resolutions = (
|
||||
JevFacetResolution(
|
||||
facet="tags",
|
||||
value="spatial sound",
|
||||
fit_probability=0.94,
|
||||
selected_label_id="tag-spatial-audio",
|
||||
selected_label="spatial audio",
|
||||
selected_new_label=False,
|
||||
new_probability=0.04,
|
||||
choice_confidence=0.93,
|
||||
option_probabilities=(
|
||||
JevFacetOptionScore(
|
||||
label_id="tag-spatial-audio",
|
||||
label="spatial audio",
|
||||
probability=0.93,
|
||||
),
|
||||
JevFacetOptionScore(
|
||||
label_id=None,
|
||||
label="spatial sound",
|
||||
probability=0.07,
|
||||
),
|
||||
),
|
||||
rank=1,
|
||||
added_to_project=False,
|
||||
),
|
||||
)
|
||||
|
||||
created = store.create(
|
||||
item,
|
||||
submitted_by="agent-pilot",
|
||||
jev_relation_probability=0.97,
|
||||
jev_lens="technical",
|
||||
jev_lens_confidence=0.98,
|
||||
jev_model="typesafe/jev-1.13",
|
||||
jev_facet_scores=scores,
|
||||
jev_facet_resolutions=resolutions,
|
||||
)
|
||||
|
||||
assert created.jev_facet_scores == scores
|
||||
assert created.jev_facet_resolutions == resolutions
|
||||
assert created.jev_facet_resolutions[0].selected_label == "spatial audio"
|
||||
assert store.get(item.review_id) == created
|
||||
assert store.list_items(
|
||||
item.project_id, status="needs-user-review", limit=10, offset=0
|
||||
) == [created]
|
||||
assert (
|
||||
store.create(
|
||||
item,
|
||||
submitted_by="agent-pilot",
|
||||
jev_relation_probability=0.97,
|
||||
jev_lens="technical",
|
||||
jev_lens_confidence=0.98,
|
||||
jev_model="typesafe/jev-1.13",
|
||||
jev_facet_scores=scores,
|
||||
jev_facet_resolutions=resolutions,
|
||||
)
|
||||
== created
|
||||
)
|
||||
@@ -16,6 +16,7 @@ from mcp.types import TextContent
|
||||
from gewu.adapters.git_repository import GitCanonicalRepository
|
||||
from gewu.adapters.source_extractor import LocalSourceExtractor
|
||||
from gewu.adapters.sqlite_identity import SQLiteIdentityStore
|
||||
from gewu.adapters.sqlite_ingest_reviews import SQLiteIngestReviewStore
|
||||
from gewu.adapters.url_fetch import PublicUrlFetcher
|
||||
from gewu.application.acceptance import AcceptanceService
|
||||
from gewu.application.context import ValidationContext
|
||||
@@ -176,6 +177,7 @@ def test_mcp_source_to_citation_vertical_slice(tmp_path: Path) -> None:
|
||||
acceptance_service=acceptance,
|
||||
context_provider=lambda: context,
|
||||
url_fetcher=_fixture_fetcher(),
|
||||
ingest_review_store=SQLiteIngestReviewStore(tmp_path / "ingest-reviews.sqlite3"),
|
||||
)
|
||||
async with app.router.lifespan_context(app):
|
||||
async with httpx2.AsyncClient(
|
||||
@@ -193,6 +195,7 @@ def test_mcp_source_to_citation_vertical_slice(tmp_path: Path) -> None:
|
||||
"node_history",
|
||||
"get_project_context",
|
||||
"get_source",
|
||||
"ingest_review_create",
|
||||
"source_upload_begin",
|
||||
"source_capture_text",
|
||||
"source_capture_url",
|
||||
@@ -201,6 +204,7 @@ def test_mcp_source_to_citation_vertical_slice(tmp_path: Path) -> None:
|
||||
"source_upload_finalize",
|
||||
"source_extract",
|
||||
"source_extraction_block",
|
||||
"proposal_prepare",
|
||||
"proposal_evaluate",
|
||||
"proposal_submit",
|
||||
"proposal_status",
|
||||
@@ -221,6 +225,62 @@ def test_mcp_source_to_citation_vertical_slice(tmp_path: Path) -> None:
|
||||
PROJECTS["dome-performance"].key_questions
|
||||
)
|
||||
assert "autonomy_policy_id" not in lens_result["lens"]
|
||||
draft = proposal().model_dump(mode="json")
|
||||
draft.pop("payload_digest")
|
||||
for operation in draft["operations"]:
|
||||
node = operation.get("node")
|
||||
if node is None:
|
||||
continue
|
||||
for group in ("claims", "assumptions", "limitations"):
|
||||
for claim in node.get(group, []):
|
||||
claim["assessment"].pop("assertion_digest")
|
||||
claim["assessment"].pop("evidence_digests")
|
||||
for evidence in claim["evidence"]:
|
||||
evidence.pop("assertion_path")
|
||||
evidence.pop("assertion_digest")
|
||||
prepared_result = await mcp.call_tool(
|
||||
"proposal_prepare",
|
||||
{"proposal_json": json.dumps(draft, ensure_ascii=False)},
|
||||
)
|
||||
assert not prepared_result.is_error, prepared_result.content
|
||||
prepared_payload = json.loads(_text_payload(prepared_result))
|
||||
assert prepared_payload["status"] == "prepared"
|
||||
prepared_proposal = ProposalRevision.model_validate_json(
|
||||
prepared_payload["proposal_json"], strict=True
|
||||
)
|
||||
assert prepared_payload["payload_digest"] == prepared_proposal.payload_digest
|
||||
assert prepared_proposal.payload_digest == proposal_payload_digest(
|
||||
prepared_proposal
|
||||
)
|
||||
invalid_draft = json.loads(json.dumps(draft))
|
||||
invalid_evidence = invalid_draft["operations"][1]["node"]["claims"][0][
|
||||
"evidence"
|
||||
][0]
|
||||
invalid_evidence["locator"]["kind"] = "text_span"
|
||||
invalid_evidence["locator"]["page"] = 1
|
||||
invalid_result = await mcp.call_tool(
|
||||
"proposal_prepare",
|
||||
{"proposal_json": json.dumps(invalid_draft, ensure_ascii=False)},
|
||||
)
|
||||
assert not invalid_result.is_error, invalid_result.content
|
||||
invalid_payload = json.loads(_text_payload(invalid_result))
|
||||
assert invalid_payload["status"] == "invalid"
|
||||
assert any(
|
||||
issue["path"].endswith("locator")
|
||||
and "PDF locators" in issue["message"]
|
||||
for issue in invalid_payload["issues"]
|
||||
)
|
||||
invalid_review = await mcp.call_tool(
|
||||
"ingest_review_create", {"review_json": "{}"}
|
||||
)
|
||||
assert not invalid_review.is_error, invalid_review.content
|
||||
review_payload = json.loads(_text_payload(invalid_review))
|
||||
assert review_payload["status"] == "invalid"
|
||||
assert {issue["path"] for issue in review_payload["issues"]} >= {
|
||||
"review_id",
|
||||
"extraction_id",
|
||||
"relevance_rationale",
|
||||
}
|
||||
denied_lens = await mcp.call_tool(
|
||||
"get_project_context", {"project_id": "interactive-video"}
|
||||
)
|
||||
@@ -238,6 +298,11 @@ def test_mcp_source_to_citation_vertical_slice(tmp_path: Path) -> None:
|
||||
{"proposal_json": proposal().model_dump_json()},
|
||||
)
|
||||
assert viewer_proposal.is_error
|
||||
viewer_prepare = await viewer_mcp.call_tool(
|
||||
"proposal_prepare",
|
||||
{"proposal_json": json.dumps(draft, ensure_ascii=False)},
|
||||
)
|
||||
assert viewer_prepare.is_error
|
||||
viewer_upload = await viewer_mcp.call_tool(
|
||||
"source_upload_begin",
|
||||
{
|
||||
@@ -549,9 +614,28 @@ def test_mcp_source_to_citation_vertical_slice(tmp_path: Path) -> None:
|
||||
},
|
||||
)
|
||||
assert not html_block_result.is_error
|
||||
html_block = _text_payload(html_block_result)
|
||||
assert "Exact HTML statement." in html_block
|
||||
assert "ignoreInjection" not in html_block
|
||||
html_block = json.loads(_text_payload(html_block_result))
|
||||
assert "Exact HTML statement." in html_block["normalized_text"]
|
||||
assert "original_text" not in html_block
|
||||
assert "normalization_map" not in html_block
|
||||
mapped_html_result = await mcp.call_tool(
|
||||
"source_extraction_block",
|
||||
{
|
||||
"project_id": "dome-performance",
|
||||
"extraction_id": html_extraction["extraction_id"],
|
||||
"block_id": html_extraction["blocks"][0]["block_id"],
|
||||
"offset": 0,
|
||||
"length": 500,
|
||||
"include_original_mapping": True,
|
||||
},
|
||||
)
|
||||
assert not mapped_html_result.is_error
|
||||
mapped_html = json.loads(_text_payload(mapped_html_result))
|
||||
assert mapped_html["original_text"]
|
||||
assert len(mapped_html["normalization_map"]) == len(
|
||||
mapped_html["normalized_text"]
|
||||
)
|
||||
assert "ignoreInjection" not in html_block["normalized_text"]
|
||||
captured_pdf = await mcp.call_tool(
|
||||
"source_capture_url",
|
||||
{
|
||||
@@ -591,6 +675,7 @@ def test_mcp_source_to_citation_vertical_slice(tmp_path: Path) -> None:
|
||||
pdf_block = json.loads(_text_payload(pdf_block_result))
|
||||
assert pdf_block["page"] == 1
|
||||
assert "Archived PDF evidence." in pdf_block["normalized_text"]
|
||||
assert "normalization_map" not in pdf_block
|
||||
malformed_pdf = await mcp.call_tool(
|
||||
"source_capture_url",
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ from gewu.domain.validation import (
|
||||
assertion_digest,
|
||||
edge_assertion_digest,
|
||||
evidence_digest,
|
||||
prepare_proposal_digests,
|
||||
proposal_payload_digest,
|
||||
validate_proposal,
|
||||
)
|
||||
@@ -51,6 +52,33 @@ def test_supported_exact_citation_and_both_bundle_policies_are_eligible() -> Non
|
||||
assert set(report.affected_policy_ids) == {"dome-policy", "shared"}
|
||||
|
||||
|
||||
def test_proposal_digest_preparation_fills_all_dependent_claim_digests() -> None:
|
||||
draft = proposal().model_dump(mode="json")
|
||||
draft.pop("payload_digest")
|
||||
for operation in draft["operations"]:
|
||||
node = operation.get("node")
|
||||
if node is None:
|
||||
continue
|
||||
for group in ("claims", "assumptions", "limitations"):
|
||||
for claim in node.get(group, []):
|
||||
claim["assessment"].pop("assertion_digest")
|
||||
claim["assessment"].pop("evidence_digests")
|
||||
for evidence in claim["evidence"]:
|
||||
evidence.pop("assertion_path")
|
||||
evidence.pop("assertion_digest")
|
||||
|
||||
prepared = prepare_proposal_digests(draft)
|
||||
|
||||
assert prepared.payload_digest == proposal_payload_digest(prepared)
|
||||
assert prepared.operations[1].node is not None
|
||||
claim = prepared.operations[1].node.claims[0]
|
||||
assert claim.evidence[0].assertion_path == "claims[0].text"
|
||||
assert claim.evidence[0].assertion_digest == assertion_digest(claim.text)
|
||||
assert claim.assessment.assertion_digest == assertion_digest(claim.text)
|
||||
assert claim.assessment.evidence_digests == (evidence_digest(claim.evidence[0]),)
|
||||
assert _validate(prepared).status == "eligible"
|
||||
|
||||
|
||||
def test_transcript_timestamp_locator_must_match_the_cited_cue() -> None:
|
||||
transcript_bytes = (
|
||||
b"WEBVTT\n\n"
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from gewu.adapters.openrouter_jev import JevAssessment
|
||||
from gewu.adapters.sqlite_facet_labels import SQLiteFacetLabelIndex
|
||||
from gewu.domain.contracts import (
|
||||
FacetLabelProposal,
|
||||
IngestReviewCreate,
|
||||
JevFacetResolution,
|
||||
JevFacetScore,
|
||||
Locator,
|
||||
ProjectFacetLabel,
|
||||
)
|
||||
from gewu.transports.mcp_server import _promote_jev_labels
|
||||
|
||||
|
||||
class _FixedEmbeddings:
|
||||
dimensions = 3
|
||||
model_id = "test-embedding-model"
|
||||
artifact_sha256 = "a" * 64
|
||||
|
||||
def embed_queries(self, texts: tuple[str, ...]) -> tuple[tuple[float, ...], ...]:
|
||||
return tuple((1.0, 0.0, 0.0) for _ in texts)
|
||||
|
||||
def embed_passages(self, texts: tuple[str, ...]) -> tuple[tuple[float, ...], ...]:
|
||||
return tuple((1.0, 0.0, 0.0) for _ in texts)
|
||||
|
||||
|
||||
class _ProjectRegistry:
|
||||
def __init__(self) -> None:
|
||||
self.added: list[ProjectFacetLabel] = []
|
||||
|
||||
def add_facet_labels(self, project_id: str, labels: tuple[ProjectFacetLabel, ...]):
|
||||
assert project_id == "dome-performance"
|
||||
self.added.extend(labels)
|
||||
|
||||
|
||||
def _review(candidate: str = "immersive sound field") -> IngestReviewCreate:
|
||||
return IngestReviewCreate(
|
||||
review_id="review-facet-promotion-1",
|
||||
project_id="dome-performance",
|
||||
run_id="run-facet-promotion-1",
|
||||
source_revision_id="source-revision-facet-promotion-1",
|
||||
extraction_id="b" * 64,
|
||||
locator=Locator(kind="text_span", block_id="block-1", start=0, end=5),
|
||||
excerpt="quote",
|
||||
lens="technical",
|
||||
summary="A reusable spatial-audio technique",
|
||||
relevance_rationale="This is a direct, reusable project technique.",
|
||||
candidate_facet_labels=(
|
||||
FacetLabelProposal(
|
||||
facet="tags",
|
||||
value=candidate,
|
||||
definition="Audio techniques that render sound in immersive spatial fields.",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _assessment(
|
||||
value: str = "immersive sound field",
|
||||
*,
|
||||
fit: float = 0.97,
|
||||
new: float = 0.96,
|
||||
confidence: float = 0.93,
|
||||
) -> JevAssessment:
|
||||
return JevAssessment(
|
||||
relation_probability=0.98,
|
||||
lens="technical",
|
||||
lens_confidence=0.99,
|
||||
model="typesafe/jev-test",
|
||||
facet_scores=(JevFacetScore(facet="tags", value=value, probability=fit),),
|
||||
facet_resolutions=(
|
||||
JevFacetResolution(
|
||||
facet="tags",
|
||||
value=value,
|
||||
fit_probability=fit,
|
||||
selected_new_label=True,
|
||||
new_probability=new,
|
||||
choice_confidence=confidence,
|
||||
rank=1,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_high_confidence_top_rank_new_label_is_added_and_embedded(
|
||||
tmp_path, monkeypatch
|
||||
) -> None:
|
||||
monkeypatch.setenv("GEWU_FACET_LABEL_FIT_THRESHOLD", "0.90")
|
||||
monkeypatch.setenv("GEWU_FACET_LABEL_NEW_THRESHOLD", "0.90")
|
||||
monkeypatch.setenv("GEWU_FACET_LABEL_CONFIDENCE_THRESHOLD", "0.80")
|
||||
registry = _ProjectRegistry()
|
||||
embeddings = _FixedEmbeddings()
|
||||
label_index = SQLiteFacetLabelIndex(tmp_path / "labels.sqlite3")
|
||||
|
||||
resolutions = _promote_jev_labels(
|
||||
item=_review(),
|
||||
assessment=_assessment(),
|
||||
project_id="dome-performance",
|
||||
project_registry=registry,
|
||||
facet_label_index=label_index,
|
||||
embedding_provider=embeddings,
|
||||
existing_catalog=(),
|
||||
semantic_search_ready=True,
|
||||
)
|
||||
|
||||
assert len(registry.added) == 1
|
||||
assert registry.added[0].canonical_label == "immersive sound field"
|
||||
assert resolutions[0].rank == 1
|
||||
assert resolutions[0].added_to_project
|
||||
cached = label_index.closest(
|
||||
project_id="dome-performance",
|
||||
facet="tags",
|
||||
query="spatial audio rendering",
|
||||
labels=tuple(registry.added),
|
||||
embeddings=embeddings,
|
||||
limit=1,
|
||||
)
|
||||
assert cached[0].canonical_label == "immersive sound field"
|
||||
|
||||
|
||||
def test_matching_existing_label_is_never_promoted_as_new(
|
||||
tmp_path, monkeypatch
|
||||
) -> None:
|
||||
monkeypatch.setenv("GEWU_FACET_LABEL_FIT_THRESHOLD", "0.90")
|
||||
monkeypatch.setenv("GEWU_FACET_LABEL_NEW_THRESHOLD", "0.90")
|
||||
monkeypatch.setenv("GEWU_FACET_LABEL_CONFIDENCE_THRESHOLD", "0.80")
|
||||
registry = _ProjectRegistry()
|
||||
existing = ProjectFacetLabel(
|
||||
label_id="tag-spatial-audio",
|
||||
facet="tags",
|
||||
canonical_label="spatial audio",
|
||||
definition="Audio techniques for placing or rendering sound spatially.",
|
||||
created_at=datetime(2026, 1, 1, tzinfo=UTC),
|
||||
)
|
||||
|
||||
resolutions = _promote_jev_labels(
|
||||
item=_review("spatial audio"),
|
||||
assessment=_assessment("spatial audio"),
|
||||
project_id="dome-performance",
|
||||
project_registry=registry,
|
||||
facet_label_index=SQLiteFacetLabelIndex(tmp_path / "labels.sqlite3"),
|
||||
embedding_provider=_FixedEmbeddings(),
|
||||
existing_catalog=(existing,),
|
||||
semantic_search_ready=True,
|
||||
)
|
||||
|
||||
assert registry.added == []
|
||||
assert not resolutions[0].added_to_project
|
||||
Reference in new issue
Block a user