a616b64273
CI / detect-changes (push) Successful in 10s
CI / guardrail-integrity (push) Has been skipped
CI / branch-name (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 14s
CI / loc-budget (push) Failing after 19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / test-go (push) Successful in 47s
CI / nodejs-build (push) Successful in 2m46s
CI / iace-gt-coverage (push) Successful in 28s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
[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>
253 lines
11 KiB
Go
253 lines
11 KiB
Go
package iace
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ============================================================================
|
|
// API Request Types
|
|
// ============================================================================
|
|
|
|
// CreateProjectRequest is the API request for creating a new IACE project
|
|
type CreateProjectRequest struct {
|
|
ParentProjectID *uuid.UUID `json:"parent_project_id,omitempty"`
|
|
MachineName string `json:"machine_name" binding:"required"`
|
|
MachineType string `json:"machine_type" binding:"required"`
|
|
Manufacturer string `json:"manufacturer" binding:"required"`
|
|
CustomerName string `json:"customer_name,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
NarrativeText string `json:"narrative_text,omitempty"`
|
|
CEMarkingTarget string `json:"ce_marking_target,omitempty"`
|
|
Metadata json.RawMessage `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// UpdateProjectRequest is the API request for updating an existing project
|
|
type UpdateProjectRequest struct {
|
|
MachineName *string `json:"machine_name,omitempty"`
|
|
MachineType *string `json:"machine_type,omitempty"`
|
|
Manufacturer *string `json:"manufacturer,omitempty"`
|
|
CustomerName *string `json:"customer_name,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
NarrativeText *string `json:"narrative_text,omitempty"`
|
|
CEMarkingTarget *string `json:"ce_marking_target,omitempty"`
|
|
Metadata *json.RawMessage `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// CreateComponentRequest is the API request for adding a component to a project
|
|
type CreateComponentRequest struct {
|
|
ProjectID uuid.UUID `json:"project_id" binding:"required"`
|
|
ParentID *uuid.UUID `json:"parent_id,omitempty"`
|
|
Name string `json:"name" binding:"required"`
|
|
ComponentType ComponentType `json:"component_type" binding:"required"`
|
|
Version string `json:"version,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
IsSafetyRelevant bool `json:"is_safety_relevant"`
|
|
IsNetworked bool `json:"is_networked"`
|
|
}
|
|
|
|
// CreateHazardRequest is the API request for creating a new hazard
|
|
type CreateHazardRequest struct {
|
|
ProjectID uuid.UUID `json:"project_id" binding:"required"`
|
|
ComponentID uuid.UUID `json:"component_id" binding:"required"`
|
|
LibraryHazardID *uuid.UUID `json:"library_hazard_id,omitempty"`
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description,omitempty"`
|
|
Scenario string `json:"scenario,omitempty"`
|
|
Category string `json:"category" binding:"required"`
|
|
SubCategory string `json:"sub_category,omitempty"`
|
|
MachineModule string `json:"machine_module,omitempty"`
|
|
Function string `json:"function,omitempty"`
|
|
LifecyclePhase string `json:"lifecycle_phase,omitempty"`
|
|
HazardousZone string `json:"hazardous_zone,omitempty"`
|
|
TriggerEvent string `json:"trigger_event,omitempty"`
|
|
AffectedPerson string `json:"affected_person,omitempty"`
|
|
PossibleHarm string `json:"possible_harm,omitempty"`
|
|
}
|
|
|
|
// AssessRiskRequest is the API request for performing a risk assessment
|
|
type AssessRiskRequest struct {
|
|
HazardID uuid.UUID `json:"hazard_id" binding:"required"`
|
|
Severity int `json:"severity" binding:"required"`
|
|
Exposure int `json:"exposure" binding:"required"`
|
|
Probability int `json:"probability" binding:"required"`
|
|
Avoidance int `json:"avoidance,omitempty"` // 0=disabled, 1-5 (3=neutral)
|
|
ControlMaturity int `json:"control_maturity" binding:"required"`
|
|
ControlCoverage float64 `json:"control_coverage" binding:"required"`
|
|
TestEvidenceStrength float64 `json:"test_evidence_strength" binding:"required"`
|
|
AcceptanceJustification string `json:"acceptance_justification,omitempty"`
|
|
}
|
|
|
|
// CreateMitigationRequest is the API request for creating a mitigation measure
|
|
type CreateMitigationRequest struct {
|
|
HazardID uuid.UUID `json:"hazard_id" binding:"required"`
|
|
ReductionType ReductionType `json:"reduction_type" binding:"required"`
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description,omitempty"`
|
|
}
|
|
|
|
// CreateVerificationPlanRequest is the API request for creating a verification plan
|
|
type CreateVerificationPlanRequest struct {
|
|
ProjectID uuid.UUID `json:"project_id" binding:"required"`
|
|
HazardID *uuid.UUID `json:"hazard_id,omitempty"`
|
|
MitigationID *uuid.UUID `json:"mitigation_id,omitempty"`
|
|
Title string `json:"title" binding:"required"`
|
|
Description string `json:"description,omitempty"`
|
|
AcceptanceCriteria string `json:"acceptance_criteria,omitempty"`
|
|
Method VerificationMethod `json:"method" binding:"required"`
|
|
}
|
|
|
|
// CreateMonitoringEventRequest is the API request for logging a monitoring event
|
|
type CreateMonitoringEventRequest struct {
|
|
ProjectID uuid.UUID `json:"project_id" binding:"required"`
|
|
EventType MonitoringEventType `json:"event_type" binding:"required"`
|
|
Title string `json:"title" binding:"required"`
|
|
Description string `json:"description,omitempty"`
|
|
Severity string `json:"severity" binding:"required"`
|
|
}
|
|
|
|
// InitFromProfileRequest is the API request for initializing a project from a company profile
|
|
type InitFromProfileRequest struct {
|
|
CompanyProfile json.RawMessage `json:"company_profile" binding:"required"`
|
|
ComplianceScope json.RawMessage `json:"compliance_scope" binding:"required"`
|
|
}
|
|
|
|
// ============================================================================
|
|
// API Response Types
|
|
// ============================================================================
|
|
|
|
// ProjectListResponse is the API response for listing projects
|
|
type ProjectListResponse struct {
|
|
Projects []Project `json:"projects"`
|
|
Total int `json:"total"`
|
|
}
|
|
|
|
// ProjectDetailResponse is the API response for a single project with related entities
|
|
type ProjectDetailResponse struct {
|
|
Project
|
|
Components []Component `json:"components"`
|
|
Classifications []RegulatoryClassification `json:"classifications"`
|
|
CompletenessGates []CompletenessGate `json:"completeness_gates"`
|
|
}
|
|
|
|
// RiskSummaryResponse is the API response for an aggregated risk overview
|
|
type RiskSummaryResponse struct {
|
|
TotalHazards int `json:"total_hazards"`
|
|
NotAcceptable int `json:"not_acceptable,omitempty"`
|
|
VeryHigh int `json:"very_high,omitempty"`
|
|
Critical int `json:"critical"`
|
|
High int `json:"high"`
|
|
Medium int `json:"medium"`
|
|
Low int `json:"low"`
|
|
Negligible int `json:"negligible"`
|
|
OverallRiskLevel RiskLevel `json:"overall_risk_level"`
|
|
AllAcceptable bool `json:"all_acceptable"`
|
|
}
|
|
|
|
// LifecyclePhaseInfo represents a machine lifecycle phase with labels
|
|
type LifecyclePhaseInfo struct {
|
|
ID string `json:"id"`
|
|
LabelDE string `json:"label_de"`
|
|
LabelEN string `json:"label_en"`
|
|
Sort int `json:"sort_order"`
|
|
}
|
|
|
|
// RoleInfo represents an affected person role with labels
|
|
type RoleInfo struct {
|
|
ID string `json:"id"`
|
|
LabelDE string `json:"label_de"`
|
|
LabelEN string `json:"label_en"`
|
|
Sort int `json:"sort_order"`
|
|
}
|
|
|
|
// EvidenceTypeInfo represents an evidence/verification type with labels
|
|
type EvidenceTypeInfo struct {
|
|
ID string `json:"id"`
|
|
Category string `json:"category"`
|
|
LabelDE string `json:"label_de"`
|
|
LabelEN string `json:"label_en"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
Sort int `json:"sort_order"`
|
|
}
|
|
|
|
// ProtectiveMeasureEntry represents a protective measure from the library
|
|
type ProtectiveMeasureEntry struct {
|
|
ID string `json:"id"`
|
|
ReductionType string `json:"reduction_type"`
|
|
SubType string `json:"sub_type,omitempty"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
HazardCategory string `json:"hazard_category,omitempty"`
|
|
Examples []string `json:"examples,omitempty"`
|
|
NormReferences []string `json:"norm_references,omitempty"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
// Mandatory indicates this measure is REQUIRED by a harmonized standard.
|
|
// When true, the measure must be implemented if the referenced norm is applied.
|
|
// The norm creates a "presumption of conformity" — deviating requires the
|
|
// manufacturer to independently prove equivalent safety.
|
|
Mandatory bool `json:"mandatory,omitempty"`
|
|
MandatoryNorm string `json:"mandatory_norm,omitempty"`
|
|
// RiskReduction describes the typical risk reduction effect of this measure.
|
|
// Used by the Suppression Engine to automatically calculate residual risk
|
|
// when measures are assigned to hazards.
|
|
RiskReduction *RiskReduction `json:"risk_reduction,omitempty"`
|
|
}
|
|
|
|
// RiskReduction describes how a protective measure reduces risk parameters.
|
|
// Deltas are negative integers (e.g. -2 means "reduces by 2 levels").
|
|
// The Suppression Engine cumulates deltas across all assigned measures
|
|
// and clamps each parameter to a minimum of 1.
|
|
type RiskReduction struct {
|
|
// SeverityDelta reduces the severity rating (e.g. -1 for PPE, -2 for inherent safe design).
|
|
SeverityDelta int `json:"severity_delta,omitempty"`
|
|
// ExposureDelta reduces the exposure/frequency rating (e.g. -2 for fixed guard, -1 for interlock).
|
|
ExposureDelta int `json:"exposure_delta,omitempty"`
|
|
// ProbabilityDelta reduces the probability rating (e.g. -2 for interlock, -1 for training).
|
|
ProbabilityDelta int `json:"probability_delta,omitempty"`
|
|
}
|
|
|
|
// ValidateMitigationHierarchyRequest is the request for hierarchy validation
|
|
type ValidateMitigationHierarchyRequest struct {
|
|
HazardID uuid.UUID `json:"hazard_id" binding:"required"`
|
|
ReductionType ReductionType `json:"reduction_type" binding:"required"`
|
|
}
|
|
|
|
// ValidateMitigationHierarchyResponse is the response from hierarchy validation
|
|
type ValidateMitigationHierarchyResponse struct {
|
|
Valid bool `json:"valid"`
|
|
Warnings []string `json:"warnings,omitempty"`
|
|
}
|
|
|
|
// VariantGap compares a variant project against its base project
|
|
type VariantGap struct {
|
|
BaseProject ProjectSummary `json:"base_project"`
|
|
Variant ProjectSummary `json:"variant"`
|
|
Gap GapDetail `json:"gap"`
|
|
}
|
|
|
|
// ProjectSummary is a lightweight project view for gap comparisons
|
|
type ProjectSummary struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
HazardCount int `json:"hazard_count"`
|
|
MeasureCount int `json:"measure_count"`
|
|
}
|
|
|
|
// GapDetail describes the delta between variant and base project
|
|
type GapDetail struct {
|
|
AdditionalHazards int `json:"additional_hazards"`
|
|
AdditionalMeasures int `json:"additional_measures"`
|
|
CategoriesAffected []string `json:"categories_affected"`
|
|
}
|
|
|
|
// CompletenessGate represents a single gate in the project completeness checklist
|
|
type CompletenessGate struct {
|
|
ID string `json:"id"`
|
|
Category string `json:"category"`
|
|
Label string `json:"label"`
|
|
Required bool `json:"required"`
|
|
Passed bool `json:"passed"`
|
|
Details string `json:"details,omitempty"`
|
|
}
|