package iace import ( "context" "encoding/json" "fmt" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" ) // ============================================================================ // Component CRUD Operations // ============================================================================ // CreateComponent creates a new component within a project func (s *Store) CreateComponent(ctx context.Context, req CreateComponentRequest) (*Component, error) { comp := &Component{ ID: uuid.New(), ProjectID: req.ProjectID, ParentID: req.ParentID, Name: req.Name, ComponentType: req.ComponentType, Version: req.Version, Description: req.Description, IsSafetyRelevant: req.IsSafetyRelevant, IsNetworked: req.IsNetworked, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } _, err := s.pool.Exec(ctx, ` INSERT INTO iace_components ( id, project_id, parent_id, name, component_type, version, description, is_safety_relevant, is_networked, metadata, sort_order, created_at, updated_at ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 ) `, comp.ID, comp.ProjectID, comp.ParentID, comp.Name, string(comp.ComponentType), comp.Version, comp.Description, comp.IsSafetyRelevant, comp.IsNetworked, comp.Metadata, comp.SortOrder, comp.CreatedAt, comp.UpdatedAt, ) if err != nil { return nil, fmt.Errorf("create component: %w", err) } return comp, nil } // GetComponent retrieves a component by ID func (s *Store) GetComponent(ctx context.Context, id uuid.UUID) (*Component, error) { var c Component var compType string var metadata []byte err := s.pool.QueryRow(ctx, ` SELECT id, project_id, parent_id, name, component_type, version, description, is_safety_relevant, is_networked, metadata, sort_order, created_at, updated_at FROM iace_components WHERE id = $1 `, id).Scan( &c.ID, &c.ProjectID, &c.ParentID, &c.Name, &compType, &c.Version, &c.Description, &c.IsSafetyRelevant, &c.IsNetworked, &metadata, &c.SortOrder, &c.CreatedAt, &c.UpdatedAt, ) if err == pgx.ErrNoRows { return nil, nil } if err != nil { return nil, fmt.Errorf("get component: %w", err) } c.ComponentType = ComponentType(compType) json.Unmarshal(metadata, &c.Metadata) return &c, nil } // ListComponents lists all components for a project func (s *Store) ListComponents(ctx context.Context, projectID uuid.UUID) ([]Component, error) { rows, err := s.pool.Query(ctx, ` SELECT id, project_id, parent_id, name, component_type, version, description, is_safety_relevant, is_networked, metadata, sort_order, created_at, updated_at FROM iace_components WHERE project_id = $1 ORDER BY sort_order ASC, created_at ASC `, projectID) if err != nil { return nil, fmt.Errorf("list components: %w", err) } defer rows.Close() var components []Component for rows.Next() { var c Component var compType string var metadata []byte err := rows.Scan( &c.ID, &c.ProjectID, &c.ParentID, &c.Name, &compType, &c.Version, &c.Description, &c.IsSafetyRelevant, &c.IsNetworked, &metadata, &c.SortOrder, &c.CreatedAt, &c.UpdatedAt, ) if err != nil { return nil, fmt.Errorf("list components scan: %w", err) } c.ComponentType = ComponentType(compType) json.Unmarshal(metadata, &c.Metadata) components = append(components, c) } return components, nil } // UpdateComponent updates a component with a dynamic set of fields func (s *Store) UpdateComponent(ctx context.Context, id uuid.UUID, updates map[string]interface{}) (*Component, error) { if len(updates) == 0 { return s.GetComponent(ctx, id) } query := "UPDATE iace_components SET updated_at = NOW()" args := []interface{}{id} argIdx := 2 for key, val := range updates { switch key { case "name", "version", "description": query += fmt.Sprintf(", %s = $%d", key, argIdx) args = append(args, val) argIdx++ case "component_type": query += fmt.Sprintf(", component_type = $%d", argIdx) args = append(args, val) argIdx++ case "is_safety_relevant": query += fmt.Sprintf(", is_safety_relevant = $%d", argIdx) args = append(args, val) argIdx++ case "is_networked": query += fmt.Sprintf(", is_networked = $%d", argIdx) args = append(args, val) argIdx++ case "sort_order": query += fmt.Sprintf(", sort_order = $%d", argIdx) args = append(args, val) argIdx++ case "metadata": metaJSON, _ := json.Marshal(val) query += fmt.Sprintf(", metadata = $%d", argIdx) args = append(args, metaJSON) argIdx++ case "parent_id": query += fmt.Sprintf(", parent_id = $%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 component: %w", err) } return s.GetComponent(ctx, id) } // DeleteComponent deletes a component by ID func (s *Store) DeleteComponent(ctx context.Context, id uuid.UUID) error { _, err := s.pool.Exec(ctx, "DELETE FROM iace_components WHERE id = $1", id) if err != nil { return fmt.Errorf("delete component: %w", err) } return nil } // ============================================================================ // Classification Operations // ============================================================================ // UpsertClassification inserts or updates a regulatory classification for a project func (s *Store) UpsertClassification(ctx context.Context, projectID uuid.UUID, regulation RegulationType, result string, riskLevel string, confidence float64, reasoning string, ragSources, requirements json.RawMessage) (*RegulatoryClassification, error) { id := uuid.New() now := time.Now().UTC() _, err := s.pool.Exec(ctx, ` INSERT INTO iace_classifications ( id, project_id, regulation, classification_result, risk_level, confidence, reasoning, rag_sources, requirements, created_at, updated_at ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 ) ON CONFLICT (project_id, regulation) DO UPDATE SET classification_result = EXCLUDED.classification_result, risk_level = EXCLUDED.risk_level, confidence = EXCLUDED.confidence, reasoning = EXCLUDED.reasoning, rag_sources = EXCLUDED.rag_sources, requirements = EXCLUDED.requirements, updated_at = EXCLUDED.updated_at `, id, projectID, string(regulation), result, riskLevel, confidence, reasoning, ragSources, requirements, now, now, ) if err != nil { return nil, fmt.Errorf("upsert classification: %w", err) } // Retrieve the upserted row (may have kept the original ID on conflict) return s.getClassificationByProjectAndRegulation(ctx, projectID, regulation) } // getClassificationByProjectAndRegulation is a helper to fetch a single classification func (s *Store) getClassificationByProjectAndRegulation(ctx context.Context, projectID uuid.UUID, regulation RegulationType) (*RegulatoryClassification, error) { var c RegulatoryClassification var reg, rl string var ragSources, requirements []byte err := s.pool.QueryRow(ctx, ` SELECT id, project_id, regulation, classification_result, risk_level, confidence, reasoning, rag_sources, requirements, created_at, updated_at FROM iace_classifications WHERE project_id = $1 AND regulation = $2 `, projectID, string(regulation)).Scan( &c.ID, &c.ProjectID, ®, &c.ClassificationResult, &rl, &c.Confidence, &c.Reasoning, &ragSources, &requirements, &c.CreatedAt, &c.UpdatedAt, ) if err == pgx.ErrNoRows { return nil, nil } if err != nil { return nil, fmt.Errorf("get classification: %w", err) } c.Regulation = RegulationType(reg) c.RiskLevel = RiskLevel(rl) json.Unmarshal(ragSources, &c.RAGSources) json.Unmarshal(requirements, &c.Requirements) return &c, nil } // GetClassifications retrieves all classifications for a project func (s *Store) GetClassifications(ctx context.Context, projectID uuid.UUID) ([]RegulatoryClassification, error) { rows, err := s.pool.Query(ctx, ` SELECT id, project_id, regulation, classification_result, risk_level, confidence, reasoning, rag_sources, requirements, created_at, updated_at FROM iace_classifications WHERE project_id = $1 ORDER BY regulation ASC `, projectID) if err != nil { return nil, fmt.Errorf("get classifications: %w", err) } defer rows.Close() var classifications []RegulatoryClassification for rows.Next() { var c RegulatoryClassification var reg, rl string var ragSources, requirements []byte err := rows.Scan( &c.ID, &c.ProjectID, ®, &c.ClassificationResult, &rl, &c.Confidence, &c.Reasoning, &ragSources, &requirements, &c.CreatedAt, &c.UpdatedAt, ) if err != nil { return nil, fmt.Errorf("get classifications scan: %w", err) } c.Regulation = RegulationType(reg) c.RiskLevel = RiskLevel(rl) json.Unmarshal(ragSources, &c.RAGSources) json.Unmarshal(requirements, &c.Requirements) classifications = append(classifications, c) } return classifications, nil }