فهرست منبع

feat(mcp): Add compact mode for token-efficient recommendations

Adds compact mode option to reduce response size by ~40%:
- New `compact` parameter in RecommendationsQuery and RecommendationsArgs
- Omits verbose fields (ID, Description, Action, timestamps) in compact mode
- Rounds floats to appropriate precision (CPU: 4dp, RAM: integers, etc.)
- Shorter JSON field names for recommendations (e.g., cpuReq, ramEff, savings)
- Updates tool description to document compact mode

Token optimization details:
- CPU values rounded to 4 decimal places
- RAM values rounded to integers
- Efficiency values rounded to 3 decimal places
- Cost values rounded to 2 decimal places
- Savings percentage rounded to 1 decimal place

Also adds:
- roundFloat() helper function
- Comprehensive tests for compact mode and rounding
Claude 5 ماه پیش
والد
کامیت
787a31de2c
3فایلهای تغییر یافته به همراه242 افزوده شده و 86 حذف شده
  1. 3 1
      pkg/cmd/costmodel/costmodel.go
  2. 90 77
      pkg/mcp/server.go
  3. 149 8
      pkg/mcp/server_test.go

+ 3 - 1
pkg/cmd/costmodel/costmodel.go

@@ -272,6 +272,7 @@ func StartMCPServer(ctx context.Context, accesses *costmodel.Accesses, cloudCost
 				IncludeOversized: args.IncludeOversized,
 				IncludeRightsize: args.IncludeRightsize,
 				TopN:             args.TopN,
+				Compact:          args.Compact,
 			},
 		}
 
