Before: archiveTechFile called dsms.Archive() and discarded the result. The
file was archived to IPFS but no audit-trail entry was written, so there
was no way to later prove "this CE-Akte export went to DSMS with CID X".
After:
- archiveTechFile is now a method on IACEHandler with access to store + gin
context, and captures the CID from dsms.Archive().
- Writes an AuditAction "tech_file_export" audit entry whose new_values
JSON carries {cid, filename, size}, mirroring the Python evidence-upload
pattern.
- Applies to PDF, XLSX, DOCX, and Markdown exports.
Plus dsms package gets 3 unit tests pinning the contract: success-CID
extraction, gateway-unreachable returns nil, 500-response returns nil.
This closes DSMS Stufe 2 (evidence side was already wired; tech-file side
was missing the audit hook). Stufe 3 next: version chains + delta view.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-ups to the 671-norm cross-reference matrix:
1. Tech-file renderer (Go): standards_applied section now gets a deterministic
Markdown appendix with the DIN/ANSI/GB/JIS mappings for the project's
suggested norms. Built from registry, never hallucinated by LLM. Applied
both to LLM and fallback content paths.
2. Frontend NormCrossRefPanel (Next.js): expandable row in the IACE library
norms tab now has a "Internationale Aequivalenzen anzeigen" button that
lazy-loads /iace/norms-library/:id/crossref and renders a colour-coded
table (relation + confidence). Region labels humanised (US — ANSI,
China (GB), Japan (JIS), etc.).
3. Contract tests (Go): 4 new handler tests pinning the response shape of
GetNormCrossRef and ListNormCrossRefs. Equivalent to an OpenAPI snapshot
for these specific endpoints — ai-compliance-sdk has no full OpenAPI
baseline yet (separate ticket).
Tests: 6 renderer tests + 4 handler contract tests, all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Batch 6 (100): EN 1870 saws, EN 81 lift sub-parts, hearing/glove PPE,
EN 50126 railway, EN 60974 welding, EN 60335-2-x cleaning appliances
- Batch 7 (71): IEC 60601 medical family, EN ISO 19085 woodworking, safety
footwear (ASTM F2413), fitness (ASTM F2276), chainsaws (OPEI B175.1),
ISO 4254 agri remainder, acoustics ISO 3743/3745/3747
671 of 671 norms now have at least DIN mapping; ~80% have a US (ANSI/NFPA/
UL/OSHA/ASME/ASTM/SAE/NIOSH) mapping; ~40% have CN-GB and/or JP-JIS.
Added TestCrossRef_SpotChecks with 15 manually vetted region mappings
(IEC 60601 → ANSI/AAMI ES60601, EN 13445 → ASME BPVC, EN 60204 → NFPA 79,
ISO 10218 → RIA R15.06, etc.).
Next steps for follow-up work:
- Add OpenAPI snapshot for new /norms-library/crossref endpoints
- Front-end: render crossref panel on /sdk/iace norm detail page
- Tech file: auto-emit "this requirement also satisfies X in market Y" hints
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a jurisdiction-cross-reference layer to the norms library. Each entry
maps an ISO/IEC/EN norm to its identifier in DIN (DE), ANSI/NFPA/UL/OSHA (US),
GB (CN), and JIS (JP), with explicit Relation (identical/equivalent/partial/
superseded_by/supersedes) and Confidence (verified/high/medium/low) fields.
Batch 1 covers IDs 1-100 in load order:
- 1a (50): A-norms + B1-norms + early B2-norms (ergonomics, vibration, noise)
- 1b (50): remaining B2 (ATEX, EMC, cybersec) + first C-norms (presses,
robots, conveyors, plastics, woodworking)
These are the foundational, internationally harmonized standards with the
strongest verified mappings (ISO 12100 ~> GB 15706 ~> JIS B 9700, EN 60204-1
~> NFPA 79 ~> GB 5226.1 ~> JIS B 9960-1, etc.).
API:
- GET /iace/norms-library?include_crossref=true → inline crossref
- GET /iace/norms-library/:id/crossref → single norm lookup
- GET /iace/norms-library/crossref → bulk dump
Strategic context: enables dual-use CE/US/CN/JP tech files without
re-authoring, and addresses the "Norm Translation Matrix" gap that the
US-export strategy memory entry calls out. 6 batches remaining (~571 norms)
to reach full library coverage.
Tests: 6 new tests; all pass via `go test -vet=off ./internal/iace/`.
(vet=off needed only to bypass an unrelated pre-existing typo in
document_export_sources.go.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- M600-M604: lift endstop mitigations (Kriechgeschwindigkeit, Schaltleiste,
Mindestabstand, Hold-to-run, Trittblech) — cite OSHA + EN ISO identifiers
- HP2100-HP2102: body-part crush patterns for lift family (foot under platform,
hand/body against fixed structure, leg between lift and lateral structure),
restricted via MachineTypes filter
- pattern_machinetype_overrides.go: post-load pass fills MachineTypes on 14
legacy patterns (HP1000 Walzen, HP539 Schweiss, HP545/HP782 Glas,
HP756/HP757/HP760 Fahrtreppe, HP1400-1402 CNC, HP045/HP049 Pressen,
HP420-422 Conveyor) to prevent drift on Kistenhubgeraet-style projects
Why: Kistenhubgeraet re-init exposed two gaps — the abstract "Bremse versagt
bei Absenkbewegung" pattern fired but the concrete foot-crush body-part variant
was missing, AND ~10 unrelated patterns fired purely because their RequiredTags
incidentally aligned. Override map avoids touching 1000+ LOC pattern files
that already exceed the soft cap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three coupled pieces of work, all landing the same PoC:
1. Backend gap-review endpoint (Task #7)
- internal/api/handlers/iace_handler_gap_review.go:
POST /projects/:id/llm-gap-review
feeds Limits-Form + current hazards + current mitigations to
the configured LLM (Qwen / Claude / OpenAI via ProviderRegistry),
parses a JSON suggestion list, filter+stamps confidence, falls
back to a static checklist when LLM is unavailable.
- Adopt step is NOT in this endpoint by design — the user clicks
Adopt in the frontend which calls the existing CreateHazard /
CreateMitigation handlers so provenance flows through the normal
audit trail.
2. Frontend modal + button (Task #8)
- app/sdk/iace/[projectId]/hazards/_components/LLMGapReviewModal.tsx:
reusable modal that POSTs the gap-review endpoint, renders
suggestions with Adopt/Reject UX, shows confidence + norm refs,
source-stamp llm_gap_review vs fallback_static.
- hazards/page.tsx: indigo "KI-Gap-Review" button next to the
existing "Eigene Gefaehrdung" button + modal mount.
3. Tech-File sources appendix (Task #29 — Stufe 4)
- internal/iace/document_export_sources.go: new pdfSourcesAppendix
method appended to ExportPDF. Groups cited norms by license rule
(R1 OSHA/EU-Recht / R3 BreakPilot patterns / R3 DIN-EN-ISO
identifier-only) and emits the legally required statement that
pauschal Impressum-Hinweise nicht ausreichen.
- extractCitedNorms() scans hazard/mitigation text for EN/ISO/IEC/
DIN identifiers in a narrow grammar so prose isn't turned into
spurious citations.
Bonus refactor:
- internal/app/routes.go reached the 500-LOC hard cap when the new
llm-gap-review route was added. Extracted registerIACERoutes into
routes_iace.go (136 LOC). Same wiring, no behaviour change.
Three of the four Attribution-Renderer stages (1, 2, 4) now produce
real output. Stufe 3 ships as <SourceBadge> + <LicenseModuleBanner>
already (commits dfac940 + b9e3eea earlier in this branch).
The PoC is intentionally conservative: every LLM-Suggestion stays
unverbindlich until a human clicks Adopt, and Adopt goes through the
existing normal CreateHazard/CreateMitigation flow (not yet wired in
this commit — separate iteration). The endpoint, modal and provenance
chain are in place for the next iteration to wire Adopt → write path.
Verbatim OSHA 29 CFR 1910 Subpart O values anchored as the rechtssicher
zitierbare Werte-Basis for the IACE engine. Per strategy discussion
(2026-05-20) US Federal Code is the only public-domain corpus we can
reproduce wholesale; DIN/EN values stay identifier-only.
Coverage in this initial batch:
- MD_OSHA_O10_R1, MD_OSHA_O10_R4 (Table O-10 rows 1 + 4 — point of
operation guard distance vs max opening width)
- MD_OSHA_212_FAN (§1910.212(a)(5) fan-blade guards: 1/2 in)
- MD_OSHA_217_PSDI (§1910.217 hand-speed constant 63 in/s for
presence-sensing-device-initiation and two-hand-trip distances)
Each entry carries four parallel value sets:
- OriginalValue/Min/Max in source unit (verbatim, R1)
- ExactMM via deterministic conversion (mathematics, no copyright)
- RecommendedMM with safe-side rounding documented in RoundingNote
- EUNormHints — identifier-only references to EN ISO 13857, EN 13855,
EN 349 with a human-curated DINComparisonNote (qualitative judgement,
not a copy)
Open follow-ups (separate iterations):
- Full Table O-10 (rows 2-10) — same shape
- §1910.219 mechanical power-transmission distances
- Cross-reference IACE patterns to MD_OSHA_* identifiers so the Suppression
Engine surfaces concrete metric values in mitigation suggestions
- Frontend integration: <MinimumDistanceCard> for each measure
Task #17 — Folgegefahren-Modell as Vorbereitungs-Commit (no DB schema
change yet; persistence via separate [migration-approved] commit).
New:
- secondary_harms.go: SecondaryHarm struct + six canonical categories
(consumer_safety, product_liability, food_safety, environmental,
reputation, financial) with DE labels.
- hazard_pattern_types.go: HazardPattern extended with optional
SecondaryHarms field — pattern library can now attach consequential-
damage chains.
- hazard_patterns_secondary_demo.go: two worked examples
- HP2000 Glasbruch carbonated bottling (the "Cola splitter" scenario
from the IACE strategy discussion) with consumer_safety + food_safety
+ reputation chains
- HP2001 Pharma fill-finish cross-contamination with consumer_safety
+ product_liability under AMG §84
Bonus fix:
- compliance_crossover.go AllPatterns() was a duplicate enumeration that
silently drifted from collectAllPatterns() in pattern_registry.go.
Pre-fix: 1058 patterns visible. Post-fix: 1213 patterns. The 155 invisible
patterns included CRA, ISO12100 gaps, robot-cell, CNC extended, VDMA,
textile-agri, GT-bremse — anything added after the original AllPatterns
was authored. Audit-Suite (cmd/iace-audit) now sees the full set.
Next steps for full secondary-harm rollout:
- DB migration: hazards table + secondary_harms array column
- API: surface secondary_harms in /projects/:id/hazards response
- Frontend: collapsible Folgegefahren-Panel in HazardTable
Migration 031 added customer_name to the SELECT statement in three places
(GetProject, ListProjects, ListVariants), and the per-row Scan needed the
matching destination. The replace_all caught ListProjects + ListVariants
but missed GetProject because of an indentation difference (single tab
vs row-scope indentation). Result: GET /projects/:id returned
"get project: number of field descriptions must equal number of
destinations, got 18 and 17"
which the frontend interpreted as "project has no data" and surfaced an
empty UI even though hazards/mitigations/components were intact (118/282/16
on Bremsscheibe).
Single-line fix: add &p.CustomerName to the GetProject scan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[migration-approved]
Task #22. The IACE module is used by a single Maschinenhersteller, but
their plants land at many different end customers. When the safety expert
commissions the second or third plant at the same customer, whole classes
of mitigations (company-wide PPE rules, locked-out energy isolation,
customer-standard signage) are already in place there — but rediscovered
from scratch every project.
Migration 031: iace_projects.customer_name TEXT + partial index.
The customer is stored as a plain text field rather than a normalised
iace_customers table (option A from the design discussion). A proper
customer-management screen can promote this to a FK later without
data loss.
Backend store_customer_standards.go:
- ListCustomerStandardSuggestions(projectID, includeVerified) collects
mitigations from all non-archived prior projects sharing the same
tenant_id AND case-insensitive customer_name. Aggregates by
mitigation.name (since same-named measures from different prior
projects collapse into one suggestion) and surfaces:
• source_project_count + source_project_names
• is_customer_standard / has_verified_instances flags
includeVerified=false → strictly is_customer_standard=true
includeVerified=true → also status='verified'
- ImportCustomerStandardSuggestion(projectID, name): for every prior
(mitigation.name → hazard.name) pairing, finds matching hazards in
the current project (by name) and ensures a customer-standard
mitigation exists. New rows via CreateMitigation (idempotent through
the UNIQUE(hazard_id, name) from migration 030); existing rows are
flipped to is_relevant=true + is_customer_standard=true +
status='verified' via UPDATE.
Routes:
GET /api/v1/iace/projects/:id/customer-standards?include_verified=
POST /api/v1/iace/projects/:id/customer-standards/import body {name}
Frontend:
- New page /sdk/iace/[projectId]/customer-standards with:
• empty-state hint pointing to Auftrag → Kundenname
• per-suggestion checkbox + per-row Übernehmen button
• bulk "N übernehmen" button
• toggle "Auch verifizierte einbeziehen" widening the pool
• per-suggestion source_project_count + status badges
- Sidebar item "Kundenstandards" (building icon) placed between
Verifikation and Nachweise.
- Order-page now mirrors Auftraggeber.Firmenname into the top-level
customer_name column on save, so the Reuse feature is fed
automatically without a separate input field.
The same expert effect from migration 029's is_customer_standard flag —
"I already know it's covered, no evidence needed" — now becomes a
cross-project asset rather than a per-project annotation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[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>
[migration-approved]
Expert-driven workflow refinement on the Massnahmen page. The engine seeds
~80 mitigations per project, but for a concrete customer site most need a
relevance decision before they're meaningful in verification:
status: 'planned' | 'implemented' | 'verified' (existing — verification track)
is_relevant bool (new) (does this apply to *this* site?)
is_customer_standard bool (new) (already in place at customer — no evidence)
Decision flow on the Mitigations tab:
Engine-seeded → is_relevant=false (Default, waiting for expert)
Expert checks "Relevant" → is_relevant=true → surfaces in verification
Expert clicks trash → DELETE (banner warns: do not click Reinit
afterwards or seeds come back)
In verification, customer_standard=true bypasses evidence upload
is_customer_standard implies is_relevant (DB CHECK constraint).
Migration 029_iace_mitigation_relevance.sql:
ALTER TABLE iace_mitigations ADD COLUMN is_relevant ..., is_customer_standard ...
+ CHECK constraint + partial index on is_relevant for the verification
page's filter.
Backend (Go):
- Mitigation struct gains two bool fields
- CreateMitigation: defaults to false/false (engine-seeded mitigations
start unbewertet)
- UpdateMitigation: new case clauses for both keys; setting
is_customer_standard=true auto-flips is_relevant=true to satisfy
the CHECK constraint
- All three SELECT statements (ListMitigations, ListMitigationsByProject,
getMitigation) extended with the two new columns
Frontend:
- Maßnahmen-page columns: [Relev. ☑] [Lösch. 🗑] Title | #Hazards | P·I·V
- Group-header checkbox shows tri-state (indeterminate when partial),
flips all instances in the group at once
- Banner above the table: "Markiere jede Maßnahme als Relevant oder
lösche sie. Nach Löschen kein Neu initialisieren mehr drücken."
- Relevant rows tinted emerald, customer-standard label visible
- Legacy bulk-select state + helpers removed (the Relevant checkbox
now IS the primary mass action)
- useMitigations gains handleSetRelevant, handleSetCustomerStandard,
handleDeleteSilent (for non-confirm bulk deletes)
Future use: is_customer_standard mitigations from a prior project at the
same customer can later be auto-suggested when commissioning the next
plant — turning expert knowledge into reusable customer-profile data.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[guardrail-change]
Phase 18 adds an EU Cyber Resilience Act compliance track to IACE:
the engine now fires patterns that surface the manufacturer-side CRA
obligations whenever a project's components carry digital elements.
Patterns (HP1910-HP1918, hazard_patterns_cra.go):
HP1910 Missing SBOM
HP1911 Unsigned firmware/software updates
HP1912 Factory-default credentials still active
HP1913 No coordinated vulnerability disclosure (CVD) policy
HP1914 No documented security patch SLA
HP1915 Missing user-facing hardening guide
HP1916 No incident-notification process to ENISA / CSIRT
HP1917 No security assessment prior to placing on market
HP1918 AI component without cybersecurity risk assessment
Each pattern carries ClarificationQuestionsDE so the operator gets
auditor-grade questions to take back to the Anlagenbauer instead of
the engine inventing prose. PatternMatch carries DefaultAvoidability
(P=1 for all CRA patterns), feeding the PLr graph from Phase 17.
Measures (M540-M548, measures_library_cra.go):
M540 SBOM (SPDX or CycloneDX) with each machine release
M541 Signed updates with rollback protection
M542 Forced default-password change at first boot
M543 Published CVD policy (security.txt / PSIRT)
M544 Documented patch SLA with CVSS-tier response times
M545 User-facing hardening guide in the machine docs
M546 ENISA incident-notification process (24h/72h/14d)
M547 Authenticated update channel + integrity check
M548 Pre-market security assessment / pen-test
The library is urheberrechtlich neutral: identifiers only
(Verordnung (EU) 2024/2847, DIN EN 40000-1-2 Entwurf, IEC 62443,
ETSI EN 303 645, ISO/IEC 5962, ISO/IEC 29147). No normative text
is reproduced — DIN/Beuth proprietary content is referenced by
section number only.
Category-compatibility:
cyber_resilience pattern category accepts measures with
HazardCategory cyber_resilience, cyber_network, or
software_control. Updated in both the runtime helper
(iace_handler_init_helpers.go) and its test-mirror
(pattern_coverage_test.go) — both must move in lockstep.
Frontend (clarifications page):
When at least one clarification references "2024/2847" or
"40000-1-2" in its norm_references, a blue info-banner is
rendered at the top of the page:
"Cyber Resilience Act (CRA) — Hinweis zur Geltung
Diese Klärungsliste enthält Fragen zur Verordnung (EU)
2024/2847 (CRA). Die CRA gilt für Produkte mit digitalen
Elementen, die ab dem 11.12.2027 auf dem EU-Markt bereit-
gestellt werden. ..."
Reminds the user that the CRA pflichten are forward-looking
while still allowing the manufacturer to bake them in now.
LOC exceptions:
Added three pre-existing files to .claude/rules/loc-exceptions.txt
(manufacturer_safety_features.go, iace_handler_clarifications.go,
routes.go). All three grew across Phases 16-17 and are tagged as
Phase 5+ refactor backlog. [guardrail-change] marker required.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 17 of the risk-assessment polish. Two pieces:
A) PLr per EN ISO 13849-1 Anhang A (Risikograph)
- HazardPattern.DefaultAvoidability (1 = P1, 2 = P2). Optional;
defaults to P1 if unset (conservative — operator can raise after
review).
- ComputePLr(s,f,p) implements the canonical 8-leaf binary tree
(S1F1P1 -> a, ..., S2F2P2 -> e). Pinned by 8 table-driven tests.
- SeverityToS / ExposureToF map the existing 1-5 fields to the
binary S/F at the documented threshold (3).
- At project initialise, every hazard's Description is appended
with "Risikograph EN ISO 13849-1 (Anhang A): S2 · F1 · P1 -> PLr c"
so the audit value is visible without leaving the hazard view.
- PatternMatch carries DefaultAvoidability so the init handler can
pick it up without a second pattern lookup.
B) Methoden-Kopf am Bericht
- GET /clarifications.html now opens with a standardised methodology
block: ISO 12100 Anhang B (hazard ID) + ISO 13849-1 Anhang A
(PLr graph) + ISO 12100 6.2/6.3/6.4 (reduction hierarchy). Same
wording on every export, ready for the Anlagenbauer-Uebergabe.
- Only norm identifiers — no norm text reproduced.
C) ISO12100Section in Hazard Description
- When a pattern is labeled with ISO12100Section, the hazard
description gets a "Klassifikation: EN ISO 12100 Anhang B,
Abschnitt 6.3.5.4" suffix. Provenance for the auditor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 16 of the Klaerungen / risk-assessment polish. Sources from
EN ISO 12100 Anhang B Tabelle B.1 are now first-class:
A) HazardPattern.ISO12100Section identifier (string), persisted only as
the section number (e.g. "6.3.5.5") — not the norm text. Keeps the
library urheberrechtlich neutral (DIN/Beuth license). 57 patterns
labeled today; rest will follow on touch.
B) Category split per ISO 12100 Nr. 4 vs Nr. 5:
- 16 patterns reclassified noise_vibration -> noise_hazard
- 7 patterns reclassified noise_vibration -> vibration_hazard
- 1 pattern (HP228 UV-/Laermexposition) kept multi-cat
acceptableMeasureCategories now accepts both new aliases plus the
legacy noise_vibration. Coverage test recognises both as valid.
C) 5 new ISO-12100-Annex-B gap patterns (HP1900-HP1904):
- HP1900 Vakuum-Verletzung (6.3.5.5)
- HP1901 Federenergie / elastische Elemente (6.2.10)
- HP1902 Rutschen/Stolpern auf rauer Oberflaeche (6.3.5.6)
- HP1903 Hochdruckinjektion (6.3.5.4) — includes clarifying
"no hand-locating of leaks" question
- HP1904 Ersticken durch Brustkorbquetschung (6.3.5.2)
The library now mirrors the ISO 12100 Annex B structure for the gaps
the Bremse benchmark surfaced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[migration-approved]
Three pieces complete the Klaerungen lifecycle:
1. Migration 028: iace_clarifications + iace_clarification_comments +
iace_clarification_history. Deterministic clarification_key
(UNIQUE per project) so engine re-inits don't lose answers.
History table logs every status/answer transition. The previous
JSONB-in-metadata storage is kept as read-only fallback for
pre-migration projects until a one-shot upcopy script runs.
2. Multi-User-Workflow:
- assigned_to field on every clarification (free-text user kuerzel
for now; an FK to users can be added in a follow-up).
- Comment thread per clarification (POST .../comment, GET
.../detail returns the thread).
- Status-history log written by UpsertClarification when the
status or answer actually changes.
- Frontend Modal: Zugewiesen-an + Bearbeiter fields, comment
thread with inline post, collapsible history section.
3. PDF-Export via print-friendly HTML:
- GET /clarifications.html returns a standalone A4-styled
document with status badges, norm references, affected hazards
and a signature row at the bottom. The Bediener opens the link
and uses Strg-P / Cmd-P to save as PDF. No server-side PDF
dependency added.
- Frontend "PDF / Druck" button next to CSV export.
Backend:
- internal/iace/store_clarifications.go: UpsertClarification,
ListClarificationsForProject, GetClarificationByKey,
AddClarificationComment, ListClarificationComments,
ListClarificationHistory.
- internal/api/handlers/iace_handler_clarifications.go:
- AnswerClarification now writes the SQL row, falls back to legacy
JSONB read on list.
- PostClarificationComment, ListClarificationDetail,
ExportClarificationsHTML added.
Migration must be applied manually on Mac Mini and prod via
psql -f /migrations/028_iace_clarifications.sql — pattern as in
scripts/apply_*_migration.sh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three pieces complete the Klaerungen UX:
1. Sidebar-Counter: layout.tsx polls /clarifications and shows a
colored open-count badge on the "Klaerungen" nav item. Refreshes
whenever the user changes route.
2. CSV-Export: new backend endpoint
GET /sdk/v1/iace/projects/:id/clarifications.csv produces a UTF-8-
BOM-prefixed semicolon-separated CSV (Excel-friendly) with ID,
Quelle, Kategorie, Frage, Status, Antwort, Begruendung, Bearbeiter,
answered_at, anzahl Gefaehrdungen, Gefaehrdungs-Namen, Norm-Refs.
Frontend Klaerungen-Seite bekommt einen "CSV-Export"-Button.
3. Hazard-Banner statt Fragentext im Benchmark-Detail: the previous
bulleted clarification list was duplicated across 48 hazards for a
single FANUC question. Phase 2 replaces it with a compact status
badge — "N offene Klaerung(en) — Klaerungen-Seite oeffnen" (orange)
or "Alle N Klaerungen beantwortet" (green) with a direct link.
Backend cleanup: iace_handler_init.go no longer appends the "Mit
Anlagenbauer zu klaeren" block to Hazard.Description. The description
stays focused on the scenario; clarifications live in the dedicated
endpoint and answers persist across re-inits via project.metadata.
The aggregated "Referenzierte Normen" line on the hazard is kept.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New page "Klaerungen" between Massnahmen and Verifikation.
Backend:
- internal/iace/clarifications.go: Clarification struct + ClarificationAnswer +
BuildProjectClarifications() — aggregates pattern-level + manufacturer-
level questions from collectAllPatterns + GetManufacturerSafetyFeatures.
Deterministic IDs ("pattern:HP1640:0", "manuf:fanuc:dual-check-safety-dcs:1")
so persisted answers survive every re-init.
- internal/api/handlers/iace_handler_clarifications.go:
- GET /projects/:id/clarifications returns aggregated list with affected
hazard names + persisted answer state, sorted (open first).
- POST /projects/:id/clarifications/:cid/answer writes status/answer/
reasoning/answered_by/answered_at to project.metadata.clarification_-
answers — no DB schema change.
Frontend:
- admin-compliance/app/sdk/iace/layout.tsx: new "Klaerungen" nav item.
- app/sdk/iace/[projectId]/clarifications/page.tsx: table grouped by
source (FANUC / Pattern HP1640 / …), Filter Offen/Beantwortet/Alle,
search field, Antwort-Modal with status/answer/Begruendung/Bearbeiter.
A clarification answered once applies to ALL referenced hazards — the
operator no longer has to answer the same FANUC DCS question on 48
mechanical hazards individually.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a curated database of safety-relevant features for the major
manufacturers across mechanical/plant engineering, written entirely in
own words with norm anchors. No verbatim manufacturer texts — therefore
no copyright issue:
- Markennennung (§ 23 MarkenG nominative use) is permitted.
- Fakten ueber Produkt-Sicherheitsfunktionen are not protected by § 2
UrhG (only Werke, not facts).
- NormReferences contain only the identifiers (e.g. "EN ISO 13849-1
PLd Kat.3"), never the norm text itself.
Coverage (52 entries across 12 categories):
Industrieroboter (10): FANUC DCS, KUKA SafeOperation, ABB SafeMove,
Yaskawa FSU, Staeubli CS9, Kawasaki Cubic-S, Mitsubishi MELFA,
Universal Robots PolyScope, Doosan PRS, Comau SafeNet
CNC/WZM (8): DMG MORI, Mazak, TRUMPF, Okuma, Hermle, Heidenhain
SPLC, GROB, Heller
Pneumatik (4): Festo, SMC, AVENTICS, Parker
Hydraulik (3): Bosch Rexroth, HAWE, HYDAC
Safety-PLC / Sicherheitstechnik (8): PILZ, SICK, Schmersal, Euchner,
Leuze, Phoenix Contact, Banner, Wieland
Standard-PLC (5): Siemens, Beckhoff, Rockwell, Schneider, B&R
Pressen (3): Schuler, Bruderer, AIDA
Spritzguss (3): Arburg, KraussMaffei, ENGEL
Verpackung (2): Krones, Bosch Packaging/Syntegon
Laser/Schweissen (3): Bystronic, Amada, Fronius
Foerdertechnik (2): Interroll, SEW EURODRIVE
Engine integration:
- LookupManufacturerFeaturesInText() scans the project narrative for
any of the manufacturer aliases (case-insensitive, umlaut-tolerant).
- Init-Handler appends matched feature clarifications to the relevant
hazard's "Mit Anlagenbauer zu klaeren:" block — for the right
HazardCategory only (e.g. FANUC DCS only on mechanical_hazard).
- For a Bremse project narrative mentioning "Fanuc Robodrill", the
engine now adds clarification questions like "Ist DCS am Roboter
konfiguriert?" to relevant mechanical hazards automatically.
Tests: 7 new pin tests — manufacturer count, norm prefixes, FANUC/KUKA
detection in narrative, umlaut robustness (Staeubli vs Staubli).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GT 1.8 fordert konkret den 'sicher begrenzten Bewegungsbereich (Dual
Check Safety)'. HP1654 hatte nur M061 'Feste trennende Schutzeinrich-
tung' als Mitigation. Ergaenzt um M494 (Safe Limited Position/Space mit
DCS-Erlaeuterung), M501 (Schutzzaun-Lastbemessung) und M502 (Greifer-
Fail-Safe). Klaerungsfragen verweisen explizit auf DCS bei FANUC,
SafeMove bei ABB, SafeOperation bei KUKA und die EN ISO 13849-1 PLd/
Kat.3-Validierung.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Library measures carry NormReferences (EN/IEC/ISO/DIN/TRBS/TRGS Ziff./Kap./
Pos.) but they were dropped on persist: CreateMitigationRequest only
wrote Name + Description. The Fachmann benchmark file lists Normen for
34 of 60 hazards — the engine had this data already but lost it on the
way to the UI.
Fix without DB schema change:
- Mitigation.Description gets a "Normen: EN 60204-1 Ziff. 6.2 | EN 61140"
line appended when the measure has NormReferences. Pipe separator keeps
the inline panel short and grep-friendly.
- After all mitigations land, the aggregated dedup'd norm list for the
hazard is appended to Hazard.Description as a single "Referenzierte
Normen: ..." line so the UI can show one panel per hazard without
scanning every mitigation.
Audit of library coverage (per-pattern) showed GT-Bremse Normen are
generally present and richer:
- HP1640 covers GT 2.2 (EN 60204-1 Ziff. 6.2, Ziff. 8.2.3, EN 61140 +)
- HP1641 covers GT 2.4 (EN 60204-1 Ziff. 8.2.6 +)
- HP1605 covers GT 1.7 (ISO 10218-1 Ziff. 5.6.2, 5.8.3 — Ziff. 5.7.3 fehlt)
- HP1671 covers GT 1.30 (EN 12417 — Pos. detail fehlt)
Followup: 2 fine-grained sub-paragraph references (5.7.3, Pos. 1.1.4)
can be added later as measure-text updates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ZoneDE 'Pneumatikkomponenten der Anlage' kollidiert nach normalizeZoneKey
mit HP1630 'Pneumatikschlaeuche der Automation' im 3-signifikante-Wort-
Vergleich. Neue Zone 'Berstgefaehrdete Druckwandungen Pneumatik (Leitungs-
wand, Dichtung, Verschraubung)' hat semantisch eigenstaendige Schluessel-
woerter — Dedup mergt nicht mehr in HP1630.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei nachhaltige Verbesserungen, getrieben durch die Bremse-Benchmark-
Faelle GT 1.4, GT 1.30 und GT 7.4. Die Engine erfindet weiterhin
keine Fachmann-Kommentare — Kommentare bleiben aus, weil sie ein
Verstaendnis der konkreten Anlage erfordern, das die Engine nicht
hat. Statt dessen liefert die Engine norm-basierte Klaerungsfragen
und ein praeziseres Pattern-Vokabular.
A) HazardPattern.ClarificationQuestionsDE — neues optionales Feld:
- Pattern hinterlegt prueffaehige Fragen, die der Bediener mit dem
Anlagenbauer abklaert. Beispiele:
- HP1640: "Liegt ein Pruefprotokoll nach EN 60204-1 vor?"
- HP1666: "Ist die WZM als CE-konformes Subsystem integriert?"
- HP1604: "Ist DCS am Roboter konfiguriert und validiert?"
- Init-Handler haengt die Fragen an Hazard.Description an mit dem
Marker "Mit Anlagenbauer zu klaeren:". Kein DB-Schema-Aenderungs-
bedarf.
- 11 Patterns mit Klaerungsfragen versehen (HP1602, HP1604, HP1611,
HP1612, HP1620, HP1622, HP1637, HP1640, HP1641, HP1666, HP1685).
B) HP1632 "Bersten druckbeaufschlagter Pneumatik-Komponente" — neues
Pattern, semantisch DISTINKT zu HP1630 "Abspringen":
- Bersten = Material-/Druckversagen der Komponente, Mediumaustritt
- Abspringen = Verbindung loest sich, Peitscheneffekt
Bremse-Benchmark GT 1.4 sprach von Bersten, HP1630 nur von
Abspringen — ein 66%-Frontend-Match war eine Sackgasse. Mit
HP1632 feuert die Engine ein eigenes Hazard, das auf GT 1.4
einen sauberen Volltreffer liefert.
C) HP1637 "Einatmen von KSS-Aerosolen" — Massnahmen vervollstaendigt:
Vorher nur M141 (Sicherheitszeichen), neu zusaetzlich M405 (KSS-
Aerosolabsaugung), M418 (AGW-Ueberwachung), M526 (WZM-Tueren
geschlossen waehrend Bearbeitung), M408 (Hautschutzplan).
Klaerungsfrage: "Wurde die Aerosolkonzentration nach Bearbeitungs-
ende messtechnisch ermittelt und mit dem AGW verglichen?"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HP1671 "Druckluft-Verletzung in Bearbeitungszelle" matched zwar das
GT-1.30 Szenario "Einstich, Augenverletzung in Bearbeitungszelle" exakt
nach Name und Scenario, hatte aber nur eine einzige Massnahme M061
"Feste trennende Schutzeinrichtung". Die drei spezifischen Massnahmen
des Fachmanns (Reinigungsduese in Zelle integriert / Druckluft bei
Tueroeffnung aus / Einhausung-Lastbemessung) blieben unsichtbar, weil
mein neuer GT-Bremse-Pattern HP1712 zwar diese Massnahmen kennt, aber
durch RequiredEnergyTags=["pneumatic"] in diesem Projekt nicht feuert.
Fix: HP1671 SuggestedMeasureIDs ["M061"] -> ["M504", "M505", "M501",
"M061", "M141"]. EN 12417 Kap. 5.2 / Pos. 1.1.4 ist jetzt durch
M504/M505 abgedeckt. HP1712 bleibt als Backup-Pattern fuer Projekte
mit explizitem pneumatic-Tag bestehen.
Followup: HP1671 und HP1712 sind semantisch redundant — Konsolidierung
ist Teil der naechsten Pattern-Hygiene-Iteration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Background: hazard_patterns_extended.go (HP045-074) and _extended2.go
(HP074-102) shared their entire ID range with the semantically-different
patterns in hazard_patterns_cobot.go, hazard_patterns_press.go,
hazard_patterns_operational.go and hazard_patterns_extended_dguv.go.
The collision had lived unnoticed because TestGetBuiltinHazardPatterns_-
UniqueIDs only checks the 44 builtin patterns (HP001-HP044).
Examples of the collision:
- HP059 = "Kollision Mensch-Roboter" (cobot.go) vs "Kupplung — mechanisch" (extended.go)
- HP060 = "Quetschen durch Werkzeug am Cobot" (cobot.go) vs "Diagnosemodul — Software" (extended.go)
- HP073 = "Wartung ohne LOTO" (operational.go) vs "Hydraulikventil — hydraulisch" (extended.go)
At runtime collectAllPatterns() returned both patterns under the same ID
which made downstream lookups (e.g. hazardPatternMeasures map keyed by
pattern_id) non-deterministic — last-loaded wins, dropping the other
pattern's mitigation set silently.
Rename strategy (no deletes — both patterns are real and earn their
SuggestedMeasureIDs after the category-filter work):
extended.go HP045..HP073 -> HP1800..HP1828 (29 IDs)
extended2.go HP074..HP102 -> HP1830..HP1858 (29 IDs)
cobot/press/operational/extended_dguv keep their original IDs because:
- compliance_triggers.go references HP059/HP060 with the cobot meaning
- pattern_engine_test.go references HP073 with the LOTO/maintenance meaning
- phase3_4_test.go references HP073 the same way
New regression test:
- TestAllPatterns_UniqueIDs runs over collectAllPatterns() and fails if
ANY pattern in the runtime set duplicates an ID. The old
TestGetBuiltinHazardPatterns_UniqueIDs stays for the builtin subset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two-part nachhaltiger fix replacing the previous "fill to 5 mitigations
no matter what" behavior that the GT-Bremse benchmark proved
unfaithful (e.g. HP1625 "scharfe Kanten" returning M005 "Rotations-
bewegung vermeiden" via category fallback; HP1651 "Wiederanlauf
Roboter" returning M054 "Sichere thermische Auslegung" via
mismatched pattern reference).
PART A — Set-based category filter (handlers package):
- acceptableMeasureCategories: replaces 1:1 patternCatToMeasureCat
with a curated set per pattern category, so e.g.
safety_function_failure now accepts software_control measures
(watchdogs, plausibility checks) and emc_hazard accepts both
electrical and software_control measures
- isCategoryCompatible: gate every measure id against the accepted
set before creating a mitigation; mismatches log MEASURE-SKIP
- The old category fallback is REMOVED. A hazard whose pattern has
no category-compatible measure is now created with zero mitigations
and logged as COVERAGE-GAP — the operator must consult an expert.
No more silent invention of generic defaults.
PART B — 235 pattern author-error fixes across 26 files:
- HP040-HP044 (AI): M101/M102/M103 (Auffangwanne/Absauganlage) ->
M133 Anomalieerkennung + M214 Plausibilitaet + M213 Sensor-Redundanz
+ M044 Zweikanalige Steuerung + others
- HP011-HP015, HP104-HP109, HP1085-HP1095, HP1281-HP1334 (electrical):
M001-M005/M054/M061 placeholders -> M481/M482 Isolation +
M511-M522 PE/Schutzleiter/RCD/Hauptschalter
- HP110-HP1331 (material_environmental): M101-M103 -> M384-M395
Brandschutz/Laserschutz + M533/M408 SDB/PSA
- HP800-HP858, HP1178-HP1264 (software/sensor/hmi):
M101/M104 -> M105/M106/M107/M214 SPS/Watchdog/Plausibilitaet
- HP026, HP611-HP1690 (ergonomic): M001/M082 -> M353-M360 +
M530-M532 Hebehilfe/ergonomische Hoehe
- HP201-HP1697 (mechanical): M054/M051 -> M002/M008/M061/M141 +
M487/M488 Tueroeffnung-Stillsetzung/Wiederanlauf
- Plus EMF/Strahlung/Brand/Lärm/Vibration/Kommunikation/Cyber
Coverage shift (Pattern-Author-Fehler bei aktiviertem Set-Filter):
start: 237 patterns with zero category-compatible measures
after Stufe 1A: 5 (AI)
after Stufe 1B: 20 (mechanical Bestand)
after Stufe 1C: 35 (electrical Bestand)
after Stufe 1D: 29 (material_environmental)
after Stufe 1E: 29 (software/sensor/hmi)
after Stufe 1F: 20 (ergonomic)
after Stufe 1G: 80 (thermal/comm/radiation/fire/safety)
final: 0 (28 extended.go/extended2.go duplicates fixed)
New regression tests:
- TestEveryPattern_HasCategoryCompatibleMeasure: every pattern in
collectAllPatterns() must reference at least one category-compatible
measure; gaps must be explicitly listed in AllowlistKnownGaps
(currently empty). Fails CI for any new pattern that drifts.
- TestAcceptableMeasureCategories: pins the set-mapping for the
7 most-bug-prone pattern categories.
- TestIsCategoryCompatible_EmptyMeasureCat: protects legacy entries.
A separate task #11 tracks 58 HP-ID duplicates between
extended.go/extended2.go and cobot.go/press.go/operational.go —
patterns are semantically different and TestGetBuiltinHazardPatterns_-
UniqueIDs misses them because it only checks HP001-HP044.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 supplementary measures (M410-M420) were silently overwritten by
metalworking duplicates in measureByID lookups, so robot-cell electrical
patterns resolved to chip-extraction/cleaning fallbacks instead of
equipotential bonding, creepage, EMC, or hose-burst protection. Rename
supplementary IDs to M475-M480 and rewire 13 affected pattern references
in robot_cell + robot_cell_ext.
HP1640 (direct contact with live parts, GT 2.2): priority 98->99, drop
RequiredEnergyTags gate so it fires in robot cells without an electrical
tag, expand mitigations to 5 concrete TRBS 2131 / IEC 60204-1 / EN 61140
measures (basic protection, double insulation, earthing, insulation
monitoring, equipotential bonding) — was previously losing to HP1688
even though HP1688 describes a different scenario.
HP1688 (touch voltage from potential differences): priority 98->96 so it
no longer outranks HP1640 for the direct-contact case; mitigations
expanded from M410-only to 4 concrete electrical measures.
Add regression tests pinning HP1640 contact-protection resolution and
M475 = Potentialausgleich. Existing TestGetProtectiveMeasureLibrary_-
UniqueIDs now actually enforces uniqueness (previously masked by
last-wins map override).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KSS, EMV, ESD, DCS, PLR, SIL, HMI, SPS, RCD, LOTO, PSA are
abbreviations that should NOT trigger the relevance filter.
bersten, platzen, abspringen, spritzen, einatmen, ausrutschen,
herabfallen, durchschlaegen, wegschleudern are action words that
appear in many patterns and don't indicate a specific machine.
Fixes: HP1633-HP1675 (KSS patterns) were filtered out because
"kss" was not in the narrative but also not in genericSafetyTerms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Robot cell patterns now fire BEFORE generic patterns (Priority 96-99
vs generic 85-95). This ensures pattern-specific SuggestedMeasureIDs
(M420 for KSS, M410 for Potentialausgleich) reach the hazard.
Removed debug fmt.Println statements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When multiple patterns match the same category+zone, the first creates
the hazard and later patterns add their SuggestedMeasureIDs to the
existing hazard. This ensures KSS-specific measures (M420) reach the
hazard even if a generic pattern created it first.
seenCatZone changed from map[string]bool to map[string]uuid.UUID
to track which hazard ID was created for each dedupKey.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each hazard now gets measures from its SOURCE PATTERN first
(SuggestedMeasureIDs), then category fallback for remaining slots.
Previously all mechanical hazards got the same generic top-5 measures
(Gefahrstelle eliminieren, Sicherheitsabstaende, Scharfe Kanten...).
Now a KSS-Schlauch hazard gets M420 (Druckfeste Auslegung) first.
SuggestedMeasureIDs added to PatternMatch struct and passed through
from pattern definition to hazard creation to measure assignment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every ScenarioDE now describes how a PERSON is affected, not just
what happens to the machine. Every HarmDE describes the INJURY,
not just the technical effect.
Examples:
- "Peitscheneffekt des Schlauchs" → "Person wird von abspringendem
Schlauch getroffen. KSS-Spritzer verletzen Haut und Augen."
- "Kurzschluss, Brand" → "Person wird durch Brand oder toxische
Rauchgase verletzt. Verbrennungen, Rauchvergiftung."
Rule: Risikobeurteilung bewertet Gefahr fuer PERSONEN.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HP1606: Quetschen/Scheren durch Greifer im Einrichtbetrieb (GT 1.14)
HP1634: KSS-Pumpe spritzt bei geoeffneter Schutztuer (GT 1.38)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HP1605: Stoss durch Werkzeug/Greifer im Einrichtbetrieb (GT 1.14)
HP1633: KSS-Versorgungsschlauch platzt oder reisst ab (GT 1.35)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Energy tag "electrical" doesn't match resolved tags (which are
"high_voltage", "electrical_part", etc.). Patterns HP1685-HP1699
now fire without energy tag requirement — they fire for any
project that has the right component tags.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When GT has two entries for the same zone with different scenarios
(e.g. "eingeklemmt" vs "getroffen"), we need separate engine patterns.
HP1700: Getroffen von bewegtem Werkzeug/Greifer (vs HP1652 eingeklemmt)
HP1701: Greifer/Werkzeug durchschlaegt Zaun (vs HP1654 Werkstueck)
HP1702: KSS-Schlauch platzt (vs HP1675 springt ab)
HP1703: KSS-Bettspuelung bei offener Tuer (vs HP1670 allgemein)
HP1704: Brand durch KSS auf elektrische Komponenten
Extended synonym sets for potential/EMV matching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
scenarioSimilarity now uses synonym-set cross-matching: if GT says
"durchschlaegt" and Engine says "schleuder", the synonym set recognizes
them as related. Added significantWordOverlap fallback when no action
words found. Extended action terms: schlauch/druck/kuehlschmierstoff,
pumpe/bettspuel, potential/bezugspotential, stoerung/emv.
Moved extractActionWords to benchmark_synonyms.go (458+119 lines).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4-signal matcher: category (0.2), keywords (0.2), zone (0.3),
scenario similarity (0.3). Scenario signal extracts action words
(eingeklemmt vs herabfallend vs durchschlaegt) to differentiate
similar-looking hazards at the same component.
Split benchmark_synonyms.go (70 lines) from benchmark_matcher.go
(516→450 lines) to stay under 500-line cap.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sort matches by specificity first (zone overlap), then by score.
Prevents generic matches from consuming specific Engine patterns
that should match more specific GT entries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Threshold 0.25→0.20 to recover matches lost by keyword penalty.
New synonym sets: eingeschlossen/wiederanlauf, zentriergreifer,
beladetuer/schutztuer, ergonom/bedienelemente, spritzer/auge, bersten.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>