coverage¶
Read an ODD's own decisions back against the ODD source: which models fired,
which can never fire, and which elements produced no output at all. See the
ODD coverage guide for what each finding means; the CLI
wraps analyze and renders CoverageReport.to_dict().
opm.coverage ¶
What an ODD declares, and what of it actually runs.
-t json records every decision the processing model made on one document.
Coverage rolls those records up across a corpus and reads them against the ODD
itself, to answer the two questions an ODD author keeps asking:
- What did I write that never runs? Models that never fired, models that
cannot fire because an earlier sibling has no
@predicate, andelementSpecs for elements the corpus never contains. - What is in my documents that I never handled? Elements no model matched at
all (
unmatchedrecords), and elements whose spec exists but whose every predicate was false — those produce no record whatsoever, which is exactly why they are hard to notice by hand.
Models inherited from an extended ODD are reported separately and never counted
as the author's problem: a local elementSpec replaces the inherited one
wholesale, so an inherited model is only changeable by redeclaring the element.
The locality split comes from opm.odd_compiler.parse_odd.spec_origin,
surfaced in the compiled ODD_MODELS table as source.
Coverage transforms whole documents; it ignores [chunking], since a chunk
selector may not cover the document and coverage is about the ODD, not the site.
Occurrence
dataclass
¶
Occurrence(
element: str,
count: int = 0,
xpath: str | None = None,
file: str | None = None,
line: int | None = None,
)
Where an element first turned up, and how often it did.
ModelInfo
dataclass
¶
ModelInfo(
key: str,
element: str,
behaviour: str | None = None,
predicate: str | None = None,
desc: str | None = None,
output: str | None = None,
source: str | None = None,
template: bool = False,
hits: int = 0,
unreachable: str | None = None,
)
One ODD_MODELS entry plus the verdict on it.
emits_records
property
¶
Whether firing this model would be visible in the JSON records.
A model with neither @behaviour nor a pb:template compiles to a
bare recursion into the children: it produces no record, so a zero hit
count says nothing about whether it ran.
CoverageReport
dataclass
¶
CoverageReport(
odd: Path,
channel: str,
documents: list[Path] = list(),
models: dict[str, ModelInfo] = dict(),
behaviours: Counter = Counter(),
elements_seen: Counter = Counter(),
records: int = 0,
suppressed: int = 0,
unmatched: dict[str, Occurrence] = dict(),
dropped: dict[str, Occurrence] = dict(),
unused_specs: list[dict] = list(),
attribute_only_specs: list[str] = list(),
unsupported: list[dict] = list(),
)
The full diagnostic picture of one ODD against one corpus.
unused_models ¶
unused_models() -> list[ModelInfo]
Local models that could have fired on this corpus and did not.
Restricted to models whose element the corpus actually contains: a model for an element that never appears says nothing about the model, and those elements are already listed as unexercised specs. Unreachable models are excluded too — they are reported on their own, with the reason, and listing them twice would only pad the actionable list.
Source code in src/opm/coverage.py
iter_element_paths ¶
Yield (element, xpath) for the whole tree, top-down.
The paths are the ones -t json records
(opm.runtime.json_output_functions._element_path), which is what
makes "this element produced no record" answerable by set difference. They
are built once on the way down rather than reconstructed per element: the
bottom-up version rescans the siblings at every step, which turns a big
document into a quadratic walk.
Source code in src/opm/coverage.py
unreachable_models ¶
Models that can never fire, mapped to the reason why.
Mirrors the dispatch the code generator emits: conditional models become an
if/elif chain in document order and the first model without a
predicate becomes the else. Two consequences, both silent in the ODD:
when the very first model has no predicate the rest of the spec is dropped
on the floor, and any further unconditional model after the first can never
be the fallback.
Source code in src/opm/coverage.py
analyze ¶
analyze(
paths,
*,
cfg=None,
odd: Path | None = None,
output_mode: str = "json",
parameters: dict | None = None,
base_css: str | None = None,
) -> CoverageReport
Run paths through the ODD in JSON mode and report on the outcome.
output_mode is a JSON mode (json, json-typst, …); the channel it
inspects decides which @output-tagged models participate, so a coverage
run is always about one channel. base_css is passed on to the compiler
(see resolve_transform_module).
Source code in src/opm/coverage.py
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 | |