Skip to content

feat: add requirement reference-coverage pie charts and gap tables to the platform verification report - #896

Merged
antonkri merged 14 commits into
mainfrom
feat/requirement-reference-coverage
Oct 7, 2026
Merged

antonkri merged 14 commits into
mainfrom
feat/requirement-reference-coverage

Conversation

@antonkri

@antonkri antonkri commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

What

Adds requirement reference coverage reporting to the platform verification report, in two parts.

1. Reference-coverage pie charts

For every requirement level, a pie chart showing how many requirements are referenced by at least one lower-hierarchy requirement:

  • Stakeholder requirements: referenced by a feat_req (via derived_from).
  • Feature requirements (per feature): referenced by a comp_req (via derived_from).
  • Component requirements: no lower level exists, so no chart is generated.

Only references from requirements within the report-version scope are counted (req_in_report_version).

2. Gap tables

Under each pie, a collapsible table listing exactly which requirements make up the red "not referenced" slice (id, title, safety, status):

  • Stakeholder section: Not referenced by feature requirements (n)
  • Each feature section: Not referenced by component requirements (n)

Collapsed by default (sphinx-design dropdown), so the report stays readable. The pie and the table are rendered from the same pre-scoped ID list, so they cannot disagree.

Previously proposed separately as #921; folded into this PR.

Why a table and not a column

An earlier revision added a Referenced by column (derived_from_back) to the requirement tables. That column was removed again in 0ec4ad8, because the raw backlink field is not report-version scoped: it could list a referencing requirement that the pie counts as "not referenced" (see this thread).

The distinction is structural:

determined by report-version scopable?
Row selection (:filter:) Jinja, before rendering yes
Cell content (:columns:) the Need object itself no

A scoped column would need the scoping to happen before sphinx-needs post-processing, which means metamodel changes. A scoped table only needs row selection, which the existing id_filter_list machinery already does. Hence the drill-down ships as rows.

Files

  • src/needs_templates/platform_verification_report.need
  • src/extensions/score_sphinx_needs_templates/tests/test_needs_templates.py

The report-specific centering CSS lives in the template's own raw:: html style block (as in module_verification_report.need) rather than in the globally loaded score_design.css, so no CSS asset hash changes and no expected-output fixtures are touched.

Validation

  • bazel test //src/extensions/score_sphinx_needs_templates:unit_tests: PASSED, including a new test asserting that a stakeholder requirement referenced only from outside the report version still shows up as a gap.
  • pytest src/tests/docs_bzl/test_docs_bzl_scenarios.py for all scenarios with expected HTML output: 20 passed.
  • minijinja standalone render of the template: valid RST, correct grid/indentation.

Draft for preview. A companion draft PR in reference_integration pins score_docs_as_code so the rendered result can be reviewed.

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Documentation preview for this pull request is available at:
pr-896: https://eclipse-score.github.io/docs-as-code/pr-896/

…y' column

Add per-requirement reference-coverage pie charts to the platform
verification report, showing how many requirements are referenced by at
least one lower-hierarchy requirement (stakeholder <- feature requirement,
feature requirement <- component requirement). Only references within the
report version scope are counted. Component requirements have no lower
level, so no chart is generated.

The stakeholder and feature requirement tables gain a 'Referenced by'
column (derived_from_back) listing referencing requirements.
Place the third pie chart (reference coverage), which wraps onto a row of
its own in the two-column pie grid, in the center via a new opt-in
`score-centered-grid-item` grid-item class.

The requirement tables gained a "Referenced by" column and became cramped;
give them an opt-in `score-wide-needs-table` class with a larger minimum
width and horizontal overflow so every column stays readable.
Remove the opt-in score-wide-needs-table class (min-width / horizontal
overflow) from the requirement needtables and its CSS, restoring the
table sizing to the state it had before the widening change. The
reference-coverage pie centering is kept.
@antonkri
antonkri force-pushed the feat/requirement-reference-coverage branch from d082b00 to b16a3dc Compare October 5, 2026 10:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The backlink column can contradict scoped chart results, and repeated full-graph scans introduce quadratic rendering cost.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds requirement-reference coverage reporting to platform verification reports.

Changes:

  • Adds stakeholder and feature reference-coverage pie charts and backlink columns.
  • Adds CSS to center wrapped pie charts.
  • Updates generated HTML expectations for the CSS hash.
