2071 lines
64 KiB
Go
2071 lines
64 KiB
Go
package result
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
|
"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/event"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type Service struct {
|
|
repo *repository.Store
|
|
config *config.Config
|
|
logger *slog.Logger
|
|
mongoLogger *zap.Logger
|
|
client *http.Client
|
|
betSvc bet.Service
|
|
oddSvc odds.ServiceImpl
|
|
eventSvc event.Service
|
|
leagueSvc league.Service
|
|
notificationSvc *notificationservice.Service
|
|
userSvc user.Service
|
|
}
|
|
|
|
func NewService(
|
|
repo *repository.Store,
|
|
cfg *config.Config,
|
|
logger *slog.Logger,
|
|
mongoLogger *zap.Logger,
|
|
betSvc bet.Service,
|
|
oddSvc odds.ServiceImpl,
|
|
eventSvc event.Service,
|
|
leagueSvc league.Service,
|
|
notificationSvc *notificationservice.Service,
|
|
userSvc user.Service,
|
|
) *Service {
|
|
return &Service{
|
|
repo: repo,
|
|
config: cfg,
|
|
logger: logger,
|
|
mongoLogger: mongoLogger,
|
|
client: &http.Client{Timeout: 10 * time.Second},
|
|
betSvc: betSvc,
|
|
oddSvc: oddSvc,
|
|
eventSvc: eventSvc,
|
|
leagueSvc: leagueSvc,
|
|
notificationSvc: notificationSvc,
|
|
userSvc: userSvc,
|
|
}
|
|
}
|
|
|
|
var (
|
|
ErrEventIsNotActive = fmt.Errorf("event has been cancelled or postponed")
|
|
)
|
|
|
|
func (s *Service) UpdateResultForOutcomes(ctx context.Context, eventID int64, resultRes json.RawMessage, sportID int64) error {
|
|
// TODO: Optimize this since there could be many outcomes with the same event_id and market_id that could be updated the same time
|
|
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID, true)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to get pending bet outcomes",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err)
|
|
}
|
|
|
|
// if len(outcomes) == 0 {
|
|
// s.mongoLogger.Info(
|
|
// "No bets have been placed on event",
|
|
// zap.Int64("eventID", eventID),
|
|
// )
|
|
|
|
// }
|
|
for _, outcome := range outcomes {
|
|
if outcome.Expires.After(time.Now()) {
|
|
s.mongoLogger.Warn(
|
|
"Outcome is not expired yet",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
zap.Error(err),
|
|
)
|
|
return fmt.Errorf("Outcome has not expired yet")
|
|
}
|
|
|
|
parseResult, err := s.parseResult(resultRes, outcome, sportID)
|
|
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse result",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
zap.Error(err),
|
|
)
|
|
return err
|
|
}
|
|
outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, parseResult.Status)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to update bet outcome status",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
zap.Error(err),
|
|
)
|
|
return err
|
|
}
|
|
if outcome.Status == domain.OUTCOME_STATUS_ERROR || outcome.Status == domain.OUTCOME_STATUS_PENDING {
|
|
s.mongoLogger.Error(
|
|
"Outcome has been updated to pending or error",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return fmt.Errorf("Error while updating outcome")
|
|
}
|
|
|
|
status, err := s.betSvc.CheckBetOutcomeForBet(ctx, outcome.BetID)
|
|
if err != nil {
|
|
if err != bet.ErrOutcomesNotCompleted {
|
|
s.mongoLogger.Error(
|
|
"Failed to check bet outcome for bet",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Int64("betID", outcome.BetID),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
return err
|
|
}
|
|
s.mongoLogger.Info(
|
|
"Updating bet status",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Int64("betID", outcome.BetID),
|
|
zap.String("status", status.String()),
|
|
zap.Int64("eventID", eventID),
|
|
)
|
|
err = s.betSvc.UpdateStatus(ctx, outcome.BetID, status)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to update bet status",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
func (s *Service) GetTotalBetsForEvents(ctx context.Context, eventID int64) (map[int64]int64, error) {
|
|
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID, false)
|
|
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"[GetTotalBetsForEvent] Failed to get all the pending bet outcomes",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err)
|
|
}
|
|
|
|
// Get all the unique bet_ids and how many outcomes have this bet_id
|
|
betIDSet := make(map[int64]int64)
|
|
for _, outcome := range outcomes {
|
|
betIDSet[outcome.BetID] += 1
|
|
}
|
|
|
|
return betIDSet, nil
|
|
|
|
}
|
|
|
|
// Returns total number of bets refunded
|
|
func (s *Service) RefundAllOutcomes(ctx context.Context, eventID int64) (map[int64]int64, error) {
|
|
|
|
outcomes, err := s.betSvc.UpdateBetOutcomeStatusForEvent(ctx, eventID, domain.OUTCOME_STATUS_VOID)
|
|
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"[RefundAllOutcomes] Failed to update all outcomes for event",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
|
|
// Get all the unique bet_ids and how many outcomes have this bet_id
|
|
betIDSet := make(map[int64]int64)
|
|
for _, outcome := range outcomes {
|
|
betIDSet[outcome.BetID] += 1
|
|
}
|
|
|
|
for betID := range betIDSet {
|
|
status, err := s.betSvc.CheckBetOutcomeForBet(ctx, betID)
|
|
if err != nil {
|
|
if err != bet.ErrOutcomesNotCompleted {
|
|
s.mongoLogger.Error(
|
|
"[RefundAllOutcomes] Failed to check bet outcome for bet",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Int64("betID", betID),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
return nil, err
|
|
}
|
|
err = s.betSvc.UpdateStatus(ctx, betID, status)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"[RefundAllOutcomes] Failed to update bet status",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Int64("betID", betID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
}
|
|
return betIDSet, nil
|
|
// for _, outcome := range outcomes {
|
|
// outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, domain.OUTCOME_STATUS_VOID)
|
|
// if err != nil {
|
|
// s.logger.Error("Failed to refund all outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
|
// return err
|
|
// }
|
|
|
|
// // Check if all the bet outcomes have been set to refund for
|
|
// status, err := s.betSvc.CheckBetOutcomeForBet(ctx, outcome.BetID)
|
|
// if err != nil {
|
|
// if err != bet.ErrOutcomesNotCompleted {
|
|
// s.logger.Error("Failed to check bet outcome for bet", "event_id", outcome.EventID, "error", err)
|
|
// }
|
|
// return err
|
|
// }
|
|
// err = s.betSvc.UpdateStatus(ctx, outcome.BetID, domain.OUTCOME_STATUS_VOID)
|
|
|
|
// if err != nil {
|
|
// s.logger.Error("Failed to update bet status", "event id", outcome.EventID, "error", err)
|
|
// return err
|
|
// }
|
|
// }
|
|
|
|
}
|
|
|
|
func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
|
// TODO: Optimize this because there could be many bet outcomes for the same odd
|
|
// Take market id and match result as param and update all the bet outcomes at the same time
|
|
events, err := s.repo.GetExpiredUpcomingEvents(ctx, domain.EventFilter{})
|
|
if err != nil {
|
|
s.logger.Error("Failed to fetch events")
|
|
s.mongoLogger.Error(
|
|
"[FetchAndProcessResult] Failed to fetch events",
|
|
zap.Error(err),
|
|
)
|
|
return err
|
|
}
|
|
|
|
removed := 0
|
|
empty_sport_id := make([]int64, 0)
|
|
var resultStatusCounts domain.ResultStatusCounts
|
|
var resultStatusBets domain.ResultStatusBets
|
|
for _, event := range events {
|
|
|
|
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse Event ID",
|
|
zap.String("eventID", event.ID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
result, err := s.fetchResult(ctx, eventID)
|
|
if err != nil {
|
|
if err == ErrEventIsNotActive {
|
|
s.logger.Warn("Event is not active", "event_id", eventID, "error", err)
|
|
s.mongoLogger.Warn(
|
|
"Event is not active",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
s.mongoLogger.Error(
|
|
"Failed to fetch result",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
var commonResp domain.CommonResultResponse
|
|
if err := json.Unmarshal(result.Results[0], &commonResp); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal common result",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
timeStatusParsed := commonResp.TimeStatus.Int64
|
|
// if err != nil {
|
|
// s.mongoLogger.Error(
|
|
// "Failed to parse time status",
|
|
// zap.Int64("time_status", timeStatusParsed),
|
|
// zap.Error(err),
|
|
// )
|
|
// continue
|
|
// }
|
|
|
|
// TODO: Figure out what to do with the events that have been cancelled or postponed, etc...
|
|
// if timeStatusParsed != int64(domain.TIME_STATUS_ENDED) {
|
|
// s.logger.Warn("Event is not ended yet", "event_id", eventID, "time_status", commonResp.TimeStatus)
|
|
// fmt.Printf("⚠️ Event %v is not ended yet (%d/%d) \n", event.ID, i+1, len(events))
|
|
// isDeleted = false
|
|
// continue
|
|
// }
|
|
// Admin users will be able to review the events
|
|
switch timeStatusParsed {
|
|
case int64(domain.TIME_STATUS_NOT_STARTED), int64(domain.TIME_STATUS_IN_PLAY):
|
|
resultStatusCounts.IsNotFinished += 1
|
|
bets, err := s.GetTotalBetsForEvents(ctx, eventID)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
resultStatusCounts.IsNotFinishedBets = len(bets)
|
|
for k := range bets {
|
|
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k)
|
|
}
|
|
|
|
case int64(domain.TIME_STATUS_TO_BE_FIXED):
|
|
resultStatusCounts.IsToBeFixed += 1
|
|
bets, err := s.GetTotalBetsForEvents(ctx, eventID)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
resultStatusCounts.IsToBeFixedBets = len(bets)
|
|
for k := range bets {
|
|
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k)
|
|
}
|
|
// s.mongoLogger.Warn(
|
|
// "Event needs to be rescheduled or corrected",
|
|
// zap.Int64("eventID", eventID),
|
|
// zap.Error(err),
|
|
// )
|
|
case int64(domain.TIME_STATUS_POSTPONED), int64(domain.TIME_STATUS_SUSPENDED):
|
|
resultStatusCounts.IsPostponed += 1
|
|
bets, err := s.GetTotalBetsForEvents(ctx, eventID)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
resultStatusCounts.IsPostponed = len(bets)
|
|
for k := range bets {
|
|
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k)
|
|
}
|
|
// s.mongoLogger.Warn(
|
|
// "Event has been temporarily postponed",
|
|
// zap.Int64("eventID", eventID),
|
|
// zap.Error(err),
|
|
// )
|
|
case int64(domain.TIME_STATUS_ENDED), int64(domain.TIME_STATUS_WALKOVER), int64(domain.TIME_STATUS_DECIDED_BY_FA):
|
|
if commonResp.SportID == "" {
|
|
empty_sport_id = append(empty_sport_id, eventID)
|
|
continue
|
|
}
|
|
sportID, err := strconv.ParseInt(commonResp.SportID, 10, 64)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse sport id",
|
|
zap.String("sportID", commonResp.SportID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
err = s.UpdateResultForOutcomes(ctx, eventID, result.Results[0], sportID)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Error while updating result for event",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
err = s.repo.DeleteEvent(ctx, event.ID)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to remove event",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
err = s.repo.DeleteOddsForEvent(ctx, event.ID)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to remove odds for event",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
removed += 1
|
|
resultStatusCounts.IsEnded += 1
|
|
bets, err := s.GetTotalBetsForEvents(ctx, eventID)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
resultStatusCounts.IsEndedBets = len(bets)
|
|
for k := range bets {
|
|
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k)
|
|
}
|
|
case int64(domain.TIME_STATUS_ABANDONED), int64(domain.TIME_STATUS_CANCELLED), int64(domain.TIME_STATUS_REMOVED):
|
|
// s.SendAdminResultStatusErrorNotification(
|
|
// ctx,
|
|
// "Cannot Update outcomes for event (ABANDONED | CANCELLED | REMOVED)",
|
|
// "Event abandoned/cancelled/removed",
|
|
// event.ID,
|
|
// )
|
|
// s.mongoLogger.Info(
|
|
// "Event abandoned/cancelled/removed",
|
|
// zap.Int64("eventID", eventID),
|
|
// zap.Int64("status", timeStatusParsed),
|
|
// )
|
|
totalBetsRefunded, err := s.RefundAllOutcomes(ctx, eventID)
|
|
|
|
err = s.repo.DeleteEvent(ctx, event.ID)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to remove event",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
err = s.repo.DeleteOddsForEvent(ctx, event.ID)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to remove odds for event",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
removed += 1
|
|
resultStatusCounts.IsRemoved += 1
|
|
resultStatusCounts.IsRemovedBets = len(totalBetsRefunded)
|
|
for k := range totalBetsRefunded {
|
|
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
s.SendAdminResultStatusErrorNotification(
|
|
ctx,
|
|
resultStatusCounts,
|
|
)
|
|
|
|
var logMessage string
|
|
if resultStatusCounts.IsNotFinished != 0 || resultStatusCounts.IsPostponed != 0 ||
|
|
resultStatusCounts.IsRemoved != 0 || resultStatusCounts.IsToBeFixed != 0 {
|
|
logMessage = "Completed processed results with issues"
|
|
} else {
|
|
logMessage = "Successfully processed results with no issues"
|
|
}
|
|
|
|
s.mongoLogger.Info(
|
|
logMessage,
|
|
zap.Int("number_of_removed_events", removed),
|
|
zap.Int("total_expired_events", len(events)),
|
|
zap.Any("events_with_empty_sport_id", empty_sport_id),
|
|
zap.Any("result status counts", resultStatusCounts),
|
|
zap.Any("bets by event status", resultStatusBets),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func buildHeadlineAndMessage(counts domain.ResultStatusCounts) (string, string) {
|
|
totalIssues := counts.IsNotFinished + counts.IsToBeFixed + counts.IsPostponed + counts.IsRemoved
|
|
if totalIssues == 0 {
|
|
return "✅ Event Results Processed", "All event results were processed successfully. No issues detected."
|
|
}
|
|
|
|
parts := []string{}
|
|
if counts.IsNotFinished > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d unfinished", counts.IsNotFinished))
|
|
}
|
|
if counts.IsToBeFixed > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d to-fix", counts.IsToBeFixed))
|
|
}
|
|
if counts.IsPostponed > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d postponed", counts.IsPostponed))
|
|
}
|
|
if counts.IsRemoved > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d removed", counts.IsRemoved))
|
|
}
|
|
|
|
headline := "⚠️ Issues Found Processing Event Results"
|
|
message := fmt.Sprintf("Processed expired event results: %s. Please review pending entries.", strings.Join(parts, ", "))
|
|
return headline, message
|
|
}
|
|
|
|
func (s *Service) SendAdminResultStatusErrorNotification(
|
|
ctx context.Context,
|
|
counts domain.ResultStatusCounts,
|
|
) error {
|
|
|
|
superAdmins, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{
|
|
Role: string(domain.RoleSuperAdmin),
|
|
})
|
|
if err != nil {
|
|
s.mongoLogger.Error("failed to get super_admin recipients", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
metaBytes, err := json.Marshal(counts)
|
|
if err != nil {
|
|
s.mongoLogger.Error("failed to marshal metadata", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
headline, message := buildHeadlineAndMessage(counts)
|
|
errorSeverity := domain.NotificationErrorSeverityLow
|
|
notification := &domain.Notification{
|
|
ErrorSeverity: &errorSeverity,
|
|
DeliveryStatus: domain.DeliveryStatusPending,
|
|
IsRead: false,
|
|
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
|
Level: domain.NotificationLevelWarning,
|
|
Reciever: domain.NotificationRecieverSideAdmin,
|
|
DeliveryChannel: domain.DeliveryChannelInApp,
|
|
Payload: domain.NotificationPayload{
|
|
Headline: headline,
|
|
Message: message,
|
|
},
|
|
Priority: 2,
|
|
Metadata: metaBytes,
|
|
}
|
|
|
|
var sendErrors []error
|
|
for _, user := range superAdmins {
|
|
notification.RecipientID = user.ID
|
|
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil {
|
|
s.mongoLogger.Error("failed to send admin notification",
|
|
zap.Int64("admin_id", user.ID),
|
|
zap.Error(err),
|
|
)
|
|
sendErrors = append(sendErrors, err)
|
|
}
|
|
}
|
|
|
|
if len(sendErrors) > 0 {
|
|
return fmt.Errorf("sent with partial failure: %d errors", len(sendErrors))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error) {
|
|
events, err := s.repo.GetExpiredUpcomingEvents(ctx, domain.EventFilter{})
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to fetch events",
|
|
zap.Error(err),
|
|
)
|
|
return 0, err
|
|
}
|
|
skipped := 0
|
|
updated := 0
|
|
var leagueCountries []string
|
|
eventResultStats := make(map[string]int)
|
|
for _, event := range events {
|
|
// fmt.Printf("⚙️ Processing event %v (%d/%d) \n", event.ID, i+1, len(events))
|
|
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse event id",
|
|
zap.String("eventID", event.ID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
if event.Status == domain.STATUS_REMOVED {
|
|
skipped += 1
|
|
continue
|
|
}
|
|
result, err := s.fetchResult(ctx, eventID)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to fetch result",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
if result.Success != 1 || len(result.Results) == 0 {
|
|
s.mongoLogger.Error(
|
|
"Invalid API result response",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
var commonResp domain.CommonResultResponse
|
|
if err := json.Unmarshal(result.Results[0], &commonResp); err != nil {
|
|
fmt.Printf("UnMarshalling error %v \n", err)
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal common result",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
var eventStatus domain.EventStatus
|
|
// TODO Change event status to int64 enum
|
|
timeStatus := commonResp.TimeStatus.Int64
|
|
// if err != nil {
|
|
// s.mongoLogger.Error(
|
|
// "Invalid time status",
|
|
// zap.Int64("eventID", eventID),
|
|
// zap.Error(err),
|
|
// )
|
|
// }
|
|
switch timeStatus {
|
|
case int64(domain.TIME_STATUS_NOT_STARTED):
|
|
eventStatus = domain.STATUS_PENDING
|
|
eventResultStats["STATUS_PENDING"] += 1
|
|
case int64(domain.TIME_STATUS_IN_PLAY):
|
|
eventStatus = domain.STATUS_IN_PLAY
|
|
eventResultStats["STATUS_IN_PLAY"] += 1
|
|
case int64(domain.TIME_STATUS_TO_BE_FIXED):
|
|
eventStatus = domain.STATUS_TO_BE_FIXED
|
|
eventResultStats["STATUS_TO_BE_FIXED"] += 1
|
|
case int64(domain.TIME_STATUS_ENDED):
|
|
eventStatus = domain.STATUS_ENDED
|
|
eventResultStats["STATUS_ENDED"] += 1
|
|
case int64(domain.TIME_STATUS_POSTPONED):
|
|
eventStatus = domain.STATUS_POSTPONED
|
|
eventResultStats["STATUS_POSTPONED"] += 1
|
|
case int64(domain.TIME_STATUS_CANCELLED):
|
|
eventStatus = domain.STATUS_CANCELLED
|
|
eventResultStats["STATUS_CANCELLED"] += 1
|
|
case int64(domain.TIME_STATUS_WALKOVER):
|
|
eventStatus = domain.STATUS_WALKOVER
|
|
eventResultStats["STATUS_WALKOVER"] += 1
|
|
case int64(domain.TIME_STATUS_INTERRUPTED):
|
|
eventStatus = domain.STATUS_INTERRUPTED
|
|
eventResultStats["STATUS_INTERRUPTED"] += 1
|
|
case int64(domain.TIME_STATUS_ABANDONED):
|
|
eventStatus = domain.STATUS_ABANDONED
|
|
eventResultStats["STATUS_ABANDONED"] += 1
|
|
case int64(domain.TIME_STATUS_RETIRED):
|
|
eventStatus = domain.STATUS_RETIRED
|
|
eventResultStats["STATUS_RETIRED"] += 1
|
|
case int64(domain.TIME_STATUS_SUSPENDED):
|
|
eventStatus = domain.STATUS_SUSPENDED
|
|
eventResultStats["STATUS_SUSPENDED"] += 1
|
|
case int64(domain.TIME_STATUS_DECIDED_BY_FA):
|
|
eventStatus = domain.STATUS_DECIDED_BY_FA
|
|
eventResultStats["STATUS_DECIDED_BY_FA"] += 1
|
|
case int64(domain.TIME_STATUS_REMOVED):
|
|
eventStatus = domain.STATUS_REMOVED
|
|
eventResultStats["STATUS_REMOVED"] += 1
|
|
default:
|
|
s.mongoLogger.Error(
|
|
"Invalid time status",
|
|
zap.Int64("time_status", timeStatus),
|
|
zap.Int64("eventID", eventID),
|
|
)
|
|
continue
|
|
}
|
|
|
|
err = s.eventSvc.UpdateFinalScore(ctx, strconv.FormatInt(eventID, 10), commonResp.SS, eventStatus)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to update final score",
|
|
zap.Int64("eventID", eventID),
|
|
zap.String("SS", commonResp.SS),
|
|
zap.String("eventStatus", string(eventStatus)),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
updated++
|
|
// fmt.Printf("✅ Successfully updated event %v to %v (%d/%d) \n", event.ID, eventStatus, i+1, len(events))
|
|
// s.mongoLogger.Info(
|
|
// "Updated Event Status",
|
|
// zap.Int64("eventID", eventID),
|
|
// zap.String("MatchName", event.MatchName),
|
|
// zap.String("SS", commonResp.SS),
|
|
// zap.String("status", string(eventStatus)),
|
|
// )
|
|
// Update the league because the league country code is only found on the result response
|
|
if commonResp.League.ID == "" {
|
|
s.mongoLogger.Warn(
|
|
"League ID empty on result response",
|
|
zap.Int64("eventID", eventID),
|
|
zap.String("leagueID", commonResp.League.ID),
|
|
)
|
|
continue
|
|
}
|
|
leagueID, err := strconv.ParseInt(commonResp.League.ID, 10, 64)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Invalid League ID",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
err = s.leagueSvc.UpdateLeague(ctx, domain.UpdateLeague{
|
|
ID: int64(event.LeagueID),
|
|
CountryCode: domain.ValidString{
|
|
Value: commonResp.League.CC,
|
|
Valid: true,
|
|
},
|
|
Bet365ID: domain.ValidInt32{
|
|
Value: int32(leagueID),
|
|
Valid: true,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Error Updating League",
|
|
zap.String("League Name", commonResp.League.Name),
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
// fmt.Printf("✅ Updated League %v with country code %v \n", leagueID, commonResp.League.CC)
|
|
// s.logger.Info("Updated League with country code", "leagueID", leagueID, "code", commonResp.League.CC)
|
|
leagueCountries = append(leagueCountries, commonResp.League.CC)
|
|
}
|
|
|
|
if updated == 0 {
|
|
s.logger.Info("No events were updated")
|
|
s.mongoLogger.Info(
|
|
"No events were updated",
|
|
)
|
|
return 0, nil
|
|
}
|
|
|
|
if skipped != 0 {
|
|
s.mongoLogger.Info(
|
|
"Skipping updating event due to removal",
|
|
zap.Int("eventID", skipped),
|
|
)
|
|
}
|
|
|
|
s.mongoLogger.Info(
|
|
"Successfully updated expired events",
|
|
zap.Int("updated_events", updated),
|
|
zap.Int("total_events", len(events)),
|
|
zap.Int("updated_leagues countries", len(leagueCountries)),
|
|
zap.Any("event_result_stats", eventResultStats),
|
|
)
|
|
return int64(updated), nil
|
|
|
|
}
|
|
|
|
func (s *Service) GetResultsForEvent(ctx context.Context, eventID string) (json.RawMessage, []domain.BetOutcome, error) {
|
|
id, err := strconv.ParseInt(eventID, 10, 64)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse event id",
|
|
zap.String("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return json.RawMessage{}, nil, err
|
|
}
|
|
|
|
result, err := s.fetchResult(ctx, id)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to fetch result",
|
|
zap.Int64("eventID", id),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
if result.Success != 1 || len(result.Results) == 0 {
|
|
s.mongoLogger.Error(
|
|
"Invalid API result response",
|
|
zap.Any("result", result),
|
|
zap.Int64("eventID", id),
|
|
zap.Error(err),
|
|
)
|
|
return json.RawMessage{}, nil, fmt.Errorf("invalid API response for event %d", id)
|
|
}
|
|
|
|
var commonResp domain.CommonResultResponse
|
|
if err := json.Unmarshal(result.Results[0], &commonResp); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal common result",
|
|
zap.String("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return json.RawMessage{}, nil, err
|
|
}
|
|
if commonResp.SportID == "" {
|
|
s.mongoLogger.Warn(
|
|
"Sport ID is empty",
|
|
zap.String("eventID", eventID),
|
|
)
|
|
return json.RawMessage{}, nil, fmt.Errorf("sport id empty for event: %v", eventID)
|
|
}
|
|
sportID, err := strconv.ParseInt(commonResp.SportID, 10, 32)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse sport id",
|
|
zap.String("sportID", commonResp.SportID),
|
|
zap.String("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return json.RawMessage{}, nil, fmt.Errorf("failed to parse sport id: %w", err)
|
|
}
|
|
|
|
expireUnix, err := strconv.ParseInt(commonResp.Time, 10, 64)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse expire time",
|
|
zap.String("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return json.RawMessage{}, nil, fmt.Errorf("Failed to parse expire time for event %s: %w", eventID, err)
|
|
}
|
|
|
|
expires := time.Unix(expireUnix, 0)
|
|
|
|
odds, err := s.oddSvc.FetchNonLiveOddsByEventID(ctx, eventID)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to fetch non-live odds by event ID",
|
|
zap.String("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return json.RawMessage{}, nil, fmt.Errorf("failed to fetch non-live odds for event %s: %w", eventID, err)
|
|
}
|
|
|
|
parsedOddSections, err := s.oddSvc.ParseOddSections(ctx, odds.Results[0], int32(sportID))
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse odd section",
|
|
zap.Int64("sportID", sportID),
|
|
zap.String("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return json.RawMessage{}, nil, fmt.Errorf("failed to parse odd section for event %v: %w", eventID, err)
|
|
}
|
|
|
|
outcomes := make([]domain.BetOutcome, 0)
|
|
for _, section := range parsedOddSections.Sections {
|
|
for _, market := range section.Sp {
|
|
marketIDint := market.ID.Int64
|
|
// if err != nil {
|
|
// s.mongoLogger.Error(
|
|
// "Invalid market id",
|
|
// zap.Int64("market_id", marketIDint),
|
|
// zap.String("market_name", market.Name),
|
|
// zap.String("eventID", eventID),
|
|
// zap.Error(err),
|
|
// )
|
|
// continue
|
|
// }
|
|
|
|
isSupported, ok := domain.SupportedMarkets[marketIDint]
|
|
|
|
if !ok || !isSupported {
|
|
s.mongoLogger.Info(
|
|
"Unsupported market",
|
|
zap.Int64("marketID", marketIDint),
|
|
zap.String("marketName", market.Name),
|
|
)
|
|
continue
|
|
}
|
|
for _, oddRes := range market.Odds {
|
|
var odd domain.RawOdd
|
|
if err := json.Unmarshal(oddRes, &odd); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal odd",
|
|
zap.Int64("marketID", marketIDint),
|
|
zap.String("marketName", market.Name),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
oddID, err := strconv.ParseInt(odd.ID, 10, 64)
|
|
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse odd id",
|
|
zap.String("odd_id", odd.ID),
|
|
zap.Int64("marketID", marketIDint),
|
|
zap.String("marketName", market.Name),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
oddValue, err := strconv.ParseFloat(odd.Odds, 64)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse odd value",
|
|
zap.String("odd_id", odd.ID),
|
|
zap.String("odd_value", odd.Odds),
|
|
zap.Int64("marketID", marketIDint),
|
|
zap.String("marketName", market.Name),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
outcome := domain.BetOutcome{
|
|
EventID: id,
|
|
MarketID: marketIDint,
|
|
OddID: oddID,
|
|
MarketName: market.Name,
|
|
OddHeader: odd.Header,
|
|
OddHandicap: odd.Handicap,
|
|
OddName: odd.Name,
|
|
Odd: float32(oddValue),
|
|
SportID: sportID,
|
|
HomeTeamName: commonResp.Home.Name,
|
|
AwayTeamName: commonResp.Away.Name,
|
|
Status: domain.OUTCOME_STATUS_PENDING,
|
|
Expires: expires,
|
|
BetID: 0, // This won't be set
|
|
}
|
|
outcomes = append(outcomes, outcome)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(outcomes) == 0 {
|
|
s.mongoLogger.Warn(
|
|
"No outcomes found for event",
|
|
zap.String("eventID", eventID),
|
|
)
|
|
return json.RawMessage{}, nil, fmt.Errorf("no outcomes found for event %s", eventID)
|
|
}
|
|
s.mongoLogger.Info(
|
|
"Successfully fetched outcomes for event",
|
|
zap.String("eventID", eventID),
|
|
zap.Int("outcomes", len(outcomes)),
|
|
)
|
|
|
|
// Get results for outcome
|
|
for i, outcome := range outcomes {
|
|
// Parse the result based on sport type
|
|
parsedResult, err := s.parseResult(result.Results[0], outcome, sportID)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse result for outcome",
|
|
zap.Int64("event_id", outcome.EventID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Int64("outcomeID", outcome.ID),
|
|
zap.Error(err),
|
|
)
|
|
return json.RawMessage{}, nil, fmt.Errorf("failed to parse result for outcome %d: %w", i, err)
|
|
}
|
|
outcomes[i].Status = parsedResult.Status
|
|
}
|
|
|
|
return result.Results[0], outcomes, err
|
|
}
|
|
|
|
func (s *Service) fetchResult(ctx context.Context, eventID int64) (domain.BaseResultResponse, error) {
|
|
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%d", s.config.Bet365Token, eventID)
|
|
// url := fmt.Sprintf("https://api.b365api.com/v1/event/view?token=%s&event_id=%d", s.config.Bet365Token, eventID)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to create request",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.BaseResultResponse{}, err
|
|
}
|
|
|
|
resp, err := s.client.Do(req)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to get fetch result response",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.BaseResultResponse{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
s.mongoLogger.Error(
|
|
"Unexpected status code",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Int("status_code", resp.StatusCode),
|
|
zap.Error(err),
|
|
)
|
|
return domain.BaseResultResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
}
|
|
|
|
var resultResp domain.BaseResultResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&resultResp); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to decode result",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.BaseResultResponse{}, err
|
|
}
|
|
|
|
if resultResp.Success != 1 || len(resultResp.Results) == 0 {
|
|
s.mongoLogger.Error(
|
|
"Invalid API response",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.BaseResultResponse{}, fmt.Errorf("invalid API response")
|
|
}
|
|
|
|
return resultResp, nil
|
|
}
|
|
|
|
func (s *Service) parseResult(resultResp json.RawMessage, outcome domain.BetOutcome, sportID int64) (domain.CreateResult, error) {
|
|
|
|
var result domain.CreateResult
|
|
var err error
|
|
switch sportID {
|
|
case domain.FOOTBALL:
|
|
result, err = s.parseFootball(resultResp, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse football",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.BASKETBALL:
|
|
result, err = s.parseBasketball(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse basketball",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.ICE_HOCKEY:
|
|
result, err = s.parseIceHockey(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse ice hockey",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.CRICKET:
|
|
result, err = s.parseCricket(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse cricket",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.VOLLEYBALL:
|
|
result, err = s.parseVolleyball(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse volleyball",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.DARTS:
|
|
result, err = s.parseDarts(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse darts",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.FUTSAL:
|
|
result, err = s.parseFutsal(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse futsal",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.AMERICAN_FOOTBALL:
|
|
result, err = s.parseNFL(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse american football",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.RUGBY_UNION:
|
|
result, err = s.parseRugbyUnion(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse rugby_union",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.RUGBY_LEAGUE:
|
|
result, err = s.parseRugbyLeague(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse rugby_league",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
case domain.BASEBALL:
|
|
result, err = s.parseBaseball(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to parse baseball",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
default:
|
|
s.mongoLogger.Error(
|
|
"Unsupported sport",
|
|
zap.Int64("event id", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("sport_id", sportID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, fmt.Errorf("unsupported sport: %v", sportID)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Service) parseFootball(resultRes json.RawMessage, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var fbResp domain.FootballResultResponse
|
|
if err := json.Unmarshal(resultRes, &fbResp); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal football result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
result := fbResp
|
|
|
|
finalScore := parseSS(result.SS)
|
|
firstHalfScore := parseScore(result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away)
|
|
secondHalfScore := parseScore(result.Scores.SecondHalf.Home, result.Scores.SecondHalf.Away)
|
|
|
|
corners := parseStats(result.Stats.Corners)
|
|
halfTimeCorners := parseStats(result.Stats.HalfTimeCorners)
|
|
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, result.Events)
|
|
if err != nil {
|
|
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate football outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
return domain.CreateResult{
|
|
BetOutcomeID: 0,
|
|
EventID: outcome.EventID,
|
|
OddID: outcome.OddID,
|
|
MarketID: outcome.MarketID,
|
|
Status: status,
|
|
Score: result.SS,
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (s *Service) parseBasketball(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var basketBallRes domain.BasketballResultResponse
|
|
|
|
if err := json.Unmarshal(response, &basketBallRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal basketball result", "event_id", eventID, "error", err)
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal basketball result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
status, err := s.evaluateBasketballOutcome(outcome, basketBallRes)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate basketball outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
return domain.CreateResult{
|
|
BetOutcomeID: 0,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
Score: basketBallRes.SS,
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (s *Service) parseIceHockey(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var iceHockeyRes domain.IceHockeyResultResponse
|
|
if err := json.Unmarshal(response, &iceHockeyRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "event_id", eventID, "error", err)
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal ice hockey result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
status, err := s.evaluateIceHockeyOutcome(outcome, iceHockeyRes)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate ice hockey outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
return domain.CreateResult{
|
|
BetOutcomeID: 0,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
Score: iceHockeyRes.SS,
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (s *Service) parseCricket(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var cricketRes domain.CricketResultResponse
|
|
|
|
if err := json.Unmarshal(response, &cricketRes); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal cricket result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
|
|
return domain.CreateResult{}, err
|
|
}
|
|
if cricketRes.TimeStatus != "3" {
|
|
s.mongoLogger.Warn(
|
|
"Match not yet completed",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.String("TimeStatus", cricketRes.TimeStatus),
|
|
)
|
|
|
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
|
}
|
|
|
|
status, err := s.evaluateCricketOutcome(outcome, cricketRes)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate cricket outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
return domain.CreateResult{
|
|
BetOutcomeID: 0,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) parseVolleyball(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var volleyballRes domain.VolleyballResultResponse
|
|
|
|
if err := json.Unmarshal(response, &volleyballRes); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal volleyball result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
if volleyballRes.TimeStatus != "3" {
|
|
s.mongoLogger.Warn(
|
|
"Match not yet completed",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.String("TimeStatus", volleyballRes.TimeStatus),
|
|
)
|
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
|
}
|
|
|
|
status, err := s.evaluateVolleyballOutcome(outcome, volleyballRes)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate volleyball outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
return domain.CreateResult{
|
|
BetOutcomeID: 0,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (s *Service) parseDarts(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var dartsRes domain.DartsResultResponse
|
|
|
|
if err := json.Unmarshal(response, &dartsRes); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal darts result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
if dartsRes.TimeStatus != "3" {
|
|
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
|
s.mongoLogger.Warn(
|
|
"Match not yet completed",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.String("TimeStatus", dartsRes.TimeStatus),
|
|
)
|
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
|
}
|
|
|
|
// result for dart is limited
|
|
// only ss is given, format with 2-4
|
|
status, err := s.evaluateDartsOutcome(outcome, dartsRes)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate darts outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
return domain.CreateResult{
|
|
BetOutcomeID: 0,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (s *Service) parseFutsal(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var futsalRes domain.FutsalResultResponse
|
|
|
|
if err := json.Unmarshal(response, &futsalRes); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal futsal result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
if futsalRes.TimeStatus != "3" {
|
|
s.mongoLogger.Warn(
|
|
"Match not yet completed",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.String("TimeStatus", futsalRes.TimeStatus),
|
|
)
|
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
|
}
|
|
|
|
status, err := s.evaluateFutsalOutcome(outcome, futsalRes)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate futsal outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
return domain.CreateResult{
|
|
BetOutcomeID: 0,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (s *Service) parseNFL(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var nflResp domain.NFLResultResponse
|
|
if err := json.Unmarshal(resultRes, &nflResp); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal NFL result",
|
|
zap.Int64("eventID", eventID),
|
|
zap.Error(err),
|
|
)
|
|
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
if nflResp.TimeStatus != "3" {
|
|
s.mongoLogger.Warn(
|
|
"Match not yet completed",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.String("TimeStatus", nflResp.TimeStatus),
|
|
)
|
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
|
}
|
|
|
|
status, err := s.evaluateNFLOutcome(outcome, nflResp)
|
|
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate NFL outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
|
|
return domain.CreateResult{
|
|
BetOutcomeID: outcome.ID,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
Score: nflResp.SS,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) parseRugbyUnion(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var rugbyResp domain.RugbyResultResponse
|
|
if err := json.Unmarshal(resultRes, &rugbyResp); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal Rugby Union result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
|
|
return domain.CreateResult{}, err
|
|
}
|
|
if rugbyResp.TimeStatus != "3" {
|
|
s.mongoLogger.Warn(
|
|
"Match not yet completed",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.String("TimeStatus", rugbyResp.TimeStatus),
|
|
)
|
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
|
}
|
|
status, err := s.evaluateRugbyOutcome(outcome, rugbyResp)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate rugby union outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
return domain.CreateResult{
|
|
BetOutcomeID: outcome.ID,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
Score: rugbyResp.SS,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) parseRugbyLeague(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var rugbyResp domain.RugbyResultResponse
|
|
if err := json.Unmarshal(resultRes, &rugbyResp); err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal Rugby League result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
|
|
return domain.CreateResult{}, err
|
|
}
|
|
if rugbyResp.TimeStatus != "3" {
|
|
s.mongoLogger.Warn(
|
|
"Match not yet completed",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.String("TimeStatus", rugbyResp.TimeStatus),
|
|
)
|
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
|
}
|
|
status, err := s.evaluateRugbyOutcome(outcome, rugbyResp)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate Rugby League outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
return domain.CreateResult{
|
|
BetOutcomeID: outcome.ID,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
Score: rugbyResp.SS,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) parseBaseball(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
|
var baseballResp domain.BaseballResultResponse
|
|
if err := json.Unmarshal(resultRes, &baseballResp); err != nil {
|
|
s.logger.Error("Failed to unmarshal Baseball result", "event_id", eventID, "error", err)
|
|
s.mongoLogger.Error(
|
|
"Failed to unmarshal Baseball result",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Error(err),
|
|
)
|
|
|
|
return domain.CreateResult{}, err
|
|
}
|
|
if baseballResp.TimeStatus != "3" {
|
|
s.mongoLogger.Warn(
|
|
"Match not yet completed",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.String("TimeStatus", baseballResp.TimeStatus),
|
|
)
|
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
|
}
|
|
status, err := s.evaluateBaseballOutcome(outcome, baseballResp)
|
|
if err != nil {
|
|
s.mongoLogger.Error(
|
|
"Failed to evaluate baseball outcome",
|
|
zap.Int64("eventID", outcome.EventID),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateResult{}, err
|
|
}
|
|
return domain.CreateResult{
|
|
BetOutcomeID: outcome.ID,
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
Status: status,
|
|
Score: baseballResp.SS,
|
|
}, nil
|
|
}
|
|
|
|
func parseScore(home string, away string) struct{ Home, Away int } {
|
|
homeVal, _ := strconv.Atoi(strings.TrimSpace(home))
|
|
awaVal, _ := strconv.Atoi(strings.TrimSpace(away))
|
|
return struct{ Home, Away int }{Home: homeVal, Away: awaVal}
|
|
}
|
|
|
|
func parseSS(scoreStr string) struct{ Home, Away int } {
|
|
parts := strings.Split(scoreStr, "-")
|
|
if len(parts) != 2 {
|
|
return struct{ Home, Away int }{0, 0}
|
|
}
|
|
home, _ := strconv.Atoi(strings.TrimSpace(parts[0]))
|
|
away, _ := strconv.Atoi(strings.TrimSpace(parts[1]))
|
|
return struct{ Home, Away int }{Home: home, Away: away}
|
|
}
|
|
|
|
func parseStats(stats []string) struct{ Home, Away int } {
|
|
if len(stats) != 2 {
|
|
return struct{ Home, Away int }{0, 0}
|
|
}
|
|
home, _ := strconv.Atoi(stats[0])
|
|
away, _ := strconv.Atoi(stats[1])
|
|
return struct{ Home, Away int }{Home: home, Away: away}
|
|
}
|
|
|
|
// evaluateOutcome determines the outcome status based on market type and odd
|
|
func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore,
|
|
firstHalfScore struct{ Home, Away int }, secondHalfScore struct{ Home, Away int },
|
|
corners struct{ Home, Away int }, halfTimeCorners struct{ Home, Away int },
|
|
events []map[string]string) (domain.OutcomeStatus, error) {
|
|
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
|
|
switch outcome.MarketID {
|
|
case int64(domain.FOOTBALL_FULL_TIME_RESULT):
|
|
return evaluateFullTimeResult(outcome, finalScore)
|
|
case int64(domain.FOOTBALL_GOALS_OVER_UNDER):
|
|
return evaluateGoalsOverUnder(outcome, finalScore)
|
|
case int64(domain.FOOTBALL_CORRECT_SCORE):
|
|
return evaluateCorrectScore(outcome, finalScore)
|
|
case int64(domain.FOOTBALL_HALF_TIME_RESULT):
|
|
return evaluateHalfTimeResult(outcome, firstHalfScore)
|
|
case int64(domain.FOOTBALL_ASIAN_HANDICAP):
|
|
return evaluateAsianHandicap(outcome, finalScore)
|
|
case int64(domain.FOOTBALL_GOAL_LINE):
|
|
return evaluateGoalLine(outcome, finalScore)
|
|
case int64(domain.FOOTBALL_FIRST_HALF_ASIAN_HANDICAP):
|
|
return evaluateAsianHandicap(outcome, firstHalfScore)
|
|
case int64(domain.FOOTBALL_FIRST_HALF_GOAL_LINE):
|
|
return evaluateGoalLine(outcome, firstHalfScore)
|
|
case int64(domain.FOOTBALL_FIRST_TEAM_TO_SCORE):
|
|
return evaluateFirstTeamToScore(outcome, events)
|
|
case int64(domain.FOOTBALL_GOALS_ODD_EVEN):
|
|
return evaluateGoalsOddEven(outcome, finalScore)
|
|
case int64(domain.FOOTBALL_DOUBLE_CHANCE):
|
|
return evaluateDoubleChance(outcome, finalScore)
|
|
case int64(domain.FOOTBALL_DRAW_NO_BET):
|
|
return evaluateDrawNoBet(outcome, finalScore)
|
|
case int64(domain.FOOTBALL_CORNERS):
|
|
return evaluateCorners(outcome, corners)
|
|
case int64(domain.FOOTBALL_CORNERS_TWO_WAY):
|
|
return evaluateCorners(outcome, corners)
|
|
case int64(domain.FOOTBALL_FIRST_HALF_CORNERS):
|
|
return evaluateCorners(outcome, halfTimeCorners)
|
|
case int64(domain.FOOTBALL_ASIAN_TOTAL_CORNERS):
|
|
return evaluateCorners(outcome, corners)
|
|
case int64(domain.FOOTBALL_FIRST_HALF_ASIAN_CORNERS):
|
|
return evaluateCorners(outcome, halfTimeCorners)
|
|
case int64(domain.FOOTBALL_FIRST_HALF_GOALS_ODD_EVEN):
|
|
return evaluateGoalsOddEven(outcome, firstHalfScore)
|
|
case int64(domain.FOOTBALL_SECOND_HALF_GOALS_ODD_EVEN):
|
|
return evaluateGoalsOddEven(outcome, secondHalfScore)
|
|
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
}
|
|
}
|
|
|
|
func (s *Service) evaluateBasketballOutcome(outcome domain.BetOutcome, res domain.BasketballResultResponse) (domain.OutcomeStatus, error) {
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
|
|
finalScore := parseSS(res.SS)
|
|
|
|
firstHalfScore := parseScore(res.Scores.FirstHalf.Home, res.Scores.FirstHalf.Away)
|
|
secondHalfScore := struct{ Home, Away int }{Home: finalScore.Home - firstHalfScore.Home, Away: finalScore.Away - firstHalfScore.Away}
|
|
|
|
firstQuarter := parseScore(res.Scores.FirstQuarter.Home, res.Scores.FirstQuarter.Away)
|
|
secondQuarter := parseScore(res.Scores.SecondQuarter.Home, res.Scores.SecondQuarter.Away)
|
|
thirdQuarter := parseScore(res.Scores.ThirdQuarter.Home, res.Scores.ThirdQuarter.Away)
|
|
fourthQuarter := parseScore(res.Scores.FourthQuarter.Home, res.Scores.FourthQuarter.Away)
|
|
|
|
switch outcome.MarketID {
|
|
case int64(domain.BASKETBALL_GAME_LINES):
|
|
return evaluateGameLines(outcome, finalScore)
|
|
case int64(domain.BASKETBALL_RESULT_AND_BOTH_TEAMS_TO_SCORE_X_POINTS):
|
|
return evaluateResultAndBTTSX(outcome, finalScore)
|
|
case int64(domain.BASKETBALL_DOUBLE_RESULT):
|
|
return evaluateDoubleResult(outcome, firstHalfScore, secondHalfScore)
|
|
case int64(domain.BASKETBALL_MATCH_RESULT_AND_TOTAL):
|
|
return evaluateResultAndTotal(outcome, finalScore)
|
|
case int64(domain.BASKETBALL_MATCH_HANDICAP_AND_TOTAL):
|
|
return evaluateHandicapAndTotal(outcome, finalScore)
|
|
case int64(domain.BASKETBALL_GAME_TOTAL_ODD_EVEN):
|
|
return evaluateGoalsOddEven(outcome, finalScore)
|
|
case int64(domain.BASKETBALL_TEAM_TOTALS):
|
|
return evaluateTeamTotal(outcome, finalScore)
|
|
case int64(domain.BASKETBALL_TEAM_TOTAL_ODD_EVEN):
|
|
return evaluateTeamOddEven(outcome, finalScore)
|
|
|
|
case int64(domain.BASKETBALL_FIRST_HALF):
|
|
return evaluateGameLines(outcome, firstHalfScore)
|
|
case int64(domain.BASKETBALL_FIRST_HALF_TEAM_TOTALS):
|
|
return evaluateTeamTotal(outcome, firstHalfScore)
|
|
case int64(domain.BASKETBALL_FIRST_HALF_HANDICAP_AND_TOTAL):
|
|
return evaluateHandicapAndTotal(outcome, firstHalfScore)
|
|
case int64(domain.BASKETBALL_FIRST_HALF_BOTH_TEAMS_TO_SCORE_X_POINTS):
|
|
return evaluateBTTSX(outcome, firstHalfScore)
|
|
case int64(domain.BASKETBALL_FIRST_HALF_DOUBLE_CHANCE):
|
|
return evaluateDoubleChance(outcome, firstHalfScore)
|
|
case int64(domain.BASKETBALL_FIRST_HALF_TOTAL_ODD_EVEN):
|
|
return evaluateGoalsOddEven(outcome, firstHalfScore)
|
|
case int64(domain.BASKETBALL_FIRST_HALF_MONEY_LINE_3_WAY):
|
|
return evaluateMoneyLine3Way(outcome, firstHalfScore)
|
|
case int64(domain.BASKETBALL_HIGHEST_SCORING_HALF):
|
|
return evaluateHighestScoringHalf(outcome, firstHalfScore, secondHalfScore)
|
|
|
|
case int64(domain.BASKETBALL_FIRST_QUARTER):
|
|
return evaluateGameLines(outcome, firstQuarter)
|
|
case int64(domain.BASKETBALL_FIRST_QUARTER_TEAM_TOTALS):
|
|
return evaluateTeamTotal(outcome, firstQuarter)
|
|
case int64(domain.BASKETBALL_FIRST_QUARTER_TOTAL_ODD_EVEN):
|
|
return evaluateGoalsOddEven(outcome, firstQuarter)
|
|
case int64(domain.BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL):
|
|
return evaluateHandicapAndTotal(outcome, firstQuarter)
|
|
case int64(domain.BASKETBALL_FIRST_QUARTER_DOUBLE_CHANCE):
|
|
return evaluateDoubleChance(outcome, firstQuarter)
|
|
case int64(domain.BASKETBALL_HIGHEST_SCORING_QUARTER):
|
|
return evaluateHighestScoringQuarter(outcome, firstQuarter, secondQuarter, thirdQuarter, fourthQuarter)
|
|
case int64(domain.BASKETBALL_FIRST_QUARTER_RESULT_AND_TOTAL):
|
|
return evaluateResultAndTotal(outcome, firstQuarter)
|
|
|
|
case int64(domain.BASKETBALL_TEAM_WITH_HIGHEST_SCORING_QUARTER):
|
|
return evaluateTeamWithHighestScoringQuarter(outcome, firstQuarter, secondQuarter, thirdQuarter, fourthQuarter)
|
|
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
}
|
|
}
|
|
|
|
func (s *Service) evaluateIceHockeyOutcome(outcome domain.BetOutcome, res domain.IceHockeyResultResponse) (domain.OutcomeStatus, error) {
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
finalScore := parseSS(res.SS)
|
|
firstPeriod := parseScore(res.Scores.FirstPeriod.Home, res.Scores.FirstPeriod.Away)
|
|
secondPeriod := parseScore(res.Scores.SecondPeriod.Home, res.Scores.SecondPeriod.Away)
|
|
thirdPeriod := parseScore(res.Scores.ThirdPeriod.Home, res.Scores.ThirdPeriod.Away)
|
|
|
|
regulation := []struct{ Home, Away int }{
|
|
firstPeriod,
|
|
secondPeriod,
|
|
thirdPeriod,
|
|
}
|
|
switch outcome.MarketID {
|
|
case int64(domain.ICE_HOCKEY_GAME_LINES):
|
|
return evaluateGameLines(outcome, finalScore)
|
|
case int64(domain.ICE_HOCKEY_THREE_WAY):
|
|
return evaluateGameLines(outcome, finalScore)
|
|
case int64(domain.ICE_HOCKEY_FIRST_PERIOD):
|
|
return evaluateGameLines(outcome, firstPeriod)
|
|
case int64(domain.ICE_HOCKEY_DRAW_NO_BET):
|
|
return evaluateDrawNoBet(outcome, finalScore)
|
|
case int64(domain.ICE_HOCKEY_DOUBLE_CHANCE):
|
|
return evaluateDoubleChance(outcome, finalScore)
|
|
case int64(domain.ICE_HOCKEY_WINNING_MARGIN):
|
|
return evaluateWinningMargin(outcome, finalScore)
|
|
case int64(domain.ICE_HOCKEY_HIGHEST_SCORING_PERIOD):
|
|
return evaluateHighestScoringPeriod(outcome, firstPeriod, secondPeriod, thirdPeriod)
|
|
case int64(domain.ICE_HOCKEY_TIED_AFTER_REGULATION):
|
|
return evaluateTiedAfterRegulation(outcome, regulation)
|
|
case int64(domain.ICE_HOCKEY_GAME_TOTAL_ODD_EVEN):
|
|
return evaluateGoalsOddEven(outcome, finalScore)
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
}
|
|
}
|
|
|
|
func (s *Service) evaluateCricketOutcome(outcome domain.BetOutcome, res domain.CricketResultResponse) (domain.OutcomeStatus, error) {
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
|
|
score := parseSS(res.SS)
|
|
|
|
switch outcome.MarketID {
|
|
case int64(domain.CRICKET_TO_WIN_THE_MATCH):
|
|
return evaluateFullTimeResult(outcome, score)
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
}
|
|
|
|
}
|
|
|
|
func (s *Service) evaluateVolleyballOutcome(outcome domain.BetOutcome, res domain.VolleyballResultResponse) (domain.OutcomeStatus, error) {
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
|
|
score := parseSS(res.SS)
|
|
|
|
// res.SS example: { 2-3 } is the win count not actuall score of sets
|
|
// for total score we need every set's score
|
|
firstSet := parseScore(res.Scores.FirstSet.Home, res.Scores.FirstSet.Away)
|
|
secondSet := parseScore(res.Scores.SecondSet.Home, res.Scores.SecondSet.Away)
|
|
thirdSet := parseScore(res.Scores.ThirdSet.Home, res.Scores.ThirdSet.Away)
|
|
fourthSet := parseScore(res.Scores.FourthSet.Home, res.Scores.FourthSet.Away)
|
|
fivethSet := parseScore(res.Scores.FivethSet.Home, res.Scores.FivethSet.Away)
|
|
|
|
totalScore := struct{ Home, Away int }{Home: 0, Away: 0}
|
|
totalScore.Home = firstSet.Home + secondSet.Home + thirdSet.Home + fourthSet.Home + fivethSet.Home
|
|
totalScore.Away = firstSet.Away + secondSet.Away + thirdSet.Away + fourthSet.Away + fivethSet.Away
|
|
|
|
switch outcome.MarketID {
|
|
case int64(domain.VOLLEYBALL_GAME_LINES):
|
|
return evaluateVolleyballGamelines(outcome, totalScore)
|
|
case int64(domain.VOLLEYBALL_MATCH_TOTAL_ODD_EVEN):
|
|
return evaluateGoalsOddEven(outcome, totalScore)
|
|
case int64(domain.VOLLEYBALL_CORRECT_SET_SCORE):
|
|
return evaluateCorrectScore(outcome, score)
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (s *Service) evaluateDartsOutcome(outcome domain.BetOutcome, res domain.DartsResultResponse) (domain.OutcomeStatus, error) {
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
|
|
score := parseSS(res.SS)
|
|
|
|
switch outcome.MarketID {
|
|
case int64(domain.DARTS_MATCH_WINNER):
|
|
return evaluateFullTimeResult(outcome, score)
|
|
case int64(domain.DARTS_TOTAL_LEGS):
|
|
return evaluateTotalLegs(outcome, score)
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (s *Service) evaluateFutsalOutcome(outcome domain.BetOutcome, res domain.FutsalResultResponse) (domain.OutcomeStatus, error) {
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
|
|
score := parseSS(res.SS)
|
|
|
|
switch outcome.MarketID {
|
|
case int64(domain.FUTSAL_GAME_LINES):
|
|
return evaluateGameLines(outcome, score)
|
|
case int64(domain.FUTSAL_MONEY_LINE):
|
|
return evaluateMoneyLine(outcome, score)
|
|
case int64(domain.FUTSAL_TEAM_TO_SCORE_FIRST):
|
|
return evaluateFirstTeamToScore(outcome, res.Events)
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (s *Service) evaluateNFLOutcome(outcome domain.BetOutcome, res domain.NFLResultResponse) (domain.OutcomeStatus, error) {
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
|
|
finalScore := parseSS(res.SS)
|
|
|
|
switch outcome.MarketID {
|
|
case int64(domain.AMERICAN_FOOTBALL_GAME_LINES):
|
|
return evaluateGameLines(outcome, finalScore)
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
}
|
|
}
|
|
|
|
func (s *Service) evaluateRugbyOutcome(outcome domain.BetOutcome, result domain.RugbyResultResponse) (domain.OutcomeStatus, error) {
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
|
|
finalScore := parseSS(result.SS)
|
|
|
|
switch outcome.MarketID {
|
|
case int64(domain.RUGBY_L_GAME_BETTING_2_WAY):
|
|
return evaluateGameBettingTwoWay(outcome, finalScore)
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
}
|
|
}
|
|
|
|
func (s *Service) evaluateBaseballOutcome(outcome domain.BetOutcome, res domain.BaseballResultResponse) (domain.OutcomeStatus, error) {
|
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
|
s.mongoLogger.Warn(
|
|
"Unsupported market type",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
|
}
|
|
|
|
finalScore := parseSS(res.SS)
|
|
|
|
switch outcome.MarketID {
|
|
case int64(domain.BASEBALL_GAME_LINES):
|
|
return evaluateGameLines(outcome, finalScore)
|
|
default:
|
|
s.mongoLogger.Warn(
|
|
"Market type not implemented",
|
|
zap.String("market_name", outcome.MarketName),
|
|
zap.Int64("market_id", outcome.MarketID),
|
|
zap.Int64("outcome_id", outcome.ID),
|
|
)
|
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
|
}
|
|
}
|