All oversized iace files now comply with the 500-line hard cap: - hazard_library_ai_sw.go split into ai_sw (false_classification..communication) and ai_fw (unauthorized_access..update_failure) - hazard_library_software_hmi.go split into software_hmi (software_fault+hmi) and config_integration (configuration_error+logging+integration) - hazard_library_machine_safety.go split to keep mechanical/electrical/thermal/emc, safety_functions extracted into hazard_library_safety_functions.go - store_hazards.go split: hazard library queries moved to store_hazard_library.go - store_projects.go split: component and classification ops to store_components.go - store_mitigations.go split: evidence/verification/ref-data to store_evidence.go - hazard_library.go GetBuiltinHazardLibrary() updated to call all sub-functions - All iace tests pass (go test ./internal/iace/...) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
234 lines
6.9 KiB
Go
234 lines
6.9 KiB
Go
package iace
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// ============================================================================
|
|
// Project CRUD Operations
|
|
// ============================================================================
|
|
|
|
// CreateProject creates a new IACE project
|
|
func (s *Store) CreateProject(ctx context.Context, tenantID uuid.UUID, req CreateProjectRequest) (*Project, error) {
|
|
project := &Project{
|
|
ID: uuid.New(),
|
|
TenantID: tenantID,
|
|
MachineName: req.MachineName,
|
|
MachineType: req.MachineType,
|
|
Manufacturer: req.Manufacturer,
|
|
Description: req.Description,
|
|
NarrativeText: req.NarrativeText,
|
|
Status: ProjectStatusDraft,
|
|
CEMarkingTarget: req.CEMarkingTarget,
|
|
Metadata: req.Metadata,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
_, err := s.pool.Exec(ctx, `
|
|
INSERT INTO iace_projects (
|
|
id, tenant_id, machine_name, machine_type, manufacturer,
|
|
description, narrative_text, status, ce_marking_target,
|
|
completeness_score, risk_summary, triggered_regulations, metadata,
|
|
created_at, updated_at, archived_at
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5,
|
|
$6, $7, $8, $9,
|
|
$10, $11, $12, $13,
|
|
$14, $15, $16
|
|
)
|
|
`,
|
|
project.ID, project.TenantID, project.MachineName, project.MachineType, project.Manufacturer,
|
|
project.Description, project.NarrativeText, string(project.Status), project.CEMarkingTarget,
|
|
project.CompletenessScore, nil, project.TriggeredRegulations, project.Metadata,
|
|
project.CreatedAt, project.UpdatedAt, project.ArchivedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create project: %w", err)
|
|
}
|
|
|
|
return project, nil
|
|
}
|
|
|
|
// GetProject retrieves a project by ID
|
|
func (s *Store) GetProject(ctx context.Context, id uuid.UUID) (*Project, error) {
|
|
var p Project
|
|
var status string
|
|
var riskSummary, triggeredRegulations, metadata []byte
|
|
|
|
err := s.pool.QueryRow(ctx, `
|
|
SELECT
|
|
id, tenant_id, machine_name, machine_type, manufacturer,
|
|
description, narrative_text, status, ce_marking_target,
|
|
completeness_score, risk_summary, triggered_regulations, metadata,
|
|
created_at, updated_at, archived_at
|
|
FROM iace_projects WHERE id = $1
|
|
`, id).Scan(
|
|
&p.ID, &p.TenantID, &p.MachineName, &p.MachineType, &p.Manufacturer,
|
|
&p.Description, &p.NarrativeText, &status, &p.CEMarkingTarget,
|
|
&p.CompletenessScore, &riskSummary, &triggeredRegulations, &metadata,
|
|
&p.CreatedAt, &p.UpdatedAt, &p.ArchivedAt,
|
|
)
|
|
if err == pgx.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get project: %w", err)
|
|
}
|
|
|
|
p.Status = ProjectStatus(status)
|
|
json.Unmarshal(riskSummary, &p.RiskSummary)
|
|
json.Unmarshal(triggeredRegulations, &p.TriggeredRegulations)
|
|
json.Unmarshal(metadata, &p.Metadata)
|
|
|
|
return &p, nil
|
|
}
|
|
|
|
// ListProjects lists all projects for a tenant
|
|
func (s *Store) ListProjects(ctx context.Context, tenantID uuid.UUID) ([]Project, error) {
|
|
rows, err := s.pool.Query(ctx, `
|
|
SELECT
|
|
id, tenant_id, machine_name, machine_type, manufacturer,
|
|
description, narrative_text, status, ce_marking_target,
|
|
completeness_score, risk_summary, triggered_regulations, metadata,
|
|
created_at, updated_at, archived_at
|
|
FROM iace_projects WHERE tenant_id = $1
|
|
ORDER BY created_at DESC
|
|
`, tenantID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list projects: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var projects []Project
|
|
for rows.Next() {
|
|
var p Project
|
|
var status string
|
|
var riskSummary, triggeredRegulations, metadata []byte
|
|
|
|
err := rows.Scan(
|
|
&p.ID, &p.TenantID, &p.MachineName, &p.MachineType, &p.Manufacturer,
|
|
&p.Description, &p.NarrativeText, &status, &p.CEMarkingTarget,
|
|
&p.CompletenessScore, &riskSummary, &triggeredRegulations, &metadata,
|
|
&p.CreatedAt, &p.UpdatedAt, &p.ArchivedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list projects scan: %w", err)
|
|
}
|
|
|
|
p.Status = ProjectStatus(status)
|
|
json.Unmarshal(riskSummary, &p.RiskSummary)
|
|
json.Unmarshal(triggeredRegulations, &p.TriggeredRegulations)
|
|
json.Unmarshal(metadata, &p.Metadata)
|
|
|
|
projects = append(projects, p)
|
|
}
|
|
|
|
return projects, nil
|
|
}
|
|
|
|
// UpdateProject updates an existing project's mutable fields
|
|
func (s *Store) UpdateProject(ctx context.Context, id uuid.UUID, req UpdateProjectRequest) (*Project, error) {
|
|
// Fetch current project first
|
|
project, err := s.GetProject(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if project == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Apply partial updates
|
|
if req.MachineName != nil {
|
|
project.MachineName = *req.MachineName
|
|
}
|
|
if req.MachineType != nil {
|
|
project.MachineType = *req.MachineType
|
|
}
|
|
if req.Manufacturer != nil {
|
|
project.Manufacturer = *req.Manufacturer
|
|
}
|
|
if req.Description != nil {
|
|
project.Description = *req.Description
|
|
}
|
|
if req.NarrativeText != nil {
|
|
project.NarrativeText = *req.NarrativeText
|
|
}
|
|
if req.CEMarkingTarget != nil {
|
|
project.CEMarkingTarget = *req.CEMarkingTarget
|
|
}
|
|
if req.Metadata != nil {
|
|
project.Metadata = *req.Metadata
|
|
}
|
|
|
|
project.UpdatedAt = time.Now().UTC()
|
|
|
|
_, err = s.pool.Exec(ctx, `
|
|
UPDATE iace_projects SET
|
|
machine_name = $2, machine_type = $3, manufacturer = $4,
|
|
description = $5, narrative_text = $6, ce_marking_target = $7,
|
|
metadata = $8, updated_at = $9
|
|
WHERE id = $1
|
|
`,
|
|
id, project.MachineName, project.MachineType, project.Manufacturer,
|
|
project.Description, project.NarrativeText, project.CEMarkingTarget,
|
|
project.Metadata, project.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("update project: %w", err)
|
|
}
|
|
|
|
return project, nil
|
|
}
|
|
|
|
// ArchiveProject sets the archived_at timestamp and status for a project
|
|
func (s *Store) ArchiveProject(ctx context.Context, id uuid.UUID) error {
|
|
now := time.Now().UTC()
|
|
_, err := s.pool.Exec(ctx, `
|
|
UPDATE iace_projects SET
|
|
status = $2, archived_at = $3, updated_at = $3
|
|
WHERE id = $1
|
|
`, id, string(ProjectStatusArchived), now)
|
|
if err != nil {
|
|
return fmt.Errorf("archive project: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateProjectStatus updates the lifecycle status of a project
|
|
func (s *Store) UpdateProjectStatus(ctx context.Context, id uuid.UUID, status ProjectStatus) error {
|
|
_, err := s.pool.Exec(ctx, `
|
|
UPDATE iace_projects SET status = $2, updated_at = NOW()
|
|
WHERE id = $1
|
|
`, id, string(status))
|
|
if err != nil {
|
|
return fmt.Errorf("update project status: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateProjectCompleteness updates the completeness score and risk summary
|
|
func (s *Store) UpdateProjectCompleteness(ctx context.Context, id uuid.UUID, score float64, riskSummary map[string]int) error {
|
|
riskSummaryJSON, err := json.Marshal(riskSummary)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal risk summary: %w", err)
|
|
}
|
|
|
|
_, err = s.pool.Exec(ctx, `
|
|
UPDATE iace_projects SET
|
|
completeness_score = $2, risk_summary = $3, updated_at = NOW()
|
|
WHERE id = $1
|
|
`, id, score, riskSummaryJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("update project completeness: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|