Red. A process code states its lettered points once, on the section that owns them, and every section nested below inherits them; built faithfully, the nested section is a concept whose body is one heading line (710 of 2 761 on one measured standard), and the two-level directory tree does not carry the parent either. Behind `--shell-parent`, off by default: a plan entry whose span holds only its heading gets `parent_id` naming the nearest preceding entry at a smaller level whose own span holds text. An empty ancestor is passed over; a shell with no ancestor holding text gets no parent. The rule reads the plan's level and order, never the row, so the same outline through the bookmark arm's route reaches the same parents. The fixture is hand-written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
179 lines
6.7 KiB
Python
179 lines
6.7 KiB
Python
"""A section whose body is its heading alone points at the ancestor holding its text.
|
|
|
|
THE SHAPE, in the mechanism rather than in a corpus. A process code states its
|
|
lettered points once, on the section that owns them, and every section nested
|
|
below inherits them; the nested section itself carries a title and nothing
|
|
else. Built faithfully, it becomes a concept whose body is one heading line --
|
|
measured on one 2 761-concept standard, 710 such concepts -- and a reader
|
|
handed one has no way to reach the text it inherits: the directory tree is two
|
|
levels deep, so the parent is not in the path either.
|
|
|
|
The rule, behind `--shell-parent` and OFF by default: a plan entry whose span
|
|
holds no line but its heading gets `parent_id` naming the NEAREST preceding
|
|
entry at a smaller level whose own span holds text. An empty ancestor is passed
|
|
over, and a shell with no ancestor holding text gets no parent. Nothing is
|
|
copied: the door writes the existing `parent:` key and not one borrowed line.
|
|
|
|
It reads the PLAN -- level and order -- and never the row, so the same outline
|
|
reaches the same parents through a route that is not NISO-STS.
|
|
|
|
`sts-inherit.xml` is hand-written in an invented setting and carries no
|
|
sentence from any source.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from llm_ingestion_okf import cli, extract, propose
|
|
|
|
FIXTURE = Path(__file__).parent / "fixtures" / "sts-inherit.xml"
|
|
|
|
#: Every flag `okf build` turns on by default.
|
|
BUILD_DEFAULTS = dict(
|
|
outline_run=3,
|
|
table_grid=True,
|
|
unit_fold=True,
|
|
keep_table_heading=True,
|
|
sheet_section_rows=True,
|
|
drop_wrapped_outline=True,
|
|
outline_gate=True,
|
|
first_span_from_zero=True,
|
|
close_span_gaps=True,
|
|
contents_name=True,
|
|
)
|
|
|
|
#: shell title -> title of the nearest ancestor holding text, or None.
|
|
EXPECTED = {
|
|
"Rekkverk": "Vedlikehold av utstyr",
|
|
# Its parent `Rekkverk` is a shell too, so the pointer goes one further up.
|
|
"Utskifting": "Vedlikehold av utstyr",
|
|
"Utskifting av list": "Utskifting av enkeltdeler",
|
|
"Utskifting av stolpe": "Utskifting av enkeltdeler",
|
|
# A shell whose only ancestor is empty, and one with no ancestor at all.
|
|
"Kapittel uten tekst": None,
|
|
"Underkapittel uten tekst": None,
|
|
# Sections holding text never get one.
|
|
"Vedlikehold av utstyr": None,
|
|
"Utskifting av enkeltdeler": None,
|
|
}
|
|
|
|
|
|
def _plan(source: Path, **flags: object) -> dict: # type: ignore[type-arg]
|
|
data = FIXTURE.read_bytes()
|
|
text = extract.extract_text(FIXTURE.name, data)
|
|
return propose.build_plan(
|
|
source,
|
|
text,
|
|
data,
|
|
okf_type="reference",
|
|
proposed_at="2026-01-01T00:00:00Z",
|
|
**BUILD_DEFAULTS,
|
|
**flags, # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
def _parents(plan: dict) -> dict[str, str | None]: # type: ignore[type-arg]
|
|
titles = {entry["segment_id"]: entry["title"] for entry in plan["entries"]}
|
|
return {
|
|
entry["title"]: titles[entry["parent_id"]] if "parent_id" in entry else None
|
|
for entry in plan["entries"]
|
|
}
|
|
|
|
|
|
def test_a_shell_points_at_the_nearest_ancestor_holding_text() -> None:
|
|
assert _parents(_plan(FIXTURE, shell_parent=True)) == EXPECTED
|
|
|
|
|
|
def test_nothing_moves_without_the_flag() -> None:
|
|
plain = _plan(FIXTURE)
|
|
assert not [entry for entry in plain["entries"] if "parent_id" in entry]
|
|
flagged = _plan(FIXTURE, shell_parent=True)
|
|
for entry in flagged["entries"]:
|
|
entry.pop("parent_id", None)
|
|
assert flagged == plain
|
|
|
|
|
|
def test_the_rule_reads_the_plan_and_not_the_row(tmp_path: Path) -> None:
|
|
"""The same outline through the bookmark arm's route: the same parents."""
|
|
data = FIXTURE.read_bytes()
|
|
text = extract.extract_text(FIXTURE.name, data)
|
|
source = tmp_path / "same-outline.md"
|
|
source.write_text(text, encoding="utf-8")
|
|
plan = propose.build_plan(
|
|
source,
|
|
text,
|
|
source.read_bytes(),
|
|
okf_type="reference",
|
|
proposed_at="2026-01-01T00:00:00Z",
|
|
outline_marks=extract.xml_outline(FIXTURE.name, data),
|
|
shell_parent=True,
|
|
**BUILD_DEFAULTS, # type: ignore[arg-type]
|
|
)
|
|
assert {entry["derived"][1] for entry in plan["entries"]} == {propose.RULE_PDF_OUTLINE}
|
|
assert _parents(plan) == EXPECTED
|
|
|
|
|
|
def _frontmatter(bundle: Path) -> dict[str, dict[str, str]]:
|
|
by_title: dict[str, dict[str, str]] = {}
|
|
for path in bundle.rglob("*.md"):
|
|
if path.name in ("index.md", "log.md"):
|
|
continue
|
|
head = path.read_text(encoding="utf-8").split("\n---\n", 1)[0]
|
|
values = dict(line.split(": ", 1) for line in head.split("\n")[1:] if ": " in line)
|
|
by_title[values["title"]] = values
|
|
return by_title
|
|
|
|
|
|
def _build(tmp_path: Path, name: str, *extra: str) -> Path:
|
|
inbox = tmp_path / "inbox"
|
|
if not inbox.exists():
|
|
inbox.mkdir()
|
|
(inbox / FIXTURE.name).write_bytes(FIXTURE.read_bytes())
|
|
bundle = tmp_path / name
|
|
assert (
|
|
cli.main(
|
|
[
|
|
"build",
|
|
str(inbox),
|
|
"--bundle",
|
|
str(bundle),
|
|
"--bundle-id",
|
|
"inherit-fixture",
|
|
"--okf-version",
|
|
"0.2",
|
|
*extra,
|
|
]
|
|
)
|
|
== 0
|
|
)
|
|
return bundle
|
|
|
|
|
|
def test_the_door_writes_parent_as_the_ancestor_s_segment_id(tmp_path: Path) -> None:
|
|
concepts = _frontmatter(_build(tmp_path, "flagged", "--shell-parent"))
|
|
for title, ancestor in EXPECTED.items():
|
|
if ancestor is None:
|
|
assert "parent" not in concepts[title]
|
|
else:
|
|
assert concepts[title]["parent"] == concepts[ancestor]["segment_id"]
|
|
|
|
|
|
def test_a_build_without_the_flag_is_the_build_with_the_opt_out(tmp_path: Path) -> None:
|
|
default = _build(tmp_path, "default")
|
|
opted_out = _build(tmp_path, "opted-out", "--no-shell-parent")
|
|
flagged = _build(tmp_path, "flagged", "--shell-parent")
|
|
files = sorted(p.relative_to(default) for p in default.rglob("*") if p.is_file())
|
|
assert files == sorted(p.relative_to(opted_out) for p in opted_out.rglob("*") if p.is_file())
|
|
for relative in files:
|
|
assert (default / relative).read_bytes() == (opted_out / relative).read_bytes()
|
|
# The flag adds exactly one `parent:` line to each shell with an ancestor.
|
|
added = 0
|
|
for relative in files:
|
|
before = (default / relative).read_text(encoding="utf-8").split("\n")
|
|
after = (flagged / relative).read_text(encoding="utf-8").split("\n")
|
|
extra = [line for line in after if line not in before]
|
|
assert all(line.startswith("parent: ") for line in extra)
|
|
assert [line for line in after if not line.startswith("parent: ")] == before
|
|
added += len(extra)
|
|
assert added == sum(1 for ancestor in EXPECTED.values() if ancestor is not None)
|