test(witness): the JSON role map sees mml:math and figcaption (H3, red)

PM measured it on N200 Vegbygging:2024: 74 `mml:math` carrying 2 811
characters and 49 `figcaption`, and the witness counted 0 and 0. Two
independent causes and both are in this red:

- `count_sts_xml` takes every tag through `_local`, which strips a
  Clark-notation namespace AND a prefix; `count_sts_json` compared the
  raw string, so `mml:math` never reached `tag == "math"`.
- the publisher's JSON puts a figure's caption in `figcaption` under
  the `graphic`, not in the `fig/caption` NISO-STS writes. Measured
  over the eight deliveries on this machine: 430 `figcaption`.

Three tests. The JSON half of `sts-rikt.xml`'s family carries both
shapes as a committed fixture; the real N200 carries PM's four numbers
and skips where the corpus is absent; and the known-negative for
stripping a prefix is N101's 574 `tbx:` tags, whose local names name
no role -- a generalising rule measured against what it must NOT take.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-19 05:54:01 +02:00
commit 37599a3758
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
2 changed files with 76 additions and 0 deletions

View file

@ -0,0 +1,37 @@
{
"productInfo": {"meta": {"productTitle": "Formula and figure caption"}},
"standardContent": {
"id": null, "tag": "#document", "a": {},
"c": [
{"e": 1, "t": null, "x": {"id": null, "tag": "standard", "a": {}, "c": [
{"e": 1, "t": null, "x": {"id": null, "tag": "body", "a": {}, "c": [
{"e": 1, "t": null, "x": {"id": null, "tag": "sec", "a": {}, "c": [
{"e": 1, "t": null, "x": {"id": null, "tag": "title", "a": {}, "c": [
{"e": 2, "t": "Vegdekker", "x": null}
]}},
{"e": 1, "t": null, "x": {"id": null, "tag": "p", "a": {}, "c": [
{"e": 2, "t": "Kravet regnes som ", "x": null},
{"e": 1, "t": null, "x": {"id": null, "tag": "inline-formula", "a": {}, "c": [
{"e": 1, "t": null, "x": {"id": null, "tag": "mml:math", "a": {}, "c": [
{"e": 2, "t": "IRI < 2", "x": null}
]}}
]}}
]}},
{"e": 1, "t": null, "x": {"id": null, "tag": "fig", "a": {}, "c": [
{"e": 1, "t": null, "x": {"id": null, "tag": "graphic", "a": {"href": "figur-85-1.png"}, "c": [
{"e": 1, "t": null, "x": {"id": null, "tag": "figcaption", "a": {}, "c": [
{"e": 1, "t": null, "x": {"id": null, "tag": "graphic-caption-label", "a": {}, "c": [
{"e": 2, "t": "Figur 85-1", "x": null}
]}},
{"e": 1, "t": null, "x": {"id": null, "tag": "graphic-caption-title", "a": {}, "c": [
{"e": 2, "t": "Maalepunkter langs vegbanen.", "x": null}
]}}
]}}
]}}
]}}
]}}
]}}
]}}
]
}
}

View file

@ -350,6 +350,45 @@ def test_the_sts_witness_counts_citations_formulas_and_figure_captions() -> None
assert inventory.elements[role] > 0, role
def test_the_json_role_map_reads_a_prefixed_tag_and_the_publishers_figcaption() -> None:
"""H3, measured by PM on N200 2026-09-18: the delivery carries 74
`mml:math` and 49 `figcaption`, and the witness counted 0 and 0.
`count_sts_xml` takes every tag through `_local`, which strips both a
Clark-notation namespace and a prefix; `count_sts_json` compared the RAW
string, so `mml:math` never reached `tag == "math"`. And the publisher's
JSON puts a figure's caption in `figcaption` under the `graphic`, not in
the `fig/caption` NISO-STS writes -- measured over the eight deliveries on
this machine: 430 `figcaption`, 0 of them under a `caption`.
The fixture is the JSON half of `sts-rikt.xml`'s family, and both halves
of the defect are in it."""
twin = gate.FIXTURES / "witness" / "sts-mml-and-figcaption.twin.json"
counts = witness.count_sts_json(twin.read_bytes()).counts
assert counts["math"] == 1
assert counts["figure_caption"] == 1
def test_a_prefixed_tag_that_names_no_role_stays_uncounted() -> None:
"""The known-negative for stripping the prefix: N101 ships 574 `tbx:`
tags, and not one of their local names is a role. A rule that generalises
is measured against what it must NOT take."""
for tag in ("tbx:term", "tbx:definition", "tbx:note", "tbx:termEntry"):
assert witness._sts_role_json(witness._local(tag), "sec", "body") is None
@pytest.mark.skipif(not gate.N200_DEFAULT.is_file(), reason="N200 is not on this machine")
def test_the_json_role_map_counts_n200s_own_formulas_and_figure_captions() -> None:
"""The same defect on the delivery it was found in, with PM's numbers.
Skipped where the corpus is absent, and then this file's own fixture is
the only thing holding the rule -- which is why both exist."""
counts = witness.count_sts_json(gate.N200_DEFAULT.read_bytes()).counts
assert counts["math"] == 74
assert counts["figure_caption"] == 49
assert counts["citation"] == 194
assert counts["figure"] == 49
def test_the_two_sts_role_maps_are_written_twice_and_not_shared() -> None:
"""M-2: both STS witnesses went through ONE `_sts_role`, so row 5 could
never see a hole in it. Two maps, each written for its own delivery."""