feat(cra): kuratierte Maßnahmen-Bibliothek — alle 40 CRA-Anforderungen belegt

- data/measures_curated.json: 24 deduplizierte, standard-gestützte Maßnahmen
  (9 bestehende M540-548 + 15 neue M600-614), Volltext + norm_refs + multi-reg
  covers. Deckt alle 40 CRA-AI-x (vorher nur 17).
- cra_annex_i_data lädt die Bibliothek defensiv: MEASURES=Superset, MEASURE_DETAILS
  (Volltext), mapped_measures aus covers abgeleitet. Fallback = hartkodierte 9.
- Mapper: open_measures tragen jetzt name+description+norm_refs (echte Volltexte).
- useCRA: merge nutzt Backend-Volltexte statt Demo-Lookup.
- Tests: Coverage (40/40) + Volltext im Assessment.

Quelle: extern handkuratiert/recherchiert, hier dedupliziert + gemappt. Maschinen-
VO/NIS2/IEC-Maßnahmen folgen, sobald deren Spine existiert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-16 07:44:13 +02:00
parent 4c206aa332
commit 6c619ecc42
5 changed files with 203 additions and 6 deletions
@@ -13,11 +13,21 @@ compliance.api.cra_annex_i_data (pure data, no FastAPI dependency).
from dataclasses import dataclass, field, asdict
from typing import Optional
from compliance.api.cra_annex_i_data import ANNEX_I_REQUIREMENTS, MEASURES, DEADLINES
from compliance.api.cra_annex_i_data import ANNEX_I_REQUIREMENTS, MEASURES, MEASURE_DETAILS, DEADLINES
from compliance.services.cra_security_crosswalk import security_refs_for
from compliance.services.cra_prioritizer import prioritize, OBJECTIVES
from compliance.services.cra_safety_bridge import build_cross_links
def _measure_obj(mid: str) -> dict:
"""Full curated measure (name/description/norm_refs) for the assessment output,
falling back to just the name when only the thin id->name map has it."""
d = MEASURE_DETAILS.get(mid)
if d:
return {"id": mid, "name": d.get("name", ""), "description": d.get("description", ""),
"norm_refs": d.get("norm_refs", [])}
return {"id": mid, "name": MEASURES.get(mid, ""), "description": MEASURES.get(mid, ""), "norm_refs": []}
_REQ_INDEX = {r["req_id"]: r for r in ANNEX_I_REQUIREMENTS}
_SEV_ORDER = {"LOW": 1, "MEDIUM": 2, "HIGH": 3, "CRITICAL": 4}
_SEV_BY_RANK = {v: k for k, v in _SEV_ORDER.items()}
@@ -249,7 +259,7 @@ def assess_findings(findings: list, weights=None, safety_functions=None) -> CRAA
mapped=mapped,
by_risk=by_risk,
requirements_touched=sorted(reqs_touched),
open_measures=[{"id": mid, "description": MEASURES.get(mid, "")} for mid in measure_ids],
open_measures=[_measure_obj(mid) for mid in measure_ids],
unmapped_findings=unmapped,
coverage_pct=round(100.0 * covered / total, 1) if total else 0.0,
quick_wins=[m.finding_id for m in mapped if m.quick_win],