[migration-approved]
The init-handler was non-idempotent. A second click on "Neu initialisieren
in Grenzen" inserted every engine-suggested mitigation a second time —
e.g. the Bremsscheibe project ended up with 5 (hazard_id, name) duplicate
pairs (HMI-Usability-Pruefung, Eindeutiges visuelles Feedback,
Betriebsarten-Anzeige, Sicher begrenzter Bewegungsbereich, …). 45 such
duplicates accumulated across all projects.
Migration 030_iace_mitigation_unique.sql:
1. Picks one winning row per (hazard_id, name) using a stable rank:
is_relevant DESC (expert decision wins over engine default)
status DESC (verified > implemented > planned)
created_at DESC (newest beats older on otherwise-equal rows)
and deletes the losers (Bremsscheibe: 5 rows; total: 45).
2. Adds UNIQUE constraint iace_mitigations_hazard_name_uniq
(hazard_id, name).
Store-Layer (CreateMitigation):
INSERT … ON CONFLICT (hazard_id, name) DO NOTHING RETURNING id.
pgx.ErrNoRows from RETURNING → look up the existing row and return that.
Callers (engine init + manual add) always get a usable Mitigation; the
second click is silently swallowed instead of failing.
Frontend dedupe in groupByTitle stays — it covers any pre-existing
duplicates that survived the migration in edge cases (multi-row write
in flight, etc.). With the UNIQUE constraint live, the in-memory
dedupe is a belt-and-suspenders safety net rather than the load-bearing
mechanism.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>