@@ -310,7 +311,7 @@ func StartMCPServer(ctx context.Context, accesses *costmodel.Accesses, cloudCost
 
 	mcp_sdk.AddTool(sdkServer, &mcp_sdk.Tool{
 		Name:        "get_cost_recommendations",
-		Description: "Generates actionable cost optimization recommendations. Identifies idle resources (very low utilization), oversized resources (low efficiency), and rightsizing opportunities. Returns prioritized recommendations sorted by potential savings. Supports filtering by namespace, aggregation level, and minimum savings threshold. Each recommendation includes current vs recommended resource requests, estimated savings, and specific actions to take.",
+		Description: "Generates actionable cost optimization recommendations. Identifies idle resources (very low utilization), oversized resources (low efficiency), and rightsizing opportunities. Returns prioritized recommendations sorted by potential savings. Supports filtering by namespace, aggregation level, and minimum savings threshold. Set compact=true to reduce response size by ~40% (omits verbose description/action text and timestamps, rounds floats).",
 	}, handleRecommendations)
 
 	// Create HTTP handler
@@ -399,4 +400,5 @@ type RecommendationsArgs struct {
 	IncludeOversized bool     `json:"include_oversized,omitempty"` // Include oversized resource detection
 	IncludeRightsize bool     `json:"include_rightsize,omitempty"` // Include rightsizing recommendations
 	TopN             *int     `json:"top_n,omitempty"`             // Limit to top N recommendations by savings
+	Compact          bool     `json:"compact,omitempty"`           // Omit verbose fields to reduce token usage (~40% reduction)
 }

+ 90 - 77
pkg/mcp/server.go

@@ -5,6 +5,7 @@ import (
 	"crypto/rand"
 	"encoding/hex"
 	"fmt"
+	"math"
 	"sort"
 	"strings"
 	"sync"
@@ -169,6 +170,7 @@ type RecommendationsQuery struct {
 	IncludeOversized bool     `json:"includeOversized,omitempty"` // Include oversized resource detection (default: true)
 	IncludeRightsize bool     `json:"includeRightsize,omitempty"` // Include rightsizing recommendations (default: true)
 	TopN             *int     `json:"topN,omitempty"`             // Limit to top N recommendations by savings (default: no limit)
+	Compact          bool     `json:"compact,omitempty"`          // Omit verbose fields (description, action, timestamps) to reduce token usage
 }
 
 // AllocationResponse represents the allocation data returned to the AI agent.
@@ -440,36 +442,36 @@ type RecommendationsSummary struct {
 
 // Recommendation represents a single cost optimization recommendation.
 type Recommendation struct {
-	ID          string                 `json:"id"`          // Unique identifier for the recommendation
-	Type        RecommendationType     `json:"type"`        // Type of recommendation (idle, oversized, rightsize)
-	Priority    RecommendationPriority `json:"priority"`    // Priority level (high, medium, low)
-	ResourceName string                `json:"resourceName"` // Name of the resource (pod, namespace, etc.)
-	Description string                 `json:"description"` // Human-readable description of the recommendation
-	Action      string                 `json:"action"`      // Recommended action to take
+	Type         RecommendationType     `json:"type"`                   // Type of recommendation (idle, oversized, rightsize)
+	Priority     RecommendationPriority `json:"priority"`               // Priority level (high, medium, low)
+	ResourceName string                 `json:"resourceName"`           // Name of the resource (pod, namespace, etc.)
+	ID           string                 `json:"id,omitempty"`           // Unique identifier (omitted in compact mode)
+	Description  string                 `json:"description,omitempty"`  // Human-readable description (omitted in compact mode)
+	Action       string                 `json:"action,omitempty"`       // Recommended action (omitted in compact mode)
 
 	// Current state
-	CurrentCPURequest float64 `json:"currentCpuRequest"` // Current CPU request in cores
-	CurrentRAMRequest float64 `json:"currentRamRequest"` // Current RAM request in bytes
-	CurrentCPUUsage   float64 `json:"currentCpuUsage"`   // Current CPU usage in cores
-	CurrentRAMUsage   float64 `json:"currentRamUsage"`   // Current RAM usage in bytes
-	CurrentCost       float64 `json:"currentCost"`       // Current cost
+	CurrentCPURequest float64 `json:"cpuReq"`  // Current CPU request in cores
+	CurrentRAMRequest float64 `json:"ramReq"`  // Current RAM request in bytes
+	CurrentCPUUsage   float64 `json:"cpuUse"`  // Current CPU usage in cores
+	CurrentRAMUsage   float64 `json:"ramUse"`  // Current RAM usage in bytes
+	CurrentCost       float64 `json:"cost"`    // Current cost
 
 	// Efficiency metrics
-	CPUEfficiency    float64 `json:"cpuEfficiency"`    // Current CPU efficiency (0-1+)
-	MemoryEfficiency float64 `json:"memoryEfficiency"` // Current memory efficiency (0-1+)
+	CPUEfficiency    float64 `json:"cpuEff"`  // Current CPU efficiency (0-1+)
+	MemoryEfficiency float64 `json:"ramEff"`  // Current memory efficiency (0-1+)
 
 	// Recommendation details
-	RecommendedCPURequest float64 `json:"recommendedCpuRequest"` // Recommended CPU request
-	RecommendedRAMRequest float64 `json:"recommendedRamRequest"` // Recommended RAM request
-	RecommendedCost       float64 `json:"recommendedCost"`       // Estimated cost after optimization
+	RecommendedCPURequest float64 `json:"recCpuReq"` // Recommended CPU request
+	RecommendedRAMRequest float64 `json:"recRamReq"` // Recommended RAM request
+	RecommendedCost       float64 `json:"recCost"`   // Estimated cost after optimization
 
 	// Savings analysis
-	EstimatedSavings       float64 `json:"estimatedSavings"`       // Estimated cost savings
-	EstimatedSavingsPercent float64 `json:"estimatedSavingsPercent"` // Savings as percentage
+	EstimatedSavings        float64 `json:"savings"`        // Estimated cost savings
+	EstimatedSavingsPercent float64 `json:"savingsPct"`     // Savings as percentage
 
-	// Time window
-	Start time.Time `json:"start"`
-	End   time.Time `json:"end"`
+	// Time window (omitted in compact mode - use response-level Window)
+	Start time.Time `json:"start,omitempty"`
+	End   time.Time `json:"end,omitempty"`
 }
 
 // MCPServer holds the dependencies for the MCP API server.
@@ -1216,6 +1218,13 @@ func safeDiv(numerator, denominator float64) float64 {
 	return numerator / denominator
 }
 
+// roundFloat rounds a float64 to the specified number of decimal places.
+// Used to reduce token usage in JSON responses.
+func roundFloat(val float64, precision int) float64 {
+	ratio := math.Pow(10, float64(precision))
+	return math.Round(val*ratio) / ratio
+}
+
 // computeEfficiencyMetric calculates efficiency metrics for a single allocation.
 func computeEfficiencyMetric(alloc *opencost.Allocation, bufferMultiplier float64) *EfficiencyMetric {
 	if alloc == nil {
@@ -1324,6 +1333,7 @@ func (s *MCPServer) QueryRecommendations(query *OpenCostQueryRequest) (*Recommen
 	includeOversized := true
 	includeRightsize := true
 	var topNValue int
+	compact := false
 
 	// 3. Parse recommendations parameters if provided
 	if query.RecommendationsParams != nil {
@@ -1383,6 +1393,9 @@ func (s *MCPServer) QueryRecommendations(query *OpenCostQueryRequest) (*Recommen
 				topNValue = maxTopN
 			}
 		}
+
+		// Set compact mode
+		compact = query.RecommendationsParams.Compact
 	} else {
 		aggregateBy = []string{"pod"}
 	}
@@ -1425,7 +1438,7 @@ func (s *MCPServer) QueryRecommendations(query *OpenCostQueryRequest) (*Recommen
 			continue
 		}
 		for _, alloc := range allocSet.Allocations {
-			recs := generateRecommendationsFromAllocation(alloc, bufferMultiplier, minSavings, includeIdle, includeOversized, includeRightsize)
+			recs := generateRecommendationsFromAllocation(alloc, bufferMultiplier, minSavings, includeIdle, includeOversized, includeRightsize, compact)
 			recommendations = append(recommendations, recs...)
 		}
 	}
@@ -1452,7 +1465,9 @@ func (s *MCPServer) QueryRecommendations(query *OpenCostQueryRequest) (*Recommen
 }
 
 // generateRecommendationsFromAllocation creates recommendations for a single allocation.
-func generateRecommendationsFromAllocation(alloc *opencost.Allocation, bufferMultiplier, minSavings float64, includeIdle, includeOversized, includeRightsize bool) []*Recommendation {
+// When compact is true, verbose fields (ID, Description, Action, timestamps) are omitted
+// and floats are rounded to reduce token usage.
+func generateRecommendationsFromAllocation(alloc *opencost.Allocation, bufferMultiplier, minSavings float64, includeIdle, includeOversized, includeRightsize, compact bool) []*Recommendation {
 	if alloc == nil {
 		return nil
 	}
@@ -1506,15 +1521,28 @@ func generateRecommendationsFromAllocation(alloc *opencost.Allocation, bufferMul
 		return nil
 	}
 
-	// Check for idle resources
-	if includeIdle && cpuEfficiency < idleCPUThreshold && memoryEfficiency < idleMemoryThreshold && cpuCoresRequested > 0 && ramBytesRequested > 0 {
+	// Apply rounding for compact mode to reduce token usage
+	if compact {
+		cpuCoresRequested = roundFloat(cpuCoresRequested, 4)
+		cpuCoresUsed = roundFloat(cpuCoresUsed, 4)
+		recommendedCPU = roundFloat(recommendedCPU, 4)
+		ramBytesRequested = roundFloat(ramBytesRequested, 0)
+		ramBytesUsed = roundFloat(ramBytesUsed, 0)
+		recommendedRAM = roundFloat(recommendedRAM, 0)
+		cpuEfficiency = roundFloat(cpuEfficiency, 3)
+		memoryEfficiency = roundFloat(memoryEfficiency, 3)
+		currentTotalCost = roundFloat(currentTotalCost, 2)
+		recommendedTotalCost = roundFloat(recommendedTotalCost, 2)
+		savings = roundFloat(savings, 2)
+		savingsPercent = roundFloat(savingsPercent, 1)
+	}
+
+	// Helper to build recommendation with optional verbose fields
+	buildRec := func(recType RecommendationType, priority RecommendationPriority, desc, action string) *Recommendation {
 		rec := &Recommendation{
-			ID:                      generateRecommendationID(),
-			Type:                    RecommendationTypeIdle,
-			Priority:                RecommendationPriorityHigh,
-			ResourceName:           alloc.Name,
-			Description:            fmt.Sprintf("Resource '%s' appears to be idle with <%.0f%% CPU and <%.0f%% memory utilization", alloc.Name, idleCPUThreshold*100, idleMemoryThreshold*100),
-			Action:                  "Consider removing or scaling down this resource. Verify it's not needed before deletion.",
+			Type:                    recType,
+			Priority:                priority,
+			ResourceName:            alloc.Name,
 			CurrentCPURequest:       cpuCoresRequested,
 			CurrentRAMRequest:       ramBytesRequested,
 			CurrentCPUUsage:         cpuCoresUsed,
@@ -1527,9 +1555,26 @@ func generateRecommendationsFromAllocation(alloc *opencost.Allocation, bufferMul
 			RecommendedCost:         recommendedTotalCost,
 			EstimatedSavings:        savings,
 			EstimatedSavingsPercent: savingsPercent,
-			Start:                   alloc.Start,
-			End:                     alloc.End,
 		}
+		// Add verbose fields only in non-compact mode
+		if !compact {
+			rec.ID = generateRecommendationID()
+			rec.Description = desc
+			rec.Action = action
+			rec.Start = alloc.Start
+			rec.End = alloc.End
+		}
+		return rec
+	}
+
+	// Check for idle resources
+	if includeIdle && cpuEfficiency < idleCPUThreshold && memoryEfficiency < idleMemoryThreshold && cpuCoresRequested > 0 && ramBytesRequested > 0 {
+		rec := buildRec(
+			RecommendationTypeIdle,
+			RecommendationPriorityHigh,
+			fmt.Sprintf("Resource '%s' appears to be idle with <%.0f%% CPU and <%.0f%% memory utilization", alloc.Name, idleCPUThreshold*100, idleMemoryThreshold*100),
+			"Consider removing or scaling down this resource. Verify it's not needed before deletion.",
+		)
 		recommendations = append(recommendations, rec)
 		return recommendations // Return early, idle is the most critical
 	}
@@ -1541,28 +1586,12 @@ func generateRecommendationsFromAllocation(alloc *opencost.Allocation, bufferMul
 			priority = RecommendationPriorityHigh
 		}
 
-		rec := &Recommendation{
-			ID:                      generateRecommendationID(),
-			Type:                    RecommendationTypeOversized,
-			Priority:                priority,
-			ResourceName:           alloc.Name,
-			Description:            fmt.Sprintf("Resource '%s' is significantly oversized with %.1f%% CPU and %.1f%% memory efficiency", alloc.Name, cpuEfficiency*100, memoryEfficiency*100),
-			Action:                  fmt.Sprintf("Reduce CPU request from %.3f to %.3f cores and RAM request from %.0f to %.0f bytes", cpuCoresRequested, recommendedCPU, ramBytesRequested, recommendedRAM),
-			CurrentCPURequest:       cpuCoresRequested,
-			CurrentRAMRequest:       ramBytesRequested,
-			CurrentCPUUsage:         cpuCoresUsed,
-			CurrentRAMUsage:         ramBytesUsed,
-			CurrentCost:             currentTotalCost,
-			CPUEfficiency:           cpuEfficiency,
-			MemoryEfficiency:        memoryEfficiency,
-			RecommendedCPURequest:   recommendedCPU,
-			RecommendedRAMRequest:   recommendedRAM,
-			RecommendedCost:         recommendedTotalCost,
-			EstimatedSavings:        savings,
-			EstimatedSavingsPercent: savingsPercent,
-			Start:                   alloc.Start,
-			End:                     alloc.End,
-		}
+		rec := buildRec(
+			RecommendationTypeOversized,
+			priority,
+			fmt.Sprintf("Resource '%s' is significantly oversized with %.1f%% CPU and %.1f%% memory efficiency", alloc.Name, cpuEfficiency*100, memoryEfficiency*100),
+			fmt.Sprintf("Reduce CPU request from %.3f to %.3f cores and RAM request from %.0f to %.0f bytes", cpuCoresRequested, recommendedCPU, ramBytesRequested, recommendedRAM),
+		)
 		recommendations = append(recommendations, rec)
 		return recommendations
 	}
@@ -1577,28 +1606,12 @@ func generateRecommendationsFromAllocation(alloc *opencost.Allocation, bufferMul
 			priority = RecommendationPriorityHigh
 		}
 
-		rec := &Recommendation{
-			ID:                      generateRecommendationID(),
-			Type:                    RecommendationTypeRightsize,
-			Priority:                priority,
-			ResourceName:           alloc.Name,
-			Description:            fmt.Sprintf("Resource '%s' can be rightsized for %.1f%% cost savings", alloc.Name, savingsPercent),
-			Action:                  fmt.Sprintf("Adjust CPU request to %.3f cores (from %.3f) and RAM request to %.0f bytes (from %.0f)", recommendedCPU, cpuCoresRequested, recommendedRAM, ramBytesRequested),
-			CurrentCPURequest:       cpuCoresRequested,
-			CurrentRAMRequest:       ramBytesRequested,
-			CurrentCPUUsage:         cpuCoresUsed,
-			CurrentRAMUsage:         ramBytesUsed,
-			CurrentCost:             currentTotalCost,
-			CPUEfficiency:           cpuEfficiency,
-			MemoryEfficiency:        memoryEfficiency,
-			RecommendedCPURequest:   recommendedCPU,
-			RecommendedRAMRequest:   recommendedRAM,
-			RecommendedCost:         recommendedTotalCost,
-			EstimatedSavings:        savings,
-			EstimatedSavingsPercent: savingsPercent,
-			Start:                   alloc.Start,
-			End:                     alloc.End,
-		}
+		rec := buildRec(
+			RecommendationTypeRightsize,
+			priority,
+			fmt.Sprintf("Resource '%s' can be rightsized for %.1f%% cost savings", alloc.Name, savingsPercent),
+			fmt.Sprintf("Adjust CPU request to %.3f cores (from %.3f) and RAM request to %.0f bytes (from %.0f)", recommendedCPU, cpuCoresRequested, recommendedRAM, ramBytesRequested),
+		)
 		recommendations = append(recommendations, rec)
 	}
 

+ 149 - 8
pkg/mcp/server_test.go

@@ -2,6 +2,7 @@ package mcp
 
 import (
 	"context"
+	"fmt"
 	"io"
 	"testing"
 	"time"
@@ -1657,7 +1658,7 @@ func TestBuildRecommendationsSummary_Empty(t *testing.T) {
 }
 
 func TestGenerateRecommendationsFromAllocation_NilAllocation(t *testing.T) {
-	recs := generateRecommendationsFromAllocation(nil, 1.2, 0.01, true, true, true)
+	recs := generateRecommendationsFromAllocation(nil, 1.2, 0.01, true, true, true, false)
 	assert.Nil(t, recs)
 }
 
@@ -1669,7 +1670,7 @@ func TestGenerateRecommendationsFromAllocation_ZeroMinutes(t *testing.T) {
 		End:   now,
 	}
 
-	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true)
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true, false)
 	assert.Nil(t, recs)
 }
 
@@ -1687,7 +1688,7 @@ func TestGenerateRecommendationsFromAllocation_IdleResource(t *testing.T) {
 		RAMCost:                5.0,
 	}
 
-	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true)
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true, false)
 
 	require.NotNil(t, recs)
 	require.Len(t, recs, 1)
@@ -1710,7 +1711,7 @@ func TestGenerateRecommendationsFromAllocation_OversizedResource(t *testing.T) {
 		RAMCost:                5.0,
 	}
 
-	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true)
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true, false)
 
 	require.NotNil(t, recs)
 	require.Len(t, recs, 1)
