feat(optimization): Regulatory Optimization — Roadmap/Management renderer over the Capability Delta
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>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
"""Regulatory Optimization — the Roadmap / Management renderer of the Capability Delta Engine.
|
||||
|
||||
Ranks the OPEN Capability Delta (from RS-005) by regulatory leverage: which measure closes the
|
||||
most regulatory requirements at once. Answers the Geschäftsführer question "Womit anfangen?".
|
||||
Pure, deterministic, computed-not-stored. Consumes the RS-005 delta (acyclic dependency); the
|
||||
delta engine stays hermetic. No new corpus, no new meta-model class (freeze v1.0).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .engine import regulatory_leverage, roadmap_from_delta, select_within_budget
|
||||
from .schemas import BudgetPlan, OptimizationPlan, RankedMeasure
|
||||
|
||||
__all__ = [
|
||||
"regulatory_leverage",
|
||||
"select_within_budget",
|
||||
"roadmap_from_delta",
|
||||
"OptimizationPlan",
|
||||
"RankedMeasure",
|
||||
"BudgetPlan",
|
||||
]
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Regulatory Optimization — the Roadmap / Management RENDERER of the Capability Delta Engine.
|
||||
|
||||
GAP analysis and measure-prioritisation are TWO VIEWS OF THE SAME COMPUTATION. The Capability
|
||||
Delta Engine (`compliance/transition_reasoning`, RS-005) computes Required - Known = the
|
||||
Capability Delta once. Renderers read that ONE delta:
|
||||
- Interview Renderer (missing INFORMATION -> questions) = `TransitionQuestionRequest` (built)
|
||||
- Roadmap / Management Renderer (missing CAPABILITIES -> measures by leverage) = THIS module
|
||||
- Evidence Renderer (missing EVIDENCE -> upload requests) = later
|
||||
There is one truth, not a Gap engine and a separate Roadmap engine.
|
||||
|
||||
A measure (a capability to implement) has *regulatory leverage* = the number of distinct
|
||||
regulatory requirements it closes AT ONCE (e.g. patch management closes a CRA, a MaschinenVO,
|
||||
an IEC 62443 and an ISO 27001 requirement -> leverage 4). The product turns from "you have N
|
||||
obligations" into "of N identified requirements you only need M measures — and these K first".
|
||||
|
||||
Fully deterministic, computed-not-stored, NO new corpus. `regulatory_leverage`/`select_within_budget`
|
||||
are pure math over `capability -> requirements`; `roadmap_from_delta` binds them to the RS-005
|
||||
delta (dependency optimization -> transition_reasoning, acyclic; the delta engine stays hermetic).
|
||||
No new graph/meta-model class (freeze v1.0). Python 3.9 compatible.
|
||||
|
||||
Honesty (Welt-1): the percentages are exact count ratios over the IDENTIFIED requirements from
|
||||
the known patterns — never "% gesetzeskonform". Label outputs as "der identifizierten Anforderungen".
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from ..transition_reasoning import CoverageStatus, TransitionAssessment
|
||||
from .schemas import BudgetPlan, OptimizationPlan, RankedMeasure
|
||||
|
||||
|
||||
def _ranked(
|
||||
capability_requirements: Dict[str, List[str]], in_scope: Optional[List[str]]
|
||||
) -> List[RankedMeasure]:
|
||||
"""Rank measures: leverage desc, then capability_id asc (deterministic). Empty covers dropped."""
|
||||
scope = (
|
||||
set(in_scope)
|
||||
if in_scope is not None
|
||||
else {r for reqs in capability_requirements.values() for r in reqs}
|
||||
)
|
||||
measures: List[RankedMeasure] = []
|
||||
for cap, reqs in capability_requirements.items():
|
||||
covers = sorted({r for r in reqs if r in scope})
|
||||
if not covers:
|
||||
continue # this capability closes nothing in scope -> not a measure here
|
||||
measures.append(RankedMeasure(capability_id=cap, covers=covers, leverage=len(covers)))
|
||||
measures.sort(key=lambda m: (-m.leverage, m.capability_id))
|
||||
total = sum(m.leverage for m in measures)
|
||||
running = 0
|
||||
for m in measures:
|
||||
running += m.leverage
|
||||
m.cumulative_requirements = running
|
||||
m.cumulative_coverage = (running / total) if total else 0.0
|
||||
return measures
|
||||
|
||||
|
||||
def regulatory_leverage(
|
||||
capability_requirements: Dict[str, List[str]], in_scope: Optional[List[str]] = None
|
||||
) -> OptimizationPlan:
|
||||
"""Rank measures by regulatory leverage; report the compression (requirements -> measures).
|
||||
|
||||
`capability_requirements`: measure (capability_id) -> the requirement keys it satisfies. A
|
||||
requirement key is currently a regulation (via `covers_targets`); finer obligation granularity
|
||||
is a future extension. `in_scope`: restrict the requirement keys counted (default: all seen).
|
||||
"""
|
||||
measures = _ranked(capability_requirements, in_scope)
|
||||
scope = sorted(
|
||||
set(in_scope)
|
||||
if in_scope is not None
|
||||
else {r for reqs in capability_requirements.values() for r in reqs}
|
||||
)
|
||||
total = sum(m.leverage for m in measures)
|
||||
avg = (total / len(measures)) if measures else 0.0
|
||||
headline = (
|
||||
"%d identifizierte Anforderungen aus %d Regelwerken -> %d Massnahmen (Ø Hebel %.1f)."
|
||||
% (total, len(scope), len(measures), avg)
|
||||
)
|
||||
return OptimizationPlan(
|
||||
in_scope_requirements=scope,
|
||||
total_measures=len(measures),
|
||||
total_requirements=total,
|
||||
ranked_measures=measures,
|
||||
headline=headline,
|
||||
)
|
||||
|
||||
|
||||
def select_within_budget(
|
||||
capability_requirements: Dict[str, List[str]],
|
||||
budget: int,
|
||||
in_scope: Optional[List[str]] = None,
|
||||
) -> BudgetPlan:
|
||||
"""The budget answer: with K measures, pick the K highest-leverage ones and report coverage.
|
||||
|
||||
Because each requirement key is closed by exactly one measure here, greedy-by-leverage is the
|
||||
optimal cover, so ranking == selection. (When requirements become shared across capabilities,
|
||||
this becomes weighted set-cover; the signature is ready for that.)
|
||||
"""
|
||||
measures = _ranked(capability_requirements, in_scope)
|
||||
total = sum(m.leverage for m in measures)
|
||||
k = max(0, budget)
|
||||
selected = measures[:k]
|
||||
closed = selected[-1].cumulative_requirements if selected else 0
|
||||
ratio = (closed / total) if total else 0.0
|
||||
headline = (
|
||||
"Mit den Top-%d Massnahmen (nach regulatorischem Hebel) schliessen Sie %d von %d "
|
||||
"identifizierten Anforderungen (%.0f%%)." % (len(selected), closed, total, ratio * 100)
|
||||
)
|
||||
return BudgetPlan(
|
||||
budget=budget,
|
||||
selected_capabilities=[m.capability_id for m in selected],
|
||||
requirements_closed=closed,
|
||||
total_requirements=total,
|
||||
coverage_ratio=ratio,
|
||||
headline=headline,
|
||||
)
|
||||
|
||||
|
||||
def roadmap_from_delta(
|
||||
assessment: TransitionAssessment,
|
||||
capability_requirements: Dict[str, List[str]],
|
||||
in_scope: Optional[List[str]] = None,
|
||||
open_statuses: Optional[List[CoverageStatus]] = None,
|
||||
) -> OptimizationPlan:
|
||||
"""Render the Roadmap view FROM a Capability Delta (an RS-005 `TransitionAssessment`).
|
||||
|
||||
Takes the OPEN capabilities of the delta — MISSING by default — and ranks them by regulatory
|
||||
leverage. This is the same delta the Interview Renderer turns into questions; here it becomes
|
||||
prioritised measures. The binding that makes "one truth, two renderers" real in code.
|
||||
"""
|
||||
statuses = set(open_statuses) if open_statuses is not None else {CoverageStatus.MISSING}
|
||||
open_caps = [c.capability_id for c in assessment.coverage if c.status in statuses]
|
||||
delta_reqs = {cap: capability_requirements.get(cap, []) for cap in open_caps}
|
||||
return regulatory_leverage(delta_reqs, in_scope)
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Schemas for the Regulatory Optimization Engine.
|
||||
|
||||
These DTOs are *derived views* (computed-not-stored): nothing here is persisted; every value
|
||||
is recomputed from the input each call. No new meta-model class, no graph (freeze v1.0).
|
||||
Python 3.9 compatible (no `|` unions).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class RankedMeasure(BaseModel):
|
||||
"""One measure (a capability to implement) ranked by its regulatory leverage."""
|
||||
|
||||
capability_id: str
|
||||
covers: List[str] = Field(default_factory=list) # the in-scope requirements it satisfies
|
||||
leverage: int = 0 # = len(covers): how many it closes at once
|
||||
cumulative_requirements: int = 0 # running total of requirements closed (ranked order)
|
||||
cumulative_coverage: float = 0.0 # cumulative_requirements / total_requirements (0..1)
|
||||
|
||||
|
||||
class OptimizationPlan(BaseModel):
|
||||
"""Measures ranked by regulatory leverage — greatest regulatory effect first.
|
||||
|
||||
`total_requirements` counts the IDENTIFIED requirements in scope (the known delta from the
|
||||
patterns), NOT a company's total legal duties. The percentages are exact count ratios over
|
||||
this identified set — never a compliance verdict (Welt-1 discipline).
|
||||
"""
|
||||
|
||||
in_scope_requirements: List[str] = Field(default_factory=list) # the distinct requirement keys counted
|
||||
total_measures: int = 0 # number of distinct measures (delta capabilities)
|
||||
total_requirements: int = 0 # Sum of leverage = identified requirements closable
|
||||
ranked_measures: List[RankedMeasure] = Field(default_factory=list)
|
||||
headline: str = "" # "N identifizierte Anforderungen -> M Massnahmen ..."
|
||||
|
||||
|
||||
class BudgetPlan(BaseModel):
|
||||
"""The budget answer: with a budget of K measures, which K and how much do they close?"""
|
||||
|
||||
budget: int = 0
|
||||
selected_capabilities: List[str] = Field(default_factory=list)
|
||||
requirements_closed: int = 0
|
||||
total_requirements: int = 0
|
||||
coverage_ratio: float = 0.0 # requirements_closed / total_requirements (0..1)
|
||||
headline: str = ""
|
||||
Reference in New Issue
Block a user