import json import os import subprocess import sys import zipfile from pathlib import Path import yaml from infospace_bench.generator import ( init_generation_infospace, run_generation, status_generation, ) PROFILE_DIR = Path("src/infospace_bench/profiles/trading-literature") def _fixture_responses(path: Path) -> None: data = { "responses": [ { "stage_id": "summarize-source", "input_artifact_id": "*", "markdown": "# Source Summary\n\nThe chapter introduces a bucket-shop apprenticeship.\n", }, { "stage_id": "extract-entities", "input_artifact_id": "*", "markdown": ( "# Tape Reading\n\n" "## Category\n\nstrategy\n\n" "## Definition\n\n" "Inferring price intent from the ticker tape rather than fundamentals.\n\n" "## Context\n\nFramed as a learnable pattern skill in the chapter.\n\n" "# Bucket Shop\n\n" "## Category\n\ninstitution\n\n" "## Definition\n\n" "A 1900s retail brokerage that took the other side of customer tape bets.\n\n" ), }, { "stage_id": "extract-relations", "input_artifact_id": "*", "markdown": ( "# Tape Reading Reduces Tip Following\n\n" "## Subject\n\nTape Reading\n\n" "## Predicate\n\nreduces\n\n" "## Object\n\nTip Following\n\n" "## Relation Type\n\nrisk_mitigation\n\n" "## Evidence\n\nThe narrator's profits track tape behaviour, not rumour.\n" ), }, { "stage_id": "evaluate-entity", "input_artifact_id": "*", "markdown": ( "---\n" "artifact_id: entity/tape-reading.md\n" "evaluator: fixture\n" "evaluated_at: '2026-05-17T00:00:00'\n" "scores:\n" " - name: groundedness\n value: 4.0\n max_value: 5.0\n" " - name: lesson_clarity\n value: 4.0\n max_value: 5.0\n" " - name: historical_context\n value: 4.0\n max_value: 5.0\n" " - name: overgeneralization_risk\n value: 4.0\n max_value: 5.0\n" "---\n\n" "# Evaluation: entity/tape-reading.md\n" ), }, ] } path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8") CONTAINER_XML = """ """ PACKAGE_OPF = """ urn:test:trading Trading Memoir Fixture Fixture Author en """ def _write_two_chapter_epub(path: Path) -> None: with zipfile.ZipFile(path, "w") as archive: archive.writestr("mimetype", "application/epub+zip") archive.writestr("META-INF/container.xml", CONTAINER_XML) archive.writestr("OEBPS/content.opf", PACKAGE_OPF) archive.writestr( "OEBPS/ch1.xhtml", "Book" "

I

The narrator tries tape reading at a bucket shop.

", ) archive.writestr( "OEBPS/ch2.xhtml", "Book" "

II

He learns the cost of acting on rumours.

", ) def test_trading_profile_declares_required_categories_and_criteria() -> None: data = yaml.safe_load((PROFILE_DIR / "profile.yaml").read_text(encoding="utf-8")) assert data["id"] == "trading-literature" assert set(data["entity_categories"]) == { "traders", "markets", "strategies", "errors", "psychological_patterns", "institutions", "instruments", "evidence_bearing_claims", } assert set(data["relation_categories"]) == { "cause_effect", "lesson_evidence", "risk_mitigation", "actor_venue", "strategy_outcome", } assert data["evaluation_criteria"] == [ "groundedness", "lesson_clarity", "historical_context", "overgeneralization_risk", ] def test_trading_profile_evaluate_template_mentions_all_criteria() -> None: template = (PROFILE_DIR / "templates" / "evaluate-entity.md").read_text(encoding="utf-8") for criterion in ( "groundedness", "lesson_clarity", "historical_context", "overgeneralization_risk", ): assert criterion in template, f"evaluate template should reference {criterion}" def test_trading_profile_relation_template_lists_required_relation_types() -> None: template = (PROFILE_DIR / "templates" / "extract-relations.md").read_text(encoding="utf-8") for relation_type in ( "cause_effect", "lesson_evidence", "risk_mitigation", "actor_venue", "strategy_outcome", ): assert relation_type in template, f"relation template should reference {relation_type}" def test_trading_profile_contracts_present() -> None: contracts_dir = PROFILE_DIR / "contracts" expected = {"entity.contract.md", "relation.contract.md", "evaluation.contract.md", "summary.contract.md"} actual = {path.name for path in contracts_dir.glob("*.md")} assert expected.issubset(actual) def test_trading_profile_runs_end_to_end_with_fixture(tmp_path: Path) -> None: book = tmp_path / "book.epub" _write_two_chapter_epub(book) fixture = tmp_path / "responses.yaml" _fixture_responses(fixture) infospace = init_generation_infospace( tmp_path, book, "trading-fixture", name="Trading Fixture", profile="trading-literature", ) result = run_generation(infospace.root, fixture_responses=fixture) status = status_generation(infospace.root) assert result.status == "completed" assert status["profile"] == "trading-literature" assert status["source_chunk_count"] == 2 assert status["entity_count"] >= 1 assert status["relation_count"] >= 1 assert status["evaluation_count"] >= 1 # Installed profile should have copied templates and contracts into the infospace. assert (infospace.root / "profiles" / "trading-literature" / "templates" / "evaluate-entity.md").is_file() assert ( infospace.root / "profiles" / "trading-literature" / "contracts" / "entity.contract.md" ).is_file() def test_trading_profile_selectable_via_cli(tmp_path: Path) -> None: book = tmp_path / "book.epub" _write_two_chapter_epub(book) fixture = tmp_path / "responses.yaml" _fixture_responses(fixture) env = os.environ.copy() env["PYTHONPATH"] = "src:/home/worsch/markitect-tool/src" result = subprocess.run( [ sys.executable, "-m", "infospace_bench", "generate", "from-source", str(book), "--workspace", str(tmp_path), "--slug", "trading-cli", "--name", "Trading CLI", "--profile", "trading-literature", "--fixture-responses", str(fixture), "--apply", ], check=False, env=env, text=True, capture_output=True, ) assert result.returncode == 0, result.stderr payload = json.loads(result.stdout) assert payload["status"] == "completed" assert "trading-cli" in payload["root"]