Files
breakpilot-compliance/ai-compliance-sdk/internal/iace/architecture.go
T
Benjamin Admin 6846ca6b28 feat(iace): wire OSHA minimum-distance library into measures + endpoint
The May-built OSHA distance library (minimum_distances.go, 29 CFR 1910,
US public domain) was dead code — zero callers, no route, no test, while
the mm values that actually appear in measures are independent hand-prose
(some carrying ISO 13854/13857 values, not OSHA).

This surfaces it without touching the measures response contract:
- GET /iace/minimum-distances (+ ?measure_id=) returns the distances, the
  curated measure→distance link table and the licensing note.
- AllMeasureDistanceLinks/MinimumDistancesForMeasure resolve only the
  defensible links (M600 value_source; M254/M065 public-domain crossref to
  ISO), with the relation made explicit so the join stays honest.
- architecture.go lists the OSHA library so it shows in the audit explainer.
- Tests: inch→mm conversion + license completeness, link integrity, and a
  consistency test pinning that a value_source measure's prose still
  matches the OSHA source (codifies the audit finding as a regression gate).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 11:17:56 +02:00

168 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package iace
// Data-driven self-description of the IACE engine for the "Architektur &
// Datenfluss" explainer. Counts are LIVE (derived from the running engine) so
// the diagram can never drift from reality; the stage/source prose is curated.
// Purpose: let an auditor see WHERE each datum comes from and HOW a risk
// assessment is reached — every gate, library and data source, in order.
// ArchStage is one step of the deterministic pipeline.
type ArchStage struct {
ID string `json:"id"`
Title string `json:"title"`
Summary string `json:"summary"`
Input string `json:"input"`
Logic string `json:"logic"`
DataSource string `json:"data_source"` // code/library it draws from
Example string `json:"example"`
}
// ArchLibrary is one knowledge base with a LIVE entry count.
type ArchLibrary struct {
Name string `json:"name"`
Count int `json:"count"`
SourceFile string `json:"source_file"`
Description string `json:"description"`
}
// ArchDataSource is an external statistic/standard with its license + status.
type ArchDataSource struct {
Name string `json:"name"`
License string `json:"license"`
Usage string `json:"usage"`
Status string `json:"status"` // "verwendet" | "ausgeschlossen"
}
// Architecture is the full self-description returned by GET /iace/architecture.
type Architecture struct {
Stages []ArchStage `json:"stages"`
Libraries []ArchLibrary `json:"libraries"`
DataSources []ArchDataSource `json:"data_sources"`
NormMatching []string `json:"norm_matching"`
Evidence []RiskEvidence `json:"evidence"`
}
// distinctDomainGates counts the distinct dom_* capability tags the engine gates on.
func distinctDomainGates() int {
seen := map[string]bool{}
for _, tag := range domainGateTerms {
seen[tag] = true
}
return len(seen)
}
// BuildArchitecture assembles the engine self-description with live counts.
func BuildArchitecture() Architecture {
return Architecture{
Stages: []ArchStage{
{
ID: "grenzen", Title: "1 · Grenzen-Formular",
Summary: "Maschinenbeschreibung nach EN ISO 12100 (Verwendungs-, räumliche, zeitliche Grenzen).",
Input: "17 Felder: Beschreibung, Verwendung, Fehlanwendung, Schnittstellen (elektrisch/mechanisch/pneumatisch-hydraulisch), Umgebung, Personen …",
Logic: "Alle Felder werden zu einer Narrative zusammengeführt (kein Whitelist — jedes Feld ist eine potenzielle Gefährdungsquelle).",
DataSource: "project.metadata.limits_form",
Example: "„Hubantrieb über Kette … 230 V … keine pneumatischen Schnittstellen.“",
},
{
ID: "parse", Title: "2 · ParseNarrative",
Summary: "Deterministische Extraktion von Komponenten, Energiequellen, Domänen-Tags und Negationen.",
Input: "Narrative-Text + Maschinentyp",
Logic: "Keyword-Wörterbuch (Substring, umlaut-normalisiert) → Komponenten + Energie + dom_*-Tags. Negation („keine Pneumatik“) ⇒ Komponente als verneint markiert, liefert KEINE Tags.",
DataSource: "keyword_dictionary.go",
Example: "„Kette“→Komponente, „230 V“→electrical_energy, „Presse“→dom_press.",
},
{
ID: "match", Title: "3 · Pattern-Engine (Gates)",
Summary: "Jedes Gefährdungsmuster wird gegen die Maschine geprüft — harte UND-Gates.",
Input: "Komponenten-Tags, Energie-Tags, Lebensphasen, Maschinentyp, dom_*-Tags",
Logic: "patternMatches: MachineType ∧ Required-Component-Tags ∧ Required-Energy-Tags ∧ Lifecycle ∧ Operational-States. Capability-Domain-Gates (dom_*) verhindern Cross-Domänen-Leaks (z. B. Schwimmbad-Muster feuert nicht für eine Presse). Default-open, wenn ein Gate-Input leer ist.",
DataSource: "pattern_engine.go + pattern_domain_gates.go + hazard_patterns_*.go",
Example: "Presse-Muster feuert nur, wenn machine_type∈Presse-Familie UND high_force-Tag vorhanden.",
},
{
ID: "relevance", Title: "4 · Relevanz-Backstop",
Summary: "Generischer Filter gegen Rest-Leaks ungegateter Muster.",
Input: "Gefeuertes Muster + Narrative + Komponenten-Namen",
Logic: "IsPatternRelevant: Token-Grenzen + Stoppwort-Liste — ein Muster wird verworfen, wenn sein spezifischer Anker nicht in der Narrative vorkommt.",
DataSource: "pattern_relevance.go",
Example: "Verwirft „Bandsäge“-Hazard, wenn die Narrative keine Säge nennt.",
},
{
ID: "caps", Title: "5 · Kategorie-Caps",
Summary: "Begrenzung der Gefährdungen je Kategorie (skaliert mit Komponentenzahl).",
Input: "Gefeuerte Muster je Gefährdungskategorie",
Logic: "categoryHazardCap: pro Kategorie ein Maximum (verhindert Über-Flutung); Dedupe über Kategorie × Zone.",
DataSource: "iace_handler_init.go",
Example: "max. N mechanical_hazard-Gefährdungen je Projekt.",
},
{
ID: "hazards", Title: "6 · Gefährdungen",
Summary: "Die erzeugten Gefährdungen (Szenario, Auslöser, Schaden, Zone, betroffene Person).",
Input: "Überlebende Muster + zugeordnete Komponente",
Logic: "Pro Muster: Szenario/Trigger/Harm/Zone aus dem Muster; Komponentenzuordnung tag-basiert (pickComponentForPattern).",
DataSource: "iace_hazards (DB)",
Example: "„Quetschen im Werkzeugeinbauraum zwischen Ober- und Unterwerkzeug.“",
},
{
ID: "measures", Title: "7 · Maßnahmen",
Summary: "Schutzmaßnahmen je Gefährdung — kategorie-gefiltert, KEINE generischen Defaults.",
Input: "Gefährdung + musterspezifische Suggested-Measure-IDs",
Logic: "Nur Maßnahmen, deren Kategorie zur Gefährdung passt (isCategoryCompatible). Ohne passende Maßnahme ⇒ 0 Maßnahmen + Coverage-Gap (ehrlich, statt Unsinn).",
DataSource: "measures_library*.go",
Example: "Sharp-edge-Gefährdung ⇒ keine „Rotation vermeiden“-Maßnahme.",
},
{
ID: "risk", Title: "8 · Risiko (S/F/W/P + Konfidenz)",
Summary: "Konfidenz-bewusste Risikoschätzung je Gefährdung — als Bereich, nicht Punktwert.",
Input: "Gefährdungskategorie + Szenario (Kontaktart) + Lebensphasen",
Logic: "EstimateSeverity/Frequency/ProbabilityW/AvoidabilityP → R = S×(F+W+P), Band + Bereich (±1 je validierter Genauigkeit) + Konfidenz (Verletzungsmechanismus eindeutig?). W verankert am ESAW-Kontaktmodus-Ranking; eigenes Modell, KEINE Norm-Tabelle.",
DataSource: "risk_estimation.go + risk_data_sources.go (ESAW, CC BY 4.0)",
Example: "Elektrischer Schlag: R≈32 (Bereich 2145, mittelkritisch), Konfidenz hoch.",
},
{
ID: "norms", Title: "9 · Normen (A/B/C + Familien-Matching)",
Summary: "Passende Normen je Maschinentyp und Gefährdung; DIN/ISO/OSHA-Vokabular versöhnt.",
Input: "Maschinentyp + Gefährdungskategorien + Tags",
Logic: "SuggestNorms: C-Normen exakt per Maschinentyp-FAMILIE (canonicalMachineType: welding_machine→welding); B-Normen per Gefährdungskategorie/Tags; A-Normen gelten immer. Normen werden nur referenziert, Tabellen nie reproduziert.",
DataSource: "norms_engine.go + machine_type_families.go + norms_library*.go",
Example: "Schweißanlage ⇒ EN 60974-x (Lichtbogenschweißen), obwohl Norm auf „welding_machine“ getaggt.",
},
{
ID: "matrix", Title: "10 · Risiko-Matrix / GT-Benchmark",
Summary: "Projektweite Risiko-Matrix (Schwere × Wahrscheinlichkeit) und Abgleich gegen Experten-Ground-Truth.",
Input: "Alle Gefährdungen + (optional) GT-Projekt",
Logic: "BuildRiskMatrix aggregiert je Zelle; Benchmark vergleicht Tool-S/F/W/P + Fine-Kinney gegen Fachmann-GT (Übereinstimmung within±1, Rang-Konkordanz).",
DataSource: "risk_matrix.go + risk_benchmark.go",
Example: "Kistenhub vs. eigene GT: S±1 94 %, Ranking 86 %.",
},
},
Libraries: []ArchLibrary{
{Name: "Hazard-Pattern-Bibliothek", Count: len(AllPatterns()), SourceFile: "hazard_patterns_*.go", Description: "Gefährdungsmuster mit Gates (MachineType/Tags/Energy/Lifecycle) + Szenario/Trigger/Harm/Zone."},
{Name: "Maßnahmen-Bibliothek", Count: len(GetProtectiveMeasureLibrary()), SourceFile: "measures_library*.go", Description: "Schutzmaßnahmen mit Reduktionstyp + Norm-Referenzen, kategorie-gefiltert."},
{Name: "Normen-Bibliothek (A/B/C)", Count: len(collectAllNorms()), SourceFile: "norms_library*.go", Description: "A-/B-/C-Normen mit Maschinentypen, Gefährdungskategorien und Tags."},
{Name: "Komponenten-Bibliothek", Count: len(GetComponentLibrary()), SourceFile: "component_library.go", Description: "Bauteiltypen mit Capability-Tags für das Pattern-Gating."},
{Name: "Energiequellen", Count: len(GetEnergySources()), SourceFile: "component_library.go", Description: "Energiearten (elektrisch/pneumatisch/hydraulisch …) für Energie-Gates."},
{Name: "Maschinentyp-Vokabular", Count: len(MachineTypeVocabulary()), SourceFile: "machine_types.go", Description: "Kanonische Dropdown-Maschinentypen, auf die Patterns gaten."},
{Name: "Domänen-Capability-Gates", Count: distinctDomainGates(), SourceFile: "pattern_domain_gates.go", Description: "dom_*-Tags, die domänenspezifische Muster auf ihre echte Maschine begrenzen (Leak-Schutz)."},
{Name: "Kontaktmodus-Tiers", Count: len(contactModeTable), SourceFile: "risk_estimation.go", Description: "Verletzungsmechanismen mit W/P/S-Tiers (ESAW-verankert, GT-kalibriert)."},
{Name: "Kontaktmodus-Evidenz", Count: len(contactModeEvidence), SourceFile: "risk_data_sources.go", Description: "Belegte öffentliche Statistik-Quoten (ESAW) als Zitat-/Audit-Schicht."},
{Name: "OSHA-Mindestabstände", Count: len(GetOSHAMinimumDistances()), SourceFile: "minimum_distances.go", Description: "OSHA 29 CFR 1910 Sicherheitsabstände (Public Domain) + Maßnahmen-Verknüpfung; EU-Normen nur referenziert."},
},
DataSources: []ArchDataSource{
{Name: "Eurostat ESAW (Kontaktmodus-Unfallstatistik)", License: "CC BY 4.0", Usage: "Anker für Wahrscheinlichkeits-Tiers (W) + zitierbare Quoten", Status: "verwendet"},
{Name: "US BLS / OSHA (Arbeitsunfälle)", License: "Public Domain", Usage: "Ergänzende Häufigkeits-/Schwere-Anker + OSHA-Maßnahmen", Status: "verwendet"},
{Name: "UK HSE (RIDDOR)", License: "Open Government Licence v3", Usage: "Zulässige Ergänzung (Attribution)", Status: "verwendet"},
{Name: "DGUV-Statistik", License: "nur redaktionell, keine Bearbeitung", Usage: "—", Status: "ausgeschlossen"},
{Name: "DIN/Beuth/ISO/IEC Risikograph-Tabellen", License: "urheberrechtlich", Usage: "Nur als Referenz genannt, NIE reproduziert/re-implementiert", Status: "ausgeschlossen"},
},
NormMatching: []string{
"C-Normen (maschinenspezifisch): Match nur über die kanonische Maschinentyp-FAMILIE — `canonicalMachineType` faltet das feingranulare Normen-Vokabular (455 Keys: welding_machine, band_saw, mobile_crane …) auf die 68 Dropdown-Keys. Ohne Familien-Match wird die C-Norm verworfen (kein Tag/Kategorie-Fallback → keine Fremd-Domänen-Normen).",
"B-Normen (gefährdungsspezifisch): Match über Gefährdungskategorie und Komponenten-/Energie-Tags.",
"A-Normen (Grundnormen): gelten immer (z. B. EN ISO 12100).",
"DIN/ISO/OSHA-Versöhnung: Normen tragen teils OSHA-/ISO-/DIN-nahe Maschinen-Keys; die Familien-Faltung sorgt dafür, dass z. B. eine „welding_machine“-Norm für eine „welding“-Maschine matched.",
"Lizenz-Leitplanke: Norm-Tabellen/Risikographen werden NIE reproduziert — nur Norm-Referenzen ausgegeben.",
},
Evidence: AllRiskEvidence(),
}
}