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

1354 lines
51 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/notfication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
)
type Service struct {
repo *repository.Store
config *config.Config
logger *slog.Logger
client *http.Client
betSvc bet.Service
oddSvc odds.ServiceImpl
eventSvc event.Service
leagueSvc league.Service
notificationSvc *notificationservice.Service
}
func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger, betSvc bet.Service, oddSvc odds.ServiceImpl, eventSvc event.Service, leagueSvc league.Service, notificationSvc *notificationservice.Service) *Service {
return &Service{
repo: repo,
config: cfg,
logger: logger,
client: &http.Client{Timeout: 10 * time.Second},
betSvc: betSvc,
oddSvc: oddSvc,
eventSvc: eventSvc,
leagueSvc: leagueSvc,
notificationSvc: notificationSvc,
}
}
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.logger.Error("Failed to get pending bet outcomes", "error", err)
return fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err)
}
if len(outcomes) == 0 {
s.logger.Info("No bets have been placed on event", "event", eventID)
}
// if len(outcomes) == 0 {
// fmt.Printf("🕛 No bets have been placed on event %v (%d/%d) \n", eventID, i+1, len(events))
// } else {
// fmt.Printf("✅ %d bets have been placed on event %v (%d/%d) \n", len(outcomes), event.ID, i+1, len(events))
// }
for _, outcome := range outcomes {
if outcome.Expires.After(time.Now()) {
s.logger.Warn("Outcome is not expired yet", "event_id", outcome.EventID, "outcome_id", outcome.ID)
return fmt.Errorf("Outcome has not expired yet")
}
parseResult, err := s.parseResult(resultRes, outcome, sportID)
if err != nil {
s.logger.Error("Failed to parse result", "event_id", outcome.EventID, "outcome_id", outcome.ID, "error", err)
return err
}
outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, parseResult.Status)
if err != nil {
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
return err
}
if outcome.Status == domain.OUTCOME_STATUS_ERROR || outcome.Status == domain.OUTCOME_STATUS_PENDING {
s.logger.Error("Outcome is pending or error", "event_id", outcome.EventID, "outcome_id", outcome.ID)
return fmt.Errorf("Error while updating outcome")
}
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
}
s.logger.Info("Updating bet status", "bet_id", outcome.BetID, "status", status.String())
err = s.betSvc.UpdateStatus(ctx, outcome.BetID, status)
if err != nil {
s.logger.Error("Failed to update bet status", "event id", outcome.EventID, "error", err)
return err
}
}
return nil
}
func (s *Service) RefundAllOutcomes(ctx context.Context, eventID int64) error {
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID, false)
if err != nil {
s.logger.Error("Failed to get pending bet outcomes", "error", err)
return fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err)
}
if len(outcomes) == 0 {
s.logger.Info("No bets have been placed on event", "event", eventID)
}
outcomes, err = s.betSvc.UpdateBetOutcomeStatusForEvent(ctx, eventID, domain.OUTCOME_STATUS_VOID)
if err != nil {
s.logger.Error("Failed to update all outcomes for event")
}
// 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.logger.Error("Failed to check bet outcome for bet", "event_id", eventID, "error", err)
}
return err
}
err = s.betSvc.UpdateStatus(ctx, betID, status)
if err != nil {
s.logger.Error("Failed to update bet status", "event id", eventID, "error", err)
continue
}
}
return 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")
return err
}
fmt.Printf("⚠️ Expired Events: %d \n", len(events))
removed := 0
for _, event := range events {
eventID, err := strconv.ParseInt(event.ID, 10, 64)
if err != nil {
s.logger.Error("Failed to parse", "eventID", event.ID, "err", 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)
continue
}
s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
continue
}
var commonResp domain.CommonResultResponse
if err := json.Unmarshal(result.Results[0], &commonResp); err != nil {
s.logger.Error("Failed to unmarshal common result", "event_id", eventID, "error", err)
continue
}
sportID, err := strconv.ParseInt(commonResp.SportID, 10, 64)
if err != nil {
s.logger.Error("Failed to parse sport id", "event_id", eventID, "error", err)
continue
}
timeStatusParsed, err := strconv.ParseInt(strings.TrimSpace(commonResp.TimeStatus), 10, 64)
if err != nil {
s.logger.Error("Failed to parse time status", "time_status", commonResp.TimeStatus, "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
// }
// notification := &domain.Notification{
// RecipientID: recipientID,
// Type: domain.NOTIFICATION_TYPE_WALLET,
// Level: domain.NotificationLevelWarning,
// Reciever: domain.NotificationRecieverSideAdmin,
// DeliveryChannel: domain.DeliveryChannelInApp,
// Payload: domain.NotificationPayload{
// Headline: "Wallet Threshold Alert",
// Message: message,
// },
// Priority: 2, // Medium priority
// }
switch timeStatusParsed {
case int64(domain.TIME_STATUS_NOT_STARTED), int64(domain.TIME_STATUS_IN_PLAY):
continue
case int64(domain.TIME_STATUS_TO_BE_FIXED):
s.logger.Warn("Event needs to be rescheduled or corrected", "event_id", eventID)
// Admin users will be able to review the events
case int64(domain.TIME_STATUS_ENDED), int64(domain.TIME_STATUS_WALKOVER), int64(domain.TIME_STATUS_DECIDED_BY_FA):
err = s.UpdateResultForOutcomes(ctx, eventID, result.Results[0], sportID)
if err != nil {
s.logger.Error("Error while updating result for event", "event_id", eventID)
}
s.logger.Info("Removing Event", "eventID", event.ID)
err = s.repo.DeleteEvent(ctx, event.ID)
if err != nil {
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
return err
}
err = s.repo.DeleteOddsForEvent(ctx, event.ID)
if err != nil {
s.logger.Error("Failed to remove odds for event", "event_id", event.ID, "error", err)
return err
}
removed += 1
case int64(domain.TIME_STATUS_ABANDONED), int64(domain.TIME_STATUS_CANCELLED), int64(domain.TIME_STATUS_REMOVED):
s.logger.Info("Event abandoned/cancelled/removed", "event_id", eventID, "status", timeStatusParsed)
err = s.RefundAllOutcomes(ctx, eventID)
s.logger.Info("Removing Event", "eventID", event.ID)
err = s.repo.DeleteEvent(ctx, event.ID)
if err != nil {
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
return err
}
err = s.repo.DeleteOddsForEvent(ctx, event.ID)
if err != nil {
s.logger.Error("Failed to remove odds for event", "event_id", event.ID, "error", err)
return err
}
removed += 1
}
// for j, outcome := range outcomes {
// fmt.Printf("⚙️ Processing 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
// outcome.MarketName,
// event.HomeTeam+" "+event.AwayTeam, event.ID,
// j+1, len(outcomes))
// if outcome.Expires.After(time.Now()) {
// isDeleted = false
// s.logger.Warn("Outcome is not expired yet", "event_id", event.ID, "outcome_id", outcome.ID)
// continue
// }
// parseResult, err := s.parseResult(ctx, result.Results[0], outcome, sportID)
// if err != nil {
// isDeleted = false
// s.logger.Error("Failed to parse result", "event_id", outcome.EventID, "outcome_id", outcome.ID, "error", err)
// errs = append(errs, fmt.Errorf("failed to parse result for event %d: %w", outcome.EventID, err))
// continue
// }
// outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, parseResult.Status)
// if err != nil {
// isDeleted = false
// s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
// continue
// }
// if outcome.Status == domain.OUTCOME_STATUS_ERROR || outcome.Status == domain.OUTCOME_STATUS_PENDING {
// fmt.Printf("❌ Error while updating 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
// outcome.MarketName,
// event.HomeTeam+" "+event.AwayTeam, event.ID,
// j+1, len(outcomes))
// s.logger.Error("Outcome is pending or error", "event_id", outcome.EventID, "outcome_id", outcome.ID)
// isDeleted = false
// continue
// }
// fmt.Printf("✅ Successfully updated 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
// outcome.MarketName,
// event.HomeTeam+" "+event.AwayTeam, event.ID,
// j+1, len(outcomes))
// 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)
// }
// isDeleted = false
// continue
// }
// fmt.Printf("🧾 Updating bet %v - event %v (%d/%d) to %v\n", outcome.BetID, event.ID, j+1, len(outcomes), status.String())
// err = s.betSvc.UpdateStatus(ctx, outcome.BetID, status)
// if err != nil {
// s.logger.Error("Failed to update bet status", "event id", outcome.EventID, "error", err)
// isDeleted = false
// continue
// }
// fmt.Printf("✅ Successfully updated 🎫 Bet %v - event %v(%v) (%d/%d) \n",
// outcome.BetID,
// event.HomeTeam+" "+event.AwayTeam, event.ID,
// j+1, len(outcomes))
// }
}
s.logger.Info("Total Number of Removed Events", "count", removed)
s.logger.Info("Successfully processed results", "removed_events", removed, "total_events", len(events))
return nil
}
func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error) {
events, err := s.repo.GetExpiredUpcomingEvents(ctx, domain.EventFilter{})
if err != nil {
s.logger.Error("Failed to fetch events")
return 0, err
}
updated := 0
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.logger.Error("Failed to parse event id")
continue
}
if event.Status == domain.STATUS_REMOVED {
s.logger.Info("Skipping updating removed event")
continue
}
result, err := s.fetchResult(ctx, eventID)
if err != nil {
s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
continue
}
if result.Success != 1 || len(result.Results) == 0 {
s.logger.Error("Invalid API response", "event_id", eventID)
continue
}
var commonResp domain.CommonResultResponse
if err := json.Unmarshal(result.Results[0], &commonResp); err != nil {
s.logger.Error("Failed to unmarshal common result", "event_id", eventID, "error", err)
continue
}
var eventStatus domain.EventStatus
// TODO Change event status to int64 enum
timeStatus, err := strconv.ParseInt(strings.TrimSpace(commonResp.TimeStatus), 10, 64)
switch timeStatus {
case int64(domain.TIME_STATUS_NOT_STARTED):
eventStatus = domain.STATUS_PENDING
case int64(domain.TIME_STATUS_IN_PLAY):
eventStatus = domain.STATUS_IN_PLAY
case int64(domain.TIME_STATUS_TO_BE_FIXED):
eventStatus = domain.STATUS_TO_BE_FIXED
case int64(domain.TIME_STATUS_ENDED):
eventStatus = domain.STATUS_ENDED
case int64(domain.TIME_STATUS_POSTPONED):
eventStatus = domain.STATUS_POSTPONED
case int64(domain.TIME_STATUS_CANCELLED):
eventStatus = domain.STATUS_CANCELLED
case int64(domain.TIME_STATUS_WALKOVER):
eventStatus = domain.STATUS_WALKOVER
case int64(domain.TIME_STATUS_INTERRUPTED):
eventStatus = domain.STATUS_INTERRUPTED
case int64(domain.TIME_STATUS_ABANDONED):
eventStatus = domain.STATUS_ABANDONED
case int64(domain.TIME_STATUS_RETIRED):
eventStatus = domain.STATUS_RETIRED
case int64(domain.TIME_STATUS_SUSPENDED):
eventStatus = domain.STATUS_SUSPENDED
case int64(domain.TIME_STATUS_DECIDED_BY_FA):
eventStatus = domain.STATUS_DECIDED_BY_FA
case int64(domain.TIME_STATUS_REMOVED):
eventStatus = domain.STATUS_REMOVED
default:
s.logger.Error("Invalid time status", "time_status", commonResp.TimeStatus, "event_id", eventID)
}
err = s.eventSvc.UpdateFinalScore(ctx, strconv.FormatInt(eventID, 10), commonResp.SS, eventStatus)
if err != nil {
s.logger.Error("Failed to update final score", "event_id", eventID, "error", err)
continue
}
updated++
// fmt.Printf("✅ Successfully updated event %v to %v (%d/%d) \n", event.ID, eventStatus, i+1, len(events))
s.logger.Info("Updated Event Status", "event ID", event.ID, "status", eventStatus)
// Update the league because the league country code is only found on the result response
leagueID, err := strconv.ParseInt(commonResp.League.ID, 10, 64)
if err != nil {
// log.Printf("❌ Invalid league id, leagueID %v", commonResp.League.ID)
s.logger.Error("Invalid League ID", "leagueID", commonResp.League.ID)
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.logger.Error("Error Updating League", "League Name", commonResp.League.Name, "err", 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)
}
if updated == 0 {
s.logger.Info("No events were updated")
return 0, nil
}
s.logger.Info("Successfully updated live events", "updated_events", updated, "total_events", len(events))
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.logger.Error("Failed to parse event id")
return json.RawMessage{}, nil, err
}
result, err := s.fetchResult(ctx, id)
if err != nil {
s.logger.Error("Failed to fetch result", "event_id", id, "error", err)
}
if result.Success != 1 || len(result.Results) == 0 {
fmt.Printf("⚠️ Invalid API response for event %v \n", result)
s.logger.Error("Invalid API response", "event_id", id)
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.logger.Error("Failed to unmarshal common result", "event_id", eventID, "error", err)
return json.RawMessage{}, nil, err
}
sportID, err := strconv.ParseInt(commonResp.SportID, 10, 32)
if err != nil {
s.logger.Error("Failed to parse sport id", "event_id", eventID, "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.logger.Error("Failed to parse expire time", "event_id", eventID, "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.logger.Error("Failed to fetch non-live odds by event ID", "event_id", eventID, "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.logger.Error("Failed to parse odd section", "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 {
// TODO: Remove repeat code here, same as in odds service
for _, market := range section.Sp {
var marketIDstr string
err := json.Unmarshal(market.ID, &marketIDstr)
var marketIDint int64
if err != nil {
// check if its int
err := json.Unmarshal(market.ID, &marketIDint)
if err != nil {
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
continue
}
} else {
marketIDint, err = strconv.ParseInt(marketIDstr, 10, 64)
if err != nil {
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
continue
}
}
isSupported, ok := domain.SupportedMarkets[marketIDint]
if !ok || !isSupported {
// s.logger.Info("Unsupported market_id", "marketID", marketIDint, "marketName", market.Name)
continue
}
for _, oddRes := range market.Odds {
var odd domain.RawOdd
if err := json.Unmarshal(oddRes, &odd); err != nil {
s.logger.Error("Failed to unmarshal odd", "error", err)
continue
}
oddID, err := strconv.ParseInt(odd.ID, 10, 64)
if err != nil {
s.logger.Error("Failed to parse odd id", "odd_id", odd.ID, "error", err)
continue
}
oddValue, err := strconv.ParseFloat(odd.Odds, 64)
if err != nil {
s.logger.Error("Failed to parse odd value", "odd_value", odd.Odds, "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.logger.Warn("No outcomes found for event", "event_id", eventID)
return json.RawMessage{}, nil, fmt.Errorf("no outcomes found for event %s", eventID)
}
s.logger.Info("Successfully fetched outcomes for event", "event_id", eventID, "outcomes_count", 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.logger.Error("Failed to parse result for outcome", "event_id", outcome.EventID, "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.logger.Error("Failed to create request", "event_id", eventID, "error", err)
return domain.BaseResultResponse{}, err
}
resp, err := s.client.Do(req)
if err != nil {
s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
return domain.BaseResultResponse{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
s.logger.Error("Unexpected status code", "event_id", eventID, "status_code", resp.StatusCode)
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.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
return domain.BaseResultResponse{}, err
}
if resultResp.Success != 1 || len(resultResp.Results) == 0 {
s.logger.Error("Invalid API response", "event_id", eventID)
fmt.Printf("⚠️ Invalid API response for event %v \n", resultResp)
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.logger.Error("Failed to parse football", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse basketball", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse ice hockey", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse cricket", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse volleyball", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse darts", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse futsal", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse american football", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse rugby_union", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse rugby_league", "event id", outcome.EventID, "market_id", outcome.MarketID, "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.logger.Error("Failed to parse baseball", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err
}
default:
s.logger.Error("Unsupported sport", "sport", sportID)
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.logger.Error("Failed to unmarshal football result", "event_id", outcome.EventID, "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.logger.Error("Failed to evaluate football outcome", "event_id", outcome.EventID, "market_id", outcome.MarketID, "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)
return domain.CreateResult{}, err
}
status, err := s.evaluateBasketballOutcome(outcome, basketBallRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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)
return domain.CreateResult{}, err
}
status, err := s.evaluateIceHockeyOutcome(outcome, iceHockeyRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if cricketRes.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateCricketOutcome(outcome, cricketRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if volleyballRes.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateVolleyballOutcome(outcome, volleyballRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if dartsRes.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
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.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if futsalRes.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateFutsalOutcome(outcome, futsalRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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.logger.Error("Failed to unmarshal NFL result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if nflResp.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateNFLOutcome(outcome, nflResp)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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.logger.Error("Failed to unmarshal Rugby Union result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if rugbyResp.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateRugbyOutcome(outcome, rugbyResp)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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.logger.Error("Failed to unmarshal Rugby League result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if rugbyResp.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateRugbyOutcome(outcome, rugbyResp)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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)
return domain.CreateResult{}, err
}
if baseballResp.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateBaseballOutcome(outcome, baseballResp)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "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.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
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.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
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.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
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.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
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.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
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)
}
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateCricketOutcome(outcome domain.BetOutcome, res domain.CricketResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, 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)
}
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateVolleyballOutcome(outcome domain.BetOutcome, res domain.VolleyballResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
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)
}
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateDartsOutcome(outcome domain.BetOutcome, res domain.DartsResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
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)
}
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateFutsalOutcome(outcome domain.BetOutcome, res domain.FutsalResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
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)
}
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateNFLOutcome(outcome domain.BetOutcome, res domain.NFLResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
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.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, 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.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
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:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported rugby market: %s", outcome.MarketName)
}
}
func (s *Service) evaluateBaseballOutcome(outcome domain.BetOutcome, res domain.BaseballResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
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:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported baseball market: %s", outcome.MarketName)
}
}