File Description
src/​needs_templates/​platform_verification_report.need Adds reference coverage calculations, charts, and table columns.
src/​extensions/​score_layout/​assets/​css/​score_design.css Centers standalone grid items.
src/​tests/​docs_bzl/​scenarios/​reference_integration/​_expected/​docs/​modules/​modern_module/​components/​unlinked_component/​generated_metamodel/​index.html Updates CSS asset hash.
src/​tests/​docs_bzl/​scenarios/​nested_bundles/​_expected/​docs/​index.html Updates CSS asset hash.
src/​tests/​docs_bzl/​scenarios/​nested_bundles/​_expected/​docs/​concepts/​index.html Updates CSS asset hash.
src/​tests/​docs_bzl/​scenarios/​nested_bundles/​_expected/​docs/​concepts/​example_bundle/​child/​landing.html Updates CSS asset hash.
src/​tests/​docs_bzl/​scenarios/​external_bundle/​_expected/​docs/​index.html Updates CSS asset hash.
src/​tests/​docs_bzl/​scenarios/​data_files_runfiles/​_expected/​docs/​legacy_data_test/​index.html Updates CSS asset hash.
src/​tests/​docs_bzl/​scenarios/​data_files_runfiles/​_expected/​docs/​isolated_test/​index.html Updates CSS asset hash.
src/​tests/​docs_bzl/​scenarios/​data_files_runfiles/​_expected/​docs/​index.html Updates CSS asset hash.
src/​tests/​docs_bzl/​scenarios/​data_files_runfiles/​_expected/​docs/​data_test/​index.html Updates CSS asset hash.
src/​tests/​docs_bzl/​scenarios/​basic_docs/​_expected/​docs/​index.html Updates CSS asset hash.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/needs_templates/platform_verification_report.need Outdated
Comment thread src/needs_templates/platform_verification_report.need Outdated
The Stakeholder and Feature "Reference Coverage" pies previously scanned the
whole Need graph per requirement via linked_needs(id, "derived_from_back"),
which is O(all needs) each. Iterate the lower requirements once instead and
follow their cheap outgoing derived_from link; for Features the index is built
a single time before the per-feature loop, which then only tests membership.

Addresses Copilot review Finding 1 (performance).
@antonkri
antonkri force-pushed the feat/requirement-reference-coverage branch from 013c128 to 14aeb29 Compare October 6, 2026 09:31
@antonkri
antonkri marked this pull request as draft October 6, 2026 09:39
@antonkri
antonkri force-pushed the feat/requirement-reference-coverage branch from 14aeb29 to 1c70135 Compare October 6, 2026 09:40
antonkri and others added 2 commits October 6, 2026 10:43
The "Referenced by" column (backed by the unscoped `derived_from_back`
backlinks) was inconsistent with the report-version-scoped Reference Coverage
pie next to it. Remove it for now; a properly scoped approach will follow in a
separate PR.
@antonkri
antonkri marked this pull request as ready for review October 6, 2026 10:54
@antonkri

antonkri commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

@AlexanderLanin please review/approve

antonkri and others added 2 commits October 6, 2026 18:09
The reference coverage pies show *how many* requirements are not referenced by
any lower-level requirement, but not *which* ones. Add a collapsed table next
to each pie that lists exactly those requirements:

- Stakeholder section: stakeholder requirements not referenced by any in-scope
  feature requirement.
- Per feature: feature requirements not referenced by any in-scope component
  requirement.

Both tables are filtered by the same pre-scoped ID lists that feed the pies
(`ns_stkh_ref.missing` / `ns_feat_ref.missing`), so chart and table cannot
disagree. The report version scoping therefore applies to the tables as well: a
requirement referenced only from outside the report's `report_version` still
counts as a gap.

The tables are omitted entirely when there is no gap, so an empty table never
shows up.

