feat(iace): cross-domain precision overhaul + component review + schema reconcile

Engine precision (stop foreign-machine patterns leaking into a project):
- Wire project.MachineType into the engine machine-type gate (empty input no
  longer fires every machine class — press/cnc/excavator/crane/medical...).
- Capability-domain gating extended by 7 domains (outdoor, ventilation,
  machining, bulk, palletizer, playground, fitness) so domain-specific hazards
  only fire when the narrative names that domain; emitted via keyword_dictionary.
- Relevance backstop moved into iace (single gating contract, testable), and its
  dominant false-anchor class removed (a long pattern word no longer matches a
  short common token; prepositions/leitung added to the generic stoplist).
- New guard tests: TestCrossDomainPrecision (full pipeline, 0 foreign per GT) and
  TestPatternReachability now asserts 0 dead patterns. Both GTs keep coverage 1.0.

Reachability fix: the 51 dead patterns required electrical/pneumatic/hydraulic
tags nothing produced — renamed to the canonical electrical_energy/
pneumatic_pressure/hydraulic_pressure/hydraulic_part.

Component review (negation is best-effort + expert-correctable):
- Parser surfaces negated components (ComponentMatch.Negated) instead of dropping
  them; negated contribute no tags/energy → no phantom hazards.
- presence_status (vorhanden|nicht_vorhanden|geloescht) + ce_marked on components;
  only `vorhanden` feed matching. CE+safety-relevant flags the PL/SIL obligation.
- Force re-seed preserves the expert's component decisions instead of wiping them.
- Tag-based component→hazard assignment (was: all on the first component).
- Negation-aware narrative parsing ("keine Pneumatik" no longer extracts it).

Local-dev DB: ai-sdk sets search_path=compliance,core,public; reconcile migrations
152-156 bring the consolidated local iace tables to the current schema + add the
presence_status/ce_marked columns. Machine-type vocabulary endpoint for the form.

[migration-approved]

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-10 17:15:55 +02:00
parent 3bd4e0aaaf
commit afb3f83f30
47 changed files with 1275 additions and 169 deletions
+20 -1
View File
@@ -39,7 +39,26 @@ func Run() {
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, cfg.DatabaseURL)
poolCfg, err := pgxpool.ParseConfig(cfg.DatabaseURL)
if err != nil {
log.Fatalf("Failed to parse database URL: %v", err)
}
// The iace/compliance tables live in the `compliance` schema (see CLAUDE.md:
// "DB search_path: compliance,core,public"). Set it explicitly so the
// connection does not silently resolve to `public` (an empty/legacy schema)
// when the URL carries no search_path — as happened on the local dev DB.
// Only set when not already specified in the URL, so prod stays untouched.
if poolCfg.ConnConfig.RuntimeParams == nil {
poolCfg.ConnConfig.RuntimeParams = map[string]string{}
}
if poolCfg.ConnConfig.RuntimeParams["search_path"] == "" {
searchPath := os.Getenv("DB_SEARCH_PATH")
if searchPath == "" {
searchPath = "compliance,core,public"
}
poolCfg.ConnConfig.RuntimeParams["search_path"] = searchPath
}
pool, err := pgxpool.NewWithConfig(ctx, poolCfg)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
@@ -22,6 +22,7 @@ func registerIACERoutes(v1 *gin.RouterGroup, h *handlers.IACEHandler) {
iaceRoutes.GET("/norms-library/crossref", h.ListNormCrossRefs)
iaceRoutes.GET("/norms-library/:id/crossref", h.GetNormCrossRef)
iaceRoutes.GET("/lifecycle-phases", h.ListLifecyclePhases)
iaceRoutes.GET("/machine-types", h.ListMachineTypes)
iaceRoutes.GET("/roles", h.ListRoles)
iaceRoutes.GET("/evidence-types", h.ListEvidenceTypes)
iaceRoutes.GET("/protective-measures-library", h.ListProtectiveMeasures)