From 0ae315433454ed5d6298080d850f97025ba68dcc Mon Sep 17 00:00:00 2001 From: Asher Samuel Date: Thu, 15 May 2025 01:50:59 +0300 Subject: [PATCH] volleyball support --- internal/domain/result.go | 39 +++++ internal/domain/sportmarket.go | 43 +++-- internal/services/result/eval.go | 44 ----- internal/services/result/service.go | 61 ++++++- q | 256 ++++++++++++++++++++++++++++ 5 files changed, 381 insertions(+), 62 deletions(-) create mode 100644 q diff --git a/internal/domain/result.go b/internal/domain/result.go index e43476b..def8719 100644 --- a/internal/domain/result.go +++ b/internal/domain/result.go @@ -210,6 +210,45 @@ type CricketResultResponse struct { Bet365ID string `json:"bet365_id"` } +type VolleyballResultResponse struct { + ID string `json:"id"` + SportID string `json:"sport_id"` + Time string `json:"time"` + TimeStatus string `json:"time_status"` + League struct { + ID string `json:"id"` + Name string `json:"name"` + CC string `json:"cc"` + } `json:"league"` + Home struct { + ID string `json:"id"` + Name string `json:"name"` + ImageID string `json:"image_id"` + CC string `json:"cc"` + } `json:"home"` + Away struct { + ID string `json:"id"` + Name string `json:"name"` + ImageID string `json:"image_id"` + CC string `json:"cc"` + } `json:"away"` + SS string `json:"ss"` + Scores struct { + FirstSet Score `json:"1"` + SecondSet Score `json:"2"` + ThirdSet Score `json:"3"` + FourthSet Score `json:"4"` + FivethSet Score `json:"5"` + } `json:"scores"` + Stats struct { + PointsWonOnServe []string `json:"points_won_on_serve"` + LongestStreak []string `json:"longest_streak"` + } `json:"stats"` + InplayCreatedAt string `json:"inplay_created_at"` + InplayUpdatedAt string `json:"inplay_updated_at"` + Bet365ID string `json:"bet365_id"` +} + type Score struct { Home string `json:"home"` Away string `json:"away"` diff --git a/internal/domain/sportmarket.go b/internal/domain/sportmarket.go index f442f9c..fc25e36 100644 --- a/internal/domain/sportmarket.go +++ b/internal/domain/sportmarket.go @@ -114,6 +114,17 @@ const ( CRICKET_TOP_MATCH_BOWLER CricketMarket = 30246 ) +type VolleyballMarket int64 + +const ( + VOLLEYBALL_GAME_LINES VolleyballMarket = 910000 + VOLLEYBALL_CORRECT_SET_SCORE VolleyballMarket = 910201 + VOLLEYBALL_MATCH_TOTAL_ODD_EVEN VolleyballMarket = 910217 + VOLLEYBALL_SET_ONE_LINES VolleyballMarket = 910204 + VOLLEYBALL_SET_ONE_TO_GO_TO_EXTRA_POINTS VolleyballMarket = 910209 + VOLLEYBALL_SET_ONE_TOTAL_ODD_EVEN VolleyballMarket = 910218 +) + // TODO: Move this into the database so that it can be modified dynamically var SupportedMarkets = map[int64]bool{ @@ -189,16 +200,26 @@ var SupportedMarkets = map[int64]bool{ int64(ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY): false, // Cricket Markets - int64(CRICKET_TO_WIN_THE_MATCH): true, - int64(CRICKET_FIRST_OVER_TOTAL_RUNS_Odd_Even): true, - int64(CRICKET_FIRST_INNINIGS_SCORE): true, + int64(CRICKET_TO_WIN_THE_MATCH): true, - int64(CRICKET_INNINGS_OF_MATCH_BOWLED_OUT): false, - int64(CRICKET_FIRST_OVER_TOTAL_RUNS): false, - int64(CRICKET_TEAM_TOP_BATTER): false, - int64(CRICKET_TEAM_TOP_BOWLE): false, - int64(CRICKET_PLAYER_OF_THE_MATCH): false, - int64(CRICKET_FIRST_WICKET_METHOD): false, - int64(CRICKET_TOP_MATCH_BATTER): false, - int64(CRICKET_TOP_MATCH_BOWLER): false, + int64(CRICKET_FIRST_OVER_TOTAL_RUNS_Odd_Even): false, + int64(CRICKET_FIRST_INNINIGS_SCORE): false, + int64(CRICKET_INNINGS_OF_MATCH_BOWLED_OUT): false, + int64(CRICKET_FIRST_OVER_TOTAL_RUNS): false, + int64(CRICKET_TEAM_TOP_BATTER): false, + int64(CRICKET_TEAM_TOP_BOWLE): false, + int64(CRICKET_PLAYER_OF_THE_MATCH): false, + int64(CRICKET_FIRST_WICKET_METHOD): false, + int64(CRICKET_TOP_MATCH_BATTER): false, + int64(CRICKET_TOP_MATCH_BOWLER): false, + + // Volleyball Markets + int64(VOLLEYBALL_GAME_LINES): false, + + int64(VOLLEYBALL_CORRECT_SET_SCORE): true, + int64(VOLLEYBALL_MATCH_TOTAL_ODD_EVEN): true, + + int64(VOLLEYBALL_SET_ONE_LINES): false, + int64(VOLLEYBALL_SET_ONE_TO_GO_TO_EXTRA_POINTS): false, + int64(VOLLEYBALL_SET_ONE_TOTAL_ODD_EVEN): false, } diff --git a/internal/services/result/eval.go b/internal/services/result/eval.go index 8c21390..5d7cd66 100644 --- a/internal/services/result/eval.go +++ b/internal/services/result/eval.go @@ -708,47 +708,3 @@ func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Hom return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName) } - -// Cricket evalations -func evaluateInningScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { - var inningsScore int - if outcome.OddName == "1" { - inningsScore = score.Home - } else if outcome.OddName == "2" { - inningsScore = score.Away - } else { - return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) - } - - parts := strings.Fields(outcome.OddHeader) - if len(parts) != 2 { - return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd format: %s", outcome.OddHeader) - } - - evalType := parts[0] - - threshold, err := strconv.ParseFloat(parts[1], 64) - if err != nil { - return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold value: %s", parts[1]) - } - - switch evalType { - case "Over": - if float64(inningsScore) > threshold { - return domain.OUTCOME_STATUS_WIN, nil - } - return domain.OUTCOME_STATUS_LOSS, nil - case "Under": - if float64(inningsScore) < threshold { - return domain.OUTCOME_STATUS_WIN, nil - } - return domain.OUTCOME_STATUS_LOSS, nil - case "Exactly": - if float64(inningsScore) == threshold { - return domain.OUTCOME_STATUS_WIN, nil - } - return domain.OUTCOME_STATUS_LOSS, nil - default: - return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid comparison operator: %s", evalType) - } -} diff --git a/internal/services/result/service.go b/internal/services/result/service.go index fcbff6b..8eeafba 100644 --- a/internal/services/result/service.go +++ b/internal/services/result/service.go @@ -226,6 +226,12 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, spo s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err) return domain.CreateResult{}, err } + case domain.VOLLEYBALL: + result, err = s.parseVolleyball(resultResp.Results[0], eventID, oddID, marketID, outcome) + if err != nil { + s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err) + return domain.CreateResult{}, err + } default: s.logger.Error("Unsupported sport", "sport", sportID) return domain.CreateResult{}, fmt.Errorf("unsupported sport: %v", sportID) @@ -271,9 +277,6 @@ func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marke func (s *Service) parseBasketball(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) { var basketBallRes domain.BasketballResultResponse - // TODO: here !! - fmt.Println(string(response)) - if err := json.Unmarshal(response, &basketBallRes); err != nil { s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err) return domain.CreateResult{}, err @@ -355,6 +358,34 @@ func (s *Service) parseCricket(response json.RawMessage, eventID, oddID, marketI }, 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 parseScore(home string, away string) struct{ Home, Away int } { homeVal, _ := strconv.Atoi(strings.TrimSpace(home)) awaVal, _ := strconv.Atoi(strings.TrimSpace(away)) @@ -537,10 +568,26 @@ func (s *Service) evaluateCricketOutcome(outcome domain.BetOutcome, res domain.C switch outcome.MarketID { case int64(domain.CRICKET_TO_WIN_THE_MATCH): return evaluateFullTimeResult(outcome, score) - case int64(domain.CRICKET_FIRST_OVER_TOTAL_RUNS_Odd_Even): - return evaluateGoalsOddEven(outcome, score) - case int64(domain.CRICKET_FIRST_INNINIGS_SCORE): - return evaluateInningScore(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) + + switch outcome.MarketID { + // TODO: new Game Lines for volleyball + + case int64(domain.VOLLEYBALL_CORRECT_SET_SCORE): + return evaluateCorrectScore(outcome, score) + case int64(domain.VOLLEYBALL_MATCH_TOTAL_ODD_EVEN): + return evaluateGoalsOddEven(outcome, score) } return domain.OUTCOME_STATUS_PENDING, nil diff --git a/q b/q new file mode 100644 index 0000000..f9b074d --- /dev/null +++ b/q @@ -0,0 +1,256 @@ +diff --git a/internal/domain/result.go b/internal/domain/result.go +index e43476b..def8719 100644 +--- a/internal/domain/result.go ++++ b/internal/domain/result.go +@@ -210,6 +210,45 @@ type CricketResultResponse struct { + Bet365ID string `json:"bet365_id"` + } +  ++type VolleyballResultResponse struct { ++ ID string `json:"id"` ++ SportID string `json:"sport_id"` ++ Time string `json:"time"` ++ TimeStatus string `json:"time_status"` ++ League struct { ++ ID string `json:"id"` ++ Name string `json:"name"` ++ CC string `json:"cc"` ++ } `json:"league"` ++ Home struct { ++ ID string `json:"id"` ++ Name string `json:"name"` ++ ImageID string `json:"image_id"` ++ CC string `json:"cc"` ++ } `json:"home"` ++ Away struct { ++ ID string `json:"id"` ++ Name string `json:"name"` ++ ImageID string `json:"image_id"` ++ CC string `json:"cc"` ++ } `json:"away"` ++ SS string `json:"ss"` ++ Scores struct { ++ FirstSet Score `json:"1"` ++ SecondSet Score `json:"2"` ++ ThirdSet Score `json:"3"` ++ FourthSet Score `json:"4"` ++ FivethSet Score `json:"5"` ++ } `json:"scores"` ++ Stats struct { ++ PointsWonOnServe []string `json:"points_won_on_serve"` ++ LongestStreak []string `json:"longest_streak"` ++ } `json:"stats"` ++ InplayCreatedAt string `json:"inplay_created_at"` ++ InplayUpdatedAt string `json:"inplay_updated_at"` ++ Bet365ID string `json:"bet365_id"` ++} ++ + type Score struct { + Home string `json:"home"` + Away string `json:"away"` +diff --git a/internal/domain/sportmarket.go b/internal/domain/sportmarket.go +index f442f9c..fc25e36 100644 +--- a/internal/domain/sportmarket.go ++++ b/internal/domain/sportmarket.go +@@ -114,6 +114,17 @@ const ( + CRICKET_TOP_MATCH_BOWLER CricketMarket = 30246 + ) +  ++type VolleyballMarket int64 ++ ++const ( ++ VOLLEYBALL_GAME_LINES VolleyballMarket = 910000 ++ VOLLEYBALL_CORRECT_SET_SCORE VolleyballMarket = 910201 ++ VOLLEYBALL_MATCH_TOTAL_ODD_EVEN VolleyballMarket = 910217 ++ VOLLEYBALL_SET_ONE_LINES VolleyballMarket = 910204 ++ VOLLEYBALL_SET_ONE_TO_GO_TO_EXTRA_POINTS VolleyballMarket = 910209 ++ VOLLEYBALL_SET_ONE_TOTAL_ODD_EVEN VolleyballMarket = 910218 ++) ++ + // TODO: Move this into the database so that it can be modified dynamically +  + var SupportedMarkets = map[int64]bool{ +@@ -189,16 +200,26 @@ var SupportedMarkets = map[int64]bool{ + int64(ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY): false, +  + // Cricket Markets +- int64(CRICKET_TO_WIN_THE_MATCH): true, +- int64(CRICKET_FIRST_OVER_TOTAL_RUNS_Odd_Even): true, +- int64(CRICKET_FIRST_INNINIGS_SCORE): true, +- +- int64(CRICKET_INNINGS_OF_MATCH_BOWLED_OUT): false, +- int64(CRICKET_FIRST_OVER_TOTAL_RUNS): false, +- int64(CRICKET_TEAM_TOP_BATTER): false, +- int64(CRICKET_TEAM_TOP_BOWLE): false, +- int64(CRICKET_PLAYER_OF_THE_MATCH): false, +- int64(CRICKET_FIRST_WICKET_METHOD): false, +- int64(CRICKET_TOP_MATCH_BATTER): false, +- int64(CRICKET_TOP_MATCH_BOWLER): false, ++ int64(CRICKET_TO_WIN_THE_MATCH): true, ++ ++ int64(CRICKET_FIRST_OVER_TOTAL_RUNS_Odd_Even): false, ++ int64(CRICKET_FIRST_INNINIGS_SCORE): false, ++ int64(CRICKET_INNINGS_OF_MATCH_BOWLED_OUT): false, ++ int64(CRICKET_FIRST_OVER_TOTAL_RUNS): false, ++ int64(CRICKET_TEAM_TOP_BATTER): false, ++ int64(CRICKET_TEAM_TOP_BOWLE): false, ++ int64(CRICKET_PLAYER_OF_THE_MATCH): false, ++ int64(CRICKET_FIRST_WICKET_METHOD): false, ++ int64(CRICKET_TOP_MATCH_BATTER): false, ++ int64(CRICKET_TOP_MATCH_BOWLER): false, ++ ++ // Volleyball Markets ++ int64(VOLLEYBALL_GAME_LINES): false, ++ ++ int64(VOLLEYBALL_CORRECT_SET_SCORE): true, ++ int64(VOLLEYBALL_MATCH_TOTAL_ODD_EVEN): true, ++ ++ int64(VOLLEYBALL_SET_ONE_LINES): false, ++ int64(VOLLEYBALL_SET_ONE_TO_GO_TO_EXTRA_POINTS): false, ++ int64(VOLLEYBALL_SET_ONE_TOTAL_ODD_EVEN): false, + } +diff --git a/internal/services/result/eval.go b/internal/services/result/eval.go +index 8c21390..5d7cd66 100644 +--- a/internal/services/result/eval.go ++++ b/internal/services/result/eval.go +@@ -708,47 +708,3 @@ func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Hom +  + return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName) + } +- +-// Cricket evalations +-func evaluateInningScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { +- var inningsScore int +- if outcome.OddName == "1" { +- inningsScore = score.Home +- } else if outcome.OddName == "2" { +- inningsScore = score.Away +- } else { +- return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) +- } +- +- parts := strings.Fields(outcome.OddHeader) +- if len(parts) != 2 { +- return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd format: %s", outcome.OddHeader) +- } +- +- evalType := parts[0] +- +- threshold, err := strconv.ParseFloat(parts[1], 64) +- if err != nil { +- return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold value: %s", parts[1]) +- } +- +- switch evalType { +- case "Over": +- if float64(inningsScore) > threshold { +- return domain.OUTCOME_STATUS_WIN, nil +- } +- return domain.OUTCOME_STATUS_LOSS, nil +- case "Under": +- if float64(inningsScore) < threshold { +- return domain.OUTCOME_STATUS_WIN, nil +- } +- return domain.OUTCOME_STATUS_LOSS, nil +- case "Exactly": +- if float64(inningsScore) == threshold { +- return domain.OUTCOME_STATUS_WIN, nil +- } +- return domain.OUTCOME_STATUS_LOSS, nil +- default: +- return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid comparison operator: %s", evalType) +- } +-} +diff --git a/internal/services/result/service.go b/internal/services/result/service.go +index fcbff6b..92886c3 100644 +--- a/internal/services/result/service.go ++++ b/internal/services/result/service.go +@@ -226,6 +226,12 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, spo + s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err) + return domain.CreateResult{}, err + } ++ case domain.VOLLEYBALL: ++ result, err = s.parseVolleyball(resultResp.Results[0], eventID, oddID, marketID, outcome) ++ if err != nil { ++ s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err) ++ return domain.CreateResult{}, err ++ } + default: + s.logger.Error("Unsupported sport", "sport", sportID) + return domain.CreateResult{}, fmt.Errorf("unsupported sport: %v", sportID) +@@ -271,9 +277,6 @@ func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marke + func (s *Service) parseBasketball(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) { + var basketBallRes domain.BasketballResultResponse +  +- // TODO: here !! +- fmt.Println(string(response)) +- + if err := json.Unmarshal(response, &basketBallRes); err != nil { + s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err) + return domain.CreateResult{}, err +@@ -355,6 +358,34 @@ func (s *Service) parseCricket(response json.RawMessage, eventID, oddID, marketI + }, 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 parseScore(home string, away string) struct{ Home, Away int } { + homeVal, _ := strconv.Atoi(strings.TrimSpace(home)) + awaVal, _ := strconv.Atoi(strings.TrimSpace(away)) +@@ -537,10 +568,27 @@ func (s *Service) evaluateCricketOutcome(outcome domain.BetOutcome, res domain.C + switch outcome.MarketID { + case int64(domain.CRICKET_TO_WIN_THE_MATCH): + return evaluateFullTimeResult(outcome, score) +- case int64(domain.CRICKET_FIRST_OVER_TOTAL_RUNS_Odd_Even): ++ } ++ ++ 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) ++ ++ switch outcome.MarketID { ++ // new Game line since its different from basket ball ++ case int64(domain.VOLLEYBALL_GAME_LINES): ++ return evaluateGameLines(outcome, score) ++ case int64(domain.VOLLEYBALL_CORRECT_SET_SCORE): ++ return evaluateCorrectScore(outcome, score) ++ case int64(domain.VOLLEYBALL_MATCH_TOTAL_ODD_EVEN): + return evaluateGoalsOddEven(outcome, score) +- case int64(domain.CRICKET_FIRST_INNINIGS_SCORE): +- return evaluateInningScore(outcome, score) + } +  + return domain.OUTCOME_STATUS_PENDING, nil