Skip to content

Commit d892ca6

Browse files
committed
Refine requirement type autodiscovery
1 parent 4123cfc commit d892ca6

4 files changed

Lines changed: 37 additions & 88 deletions

File tree

docs/conf.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,3 @@
1818
extensions = [
1919
"score_sphinx_bundle",
2020
]
21-
22-
# Configure traceability metrics explicitly for this repository.
23-
score_metamodel_requirement_types = "tool_req"
24-
score_metamodel_include_external_needs = False

docs/how-to/dashboards_and_quality_gates.rst

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,35 @@ Typical Setup
4040

4141
For details, see :ref:`setup`.
4242

43-
Minimal Configuration Example
44-
-----------------------------
43+
Configuration
44+
-------------
45+
46+
Default Behavior (No Configuration Needed)
47+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48+
49+
By default, ``score_metamodel`` autodiscovers requirement types from the
50+
repository needs present in the current build. Requirement types are identified
51+
from ``needs_types`` entries tagged with ``requirement`` or
52+
``requirement_excl_process``.
4553

46-
In ``docs/conf.py``:
54+
This is the recommended setup for most repositories.
55+
56+
Optional Override for Requirement Types
57+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
58+
59+
If a repository needs to force a specific set of requirement types, set an
60+
explicit override in ``docs/conf.py``:
4761

4862
.. code-block:: python
4963
5064
score_metamodel_requirement_types = "feat_req,comp_req,aou_req"
51-
score_metamodel_include_external_needs = False
5265
53-
By default, ``score_metamodel`` autodiscovers requirement types from the
54-
repository needs that are present in the current build. If
55-
``score_metamodel_requirement_types`` is set, that explicit list overrides
66+
When this setting is provided, the explicit list is used instead of
5667
autodiscovery.
5768

5869
Use ``score_metamodel_include_external_needs = True`` only in repositories that
5970
intentionally aggregate requirements across module dependencies, such as
60-
integration repositories. Use ``False`` for module repositories to gate only on
61-
local traceability.
71+
integration repositories.
6272

6373
Building the Dashboard
6474
----------------------
@@ -92,7 +102,7 @@ There are two common modes:
92102

93103
**Module repository**
94104

95-
- Set ``score_metamodel_include_external_needs = False``.
105+
- No setting needed. Local-only scope is the default.
96106
- Gate only on the needs owned by the repository itself.
97107
- Use this for per-module implementation progress and traceability.
98108

src/extensions/score_metamodel/__init__.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,7 @@ def _write_metrics_json(app: Sphinx, exception: Exception | None) -> None:
157157

158158

159159
def _get_need_value(need: Any, key: str, default: Any = None) -> Any:
160-
getter = getattr(need, "get", None)
161-
if callable(getter):
162-
return getter(key, default)
163-
try:
164-
return need[key]
165-
except Exception:
166-
return default
160+
return need.get(key, default)
167161

168162

