Add cost service for per-VIN cost estimation

- Estimates cloud vs on-prem costs per active vehicle
- Queries feature_table_last_shard from ClickHouse (lightweight)
- 85% savings estimate with on-prem (hardware only)
- Deployed to cec-prd-cluster-1 (internal only)
- Text report endpoint at /cost/report
This commit is contained in:
Chris Rai
2026-01-31 21:29:59 -05:00
parent 1a890d8940
commit 3dccf80a72
8 changed files with 289 additions and 47 deletions

View File

@@ -2,6 +2,7 @@ package handlers
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
@@ -185,3 +186,82 @@ func respondJSON(w http.ResponseWriter, data interface{}) {
logger.Error().Err(err).Msg("Failed to encode JSON response")
}
}
// GetReport returns a plain text cost report
// GET /cost/report
func GetReport(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
to := time.Now()
from := to.AddDate(0, -1, 0) // Last month
summary, err := services.GetFleetCostSummary(from, to, 10)
if err != nil {
logger.Error().Err(err).Msg("Failed to get report data")
http.Error(w, "Failed to get report data", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
report := `
╔══════════════════════════════════════════════════════════════════╗
║ COST SERVICE REPORT ║
╠══════════════════════════════════════════════════════════════════╣
║ Period: %s to %s
╠══════════════════════════════════════════════════════════════════╣
║ FLEET OVERVIEW ║
║ ─────────────────────────────────────────────────────────────── ║
║ Active Vehicles: %d
║ Cloud Cost: $%.2f
║ On-Prem Cost: $%.2f
║ Savings: $%.2f (%.1f%%)
╠══════════════════════════════════════════════════════════════════╣
║ COST RATES ║
║ ─────────────────────────────────────────────────────────────── ║
║ Cloud: CPU $%.3f/core-hr Memory $%.4f/GB-hr
║ On-Prem: CPU $%.3f/core-hr Memory $%.4f/GB-hr
╠══════════════════════════════════════════════════════════════════╣
║ ANNUAL PROJECTION (based on current usage) ║
║ ─────────────────────────────────────────────────────────────── ║
║ Cloud Annual: $%.2f
║ On-Prem Annual: $%.2f
║ Annual Savings: $%.2f
╚══════════════════════════════════════════════════════════════════╝
`
annualCloud := summary.TotalCloudCost * 12
annualOnprem := summary.TotalOnpremCost * 12
annualSavings := annualCloud - annualOnprem
fmt.Fprintf(w, report,
from.Format("2006-01-02"), to.Format("2006-01-02"),
summary.VehicleCount,
summary.TotalCloudCost,
summary.TotalOnpremCost,
summary.TotalSavings, summary.SavingsPercent,
services.CloudCPUPerCoreHour, services.CloudMemoryPerGBHour,
services.OnpremCPUPerCoreHour, services.OnpremMemoryPerGBHour,
annualCloud, annualOnprem, annualSavings,
)
// Add top cost VINs if any
if len(summary.TopCostVins) > 0 {
fmt.Fprintf(w, "\nTOP COST VEHICLES:\n")
fmt.Fprintf(w, "%-20s %12s %12s %10s\n", "VIN", "Cloud $", "On-Prem $", "Savings %")
fmt.Fprintf(w, "%-20s %12s %12s %10s\n", "───────────────────", "──────────", "──────────", "────────")
for _, v := range summary.TopCostVins {
fmt.Fprintf(w, "%-20s %12.2f %12.2f %9.1f%%\n",
truncateVIN(v.VIN), v.TotalCloudCost, v.TotalOnpremCost, v.SavingsPercent)
}
}
}
func truncateVIN(vin string) string {
if len(vin) > 20 {
return vin[:17] + "..."
}
return vin
}