feat(iace): add data-driven Architektur & Datenfluss explainer tab
Adds an auditor-facing view of the IACE engine: a clickable 10-stage pipeline flow (Grenzen-Formular → ParseNarrative → Pattern-Gates → Relevanz → Caps → Gefährdungen → Maßnahmen → Risiko → Normen → Matrix), plus live library counts, the data-source/license register (incl. the DIN/Beuth + DGUV exclusions), and the norm-matching logic that reconciles DIN/ISO/OSHA machine-type vocabulary via canonicalMachineType folding. Backend: BuildArchitecture() with LIVE counts so the diagram can never drift; GET /iace/architecture; collectAllNorms() extracted from SuggestNorms as the single source of truth for the norm-library count. Frontend: useArchitecture hook + page + new IACE nav tab. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package iace
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// BuildArchitecture is the data-driven engine self-description rendered in the
|
||||
// "Architektur & Datenfluss" auditability tab. These tests pin its shape and,
|
||||
// crucially, that the library counts are LIVE (non-zero, drawn from the running
|
||||
// engine) — a zero count would mean the diagram silently drifted from reality.
|
||||
|
||||
func TestBuildArchitecture_Shape(t *testing.T) {
|
||||
a := BuildArchitecture()
|
||||
|
||||
if len(a.Stages) != 10 {
|
||||
t.Errorf("expected 10 pipeline stages, got %d", len(a.Stages))
|
||||
}
|
||||
// Stage order is the audit narrative — first is the limits form, last the matrix.
|
||||
if len(a.Stages) > 0 && a.Stages[0].ID != "grenzen" {
|
||||
t.Errorf("first stage should be grenzen, got %q", a.Stages[0].ID)
|
||||
}
|
||||
if last := a.Stages[len(a.Stages)-1]; last.ID != "matrix" {
|
||||
t.Errorf("last stage should be matrix, got %q", last.ID)
|
||||
}
|
||||
for _, s := range a.Stages {
|
||||
if s.Title == "" || s.Summary == "" || s.Logic == "" || s.DataSource == "" {
|
||||
t.Errorf("stage %q has empty required prose: %+v", s.ID, s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(a.NormMatching) == 0 {
|
||||
t.Error("norm_matching explanation must not be empty")
|
||||
}
|
||||
if len(a.Evidence) == 0 {
|
||||
t.Error("evidence (ESAW citations) must not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildArchitecture_LiveLibraryCounts(t *testing.T) {
|
||||
a := BuildArchitecture()
|
||||
|
||||
if len(a.Libraries) == 0 {
|
||||
t.Fatal("no libraries reported")
|
||||
}
|
||||
for _, l := range a.Libraries {
|
||||
if l.Name == "" || l.SourceFile == "" {
|
||||
t.Errorf("library missing name/source: %+v", l)
|
||||
}
|
||||
if l.Count <= 0 {
|
||||
t.Errorf("library %q has non-live count %d (expected >0 from running engine)", l.Name, l.Count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildArchitecture_DataSourcesIncludeExclusions(t *testing.T) {
|
||||
a := BuildArchitecture()
|
||||
|
||||
var hasUsed, hasExcluded bool
|
||||
for _, d := range a.DataSources {
|
||||
switch d.Status {
|
||||
case "verwendet":
|
||||
hasUsed = true
|
||||
case "ausgeschlossen":
|
||||
hasExcluded = true
|
||||
default:
|
||||
t.Errorf("data source %q has unexpected status %q", d.Name, d.Status)
|
||||
}
|
||||
if d.License == "" {
|
||||
t.Errorf("data source %q missing license", d.Name)
|
||||
}
|
||||
}
|
||||
if !hasUsed {
|
||||
t.Error("expected at least one used data source")
|
||||
}
|
||||
// The copyright guardrail is auditable only if the EXCLUDED norm tables are
|
||||
// shown as deliberately not-reproduced — not silently omitted.
|
||||
if !hasExcluded {
|
||||
t.Error("expected DIN/Beuth norm tables to appear as an explicit exclusion")
|
||||
}
|
||||
|
||||
// The licensing guardrail must be spelled out in the norm-matching prose:
|
||||
// norm tables are referenced, never reproduced.
|
||||
joined := strings.ToLower(strings.Join(a.NormMatching, " "))
|
||||
if !strings.Contains(joined, "reproduziert") {
|
||||
t.Error("norm-matching prose should state norm tables are never reproduced")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user