feat: Implement sports evaluation logic for NFL, Rugby, and Baseball
- Added evaluation functions for NFL money line, spread, and total points. - Implemented evaluation functions for Rugby money line, spread, and total points. - Created evaluation functions for Baseball money line, spread, total runs, first inning, and first 5 innings. - Developed unit tests for all sports markets to ensure correct evaluation outcomes. - Introduced ResultCheckerService to handle game result checking for NFL, Rugby, and Baseball. - Updated routes to include new functionality for virtual game handling.
This commit is contained in:
parent
4a2ae14a64
commit
6cc37fbf9f
13
go.mod
13
go.mod
|
|
@ -7,23 +7,27 @@ require (
|
|||
github.com/bytedance/sonic v1.13.2
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/gofiber/websocket/v2 v2.2.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.7.4
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/swaggo/fiber-swagger v1.3.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
golang.org/x/crypto v0.36.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/fasthttp/websocket v1.5.8 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
|
|
@ -31,7 +35,7 @@ require (
|
|||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/gofiber/contrib/websocket v1.3.4
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
|
|
@ -45,11 +49,10 @@ require (
|
|||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.59.0 // indirect
|
||||
github.com/valyala/fasthttp v1.59.0
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
|
|
|
|||
14
go.sum
14
go.sum
|
|
@ -22,10 +22,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
|
||||
github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
|
||||
github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8=
|
||||
github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
|
@ -51,18 +47,16 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/gofiber/contrib/websocket v1.3.4 h1:tWeBdbJ8q0WFQXariLN4dBIbGH9KBU75s0s7YXplOSg=
|
||||
github.com/gofiber/contrib/websocket v1.3.4/go.mod h1:kTFBPC6YENCnKfKx0BoOFjgXxdz7E85/STdkmZPEmPs=
|
||||
github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY=
|
||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
||||
github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
|
|
@ -118,10 +112,6 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
|
|||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
|
|
|
|||
|
|
@ -91,6 +91,38 @@ const (
|
|||
ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY IceHockeyMarket = 170240
|
||||
)
|
||||
|
||||
type AmericanFootballMarket int64
|
||||
|
||||
const (
|
||||
// Main
|
||||
AMERICAN_FOOTBALL_MONEY_LINE AmericanFootballMarket = 170001
|
||||
AMERICAN_FOOTBALL_SPREAD AmericanFootballMarket = 170002
|
||||
AMERICAN_FOOTBALL_TOTAL_POINTS AmericanFootballMarket = 170003
|
||||
)
|
||||
|
||||
type RugbyMarket int64
|
||||
|
||||
const (
|
||||
// Main
|
||||
RUGBY_MONEY_LINE RugbyMarket = 180001
|
||||
RUGBY_SPREAD RugbyMarket = 180002
|
||||
RUGBY_TOTAL_POINTS RugbyMarket = 180003
|
||||
RUGBY_HANDICAP RugbyMarket = 180004
|
||||
RUGBY_FIRST_HALF RugbyMarket = 180005
|
||||
RUGBY_SECOND_HALF RugbyMarket = 180006
|
||||
)
|
||||
|
||||
type BaseballMarket int64
|
||||
|
||||
const (
|
||||
// Main
|
||||
BASEBALL_MONEY_LINE BaseballMarket = 190001
|
||||
BASEBALL_SPREAD BaseballMarket = 190002
|
||||
BASEBALL_TOTAL_RUNS BaseballMarket = 190003
|
||||
BASEBALL_FIRST_INNING BaseballMarket = 190004
|
||||
BASEBALL_FIRST_5_INNINGS BaseballMarket = 190005
|
||||
)
|
||||
|
||||
// TODO: Move this into the database so that it can be modified dynamically
|
||||
|
||||
var SupportedMarkets = map[int64]bool{
|
||||
|
|
@ -164,4 +196,24 @@ var SupportedMarkets = map[int64]bool{
|
|||
|
||||
int64(ICE_HOCKEY_ALTERNATIVE_PUCK_LINE_TWO_WAY): false,
|
||||
int64(ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY): false,
|
||||
|
||||
// American Football Markets
|
||||
int64(AMERICAN_FOOTBALL_MONEY_LINE): true,
|
||||
int64(AMERICAN_FOOTBALL_SPREAD): true,
|
||||
int64(AMERICAN_FOOTBALL_TOTAL_POINTS): true,
|
||||
|
||||
// Rugby Markets
|
||||
int64(RUGBY_MONEY_LINE): true,
|
||||
int64(RUGBY_SPREAD): true,
|
||||
int64(RUGBY_TOTAL_POINTS): true,
|
||||
int64(RUGBY_HANDICAP): true,
|
||||
int64(RUGBY_FIRST_HALF): true,
|
||||
int64(RUGBY_SECOND_HALF): true,
|
||||
|
||||
// Baseball Markets
|
||||
int64(BASEBALL_MONEY_LINE): true,
|
||||
int64(BASEBALL_SPREAD): true,
|
||||
int64(BASEBALL_TOTAL_RUNS): true,
|
||||
int64(BASEBALL_FIRST_INNING): true,
|
||||
int64(BASEBALL_FIRST_5_INNINGS): true,
|
||||
}
|
||||
|
|
|
|||
290
internal/domain/sports_result.go
Normal file
290
internal/domain/sports_result.go
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NFLResultResponse represents the structure for NFL game results
|
||||
type NFLResultResponse 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 {
|
||||
FirstQuarter Score `json:"1"`
|
||||
SecondQuarter Score `json:"2"`
|
||||
ThirdQuarter Score `json:"3"`
|
||||
FourthQuarter Score `json:"4"`
|
||||
Overtime Score `json:"5"`
|
||||
TotalScore Score `json:"7"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
FirstDowns []string `json:"first_downs"`
|
||||
TotalYards []string `json:"total_yards"`
|
||||
PassingYards []string `json:"passing_yards"`
|
||||
RushingYards []string `json:"rushing_yards"`
|
||||
Turnovers []string `json:"turnovers"`
|
||||
TimeOfPossession []string `json:"time_of_possession"`
|
||||
ThirdDownEfficiency []string `json:"third_down_efficiency"`
|
||||
FourthDownEfficiency []string `json:"fourth_down_efficiency"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
||||
// RugbyResultResponse represents the structure for Rugby game results
|
||||
type RugbyResultResponse 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 {
|
||||
FirstHalf Score `json:"1"`
|
||||
SecondHalf Score `json:"2"`
|
||||
TotalScore Score `json:"7"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
Tries []string `json:"tries"`
|
||||
Conversions []string `json:"conversions"`
|
||||
Penalties []string `json:"penalties"`
|
||||
DropGoals []string `json:"drop_goals"`
|
||||
Possession []string `json:"possession"`
|
||||
Territory []string `json:"territory"`
|
||||
Lineouts []string `json:"lineouts"`
|
||||
Scrums []string `json:"scrums"`
|
||||
PenaltiesConceded []string `json:"penalties_conceded"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
||||
// BaseballResultResponse represents the structure for Baseball game results
|
||||
type BaseballResultResponse 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 {
|
||||
FirstInning Score `json:"1"`
|
||||
SecondInning Score `json:"2"`
|
||||
ThirdInning Score `json:"3"`
|
||||
FourthInning Score `json:"4"`
|
||||
FifthInning Score `json:"5"`
|
||||
SixthInning Score `json:"6"`
|
||||
SeventhInning Score `json:"7"`
|
||||
EighthInning Score `json:"8"`
|
||||
NinthInning Score `json:"9"`
|
||||
ExtraInnings Score `json:"10"`
|
||||
TotalScore Score `json:"7"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
Hits []string `json:"hits"`
|
||||
Errors []string `json:"errors"`
|
||||
LeftOnBase []string `json:"left_on_base"`
|
||||
Strikeouts []string `json:"strikeouts"`
|
||||
Walks []string `json:"walks"`
|
||||
HomeRuns []string `json:"home_runs"`
|
||||
TotalBases []string `json:"total_bases"`
|
||||
BattingAverage []string `json:"batting_average"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
Round string `json:"round"`
|
||||
} `json:"extra"`
|
||||
Events []map[string]string `json:"events"`
|
||||
HasLineup int `json:"has_lineup"`
|
||||
ConfirmedAt string `json:"confirmed_at"`
|
||||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
|
||||
// ParseNFLResult parses NFL result from raw JSON data
|
||||
func ParseNFLResult(data json.RawMessage) (*NFLResultResponse, error) {
|
||||
var result NFLResultResponse
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// ParseRugbyResult parses Rugby result from raw JSON data
|
||||
func ParseRugbyResult(data json.RawMessage) (*RugbyResultResponse, error) {
|
||||
var result RugbyResultResponse
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// ParseRugbyUnionResult parses Rugby Union result from raw JSON data
|
||||
func ParseRugbyUnionResult(data json.RawMessage) (*RugbyResultResponse, error) {
|
||||
return ParseRugbyResult(data)
|
||||
}
|
||||
|
||||
// ParseRugbyLeagueResult parses Rugby League result from raw JSON data
|
||||
func ParseRugbyLeagueResult(data json.RawMessage) (*RugbyResultResponse, error) {
|
||||
return ParseRugbyResult(data)
|
||||
}
|
||||
|
||||
// ParseBaseballResult parses Baseball result from raw JSON data
|
||||
func ParseBaseballResult(data json.RawMessage) (*BaseballResultResponse, error) {
|
||||
var result BaseballResultResponse
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// GetNFLWinner determines the winner of an NFL game
|
||||
func GetNFLWinner(result *NFLResultResponse) (string, error) {
|
||||
homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if homeScore > awayScore {
|
||||
return result.Home.Name, nil
|
||||
} else if awayScore > homeScore {
|
||||
return result.Away.Name, nil
|
||||
}
|
||||
return "Draw", nil
|
||||
}
|
||||
|
||||
// GetRugbyWinner determines the winner of a Rugby game
|
||||
func GetRugbyWinner(result *RugbyResultResponse) (string, error) {
|
||||
homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if homeScore > awayScore {
|
||||
return result.Home.Name, nil
|
||||
} else if awayScore > homeScore {
|
||||
return result.Away.Name, nil
|
||||
}
|
||||
return "Draw", nil
|
||||
}
|
||||
|
||||
// GetBaseballWinner determines the winner of a Baseball game
|
||||
func GetBaseballWinner(result *BaseballResultResponse) (string, error) {
|
||||
homeScore, err := strconv.Atoi(result.Scores.TotalScore.Home)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
awayScore, err := strconv.Atoi(result.Scores.TotalScore.Away)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if homeScore > awayScore {
|
||||
return result.Home.Name, nil
|
||||
} else if awayScore > homeScore {
|
||||
return result.Away.Name, nil
|
||||
}
|
||||
return "Draw", nil
|
||||
}
|
||||
|
||||
// FormatNFLScore formats the NFL score in a readable format
|
||||
func FormatNFLScore(result *NFLResultResponse) string {
|
||||
return strings.Join([]string{
|
||||
result.Home.Name + " " + result.Scores.TotalScore.Home,
|
||||
result.Away.Name + " " + result.Scores.TotalScore.Away,
|
||||
}, " - ")
|
||||
}
|
||||
|
||||
// FormatRugbyScore formats the Rugby score in a readable format
|
||||
func FormatRugbyScore(result *RugbyResultResponse) string {
|
||||
return strings.Join([]string{
|
||||
result.Home.Name + " " + result.Scores.TotalScore.Home,
|
||||
result.Away.Name + " " + result.Scores.TotalScore.Away,
|
||||
}, " - ")
|
||||
}
|
||||
|
||||
// FormatBaseballScore formats the Baseball score in a readable format
|
||||
func FormatBaseballScore(result *BaseballResultResponse) string {
|
||||
return strings.Join([]string{
|
||||
result.Home.Name + " " + result.Scores.TotalScore.Home,
|
||||
result.Away.Name + " " + result.Scores.TotalScore.Away,
|
||||
}, " - ")
|
||||
}
|
||||
|
|
@ -266,3 +266,4 @@ func (s *Service) retryFailedNotifications() {
|
|||
}(notification)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -709,3 +709,34 @@ func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Hom
|
|||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
// evaluateRugbyOutcome evaluates the outcome of a Rugby bet
|
||||
func evaluateRugbyOutcome(outcome domain.BetOutcome, result *domain.RugbyResultResponse) (domain.OutcomeStatus, error) {
|
||||
finalScore := parseSS(result.SS)
|
||||
|
||||
switch outcome.MarketName {
|
||||
case "Money Line":
|
||||
return evaluateRugbyMoneyLine(outcome, finalScore)
|
||||
case "Spread":
|
||||
return evaluateRugbySpread(outcome, finalScore)
|
||||
case "Total Points":
|
||||
return evaluateRugbyTotalPoints(outcome, finalScore)
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported rugby market: %s", outcome.MarketName)
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballOutcome evaluates the outcome of a Baseball bet
|
||||
func evaluateBaseballOutcome(outcome domain.BetOutcome, result *domain.BaseballResultResponse) (domain.OutcomeStatus, error) {
|
||||
finalScore := parseSS(result.SS)
|
||||
|
||||
switch outcome.MarketName {
|
||||
case "Money Line":
|
||||
return evaluateBaseballMoneyLine(outcome, finalScore)
|
||||
case "Spread":
|
||||
return evaluateBaseballSpread(outcome, finalScore)
|
||||
case "Total Runs":
|
||||
return evaluateBaseballTotalRuns(outcome, finalScore)
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported baseball market: %s", outcome.MarketName)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,30 +202,24 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, spo
|
|||
switch sportID {
|
||||
case domain.FOOTBALL:
|
||||
result, err = s.parseFootball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse football", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
|
||||
case domain.BASKETBALL:
|
||||
result, err = s.parseBasketball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse basketball", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
case domain.ICE_HOCKEY:
|
||||
result, err = s.parseIceHockey(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse ice hockey", "event id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
}
|
||||
case domain.AMERICAN_FOOTBALL:
|
||||
result, err = s.parseNFL(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
case domain.RUGBY_UNION:
|
||||
result, err = s.parseRugbyUnion(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
case domain.RUGBY_LEAGUE:
|
||||
result, err = s.parseRugbyLeague(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
case domain.BASEBALL:
|
||||
result, err = s.parseBaseball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||
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, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||
|
|
@ -317,6 +311,124 @@ func (s *Service) parseIceHockey(response json.RawMessage, eventID, oddID, marke
|
|||
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
finalScore := parseSS(nflResp.SS)
|
||||
|
||||
var status domain.OutcomeStatus
|
||||
var err error
|
||||
|
||||
switch outcome.MarketName {
|
||||
case "Money Line":
|
||||
status, err = evaluateNFLMoneyLine(outcome, finalScore)
|
||||
case "Spread":
|
||||
status, err = evaluateNFLSpread(outcome, finalScore)
|
||||
case "Total Points":
|
||||
status, err = evaluateNFLTotalPoints(outcome, finalScore)
|
||||
default:
|
||||
return domain.CreateResult{}, fmt.Errorf("unsupported market: %s", outcome.MarketName)
|
||||
}
|
||||
|
||||
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 := 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 := 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 := 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))
|
||||
|
|
@ -487,3 +599,22 @@ func (s *Service) evaluateIceHockeyOutcome(outcome domain.BetOutcome, res domain
|
|||
|
||||
return domain.OUTCOME_STATUS_PENDING, nil
|
||||
}
|
||||
|
||||
func (s *Service) evaluateNFLOutcome(outcome domain.BetOutcome, finalScore struct{ Home, Away int }) (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.AMERICAN_FOOTBALL_MONEY_LINE):
|
||||
return evaluateNFLMoneyLine(outcome, finalScore)
|
||||
case int64(domain.AMERICAN_FOOTBALL_SPREAD):
|
||||
return evaluateNFLSpread(outcome, finalScore)
|
||||
case int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS):
|
||||
return evaluateNFLTotalPoints(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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
280
internal/services/result/sports_eval.go
Normal file
280
internal/services/result/sports_eval.go
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
// NFL evaluations
|
||||
func evaluateNFLMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateNFLSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
|
||||
adjustedHomeScore := float64(score.Home)
|
||||
adjustedAwayScore := float64(score.Away)
|
||||
|
||||
if outcome.OddHeader == "1" {
|
||||
adjustedHomeScore += handicap
|
||||
} else if outcome.OddHeader == "2" {
|
||||
adjustedAwayScore += handicap
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
if adjustedHomeScore > adjustedAwayScore {
|
||||
if outcome.OddHeader == "1" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if adjustedHomeScore < adjustedAwayScore {
|
||||
if outcome.OddHeader == "2" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
func evaluateNFLTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalPoints := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
if outcome.OddHeader == "Over" {
|
||||
if totalPoints > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalPoints == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if outcome.OddHeader == "Under" {
|
||||
if totalPoints < threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalPoints == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// evaluateRugbyMoneyLine evaluates Rugby money line bets
|
||||
func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateRugbySpread evaluates Rugby spread bets
|
||||
func evaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
|
||||
adjustedHomeScore := float64(score.Home)
|
||||
adjustedAwayScore := float64(score.Away)
|
||||
|
||||
if outcome.OddHeader == "1" {
|
||||
adjustedHomeScore += handicap
|
||||
} else if outcome.OddHeader == "2" {
|
||||
adjustedAwayScore += handicap
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
if adjustedHomeScore > adjustedAwayScore {
|
||||
if outcome.OddHeader == "1" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if adjustedHomeScore < adjustedAwayScore {
|
||||
if outcome.OddHeader == "2" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
// evaluateRugbyTotalPoints evaluates Rugby total points bets
|
||||
func evaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalPoints := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
if outcome.OddHeader == "Over" {
|
||||
if totalPoints > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalPoints == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if outcome.OddHeader == "Under" {
|
||||
if totalPoints < threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalPoints == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// evaluateBaseballMoneyLine evaluates Baseball money line bets
|
||||
func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballSpread evaluates Baseball spread bets
|
||||
func evaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
}
|
||||
|
||||
adjustedHomeScore := float64(score.Home)
|
||||
adjustedAwayScore := float64(score.Away)
|
||||
|
||||
if outcome.OddHeader == "1" {
|
||||
adjustedHomeScore += handicap
|
||||
} else if outcome.OddHeader == "2" {
|
||||
adjustedAwayScore += handicap
|
||||
} else {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
if adjustedHomeScore > adjustedAwayScore {
|
||||
if outcome.OddHeader == "1" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if adjustedHomeScore < adjustedAwayScore {
|
||||
if outcome.OddHeader == "2" {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
// evaluateBaseballTotalRuns evaluates Baseball total runs bets
|
||||
func evaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalRuns := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||
}
|
||||
|
||||
if outcome.OddHeader == "Over" {
|
||||
if totalRuns > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalRuns == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
} else if outcome.OddHeader == "Under" {
|
||||
if totalRuns < threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} else if totalRuns == threshold {
|
||||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// evaluateBaseballFirstInning evaluates Baseball first inning bets
|
||||
func evaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "X":
|
||||
if score.Home == score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballFirst5Innings evaluates Baseball first 5 innings bets
|
||||
func evaluateBaseballFirst5Innings(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "2":
|
||||
if score.Away > score.Home {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
case "X":
|
||||
if score.Home == score.Away {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
default:
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
}
|
||||
303
internal/services/result/sports_eval_test.go
Normal file
303
internal/services/result/sports_eval_test.go
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestNFLMarkets covers all American Football (NFL) market types defined in the domain.
|
||||
// For each market (Money Line, Spread, Total Points), it tests home/away win, draw, void, and invalid input scenarios.
|
||||
func TestNFLMarkets(t *testing.T) {
|
||||
t.Log("Testing NFL (American Football) Markets")
|
||||
markets := []struct {
|
||||
marketID int64
|
||||
name string
|
||||
}{
|
||||
{int64(domain.AMERICAN_FOOTBALL_MONEY_LINE), "MONEY_LINE"},
|
||||
{int64(domain.AMERICAN_FOOTBALL_SPREAD), "SPREAD"},
|
||||
{int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS), "TOTAL_POINTS"},
|
||||
}
|
||||
|
||||
for _, m := range markets {
|
||||
t.Run(m.name, func(t *testing.T) {
|
||||
// Each subtest below covers a key scenario for the given NFL market.
|
||||
switch m.marketID {
|
||||
case int64(domain.AMERICAN_FOOTBALL_MONEY_LINE):
|
||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||
t.Run("Home Win", func(t *testing.T) {
|
||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
||||
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win", func(t *testing.T) {
|
||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 14, Away: 21})
|
||||
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Draw", func(t *testing.T) {
|
||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 17, Away: 17})
|
||||
t.Logf("Market: %s, Scenario: Draw", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||
})
|
||||
t.Run("Invalid OddHeader", func(t *testing.T) {
|
||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
||||
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.AMERICAN_FOOTBALL_SPREAD):
|
||||
t.Run("Home Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-3.5"}, struct{ Home, Away int }{Home: 24, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+3.5"}, struct{ Home, Away int }{Home: 20, Away: 24})
|
||||
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 21, Away: 21})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS):
|
||||
t.Run("Over Win", func(t *testing.T) {
|
||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "44.5"}, struct{ Home, Away int }{Home: 30, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Under Win", func(t *testing.T) {
|
||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "44.5"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
||||
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "37"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric OddName", func(t *testing.T) {
|
||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRugbyMarkets covers all Rugby (Union & League) market types defined in the domain.
|
||||
// For each market (Money Line, Spread, Handicap, Total Points), it tests home/away win, draw, void, and invalid input scenarios.
|
||||
func TestRugbyMarkets(t *testing.T) {
|
||||
t.Log("Testing Rugby Markets (Union & League)")
|
||||
markets := []struct {
|
||||
marketID int64
|
||||
name string
|
||||
}{
|
||||
{int64(domain.RUGBY_MONEY_LINE), "MONEY_LINE"},
|
||||
{int64(domain.RUGBY_SPREAD), "SPREAD"},
|
||||
{int64(domain.RUGBY_TOTAL_POINTS), "TOTAL_POINTS"},
|
||||
{int64(domain.RUGBY_HANDICAP), "HANDICAP"},
|
||||
}
|
||||
|
||||
for _, m := range markets {
|
||||
t.Run(m.name, func(t *testing.T) {
|
||||
// Each subtest below covers a key scenario for the given Rugby market.
|
||||
switch m.marketID {
|
||||
case int64(domain.RUGBY_MONEY_LINE):
|
||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||
t.Run("Home Win", func(t *testing.T) {
|
||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 30, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win", func(t *testing.T) {
|
||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 20, Away: 30})
|
||||
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Draw", func(t *testing.T) {
|
||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 25, Away: 25})
|
||||
t.Logf("Market: %s, Scenario: Draw", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||
})
|
||||
t.Run("Invalid OddHeader", func(t *testing.T) {
|
||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
||||
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.RUGBY_SPREAD), int64(domain.RUGBY_HANDICAP):
|
||||
t.Run("Home Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-6.5"}, struct{ Home, Away int }{Home: 28, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+6.5"}, struct{ Home, Away int }{Home: 20, Away: 28})
|
||||
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 21, Away: 21})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.RUGBY_TOTAL_POINTS):
|
||||
t.Run("Over Win", func(t *testing.T) {
|
||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "40.5"}, struct{ Home, Away int }{Home: 25, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Under Win", func(t *testing.T) {
|
||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "40.5"}, struct{ Home, Away int }{Home: 15, Away: 20})
|
||||
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "35"}, struct{ Home, Away int }{Home: 20, Away: 15})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric OddName", func(t *testing.T) {
|
||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 20, Away: 15})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseballMarkets covers all Baseball market types defined in the domain.
|
||||
// For each market (Money Line, Spread, Total Runs), it tests home/away win, draw, void, and invalid input scenarios.
|
||||
func TestBaseballMarkets(t *testing.T) {
|
||||
t.Log("Testing Baseball Markets")
|
||||
markets := []struct {
|
||||
marketID int64
|
||||
name string
|
||||
}{
|
||||
{int64(domain.BASEBALL_MONEY_LINE), "MONEY_LINE"},
|
||||
{int64(domain.BASEBALL_SPREAD), "SPREAD"},
|
||||
{int64(domain.BASEBALL_TOTAL_RUNS), "TOTAL_RUNS"},
|
||||
}
|
||||
|
||||
for _, m := range markets {
|
||||
t.Run(m.name, func(t *testing.T) {
|
||||
// Each subtest below covers a key scenario for the given Baseball market.
|
||||
switch m.marketID {
|
||||
case int64(domain.BASEBALL_MONEY_LINE):
|
||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||
t.Run("Home Win", func(t *testing.T) {
|
||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 6, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win", func(t *testing.T) {
|
||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 2, Away: 5})
|
||||
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Draw", func(t *testing.T) {
|
||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 4, Away: 4})
|
||||
t.Logf("Market: %s, Scenario: Draw", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||
})
|
||||
t.Run("Invalid OddHeader", func(t *testing.T) {
|
||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
||||
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.BASEBALL_SPREAD):
|
||||
t.Run("Home Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-1.5"}, struct{ Home, Away int }{Home: 5, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Away Win with Handicap", func(t *testing.T) {
|
||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+1.5"}, struct{ Home, Away int }{Home: 3, Away: 5})
|
||||
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 4, Away: 4})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 5, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.BASEBALL_TOTAL_RUNS):
|
||||
t.Run("Over Win", func(t *testing.T) {
|
||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "7.5"}, struct{ Home, Away int }{Home: 5, Away: 4})
|
||||
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Under Win", func(t *testing.T) {
|
||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "7.5"}, struct{ Home, Away int }{Home: 2, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
t.Run("Push (Void)", func(t *testing.T) {
|
||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "7"}, struct{ Home, Away int }{Home: 4, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
t.Run("Non-numeric OddName", func(t *testing.T) {
|
||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 4, Away: 3})
|
||||
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
189
internal/services/result_checker.go
Normal file
189
internal/services/result_checker.go
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
// ResultCheckerService handles the checking of game results
|
||||
type ResultCheckerService struct {
|
||||
// Add any dependencies here (e.g., repositories, external APIs)
|
||||
}
|
||||
|
||||
// NewResultCheckerService creates a new instance of ResultCheckerService
|
||||
func NewResultCheckerService() *ResultCheckerService {
|
||||
return &ResultCheckerService{}
|
||||
}
|
||||
|
||||
// CheckNFLResult checks the result of an NFL game
|
||||
func (s *ResultCheckerService) CheckNFLResult(data json.RawMessage) (*domain.Result, error) {
|
||||
nflResult, err := domain.ParseNFLResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse NFL result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetNFLWinner(nflResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine NFL winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatNFLScore(nflResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, nflResult.Home.Name, nflResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: nflResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": nflResult.Scores.FirstQuarter,
|
||||
"2": nflResult.Scores.SecondQuarter,
|
||||
"3": nflResult.Scores.ThirdQuarter,
|
||||
"4": nflResult.Scores.FourthQuarter,
|
||||
"5": nflResult.Scores.Overtime,
|
||||
"7": nflResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// determineOutcomeStatus determines the outcome status based on the winner and teams
|
||||
func determineOutcomeStatus(winner, homeTeam, awayTeam string) domain.OutcomeStatus {
|
||||
if winner == "Draw" {
|
||||
return domain.OUTCOME_STATUS_VOID
|
||||
}
|
||||
if winner == homeTeam {
|
||||
return domain.OUTCOME_STATUS_WIN
|
||||
}
|
||||
if winner == awayTeam {
|
||||
return domain.OUTCOME_STATUS_LOSS
|
||||
}
|
||||
return domain.OUTCOME_STATUS_PENDING
|
||||
}
|
||||
|
||||
// CheckRugbyResult checks the result of a Rugby game
|
||||
func (s *ResultCheckerService) CheckRugbyResult(data json.RawMessage) (*domain.Result, error) {
|
||||
rugbyResult, err := domain.ParseRugbyResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Rugby result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetRugbyWinner(rugbyResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine Rugby winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatRugbyScore(rugbyResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: rugbyResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": rugbyResult.Scores.FirstHalf,
|
||||
"2": rugbyResult.Scores.SecondHalf,
|
||||
"7": rugbyResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckBaseballResult checks the result of a Baseball game
|
||||
func (s *ResultCheckerService) CheckBaseballResult(data json.RawMessage) (*domain.Result, error) {
|
||||
baseballResult, err := domain.ParseBaseballResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Baseball result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetBaseballWinner(baseballResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine Baseball winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatBaseballScore(baseballResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, baseballResult.Home.Name, baseballResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: baseballResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": baseballResult.Scores.FirstInning,
|
||||
"2": baseballResult.Scores.SecondInning,
|
||||
"3": baseballResult.Scores.ThirdInning,
|
||||
"4": baseballResult.Scores.FourthInning,
|
||||
"5": baseballResult.Scores.FifthInning,
|
||||
"6": baseballResult.Scores.SixthInning,
|
||||
"7": baseballResult.Scores.SeventhInning,
|
||||
"8": baseballResult.Scores.EighthInning,
|
||||
"9": baseballResult.Scores.NinthInning,
|
||||
"10": baseballResult.Scores.ExtraInnings,
|
||||
"T": baseballResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckRugbyUnionResult checks the result of a Rugby Union game
|
||||
func (s *ResultCheckerService) CheckRugbyUnionResult(data json.RawMessage) (*domain.Result, error) {
|
||||
rugbyResult, err := domain.ParseRugbyUnionResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Rugby Union result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetRugbyWinner(rugbyResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine Rugby Union winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatRugbyScore(rugbyResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: rugbyResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": rugbyResult.Scores.FirstHalf,
|
||||
"2": rugbyResult.Scores.SecondHalf,
|
||||
"7": rugbyResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckRugbyLeagueResult checks the result of a Rugby League game
|
||||
func (s *ResultCheckerService) CheckRugbyLeagueResult(data json.RawMessage) (*domain.Result, error) {
|
||||
rugbyResult, err := domain.ParseRugbyLeagueResult(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Rugby League result: %w", err)
|
||||
}
|
||||
|
||||
winner, err := domain.GetRugbyWinner(rugbyResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine Rugby League winner: %w", err)
|
||||
}
|
||||
|
||||
score := domain.FormatRugbyScore(rugbyResult)
|
||||
|
||||
return &domain.Result{
|
||||
Status: determineOutcomeStatus(winner, rugbyResult.Home.Name, rugbyResult.Away.Name),
|
||||
Score: score,
|
||||
FullTimeScore: score,
|
||||
SS: rugbyResult.SS,
|
||||
Scores: map[string]domain.Score{
|
||||
"1": rugbyResult.Scores.FirstHalf,
|
||||
"2": rugbyResult.Scores.SecondHalf,
|
||||
"7": rugbyResult.Scores.TotalScore,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -177,6 +177,7 @@ func (a *App) initAppRoutes() {
|
|||
// Virtual Game Routes
|
||||
a.fiber.Post("/virtual-game/launch", a.authMiddleware, h.LaunchVirtualGame)
|
||||
a.fiber.Post("/virtual-game/callback", h.HandleVirtualGameCallback)
|
||||
|
||||
}
|
||||
|
||||
///user/profile get
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user