169163
def _as_requirement_directive(need_type: Any) -> str | None:
@@ -198,11 +192,15 @@ def _discover_requirement_types(
198192
need_type: Any = _get_need_value(need, "type", None)
199193
if isinstance(need_type, str):
200194
present_types.add(need_type)
195+
201196
discovered = tagged_requirements.intersection(present_types)
202-
if not discovered:
203-
# Fallback for repositories that use *_req directives but do not tag
204-
# requirement types in needs_types.
205-
discovered = {t for t in present_types if t.endswith("_req")}
197+
198+
if tagged_requirements and not discovered:
199+
logger.warning(
200+
"No requirement types discovered in current build for tagged "
201+
"needs_types requirement directives."
202+
)
203+
206204
if discovered:
207205
logger.info(
208206
"score_metamodel_requirement_types is not configured; "

src/extensions/score_metamodel/tests/test_traceability_metrics_json_generation.py

Lines changed: 8 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -98,47 +98,10 @@ def get_needs_view(self) -> dict[str, dict[str, object]]:
9898
}
9999

100100

101-
class _NeedObj:
102-
def __init__(self, payload: dict[str, object]):
103-
self._payload = payload
104-
105-
def get(self, key: str, default: object | None = None) -> object | None:
106-
return self._payload.get(key, default)
107-
108-
109-
class _FakeObjectNeedsData:
110-
def __init__(self, env: object):
111-
self._env = env
112-
113-
def get_needs_view(self) -> dict[str, _NeedObj]:
114-
return {
115-
"LOCAL_REQ": _NeedObj(
116-
{
117-
"id": "LOCAL_REQ",
118-
"type": "tool_req",
119-
"implemented": "YES",
120-
"source_code_link": "",
121-
"testlink": "",
122-
"is_external": False,
123-
}
124-
),
125-
"LOCAL_FEAT": _NeedObj(
126-
{
127-
"id": "LOCAL_FEAT",
128-
"type": "feat_req",
129-
"implemented": "YES",
130-
"source_code_link": "",
131-
"testlink": "",
132-
"is_external": False,
133-
}
134-
),
135-
}
136-
137-
138101
def _app(
139102
tmp_path: Path,
140103
include_external: bool,
141-
requirement_types: str = "tool_req",
104+
requirement_types: str = "",
142105
needs_types: list[dict[str, object]] | None = None,
143106
) -> SimpleNamespace:
144107
discovered_types = needs_types or [
@@ -281,7 +244,7 @@ def test_write_metrics_json_empty_when_no_types_configured_or_discovered(
281244
assert payload["metrics_by_type"] == {}
282245

283246

284-
def test_autodiscovery_falls_back_to_present_req_suffix_types(
247+
def test_autodiscovery_without_tagged_requirement_types_is_empty(
285248
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
286249
) -> None:
287250
monkeypatch.setattr(metamodel_init, "SphinxNeedsData", _FakeNeedsData)
@@ -300,7 +263,7 @@ def test_autodiscovery_falls_back_to_present_req_suffix_types(
300263
)
301264

302265
payload = json.loads((tmp_path / "metrics.json").read_text(encoding="utf-8"))
303-
assert set(payload["metrics_by_type"].keys()) == {"feat_req", "tool_req"}
266+
assert payload["metrics_by_type"] == {}
304267

305268

306269
def test_autodiscovery_respects_include_external_scope(
@@ -315,7 +278,11 @@ def test_autodiscovery_respects_include_external_scope(
315278
tmp_path,
316279
include_external=True,
317280
requirement_types="",
318-
needs_types=[{"directive": "workflow", "tags": []}],
281+
needs_types=[
282+
{"directive": "tool_req", "tags": ["requirement_excl_process"]},
283+
{"directive": "feat_req", "tags": ["requirement"]},
284+
{"directive": "gd_req", "tags": ["requirement"]},
285+
],
319286
),
320287
),
321288
None,
@@ -325,28 +292,6 @@ def test_autodiscovery_respects_include_external_scope(
325292
assert set(payload["metrics_by_type"].keys()) == {"feat_req", "gd_req", "tool_req"}
326293

327294

328-
def test_autodiscovery_handles_needitem_like_objects(
329-
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
330-
) -> None:
331-
monkeypatch.setattr(metamodel_init, "SphinxNeedsData", _FakeObjectNeedsData)
332-
333-
metamodel_init._write_metrics_json(
334-
cast(
335-
Sphinx,
336-
_app(
337-
tmp_path,
338-
include_external=False,
339-
requirement_types="",
340-
needs_types=[{"directive": "workflow", "tags": []}],
341-
),
342-
),
343-
None,
344-
)
345-
346-
payload = json.loads((tmp_path / "metrics.json").read_text(encoding="utf-8"))
347-
assert set(payload["metrics_by_type"].keys()) == {"feat_req", "tool_req"}
348-
349-
350295
@pytest.mark.parametrize(
351296
("requirement_types", "include_external", "should_exist", "expected_totals"),
352297
[

0 commit comments

Comments
 (0)