Files
Sharang Parnerkar 3f1444541f refactor(go/iace): split tech_file_generator, hazard_patterns, models, completeness
Split 4 oversized files (503-679 LOC each) into focused units all under 500 LOC:
- tech_file_generator.go → +_prompts, +_prompt_builder, +_fallback
- hazard_patterns_extended.go → +_extended2.go (HP074-HP102 extracted)
- models.go → +_entities.go, +_api.go (enums / DB entities / API types)
- completeness.go → +_gates.go (gate definitions extracted)

All files remain in package iace. Zero behavior changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 10:03:44 +02:00

161 lines
5.1 KiB
Go

package iace
import (
"context"
"fmt"
"strings"
"github.com/breakpilot/ai-compliance-sdk/internal/llm"
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
"github.com/google/uuid"
)
// ============================================================================
// TechFileGenerator — LLM-based generation of technical file sections
// ============================================================================
// TechFileGenerator generates technical file section content using LLM and RAG.
type TechFileGenerator struct {
llmRegistry *llm.ProviderRegistry
ragClient *ucca.LegalRAGClient
store *Store
}
// NewTechFileGenerator creates a new TechFileGenerator.
func NewTechFileGenerator(registry *llm.ProviderRegistry, ragClient *ucca.LegalRAGClient, store *Store) *TechFileGenerator {
return &TechFileGenerator{
llmRegistry: registry,
ragClient: ragClient,
store: store,
}
}
// SectionGenerationContext holds all project data needed for LLM section generation.
type SectionGenerationContext struct {
Project *Project
Components []Component
Hazards []Hazard
Assessments map[uuid.UUID][]RiskAssessment // keyed by hazardID
Mitigations map[uuid.UUID][]Mitigation // keyed by hazardID
Classifications []RegulatoryClassification
Evidence []Evidence
RAGContext string // aggregated text from RAG search
}
// ============================================================================
// BuildSectionContext — loads all project data + RAG context
// ============================================================================
// BuildSectionContext loads project data and RAG context for a given section type.
func (g *TechFileGenerator) BuildSectionContext(ctx context.Context, projectID uuid.UUID, sectionType string) (*SectionGenerationContext, error) {
// Load project
project, err := g.store.GetProject(ctx, projectID)
if err != nil {
return nil, fmt.Errorf("load project: %w", err)
}
if project == nil {
return nil, fmt.Errorf("project %s not found", projectID)
}
// Load components
components, err := g.store.ListComponents(ctx, projectID)
if err != nil {
return nil, fmt.Errorf("load components: %w", err)
}
// Load hazards
hazards, err := g.store.ListHazards(ctx, projectID)
if err != nil {
return nil, fmt.Errorf("load hazards: %w", err)
}
// Load assessments and mitigations per hazard
assessments := make(map[uuid.UUID][]RiskAssessment)
mitigations := make(map[uuid.UUID][]Mitigation)
for _, h := range hazards {
a, err := g.store.ListAssessments(ctx, h.ID)
if err != nil {
return nil, fmt.Errorf("load assessments for hazard %s: %w", h.ID, err)
}
assessments[h.ID] = a
m, err := g.store.ListMitigations(ctx, h.ID)
if err != nil {
return nil, fmt.Errorf("load mitigations for hazard %s: %w", h.ID, err)
}
mitigations[h.ID] = m
}
// Load classifications
classifications, err := g.store.GetClassifications(ctx, projectID)
if err != nil {
return nil, fmt.Errorf("load classifications: %w", err)
}
// Load evidence
evidence, err := g.store.ListEvidence(ctx, projectID)
if err != nil {
return nil, fmt.Errorf("load evidence: %w", err)
}
// Perform RAG search for section-specific context
ragContext := ""
if g.ragClient != nil {
ragQuery := buildRAGQuery(sectionType)
results, ragErr := g.ragClient.SearchCollection(ctx, "bp_iace_libraries", ragQuery, nil, 5)
if ragErr == nil && len(results) > 0 {
var ragParts []string
for _, r := range results {
entry := fmt.Sprintf("[%s] %s", r.RegulationShort, truncateForPrompt(r.Text, 400))
ragParts = append(ragParts, entry)
}
ragContext = strings.Join(ragParts, "\n\n")
}
// RAG failure is non-fatal — we proceed without context
}
return &SectionGenerationContext{
Project: project,
Components: components,
Hazards: hazards,
Assessments: assessments,
Mitigations: mitigations,
Classifications: classifications,
Evidence: evidence,
RAGContext: ragContext,
}, nil
}
// ============================================================================
// GenerateSection — main entry point
// ============================================================================
// GenerateSection generates the content for a technical file section using LLM.
// If LLM is unavailable, returns an enhanced placeholder with project data.
func (g *TechFileGenerator) GenerateSection(ctx context.Context, projectID uuid.UUID, sectionType string) (string, error) {
sctx, err := g.BuildSectionContext(ctx, projectID, sectionType)
if err != nil {
return "", fmt.Errorf("build section context: %w", err)
}
// Build prompts
systemPrompt := getSystemPrompt(sectionType)
userPrompt := buildUserPrompt(sctx, sectionType)
// Attempt LLM generation
resp, err := g.llmRegistry.Chat(ctx, &llm.ChatRequest{
Messages: []llm.Message{
{Role: "system", Content: systemPrompt},
{Role: "user", Content: userPrompt},
},
Temperature: 0.15,
MaxTokens: 4096,
})
if err != nil {
// LLM unavailable — return structured fallback with real project data
return buildFallbackContent(sctx, sectionType), nil
}
return resp.Message.Content, nil
}