package result import ( "context" "encoding/json" "fmt" "log" "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/ports" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/league" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/messenger" 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 { resultLogStore ports.ResultLogStore 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 messengerSvc *messenger.Service userSvc user.Service } func NewService( resultLogStore ports.ResultLogStore, cfg *config.Config, logger *slog.Logger, mongoLogger *zap.Logger, betSvc bet.Service, oddSvc odds.ServiceImpl, eventSvc *event.Service, leagueSvc *league.Service, notificationSvc *notificationservice.Service, messengerSvc *messenger.Service, userSvc user.Service, ) *Service { return &Service{ resultLogStore: resultLogStore, config: cfg, logger: logger, mongoLogger: mongoLogger, client: &http.Client{Timeout: 10 * time.Second}, betSvc: betSvc, oddSvc: oddSvc, eventSvc: eventSvc, leagueSvc: leagueSvc, notificationSvc: notificationSvc, messengerSvc: messengerSvc, 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.betSvc.GetBetOutcomeByEventID(ctx, eventID, true) logger := s.mongoLogger.With( zap.Int64("eventID", eventID), zap.Int64("sportID", sportID), ) if err != nil { logger.Error("Failed to get pending bet outcomes", zap.Error(err)) return fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err) } for _, outcome := range outcomes { outcomeLogger := logger.With( zap.Int64("outcome_id", outcome.ID), zap.Int32("outcome_status", int32(outcome.Status)), zap.Int64("outcome_bet_id", outcome.BetID), zap.String("outcome_market_id", outcome.MarketName), ) if outcome.Expires.After(time.Now()) { outcomeLogger.Warn("Outcome is not expired yet", zap.Error(err)) // return fmt.Errorf("Outcome has not expired yet") continue } parseResult, err := s.ParseB365Result(resultRes, outcome, sportID) if err != nil { outcomeLogger.Error("Failed to parse result", zap.Error(err)) continue } outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, parseResult.Status) if err != nil { outcomeLogger.Error("Failed to update bet outcome status", zap.Error(err)) continue } if outcome.Status == domain.OUTCOME_STATUS_ERROR || outcome.Status == domain.OUTCOME_STATUS_PENDING { outcomeLogger.Error("Outcome has been updated to pending or error", zap.Error(err)) // return fmt.Errorf("error while updating outcome") continue } status, err := s.betSvc.CheckBetOutcomeForBet(ctx, outcome.BetID) if err != nil { if err != bet.ErrOutcomesNotCompleted { outcomeLogger.Error("Failed to check bet outcome for bet", zap.Error(err)) } // return err continue } outcomeLogger.Info("Updating bet status", zap.String("status", status.String())) err = s.betSvc.UpdateStatus(ctx, outcome.BetID, status) if err != nil { outcomeLogger.Error("Failed to update bet status", zap.Error(err)) // return err continue } } return nil } func (s *Service) GetTotalBetsForEvents(ctx context.Context, eventID int64) (map[int64]int64, error) { outcomes, err := s.betSvc.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) FetchB365ResultAndUpdateBets(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.eventSvc.GetAllEvents(ctx, domain.EventFilter{ LastStartTime: domain.ValidTime{ Value: time.Now(), Valid: true, }, Source: domain.ValidEventSource{ Value: domain.EVENT_SOURCE_BET365, Valid: true, }, }) if err != nil { s.logger.Error("Failed to fetch events") s.mongoLogger.Error( "[FetchAndProcessResult] Failed to fetch events", zap.Error(err), ) return err } empty_sport_id := make([]int64, 0) var resultLog domain.CreateResultLog var resultStatusBets domain.ResultStatusBets for i, event := range events { if s.config.Env == "development" { log.Printf("⚙️ Processing Bets For Event %v (%d/%d) \n", event.ID, i+1, len(events)) } eventLogger := s.mongoLogger.With( zap.Int64("eventID", event.ID), ) result, err := s.FetchB365Result(ctx, event.SourceEventID) if err != nil { if err == ErrEventIsNotActive { eventLogger.Warn("Event is not active", zap.Error(err)) continue } eventLogger.Error("Failed to fetch result", zap.Error(err)) continue } var commonResp domain.CommonResultResponse if err := json.Unmarshal(result.Results[0], &commonResp); err != nil { eventLogger.Error("Failed to unmarshal common result", zap.Error(err)) continue } timeStatusParsed := commonResp.TimeStatus.Value // 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 commonRespLogger := eventLogger.With( zap.Int64("parsed_time_status", timeStatusParsed), zap.String("response_sport_id", commonResp.SportID), ) switch timeStatusParsed { case int64(domain.TIME_STATUS_NOT_STARTED), int64(domain.TIME_STATUS_IN_PLAY): resultLog.StatusNotFinishedCount += 1 bets, err := s.GetTotalBetsForEvents(ctx, event.ID) if err != nil { continue } resultLog.StatusNotFinishedBets = len(bets) for k := range bets { resultStatusBets.StatusNotFinished = append(resultStatusBets.StatusNotFinished, k) } case int64(domain.TIME_STATUS_TO_BE_FIXED): totalBetsRefunded, err := s.RefundAllOutcomes(ctx, event.ID) if err != nil { commonRespLogger.Error("Failed to refund all outcomes", zap.Error(err)) continue } err = s.eventSvc.DeleteEvent(ctx, event.ID) if err != nil { commonRespLogger.Error("Failed to remove event", zap.Error(err)) continue } err = s.oddSvc.DeleteOddsForEvent(ctx, event.ID) if err != nil { commonRespLogger.Error("Failed to remove odds for event", zap.Error(err)) continue } resultLog.RemovedCount += 1 resultLog.StatusToBeFixedCount += 1 resultLog.StatusToBeFixedBets = len(totalBetsRefunded) for k := range totalBetsRefunded { resultStatusBets.StatusToBeFixed = append(resultStatusBets.StatusToBeFixed, 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): bets, err := s.GetTotalBetsForEvents(ctx, event.ID) if err != nil { continue } resultLog.StatusPostponedCount += 1 resultLog.StatusPostponedBets = len(bets) for k := range bets { resultStatusBets.StatusPostponed = append(resultStatusBets.StatusPostponed, 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, event.ID) continue } sportID, err := strconv.ParseInt(commonResp.SportID, 10, 64) if err != nil { commonRespLogger.Error("Failed to parse sport id", zap.Error(err)) continue } err = s.UpdateResultForOutcomes(ctx, event.ID, result.Results[0], sportID) if err != nil { commonRespLogger.Error("Error while updating result for event", zap.Error(err)) } err = s.eventSvc.DeleteEvent(ctx, event.ID) if err != nil { commonRespLogger.Error("Failed to remove event", zap.Error(err)) continue } err = s.oddSvc.DeleteOddsForEvent(ctx, event.ID) if err != nil { commonRespLogger.Error("Failed to remove odds for event", zap.Error(err)) continue } resultLog.RemovedCount += 1 resultLog.StatusEndedCount += 1 bets, err := s.GetTotalBetsForEvents(ctx, event.ID) if err != nil { continue } resultLog.StatusEndedBets = len(bets) for k := range bets { resultStatusBets.StatusEnded = append(resultStatusBets.StatusEnded, 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, event.ID) if err != nil { commonRespLogger.Error("Failed to refund outcomes", zap.Error(err)) } err = s.eventSvc.DeleteEvent(ctx, event.ID) if err != nil { commonRespLogger.Error("Failed to remove event", zap.Error(err)) continue } err = s.oddSvc.DeleteOddsForEvent(ctx, event.ID) if err != nil { commonRespLogger.Error("Failed to remove odds for event", zap.Error(err)) continue } resultLog.RemovedCount += 1 resultLog.StatusRemovedCount += 1 resultLog.StatusRemovedBets = len(totalBetsRefunded) for k := range totalBetsRefunded { resultStatusBets.StatusRemoved = append(resultStatusBets.StatusRemoved, k) } } } // This will be used to send daily notifications, since events will be removed _, err = s.resultLogStore.CreateResultLog(ctx, resultLog) if err != nil { s.mongoLogger.Warn( "Failed to store result log", zap.Error(err), ) } var logMessage string if resultLog.StatusNotFinishedCount != 0 || resultLog.StatusPostponedCount != 0 || resultLog.StatusRemovedCount != 0 || resultLog.StatusToBeFixedCount != 0 { logMessage = "Completed processing results with issues" } else { logMessage = "Successfully processed results with no issues" } s.mongoLogger.Info( logMessage, zap.Int("number_of_removed_events", resultLog.RemovedCount), zap.Int("total_expired_events", len(events)), zap.Any("events_with_empty_sport_id", empty_sport_id), zap.Any("result status counts", resultLog), zap.Any("bets by event status", resultStatusBets), ) return nil } func (s *Service) CheckAndUpdateExpiredB365Events(ctx context.Context) (int64, error) { events, _, err := s.eventSvc.GetAllEvents(ctx, domain.EventFilter{ LastStartTime: domain.ValidTime{ Value: time.Now(), Valid: true, }, Source: domain.ValidEventSource{ Value: domain.EVENT_SOURCE_BET365, Valid: true, }, }) 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 i, event := range events { if s.config.Env == "development" { log.Printf("⚙️ Checking and Updating Status for Event %v (%d/%d) \n", event.ID, i+1, len(events)) } if event.Status == domain.STATUS_REMOVED { skipped += 1 continue } result, err := s.FetchB365Result(ctx, event.SourceEventID) if err != nil { s.mongoLogger.Error( "Failed to fetch result", zap.Int64("eventID", event.ID), zap.Error(err), ) continue } if result.Success != 1 || len(result.Results) == 0 { s.mongoLogger.Error( "Invalid API result response", zap.Int64("eventID", event.ID), 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", event.ID), zap.Error(err), ) continue } var eventStatus domain.EventStatus // TODO Change event status to int64 enum timeStatus := commonResp.TimeStatus.Value // 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", event.ID), ) continue } err = s.eventSvc.UpdateFinalScore(ctx, event.ID, commonResp.SS, eventStatus) if err != nil { s.mongoLogger.Error( "Failed to update final score", zap.Int64("eventID", event.ID), 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", event.ID), 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", event.ID), 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", event.ID), 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 } // Gets a B365 Result and returns the outcomes that this result would give func (s *Service) GetBet365ResultForEvent(ctx context.Context, b365EventID string) (json.RawMessage, []domain.BetOutcome, error) { result, err := s.FetchB365Result(ctx, b365EventID) if err != nil { s.mongoLogger.Error( "Failed to fetch result", zap.String("b365EventID", b365EventID), zap.Error(err), ) } if result.Success != 1 || len(result.Results) == 0 { s.mongoLogger.Error( "Invalid API result response", zap.Any("result", result), zap.String("b365EventID", b365EventID), zap.Error(err), ) return json.RawMessage{}, nil, fmt.Errorf("invalid API response for event %d", b365EventID) } var commonResp domain.CommonResultResponse if err := json.Unmarshal(result.Results[0], &commonResp); err != nil { s.mongoLogger.Error( "Failed to unmarshal common result", zap.String("b365EventID", b365EventID), zap.Error(err), ) return json.RawMessage{}, nil, err } if commonResp.SportID == "" { s.mongoLogger.Warn( "Sport ID is empty", zap.String("b365EventID", b365EventID), ) return json.RawMessage{}, nil, fmt.Errorf("sport id empty for event: %v", b365EventID) } 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("b365EventID", b365EventID), 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("b365EventID", b365EventID), zap.Error(err), ) return json.RawMessage{}, nil, fmt.Errorf("failed to parse expire time for event %s: %w", b365EventID, err) } expires := time.Unix(expireUnix, 0) odds, err := s.oddSvc.FetchB365Odds(ctx, b365EventID) if err != nil { s.mongoLogger.Error( "Failed to fetch non-live odds by event ID", zap.String("b365EventID", b365EventID), zap.Error(err), ) return json.RawMessage{}, nil, fmt.Errorf("failed to fetch non-live odds for event %s: %w", b365EventID, 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("b365EventID", b365EventID), zap.Error(err), ) return json.RawMessage{}, nil, fmt.Errorf("failed to parse odd section for event %v: %w", b365EventID, err) } outcomes := make([]domain.BetOutcome, 0) for _, section := range parsedOddSections.Sections { for _, market := range section.Sp { marketIDint, err := strconv.ParseInt(string(market.ID), 10, 64) if err != nil { s.mongoLogger.Warn( "Invalid market id", zap.Int64("market_id", marketIDint), zap.String("market_name", market.Name), zap.String("b365EventID", b365EventID), 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{ 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("b365EventID", b365EventID), ) return json.RawMessage{}, nil, fmt.Errorf("no outcomes found for event %v", b365EventID) } s.mongoLogger.Info( "Successfully fetched outcomes for event", zap.String("b365EventID", b365EventID), zap.Int("outcomes", len(outcomes)), ) // Get results for outcome for i, outcome := range outcomes { // Parse the result based on sport type parsedResult, err := s.ParseB365Result(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 } // Fetch B365 Base Result func (s *Service) FetchB365Result(ctx context.Context, b365EventID string) (domain.BaseResultResponse, error) { url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%v", s.config.Bet365Token, b365EventID) // 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.String("b365EventID", b365EventID), 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.String("b365EventID", b365EventID), zap.Error(err), ) return domain.BaseResultResponse{}, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { s.mongoLogger.Error( "Unexpected status code", zap.String("b365EventID", b365EventID), 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.String("b365EventID", b365EventID), zap.Error(err), ) return domain.BaseResultResponse{}, err } if resultResp.Success != 1 || len(resultResp.Results) == 0 { s.mongoLogger.Error( "Invalid API response", zap.String("b365EventID", b365EventID), zap.Error(err), ) return domain.BaseResultResponse{}, fmt.Errorf("invalid API response") } return resultResp, nil } func (s *Service) ParseB365Result(resultResp json.RawMessage, outcome domain.BetOutcome, sportID int64) (domain.CreateResult, error) { var result domain.CreateResult var err error logFields := []zap.Field{ zap.Int64("event id", outcome.EventID), zap.Int64("market_id", outcome.MarketID), zap.Int64("sport_id", sportID), } switch sportID { case domain.FOOTBALL: result, err = s.parseFootball(resultResp, outcome) if err != nil { s.mongoLogger.Error("Failed to parse football", append(logFields, 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", append(logFields, 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", append(logFields, 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", append(logFields, 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", append(logFields, 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", append(logFields, 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", append(logFields, 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", append(logFields, 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", append(logFields, 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", append(logFields, 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", append(logFields, zap.Error(err))...) return domain.CreateResult{}, err } default: s.mongoLogger.Error("Unsupported sport", append(logFields, 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) yellowCards := parseStats(result.Stats.YellowCards) redCards := parseStats(result.Stats.RedCards) onTarget := parseStats(result.Stats.OnTarget) offTarget := parseStats(result.Stats.OffTarget) status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, yellowCards, redCards, onTarget, offTarget, 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} } // evaluateFootballOutcome determines the outcome status based on market type and odd. // It uses helper functions to process the logic for each specific market. func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore, firstHalfScore, secondHalfScore struct{ Home, Away int }, corners, halfTimeCorners struct{ Home, Away int }, yellowCards struct{ Home, Away int }, redCards struct{ Home, Away int }, onTarget struct{ Home, Away int }, offTarget 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), int64(domain.FOOTBALL_FULL_TIME_RESULT_ENHANCED): 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), int64(domain.FOOTBALL_ALTERNATIVE_ASIAN_HANDICAP): return evaluateAsianHandicap(outcome, finalScore) case int64(domain.FOOTBALL_GOAL_LINE), int64(domain.FOOTBALL_ALTERNATIVE_GOAL_LINE): return evaluateGoalLine(outcome, finalScore) case int64(domain.FOOTBALL_FIRST_HALF_ASIAN_HANDICAP), int64(domain.FOOTBALL_ALTERNATE_FIRST_HALF_ASIAN_HANDICAP): return evaluateAsianHandicap(outcome, firstHalfScore) case int64(domain.FOOTBALL_FIRST_HALF_GOAL_LINE), int64(domain.FOOTBALL_ALTERNATE_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), int64(domain.FOOTBALL_CORNERS_TWO_WAY), int64(domain.FOOTBALL_ASIAN_TOTAL_CORNERS), int64(domain.FOOTBALL_ALTERNATIVE_CORNER): return evaluateCorners(outcome, corners) case int64(domain.FOOTBALL_FIRST_HALF_CORNERS), 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) case int64(domain.FOOTBALL_BOTH_TEAMS_TO_SCORE): return evaluateBothTeamsToScore(outcome, finalScore) case int64(domain.FOOTBALL_RESULT_BOTH_TEAMS_TO_SCORE): return evaluateResultAndBTTS(outcome, finalScore) case int64(domain.FOOTBALL_HALF_TIME_CORRECT_SCORE): return evaluateCorrectScore(outcome, firstHalfScore) case int64(domain.FOOTBALL_BOTH_TEAMS_TO_SCORE_FIRST_HALF): return evaluateBothTeamsToScore(outcome, firstHalfScore) case int64(domain.FOOTBALL_BOTH_TEAMS_TO_SCORE_SECOND_HALF): return evaluateBothTeamsToScore(outcome, secondHalfScore) case int64(domain.FOOTBALL_SECOND_HALF_RESULT): return evaluateFullTimeResult(outcome, secondHalfScore) case int64(domain.FOOTBALL_CLEAN_SHEET): return evaluateCleanSheet(outcome, finalScore) case int64(domain.FOOTBALL_LAST_TEAM_TO_SCORE): return evaluateLastTeamToScore(outcome, events) case int64(domain.FOOTBALL_WINNING_MARGIN): return evaluateFootballWinningMargin(outcome, finalScore) case int64(domain.FOOTBALL_BOTH_TEAMS_TO_RECEIVE_CARDS): return evaluateBothTeamsToReceiveCards(outcome, yellowCards, redCards) case int64(domain.FOOTBALL_HALF_TIME_DOUBLE_CHANCE): return evaluateDoubleChance(outcome, firstHalfScore) case int64(domain.FOOTBALL_HALF_TIME_RESULT_BOTH_TEAMS_TO_SCORE): return evaluateResultAndBTTS(outcome, firstHalfScore) case int64(domain.FOOTBALL_HALF_WITH_MOST_GOALS): return evaluateHalfWithMostGoals(outcome, firstHalfScore, secondHalfScore) case int64(domain.FOOTBALL_HOME_TEAM_WITH_HIGHEST_SCORING_HALF): return evaluateTeamHighestScoringHalf(outcome, firstHalfScore, secondHalfScore, "home") case int64(domain.FOOTBALL_AWAY_TEAM_WITH_HIGHEST_SCORING_HALF): return evaluateTeamHighestScoringHalf(outcome, firstHalfScore, secondHalfScore, "away") case int64(domain.FOOTBALL_SECOND_HALF_GOALS): return evaluateGoalsOverUnder(outcome, secondHalfScore) case int64(domain.FOOTBALL_TEAM_TOTAL_GOALS): return evaluateTeamTotalGoals(outcome, finalScore) case int64(domain.FOOTBALL_EXACT_TOTAL_GOALS): return evaluateExactTotalGoals(outcome, finalScore) case int64(domain.FOOTBALL_EXACT_FIRST_HALF_GOALS): return evaluateExactTotalGoals(outcome, firstHalfScore) case int64(domain.FOOTBALL_TEAMS_TO_SCORE): return evaluateTeamsToScore(outcome, finalScore) case int64(domain.FOOTBALL_EXACT_SECOND_HALF_GOALS): return evaluateExactTotalGoals(outcome, secondHalfScore) case int64(domain.FOOTBALL_FIRST_MATCH_CORNER): return evaluateFirstMatchCorner(outcome, events) case int64(domain.FOOTBALL_LAST_MATCH_CORNER): return evaluateLastMatchCorner(outcome, events) case int64(domain.FOOTBALL_CORNER_MATCH_BET): return evaluateCornerMatchBet(outcome, corners) case int64(domain.FOOTBALL_MULTI_CORNERS): return evaluateMultiCorners(outcome, corners, halfTimeCorners) case int64(domain.FOOTBALL_MATCH_SHOTS_ON_TARGET): return evaluateMatchShotsOnTarget(outcome, onTarget) case int64(domain.FOOTBALL_TEAM_SHOTS_ON_TARGET): return evaluateTeamShotsOnTarget(outcome, onTarget) case int64(domain.FOOTBALL_SPECIALS): return evaluateSpecials(outcome, finalScore, firstHalfScore, secondHalfScore) case int64(domain.FOOTBALL_ASIAN_HANDICAP_CORNERS), int64(domain.FOOTBALL_CORNER_HANDICAP): return evaluateAsianHandicap(outcome, corners) // Re-use AsianHandicap logic with corner data case int64(domain.FOOTBALL_ASIAN_TOTAL_CARDS), int64(domain.FOOTBALL_NUMBER_OF_CARDS_IN_MATCH): // Assuming 1 point for a yellow card and 2 for a red card. totalCards := yellowCards.Home + yellowCards.Away + redCards.Home + redCards.Away cardScore := struct{ Home, Away int }{totalCards, 0} return evaluateGoalLine(outcome, cardScore) // Re-use GoalLine logic case int64(domain.FOOTBALL_TIME_OF_FIRST_GOAL_BRACKETS), int64(domain.FOOTBALL_EARLY_GOAL), int64(domain.FOOTBALL_LATE_GOAL): return evaluateTimeOfFirstGoal(outcome, events) // --- Unimplemented Markets --- // Reason: The logic for these markets often requires specific data points that are not available // or not clearly structured in the provided result JSON and BetOutcome data structure. case int64(domain.FOOTBALL_MATCH_GOAL_RANGE), int64(domain.FOOTBALL_TEAM_GOAL_RANGE), int64(domain.FOOTBALL_FIRST_HALF_GOAL_RANGE), int64(domain.FOOTBALL_SECOND_HALF_GOAL_RANGE), int64(domain.FOOTBALL_RESULT_GOAL_RANGE), int64(domain.FOOTBALL_DOUBLE_CHANCE_GOAL_RANGE): // Unimplemented: The logic requires a goal range (e.g., "1-2", "3-4"), often specified in odds data as 'ED'. // This range data is not available as a field in the domain.BetOutcome struct provided to this function. return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: data for range (ED) not in BetOutcome") case int64(domain.FOOTBALL_FIRST_HALF_HANDICAP), int64(domain.FOOTBALL_ALTERNATE_FIRST_HALF_HANDICAP), int64(domain.FOOTBALL_ALTERNATIVE_HANDICAP_RESULT), int64(domain.FOOTBALL_HANDICAP_RESULT): // Unimplemented: Standard handicap markets (3-way) require specific logic to handle the "Tie" outcome based on the handicap, // which differs from Asian Handicap. This logic needs to be built out. return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: 3-way handicap logic is not yet built") case int64(domain.FOOTBALL_TO_SCORE_IN_HALF), int64(domain.FOOTBALL_BOTH_TEAMS_TO_SCORE_FIRST_HALF_SECOND_HALF): // Unimplemented: The BetOutcome struct does not clearly distinguish which half the bet applies to for this market type. // A field indicating "1st Half", "2nd Half", or "Yes/No" for each half is needed. return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: BetOutcome struct lacks required fields for half-specific bets") case int64(domain.FOOTBALL_TEN_MINUTE_RESULT), int64(domain.FOOTBALL_FIRST_TEN_MINUTE), int64(domain.FOOTBALL_TIME_OF_FIRST_TEAM_GOAL), int64(domain.FOOTBALL_TIME_OF_FIRST_CARD), int64(domain.FOOTBALL_TIME_OF_FIRST_CORNER): // Unimplemented: Requires parsing event timestamps and comparing them to specific minute markers. // While event data is available, the specific logic for each of these time-based markets needs to be implemented. return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: requires detailed time-based event parsing logic") case int64(domain.FOOTBALL_FIRST_GOAL_METHOD), int64(domain.FOOTBALL_OWN_GOAL), int64(domain.FOOTBALL_TO_MISS_PENALTY): // Unimplemented: The event result data does not specify the method of the goal (e.g., Shot, Header, Penalty, Own Goal) // or provide details about penalties that were missed. return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: result data lacks goal method or missed penalty info") case int64(domain.FOOTBALL_GOALSCORER), int64(domain.FOOTBALL_MULTI_SCORERS), int64(domain.FOOTBALL_PLAYER_TO_SCORE_ASSIST), int64(domain.FOOTBALL_PLAYER_CARD), int64(domain.FOOTBALL_PLAYER_SHOTS_ON_TARGET), int64(domain.FOOTBALL_PLAYER_SHOTS), int64(domain.FOOTBALL_TEAM_GOALSCORER): // Unimplemented: All player-specific markets require data mapping player names to actions (goals, cards, shots). // The provided event result data is anonymous (e.g., "Goal - (Temperley)") and does not contain this information. return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: requires player-specific data not present in results") 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) 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) } }