Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
eca3885
feat(metrics): add verification metrics for centralized report genera…
FScholPer Oct 13, 2026
b7fdb52
feat(metrics): add version scoping to centralized verification metrics
FScholPer Oct 13, 2026
1529431
fix(metrics): handle string-or-list for satisfied_by and belongs_to
FScholPer Oct 13, 2026
85a9215
docs: document metrics.json content including verification_metrics
FScholPer Oct 13, 2026
b1d44f9
style: fix lint and type issues for centralized metrics
FScholPer Oct 13, 2026
e8d1371
feat: make version-scoping helpers public for reuse
FScholPer Oct 13, 2026
3e3d1e9
fix: add missing Bazel dependency on score_sphinx_needs_templates
FScholPer Oct 13, 2026
24fcaf2
move to the helpers
FScholPer Oct 14, 2026
d2cc7ea
precommit fix
FScholPer Oct 14, 2026
b5b1006
fix lint
FScholPer Oct 14, 2026
3620f2f
copillot findings
FScholPer Oct 14, 2026
75d635f
feat(metrics): add verification metrics for centralized report genera…
FScholPer Oct 13, 2026
b581e48
feat(metrics): add version scoping to centralized verification metrics
FScholPer Oct 13, 2026
bbd03e8
fix(metrics): handle string-or-list for satisfied_by and belongs_to
FScholPer Oct 13, 2026
e851331
docs: document metrics.json content including verification_metrics
FScholPer Oct 13, 2026
be88d93
style: fix lint and type issues for centralized metrics
FScholPer Oct 13, 2026
af9e728
feat: make version-scoping helpers public for reuse
FScholPer Oct 13, 2026
2991bf9
fix: add missing Bazel dependency on score_sphinx_needs_templates
FScholPer Oct 13, 2026
24d3683
move to the helpers
FScholPer Oct 14, 2026
e4c1b62
precommit fix
FScholPer Oct 14, 2026
271fc66
fix lint
FScholPer Oct 14, 2026
747459b
copillot findings
FScholPer Oct 14, 2026
d7cb8cb
Merge branch 'feat/centralize-verification-metrics' of https://github…
FScholPer Oct 16, 2026
a539af8
Merge remote-tracking branch 'origin/main' into feat/centralize-verif…
FScholPer Oct 16, 2026
611ff5c
fix centralization
FScholPer Oct 16, 2026
481318c
fix: restore the ASIL/QM module report and decode filter-func arguments
antonkri Oct 16, 2026
553147b
feat: count the module report pies with the central filter functions
antonkri Oct 16, 2026
e9c3a81
test: satisfy BasedPyright in the component filter tests
antonkri Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/how-to/dashboards_and_quality_gates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ The documentation build writes ``metrics.json`` via ``score_metrics``, and the `

The dashboard charts and the CI gate both use the same computed metrics.

``metrics.json`` Content
~~~~~~~~~~~~~~~~~~~~~~~~

The exported file contains a versioned set of fields:

- ``schema_version`` – schema version identifier.
- ``generated_by`` – identifier of the producer that generated this file.
- ``overall_metrics`` – requirement coverage aggregated over all requirement types.
- ``metrics_by_type`` – the same coverage numbers per requirement type.
- ``tests`` – testcase linkage statistics, including broken test references.
- ``verification_metrics`` – per-component verification status and coverage metrics
for use by verification reports. Contains status distribution (valid, valid_inspected,
invalid) and test coverage (fully_covered, partially_covered, not_covered) for
both requirements and architecture elements, organized by component ID.

Inputs for Linkage Metrics
--------------------------

Expand Down
72 changes: 72 additions & 0 deletions scripts_bazel/traceability_metrics_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@
},
"tests": {
"$ref": "#/$defs/testMetrics"
},
"verification_metrics": {
"type": "object",
"description": "Per-component verification status and coverage metrics for verification reports.",
"additionalProperties": {
"$ref": "#/$defs/componentVerificationMetrics"
},
"propertyNames": {
"type": "string",
"minLength": 1
}
}
},
"$defs": {
Expand Down Expand Up @@ -134,6 +145,67 @@
"type": "number",
"minimum": 0,
"maximum": 100
},
"componentVerificationMetrics": {
"type": "object",
"description": "Verification metrics for a single component.",
"additionalProperties": false,
"properties": {
"requirements": {
"$ref": "#/$defs/statusAndCoverageMetrics"
},
"architecture": {
"$ref": "#/$defs/statusAndCoverageMetrics"
}
}
},
"statusAndCoverageMetrics": {
"type": "object",
"description": "Status distribution and test coverage metrics.",
"additionalProperties": false,
"properties": {
"status": {
"type": "object",
"description": "Count by status value.",
"additionalProperties": false,
"properties": {
"valid": {
"type": "integer",
"minimum": 0
},
"valid_inspected": {
"type": "integer",
"minimum": 0
},
"invalid": {
"type": "integer",
"minimum": 0
}
}
},
"coverage": {
"type": "object",
"description": "Test coverage by verification links.",
"additionalProperties": false,
"properties": {
"fully_covered": {
"type": "integer",
"minimum": 0,
"description": "Items with fully_verifies_back link."
},
"partially_covered": {
"type": "integer",
"minimum": 0,
"description": "Items with only partially_verifies_back link."
},
"not_covered": {
"type": "integer",
"minimum": 0,
"description": "Items with no verification links."
}
}
}
}
}
}
}
1 change: 1 addition & 0 deletions src/extensions/score_metrics/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ py_library(
# TODO: Figure out if all requirements are needed or if we can break it down a bit
deps = all_requirements + [
"@score_docs_as_code//src/helper_lib",
"@score_docs_as_code//src/extensions/score_sphinx_needs_templates",
],
)

Expand Down
93 changes: 93 additions & 0 deletions src/extensions/score_metrics/sphinx_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,35 @@ def func(needs: list[NeedItem], results: list[int], **kwargs) -> None: ...
from typing import Any

from score_metrics.traceability_metrics import CALCULATED_METRICS

# Import the public version-scoping helper for filtering by valid_from
from score_sphinx_needs_templates import req_in_report_version
from sphinx_needs.need_item import NeedItem

# Import shared verification metrics helpers
from src.helper_lib.verification_metrics import (
count_coverage_filtered,
count_status_filtered,
)


def _arg(kwargs: dict[str, Any], name: str, default: str = "") -> str:
"""Return a filter-func argument without surrounding whitespace and quotes.

sphinx-needs splits the argument string on ``,`` and passes the parts
verbatim, so ``f("a", "b")`` arrives as ``'"a"'`` and ``' "b"'``.
"""
return str(kwargs.get(name, default)).strip().strip("\"'")


def _version_arg(kwargs: dict[str, Any]) -> str | None:
"""Return ``arg3`` as a version, with ``_`` turned back into ``.``.

sphinx-needs resolves the module with ``rsplit(".", 1)`` over the whole
call, so a version such as ``v1.0`` must be passed as ``v1_0``.
"""
return _arg(kwargs, "arg3").replace("_", ".") or None


def _matches_source_selector(need: NeedItem, selector: str) -> bool:
"""Return whether a need matches a source selector.
Expand Down Expand Up @@ -247,3 +274,69 @@ def get_non_overlapping_link_metrics(
only_code = with_code - fully

results.extend([remaining, only_test, only_code, fully])


def get_component_status_metrics(
needs: list[Any], results: list[int], **kwargs: Any
) -> None:
"""Get status distribution for a component's requirements or architecture.

Arguments are passed via ``arg1`` .. ``arg4``:
- ``arg1`` – component_id: The component ID (e.g., "comp__my_component")
- ``arg2`` – category: Either "requirements" or "architecture"
- ``arg3`` – version: Optional, see :func:`_version_arg`
- ``arg4`` – safety: Optional safety class, e.g. ``ASIL_B`` or ``QM``

Returns ``[valid, valid_inspected, invalid]`` without a safety class,
``[valid_inspected, valid, invalid]`` for an ASIL class and
``[valid, invalid]`` for ``QM``, where ``valid_inspected`` counts as
``valid`` because QM Needs are not formally inspected.
"""
results.clear()
safety = _arg(kwargs, "arg4") or None
status = count_status_filtered(
needs,
_arg(kwargs, "arg1"),
_arg(kwargs, "arg2", "requirements"),
_version_arg(kwargs),
req_in_report_version,
safety,
)

if safety == "QM":
results.extend([status["valid"] + status["valid_inspected"], status["invalid"]])
elif safety:
results.extend([status["valid_inspected"], status["valid"], status["invalid"]])
else:
results.extend([status["valid"], status["valid_inspected"], status["invalid"]])


def get_component_coverage_metrics(
needs: list[Any], results: list[int], **kwargs: Any
) -> None:
"""Get verification coverage for a component's requirements or architecture.

Uses the verification coverage axis (fully_verifies_back / partially_verifies_back),
not the link coverage axis (testlink / source_code_link).

Arguments are the same as for :func:`get_component_status_metrics`.

Returns [fully_covered, partially_covered, not_covered].
"""
results.clear()
coverage = count_coverage_filtered(
needs,
_arg(kwargs, "arg1"),
_arg(kwargs, "arg2", "requirements"),
_version_arg(kwargs),
req_in_report_version,
_arg(kwargs, "arg4") or None,
)

results.extend(
[
coverage["fully_covered"],
coverage["partially_covered"],
coverage["not_covered"],
]
)
Loading
Loading