feat(ai-sdk): coverage blind-spot proposer (P2 slice 6, type 4)
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>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user