diff --git a/tests/fixtures/lefevre/responses.yaml b/tests/fixtures/lefevre/responses.yaml new file mode 100644 index 0000000..58bb088 --- /dev/null +++ b/tests/fixtures/lefevre/responses.yaml @@ -0,0 +1,131 @@ +responses: + - stage_id: summarize-source + input_artifact_id: "*" + markdown: | + # Source Summary + + The chapter follows the narrator's first encounters with quotation + boards, the New York Stock Exchange, and the bucket-shop circuit. + It establishes tape reading as the craft on which his later trades + are built. + - stage_id: extract-entities + input_artifact_id: "*" + markdown: | + # Larry Livingston + + ## Category + + trader + + ## Definition + + The first-person narrator, a young quotation-board boy turning + bucket-shop trader, modeled on Jesse Livermore. + + ## Context + + Lefevre uses Livingston to dramatize the apprenticeship of a stock + operator across the chapter. + + # Bucket Shop + + ## Category + + institution + + ## Definition + + A 1900s retail brokerage that took the other side of customer tape + bets without executing actual exchange orders. + + ## Context + + The bucket shop is where the narrator earns and loses his first + stakes. + + # Tape Reading + + ## Category + + strategy + + ## Definition + + Inferring price intent and momentum from the ticker tape rather + than from fundamental valuation. + + ## Context + + The chapter frames tape reading as a learnable pattern skill, not + luck. + - stage_id: extract-relations + input_artifact_id: "*" + markdown: | + # Larry Livingston Practices Tape Reading + + ## Subject + + Larry Livingston + + ## Predicate + + practices + + ## Object + + Tape Reading + + ## Relation Type + + strategy_outcome + + ## Evidence + + The narrator describes long hours reading the tape at the bucket + shop and the small book of hits and misses he keeps. + + # Bucket Shop Hosts Larry Livingston + + ## Subject + + Bucket Shop + + ## Predicate + + hosts + + ## Object + + Larry Livingston + + ## Relation Type + + actor_venue + + ## Evidence + + The narrator's first profits come from trading inside a bucket + shop. + - stage_id: evaluate-entity + input_artifact_id: "*" + markdown: | + --- + artifact_id: entity/larry-livingston.md + evaluator: fixture + evaluated_at: '2026-05-17T00:00:00' + scores: + - name: groundedness + value: 4.0 + max_value: 5.0 + - name: lesson_clarity + value: 4.0 + max_value: 5.0 + - name: historical_context + value: 4.0 + max_value: 5.0 + - name: overgeneralization_risk + value: 4.0 + max_value: 5.0 + --- + + # Evaluation: entity/larry-livingston.md diff --git a/tests/fixtures/lefevre/sources/chapter1.xhtml b/tests/fixtures/lefevre/sources/chapter1.xhtml new file mode 100644 index 0000000..4bfa920 --- /dev/null +++ b/tests/fixtures/lefevre/sources/chapter1.xhtml @@ -0,0 +1,21 @@ + + Reminiscences of a Stock Operator (Fixture) + +

I

+

+ 1 + The narrator takes his first job as a quotation-board boy at a stock + brokerage off the New York Stock Exchange. He is quick with figures + and watches the tape behaviour of stocks like Hollow Tube and Erie, + keeping a small book of hits and misses to test his memory against + the next day's quotations. +

+

+ 2 + He learns, in the bucket shops that dotted the city in those years, + that anticipating price movements is a craft built on observation of + tape behaviour, not on tips from customers crowded around the + ticker. +

+ + diff --git a/tests/fixtures/lefevre/sources/chapter2.xhtml b/tests/fixtures/lefevre/sources/chapter2.xhtml new file mode 100644 index 0000000..c8c9f6d --- /dev/null +++ b/tests/fixtures/lefevre/sources/chapter2.xhtml @@ -0,0 +1,19 @@ + + Reminiscences of a Stock Operator (Fixture) + +

II

+

+ 15 + Between the discovery that the Cosmopolitan Stock Brokerage Company is + ready to load the dice and the narrator's discovery of pyramiding, he + sees that even an old bucket-shop hand can be ruined by the wrong + side of a one-sided market. +

+

+ 16 + The lesson he draws is to test the size of his line against the size + of the float, and to scale in only when each successive purchase is + vindicated by the tape itself. +

+ + diff --git a/tests/fixtures/lefevre/sources/chapter3.xhtml b/tests/fixtures/lefevre/sources/chapter3.xhtml new file mode 100644 index 0000000..3fb42c9 --- /dev/null +++ b/tests/fixtures/lefevre/sources/chapter3.xhtml @@ -0,0 +1,13 @@ + + Reminiscences of a Stock Operator (Fixture) + +

III

+

+ 31 + The narrator learns, after a loss in a thin market, that there are + no new tricks in Wall Street: every habit of price has appeared in + some earlier era under another name, and the operator who remembers + that pays less tuition than the one who does not. +

