Files
breakpilot-compliance/ai-compliance-sdk/internal/api/handlers/iace_handler_parser.go
T
2026-05-05 07:18:55 +02:00

111 lines
3.6 KiB
Go

package handlers
import (
"net/http"
"strings"
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
"github.com/gin-gonic/gin"
)
// ParseNarrativeRequest is the request body for POST /projects/:id/parse-narrative.
type ParseNarrativeRequest struct {
NarrativeText string `json:"narrative_text" binding:"required"`
}
// ParseNarrativeResponse contains the deterministic parsing results.
type ParseNarrativeResponse struct {
Components []iace.ComponentMatch `json:"components"`
EnergySources []iace.EnergyMatch `json:"energy_sources"`
LifecyclePhases []string `json:"lifecycle_phases"`
Roles []string `json:"roles"`
Tags []string `json:"tags"`
TechSpecs []iace.TechSpec `json:"tech_specs"`
Confidence float64 `json:"confidence"`
// Pattern match results (from feeding parsed data into pattern engine)
MatchedPatterns int `json:"matched_patterns"`
SuggestedHazards []struct {
Category string `json:"category"`
PatternID string `json:"pattern_id"`
PatternName string `json:"pattern_name"`
Priority int `json:"priority"`
} `json:"suggested_hazards"`
}
// ParseNarrative handles POST /projects/:id/parse-narrative
// Deterministically extracts components, energy sources, lifecycle phases,
// roles, and hazard patterns from a free-text machine description.
// NO LLM required — pure keyword matching + pattern engine.
func (h *IACEHandler) ParseNarrative(c *gin.Context) {
var req ParseNarrativeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "narrative_text is required"})
return
}
// 1. Parse narrative text deterministically
parseResult := iace.ParseNarrative(req.NarrativeText)
// 2. Feed parsed tags into pattern engine
// Collect all component IDs for tag resolution
var componentIDs []string
for _, comp := range parseResult.Components {
componentIDs = append(componentIDs, comp.LibraryID)
}
var energyIDs []string
for _, e := range parseResult.EnergySources {
energyIDs = append(energyIDs, e.SourceID)
}
// Run pattern matching via PatternEngine
engine := iace.NewPatternEngine()
matchInput := iace.MatchInput{
ComponentLibraryIDs: componentIDs,
EnergySourceIDs: energyIDs,
LifecyclePhases: parseResult.LifecyclePhases,
CustomTags: parseResult.CustomTags,
}
matchOutput := engine.Match(matchInput)
// 3. Build response
resp := ParseNarrativeResponse{
Components: parseResult.Components,
EnergySources: parseResult.EnergySources,
LifecyclePhases: parseResult.LifecyclePhases,
Roles: parseResult.Roles,
Tags: parseResult.CustomTags,
TechSpecs: parseResult.TechSpecs,
Confidence: parseResult.Confidence,
MatchedPatterns: len(matchOutput.MatchedPatterns),
}
// Add suggested hazards from pattern engine output
for _, h := range matchOutput.SuggestedHazards {
// Find the first source pattern for name/priority lookup
patternName := ""
priority := 50
if len(h.SourcePatterns) > 0 {
for _, mp := range matchOutput.MatchedPatterns {
if mp.PatternID == h.SourcePatterns[0] {
patternName = mp.PatternName
priority = mp.Priority
break
}
}
}
resp.SuggestedHazards = append(resp.SuggestedHazards, struct {
Category string `json:"category"`
PatternID string `json:"pattern_id"`
PatternName string `json:"pattern_name"`
Priority int `json:"priority"`
}{
Category: h.Category,
PatternID: strings.Join(h.SourcePatterns, ","),
PatternName: patternName,
Priority: priority,
})
}
c.JSON(http.StatusOK, resp)
}