Adds an end-to-end test rendering the report, covering the case where the only
reference comes from an out-of-scope requirement.
Comment on lines +55 to +60
/* Center a grid item that wraps onto a row of its own (e.g. the third pie
chart in the verification report's two-column pie grids). */
.score-centered-grid-item {
margin-left: auto;
margin-right: auto;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as this is platform verification report specific we should move it there?!

Example here:

.. raw:: html
<style>
.wp-doc-table td .needstable_wrapper,
.wp-doc-table td .pst-scrollable-table-container {
margin: 0; padding: 0; overflow: visible;
}
.wp-doc-table td table.NEEDS_TABLE,
.wp-doc-table td table.NEEDS_DATATABLES {
border: 0; margin: 0; box-shadow: none; background: transparent;
width: auto;
}
.wp-doc-table td table.NEEDS_TABLE thead,
.wp-doc-table td table.NEEDS_DATATABLES thead { display: none; }
.wp-doc-table td table.NEEDS_TABLE tbody tr,
.wp-doc-table td table.NEEDS_DATATABLES tbody tr { background: transparent; }
.wp-doc-table td table.NEEDS_TABLE tbody td,
.wp-doc-table td table.NEEDS_DATATABLES tbody td {
border: 0; padding: 0; background: transparent;
}
.wp-doc-table td .dataTables_wrapper .dataTables_length,
.wp-doc-table td .dataTables_wrapper .dataTables_filter,
.wp-doc-table td .dataTables_wrapper .dataTables_info,
.wp-doc-table td .dataTables_wrapper .dataTables_paginate { display: none; }
</style>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, done in 7f130f7 - moved into the template's own raw:: html style block, exactly like the module_verification_report.need example you linked.

It turned out to be worth more than just tidiness: .score-centered-grid-item is used only by platform_verification_report.need, and having those 7 lines in the globally loaded score_design.css changed its asset hash, which dragged 10 expected-output HTML fixtures into this PR. With the rule in the template, score_design.css is untouched, the hash stays at v=8048e6ee, and the PR is down to a single changed file.

Verified with pytest src/tests/docs_bzl/test_docs_bzl_scenarios.py across all scenarios that have expected HTML output: 20 passed.

@AlexanderLanin

Copy link
Copy Markdown
Member

Just a comment to the css. Since platform report is new and not established I dont see any need to discuss those changes at length.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The list-based reference indexes remain quadratic, and the PR metadata promises a deferred table column.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
Resolved since last review (2)

{% endfor %}
{% set ns_stkh_ref = namespace(referenced=[], missing=[]) %}
{% for req_id in ns_stkh_reqs.list %}
{% if req_id in ns_ref.up_ids %}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on the analysis, but it is not actionable inside the template: these templates are rendered by minijinja, which has no set or dict literal and no mutable containers - namespace + list concatenation is the only accumulator available. A real fix means moving the index construction into a Python helper in the render context (like the existing linked_needs / needs_of_type globals).

On the cost: the finding that mattered was the previous linked_needs(..., "derived_from_back") call per row, which was O(requirements x all needs) over the whole platform graph - that one is fixed. What remains is O(references^2) over the in-scope requirements of a single report, i.e. a few thousand comparisons at realistic sizes, against a Sphinx build that takes minutes. Not worth a new render-context API in this PR.

Tracking it as a follow-up rather than resolving it silently - happy to add a reference_index(...) helper if we see report sizes grow.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minininja has set in it's compatibility markdown file and claims it's feature parity.

So could you try and implement it?
It won't be the end of the world if it doesn't work, but gathering performance bonuses where we can is always a plus.

https://github.com/mitsuhiko/minijinja/blob/main/COMPATIBILITY.md#-set-

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The {% set %} section in COMPATIBILITY.md is under ## Blocks — it documents the assignment tag, not a set type. The template already uses it throughout.

Checked against minijinja 2.22:

construct result
{1, 2, 3} syntax error: unexpected ,, expected :
set(...) unknown function: set is unknown
{"a": 1} dict literal works

Passing a Python frozenset in through the render context does work (in resolves), but it does not buy anything. 1000 membership tests, all misses:

haystack time
list 1402 ms
frozenset 1286 ms
same logic in Python 0.12 ms

The cost is the Rust/Python boundary crossing per in, not the lookup itself. A set inside the template would not have fixed it; not looping in the template does.

Implemented in 164fbf0: two render-context helpers, referenced_ids (one forward pass over the lower requirements) and split_by_reference (partition). The template just calls them, 44 lines of loop logic removed.

stkh x feat before after
20 x 25 12.53 ms 0.09 ms 141x
300 x 600 72.16 ms 0.27 ms 265x
600 x 1200 362.2 ms 0.55 ms 663x

Quadratic -> linear. The benchmark asserts the rendered output is identical in every row; bazel test //src/extensions/score_sphinx_needs_templates:unit_tests passes.

At today's size (~20 stkh_req, ~25 feat_req) this is 12 ms against a multi-minute Sphinx build, so there is no measurable gain right now. The scaling is the point.

This also resolves the earlier Copilot finding in this thread, which I had deferred as "not actionable in the template" — it was actionable, one layer down.

.. grid-item::
:class: score-centered-grid-item

.. needpie:: Stakeholder Requirements Reference Coverage

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed - the title and description claimed the Referenced by column that was removed in 0ec4ad8.

Updated the title, rewrote the What section, and added an explicit What this PR does not add section explaining why the column was dropped (the raw backlink field is not report-version scoped) and that the drill-down ships as rows instead in the stacked follow-up #921. The Files list now matches the actual single changed file.

The `.score-centered-grid-item` rule is used only by
platform_verification_report.need, so it belongs next to the markup it
styles rather than in the globally loaded score_design.css. Moves it into
the template's existing `raw:: html` style block, following the pattern
already used by module_verification_report.need.

Side effect: score_design.css is unchanged again, so its asset hash stays
at v=8048e6ee and the ten expected-output HTML fixtures no longer need to
be touched by this PR.

Addresses review feedback from @AlexanderLanin.
@antonkri antonkri changed the title feat: add requirement reference-coverage pie charts and 'Referenced by' column feat: add requirement reference-coverage pie charts to the platform verification report Oct 7, 2026
FScholPer
FScholPer previously approved these changes Oct 7, 2026
@antonkri antonkri changed the title feat: add requirement reference-coverage pie charts to the platform verification report feat: add requirement reference-coverage pie charts and gap tables to the platform verification report Oct 7, 2026
@MaximilianSoerenPollak

Copy link
Copy Markdown
Contributor

One question here.

Is this just for information, or is there actually a reason to check this?
Like if there isn''t any lower reference is that then a failure and if so should we not catch that in the metamodel?

@antonkri

antonkri commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

One question here.

Is this just for information, or is there actually a reason to check this? Like if there isn''t any lower reference is that then a failure and if so should we not catch that in the metamodel?

@MaximilianSoerenPollak Good question! It is not a en error in terms of metamodel. During the lifecycle of the project, it is ok, that e.g. not all stakeholder requirements are referenced by feature requirements at the beginning, as the implementation or break down is not there. But it is important to track this relationship to be able to judge about the completeness of the project. e.g. If not all feature requirements are referenced at least by one component requirement, then it is a metric, that shows, that feature implementation is not complete.

The reference-coverage sections built their index and partitioned the
requirements with MiniJinja loops. Every `in` test and every
`list + [item]` append crossed the Rust/Python boundary, which dominated
the cost and scaled quadratically.

Add two render-context helpers, `referenced_ids` and `split_by_reference`,
and let the template call them instead. Output is unchanged.

Measured on the stakeholder block (identical rendered output):

    stkh x feat |      old |     new | speedup
     20 x   25  | 12.53 ms | 0.09 ms |    141x
    300 x  600  | 72.16 ms | 0.27 ms |    265x
    600 x 1200  | 362.2 ms | 0.55 ms |    663x

Note that a `set` is not available inside the template: MiniJinja has no
set literal and no `set()` function, and handing a Python `set` into the
template does not help either, because the boundary crossing rather than
the lookup is what costs (frozenset was only ~8% faster than a list).
@antonkri

antonkri commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

@MaximilianSoerenPollak Informational, deliberately.

It cannot be a metamodel check, because "not referenced" is not a property of the need — it is relative to report_version. The same stkh_req is uncovered in v1.0 and covered in v2.0. report_version is an option on the document need and only exists at render time; metamodel checks run before that and have no report context. They could only answer "referenced ever?", which is the wrong question for a versioned report.

The model is also permissive here by design: derived_from sits under optional_links for feat_req and comp_req, and stkh_req declares no links at all. A stakeholder requirement with no refinement yet is a normal intermediate state during incremental development, not a modelling error — failing the build on it would block exactly the case the report is meant to make visible.

Enforcement already exists, one level up: scripts_bazel/traceability_gate.py reads the metrics.json produced by the docs build and fails CI on configurable thresholds. That is the right place for "too little coverage = red". The report provides the visibility and the drill-down into which requirements are affected.

@MaximilianSoerenPollak MaximilianSoerenPollak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like this much more, moving hte logic to python.
💯

@MaximilianSoerenPollak

Copy link
Copy Markdown
Contributor

@AlexanderLanin are all of your questions answered & okay to merge?

@antonkri
antonkri merged commit 36cdc3f into main Oct 7, 2026
35 checks passed
@antonkri
antonkri deleted the feat/requirement-reference-coverage branch October 7, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

5 participants