cfafa31ea2
Roadmap item 5. GAP analysis and measure-prioritisation are the SAME computation: Required − Known = the Capability Delta. The Capability Delta Engine (RS-005) computes it once; renderers read that ONE delta. Interview Renderer (missing info → questions) was already built; this adds the Roadmap/Management Renderer (missing capabilities → measures ranked by regulatory leverage). - compliance/optimization/: regulatory_leverage() + select_within_budget() (pure leverage math) + roadmap_from_delta(assessment, ...) — the keystone binding optimization to the RS-005 delta (dependency optimization → transition_reasoning, acyclic; the delta engine stays hermetic). leverage(measure) = number of regulatory requirements it closes at once (e.g. patch management → CRA+MaschinenVO+IEC62443+ISO27001 = 4). No new corpus, no new meta-model class (freeze v1.0). - Welt-1 honesty: percentages are exact count ratios over the IDENTIFIED requirements (the known delta), never "% gesetzeskonform". - reference suite: "Regulatory Optimization" section runs the SAME convergence delta → ranked measures + budget answer + the management sentence "of N identified requirements you close M with the top-K measures (X%) — highest regulatory leverage". - ADR-003: Capability Delta Engine — one delta, many renderers; rename Gap → Capability Delta. 13 optimization tests (31 with transition+company), mypy --strict clean, check-loc 0. Product code with no app caller + ADR/reference = non-runtime → no deploy (ADR-001). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
128 lines
5.5 KiB
Python
128 lines
5.5 KiB
Python
"""Tests for the Regulatory Optimization renderer (Roadmap / Management view of the Capability Delta).
|
|
|
|
Acceptance: rank measures by regulatory leverage (most regulatory requirements closed at once),
|
|
report the compression (identified requirements -> measures), answer the budget question, and bind
|
|
to the SAME RS-005 Capability Delta the Interview Renderer uses. Percentages are over IDENTIFIED
|
|
requirements (Welt-1), never "% gesetzeskonform".
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from compliance.optimization import (
|
|
BudgetPlan, OptimizationPlan, regulatory_leverage, roadmap_from_delta, select_within_budget,
|
|
)
|
|
from compliance.transition_reasoning import (
|
|
CapabilityCoverage, CoverageStatus, TransitionAssessment, TransitionSummary,
|
|
)
|
|
|
|
# Illustrative leverage spread (the user's patch_management example reaches leverage 4).
|
|
CAPS = {
|
|
"patch_management": ["CRA", "MaschinenVO", "IEC62443", "ISO27001"], # leverage 4
|
|
"access_control": ["CRA", "ISO27001"], # leverage 2
|
|
"sbom": ["CRA"], # leverage 1
|
|
"machine_guards": ["MaschinenVO"], # leverage 1
|
|
}
|
|
|
|
|
|
def test_ranked_by_leverage_desc():
|
|
plan = regulatory_leverage(CAPS)
|
|
order = [m.capability_id for m in plan.ranked_measures]
|
|
assert order[0] == "patch_management" # leverage 4 first
|
|
assert order[1] == "access_control" # leverage 2 next
|
|
assert plan.ranked_measures[0].leverage == 4
|
|
|
|
|
|
def test_compression_counts():
|
|
plan = regulatory_leverage(CAPS)
|
|
# total requirements = 4 + 2 + 1 + 1 = 8 closed by 4 measures
|
|
assert plan.total_requirements == 8 and plan.total_measures == 4
|
|
assert "8 identifizierte Anforderungen" in plan.headline
|
|
|
|
|
|
def test_cumulative_coverage_monotone_to_one():
|
|
plan = regulatory_leverage(CAPS)
|
|
cums = [m.cumulative_coverage for m in plan.ranked_measures]
|
|
assert cums == sorted(cums) # non-decreasing
|
|
assert abs(cums[-1] - 1.0) < 1e-9 # full set -> 100%
|
|
assert plan.ranked_measures[0].cumulative_requirements == 4
|
|
|
|
|
|
def test_tie_break_deterministic_by_id():
|
|
# machine_guards vs sbom both leverage 1 -> alphabetical: machine_guards before sbom
|
|
plan = regulatory_leverage(CAPS)
|
|
tail = [m.capability_id for m in plan.ranked_measures if m.leverage == 1]
|
|
assert tail == ["machine_guards", "sbom"]
|
|
|
|
|
|
def test_budget_picks_highest_leverage():
|
|
b = select_within_budget(CAPS, 2)
|
|
assert b.selected_capabilities == ["patch_management", "access_control"]
|
|
assert b.requirements_closed == 6 and b.total_requirements == 8
|
|
assert abs(b.coverage_ratio - 0.75) < 1e-9
|
|
assert "6 von 8" in b.headline and "75%" in b.headline
|
|
|
|
|
|
def test_budget_over_and_zero():
|
|
assert select_within_budget(CAPS, 99).requirements_closed == 8 # capped at all
|
|
z = select_within_budget(CAPS, 0)
|
|
assert z.selected_capabilities == [] and z.requirements_closed == 0
|
|
|
|
|
|
def test_in_scope_filter():
|
|
# restrict to CRA + MaschinenVO: patch=2, access=1(CRA), sbom=1, guards=1 -> total 5
|
|
plan = regulatory_leverage(CAPS, in_scope=["CRA", "MaschinenVO"])
|
|
assert plan.total_requirements == 5
|
|
assert plan.ranked_measures[0].capability_id == "patch_management"
|
|
assert plan.ranked_measures[0].leverage == 2 # only CRA+MaschinenVO counted
|
|
|
|
|
|
def test_deterministic():
|
|
a, b = regulatory_leverage(CAPS), regulatory_leverage(CAPS)
|
|
assert [m.capability_id for m in a.ranked_measures] == [m.capability_id for m in b.ranked_measures]
|
|
assert a.headline == b.headline
|
|
|
|
|
|
def test_empty():
|
|
plan = regulatory_leverage({})
|
|
assert plan.total_requirements == 0 and plan.ranked_measures == []
|
|
assert isinstance(plan, OptimizationPlan)
|
|
|
|
|
|
def test_capability_with_no_in_scope_requirement_dropped():
|
|
plan = regulatory_leverage({"x": ["CRA"], "y": ["DataAct"]}, in_scope=["CRA"])
|
|
assert [m.capability_id for m in plan.ranked_measures] == ["x"] # y covers nothing in scope
|
|
|
|
|
|
# The keystone: optimization renders the SAME RS-005 delta the interview uses.
|
|
def _delta():
|
|
return TransitionAssessment(
|
|
target_id="CRA+MaschinenVO",
|
|
coverage=[
|
|
CapabilityCoverage(capability_id="patch_management", status=CoverageStatus.MISSING),
|
|
CapabilityCoverage(capability_id="sbom", status=CoverageStatus.MISSING),
|
|
CapabilityCoverage(capability_id="access_control", status=CoverageStatus.ALREADY_COVERED),
|
|
CapabilityCoverage(capability_id="machine_guards", status=CoverageStatus.MISSING),
|
|
],
|
|
summary=TransitionSummary(),
|
|
)
|
|
|
|
|
|
def test_roadmap_from_delta_uses_only_open_capabilities():
|
|
plan = roadmap_from_delta(_delta(), CAPS)
|
|
ids = [m.capability_id for m in plan.ranked_measures]
|
|
assert "access_control" not in ids # ALREADY_COVERED is not an open measure
|
|
assert ids == ["patch_management", "machine_guards", "sbom"] # MISSING ranked by leverage
|
|
assert isinstance(plan, OptimizationPlan)
|
|
|
|
|
|
def test_roadmap_from_delta_honours_status_filter():
|
|
# include NEEDS_CONFIRMATION too -> still only those present in CAPS contribute requirements
|
|
a = _delta()
|
|
a.coverage.append(CapabilityCoverage(capability_id="access_control", status=CoverageStatus.NEEDS_CONFIRMATION))
|
|
plan = roadmap_from_delta(a, CAPS, open_statuses=[CoverageStatus.MISSING, CoverageStatus.NEEDS_CONFIRMATION])
|
|
assert "access_control" in [m.capability_id for m in plan.ranked_measures]
|
|
|
|
|
|
def test_budget_returns_budgetplan_type():
|
|
assert isinstance(select_within_budget(CAPS, 1), BudgetPlan)
|