e7f2f98da3
Major features: - 215 norms library with section references + Beuth URLs (A/B1/B2/C norms) - 173 hazard patterns with detail fields (scenario, trigger, harm, zone) - Deterministic pattern matching: Component × Lifecycle × Pattern cross-product - SIL/PL auto-calculation from S×E×P risk graph - Risk assessment table with editable S/E/P dropdowns - Production Line Dashboard with animated station flow (Running Dots) - IACE process flow + norms coverage on start page - Non-blocking cookie banner, ProcessFlow SSR fix - 104 Playwright E2E tests passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
249 lines
6.8 KiB
Go
249 lines
6.8 KiB
Go
package iace
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// ============================================================================
|
|
// Mitigation CRUD Operations
|
|
// ============================================================================
|
|
|
|
// CreateMitigation creates a new mitigation measure for a hazard
|
|
func (s *Store) CreateMitigation(ctx context.Context, req CreateMitigationRequest) (*Mitigation, error) {
|
|
m := &Mitigation{
|
|
ID: uuid.New(),
|
|
HazardID: req.HazardID,
|
|
ReductionType: req.ReductionType,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
Status: MitigationStatusPlanned,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
_, err := s.pool.Exec(ctx, `
|
|
INSERT INTO iace_mitigations (
|
|
id, hazard_id, reduction_type, name, description,
|
|
status, verification_method, verification_result,
|
|
verified_at, verified_by,
|
|
created_at, updated_at
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5,
|
|
$6, $7, $8,
|
|
$9, $10,
|
|
$11, $12
|
|
)
|
|
`,
|
|
m.ID, m.HazardID, string(m.ReductionType), m.Name, m.Description,
|
|
string(m.Status), "", "",
|
|
nil, uuid.Nil,
|
|
m.CreatedAt, m.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create mitigation: %w", err)
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// UpdateMitigation updates a mitigation with a dynamic set of fields
|
|
func (s *Store) UpdateMitigation(ctx context.Context, id uuid.UUID, updates map[string]interface{}) (*Mitigation, error) {
|
|
if len(updates) == 0 {
|
|
return s.getMitigation(ctx, id)
|
|
}
|
|
|
|
query := "UPDATE iace_mitigations SET updated_at = NOW()"
|
|
args := []interface{}{id}
|
|
argIdx := 2
|
|
|
|
for key, val := range updates {
|
|
switch key {
|
|
case "name", "description", "verification_result":
|
|
query += fmt.Sprintf(", %s = $%d", key, argIdx)
|
|
args = append(args, val)
|
|
argIdx++
|
|
case "status":
|
|
query += fmt.Sprintf(", status = $%d", argIdx)
|
|
args = append(args, val)
|
|
argIdx++
|
|
case "reduction_type":
|
|
query += fmt.Sprintf(", reduction_type = $%d", argIdx)
|
|
args = append(args, val)
|
|
argIdx++
|
|
case "verification_method":
|
|
query += fmt.Sprintf(", verification_method = $%d", argIdx)
|
|
args = append(args, val)
|
|
argIdx++
|
|
}
|
|
}
|
|
|
|
query += " WHERE id = $1"
|
|
|
|
_, err := s.pool.Exec(ctx, query, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("update mitigation: %w", err)
|
|
}
|
|
|
|
return s.getMitigation(ctx, id)
|
|
}
|
|
|
|
// VerifyMitigation marks a mitigation as verified
|
|
func (s *Store) VerifyMitigation(ctx context.Context, id uuid.UUID, verificationResult string, verifiedBy string) error {
|
|
now := time.Now().UTC()
|
|
verifiedByUUID, err := uuid.Parse(verifiedBy)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid verified_by UUID: %w", err)
|
|
}
|
|
|
|
_, err = s.pool.Exec(ctx, `
|
|
UPDATE iace_mitigations SET
|
|
status = $2,
|
|
verification_result = $3,
|
|
verified_at = $4,
|
|
verified_by = $5,
|
|
updated_at = $4
|
|
WHERE id = $1
|
|
`, id, string(MitigationStatusVerified), verificationResult, now, verifiedByUUID)
|
|
if err != nil {
|
|
return fmt.Errorf("verify mitigation: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ListMitigations lists all mitigations for a hazard
|
|
func (s *Store) ListMitigations(ctx context.Context, hazardID uuid.UUID) ([]Mitigation, error) {
|
|
rows, err := s.pool.Query(ctx, `
|
|
SELECT
|
|
id, hazard_id, reduction_type, name, description,
|
|
status, verification_method, verification_result,
|
|
verified_at, verified_by,
|
|
created_at, updated_at
|
|
FROM iace_mitigations WHERE hazard_id = $1
|
|
ORDER BY created_at ASC
|
|
`, hazardID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list mitigations: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var mitigations []Mitigation
|
|
for rows.Next() {
|
|
var m Mitigation
|
|
var reductionType, status, verificationMethod string
|
|
|
|
err := rows.Scan(
|
|
&m.ID, &m.HazardID, &reductionType, &m.Name, &m.Description,
|
|
&status, &verificationMethod, &m.VerificationResult,
|
|
&m.VerifiedAt, &m.VerifiedBy,
|
|
&m.CreatedAt, &m.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list mitigations scan: %w", err)
|
|
}
|
|
|
|
m.ReductionType = ReductionType(reductionType)
|
|
m.Status = MitigationStatus(status)
|
|
m.VerificationMethod = VerificationMethod(verificationMethod)
|
|
|
|
mitigations = append(mitigations, m)
|
|
}
|
|
|
|
return mitigations, nil
|
|
}
|
|
|
|
// ListMitigationsByProject lists all mitigations for all hazards in a project.
|
|
func (s *Store) ListMitigationsByProject(ctx context.Context, projectID uuid.UUID) ([]Mitigation, error) {
|
|
rows, err := s.pool.Query(ctx, `
|
|
SELECT
|
|
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.created_at, m.updated_at
|
|
FROM iace_mitigations m
|
|
JOIN iace_hazards h ON h.id = m.hazard_id
|
|
WHERE h.project_id = $1
|
|
ORDER BY m.created_at ASC
|
|
`, projectID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list mitigations by project: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var mitigations []Mitigation
|
|
for rows.Next() {
|
|
var m Mitigation
|
|
var reductionType, status, verificationMethod string
|
|
|
|
err := rows.Scan(
|
|
&m.ID, &m.HazardID, &reductionType, &m.Name, &m.Description,
|
|
&status, &verificationMethod, &m.VerificationResult,
|
|
&m.VerifiedAt, &m.VerifiedBy,
|
|
&m.CreatedAt, &m.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list mitigations by project scan: %w", err)
|
|
}
|
|
|
|
m.ReductionType = ReductionType(reductionType)
|
|
m.Status = MitigationStatus(status)
|
|
m.VerificationMethod = VerificationMethod(verificationMethod)
|
|
|
|
mitigations = append(mitigations, m)
|
|
}
|
|
|
|
return mitigations, nil
|
|
}
|
|
|
|
// DeleteMitigation deletes a mitigation by ID.
|
|
func (s *Store) DeleteMitigation(ctx context.Context, id uuid.UUID) error {
|
|
_, err := s.pool.Exec(ctx, `DELETE FROM iace_mitigations WHERE id = $1`, id)
|
|
if err != nil {
|
|
return fmt.Errorf("delete mitigation: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetMitigation fetches a single mitigation by ID.
|
|
func (s *Store) GetMitigation(ctx context.Context, id uuid.UUID) (*Mitigation, error) {
|
|
return s.getMitigation(ctx, id)
|
|
}
|
|
|
|
// getMitigation is a helper to fetch a single mitigation by ID
|
|
func (s *Store) getMitigation(ctx context.Context, id uuid.UUID) (*Mitigation, error) {
|
|
var m Mitigation
|
|
var reductionType, status, verificationMethod string
|
|
|
|
err := s.pool.QueryRow(ctx, `
|
|
SELECT
|
|
id, hazard_id, reduction_type, name, description,
|
|
status, verification_method, verification_result,
|
|
verified_at, verified_by,
|
|
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.CreatedAt, &m.UpdatedAt,
|
|
)
|
|
if err == pgx.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get mitigation: %w", err)
|
|
}
|
|
|
|
m.ReductionType = ReductionType(reductionType)
|
|
m.Status = MitigationStatus(status)
|
|
m.VerificationMethod = VerificationMethod(verificationMethod)
|
|
|
|
return &m, nil
|
|
}
|
|
|