From cf86dc241b3cd3bd4083966f683e2701a862823e Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Wed, 24 Jun 2026 21:51:26 +0200 Subject: [PATCH] test(ai-sdk): GT #3 (commercial dishwasher) + fix Drehtisch keyword mislabel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ground_truth_warewashing.json + TestWarewashing_GTCoverage. The test runs the UC-M narrative through the SAME chain as production (ParseNarrative -> engine -> relevance + cyber filter), so keyword/gating fixes are measured on the real hazard set, and false positives show up as "extra". Class A (generic keyword hygiene): spuelarm/spuelfeld no longer map to library component C004 ("Drehtisch" / rotary table) — that mislabelled the spray arm. Keep the rotating_part tag. Removes the bogus "Drehtisch" hazard. GT #3 baseline -> after Class A: recall 80% (unchanged), one false positive (Drehtisch) removed. Kistenhub 97.1% and Bremse pinned mappings unchanged. Co-Authored-By: Claude Opus 4.7 --- .../internal/iace/gt_warewashing_test.go | 146 +++++++++++ .../internal/iace/keyword_dictionary.go | 6 +- .../testdata/ground_truth_warewashing.json | 233 ++++++++++++++++++ 3 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 ai-compliance-sdk/internal/iace/gt_warewashing_test.go create mode 100644 ai-compliance-sdk/internal/iace/testdata/ground_truth_warewashing.json diff --git a/ai-compliance-sdk/internal/iace/gt_warewashing_test.go b/ai-compliance-sdk/internal/iace/gt_warewashing_test.go new file mode 100644 index 00000000..456619c7 --- /dev/null +++ b/ai-compliance-sdk/internal/iace/gt_warewashing_test.go @@ -0,0 +1,146 @@ +package iace + +import ( + "encoding/json" + "os" + "path/filepath" + "sort" + "testing" +) + +// GT #3 — commercial UNDERCOUNTER dishwasher (Winterhalter UC-M). Self-assessed +// ground truth: we can judge what a dishwasher is. The test runs the narrative +// through the SAME chain as production (ParseNarrative -> engine -> relevance +// filter + cyber-skip), so keyword/gating fixes are measured on the hazard set +// the user actually sees — not the raw pattern flood. + +// Condensed UC-M limits_form narrative. Deliberately includes "Cool-Ausfuehrung" +// and "Filter" so the known false components (Kuehlaggregat, Absauganlage) are +// reproduced and visible in the baseline. +const warewashingNarrative = `Gewerbliche Untertisch-Geschirrspuelmaschine fuer Gastronomie-Kueche, ` + + `vernetzt ueber LAN und WLAN (Connected Wash Internetportal). Heisswasser-Boiler mit ` + + `Nachspueltemperatur ca. 85 Grad C, Tank mit Hygiene-Tankheizkoerper. Spuelpumpe 150-200 l/min ` + + `mit rotierenden Spuelfeldern und Spuelarmen, Ablaufpumpe. Eingebautes Dosiergeraet fuer Reiniger ` + + `und Klarspueler (aetzende Konzentrate). 4-fach-Laugenfiltration mit Filter. Doppelwandige Tuer ` + + `mit Sicherheitsschalter und Rastposition (Thermostopp). Elektromotor (Drehstrom) 400 V. ` + + `Touch-Steuerung (SPS) mit Bedienfeld und HMI, USB-Schnittstelle fuer Softwareupdates, ` + + `PIN-geschuetzter Servicetechniker-Fernzugriff. Cool-Ausfuehrung mit kalter Nachspuelung. ` + + `Untertischmontage. Eingreifen in die Spuelkammer moeglich. Aerosole und Daempfe der ` + + `Reinigungschemie gelangen in die Atemzone. Manuelles Be- und Entladen der Spuelkoerbe von Hand. ` + + `Reinigung und Wartung durch Servicetechniker. Branche Lebensmittel und Getraenke.` + +// warewashingCyberCategories mirrors handlers.nativeCyberSecurityCategories — +// native cyber/AI hazards are routed to the CRA module, not the CE hazard log. +var warewashingCyberCategories = map[string]bool{ + "unauthorized_access": true, "firmware_corruption": true, "cyber_resilience": true, + "logging_audit_failure": true, "cyber_network": true, "sensor_spoofing": true, + "ai_specific": true, "ai_misclassification": true, "false_classification": true, + "model_drift": true, "data_poisoning": true, "unintended_bias": true, +} + +// warewashingEngineOutput runs the production chain and returns the filtered +// hazards/mitigations the user would see for the UC-M. +func warewashingEngineOutput() ([]Hazard, []Mitigation, int) { + res := ParseNarrative(warewashingNarrative, "Gewerbliche Untertisch-Geschirrspuelmaschine (vernetzt)") + + var compIDs, compNames []string + for _, c := range res.Components { + if c.Negated { + continue + } + compIDs = append(compIDs, c.LibraryID) + compNames = append(compNames, c.NameDE) + } + var energyIDs []string + for _, e := range res.EnergySources { + energyIDs = append(energyIDs, e.SourceID) + } + lifecycles := append([]string{}, res.LifecyclePhases...) + lifecycles = append(lifecycles, "normal_operation", "maintenance", "cleaning", "setup", "fault_clearing") + + input := MatchInput{ + ComponentLibraryIDs: compIDs, + EnergySourceIDs: energyIDs, + LifecyclePhases: lifecycles, + CustomTags: res.CustomTags, + OperationalStates: append(res.OperationalStates, "normal_operation", "cleaning", "maintenance"), + HumanRoles: res.Roles, + MachineTypes: []string{"food_processing", "Gewerbliche Untertisch-Geschirrspuelmaschine (vernetzt)"}, + } + + out := NewPatternEngine().Match(input) + + var kept []PatternMatch + for _, pm := range out.MatchedPatterns { + if !IsPatternRelevant(pm, warewashingNarrative, compNames) { + continue + } + allCyber := len(pm.HazardCats) > 0 + for _, c := range pm.HazardCats { + if !warewashingCyberCategories[c] { + allCyber = false + } + } + if allCyber { + continue + } + kept = append(kept, pm) + } + filtered := *out + filtered.MatchedPatterns = kept + hazards, mitigations := patternsToHazardsAndMitigations(&filtered) + return hazards, mitigations, len(kept) +} + +func TestWarewashing_GTCoverage(t *testing.T) { + gtPath := filepath.Join("testdata", "ground_truth_warewashing.json") + raw, err := os.ReadFile(gtPath) + if err != nil { + t.Fatalf("read GT: %v", err) + } + var gt GroundTruth + if err := json.Unmarshal(raw, >); err != nil { + t.Fatalf("parse GT: %v", err) + } + + hazards, mitigations, nPatterns := warewashingEngineOutput() + t.Logf("Engine: %d patterns kept (relevance+cyber filter) -> %d hazards", nPatterns, len(hazards)) + + result := CompareBenchmark(>, hazards, mitigations) + precision := 0.0 + if result.TotalEngine > 0 { + precision = float64(len(result.MatchedPairs)) / float64(result.TotalEngine) + } + t.Logf("=== Warewashing-GT (GT #3) Baseline ===") + t.Logf("Recall (Coverage): %.1f%% (%d/%d matched, %d missing)", + result.CoverageScore*100, len(result.MatchedPairs), result.TotalGT, len(result.MissingFromEngine)) + t.Logf("Precision: %.1f%% (%d engine hazards, %d extra)", + precision*100, result.TotalEngine, len(result.ExtraInEngine)) + + if len(result.MissingFromEngine) > 0 { + t.Logf("--- MISSING (recall gaps) ---") + for _, m := range result.MissingFromEngine { + t.Logf(" MISS %s: %s", m.Nr, abbrev(m.HazardType, 60)) + } + } + if len(result.ExtraInEngine) > 0 { + t.Logf("--- EXTRA (false positives / precision loss) ---") + names := make([]string, 0, len(result.ExtraInEngine)) + for _, e := range result.ExtraInEngine { + n := e.Name + if n == "" { + n = e.Scenario + } + names = append(names, "["+e.Category+"] "+n) + } + sort.Strings(names) + for _, n := range names { + t.Logf(" EXTRA %s", abbrev(n, 85)) + } + } + + // Loose smoke floor for the baseline — fixes should push recall up, not down. + if result.CoverageScore < 0.4 { + t.Errorf("warewashing recall below 40%% floor: %.1f%%", result.CoverageScore*100) + } +} diff --git a/ai-compliance-sdk/internal/iace/keyword_dictionary.go b/ai-compliance-sdk/internal/iace/keyword_dictionary.go index c48a6ccc..5cf36550 100644 --- a/ai-compliance-sdk/internal/iace/keyword_dictionary.go +++ b/ai-compliance-sdk/internal/iace/keyword_dictionary.go @@ -101,7 +101,11 @@ func GetKeywordDictionary() []KeywordEntry { {Keywords: []string{"dampf", "wrasen", "schwaden", "brueden"}, ExtraTags: []string{"steam_emission", "high_temperature"}}, {Keywords: []string{"boiler", "spuelboiler", "nachspuelboiler", "tankheiz", "boilerheiz"}, ComponentIDs: []string{"C094"}, ExtraTags: []string{"heating_element", "high_temperature"}}, {Keywords: []string{"reiniger", "klarspueler", "spuelmittel", "reinigungsmittel", "reinigerkonzentrat", "spuelchemie", "dosiergeraet", "dosierpumpe", "sauglanze", "entkalker"}, ExtraTags: []string{"corrosive_chemical"}}, - {Keywords: []string{"spuelarm", "spuelfeld", "wascharm", "spruehfeld"}, ComponentIDs: []string{"C004"}, ExtraTags: []string{"rotating_part"}}, + // Spuelarm/Spuelfeld emit only the rotating_part capability tag. They are + // NOT mapped to a library component — C004 is a "Drehtisch" (rotary table) + // and that mislabels the spray arm. Keyword->component must be semantically + // honest (generic hygiene; surfaced by the warewashing GT). + {Keywords: []string{"spuelarm", "spuelfeld", "wascharm", "spruehfeld"}, ExtraTags: []string{"rotating_part"}}, {Keywords: []string{"spuelkammer", "spueltuer", "geraetetuer", "haubentuer", "klapptuer"}, ExtraTags: []string{"access_door"}}, // Ghost-Closure (Emit-Seite): macht die 34 toten Required-Tags // emittierbar, jeweils NUR via domaenenspezifische Keywords -> die 120 diff --git a/ai-compliance-sdk/internal/iace/testdata/ground_truth_warewashing.json b/ai-compliance-sdk/internal/iace/testdata/ground_truth_warewashing.json new file mode 100644 index 00000000..725972a0 --- /dev/null +++ b/ai-compliance-sdk/internal/iace/testdata/ground_truth_warewashing.json @@ -0,0 +1,233 @@ +{ + "machine_name": "Gewerbliche Untertisch-Geschirrspuelmaschine (Winterhalter UC-M)", + "machine_description": "Untertisch-Gewerbespuelmaschine, vernetzt (Connected Wash), Heisswasser-Boiler, Spuelpumpe mit rotierenden Spuelfeldern, Tuer mit Sicherheitsschalter, Reiniger-/Klarspueler-Dosierung.", + "source": "Selbstbewertung GT #3 (Fachmann-Erwartung, EN 60335-2-58 + EN ISO 12100)", + "version": "1.0", + "entries": [ + { + "nr": "1.1", + "hazard_group": "Thermische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Verbrühung durch Heißwasser und Dampf", + "hazard_cause": "Beim Öffnen der Tür während oder kurz nach dem Spülgang tritt heißes Wasser und Wrasen (Dampf) aus der Spülkammer aus und trifft Gesicht, Hände und Arme", + "lifecycle_phases": ["Betrieb", "Reinigung"], + "component_zone": "Tür und Beschickungsöffnung der Spülkammer", + "risk_in": {"f": 4, "w": 3, "p": 2, "s": 3, "r": 27}, + "measures": ["Türverriegelung beendet Spülgang vor dem Öffnen", "Wrasen-/Dampfreduzierung", "Warnhinweis heißer Dampf"], + "measure_type": "KM", + "risk_out": {"f": 2, "w": 1, "p": 1, "s": 2, "r": 8}, + "norm_references": ["EN 60335-2-58"], + "sufficient": true + }, + { + "nr": "1.2", + "hazard_group": "Thermische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Verbrennung an heißen Oberflächen", + "hazard_cause": "Berührung heißer Oberflächen von Boiler, Tankheizkörper oder Spülkammerwänden bei Reinigung, Entkalkung oder Wartung", + "lifecycle_phases": ["Reinigung", "Instandhaltung"], + "component_zone": "Boiler, Tankheizkörper, Spülkammerwände", + "risk_in": {"f": 3, "w": 2, "p": 2, "s": 2, "r": 14}, + "measures": ["Temperaturbegrenzung zugänglicher Oberflächen", "Warnhinweis heiße Oberfläche"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 2, "r": 6}, + "norm_references": ["EN ISO 13732-1"], + "sufficient": true + }, + { + "nr": "1.3", + "hazard_group": "Thermische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Verbrennung an heißem Spülgut", + "hazard_cause": "Geschirr und Gläser sind nach der Heißwasser-Nachspülung sehr heiß, beim Entladen kommt es zu Verbrennungen an den Händen", + "lifecycle_phases": ["Betrieb"], + "component_zone": "Spülkammer, Entnahmebereich, Korb", + "risk_in": {"f": 3, "w": 3, "p": 2, "s": 2, "r": 16}, + "measures": ["Abkühl-/Trocknungszeit", "Warnhinweis heißes Spülgut"], + "measure_type": "BI", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 2, "r": 6}, + "norm_references": ["EN 60335-2-58"], + "sufficient": true + }, + { + "nr": "2.1", + "hazard_group": "Gefährdungen durch Materialien und Substanzen", + "hazard_group_applicable": true, + "hazard_type": "Verätzung von Haut und Augen durch Reiniger-/Klarspüler-Konzentrat", + "hazard_cause": "Direkter Kontakt mit dem ätzenden Reiniger- bzw. Klarspüler-Konzentrat beim Nachfüllen, Sauglanzenwechsel oder bei Leckage des Dosiergeräts", + "lifecycle_phases": ["Betrieb", "Instandhaltung"], + "component_zone": "Dosiergerät, Reiniger- und Klarspüler-Gebinde, Sauglanzen", + "risk_in": {"f": 3, "w": 3, "p": 2, "s": 3, "r": 24}, + "measures": ["Geschlossenes Dosiersystem mit Sauglanzen", "PSA Augen-/Hautschutz", "GHS-Kennzeichnung und Sicherheitsdatenblatt"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 3, "r": 9}, + "norm_references": ["Verordnung (EG) Nr. 1272/2008", "TRGS 500"], + "sufficient": true + }, + { + "nr": "2.2", + "hazard_group": "Gefährdungen durch Materialien und Substanzen", + "hazard_group_applicable": true, + "hazard_type": "Reizung der Atemwege durch Reinigungs-Aerosole und Dämpfe", + "hazard_cause": "Einatmen von Aerosolen und Dämpfen der Reinigungschemie beim Öffnen kurz nach dem Spülgang oder bei der Entkalkung mit Säure", + "lifecycle_phases": ["Betrieb", "Instandhaltung"], + "component_zone": "Atemzone vor der Spülkammer, Aufstellbereich", + "risk_in": {"f": 2, "w": 2, "p": 2, "s": 2, "r": 12}, + "measures": ["Be-/Entlüftung", "geschlossene Haube", "Warnung vor Vermischen von Reiniger und Säure"], + "measure_type": "BI", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 2, "r": 6}, + "norm_references": ["TRGS 500"], + "sufficient": true + }, + { + "nr": "3.1", + "hazard_group": "Elektrische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Elektrischer Schlag in Nassumgebung", + "hazard_cause": "Berührung spannungsführender Teile bei unzureichendem IP-Schutz, defekten Kabeldurchführungen oder Feuchtigkeit im Steuerungsgehäuse", + "lifecycle_phases": ["Betrieb", "Reinigung", "Instandhaltung"], + "component_zone": "Steuerungsgehäuse, Kabelübergänge, Antriebsgehäuse", + "risk_in": {"f": 3, "w": 2, "p": 3, "s": 4, "r": 32}, + "measures": ["IP-Schutz gegen eindringendes Wasser", "Fehlerstrom-Schutzeinrichtung (RCD)"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 4, "r": 12}, + "norm_references": ["IEC 60335-1"], + "sufficient": true + }, + { + "nr": "3.2", + "hazard_group": "Elektrische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Kurzschluss und Brand bei Reinigung am Schaltschrank", + "hazard_cause": "Reinigung ohne vorherige Freischaltung oder mit Hochdruckreiniger am elektrisch aktiven Schaltschrank führt zu Kurzschluss und Brand", + "lifecycle_phases": ["Reinigung", "Instandhaltung"], + "component_zone": "Schaltschrank, elektrisch aktive Komponenten", + "risk_in": {"f": 2, "w": 2, "p": 2, "s": 3, "r": 18}, + "measures": ["Netztrenneinrichtung", "Warnhinweis Reinigung nur spannungsfrei, kein Hochdruckreiniger"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 3, "r": 9}, + "norm_references": ["IEC 60204-1"], + "sufficient": true + }, + { + "nr": "3.3", + "hazard_group": "Elektrische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Motorüberlast mit Überhitzung", + "hazard_cause": "Blockierter oder überlasteter Pumpenmotor überhitzt, Wicklungsbrand und Rauchentwicklung", + "lifecycle_phases": ["Betrieb"], + "component_zone": "Motorgehäuse, Umgebung", + "risk_in": {"f": 2, "w": 2, "p": 2, "s": 2, "r": 12}, + "measures": ["Überstromschutz", "Motorschutzschalter"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 2, "r": 6}, + "norm_references": ["IEC 60204-1"], + "sufficient": true + }, + { + "nr": "4.1", + "hazard_group": "Mechanische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Ausrutschen auf nassem Boden", + "hazard_cause": "Aus der Spülmaschine austretendes Wasser durch Leckage oder beim Öffnen macht den Boden im Aufstellbereich rutschig, Person rutscht aus und stürzt", + "lifecycle_phases": ["Betrieb", "Reinigung", "Instandhaltung"], + "component_zone": "Aufstell- und Bedienbereich der Spülmaschine", + "risk_in": {"f": 3, "w": 3, "p": 2, "s": 2, "r": 16}, + "measures": ["Rutschhemmender Bodenbelag", "Bodenablauf bzw. Leckagewanne"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 2, "r": 6}, + "norm_references": ["ASR A1.5/1,2"], + "sufficient": true + }, + { + "nr": "4.2", + "hazard_group": "Mechanische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Quetschen der Finger an der Tür/Haube", + "hazard_cause": "Beim Schließen der Tür bzw. Absenken der Haube werden Finger zwischen Tür/Haube und Gehäuse gequetscht", + "lifecycle_phases": ["Betrieb"], + "component_zone": "Tür- und Haubenkante, Schließbereich", + "risk_in": {"f": 3, "w": 2, "p": 2, "s": 1, "r": 7}, + "measures": ["Geringe Schließkraft, Einklemmschutz", "Abgerundete Türkanten"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 1, "r": 3}, + "norm_references": ["EN ISO 12100"], + "sufficient": true + }, + { + "nr": "4.3", + "hazard_group": "Mechanische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Kontakt mit rotierendem Spülarm bei geöffneter Tür", + "hazard_cause": "Eingreifen in die Spülkammer bei noch nachlaufendem rotierendem Spülarm/Spülfeld nach dem Öffnen der Tür", + "lifecycle_phases": ["Betrieb", "Reinigung"], + "component_zone": "Spülkammer, Spülarm und Spülfeld", + "risk_in": {"f": 2, "w": 2, "p": 2, "s": 1, "r": 6}, + "measures": ["Türverriegelung stoppt Spülarm beim Öffnen"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 1, "r": 3}, + "norm_references": ["EN ISO 12100"], + "sufficient": true + }, + { + "nr": "5.1", + "hazard_group": "Ergonomische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Belastung des Bewegungsapparats durch wiederholte Be- und Entladung", + "hazard_cause": "Wiederholtes Heben und Bücken beim manuellen Be- und Entladen der Spülkörbe am Untertischgerät", + "lifecycle_phases": ["Betrieb"], + "component_zone": "Be- und Entladestelle, Spülkorb", + "risk_in": {"f": 4, "w": 3, "p": 2, "s": 1, "r": 9}, + "measures": ["Ergonomische Arbeitshöhe", "Be-/Entladung auf günstiger Greifhöhe"], + "measure_type": "KM", + "risk_out": {"f": 2, "w": 1, "p": 1, "s": 1, "r": 4}, + "norm_references": ["EN 1005-2"], + "sufficient": true + }, + { + "nr": "5.2", + "hazard_group": "Ergonomische Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Zwangshaltung durch ungünstige Bedienelement-Position", + "hazard_cause": "Bedienelemente am HMI außerhalb der ergonomisch günstigen Reichweite führen bei dauerhafter Bedienung zu Zwangshaltung", + "lifecycle_phases": ["Betrieb"], + "component_zone": "Bedienstand HMI, Steuerpult", + "risk_in": {"f": 3, "w": 2, "p": 1, "s": 1, "r": 6}, + "measures": ["Bedienelemente in ergonomisch günstiger Höhe"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 1, "r": 3}, + "norm_references": ["EN 894-3"], + "sufficient": true + }, + { + "nr": "6.1", + "hazard_group": "zusätzliche Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Verlust einer Sicherheitsfunktion durch Steuerungs- oder Softwarefehler", + "hazard_cause": "Steuerungs- oder Softwarefehler der eigenen Maschinensteuerung führt zu unkontrolliertem Verhalten oder Verlust einer Sicherheitsfunktion", + "lifecycle_phases": ["Betrieb", "Instandhaltung"], + "component_zone": "Gesamte Maschine, Steuerung", + "risk_in": {"f": 2, "w": 2, "p": 2, "s": 3, "r": 18}, + "measures": ["Sichere Fehlerbehandlung", "Sichere Software-Fallbacks", "Watchdog"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 3, "r": 9}, + "norm_references": ["EN ISO 13849-1"], + "sufficient": true + }, + { + "nr": "6.2", + "hazard_group": "zusätzliche Gefährdungen", + "hazard_group_applicable": true, + "hazard_type": "Verlust der Sicherheitsfunktion nach fehlerhaftem Software-Update", + "hazard_cause": "Korrupte oder inkompatible Firmware nach fehlerhaftem Update über die USB-Schnittstelle lässt die Steuerung undefiniert verhalten oder Sicherheitsfunktion verlieren", + "lifecycle_phases": ["Instandhaltung"], + "component_zone": "Gesamte Maschine, Steuerung, Update-Schnittstelle", + "risk_in": {"f": 2, "w": 2, "p": 2, "s": 3, "r": 18}, + "measures": ["Atomares Update mit Rückfall auf lauffähige Version", "Kompatibilitätsprüfung vor Update"], + "measure_type": "KM", + "risk_out": {"f": 1, "w": 1, "p": 1, "s": 3, "r": 9}, + "norm_references": ["EN ISO 13849-1"], + "sufficient": true + } + ] +}