feat(iace): unified FailureKnowledge ontology + NASA starter (FMEA P2)
The source-agnostic failure ontology shared by the FMEA library and the CE hazard side: Component → FailureMode → Mechanism → Effect → Hazard → Harm → Control, each row source+licence tagged. A licence ALLOWLIST (FailureKnowledgeLicenseAllowed) rejects copyrighted/proprietary/NC sources up front (© IITRI, DIN/ISO, AIAG, OREDA, CC-BY-NC) — the discipline learned from the FMD-91/NPRD-91 licence finding. Seeded with a curated NASA NTRS lessons-learned starter (5 real entries, public domain). GET /iace/failure-knowledge (+ ?domain=). Tests pin the governance invariant: every entry must carry a commercially-usable licence. Next: Playwright+OCR bulk loader (NTRS API → PDF/OCR → tuple extraction) to grow the corpus from NASA/OSHA/CPSC/MAUDE/NTSB. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ListFailureKnowledge handles GET /failure-knowledge.
|
||||
// Read-only unified failure-knowledge ontology (Component → FailureMode →
|
||||
// Mechanism → Effect → Hazard → Harm → Control) curated from commercially-usable
|
||||
// open sources (currently NASA NTRS, public domain). Optional ?domain= filter.
|
||||
// This is the shared corpus that seeds the FMEA library and the CE hazard side.
|
||||
func (h *IACEHandler) ListFailureKnowledge(c *gin.Context) {
|
||||
var items []iace.FailureKnowledge
|
||||
if d := c.Query("domain"); d != "" {
|
||||
items = iace.FailureKnowledgeByDomain(d)
|
||||
} else {
|
||||
items = iace.AllFailureKnowledge()
|
||||
}
|
||||
if items == nil {
|
||||
items = []iace.FailureKnowledge{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"failure_knowledge": items,
|
||||
"total": len(items),
|
||||
})
|
||||
}
|
||||
@@ -31,6 +31,7 @@ func registerIACERoutes(v1 *gin.RouterGroup, h *handlers.IACEHandler) {
|
||||
iaceRoutes.GET("/component-library", h.ListComponentLibrary)
|
||||
iaceRoutes.GET("/energy-sources", h.ListEnergySources)
|
||||
iaceRoutes.GET("/minimum-distances", h.ListMinimumDistances)
|
||||
iaceRoutes.GET("/failure-knowledge", h.ListFailureKnowledge)
|
||||
iaceRoutes.GET("/tags", h.ListTags)
|
||||
iaceRoutes.GET("/hazard-patterns", h.ListHazardPatterns)
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package iace
|
||||
|
||||
import "strings"
|
||||
|
||||
// FailureKnowledge is one curated, source-attributed failure record in the
|
||||
// UNIFIED ontology shared by the FMEA library and the CE hazard side:
|
||||
//
|
||||
// Component → FailureMode → (Mechanism) → Effect → Hazard → Harm → Control
|
||||
//
|
||||
// Every source (NASA, OSHA, CPSC, MAUDE, NTSB …) maps into THIS one schema with
|
||||
// a licence tag. The licence allowlist (FailureKnowledgeLicenseAllowed) rejects
|
||||
// non-commercial / copyrighted / proprietary sources up front — the same
|
||||
// discipline that kept FMD-91/NPRD-91 (© IITRI) and DIN/ISO out.
|
||||
type FailureKnowledge struct {
|
||||
ID string `json:"id"` // FK-NASA-0001
|
||||
Component string `json:"component"` // canonical → component library
|
||||
FailureMode string `json:"failure_mode"` // canonical → mode taxonomy
|
||||
Mechanism string `json:"mechanism"` // cause: fatigue, contamination, wear…
|
||||
Effect string `json:"effect"` // system-level effect
|
||||
Hazard string `json:"hazard,omitempty"` // EN ISO 12100 category; "" = pure reliability
|
||||
Harm string `json:"harm,omitempty"`
|
||||
Control string `json:"control"` // recommended action / mitigation
|
||||
Domain string `json:"domain"` // space/aviation/medical/consumer/industrial/general
|
||||
Source string `json:"source"`
|
||||
License string `json:"license"`
|
||||
Attribution string `json:"attribution"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// FailureKnowledgeLicenseAllowed reports whether a licence string is a
|
||||
// commercially-usable open licence. Allowlist by keyword; anything signalling
|
||||
// "all rights reserved", non-commercial, or a known copyrighted/proprietary
|
||||
// source is rejected.
|
||||
func FailureKnowledgeLicenseAllowed(license string) bool {
|
||||
l := strings.ToLower(license)
|
||||
// Hard rejects first (a copyrighted source may also say "public"…).
|
||||
for _, bad := range []string{
|
||||
"all rights reserved", "non-commercial", "noncommercial", "cc by-nc", "cc-by-nc",
|
||||
"-nd", "no derivatives", "proprietary", "iitri", "quanterion", "oreda",
|
||||
"din ", "beuth", "iso ", "iec ", "aiag", "vda", "sae j", "dguv",
|
||||
} {
|
||||
if strings.Contains(l, bad) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, ok := range []string{
|
||||
"public domain", "public_use_permitted", "gov_public", "cc0",
|
||||
"cc by 4", "cc-by 4", "cc by-sa", "cc-by-sa",
|
||||
"open government licence", "ogl", "mit", "apache", "bsd",
|
||||
"reproduction authorised", "reproduction authorized",
|
||||
} {
|
||||
if strings.Contains(l, ok) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetNASAFailureKnowledge returns the curated NASA lessons-learned starter set
|
||||
// (US-Gov public domain, NTRS). Each entry is anchored on a real NTRS document;
|
||||
// fields the source did not state are left empty rather than guessed.
|
||||
func GetNASAFailureKnowledge() []FailureKnowledge {
|
||||
const lic = "Public Domain (NASA NTRS, GOV_PUBLIC_USE_PERMITTED)"
|
||||
ntrs := func(id string) string {
|
||||
return "https://ntrs.nasa.gov/api/citations/" + id + "/downloads/" + id + ".pdf"
|
||||
}
|
||||
return []FailureKnowledge{
|
||||
{
|
||||
ID: "FK-NASA-0001", Component: "flow_control_valve_poppet", FailureMode: "fracture",
|
||||
Mechanism: "high-cycle fatigue / contamination", Effect: "sluggish valve response, loss of flow control",
|
||||
Control: "contamination control + design margin against resonant fatigue", Domain: "propulsion",
|
||||
Source: "NASA NTRS 20110013003", License: lic,
|
||||
Attribution: "NASA, Lessons Learned from the SSME Hydrogen Flow Control Valve Poppet Breakage (public domain)",
|
||||
URL: ntrs("20110013003"),
|
||||
},
|
||||
{
|
||||
ID: "FK-NASA-0002", Component: "control_moment_gyroscope", FailureMode: "mechanical_failure",
|
||||
Mechanism: "in-service degradation (under investigation)", Effect: "loss of attitude-control actuator",
|
||||
Control: "redundancy + condition monitoring + return-for-failure-analysis", Domain: "spacecraft",
|
||||
Source: "NASA NTRS 20100021932", License: lic,
|
||||
Attribution: "NASA, Space Station Control Moment Gyroscope Lessons Learned (public domain)",
|
||||
URL: ntrs("20100021932"),
|
||||
},
|
||||
{
|
||||
ID: "FK-NASA-0003", Component: "composite_structure", FailureMode: "fracture",
|
||||
Mechanism: "accumulation/propagation of damage", Effect: "load-carrying capability below required → structural failure",
|
||||
Hazard: "mechanical_hazard", Harm: "structural collapse", Control: "damage-tolerance design + inspection",
|
||||
Domain: "structures", Source: "NASA NTRS 20080015747", License: lic,
|
||||
Attribution: "NASA, Lessons Learned from Recent Failure and Incident Investigations (public domain)",
|
||||
URL: ntrs("20080015747"),
|
||||
},
|
||||
{
|
||||
ID: "FK-NASA-0004", Component: "pressurized_garment", FailureMode: "ignition",
|
||||
Mechanism: "flash fire during functional test", Effect: "unit destroyed",
|
||||
Hazard: "fire_explosion", Harm: "fire", Control: "oxygen-fire control + material/ignition-source review",
|
||||
Domain: "life_support", Source: "NASA NTRS 20230013281", License: lic,
|
||||
Attribution: "NASA, Lessons Learned from the EMU Fire (public domain)",
|
||||
URL: ntrs("20230013281"),
|
||||
},
|
||||
{
|
||||
ID: "FK-NASA-0005", Component: "fuel_cell", FailureMode: "loss_of_function",
|
||||
Mechanism: "design conditions leading to component failure", Effect: "loss of electrical power generation",
|
||||
Hazard: "electrical_hazard", Control: "design changes per identified failure mode", Domain: "power",
|
||||
Source: "NASA NTRS 20090016297", License: lic,
|
||||
Attribution: "NASA, Apollo CSM Power Generation System Design Considerations (public domain)",
|
||||
URL: ntrs("20090016297"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AllFailureKnowledge aggregates every source's curated entries (currently NASA;
|
||||
// OSHA/CPSC/MAUDE/NTSB will append here as they are added).
|
||||
func AllFailureKnowledge() []FailureKnowledge {
|
||||
var all []FailureKnowledge
|
||||
all = append(all, GetNASAFailureKnowledge()...)
|
||||
return all
|
||||
}
|
||||
|
||||
// FailureKnowledgeByDomain filters the corpus by domain (e.g. "industrial").
|
||||
func FailureKnowledgeByDomain(domain string) []FailureKnowledge {
|
||||
var out []FailureKnowledge
|
||||
for _, fk := range AllFailureKnowledge() {
|
||||
if fk.Domain == domain {
|
||||
out = append(out, fk)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package iace
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFailureKnowledgeLicenseAllowed(t *testing.T) {
|
||||
accept := []string{
|
||||
"Public Domain (NASA NTRS, GOV_PUBLIC_USE_PERMITTED)",
|
||||
"US Public Domain",
|
||||
"CC BY 4.0",
|
||||
"CC BY-SA 4.0",
|
||||
"Open Government Licence v3.0",
|
||||
"MIT",
|
||||
}
|
||||
for _, l := range accept {
|
||||
if !FailureKnowledgeLicenseAllowed(l) {
|
||||
t.Errorf("license should be ALLOWED: %q", l)
|
||||
}
|
||||
}
|
||||
reject := []string{
|
||||
"© 1991, IIT Research Institute. All Rights Reserved.", // FMD-91/NPRD-91
|
||||
"CC BY-NC 4.0",
|
||||
"proprietary (Quanterion)",
|
||||
"DIN EN ISO 13849 table",
|
||||
"AIAG-VDA handbook",
|
||||
"OREDA member-only",
|
||||
"CC BY-ND",
|
||||
"",
|
||||
}
|
||||
for _, l := range reject {
|
||||
if FailureKnowledgeLicenseAllowed(l) {
|
||||
t.Errorf("license should be REJECTED: %q", l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNASAFailureKnowledge_Integrity(t *testing.T) {
|
||||
seen := map[string]bool{}
|
||||
nasa := GetNASAFailureKnowledge()
|
||||
if len(nasa) == 0 {
|
||||
t.Fatal("NASA starter set is empty")
|
||||
}
|
||||
for _, fk := range nasa {
|
||||
if seen[fk.ID] {
|
||||
t.Errorf("duplicate FK id %q", fk.ID)
|
||||
}
|
||||
seen[fk.ID] = true
|
||||
if fk.ID == "" || fk.Component == "" || fk.FailureMode == "" || fk.Effect == "" ||
|
||||
fk.Control == "" || fk.Source == "" || fk.License == "" || fk.Attribution == "" {
|
||||
t.Errorf("%s: empty required field: %+v", fk.ID, fk)
|
||||
}
|
||||
if fk.URL == "" {
|
||||
t.Errorf("%s: NASA entry missing source URL", fk.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Governance invariant: EVERY curated entry must carry a commercially-usable
|
||||
// licence — this is the gate that keeps copyrighted/proprietary data out.
|
||||
func TestAllFailureKnowledge_LicensesAllowed(t *testing.T) {
|
||||
for _, fk := range AllFailureKnowledge() {
|
||||
if !FailureKnowledgeLicenseAllowed(fk.License) {
|
||||
t.Errorf("%s carries a non-allowed licence %q", fk.ID, fk.License)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureKnowledgeByDomain(t *testing.T) {
|
||||
all := AllFailureKnowledge()
|
||||
if len(all) == 0 {
|
||||
t.Fatal("no failure knowledge")
|
||||
}
|
||||
d := all[0].Domain
|
||||
got := FailureKnowledgeByDomain(d)
|
||||
if len(got) == 0 {
|
||||
t.Errorf("expected entries for domain %q", d)
|
||||
}
|
||||
for _, fk := range got {
|
||||
if fk.Domain != d {
|
||||
t.Errorf("domain filter leaked %q into %q", fk.Domain, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user