Files
breakpilot-compliance/ai-compliance-sdk/internal/iace/fmea_data_sources_test.go
T
Benjamin Admin de140e564e feat(iace): FMEA P1 — open methodology anchors + bp_iace_fmea_kb
P1 of the auto-FMEA build plan: establish the public-domain methodology
foundation (no AIAG-VDA/SAE/IEC tables reproduced).
- fmea_data_sources.go: MIL-STD-882E severity (Cat I-IV→1-10) + probability
  (A-F→1-10 with per-hour λ bands), OccurrenceFromRate(λp·α), SeverityForCategory,
  MIL-STD-1629A CriticalityCm = λp·α·β·t. Own 1-10 projection, government-anchored.
- 4 versioned source docs (MIL-STD-1629A, MIL-STD-882E, NASA RCM, FMD-91/NPRD-91)
  ingested into the new RAG collection bp_iace_fmea_kb (whitelisted).
- Tests for all scales/mappings/criticality (green).

Next (P1 step 2): fetch FMD-91/NPRD-91 bulk λ/α tables from DTIC.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-12 22:59:01 +02:00

94 lines
2.5 KiB
Go

package iace
import (
"math"
"testing"
)
func TestMILStd882Severity_OrderedAndComplete(t *testing.T) {
bands := MILStd882Severity()
if len(bands) != 4 {
t.Fatalf("expected 4 severity categories, got %d", len(bands))
}
wantCat := []string{"I", "II", "III", "IV"}
prev := 11
for i, b := range bands {
if b.Category != wantCat[i] {
t.Errorf("band %d: category %q, want %q", i, b.Category, wantCat[i])
}
if b.LabelDE == "" || b.Desc == "" {
t.Errorf("band %s: empty label/desc", b.Category)
}
if b.Severity < 1 || b.Severity > 10 {
t.Errorf("band %s: severity %d out of 1-10", b.Category, b.Severity)
}
if b.Severity >= prev {
t.Errorf("severity not strictly descending at %s (%d >= %d)", b.Category, b.Severity, prev)
}
prev = b.Severity
}
}
func TestMILStd882Probability_OrderedBands(t *testing.T) {
bands := MILStd882Probability()
if len(bands) != 6 {
t.Fatalf("expected 6 probability levels, got %d", len(bands))
}
prevOcc, prevLambda := 11, math.Inf(1)
for _, b := range bands {
if b.Occurrence < 1 || b.Occurrence > 10 {
t.Errorf("%s: occurrence %d out of 1-10", b.Level, b.Occurrence)
}
if b.Occurrence >= prevOcc {
t.Errorf("occurrence not descending at %s", b.Level)
}
if b.LambdaMax >= prevLambda {
t.Errorf("lambda_max not descending at %s", b.Level)
}
prevOcc, prevLambda = b.Occurrence, b.LambdaMax
}
}
func TestOccurrenceFromRate(t *testing.T) {
cases := []struct {
rate float64
want int
}{
{1.0, 10}, // A frequent
{0.05, 8}, // B probable (1e-2..1e-1)
{0.005, 6}, // C occasional (1e-3..1e-2)
{1e-4, 4}, // D remote (1e-6..1e-3)
{1e-8, 2}, // E improbable (<1e-6)
{0, 1}, // F eliminated
{-5, 1}, // guard
}
for _, c := range cases {
if got := OccurrenceFromRate(c.rate); got != c.want {
t.Errorf("OccurrenceFromRate(%g) = %d, want %d", c.rate, got, c.want)
}
}
}
func TestSeverityForCategory(t *testing.T) {
if got := SeverityForCategory("I"); got != 10 {
t.Errorf("Cat I = %d, want 10", got)
}
if got := SeverityForCategory("IV"); got != 2 {
t.Errorf("Cat IV = %d, want 2", got)
}
if got := SeverityForCategory("Z"); got != 0 {
t.Errorf("unknown cat = %d, want 0", got)
}
}
func TestCriticalityCm(t *testing.T) {
// Cm = λp·α·β·t = 1e-5 · 0.3 · 1.0 · 1000 = 3e-3
got := CriticalityCm(1e-5, 0.3, 1.0, 1000)
if math.Abs(got-3e-3) > 1e-9 {
t.Errorf("Cm = %g, want 3e-3", got)
}
if CriticalityCm(-1, 0.3, 1, 1000) != 0 {
t.Error("negative input should yield 0")
}
}