llm-ingestion-okf/tests/fixtures/accounting/make_accounting_fixtures.py
Kjell Tore Guttormsen 9d1f4b14ed test(fixtures): replace sector-specific example material with generic, fictitious examples — green
Every fixture, test document, tool example and document now uses an invented
kitchen-and-baking handbook series, written in this repository. The package's
behaviour is unchanged; src/ changes are comments and help text only.

- Generated fixtures are regenerated from their generators. Their structural
  counts are identical before and after: elements, images, rows, cells,
  headings, bookmarks and the witness inventory's per-document totals. The
  image-inbox and accounting documents are renamed kapittel-84-*.
- tools/okf_accounting_gate.py: the two options that named one real corpus
  each are replaced by a generic, repeatable --corpus PATH with no default.
  Row 5 compares the PDF pair alone. Gate verdict unchanged: RED rows 2, 3, 6.
- tools/okf_witness.py: the STS JSON reader for one publisher's delivery is
  removed, along with its three twins and five tests. The mutation harness
  loses W09.
- docs/: 13 dated reports that documented runs on a retired reference corpus
  are removed, and 40 are neutralized. Dead links are removed, and no new
  dangling path is introduced.
- The synthetic MCP-gate corpus and the residual probe words are neutral.

Valgt: keep the `okf quality --fasit` bar value (the measured fraction, one corpus) and
rewrite only its provenance, because the verdict stays unchanged and the
number names nothing.

Term check with the local list: 0 of 411 tracked files, 0 file names, 0 of
27 binary fixtures. Suite after git add: 2457 passed, 1 skipped. The base
tree had 2460 passed and 2 skipped; five tests went with the JSON reader and
four were added by the term check. ruff, ruff format and mypy --strict src/
are clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 14:52:02 +02:00

357 lines
17 KiB
Python

