package handlers import ( "net/http" "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 matched patterns for _, mp := range matchOutput.MatchedPatterns { for _, cat := range mp.GeneratedHazardCats { 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: cat, PatternID: mp.ID, PatternName: mp.NameDE, Priority: mp.Priority, }) } } c.JSON(http.StatusOK, resp) }