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 <noreply@anthropic.com>
This commit is contained in:
2026-05-17 22:31:17 +02:00
parent bb70b2f4b9
commit 348deca9f2
14 changed files with 430 additions and 1 deletions

131
tests/fixtures/lefevre/responses.yaml vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,21 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>Reminiscences of a Stock Operator (Fixture)</title></head>
<body>
<h2>I</h2>
<p>
<span id="Page_1">1</span>
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.
</p>
<p>
<span id="Page_2">2</span>
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.
</p>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>Reminiscences of a Stock Operator (Fixture)</title></head>
<body>
<h2>II</h2>
<p>
<span id="Page_15">15</span>
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.
</p>
<p>
<span id="Page_16">16</span>
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.
</p>
</body>
</html>

View File

@@ -0,0 +1,13 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>Reminiscences of a Stock Operator (Fixture)</title></head>
<body>
<h2>III</h2>
<p>
<span id="Page_31">31</span>
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.
</p>
</body>
</html>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="bookid">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:identifier id="bookid">urn:fixture:lefevre</dc:identifier>
<dc:title>Reminiscences of a Stock Operator (Fixture)</dc:title>
<dc:creator>Edwin Lefevre (Fixture)</dc:creator>
<dc:language>en</dc:language>
<dc:rights>Public domain in the USA. Fixture content for infospace-bench tests.</dc:rights>
<dc:subject>Speculation</dc:subject>
<dc:subject>New York Stock Exchange</dc:subject>
<dc:source>https://www.gutenberg.org/ebooks/60979</dc:source>
<meta property="dcterms:modified">2026-05-17T00:00:00Z</meta>
</metadata>
<manifest>
<item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/>
<item id="cover" href="cover.xhtml" media-type="application/xhtml+xml"/>
<item id="pgheader" href="pgheader.xhtml" media-type="application/xhtml+xml"/>
<item id="ch1" href="chapter1.xhtml" media-type="application/xhtml+xml"/>
<item id="ch2" href="chapter2.xhtml" media-type="application/xhtml+xml"/>
<item id="ch3" href="chapter3.xhtml" media-type="application/xhtml+xml"/>
<item id="notes" href="transcriber-notes.xhtml" media-type="application/xhtml+xml"/>
<item id="license" href="license.xhtml" media-type="application/xhtml+xml"/>
<item id="pgfooter" href="pgfooter.xhtml" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="cover"/>
<itemref idref="nav" linear="no"/>
<itemref idref="pgheader"/>
<itemref idref="ch1"/>
<itemref idref="ch2"/>
<itemref idref="ch3"/>
<itemref idref="notes"/>
<itemref idref="license"/>
<itemref idref="pgfooter"/>
</spine>
</package>

View File

@@ -0,0 +1,4 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>Cover</title></head>
<body><h1>Cover</h1><p>Reminiscences of a Stock Operator (Fixture)</p></body>
</html>

View File

@@ -0,0 +1,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>License</title></head>
<body>
<h2>License</h2>
<p>This fixture is checked into infospace-bench solely for deterministic regression testing.</p>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="en">
<head><title>Contents</title></head>
<body>
<nav epub:type="toc">
<ol>
<li><a href="chapter1.xhtml">I</a></li>
<li><a href="chapter2.xhtml">II</a></li>
<li><a href="chapter3.xhtml">III</a></li>
</ol>
</nav>
</body>
</html>

View File

@@ -0,0 +1,6 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>End</title></head>
<body>
<p>*** END OF THE PROJECT GUTENBERG EBOOK REMINISCENCES OF A STOCK OPERATOR ***</p>
</body>
</html>

View File

@@ -0,0 +1,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>Reminiscences of a Stock Operator (Fixture)</title></head>
<body>
<p>*** START OF THE PROJECT GUTENBERG EBOOK REMINISCENCES OF A STOCK OPERATOR ***</p>
<p>Produced by test fixtures for infospace-bench.</p>
</body>
</html>

View File

@@ -0,0 +1,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>Transcriber's Notes</title></head>
<body>
<h2>Transcriber's Notes</h2>
<p>Minor spelling and punctuation has been normalised in this fixture edition.</p>
</body>
</html>

View File

@@ -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"]

View File

@@ -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"
```