"""The accounting corpus's SECOND document per format: the elements the
witness could not see until 2026-09-18.
An independent review of the gate found that the witness -- and therefore the
whole accounting, because rows 2 and 3 require the build's inventory to EQUAL
it -- counted no header, no comment, no speaker note, no hidden sheet, no
formula, no citation and no figure caption. What nothing counts, nothing can
lose visibly. It also found that 20 of 63 element types had a count of ZERO in
their only fixture, so six of seven witness mutants survived.
These documents are written BY HAND, part by part, for the reason the XML
fixtures state: a library that writes and then reads its own format proves
only that it agrees with itself. Every count they carry is written down in
`tests/fixtures/README.md` by a person reading these strings, not by running
the witness over them.
python3 tests/fixtures/accounting/make_accounting_fixtures.py
"""
from __future__ import annotations
import io
import zipfile
from pathlib import Path
HERE = Path(__file__).resolve().parent
CORPUS = HERE / "corpus"
_ZIP_DATE = (2020, 1, 1, 0, 0, 0)
_XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
_W = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"'
_A = 'xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"'
_P = 'xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"'
_S = 'xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"'
def build_zip(parts: dict[str, str | bytes]) -> bytes:
"""Zip the parts with a fixed timestamp, so a regeneration that changes
nothing leaves the bytes alone and `git diff --quiet` stays a real check."""
out = io.BytesIO()
with zipfile.ZipFile(out, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for name, payload in parts.items():
info = zipfile.ZipInfo(name, date_time=_ZIP_DATE)
info.compress_type = zipfile.ZIP_DEFLATED
archive.writestr(info, payload)
return out.getvalue()
# --- docx: a header, a footer, a comment, an endnote and a text box ----------
_DOCX_BODY = (
'<w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr>'
"<w:r><w:t>Krav til bakebrett</w:t></w:r></w:p>"
"<w:p><w:r><w:t>Bakebrett skal ha kant paa begge sider.</w:t></w:r></w:p>"
"<w:tbl><w:tr>"
"<w:tc><w:p><w:r><w:t>Bredde</w:t></w:r></w:p></w:tc>"
"<w:tc><w:p><w:r><w:t>0,4 m</w:t></w:r></w:p></w:tc>"
"</w:tr></w:tbl>"
"<w:p><w:r><w:pict><w:txbxContent>"
"<w:p><w:r><w:t>Merk: kravet gjelder ikke engangsbrett.</w:t></w:r></w:p>"
"</w:txbxContent></w:pict></w:r></w:p>"
)
_DOCX_PARTS: dict[str, str | bytes] = {
"[Content_Types].xml": _XML
+ '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
+ '<Default Extension="xml" ContentType="application/xml"/>'
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.'
+ 'relationships+xml"/>'
+ '<Override PartName="/word/document.xml" ContentType="application/vnd.'
+ 'openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
+ '<Override PartName="/word/styles.xml" ContentType="application/vnd.'
+ 'openxmlformats-officedocument.wordprocessingml.styles+xml"/>'
+ "</Types>",
"_rels/.rels": _XML
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/officeDocument" Target="word/document.xml"/></Relationships>',
"word/_rels/document.xml.rels": _XML
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/styles" Target="styles.xml"/></Relationships>',
"word/styles.xml": _XML
+ f"<w:styles {_W}>"
+ '<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/></w:style>'
+ "</w:styles>",
"word/document.xml": _XML + f"<w:document {_W}><w:body>{_DOCX_BODY}</w:body></w:document>",
"word/header1.xml": _XML
+ f"<w:hdr {_W}><w:p><w:r><w:t>Utkast - gjelder ikke etter 2026-01-01</w:t></w:r></w:p>"
+ "</w:hdr>",
"word/footer1.xml": _XML
+ f"<w:ftr {_W}><w:p><w:r><w:t>Eksempelforlaget, side 1</w:t></w:r></w:p></w:ftr>",
"word/comments.xml": _XML
+ f"<w:comments {_W}>"
+ '<w:comment w:id="1"><w:p><w:r><w:t>Unntak: gjelder IKKE bakebrett i kjeller.'
+ "</w:t></w:r></w:p></w:comment></w:comments>",
"word/footnotes.xml": _XML
+ f"<w:footnotes {_W}>"
+ '<w:footnote w:id="0"><w:p><w:r><w:t>separator</w:t></w:r></w:p></w:footnote>'
+ '<w:footnote w:id="2"><w:p><w:r><w:t>Se kokebok Q400 kapittel 5.</w:t></w:r></w:p>'
+ "</w:footnote></w:footnotes>",
"word/endnotes.xml": _XML
+ f"<w:endnotes {_W}>"
+ '<w:endnote w:id="0"><w:p><w:r><w:t>separator</w:t></w:r></w:p></w:endnote>'
+ '<w:endnote w:id="3"><w:p><w:r><w:t>Kravet ble skjerpet i 2024.</w:t></w:r></w:p>'
+ "</w:endnote></w:endnotes>",
}
# --- pptx: a speaker note and a hidden slide --------------------------------
def _slide(title: str, body: str, *, hidden: bool = False) -> str:
show = ' show="0"' if hidden else ""
return (
_XML
+ f"<p:sld {_P} {_A}{show}><p:cSld><p:spTree>"
+ '<p:sp><p:nvSpPr><p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr>'
+ f"<p:txBody><a:p><a:r><a:t>{title}</a:t></a:r></a:p></p:txBody></p:sp>"
+ "<p:sp><p:nvSpPr><p:nvPr/></p:nvSpPr>"
+ f"<p:txBody><a:p><a:r><a:t>{body}</a:t></a:r></a:p></p:txBody></p:sp>"
+ "<p:graphicFrame><a:graphic><a:graphicData "
+ 'uri="http://schemas.openxmlformats.org/drawingml/2006/table">'
+ "<a:tbl><a:tr>"
+ "<a:tc><a:txBody><a:p><a:r><a:t>Post</a:t></a:r></a:p></a:txBody></a:tc>"
+ "<a:tc><a:txBody><a:p><a:r><a:t>84.1</a:t></a:r></a:p></a:txBody></a:tc>"
+ "</a:tr></a:tbl></a:graphicData></a:graphic></p:graphicFrame>"
+ "</p:spTree></p:cSld></p:sld>"
)
_PPTX_PARTS: dict[str, str | bytes] = {
"[Content_Types].xml": _XML
+ '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
+ '<Default Extension="xml" ContentType="application/xml"/>'
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.'
+ 'relationships+xml"/>'
+ '<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.'
+ 'openxmlformats-officedocument.presentationml.presentation.main+xml"/>'
+ "".join(
f'<Override PartName="/ppt/slides/slide{n}.xml" ContentType="application/vnd.'
f'openxmlformats-officedocument.presentationml.slide+xml"/>'
for n in (1, 2)
)
+ '<Override PartName="/ppt/notesSlides/notesSlide1.xml" ContentType="application/vnd.'
+ 'openxmlformats-officedocument.presentationml.notesSlide+xml"/>'
+ "</Types>",
"_rels/.rels": _XML
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/officeDocument" Target="ppt/presentation.xml"/></Relationships>',
"ppt/_rels/presentation.xml.rels": _XML
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/slide" Target="slides/slide1.xml"/>'
+ '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/slide" Target="slides/slide2.xml"/></Relationships>',
"ppt/slides/_rels/slide1.xml.rels": _XML
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/notesSlide" Target="../notesSlides/notesSlide1.xml"/></Relationships>',
"ppt/presentation.xml": _XML
+ f"<p:presentation {_P}><p:sldIdLst>"
+ '<p:sldId id="256" r:id="rId1"/><p:sldId id="257" r:id="rId2"/>'
+ "</p:sldIdLst></p:presentation>",
"ppt/slides/slide1.xml": _slide("Kapittel 84 Boller", "Hevetider er gitt i tabell."),
"ppt/slides/slide2.xml": _slide("Utgaatt lysbilde", "Ikke vis dette.", hidden=True),
"ppt/notesSlides/notesSlide1.xml": _XML
+ f"<p:notes {_P} {_A}><p:cSld><p:spTree><p:sp><p:txBody>"
+ "<a:p><a:r><a:t>Husk aa nevne at hevetidsklassen er skjerpet.</a:t></a:r></a:p>"
+ "</p:txBody></p:sp></p:spTree></p:cSld></p:notes>",
}
# --- xlsx: a hidden sheet and a formula --------------------------------------
_XLSX_STRINGS = ["Post", "Enhet", "Mengde", "Sum", "Internt", "Kladd"]
_XLSX_PARTS: dict[str, str | bytes] = {
"[Content_Types].xml": _XML
+ '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
+ '<Default Extension="xml" ContentType="application/xml"/>'
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.'
+ 'relationships+xml"/>'
+ '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-'
+ 'officedocument.spreadsheetml.sheet.main+xml"/>'
# Declaring the parts is not decoration. Without the sharedStrings
# override every `t="s"` cell converts to an EMPTY cell -- the fixture
# would have reported four cells lost that the build never lost.
+ '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.'
+ 'openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
+ '<Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.'
+ 'openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
+ '<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.'
+ 'openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>'
+ "</Types>",
"_rels/.rels": _XML
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>',
"xl/workbook.xml": _XML
+ f'<workbook {_S} xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships"><sheets>'
+ '<sheet name="Mengder" sheetId="1" r:id="rId1"/>'
+ '<sheet name="Internt" sheetId="2" state="hidden" r:id="rId2"/>'
+ "</sheets></workbook>",
"xl/_rels/workbook.xml.rels": _XML
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/worksheet" Target="worksheets/sheet1.xml"/>'
+ '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/worksheet" Target="worksheets/sheet2.xml"/>'
# The shared string table is reached through the WORKBOOK's relationship,
# not by its path: without this line every `t="s"` cell converts empty and
# the fixture reports four cells lost that the build never lost.
+ '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/sharedStrings" Target="sharedStrings.xml"/></Relationships>',
"xl/sharedStrings.xml": _XML
+ f'<sst {_S} count="{len(_XLSX_STRINGS)}" uniqueCount="{len(_XLSX_STRINGS)}">'
+ "".join(f"<si><t>{value}</t></si>" for value in _XLSX_STRINGS)
+ "</sst>",
"xl/worksheets/sheet1.xml": _XML
+ f'<worksheet {_S}><dimension ref="A1:C3"/><sheetData>'
+ '<row r="1"><c r="A1" t="s"><v>0</v></c><c r="B1" t="s"><v>1</v></c>'
+ '<c r="C1" t="s"><v>2</v></c></row>'
+ '<row r="2"><c r="A2" t="s"><v>3</v></c><c r="B2"><v>12</v></c>'
+ '<c r="C2"><f>B2*2</f><v>24</v></c></row>'
+ '</sheetData><drawing r:id="rId1" xmlns:r="http://schemas.openxmlformats.org/'
+ 'officeDocument/2006/relationships"/></worksheet>',
"xl/drawings/drawing1.xml": _XML
+ '<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/'
+ 'spreadsheetDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">'
+ "<xdr:twoCellAnchor><xdr:pic><xdr:nvPicPr>"
+ '<xdr:cNvPr id="1" name="Diagram"/>'
+ "</xdr:nvPicPr></xdr:pic></xdr:twoCellAnchor></xdr:wsDr>",
"xl/worksheets/_rels/sheet1.xml.rels": _XML
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/'
+ 'relationships/drawing" Target="../drawings/drawing1.xml"/></Relationships>',
"xl/worksheets/sheet2.xml": _XML
+ f'<worksheet {_S}><dimension ref="A1:A1"/><sheetData>'
+ '<row r="1"><c r="A1" t="s"><v>5</v></c></row>'
+ "</sheetData></worksheet>",
}
# --- odt: a header, a list, an annotation and a picture ----------------------
_ODT_NS = (
'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '
'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '
'xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" '
'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" '
'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" '
'xmlns:xlink="http://www.w3.org/1999/xlink"'
)
_ODT_PARTS: dict[str, str | bytes] = {
"mimetype": "application/vnd.oasis.opendocument.text",
"META-INF/manifest.xml": _XML
+ '<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">'
+ '<manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.'
+ 'opendocument.text"/>'
+ '<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>'
+ "</manifest:manifest>",
"content.xml": _XML
+ f'<office:document-content {_ODT_NS} office:version="1.3">'
+ "<office:body><office:text>"
+ '<text:h text:outline-level="1">Stell av bakebrett</text:h>'
+ "<text:p>Bakebrett vaskes hver uke.</text:p>"
+ "<text:list><text:list-item><text:p>Kant</text:p></text:list-item>"
+ "<text:list-item><text:p>Flate</text:p></text:list-item></text:list>"
+ "<text:p>Se figuren under."
+ '<draw:frame><draw:image xlink:href="graphics/figur-84-1.png"/></draw:frame></text:p>'
+ "<office:annotation><text:p>Sjekk denne mot Q400 foer utsendelse.</text:p>"
+ "</office:annotation>"
+ "<table:table><table:table-row>"
+ "<table:table-cell><text:p>Type</text:p></table:table-cell>"
+ "<table:table-cell><text:p>Bakebrett</text:p></table:table-cell>"
+ "</table:table-row></table:table>"
+ "</office:text></office:body></office:document-content>",
"styles.xml": _XML
+ f'<office:document-styles {_ODT_NS} office:version="1.3">'
+ "<office:styles/><office:master-styles>"
+ '<style:master-page style:name="Standard">'
+ "<style:header><text:p>Intern arbeidsversjon</text:p></style:header>"
+ "<style:footer><text:p>Eksempelforlaget</text:p></style:footer>"
+ "</style:master-page></office:master-styles></office:document-styles>",
}
# --- rtf: a picture ----------------------------------------------------------
_RTF = (
r"{\rtf1\ansi\deff0{\fonttbl{\f0 Times New Roman;}}"
r"\pard Figur 84-1 viser prinsippet.\par"
r"\pard{\pict\pngblip\picw16\pich16 89504e470d0a1a0a}\par"
"}"
)
# --- html: a picture and a caption -------------------------------------------
_HTML = """<!DOCTYPE html>
<html lang="no">
<head><title>Figur 84-1</title></head>
<body>
<h1>Figur 84-1</h1>
<p>Prinsippet for hevetidsklasser.</p>
<img src="graphics/figur-84-1.png" alt="Prinsippskisse">
<table><tr><th>Klasse</th><th>Avvik</th></tr><tr><td>A</td><td>5 min</td></tr></table>
<ul><li>Klasse A</li><li>Klasse B</li></ul>
</body>
</html>
"""
# --- sts: a citation, a formula, a figure with a caption, a table, a footnote -
_STS = """<standard>
<front><std-ident><doc-number>P762</doc-number><year>2025</year></std-ident></front>
<body>
<sec><label>85</label><title>Bakeplater</title>
<p>Platen skal ha jevn varme etter <mixed-citation>EKS 1234-1:2030</mixed-citation>.</p>
<p>Kravet regnes som <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>T</mml:mi><mml:mo>&lt;</mml:mo><mml:mn>2</mml:mn></mml:math>.</p>
<fig><label>Figur 85-1</label><caption><p>Maalepunkter langs bakeplaten.</p></caption>
<graphic xlink:href="figur-84-1.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></fig>
<table-wrap><label>Tabell 85-1</label>
<table><tr><th>Klasse</th><th>T</th></tr><tr><td>1</td><td>1,5</td></tr></table>
</table-wrap>
<list><list-item><p>Maales hver 20. centimeter.</p></list-item></list>
<fn><p>Gjelder ikke steinovner.</p></fn>
</sec>
</body>
</standard>
"""
def main() -> int:
written = {
"topptekst-og-kommentar.docx": build_zip(_DOCX_PARTS),
"notater-og-skjult.pptx": build_zip(_PPTX_PARTS),
"skjult-ark-og-formel.xlsx": build_zip(_XLSX_PARTS),
"liste-og-bilde.odt": build_zip(_ODT_PARTS),
"bilde.rtf": _RTF.encode("latin-1"),
"figur.html": _HTML.encode("utf-8"),
"sts-rikt.xml": _STS.encode("utf-8"),
}
for name, payload in written.items():
(CORPUS / name).write_bytes(payload)
print(f"wrote {name}")
return 0
if __name__ == "__main__":
raise SystemExit(main())