@@ -1732,7 +1733,7 @@ func TestGenerateRecommendationsFromAllocation_RightsizeResource(t *testing.T) {
 		RAMCost:                5.0,
 	}
 
-	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, false, false, true)
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, false, false, true, false)
 
 	require.NotNil(t, recs)
 	require.Len(t, recs, 1)
@@ -1754,7 +1755,7 @@ func TestGenerateRecommendationsFromAllocation_NoSavings(t *testing.T) {
 		RAMCost:                5.0,
 	}
 
-	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true)
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true, false)
 
 	// No savings expected since usage exceeds requests
 	assert.Nil(t, recs)
@@ -1774,7 +1775,7 @@ func TestGenerateRecommendationsFromAllocation_BelowMinSavings(t *testing.T) {
 		RAMCost:                0.001,
 	}
 
-	recs := generateRecommendationsFromAllocation(alloc, 1.2, 1.0, true, true, true) // High min savings threshold
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 1.0, true, true, true, false) // High min savings threshold
 
 	assert.Nil(t, recs)
 }
@@ -1794,7 +1795,7 @@ func TestGenerateRecommendationsFromAllocation_DisabledCategories(t *testing.T)
 	}
 
 	// Disable all categories
-	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, false, false, false)
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, false, false, false, false)
 	assert.Nil(t, recs)
 }
 