+ + diff --git a/tests/fixtures/lefevre/sources/container.xml b/tests/fixtures/lefevre/sources/container.xml new file mode 100644 index 0000000..b89e315 --- /dev/null +++ b/tests/fixtures/lefevre/sources/container.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/fixtures/lefevre/sources/content.opf b/tests/fixtures/lefevre/sources/content.opf new file mode 100644 index 0000000..ba158cd --- /dev/null +++ b/tests/fixtures/lefevre/sources/content.opf @@ -0,0 +1,36 @@ + + + + urn:fixture:lefevre + Reminiscences of a Stock Operator (Fixture) + Edwin Lefevre (Fixture) + en + Public domain in the USA. Fixture content for infospace-bench tests. + Speculation + New York Stock Exchange + https://www.gutenberg.org/ebooks/60979 + 2026-05-17T00:00:00Z + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/lefevre/sources/cover.xhtml b/tests/fixtures/lefevre/sources/cover.xhtml new file mode 100644 index 0000000..4c38d8a --- /dev/null +++ b/tests/fixtures/lefevre/sources/cover.xhtml @@ -0,0 +1,4 @@ + + Cover +

Cover

Reminiscences of a Stock Operator (Fixture)

+ diff --git a/tests/fixtures/lefevre/sources/license.xhtml b/tests/fixtures/lefevre/sources/license.xhtml new file mode 100644 index 0000000..c9bdb76 --- /dev/null +++ b/tests/fixtures/lefevre/sources/license.xhtml @@ -0,0 +1,7 @@ + + License + +

License

+

This fixture is checked into infospace-bench solely for deterministic regression testing.

+ + diff --git a/tests/fixtures/lefevre/sources/nav.xhtml b/tests/fixtures/lefevre/sources/nav.xhtml new file mode 100644 index 0000000..d5057e7 --- /dev/null +++ b/tests/fixtures/lefevre/sources/nav.xhtml @@ -0,0 +1,12 @@ + + Contents + + + + diff --git a/tests/fixtures/lefevre/sources/pgfooter.xhtml b/tests/fixtures/lefevre/sources/pgfooter.xhtml new file mode 100644 index 0000000..dcd614e --- /dev/null +++ b/tests/fixtures/lefevre/sources/pgfooter.xhtml @@ -0,0 +1,6 @@ + + End + +

*** END OF THE PROJECT GUTENBERG EBOOK REMINISCENCES OF A STOCK OPERATOR ***

+ + diff --git a/tests/fixtures/lefevre/sources/pgheader.xhtml b/tests/fixtures/lefevre/sources/pgheader.xhtml new file mode 100644 index 0000000..4eccb0e --- /dev/null +++ b/tests/fixtures/lefevre/sources/pgheader.xhtml @@ -0,0 +1,7 @@ + + Reminiscences of a Stock Operator (Fixture) + +

*** START OF THE PROJECT GUTENBERG EBOOK REMINISCENCES OF A STOCK OPERATOR ***

+

Produced by test fixtures for infospace-bench.

+ + diff --git a/tests/fixtures/lefevre/sources/transcriber-notes.xhtml b/tests/fixtures/lefevre/sources/transcriber-notes.xhtml new file mode 100644 index 0000000..164a979 --- /dev/null +++ b/tests/fixtures/lefevre/sources/transcriber-notes.xhtml @@ -0,0 +1,7 @@ + + Transcriber's Notes + +

Transcriber's Notes

+

Minor spelling and punctuation has been normalised in this fixture edition.

