From 348deca9f2ced81cd9a7ac622eecb08bf462a264 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 17 May 2026 22:31:17 +0200 Subject: [PATCH] IB-WP-0016-T05: deterministic Lefevre acceptance fixture Check in a small Lefevre-shaped EPUB fixture as separate source files under tests/fixtures/lefevre/sources/ (container.xml, OPF, nav, cover, PG header, three roman-numeral chapters with page anchors, transcriber notes, license, PG footer). The test helper assembles these into an EPUB at test time so the inputs stay inspectable in git. Fixture responses tuned to the trading-literature profile (T04) live at tests/fixtures/lefevre/responses.yaml: trader / institution / strategy categories on entities, strategy_outcome / actor_venue relation types, and all four trading-tuned evaluation criteria. Three tests cover the acceptance: - end-to-end Python pipeline: stable chapter-NN source slugs, full artifact tree (entities, relations, evaluations, metrics, history, generation report), budget registry persisted, chapter_number provenance round-trips through artifacts/index.yaml - regression: PG boilerplate (cover, nav, header, notes, license, footer) is excluded by default and only appears under include_non_body=True - CLI smoke through generate from-source --profile trading-literature --fixture-responses ... 125 tests pass. Co-Authored-By: Claude Opus 4.7 --- tests/fixtures/lefevre/responses.yaml | 131 ++++++++++++++ tests/fixtures/lefevre/sources/chapter1.xhtml | 21 +++ tests/fixtures/lefevre/sources/chapter2.xhtml | 19 +++ tests/fixtures/lefevre/sources/chapter3.xhtml | 13 ++ tests/fixtures/lefevre/sources/container.xml | 6 + tests/fixtures/lefevre/sources/content.opf | 36 ++++ tests/fixtures/lefevre/sources/cover.xhtml | 4 + tests/fixtures/lefevre/sources/license.xhtml | 7 + tests/fixtures/lefevre/sources/nav.xhtml | 12 ++ tests/fixtures/lefevre/sources/pgfooter.xhtml | 6 + tests/fixtures/lefevre/sources/pgheader.xhtml | 7 + .../lefevre/sources/transcriber-notes.xhtml | 7 + tests/test_lefevre_fixture.py | 160 ++++++++++++++++++ ...-0016-lefevre-ebook-infospace-readiness.md | 2 +- 14 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/lefevre/responses.yaml create mode 100644 tests/fixtures/lefevre/sources/chapter1.xhtml create mode 100644 tests/fixtures/lefevre/sources/chapter2.xhtml create mode 100644 tests/fixtures/lefevre/sources/chapter3.xhtml create mode 100644 tests/fixtures/lefevre/sources/container.xml create mode 100644 tests/fixtures/lefevre/sources/content.opf create mode 100644 tests/fixtures/lefevre/sources/cover.xhtml create mode 100644 tests/fixtures/lefevre/sources/license.xhtml create mode 100644 tests/fixtures/lefevre/sources/nav.xhtml create mode 100644 tests/fixtures/lefevre/sources/pgfooter.xhtml create mode 100644 tests/fixtures/lefevre/sources/pgheader.xhtml create mode 100644 tests/fixtures/lefevre/sources/transcriber-notes.xhtml create mode 100644 tests/test_lefevre_fixture.py 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" ```