Yimaru-BackEnd/internal/services/report/service.go

812 lines
24 KiB
Go

package report
import (
"context"
"encoding/csv"
"errors"
"fmt"
"log/slog"
"os"
"sort"
"strconv"
"strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
// notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
// virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
)
var (
ErrInvalidTimeRange = errors.New("invalid time range - start time must be before end time")
ErrInvalidReportCriteria = errors.New("invalid report criteria")
)
type Service struct {
betStore bet.BetStore
walletStore wallet.WalletStore
transactionStore transaction.TransactionStore
branchStore branch.BranchStore
userStore user.UserStore
repo repository.ReportRepository
companyStore company.CompanyStore
virtulaGamesStore repository.VirtualGameRepository
notificationStore repository.NotificationRepository
logger *slog.Logger
}
func NewService(
betStore bet.BetStore,
walletStore wallet.WalletStore,
transactionStore transaction.TransactionStore,
branchStore branch.BranchStore,
userStore user.UserStore,
repo repository.ReportRepository,
companyStore company.CompanyStore,
virtulaGamesStore repository.VirtualGameRepository,
notificationStore repository.NotificationRepository,
logger *slog.Logger,
) *Service {
return &Service{
betStore: betStore,
walletStore: walletStore,
transactionStore: transactionStore,
branchStore: branchStore,
userStore: userStore,
repo: repo,
companyStore: companyStore,
virtulaGamesStore: virtulaGamesStore,
notificationStore: notificationStore,
logger: logger,
}
}
// GetDashboardSummary returns comprehensive dashboard metrics
func (s *Service) GetDashboardSummary(ctx context.Context, filter domain.ReportFilter) (domain.DashboardSummary, error) {
if err := validateTimeRange(filter); err != nil {
return domain.DashboardSummary{}, err
}
var summary domain.DashboardSummary
var err error
// Get bets summary
summary.TotalStakes, summary.TotalBets, summary.ActiveBets, summary.TotalWins, summary.TotalLosses, summary.WinBalance, err =
s.betStore.GetBetSummary(ctx, filter)
if err != nil {
s.logger.Error("failed to get bet summary", "error", err)
return domain.DashboardSummary{}, err
}
// Get customer metrics
summary.CustomerCount, summary.ActiveCustomers, summary.InactiveCustomers, err = s.userStore.GetCustomerCounts(ctx, filter)
if err != nil {
s.logger.Error("failed to get customer counts", "error", err)
return domain.DashboardSummary{}, err
}
// Get branch metrics
summary.BranchesCount, summary.ActiveBranches, summary.InactiveBranches, err = s.branchStore.GetBranchCounts(ctx, filter)
if err != nil {
s.logger.Error("failed to get branch counts", "error", err)
return domain.DashboardSummary{}, err
}
// Get transaction metrics
summary.TotalDeposits, summary.TotalWithdrawals, err = s.transactionStore.GetTransactionTotals(ctx, filter)
if err != nil {
s.logger.Error("failed to get transaction totals", "error", err)
return domain.DashboardSummary{}, err
}
// Get user role metrics
summary.TotalCashiers, summary.ActiveCashiers, summary.InactiveCashiers, err = s.userStore.GetRoleCounts(ctx, string(domain.RoleCashier), filter)
if err != nil {
s.logger.Error("failed to get cashier counts", "error", err)
return domain.DashboardSummary{}, err
}
summary.TotalManagers, summary.ActiveManagers, summary.InactiveManagers, err = s.userStore.GetRoleCounts(ctx, string(domain.RoleBranchManager), filter)
if err != nil {
s.logger.Error("failed to get manager counts", "error", err)
return domain.DashboardSummary{}, err
}
summary.TotalAdmins, summary.ActiveAdmins, summary.InactiveAdmins, err = s.userStore.GetRoleCounts(ctx, string(domain.RoleAdmin), filter)
if err != nil {
s.logger.Error("failed to get admin counts", "error", err)
return domain.DashboardSummary{}, err
}
// Get wallet metrics
summary.TotalWallets, err = s.walletStore.GetTotalWallets(ctx, filter)
if err != nil {
s.logger.Error("failed to get wallet counts", "error", err)
return domain.DashboardSummary{}, err
}
// Get sport/game metrics
summary.TotalGames, summary.ActiveGames, summary.InactiveGames, err = s.virtulaGamesStore.GetGameCounts(ctx, filter)
if err != nil {
s.logger.Error("failed to get game counts", "error", err)
return domain.DashboardSummary{}, err
}
// Get company metrics
summary.TotalCompanies, summary.ActiveCompanies, summary.InactiveCompanies, err = s.companyStore.GetCompanyCounts(ctx, filter)
if err != nil {
s.logger.Error("failed to get company counts", "error", err)
return domain.DashboardSummary{}, err
}
// Get notification metrics
summary.TotalNotifications, summary.ReadNotifications, summary.UnreadNotifications, err = s.notificationStore.GetNotificationCounts(ctx, filter)
if err != nil {
s.logger.Error("failed to get notification counts", "error", err)
return domain.DashboardSummary{}, err
}
// Calculate derived metrics
if summary.TotalBets > 0 {
summary.AverageStake = summary.TotalStakes / domain.Currency(summary.TotalBets)
summary.WinRate = float64(summary.TotalWins) / float64(summary.TotalBets) * 100
summary.Profit = summary.TotalStakes - summary.WinBalance
}
return summary, nil
}
// Getdomain.BetAnalysis returns detailed bet analysis
func (s *Service) GetBetAnalysis(ctx context.Context, filter domain.ReportFilter) ([]domain.BetAnalysis, error) {
if err := validateTimeRange(filter); err != nil {
return nil, err
}
// Get basic bet stats
betStats, err := s.betStore.GetBetStats(ctx, filter)
if err != nil {
s.logger.Error("failed to get bet stats", "error", err)
return nil, err
}
// Get sport popularity
sportPopularity, err := s.betStore.GetSportPopularity(ctx, filter)
if err != nil {
s.logger.Error("failed to get sport popularity", "error", err)
return nil, err
}
// Get market popularity
marketPopularity, err := s.betStore.GetMarketPopularity(ctx, filter)
if err != nil {
s.logger.Error("failed to get market popularity", "error", err)
return nil, err
}
// Get extreme values
extremeValues, err := s.betStore.GetExtremeValues(ctx, filter)
if err != nil {
s.logger.Error("failed to get extreme values", "error", err)
return nil, err
}
// Combine data into analysis
var analysis []domain.BetAnalysis
for _, stat := range betStats {
a := domain.BetAnalysis{
Date: stat.Date,
TotalBets: stat.TotalBets,
TotalStakes: stat.TotalStakes,
TotalWins: stat.TotalWins,
TotalPayouts: stat.TotalPayouts,
Profit: stat.TotalStakes - stat.TotalPayouts,
AverageOdds: stat.AverageOdds,
}
// Add sport popularity
if sport, ok := sportPopularity[stat.Date]; ok {
a.MostPopularSport = sport
}
// Add market popularity
if market, ok := marketPopularity[stat.Date]; ok {
a.MostPopularMarket = market
}
// Add extreme values
if extremes, ok := extremeValues[stat.Date]; ok {
a.HighestStake = extremes.HighestStake
a.HighestPayout = extremes.HighestPayout
}
analysis = append(analysis, a)
}
// Sort by date
sort.Slice(analysis, func(i, j int) bool {
return analysis[i].Date.Before(analysis[j].Date)
})
return analysis, nil
}
// Getdomain.CustomerActivity returns customer activity report
func (s *Service) GetCustomerActivity(ctx context.Context, filter domain.ReportFilter) ([]domain.CustomerActivity, error) {
if err := validateTimeRange(filter); err != nil {
return nil, err
}
// Get customer bet activity
customerBets, err := s.betStore.GetCustomerBetActivity(ctx, filter)
if err != nil {
s.logger.Error("failed to get customer bet activity", "error", err)
return nil, err
}
// Get customer details
customerDetails, err := s.userStore.GetCustomerDetails(ctx, filter)
if err != nil {
s.logger.Error("failed to get customer details", "error", err)
return nil, err
}
// Get customer preferences
customerPrefs, err := s.betStore.GetCustomerPreferences(ctx, filter)
if err != nil {
s.logger.Error("failed to get customer preferences", "error", err)
return nil, err
}
// Combine data into activity report
var activities []domain.CustomerActivity
for _, bet := range customerBets {
activity := domain.CustomerActivity{
CustomerID: bet.CustomerID,
TotalBets: bet.TotalBets,
TotalStakes: bet.TotalStakes,
TotalWins: bet.TotalWins,
TotalPayouts: bet.TotalPayouts,
Profit: bet.TotalStakes - bet.TotalPayouts,
FirstBetDate: bet.FirstBetDate,
LastBetDate: bet.LastBetDate,
AverageStake: bet.TotalStakes / domain.Currency(bet.TotalBets),
AverageOdds: bet.AverageOdds,
}
// Add customer details
if details, ok := customerDetails[bet.CustomerID]; ok {
activity.CustomerName = details.Name
}
// Add preferences
if prefs, ok := customerPrefs[bet.CustomerID]; ok {
activity.FavoriteSport = prefs.FavoriteSport
activity.FavoriteMarket = prefs.FavoriteMarket
}
// Calculate win rate
if bet.TotalBets > 0 {
activity.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100
}
// Determine activity level
activity.ActivityLevel = calculateActivityLevel(bet.TotalBets, bet.TotalStakes)
activities = append(activities, activity)
}
// Sort by total stakes (descending)
sort.Slice(activities, func(i, j int) bool {
return activities[i].TotalStakes > activities[j].TotalStakes
})
return activities, nil
}
// Getdomain.BranchPerformance returns branch performance report
func (s *Service) GetBranchPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.BranchPerformance, error) {
// Get branch bet activity
branchBets, err := s.betStore.GetBranchBetActivity(ctx, filter)
if err != nil {
s.logger.Error("failed to get branch bet activity", "error", err)
return nil, err
}
// Get branch details
branchDetails, err := s.branchStore.GetBranchDetails(ctx, filter)
if err != nil {
s.logger.Error("failed to get branch details", "error", err)
return nil, err
}
// Get branch transactions
branchTransactions, err := s.transactionStore.GetBranchTransactionTotals(ctx, filter)
if err != nil {
s.logger.Error("failed to get branch transactions", "error", err)
return nil, err
}
// Get branch customer counts
branchCustomers, err := s.userStore.GetBranchCustomerCounts(ctx, filter)
if err != nil {
s.logger.Error("failed to get branch customer counts", "error", err)
return nil, err
}
// Combine data into performance report
var performances []domain.BranchPerformance
for _, bet := range branchBets {
performance := domain.BranchPerformance{
BranchID: bet.BranchID,
TotalBets: bet.TotalBets,
TotalStakes: bet.TotalStakes,
TotalWins: bet.TotalWins,
TotalPayouts: bet.TotalPayouts,
Profit: bet.TotalStakes - bet.TotalPayouts,
}
// Add branch details
if details, ok := branchDetails[bet.BranchID]; ok {
performance.BranchName = details.Name
performance.Location = details.Location
performance.ManagerName = details.ManagerName
}
// Add transactions
if transactions, ok := branchTransactions[bet.BranchID]; ok {
performance.Deposits = transactions.Deposits
performance.Withdrawals = transactions.Withdrawals
}
// Add customer counts
if customers, ok := branchCustomers[bet.BranchID]; ok {
performance.CustomerCount = customers
}
// Calculate metrics
if bet.TotalBets > 0 {
performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100
performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets)
}
// Calculate performance score
performance.PerformanceScore = calculatePerformanceScore(performance)
performances = append(performances, performance)
}
// Sort by performance score (descending)
sort.Slice(performances, func(i, j int) bool {
return performances[i].PerformanceScore > performances[j].PerformanceScore
})
return performances, nil
}
// Getdomain.SportPerformance returns sport performance report
func (s *Service) GetSportPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.SportPerformance, error) {
// Get sport bet activity
sportBets, err := s.betStore.GetSportBetActivity(ctx, filter)
if err != nil {
s.logger.Error("failed to get sport bet activity", "error", err)
return nil, err
}
// Get sport details (names)
sportDetails, err := s.betStore.GetSportDetails(ctx, filter)
if err != nil {
s.logger.Error("failed to get sport details", "error", err)
return nil, err
}
// Get sport market popularity
sportMarkets, err := s.betStore.GetSportMarketPopularity(ctx, filter)
if err != nil {
s.logger.Error("failed to get sport market popularity", "error", err)
return nil, err
}
// Combine data into performance report
var performances []domain.SportPerformance
for _, bet := range sportBets {
performance := domain.SportPerformance{
SportID: bet.SportID,
TotalBets: bet.TotalBets,
TotalStakes: bet.TotalStakes,
TotalWins: bet.TotalWins,
TotalPayouts: bet.TotalPayouts,
Profit: bet.TotalStakes - bet.TotalPayouts,
AverageOdds: bet.AverageOdds,
}
// Add sport details
if details, ok := sportDetails[bet.SportID]; ok {
performance.SportName = details
}
// Add market popularity
if market, ok := sportMarkets[bet.SportID]; ok {
performance.MostPopularMarket = market
}
// Calculate metrics
if bet.TotalBets > 0 {
performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100
performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets)
}
performances = append(performances, performance)
}
// Sort by total stakes (descending) and assign popularity rank
sort.Slice(performances, func(i, j int) bool {
return performances[i].TotalStakes > performances[j].TotalStakes
})
for i := range performances {
performances[i].PopularityRank = i + 1
}
return performances, nil
}
func (s *Service) GenerateReport(ctx context.Context, period string) error {
data, err := s.fetchReportData(ctx, period)
if err != nil {
return fmt.Errorf("fetch data: %w", err)
}
// Ensure the reports directory exists
if err := os.MkdirAll("reports", os.ModePerm); err != nil {
return fmt.Errorf("creating reports directory: %w", err)
}
filePath := fmt.Sprintf("reports/report_%s_%s.csv", period, time.Now().Format("2006-01-02_15-04"))
file, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("create file: %w", err)
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// Summary section
if err := writer.Write([]string{"Sports Betting Reports (Periodic)"}); err != nil {
return fmt.Errorf("write header: %w", err)
}
if err := writer.Write([]string{"Period", "Total Bets", "Total Cash Made", "Total Cash Out", "Total Cash Backs", "Total Deposits", "Total Withdrawals", "Total Tickets"}); err != nil {
return fmt.Errorf("write header row: %w", err)
}
if err := writer.Write([]string{
period,
fmt.Sprintf("%d", data.TotalBets),
fmt.Sprintf("%.2f", data.TotalCashIn),
fmt.Sprintf("%.2f", data.TotalCashOut),
fmt.Sprintf("%.2f", data.CashBacks),
fmt.Sprintf("%.2f", data.Deposits),
fmt.Sprintf("%.2f", data.Withdrawals),
fmt.Sprintf("%d", data.TotalTickets),
}); err != nil {
return fmt.Errorf("write summary row: %w", err)
}
writer.Write([]string{}) // Empty line
// Virtual Game Summary section
writer.Write([]string{"Virtual Game Reports (Periodic)"})
writer.Write([]string{"Game Name", "Number of Bets", "Total Transaction Sum"})
for _, row := range data.VirtualGameStats {
if err := writer.Write([]string{
row.GameName,
fmt.Sprintf("%d", row.NumBets),
fmt.Sprintf("%.2f", row.TotalTransaction),
}); err != nil {
return fmt.Errorf("write virtual game row: %w", err)
}
}
writer.Write([]string{}) // Empty line
// Company Reports
writer.Write([]string{"Company Reports (Periodic)"})
writer.Write([]string{"Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
for _, cr := range data.CompanyReports {
if err := writer.Write([]string{
fmt.Sprintf("%d", cr.CompanyID),
cr.CompanyName,
fmt.Sprintf("%d", cr.TotalBets),
fmt.Sprintf("%.2f", cr.TotalCashIn),
fmt.Sprintf("%.2f", cr.TotalCashOut),
fmt.Sprintf("%.2f", cr.TotalCashBacks),
}); err != nil {
return fmt.Errorf("write company row: %w", err)
}
}
writer.Write([]string{}) // Empty line
// Branch Reports
writer.Write([]string{"Branch Reports (Periodic)"})
writer.Write([]string{"Branch ID", "Branch Name", "Company ID", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
for _, br := range data.BranchReports {
if err := writer.Write([]string{
fmt.Sprintf("%d", br.BranchID),
br.BranchName,
fmt.Sprintf("%d", br.CompanyID),
fmt.Sprintf("%d", br.TotalBets),
fmt.Sprintf("%.2f", br.TotalCashIn),
fmt.Sprintf("%.2f", br.TotalCashOut),
fmt.Sprintf("%.2f", br.TotalCashBacks),
}); err != nil {
return fmt.Errorf("write branch row: %w", err)
}
}
// Total Summary
var totalBets int64
var totalCashIn, totalCashOut, totalCashBacks float64
for _, cr := range data.CompanyReports {
totalBets += cr.TotalBets
totalCashIn += cr.TotalCashIn
totalCashOut += cr.TotalCashOut
totalCashBacks += cr.TotalCashBacks
}
writer.Write([]string{}) // Empty line
writer.Write([]string{"Total Summary"})
writer.Write([]string{"Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
if err := writer.Write([]string{
fmt.Sprintf("%d", totalBets),
fmt.Sprintf("%.2f", totalCashIn),
fmt.Sprintf("%.2f", totalCashOut),
fmt.Sprintf("%.2f", totalCashBacks),
}); err != nil {
return fmt.Errorf("write total summary row: %w", err)
}
return nil
}
func (s *Service) fetchReportData(ctx context.Context, period string) (domain.ReportData, error) {
from, to := getTimeRange(period)
// companyID := int64(0)
// Basic metrics
totalBets, _ := s.repo.GetTotalBetsMadeInRange(ctx, from, to)
cashIn, _ := s.repo.GetTotalCashMadeInRange(ctx, from, to)
cashOut, _ := s.repo.GetTotalCashOutInRange(ctx, from, to)
cashBacks, _ := s.repo.GetTotalCashBacksInRange(ctx, from, to)
// Wallet Transactions
transactions, _ := s.repo.GetWalletTransactionsInRange(ctx, from, to)
var totalDeposits, totalWithdrawals float64
for _, tx := range transactions {
switch strings.ToLower(tx.Type.String) {
case "deposit":
totalDeposits += float64(tx.TotalAmount)
case "withdraw":
totalWithdrawals += float64(tx.TotalAmount)
}
}
// Ticket Count
totalTickets, _ := s.repo.GetAllTicketsInRange(ctx, from, to)
// Virtual Game Summary
virtualGameStats, _ := s.repo.GetVirtualGameSummaryInRange(ctx, from, to)
// Convert []dbgen.GetVirtualGameSummaryInRangeRow to []domain.VirtualGameStat
var virtualGameStatsDomain []domain.VirtualGameStat
for _, row := range virtualGameStats {
var totalTransaction float64
switch v := row.TotalTransactionSum.(type) {
case string:
val, err := strconv.ParseFloat(v, 64)
if err == nil {
totalTransaction = val
}
case float64:
totalTransaction = v
case int:
totalTransaction = float64(v)
default:
totalTransaction = 0
}
virtualGameStatsDomain = append(virtualGameStatsDomain, domain.VirtualGameStat{
GameName: row.GameName,
NumBets: row.NumberOfBets,
TotalTransaction: totalTransaction,
})
}
companyRows, _ := s.repo.GetCompanyWiseReport(ctx, from, to)
var companyReports []domain.CompanyReport
for _, row := range companyRows {
var totalCashIn, totalCashOut, totalCashBacks float64
switch v := row.TotalCashMade.(type) {
case string:
val, err := strconv.ParseFloat(v, 64)
if err == nil {
totalCashIn = val
}
case float64:
totalCashIn = v
case int:
totalCashIn = float64(v)
default:
totalCashIn = 0
}
switch v := row.TotalCashOut.(type) {
case string:
val, err := strconv.ParseFloat(v, 64)
if err == nil {
totalCashOut = val
}
case float64:
totalCashOut = v
case int:
totalCashOut = float64(v)
default:
totalCashOut = 0
}
switch v := row.TotalCashBacks.(type) {
case string:
val, err := strconv.ParseFloat(v, 64)
if err == nil {
totalCashBacks = val
}
case float64:
totalCashBacks = v
case int:
totalCashBacks = float64(v)
default:
totalCashBacks = 0
}
companyReports = append(companyReports, domain.CompanyReport{
CompanyID: row.CompanyID,
CompanyName: row.CompanyName,
TotalBets: row.TotalBets,
TotalCashIn: totalCashIn,
TotalCashOut: totalCashOut,
TotalCashBacks: totalCashBacks,
})
}
branchRows, _ := s.repo.GetBranchWiseReport(ctx, from, to)
var branchReports []domain.BranchReport
for _, row := range branchRows {
var totalCashIn, totalCashOut, totalCashBacks float64
switch v := row.TotalCashMade.(type) {
case string:
val, err := strconv.ParseFloat(v, 64)
if err == nil {
totalCashIn = val
}
case float64:
totalCashIn = v
case int:
totalCashIn = float64(v)
default:
totalCashIn = 0
}
switch v := row.TotalCashOut.(type) {
case string:
val, err := strconv.ParseFloat(v, 64)
if err == nil {
totalCashOut = val
}
case float64:
totalCashOut = v
case int:
totalCashOut = float64(v)
default:
totalCashOut = 0
}
switch v := row.TotalCashBacks.(type) {
case string:
val, err := strconv.ParseFloat(v, 64)
if err == nil {
totalCashBacks = val
}
case float64:
totalCashBacks = v
case int:
totalCashBacks = float64(v)
default:
totalCashBacks = 0
}
branchReports = append(branchReports, domain.BranchReport{
BranchID: row.BranchID,
BranchName: row.BranchName,
CompanyID: row.CompanyID,
TotalBets: row.TotalBets,
TotalCashIn: totalCashIn,
TotalCashOut: totalCashOut,
TotalCashBacks: totalCashBacks,
})
}
return domain.ReportData{
TotalBets: totalBets,
TotalCashIn: cashIn,
TotalCashOut: cashOut,
CashBacks: cashBacks,
Deposits: totalDeposits,
Withdrawals: totalWithdrawals,
TotalTickets: totalTickets.TotalTickets,
VirtualGameStats: virtualGameStatsDomain,
CompanyReports: companyReports,
BranchReports: branchReports,
}, nil
}
func getTimeRange(period string) (time.Time, time.Time) {
now := time.Now()
switch strings.ToLower(period) {
case "daily":
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
end := start.Add(5 * time.Minute)
return start, end
case "weekly":
weekday := int(now.Weekday())
if weekday == 0 {
weekday = 7
}
start := now.AddDate(0, 0, -weekday+1)
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, now.Location())
end := start.AddDate(0, 0, 7)
return start, end
case "monthly":
start := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
end := start.AddDate(0, 1, 0)
return start, end
default:
// Default to daily
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
end := start.Add(24 * time.Hour)
return start, end
}
}
// Helper functions
func validateTimeRange(filter domain.ReportFilter) error {
if filter.StartTime.Valid && filter.EndTime.Valid {
if filter.StartTime.Value.After(filter.EndTime.Value) {
return ErrInvalidTimeRange
}
}
return nil
}
func calculateActivityLevel(totalBets int64, totalStakes domain.Currency) string {
switch {
case totalBets > 100 || totalStakes > 10000:
return "High"
case totalBets > 50 || totalStakes > 5000:
return "Medium"
default:
return "Low"
}
}
func calculatePerformanceScore(perf domain.BranchPerformance) float64 {
// Simple scoring algorithm - can be enhanced based on business rules
profitScore := float64(perf.Profit) / 1000
customerScore := float64(perf.CustomerCount) * 0.1
betScore := float64(perf.TotalBets) * 0.01
winRateScore := perf.WinRate * 0.1
return profitScore + customerScore + betScore + winRateScore
}