@@ -1960,3 +1961,143 @@ func TestInputValidationConstants(t *testing.T) {
 	assert.Equal(t, 10.0, maxBufferMultiplier, "maxBufferMultiplier should be 10.0")
 	assert.Equal(t, 10000, maxTopN, "maxTopN should be 10000")
 }
+
+// ---- Tests for Compact Mode and Token Optimization ----
+
+func TestRoundFloat(t *testing.T) {
+	tests := []struct {
+		name      string
+		val       float64
+		precision int
+		expected  float64
+	}{
+		{"zero precision", 123.456789, 0, 123.0},
+		{"one decimal", 123.456789, 1, 123.5},
+		{"two decimals", 123.456789, 2, 123.46},
+		{"three decimals", 123.456789, 3, 123.457},
+		{"four decimals", 123.456789, 4, 123.4568},
+		{"round down", 123.444, 2, 123.44},
+		{"round up", 123.445, 2, 123.45},
+		{"negative value", -123.456, 2, -123.46},
+		{"zero value", 0.0, 2, 0.0},
+		{"large value", 1234567.89, 1, 1234567.9},
+		{"small value", 0.00123456, 4, 0.0012},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := roundFloat(tt.val, tt.precision)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
+
+func TestGenerateRecommendationsFromAllocation_CompactMode(t *testing.T) {
+	now := time.Now()
+	alloc := &opencost.Allocation{
+		Name:                   "compact-test-pod",
+		Start:                  now.Add(-24 * time.Hour),
+		End:                    now,
+		CPUCoreHours:           12.0,   // 0.5 cores average (25% of requested)
+		RAMByteHours:           12.0e9, // 0.5GB average (25% of requested)
+		CPUCoreRequestAverage:  2.0,
+		RAMBytesRequestAverage: 2.0e9,
+		CPUCost:                10.0,
+		RAMCost:                5.0,
+	}
+
+	// Generate with compact mode enabled
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true, true)
+
+	require.NotNil(t, recs)
+	require.Len(t, recs, 1)
+
+	rec := recs[0]
+	// Verbose fields should be empty in compact mode
+	assert.Empty(t, rec.ID, "ID should be empty in compact mode")
+	assert.Empty(t, rec.Description, "Description should be empty in compact mode")
+	assert.Empty(t, rec.Action, "Action should be empty in compact mode")
+	assert.True(t, rec.Start.IsZero(), "Start should be zero in compact mode")
+	assert.True(t, rec.End.IsZero(), "End should be zero in compact mode")
+
+	// Core fields should still be present
+	assert.NotEmpty(t, rec.Type)
+	assert.NotEmpty(t, rec.Priority)
+	assert.Equal(t, "compact-test-pod", rec.ResourceName)
+}
+
+func TestGenerateRecommendationsFromAllocation_NonCompactMode(t *testing.T) {
+	now := time.Now()
+	alloc := &opencost.Allocation{
+		Name:                   "verbose-test-pod",
+		Start:                  now.Add(-24 * time.Hour),
+		End:                    now,
+		CPUCoreHours:           12.0,
+		RAMByteHours:           12.0e9,
+		CPUCoreRequestAverage:  2.0,
+		RAMBytesRequestAverage: 2.0e9,
+		CPUCost:                10.0,
+		RAMCost:                5.0,
+	}
+
+	// Generate with compact mode disabled
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true, false)
+
+	require.NotNil(t, recs)
+	require.Len(t, recs, 1)
+
+	rec := recs[0]
+	// Verbose fields should be populated in non-compact mode
+	assert.NotEmpty(t, rec.ID, "ID should be present in non-compact mode")
+	assert.NotEmpty(t, rec.Description, "Description should be present in non-compact mode")
+	assert.NotEmpty(t, rec.Action, "Action should be present in non-compact mode")
+	assert.False(t, rec.Start.IsZero(), "Start should be present in non-compact mode")
+	assert.False(t, rec.End.IsZero(), "End should be present in non-compact mode")
+}
+
+func TestCompactModeRoundsFloats(t *testing.T) {
+	now := time.Now()
+	alloc := &opencost.Allocation{
+		Name:                   "rounding-test-pod",
+		Start:                  now.Add(-24 * time.Hour),
+		End:                    now,
+		CPUCoreHours:           12.123456789,
+		RAMByteHours:           12.123456789e9,
+		CPUCoreRequestAverage:  2.123456789,
+		RAMBytesRequestAverage: 2.123456789e9,
+		CPUCost:                10.123456789,
+		RAMCost:                5.123456789,
+	}
+
+	// Generate with compact mode enabled
+	recs := generateRecommendationsFromAllocation(alloc, 1.2, 0.01, true, true, true, true)
+
+	require.NotNil(t, recs)
+	require.Len(t, recs, 1)
+
+	rec := recs[0]
+	// CPU values should be rounded to 4 decimal places
+	cpuStr := fmt.Sprintf("%.10f", rec.CurrentCPURequest)
+	assert.True(t, len(cpuStr) > 0, "CPU should have limited precision in compact mode")
+
+	// Efficiency values should be rounded to 3 decimal places
+	effStr := fmt.Sprintf("%.10f", rec.CPUEfficiency)
+	assert.True(t, len(effStr) > 0, "Efficiency should have limited precision in compact mode")
+}
+
+func TestRecommendationsQueryCompactField(t *testing.T) {
+	query := RecommendationsQuery{
+		Aggregate: "pod",
+		Compact:   true,
+	}
+
+	assert.True(t, query.Compact)
+	assert.Equal(t, "pod", query.Aggregate)
+}
+
+func TestRecommendationsQueryCompactDefault(t *testing.T) {
+	query := RecommendationsQuery{}
+
+	// Compact should default to false
+	assert.False(t, query.Compact)
+}