+ + diff --git a/tests/test_lefevre_fixture.py b/tests/test_lefevre_fixture.py new file mode 100644 index 0000000..cb211f3 --- /dev/null +++ b/tests/test_lefevre_fixture.py @@ -0,0 +1,160 @@ +import json +import os +import subprocess +import sys +import zipfile +from pathlib import Path + +from infospace_bench.budget import ( + read_plan_snapshots, + read_usage_runs, +) +from infospace_bench.generator import ( + init_generation_infospace, + plan_generation, + run_generation, + status_generation, +) +from infospace_bench.source_intake import ( + SECTION_ROLE_BODY, + SECTION_ROLE_COVER, + SECTION_ROLE_FOOTER, + SECTION_ROLE_HEADER, + SECTION_ROLE_LICENSE, + SECTION_ROLE_NAV, + SECTION_ROLE_NOTES, + normalize_source, +) + + +FIXTURE_ROOT = Path(__file__).parent / "fixtures" / "lefevre" +FIXTURE_SOURCES = FIXTURE_ROOT / "sources" +FIXTURE_RESPONSES = FIXTURE_ROOT / "responses.yaml" + + +def _build_fixture_epub(target: Path) -> Path: + """Assemble the checked-in Lefevre fixture sources into a single EPUB zip.""" + layout = { + "mimetype": "application/epub+zip", + "META-INF/container.xml": (FIXTURE_SOURCES / "container.xml").read_text(encoding="utf-8"), + } + for source in sorted(FIXTURE_SOURCES.glob("*.xhtml")): + layout[f"OEBPS/{source.name}"] = source.read_text(encoding="utf-8") + layout["OEBPS/content.opf"] = (FIXTURE_SOURCES / "content.opf").read_text(encoding="utf-8") + with zipfile.ZipFile(target, "w") as archive: + for path_in_zip, contents in layout.items(): + archive.writestr(path_in_zip, contents) + return target + + +def test_lefevre_fixture_builds_a_complete_infospace(tmp_path: Path) -> None: + book = _build_fixture_epub(tmp_path / "lefevre.epub") + + infospace = init_generation_infospace( + tmp_path, + book, + "lefevre-fixture", + name="Reminiscences of a Stock Operator (Fixture)", + profile="trading-literature", + ) + plan_generation(infospace.root) + result = run_generation(infospace.root, fixture_responses=FIXTURE_RESPONSES) + status = status_generation(infospace.root) + + assert result.status == "completed" + assert status["profile"] == "trading-literature" + # Three body chapters in the fixture spine; cover/nav/header/notes/license/footer are excluded by default. + assert status["source_chunk_count"] == 3 + assert status["entity_count"] >= 1 + assert status["relation_count"] >= 1 + assert status["evaluation_count"] >= 1 + assert status["history_snapshot_count"] >= 1 + + # Stable chapter-NN source filenames from the IB-WP-0016 T02 work. + expected_sources = {"chapter-01.md", "chapter-02.md", "chapter-03.md"} + actual_sources = { + path.name + for path in (infospace.root / "artifacts" / "sources").glob("*.md") + } + assert expected_sources == actual_sources + + # Manifest-backed artifacts: entities, relations, evaluations, metrics, history, report + assert (infospace.root / "artifacts" / "entities").is_dir() + assert (infospace.root / "artifacts" / "relations").is_dir() + assert any((infospace.root / "output" / "evaluations").glob("*.md")) + assert (infospace.root / "output" / "metrics" / "metrics.yaml").is_file() + assert (infospace.root / "output" / "metrics" / "history.yaml").is_file() + assert (infospace.root / "reports" / "generation-summary.md").is_file() + + # Budget registry artifacts (IB-WP-0019) should land alongside the run. + assert read_plan_snapshots(infospace.root), "plan snapshot must persist" + runs = read_usage_runs(infospace.root) + assert runs and runs[0]["snapshot_id"] == read_plan_snapshots(infospace.root)[-1]["snapshot_id"] + + # Book provenance plumb-through: every source artifact knows the chapter it came from. + import yaml as _yaml + + index = _yaml.safe_load((infospace.root / "artifacts" / "index.yaml").read_text(encoding="utf-8")) + chapter_numbers = sorted( + item["provenance"]["chapter_number"] + for item in index["artifacts"] + if item["kind"] == "source" + ) + assert chapter_numbers == [1, 2, 3] + + +def test_lefevre_fixture_excludes_gutenberg_boilerplate_by_default(tmp_path: Path) -> None: + book = _build_fixture_epub(tmp_path / "lefevre.epub") + + default_chunks = normalize_source(book) + include_all_chunks = normalize_source(book, include_non_body=True) + + # Default: only the three body chapters survive. + assert [chunk.chapter_label for chunk in default_chunks] == ["I", "II", "III"] + assert {chunk.section_role for chunk in default_chunks} == {SECTION_ROLE_BODY} + + # include_non_body: cover, nav, PG header, notes, license, footer all appear. + roles = {chunk.section_role for chunk in include_all_chunks} + assert SECTION_ROLE_COVER in roles + assert SECTION_ROLE_NAV in roles + assert SECTION_ROLE_HEADER in roles + assert SECTION_ROLE_NOTES in roles + assert SECTION_ROLE_LICENSE in roles + assert SECTION_ROLE_FOOTER in roles + + +def test_lefevre_fixture_cli_end_to_end(tmp_path: Path) -> None: + book = _build_fixture_epub(tmp_path / "lefevre.epub") + 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", + "lefevre-fixture-cli", + "--name", + "Lefevre Fixture (CLI)", + "--profile", + "trading-literature", + "--fixture-responses", + str(FIXTURE_RESPONSES), + "--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 "lefevre-fixture-cli" in payload["root"] diff --git a/workplans/IB-WP-0016-lefevre-ebook-infospace-readiness.md b/workplans/IB-WP-0016-lefevre-ebook-infospace-readiness.md index 928f3de..5654e9b 100644 --- a/workplans/IB-WP-0016-lefevre-ebook-infospace-readiness.md +++ b/workplans/IB-WP-0016-lefevre-ebook-infospace-readiness.md @@ -175,7 +175,7 @@ state_hub_task_id: "1a1b8fde-773f-46a6-887a-3c87a425d7a3" ```task id: IB-WP-0016-T05 -status: todo +status: done priority: high state_hub_task_id: "c9bbc84e-691b-4530-a79a-6ecfa9c41fdd" ```