package controllers import ( "encoding/json" "strconv" "strings" "time" "doormile/db" "doormile/models" "doormile/utils" "github.com/gofiber/fiber/v2" ) // POST /api/v1/internal/agent-decisions func CreateAgentDecision(c *fiber.Ctx) error { type req struct { DecisionType string `json:"decision_type"` BookingID *uint64 `json:"booking_id"` Context map[string]interface{} `json:"context"` Decision map[string]interface{} `json:"decision"` Reasoning string `json:"reasoning"` ContextEmbedding []float32 `json:"context_embedding"` } body := new(req) if err := c.BodyParser(body); err != nil { return utils.BadRequest(c, "invalid request body") } if body.DecisionType == "" { return utils.BadRequest(c, "decision_type is required") } contextJSON, err := json.Marshal(body.Context) if err != nil { return utils.BadRequest(c, "invalid context JSON") } decisionJSON, err := json.Marshal(body.Decision) if err != nil { return utils.BadRequest(c, "invalid decision JSON") } record := models.AgentDecision{ DecisionType: body.DecisionType, BookingID: body.BookingID, Context: string(contextJSON), Decision: string(decisionJSON), Reasoning: body.Reasoning, CreatedAt: time.Now(), } if err := db.DB.Create(&record).Error; err != nil { utils.Error("CreateAgentDecision: insert failed", "error", err) return utils.Internal(c, "failed to create agent decision") } if len(body.ContextEmbedding) > 0 { parts := make([]string, len(body.ContextEmbedding)) for i, v := range body.ContextEmbedding { parts[i] = strconv.FormatFloat(float64(v), 'f', -1, 32) } embeddingStr := "[" + strings.Join(parts, ",") + "]" if err := db.DB.Exec( "UPDATE agent_decisions SET context_embedding = ? WHERE id = ?", embeddingStr, record.ID, ).Error; err != nil { utils.Warn("CreateAgentDecision: embedding update failed", "id", record.ID, "error", err) } } return utils.Created(c, fiber.Map{"id": record.ID}) } // GET /api/v1/internal/agent-decisions/similar func FindSimilarDecisions(c *fiber.Ctx) error { decisionType := c.Query("decision_type") limit, err := strconv.Atoi(c.Query("limit", "5")) if err != nil || limit < 1 { limit = 5 } type req struct { Embedding []float32 `json:"embedding"` } body := new(req) if err := c.BodyParser(body); err != nil || len(body.Embedding) == 0 { return utils.BadRequest(c, "embedding array is required") } parts := make([]string, len(body.Embedding)) for i, v := range body.Embedding { parts[i] = strconv.FormatFloat(float64(v), 'f', -1, 32) } embeddingStr := "[" + strings.Join(parts, ",") + "]" type row struct { Decision string `json:"decision"` Reasoning string `json:"reasoning"` Outcome *string `json:"outcome"` Distance float64 `json:"distance"` } var results []row if err := db.DB.Raw( `SELECT decision, reasoning, outcome, context_embedding <=> ? AS distance FROM agent_decisions WHERE decision_type = ? AND outcome IS NOT NULL ORDER BY context_embedding <=> ? LIMIT ?`, embeddingStr, decisionType, embeddingStr, limit, ).Scan(&results).Error; err != nil { utils.Error("FindSimilarDecisions: query failed", "error", err) return utils.Internal(c, "failed to query similar decisions") } return utils.List(c, results, int64(len(results))) } // PATCH /api/v1/internal/agent-decisions/:id/outcome func UpdateDecisionOutcome(c *fiber.Ctx) error { id, err := strconv.ParseUint(c.Params("id"), 10, 64) if err != nil { return utils.BadRequest(c, "invalid decision ID") } type req struct { Outcome string `json:"outcome"` } body := new(req) if err := c.BodyParser(body); err != nil { return utils.BadRequest(c, "invalid request body") } if body.Outcome == "" { return utils.BadRequest(c, "outcome is required") } now := time.Now() result := db.DB.Model(&models.AgentDecision{}). Where("id = ?", id). Updates(map[string]interface{}{ "outcome": body.Outcome, "outcome_recorded_at": now, }) if result.Error != nil { utils.Error("UpdateDecisionOutcome: update failed", "id", id, "error", result.Error) return utils.Internal(c, "failed to update outcome") } if result.RowsAffected == 0 { return utils.NotFound(c, "agent decision not found") } return utils.OK(c, fiber.Map{"id": id, "outcome": body.Outcome}) }