feat(presentation): project Lefevre into a content-explorer snapshot
Map the Lefevre infospace to a schema 0.1 snapshot and presentation manifest. Category kinds, Gutenberg rights, and chunk provenance stay on the projection. Relation blocks are included only when both ends match a stored entity title. Assistant: grok Assistant-Session: 01a137af-2bea-7940-9a65-acd892344a72
This commit is contained in:
parent
f001b0c1cf
commit
fd0dc5fc02
9 changed files with 1421 additions and 22 deletions
170
tests/test_content_explorer_projection.py
Normal file
170
tests/test_content_explorer_projection.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
"""Projection of the Lefevre infospace into a content-explorer presentation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import json
|
||||
|
||||
import yaml
|
||||
|
||||
from infospace_bench.markdown_adapter import extract_section_text, parse_markdown_artifact
|
||||
from infospace_bench.presentation import display_title, project_infospace
|
||||
from infospace_bench.semantics import slugify
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
LEFEVRE = ROOT / "infospaces" / "lefevre-reminiscences-of-a-stock-operator"
|
||||
CONTENT_EXPLORER = Path("/home/worsch/content-explorer")
|
||||
sys.path.insert(0, str(CONTENT_EXPLORER))
|
||||
|
||||
from content_explorer.contract import validate_documents # noqa: E402
|
||||
|
||||
|
||||
def test_lefevre_projection_matches_stored_artifacts_and_contract() -> None:
|
||||
first = project_infospace(LEFEVRE)
|
||||
second = project_infospace(LEFEVRE)
|
||||
snapshot = first.snapshot
|
||||
manifest = first.manifest
|
||||
|
||||
assert [item["id"] for item in snapshot["entities"]] == [
|
||||
item["id"] for item in second.snapshot["entities"]
|
||||
]
|
||||
assert [item["id"] for item in snapshot["relations"]] == [
|
||||
item["id"] for item in second.snapshot["relations"]
|
||||
]
|
||||
|
||||
index = yaml.safe_load((LEFEVRE / "artifacts" / "index.yaml").read_text(encoding="utf-8"))
|
||||
indexed = index["artifacts"]
|
||||
source_rows = [item for item in indexed if item["kind"] == "source"]
|
||||
entity_rows = [item for item in indexed if item["kind"] == "entity"]
|
||||
generated_rows = [item for item in indexed if item["kind"] == "generated"]
|
||||
relation_rows = [item for item in indexed if item["kind"] == "relation"]
|
||||
|
||||
assert len(entity_rows) == 14
|
||||
assert len(snapshot["entities"]) == 14
|
||||
assert {item["kindId"] for item in snapshot["entities"]} >= {
|
||||
"instrument",
|
||||
"strategy",
|
||||
"market",
|
||||
"event",
|
||||
"evidence-bearing-claim",
|
||||
"institution",
|
||||
"error",
|
||||
"trader",
|
||||
}
|
||||
assert {item["id"] for item in snapshot["kinds"]} == {
|
||||
item["kindId"] for item in snapshot["entities"]
|
||||
}
|
||||
assert {item["id"] for item in snapshot["kinds"]}.isdisjoint(
|
||||
{"source", "entity", "relation", "generated"}
|
||||
)
|
||||
|
||||
book = source_rows[0]["provenance"]["book_metadata"]
|
||||
assert len(snapshot["sources"]) == 1
|
||||
work = snapshot["sources"][0]
|
||||
assert work["license"] == book["rights"] == "Public domain in the USA."
|
||||
assert work["uri"] == book["source_url"]
|
||||
assert work["title"] == book["title"]
|
||||
attribution = work["attribution"]
|
||||
assert "provenance:" in attribution
|
||||
assert len(source_rows) == 6
|
||||
for source in source_rows:
|
||||
provenance = source["provenance"]
|
||||
assert provenance["digest"] in attribution
|
||||
assert provenance["original_path"] in attribution
|
||||
assert provenance["chunk_id"] in attribution
|
||||
|
||||
generated_titles = {item["title"] for item in generated_rows}
|
||||
generated_ids = {item["id"] for item in generated_rows}
|
||||
by_path = {item["properties"]["artifact_path"]: item for item in snapshot["entities"]}
|
||||
assert len(by_path) == 14
|
||||
for row in entity_rows:
|
||||
projected = by_path[row["path"]]
|
||||
parsed = parse_markdown_artifact(LEFEVRE / row["path"])
|
||||
heading = next(item.text for item in parsed.headings if item.level == 1)
|
||||
definition = extract_section_text(parsed, "Definition").strip()
|
||||
category = extract_section_text(parsed, "Category").strip()
|
||||
assert projected["title"] == display_title(heading)
|
||||
assert projected["summary"] == definition
|
||||
assert projected["tldr"]
|
||||
assert definition.startswith(projected["tldr"])
|
||||
assert projected["kindId"] == slugify(category)
|
||||
assert projected["id"] == (
|
||||
f"urn:ce:lefevre-reminiscences-of-a-stock-operator:"
|
||||
f"{projected['kindId']}:{slugify(projected['title'])}"
|
||||
)
|
||||
assert projected["sourceRefs"] == [work["id"]]
|
||||
assert projected["properties"]["workflow_id"] == row["provenance"]["workflow_id"]
|
||||
assert projected["properties"]["input_artifact_id"] == row["provenance"]["input_artifact_id"]
|
||||
assert projected["title"] not in generated_titles
|
||||
assert projected["id"] not in generated_ids
|
||||
|
||||
block_count = 0
|
||||
for row in relation_rows:
|
||||
text = (LEFEVRE / row["path"]).read_text(encoding="utf-8")
|
||||
block_count += text.count("\n## Subject") + int(text.startswith("## Subject"))
|
||||
assert block_count == 27
|
||||
assert len(first.resolved_relations) + len(first.quarantined_relations) == block_count
|
||||
assert first.quarantined_relations
|
||||
assert len(snapshot["relations"]) == len(first.resolved_relations)
|
||||
entities_by_id = {item["id"]: item for item in snapshot["entities"]}
|
||||
relations_by_id = {item["id"]: item for item in snapshot["relations"]}
|
||||
for record in first.resolved_relations:
|
||||
projected = relations_by_id[record.relation_id]
|
||||
assert projected["predicate"] == record.relation_type.replace("_", "-")
|
||||
assert " " not in projected["predicate"]
|
||||
if record.predicate_text != projected["predicate"]:
|
||||
assert projected["predicate"] != record.predicate_text
|
||||
assert slugify(record.subject) == slugify(entities_by_id[record.from_id]["title"])
|
||||
assert slugify(record.object) == slugify(entities_by_id[record.to_id]["title"])
|
||||
assert projected["from"] == record.from_id
|
||||
assert projected["to"] == record.to_id
|
||||
assert projected["sourceRefs"] == [work["id"]]
|
||||
resolved_keys = {(item.artifact_id, item.block_index) for item in first.resolved_relations}
|
||||
for record in first.quarantined_relations:
|
||||
assert (record.artifact_id, record.block_index) not in resolved_keys
|
||||
subject_hits = [
|
||||
item
|
||||
for item in snapshot["entities"]
|
||||
if slugify(item["title"]) == slugify(record.subject)
|
||||
]
|
||||
object_hits = [
|
||||
item
|
||||
for item in snapshot["entities"]
|
||||
if slugify(item["title"]) == slugify(record.object)
|
||||
]
|
||||
assert not (len(subject_hits) == 1 and len(object_hits) == 1)
|
||||
|
||||
assert manifest["schemaVersion"] == "0.1"
|
||||
assert manifest["id"] == "lefevre-reminiscences-of-a-stock-operator"
|
||||
assert manifest["panels"] == ["summary", "relations", "evidence", "claims"]
|
||||
assert manifest["factPolicy"] == "show-all"
|
||||
assert set(manifest["kindViews"]) == {item["id"] for item in snapshot["kinds"]}
|
||||
for view in manifest["kindViews"].values():
|
||||
assert view["columns"]
|
||||
assert "facets" in view
|
||||
assert set(manifest["home"]["featuredKinds"]) == {item["id"] for item in snapshot["kinds"]}
|
||||
assert set(manifest["relationLabels"]) == {item["predicate"] for item in snapshot["relations"]}
|
||||
for wording in manifest["relationLabels"].values():
|
||||
assert wording["forward"]
|
||||
assert wording["inverse"]
|
||||
|
||||
export_dir = LEFEVRE / "exports" / "content-explorer"
|
||||
assert json.loads((export_dir / "snapshot.json").read_text(encoding="utf-8")) == snapshot
|
||||
assert json.loads((export_dir / "manifest.json").read_text(encoding="utf-8")) == manifest
|
||||
|
||||
errors = validate_documents(snapshot, manifest, require_size=True)
|
||||
assert errors == []
|
||||
raw_errors = validate_documents(index, index, require_size=True)
|
||||
assert raw_errors
|
||||
|
||||
note = (LEFEVRE / "exports" / "content-explorer" / "PROJECTION.md").read_text(encoding="utf-8")
|
||||
assert "lefevre-reminiscences-of-a-stock-operator" in note
|
||||
assert "content-explorer only presents" in note.lower()
|
||||
assert "infospace-bench still owns" in note.lower()
|
||||
|
||||
print("ENTITY_IDS " + ",".join(item["id"] for item in snapshot["entities"]))
|
||||
print("RELATION_IDS " + ",".join(item["id"] for item in snapshot["relations"]))
|
||||
print("VALIDATOR_ERRORS 0")
|
||||
print("RAW_ARTIFACTS_ERRORS " + str(len(raw_errors)))
|
||||
Loading…
Add table
Add a link
Reference in a new issue