4d225f73a8
Completes the proposer's four types.
- FindCoverageGaps (proposer_coverage.go): deterministic — which EN ISO 12100
hazard groups A-G did the engine leave with zero hazards for this machine? An
empty group is a structural blind-spot signal (the machine may truly lack it,
or a pattern/GT case is missing). Useful with no model at all.
- ProposeMissingHazards + BuildCoveragePrompt: optional LLM expansion of each gap
into specific expected-but-missing hazards a safety assessor would name
(propose-only, reuses LLMCompleter, degrades to nil on any error).
- Wired into iace-audit propose -> audit-reports/coverage.{md,json}.
On the dishwasher: D. Pneumatik (truly absent — nothing invented), E. Laerm
(borderline), F. Ergonomie (a genuine gap: manual loading the engine did not
produce). P3 (pin an accepted proposal into a GT case) remains as a human-in-the-
loop follow-up.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
60 lines
2.0 KiB
Go
60 lines
2.0 KiB
Go
package iace
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestFindCoverageGaps(t *testing.T) {
|
|
hazards := []Hazard{
|
|
{Category: "mechanical_hazard"},
|
|
{Category: "thermal_hazard"},
|
|
{Category: "electrical_hazard"},
|
|
{Category: "material_environmental"},
|
|
}
|
|
gapKeys := map[string]bool{}
|
|
for _, g := range FindCoverageGaps(hazards) {
|
|
gapKeys[g.Key] = true
|
|
}
|
|
for _, want := range []string{"pneumatic_hydraulic", "noise_vibration", "ergonomic"} {
|
|
if !gapKeys[want] {
|
|
t.Errorf("expected gap %s", want)
|
|
}
|
|
}
|
|
for _, notWant := range []string{"mechanical", "thermal", "electrical", "material"} {
|
|
if gapKeys[notWant] {
|
|
t.Errorf("did not expect gap %s (covered)", notWant)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildCoveragePrompt_ContainsContext(t *testing.T) {
|
|
produced := []Hazard{{Category: "thermal_hazard"}}
|
|
gaps := []CoverageGap{{Group: "F. Ergonomie", Key: "ergonomic"}}
|
|
system, user := BuildCoveragePrompt("Geschirrspuelmaschine", "Eine Spuelmaschine mit Tank.", produced, gaps)
|
|
if !strings.Contains(system, "EN ISO 12100") || !strings.Contains(system, "JSON") {
|
|
t.Errorf("system prompt missing framing")
|
|
}
|
|
for _, want := range []string{"Geschirrspuelmaschine", "thermal_hazard", "F. Ergonomie", "Spuelmaschine mit Tank"} {
|
|
if !strings.Contains(user, want) {
|
|
t.Errorf("user prompt missing %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProposeMissingHazards_ParsesAndDegrades(t *testing.T) {
|
|
gaps := []CoverageGap{{Group: "F. Ergonomie", Key: "ergonomic"}}
|
|
c := fakeCompleter{out: `Hier: [{"group":"F. Ergonomie","hazard":"Heben schwerer Koerbe","why":"manuelles Beladen"}] fertig`}
|
|
got := ProposeMissingHazards(context.Background(), c, "x", "n", nil, gaps)
|
|
if len(got) != 1 || got[0].Hazard != "Heben schwerer Koerbe" {
|
|
t.Fatalf("parse: got %+v", got)
|
|
}
|
|
if ProposeMissingHazards(context.Background(), nil, "x", "n", nil, gaps) != nil {
|
|
t.Errorf("nil completer must return nil")
|
|
}
|
|
if ProposeMissingHazards(context.Background(), fakeCompleter{err: context.DeadlineExceeded}, "x", "n", nil, gaps) != nil {
|
|
t.Errorf("error must return nil")
|
|
}
|
|
}
|