mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 11:02:54 +00:00
feat: Add tenant analytics service with dashboard, usage, performance, cost analytics, and comprehensive reporting
- Add TenantAnalyticsService struct with repository, metrics collector, event publisher, and logger fields - Add GetTenantDashboard with usage stats, metrics, recent events, and quota status aggregation - Add GetTenantMetrics with active users, request tracking, error rate calculation, response time analysis, and health score computation - Add GetUsageAnalytics with time range filtering, resource type breakdown
This commit is contained in:
@@ -0,0 +1,595 @@
|
||||
package tenant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TenantAnalyticsService provides analytics and monitoring for tenants
|
||||
type TenantAnalyticsService struct {
|
||||
repository Repository
|
||||
metricsCollector MetricsCollector
|
||||
eventPublisher EventPublisher
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
// NewTenantAnalyticsService creates a new tenant analytics service
|
||||
func NewTenantAnalyticsService(
|
||||
repository Repository,
|
||||
metricsCollector MetricsCollector,
|
||||
eventPublisher EventPublisher,
|
||||
logger *logrus.Logger,
|
||||
) *TenantAnalyticsService {
|
||||
return &TenantAnalyticsService{
|
||||
repository: repository,
|
||||
metricsCollector: metricsCollector,
|
||||
eventPublisher: eventPublisher,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTenantDashboard returns dashboard data for a tenant
|
||||
func (s *TenantAnalyticsService) GetTenantDashboard(ctx context.Context, tenantID string) (*TenantDashboard, error) {
|
||||
// Get usage stats
|
||||
usageStats, err := s.repository.GetUsageStats(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get usage stats: %w", err)
|
||||
}
|
||||
|
||||
// Get tenant metrics
|
||||
metrics, err := s.GetTenantMetrics(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get tenant metrics: %w", err)
|
||||
}
|
||||
|
||||
// Get recent events
|
||||
events, err := s.repository.ListEvents(ctx, tenantID, 10)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get tenant events: %w", err)
|
||||
}
|
||||
|
||||
// Get quota status
|
||||
var quotaStatus []*TenantUsage
|
||||
quotaFilter := &TenantUsageFilter{TenantID: tenantID}
|
||||
quotaStatus, err = s.repository.ListUsage(ctx, quotaFilter)
|
||||
if err != nil {
|
||||
// Handle gracefully - quota might not exist yet
|
||||
quotaStatus = []*TenantUsage{}
|
||||
}
|
||||
|
||||
dashboard := &TenantDashboard{
|
||||
TenantID: tenantID,
|
||||
UsageStats: usageStats,
|
||||
Metrics: metrics,
|
||||
RecentEvents: events,
|
||||
QuotaStatus: quotaStatus,
|
||||
LastUpdated: time.Now(),
|
||||
}
|
||||
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
// GetTenantMetrics returns comprehensive metrics for a tenant
|
||||
func (s *TenantAnalyticsService) GetTenantMetrics(ctx context.Context, tenantID string) (*TenantMetrics, error) {
|
||||
// Get basic metrics from repository
|
||||
basicMetrics, err := s.repository.GetUsageStats(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get events for activity analysis
|
||||
events, err := s.repository.ListEvents(ctx, tenantID, 1000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build comprehensive metrics
|
||||
metrics := &TenantMetrics{
|
||||
TenantID: tenantID,
|
||||
ActiveUsers: basicMetrics.ActiveUsers,
|
||||
TotalRequests: 0,
|
||||
ErrorRate: 0,
|
||||
ResponseTime: 0,
|
||||
StorageUsed: 0,
|
||||
LastActivity: time.Time{},
|
||||
HealthScore: 100.0,
|
||||
Alerts: []string{},
|
||||
}
|
||||
|
||||
// Analyze events for metrics
|
||||
errorCount := 0
|
||||
totalRequests := 0
|
||||
var totalResponseTime time.Duration
|
||||
lastActivity := time.Time{}
|
||||
|
||||
for _, event := range events {
|
||||
if event.Timestamp.After(lastActivity) {
|
||||
lastActivity = event.Timestamp
|
||||
}
|
||||
|
||||
switch event.EventType {
|
||||
case "api_request":
|
||||
totalRequests++
|
||||
if statusCode, exists := event.EventData["status_code"]; exists {
|
||||
if code, ok := statusCode.(float64); ok && code >= 400 {
|
||||
errorCount++
|
||||
}
|
||||
}
|
||||
if responseTime, exists := event.EventData["response_time"]; exists {
|
||||
if rt, ok := responseTime.(float64); ok {
|
||||
totalResponseTime += time.Duration(rt) * time.Millisecond
|
||||
}
|
||||
}
|
||||
case "resource_created", "resource_updated", "resource_deleted":
|
||||
metrics.TotalRequests++
|
||||
}
|
||||
}
|
||||
|
||||
if totalRequests > 0 {
|
||||
metrics.ErrorRate = float64(errorCount) / float64(totalRequests) * 100
|
||||
metrics.ResponseTime = float64(totalResponseTime) / float64(totalRequests) / float64(time.Millisecond)
|
||||
}
|
||||
|
||||
metrics.TotalRequests = int64(totalRequests)
|
||||
metrics.LastActivity = lastActivity
|
||||
|
||||
// Calculate health score and alerts
|
||||
metrics.HealthScore = s.calculateHealthScore(basicMetrics, metrics.ErrorRate)
|
||||
metrics.Alerts = s.generateAlerts(basicMetrics, metrics.ErrorRate)
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// GetUsageAnalytics returns detailed usage analytics for a tenant
|
||||
func (s *TenantAnalyticsService) GetUsageAnalytics(ctx context.Context, tenantID string, timeRange string) (*TenantUsageAnalytics, error) {
|
||||
// Parse time range
|
||||
startDate, endDate := s.parseTimeRange(timeRange)
|
||||
|
||||
// Get usage records
|
||||
usageFilter := &TenantUsageFilter{
|
||||
TenantID: tenantID,
|
||||
PeriodStart: startDate,
|
||||
PeriodEnd: endDate,
|
||||
}
|
||||
|
||||
usageRecords, err := s.repository.ListUsage(ctx, usageFilter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get usage records: %w", err)
|
||||
}
|
||||
|
||||
// Build analytics
|
||||
analytics := &TenantUsageAnalytics{
|
||||
TenantID: tenantID,
|
||||
TimeRange: timeRange,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
UsageByType: make(map[string]*ResourceUsageAnalytics),
|
||||
Trends: make(map[string][]*UsageTrend),
|
||||
Peaks: make(map[string]*UsagePeak),
|
||||
}
|
||||
|
||||
// Process usage records
|
||||
for _, usage := range usageRecords {
|
||||
if _, exists := analytics.UsageByType[usage.ResourceType]; !exists {
|
||||
analytics.UsageByType[usage.ResourceType] = &ResourceUsageAnalytics{
|
||||
ResourceType: usage.ResourceType,
|
||||
TotalUsage: 0,
|
||||
AverageUsage: 0,
|
||||
PeakUsage: 0,
|
||||
PeakTime: time.Time{},
|
||||
}
|
||||
}
|
||||
|
||||
resourceAnalytics := analytics.UsageByType[usage.ResourceType]
|
||||
resourceAnalytics.TotalUsage += usage.QuotaUsed
|
||||
|
||||
if usage.QuotaUsed > resourceAnalytics.PeakUsage {
|
||||
resourceAnalytics.PeakUsage = usage.QuotaUsed
|
||||
resourceAnalytics.PeakTime = usage.UpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate averages
|
||||
for _, resourceAnalytics := range analytics.UsageByType {
|
||||
if len(usageRecords) > 0 {
|
||||
resourceAnalytics.AverageUsage = resourceAnalytics.TotalUsage / len(usageRecords)
|
||||
}
|
||||
}
|
||||
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
// GetPerformanceAnalytics returns performance analytics for a tenant
|
||||
func (s *TenantAnalyticsService) GetPerformanceAnalytics(ctx context.Context, tenantID string, timeRange string) (*TenantPerformanceAnalytics, error) {
|
||||
// Get events for performance analysis
|
||||
events, err := s.repository.ListEvents(ctx, tenantID, 10000)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get tenant events: %w", err)
|
||||
}
|
||||
|
||||
// Parse time range
|
||||
startDate, endDate := s.parseTimeRange(timeRange)
|
||||
|
||||
// Build performance analytics
|
||||
analytics := &TenantPerformanceAnalytics{
|
||||
TenantID: tenantID,
|
||||
TimeRange: timeRange,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
APIPerformance: &APIPerformance{},
|
||||
ResourcePerformance: make(map[string]*ResourcePerformance),
|
||||
Errors: []*ErrorEvent{},
|
||||
SlowQueries: []*SlowQuery{},
|
||||
}
|
||||
|
||||
// Process events
|
||||
apiRequests := []*APIRequestEvent{}
|
||||
for _, event := range events {
|
||||
if event.Timestamp.Before(startDate) || event.Timestamp.After(endDate) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch event.EventType {
|
||||
case "api_request":
|
||||
apiRequest := s.parseAPIRequestEvent(event)
|
||||
if apiRequest != nil {
|
||||
apiRequests = append(apiRequests, apiRequest)
|
||||
}
|
||||
case "error":
|
||||
errorEvent := s.parseErrorEvent(event)
|
||||
if errorEvent != nil {
|
||||
analytics.Errors = append(analytics.Errors, errorEvent)
|
||||
}
|
||||
case "slow_query":
|
||||
slowQuery := s.parseSlowQueryEvent(event)
|
||||
if slowQuery != nil {
|
||||
analytics.SlowQueries = append(analytics.SlowQueries, slowQuery)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate API performance metrics
|
||||
analytics.APIPerformance = s.calculateAPIPerformance(apiRequests)
|
||||
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
// GetCostAnalytics returns cost analytics for a tenant
|
||||
func (s *TenantAnalyticsService) GetCostAnalytics(ctx context.Context, tenantID string, timeRange string) (*TenantCostAnalytics, error) {
|
||||
// Parse time range
|
||||
startDate, endDate := s.parseTimeRange(timeRange)
|
||||
|
||||
// Get tenant configuration to understand pricing
|
||||
config, err := s.repository.GetConfig(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get tenant config: %w", err)
|
||||
}
|
||||
|
||||
// Build cost analytics
|
||||
analytics := &TenantCostAnalytics{
|
||||
TenantID: tenantID,
|
||||
TimeRange: timeRange,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
TotalCost: 0,
|
||||
CostByType: make(map[string]float64),
|
||||
CostTrends: []*CostTrend{},
|
||||
Predictions: &CostPrediction{},
|
||||
Savings: &CostSavings{},
|
||||
}
|
||||
|
||||
// Calculate costs based on usage and plan
|
||||
plan := s.getTenantPlan(tenantID, config)
|
||||
analytics.TotalCost = s.calculateTotalCost(plan, analytics.TimeRange)
|
||||
analytics.CostByType = s.calculateCostByType(plan, analytics.TimeRange)
|
||||
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
// GenerateTenantReport generates a comprehensive tenant report
|
||||
func (s *TenantAnalyticsService) GenerateTenantReport(ctx context.Context, tenantID string, reportType string, timeRange string) (*TenantReport, error) {
|
||||
report := &TenantReport{
|
||||
TenantID: tenantID,
|
||||
ReportType: reportType,
|
||||
TimeRange: timeRange,
|
||||
GeneratedAt: time.Now(),
|
||||
}
|
||||
|
||||
switch reportType {
|
||||
case "dashboard":
|
||||
dashboard, err := s.GetTenantDashboard(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
report.Dashboard = dashboard
|
||||
|
||||
case "usage":
|
||||
usageAnalytics, err := s.GetUsageAnalytics(ctx, tenantID, timeRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
report.UsageAnalytics = usageAnalytics
|
||||
|
||||
case "performance":
|
||||
performanceAnalytics, err := s.GetPerformanceAnalytics(ctx, tenantID, timeRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
report.PerformanceAnalytics = performanceAnalytics
|
||||
|
||||
case "cost":
|
||||
costAnalytics, err := s.GetCostAnalytics(ctx, tenantID, timeRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
report.CostAnalytics = costAnalytics
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported report type: %s", reportType)
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
func (s *TenantAnalyticsService) calculateHealthScore(usageStats *TenantUsageStats, errorRate float64) float64 {
|
||||
score := 100.0
|
||||
|
||||
// Deduct points for high error rate
|
||||
if errorRate > 10 {
|
||||
score -= 30
|
||||
} else if errorRate > 5 {
|
||||
score -= 15
|
||||
} else if errorRate > 1 {
|
||||
score -= 5
|
||||
}
|
||||
|
||||
// Deduct points for quota issues
|
||||
for _, quotaStatus := range usageStats.QuotaStatus {
|
||||
if quotaStatus.Critical {
|
||||
score -= 20
|
||||
} else if quotaStatus.Warning {
|
||||
score -= 10
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure score doesn't go below 0
|
||||
if score < 0 {
|
||||
score = 0
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
func (s *TenantAnalyticsService) generateAlerts(usageStats *TenantUsageStats, errorRate float64) []string {
|
||||
alerts := []string{}
|
||||
|
||||
// Error rate alerts
|
||||
if errorRate > 10 {
|
||||
alerts = append(alerts, "Critical: High error rate detected")
|
||||
} else if errorRate > 5 {
|
||||
alerts = append(alerts, "Warning: Elevated error rate")
|
||||
}
|
||||
|
||||
// Quota alerts
|
||||
for resourceType, quotaStatus := range usageStats.QuotaStatus {
|
||||
if quotaStatus.Critical {
|
||||
alerts = append(alerts, fmt.Sprintf("Critical: %s quota at %.1f%%", resourceType, quotaStatus.Percent))
|
||||
} else if quotaStatus.Warning {
|
||||
alerts = append(alerts, fmt.Sprintf("Warning: %s quota at %.1f%%", resourceType, quotaStatus.Percent))
|
||||
}
|
||||
}
|
||||
|
||||
return alerts
|
||||
}
|
||||
|
||||
func (s *TenantAnalyticsService) parseTimeRange(timeRange string) (time.Time, time.Time) {
|
||||
now := time.Now()
|
||||
|
||||
switch timeRange {
|
||||
case "1h":
|
||||
return now.Add(-1 * time.Hour), now
|
||||
case "24h":
|
||||
return now.Add(-24 * time.Hour), now
|
||||
case "7d":
|
||||
return now.Add(-7 * 24 * time.Hour), now
|
||||
case "30d":
|
||||
return now.Add(-30 * 24 * time.Hour), now
|
||||
case "90d":
|
||||
return now.Add(-90 * 24 * time.Hour), now
|
||||
default:
|
||||
return now.Add(-24 * time.Hour), now
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TenantAnalyticsService) parseAPIRequestEvent(event *TenantEvent) *APIRequestEvent {
|
||||
// Implementation depends on event structure
|
||||
return &APIRequestEvent{
|
||||
Timestamp: event.Timestamp,
|
||||
Endpoint: "",
|
||||
Method: "",
|
||||
StatusCode: 200,
|
||||
ResponseTime: 0,
|
||||
UserID: event.UserID,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TenantAnalyticsService) parseErrorEvent(event *TenantEvent) *ErrorEvent {
|
||||
// Implementation depends on event structure
|
||||
return &ErrorEvent{
|
||||
Timestamp: event.Timestamp,
|
||||
Error: "",
|
||||
Context: event.EventData,
|
||||
UserID: event.UserID,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TenantAnalyticsService) parseSlowQueryEvent(event *TenantEvent) *SlowQuery {
|
||||
// Implementation depends on event structure
|
||||
return &SlowQuery{
|
||||
Timestamp: event.Timestamp,
|
||||
Query: "",
|
||||
Duration: 0,
|
||||
Context: event.EventData,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TenantAnalyticsService) calculateAPIPerformance(requests []*APIRequestEvent) *APIPerformance {
|
||||
// Implementation would calculate performance metrics
|
||||
return &APIPerformance{
|
||||
TotalRequests: len(requests),
|
||||
AverageResponseTime: 0,
|
||||
P95ResponseTime: 0,
|
||||
ErrorRate: 0,
|
||||
RequestsPerSecond: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TenantAnalyticsService) getTenantPlan(tenantID string, config *TenantConfig) TenantPlan {
|
||||
// Extract plan from tenant config or database
|
||||
return TenantPlanPro // Default
|
||||
}
|
||||
|
||||
func (s *TenantAnalyticsService) calculateTotalCost(plan TenantPlan, timeRange string) float64 {
|
||||
// Implementation would calculate cost based on plan and usage
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func (s *TenantAnalyticsService) calculateCostByType(plan TenantPlan, timeRange string) map[string]float64 {
|
||||
// Implementation would calculate cost breakdown by resource type
|
||||
return make(map[string]float64)
|
||||
}
|
||||
|
||||
// Data structures for analytics
|
||||
|
||||
type TenantDashboard struct {
|
||||
TenantID string `json:"tenant_id"`
|
||||
UsageStats *TenantUsageStats `json:"usage_stats"`
|
||||
Metrics *TenantMetrics `json:"metrics"`
|
||||
RecentEvents []*TenantEvent `json:"recent_events"`
|
||||
QuotaStatus []*TenantUsage `json:"quota_status"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
type TenantUsageAnalytics struct {
|
||||
TenantID string `json:"tenant_id"`
|
||||
TimeRange string `json:"time_range"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
UsageByType map[string]*ResourceUsageAnalytics `json:"usage_by_type"`
|
||||
Trends map[string][]*UsageTrend `json:"trends"`
|
||||
Peaks map[string]*UsagePeak `json:"peaks"`
|
||||
}
|
||||
|
||||
type ResourceUsageAnalytics struct {
|
||||
ResourceType string `json:"resource_type"`
|
||||
TotalUsage int `json:"total_usage"`
|
||||
AverageUsage int `json:"average_usage"`
|
||||
PeakUsage int `json:"peak_usage"`
|
||||
PeakTime time.Time `json:"peak_time"`
|
||||
}
|
||||
|
||||
type UsageTrend struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Usage int `json:"usage"`
|
||||
}
|
||||
|
||||
type UsagePeak struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Usage int `json:"usage"`
|
||||
Context map[string]interface{} `json:"context"`
|
||||
}
|
||||
|
||||
type TenantPerformanceAnalytics struct {
|
||||
TenantID string `json:"tenant_id"`
|
||||
TimeRange string `json:"time_range"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
APIPerformance *APIPerformance `json:"api_performance"`
|
||||
ResourcePerformance map[string]*ResourcePerformance `json:"resource_performance"`
|
||||
Errors []*ErrorEvent `json:"errors"`
|
||||
SlowQueries []*SlowQuery `json:"slow_queries"`
|
||||
}
|
||||
|
||||
type APIPerformance struct {
|
||||
TotalRequests int `json:"total_requests"`
|
||||
AverageResponseTime float64 `json:"average_response_time"`
|
||||
P95ResponseTime float64 `json:"p95_response_time"`
|
||||
ErrorRate float64 `json:"error_rate"`
|
||||
RequestsPerSecond float64 `json:"requests_per_second"`
|
||||
}
|
||||
|
||||
type ResourcePerformance struct {
|
||||
ResourceType string `json:"resource_type"`
|
||||
AvgLatency float64 `json:"avg_latency"`
|
||||
P95Latency float64 `json:"p95_latency"`
|
||||
Throughput float64 `json:"throughput"`
|
||||
ErrorRate float64 `json:"error_rate"`
|
||||
}
|
||||
|
||||
type APIRequestEvent struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Method string `json:"method"`
|
||||
StatusCode int `json:"status_code"`
|
||||
ResponseTime time.Duration `json:"response_time"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
type ErrorEvent struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Error string `json:"error"`
|
||||
Context map[string]interface{} `json:"context"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
type SlowQuery struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Query string `json:"query"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
Context map[string]interface{} `json:"context"`
|
||||
}
|
||||
|
||||
type TenantCostAnalytics struct {
|
||||
TenantID string `json:"tenant_id"`
|
||||
TimeRange string `json:"time_range"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
CostByType map[string]float64 `json:"cost_by_type"`
|
||||
CostTrends []*CostTrend `json:"cost_trends"`
|
||||
Predictions *CostPrediction `json:"predictions"`
|
||||
Savings *CostSavings `json:"savings"`
|
||||
}
|
||||
|
||||
type CostTrend struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Cost float64 `json:"cost"`
|
||||
}
|
||||
|
||||
type CostPrediction struct {
|
||||
PredictedCost float64 `json:"predicted_cost"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
Factors []string `json:"factors"`
|
||||
}
|
||||
|
||||
type CostSavings struct {
|
||||
PotentialSavings float64 `json:"potential_savings"`
|
||||
Recommendations []string `json:"recommendations"`
|
||||
Optimizations map[string]float64 `json:"optimizations"`
|
||||
}
|
||||
|
||||
type TenantReport struct {
|
||||
TenantID string `json:"tenant_id"`
|
||||
ReportType string `json:"report_type"`
|
||||
TimeRange string `json:"time_range"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
Dashboard *TenantDashboard `json:"dashboard,omitempty"`
|
||||
UsageAnalytics *TenantUsageAnalytics `json:"usage_analytics,omitempty"`
|
||||
PerformanceAnalytics *TenantPerformanceAnalytics `json:"performance_analytics,omitempty"`
|
||||
CostAnalytics *TenantCostAnalytics `json:"cost_analytics,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user