setExpandedMeasure(isDetailOpen ? null : m.id)}
- className={`grid grid-cols-[40px_24px_2fr_140px] gap-2 px-4 py-1.5 border-t border-gray-100 dark:border-gray-700 hover:bg-white dark:hover:bg-gray-800 transition-colors cursor-pointer ${selected.has(m.id) ? 'bg-purple-50 dark:bg-purple-900/10' : ''}`}>
-
+ className={`grid grid-cols-[36px_36px_2fr_120px_110px] gap-2 px-4 py-1.5 border-t border-gray-100 dark:border-gray-700 hover:bg-white dark:hover:bg-gray-800 transition-colors cursor-pointer ${m.is_relevant ? 'bg-emerald-50/40 dark:bg-emerald-900/10' : ''}`}>
e.stopPropagation()}>
- toggleSelect(m.id)}
- className="accent-purple-600" />
+ handleSetRelevant(m.id, !m.is_relevant)}
+ className="accent-purple-600" title="Als relevant markieren" />
+
+
e.stopPropagation()}>
+
handleDelete(m.id)}
+ className="text-gray-400 hover:text-red-600" title="Loeschen">
+
+
+
+
{(m.linked_hazard_names || []).join(', ') || '— (keine Gefaehrdung verknuepft)'}
+
+ {m.is_customer_standard ? 'Kundenstandard' : ''}
+
{isDetailOpen && (
diff --git a/ai-compliance-sdk/internal/iace/models_entities.go b/ai-compliance-sdk/internal/iace/models_entities.go
index 3bdc9127..60fa68ae 100644
--- a/ai-compliance-sdk/internal/iace/models_entities.go
+++ b/ai-compliance-sdk/internal/iace/models_entities.go
@@ -160,8 +160,17 @@ type Mitigation struct {
VerificationResult string `json:"verification_result,omitempty"`
VerifiedAt *time.Time `json:"verified_at,omitempty"`
VerifiedBy uuid.UUID `json:"verified_by,omitempty"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
+ // IsRelevant marks the mitigation as applicable for this concrete project.
+ // Engine-suggested mitigations start with IsRelevant = false; the expert
+ // flips it to true (or deletes the mitigation) when walking through the
+ // Massnahmen tab. Only relevant mitigations surface in verification.
+ IsRelevant bool `json:"is_relevant"`
+ // IsCustomerStandard means the customer site already has this mitigation
+ // implemented as company-wide standard, so no evidence upload is needed.
+ // Implies IsRelevant = true (DB CHECK constraint).
+ IsCustomerStandard bool `json:"is_customer_standard"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
}
// Evidence represents an uploaded file that serves as evidence for compliance
diff --git a/ai-compliance-sdk/internal/iace/store_mitigations.go b/ai-compliance-sdk/internal/iace/store_mitigations.go
index 0c2f9415..0e208b80 100644
--- a/ai-compliance-sdk/internal/iace/store_mitigations.go
+++ b/ai-compliance-sdk/internal/iace/store_mitigations.go
@@ -31,17 +31,20 @@ func (s *Store) CreateMitigation(ctx context.Context, req CreateMitigationReques
id, hazard_id, reduction_type, name, description,
status, verification_method, verification_result,
verified_at, verified_by,
+ is_relevant, is_customer_standard,
created_at, updated_at
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8,
$9, $10,
- $11, $12
+ $11, $12,
+ $13, $14
)
`,
m.ID, m.HazardID, string(m.ReductionType), m.Name, m.Description,
string(m.Status), "", "",
nil, uuid.Nil,
+ m.IsRelevant, m.IsCustomerStandard,
m.CreatedAt, m.UpdatedAt,
)
if err != nil {
@@ -79,6 +82,23 @@ func (s *Store) UpdateMitigation(ctx context.Context, id uuid.UUID, updates map[
query += fmt.Sprintf(", verification_method = $%d", argIdx)
args = append(args, val)
argIdx++
+ case "is_relevant":
+ query += fmt.Sprintf(", is_relevant = $%d", argIdx)
+ args = append(args, val)
+ argIdx++
+ case "is_customer_standard":
+ // CHECK constraint requires is_relevant=true when this is true,
+ // so we flip is_relevant on as well when the caller sets the
+ // customer-standard flag.
+ b, _ := val.(bool)
+ query += fmt.Sprintf(", is_customer_standard = $%d", argIdx)
+ args = append(args, b)
+ argIdx++
+ if b {
+ query += fmt.Sprintf(", is_relevant = $%d", argIdx)
+ args = append(args, true)
+ argIdx++
+ }
}
}
@@ -123,6 +143,7 @@ func (s *Store) ListMitigations(ctx context.Context, hazardID uuid.UUID) ([]Miti
id, hazard_id, reduction_type, name, description,
status, verification_method, verification_result,
verified_at, verified_by,
+ is_relevant, is_customer_standard,
created_at, updated_at
FROM iace_mitigations WHERE hazard_id = $1
ORDER BY created_at ASC
@@ -141,6 +162,7 @@ func (s *Store) ListMitigations(ctx context.Context, hazardID uuid.UUID) ([]Miti
&m.ID, &m.HazardID, &reductionType, &m.Name, &m.Description,
&status, &verificationMethod, &m.VerificationResult,
&m.VerifiedAt, &m.VerifiedBy,
+ &m.IsRelevant, &m.IsCustomerStandard,
&m.CreatedAt, &m.UpdatedAt,
)
if err != nil {
@@ -164,6 +186,7 @@ func (s *Store) ListMitigationsByProject(ctx context.Context, projectID uuid.UUI
m.id, m.hazard_id, m.reduction_type, m.name, m.description,
m.status, m.verification_method, m.verification_result,
m.verified_at, m.verified_by,
+ m.is_relevant, m.is_customer_standard,
m.created_at, m.updated_at
FROM iace_mitigations m
JOIN iace_hazards h ON h.id = m.hazard_id
@@ -184,6 +207,7 @@ func (s *Store) ListMitigationsByProject(ctx context.Context, projectID uuid.UUI
&m.ID, &m.HazardID, &reductionType, &m.Name, &m.Description,
&status, &verificationMethod, &m.VerificationResult,
&m.VerifiedAt, &m.VerifiedBy,
+ &m.IsRelevant, &m.IsCustomerStandard,
&m.CreatedAt, &m.UpdatedAt,
)
if err != nil {
@@ -224,12 +248,14 @@ func (s *Store) getMitigation(ctx context.Context, id uuid.UUID) (*Mitigation, e
id, hazard_id, reduction_type, name, description,
status, verification_method, verification_result,
verified_at, verified_by,
+ is_relevant, is_customer_standard,
created_at, updated_at
FROM iace_mitigations WHERE id = $1
`, id).Scan(
&m.ID, &m.HazardID, &reductionType, &m.Name, &m.Description,
&status, &verificationMethod, &m.VerificationResult,
&m.VerifiedAt, &m.VerifiedBy,
+ &m.IsRelevant, &m.IsCustomerStandard,
&m.CreatedAt, &m.UpdatedAt,
)
if err == pgx.ErrNoRows {
diff --git a/ai-compliance-sdk/migrations/029_iace_mitigation_relevance.sql b/ai-compliance-sdk/migrations/029_iace_mitigation_relevance.sql
new file mode 100644
index 00000000..1f3f0b78
--- /dev/null
+++ b/ai-compliance-sdk/migrations/029_iace_mitigation_relevance.sql
@@ -0,0 +1,39 @@
+-- Migration 029: IACE Mitigation Relevance + Customer-Standard flag
+-- ==========================================================================
+-- The engine generates ~80 mitigations per project (Bremsscheibe benchmark).
+-- Many are not applicable for a specific customer site — e.g. the customer
+-- has 30 of them already implemented as company-wide standard. To keep the
+-- verification step meaningful, the expert needs to:
+--
+-- 1. Mark each mitigation as relevant (or delete it from the project),
+-- 2. Optionally flag it as "customer standard — no evidence required".
+--
+-- This is the difference between "applicable, must be verified" and
+-- "applicable, but the expert already knows it's covered by the customer's
+-- existing setup". Both must reach the verification report; only the first
+-- needs an evidence upload.
+--
+-- A later feature reuses is_customer_standard to suggest pre-marked
+-- mitigations when the same customer commissions another plant assessment.
+-- ==========================================================================
+
+-- is_relevant: Fachmann hat die Massnahme als anwendbar bestaetigt.
+-- FALSE → Engine-Vorschlag, vom Fachmann noch nicht bewertet.
+-- TRUE → Fachmann hat 'Relevant' angekreuzt; geht in die Verifikation.
+-- is_customer_standard: Beim Kunden bereits implementiert.
+-- FALSE → benötigt Nachweis in der Verifikation.
+-- TRUE → keine Evidence-Datei notwendig; gilt als verifiziert.
+ALTER TABLE iace_mitigations
+ ADD COLUMN IF NOT EXISTS is_relevant BOOLEAN NOT NULL DEFAULT FALSE,
+ ADD COLUMN IF NOT EXISTS is_customer_standard BOOLEAN NOT NULL DEFAULT FALSE;
+
+-- An is_customer_standard mitigation is by definition relevant.
+ALTER TABLE iace_mitigations
+ DROP CONSTRAINT IF EXISTS iace_mitigations_customer_standard_chk,
+ ADD CONSTRAINT iace_mitigations_customer_standard_chk
+ CHECK (is_customer_standard = FALSE OR is_relevant = TRUE);
+
+-- Index for the verification-page filter (`WHERE is_relevant = TRUE`).
+CREATE INDEX IF NOT EXISTS idx_iace_mitigations_relevant
+ ON iace_mitigations(is_relevant)
+ WHERE is_relevant = TRUE;