futsal support - also added test for evaluations
This commit is contained in:
parent
ecaad893ba
commit
97dc54e8ae
|
|
@ -278,6 +278,42 @@ type DartsResultResponse struct {
|
||||||
Bet365ID string `json:"bet365_id"`
|
Bet365ID string `json:"bet365_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FutsalResultResponse 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 {
|
||||||
|
FirstPeriod Score `json:"1"`
|
||||||
|
SecondPeriod Score `json:"2"`
|
||||||
|
ThirdPeriod Score `json:"3"`
|
||||||
|
TotalScore Score `json:"4"`
|
||||||
|
} `json:"scores"`
|
||||||
|
Events []map[string]string `json:"events"`
|
||||||
|
InplayCreatedAt string `json:"inplay_created_at"`
|
||||||
|
InplayUpdatedAt string `json:"inplay_updated_at"`
|
||||||
|
ConfirmedAt string `json:"confirmed_at"`
|
||||||
|
Bet365ID string `json:"bet365_id"`
|
||||||
|
}
|
||||||
|
|
||||||
type Score struct {
|
type Score struct {
|
||||||
Home string `json:"home"`
|
Home string `json:"home"`
|
||||||
Away string `json:"away"`
|
Away string `json:"away"`
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,21 @@ const (
|
||||||
DARTS_FIRST_DART DartsMarket = 150125 // first_dart
|
DARTS_FIRST_DART DartsMarket = 150125 // first_dart
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FutsalMarket int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Main
|
||||||
|
FUTSAL_GAME_LINES FutsalMarket = 830001
|
||||||
|
FUTSAL_MONEY_LINE FutsalMarket = 830130
|
||||||
|
|
||||||
|
// Others
|
||||||
|
FUTSAL_DOUBLE_RESULT_9_WAY FutsalMarket = 830124
|
||||||
|
|
||||||
|
// Score
|
||||||
|
FUTSAL_TEAM_TO_SCORE_FIRST FutsalMarket = 830141
|
||||||
|
FUTSAL_RACE_TO_GOALS FutsalMarket = 830142
|
||||||
|
)
|
||||||
|
|
||||||
// TODO: Move this into the database so that it can be modified dynamically
|
// TODO: Move this into the database so that it can be modified dynamically
|
||||||
|
|
||||||
var SupportedMarkets = map[int64]bool{
|
var SupportedMarkets = map[int64]bool{
|
||||||
|
|
@ -251,4 +266,12 @@ var SupportedMarkets = map[int64]bool{
|
||||||
int64(DARTS_MOST_HUNDERED_EIGHTYS_HANDICAP): false,
|
int64(DARTS_MOST_HUNDERED_EIGHTYS_HANDICAP): false,
|
||||||
int64(DARTS_PLAYER_HUNDERED_EIGHTYS): false,
|
int64(DARTS_PLAYER_HUNDERED_EIGHTYS): false,
|
||||||
int64(DARTS_FIRST_DART): false,
|
int64(DARTS_FIRST_DART): false,
|
||||||
|
|
||||||
|
// Futsal Markets
|
||||||
|
int64(FUTSAL_MONEY_LINE): true,
|
||||||
|
int64(FUTSAL_GAME_LINES): true,
|
||||||
|
int64(FUTSAL_TEAM_TO_SCORE_FIRST): true,
|
||||||
|
|
||||||
|
int64(FUTSAL_DOUBLE_RESULT_9_WAY): false,
|
||||||
|
int64(FUTSAL_RACE_TO_GOALS): false,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,13 +230,19 @@ func (s *Service) FetchResult(ctx context.Context, eventID, oddID, marketID, spo
|
||||||
case domain.VOLLEYBALL:
|
case domain.VOLLEYBALL:
|
||||||
result, err = s.parseVolleyball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
result, err = s.parseVolleyball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err)
|
s.logger.Error("Failed to parse volleyball", "event id", eventID, "market_id", marketID, "error", err)
|
||||||
return domain.CreateResult{}, err
|
return domain.CreateResult{}, err
|
||||||
}
|
}
|
||||||
case domain.DARTS:
|
case domain.DARTS:
|
||||||
result, err = s.parseDarts(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
result, err = s.parseDarts(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err)
|
s.logger.Error("Failed to parse darts", "event id", eventID, "market_id", marketID, "error", err)
|
||||||
|
return domain.CreateResult{}, err
|
||||||
|
}
|
||||||
|
case domain.FUTSAL:
|
||||||
|
result, err = s.parseFutsal(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to parse futsal", "event id", eventID, "market_id", marketID, "error", err)
|
||||||
return domain.CreateResult{}, err
|
return domain.CreateResult{}, err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
@ -397,7 +403,10 @@ func (s *Service) parseDarts(response json.RawMessage, eventID, oddID, marketID
|
||||||
var dartsRes domain.DartsResultResponse
|
var dartsRes domain.DartsResultResponse
|
||||||
|
|
||||||
if err := json.Unmarshal(response, &dartsRes); err != nil {
|
if err := json.Unmarshal(response, &dartsRes); err != nil {
|
||||||
|
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
|
||||||
|
return domain.CreateResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dartsRes.TimeStatus != "3" {
|
if dartsRes.TimeStatus != "3" {
|
||||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||||
|
|
@ -421,6 +430,35 @@ func (s *Service) parseDarts(response json.RawMessage, eventID, oddID, marketID
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) parseFutsal(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||||
|
var futsalRes domain.FutsalResultResponse
|
||||||
|
|
||||||
|
if err := json.Unmarshal(response, &futsalRes); err != nil {
|
||||||
|
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
|
||||||
|
return domain.CreateResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if futsalRes.TimeStatus != "3" {
|
||||||
|
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||||
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := s.evaluateFutsalOutcome(outcome, futsalRes)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||||
|
return domain.CreateResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.CreateResult{
|
||||||
|
BetOutcomeID: 0,
|
||||||
|
EventID: eventID,
|
||||||
|
OddID: oddID,
|
||||||
|
MarketID: marketID,
|
||||||
|
Status: status,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func parseScore(home string, away string) struct{ Home, Away int } {
|
func parseScore(home string, away string) struct{ Home, Away int } {
|
||||||
homeVal, _ := strconv.Atoi(strings.TrimSpace(home))
|
homeVal, _ := strconv.Atoi(strings.TrimSpace(home))
|
||||||
awaVal, _ := strconv.Atoi(strings.TrimSpace(away))
|
awaVal, _ := strconv.Atoi(strings.TrimSpace(away))
|
||||||
|
|
@ -657,3 +695,23 @@ func (s *Service) evaluateDartsOutcome(outcome domain.BetOutcome, res domain.Dar
|
||||||
|
|
||||||
return domain.OUTCOME_STATUS_PENDING, nil
|
return domain.OUTCOME_STATUS_PENDING, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) evaluateFutsalOutcome(outcome domain.BetOutcome, res domain.FutsalResultResponse) (domain.OutcomeStatus, error) {
|
||||||
|
if !domain.SupportedMarkets[outcome.MarketID] {
|
||||||
|
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
||||||
|
}
|
||||||
|
|
||||||
|
score := parseSS(res.SS)
|
||||||
|
|
||||||
|
switch outcome.MarketID {
|
||||||
|
case int64(domain.FUTSAL_GAME_LINES):
|
||||||
|
return evaluateGameLines(outcome, score)
|
||||||
|
case int64(domain.FUTSAL_MONEY_LINE):
|
||||||
|
return evaluateMoneyLine(outcome, score)
|
||||||
|
case int64(domain.FUTSAL_TEAM_TO_SCORE_FIRST):
|
||||||
|
return evaluateFirstTeamToScore(outcome, res.Events)
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,103 @@
|
||||||
package result
|
package result
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEvaluateFullTimeResult(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
outcome domain.BetOutcome
|
||||||
|
score struct{ Home, Away int }
|
||||||
|
expected domain.OutcomeStatus
|
||||||
|
}{
|
||||||
|
{"Home win", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{2, 1}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
{"Away win", domain.BetOutcome{OddName: "2"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
{"Draw", domain.BetOutcome{OddName: "Draw"}, struct{ Home, Away int }{1, 1}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
{"Home selected, but Draw", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{1, 1}, domain.OUTCOME_STATUS_LOSS},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
status, _ := evaluateFullTimeResult(tt.outcome, tt.score)
|
||||||
|
if status != tt.expected {
|
||||||
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvaluateTotalLegs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Name string
|
||||||
|
outcome domain.BetOutcome
|
||||||
|
score struct{ Home, Away int }
|
||||||
|
expected domain.OutcomeStatus
|
||||||
|
}{
|
||||||
|
{"OverTotalLegs", domain.BetOutcome{OddName: "3", OddHeader: "Over"}, struct{ Home, Away int }{2, 4}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
{"OverTotalLegs", domain.BetOutcome{OddName: "3", OddHeader: "Under"}, struct{ Home, Away int }{2, 4}, domain.OUTCOME_STATUS_LOSS},
|
||||||
|
{"UnderTotalLegs", domain.BetOutcome{OddName: "7", OddHeader: "Under"}, struct{ Home, Away int }{2, 3}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.Name, func(t *testing.T) {
|
||||||
|
status, _ := evaluateTotalLegs(tt.outcome, tt.score)
|
||||||
|
if status != tt.expected {
|
||||||
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvaluateGameLines(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Name string
|
||||||
|
outcome domain.BetOutcome
|
||||||
|
score struct{ Home, Away int }
|
||||||
|
expected domain.OutcomeStatus
|
||||||
|
}{
|
||||||
|
{"GameLines - Total", domain.BetOutcome{OddName: "Total", OddHandicap: "O 5.5"}, struct{ Home, Away int }{2, 4}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
{"GameLines - Total", domain.BetOutcome{OddName: "Total", OddHandicap: "O 5.5"}, struct{ Home, Away int }{2, 3}, domain.OUTCOME_STATUS_LOSS},
|
||||||
|
{"GameLines - Money Line", domain.BetOutcome{OddName: "Money Line", OddHeader: "1"}, struct{ Home, Away int }{2, 3}, domain.OUTCOME_STATUS_LOSS},
|
||||||
|
{"GameLines - Money Line", domain.BetOutcome{OddName: "Money Line", OddHeader: "1"}, struct{ Home, Away int }{3, 2}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.Name, func(t *testing.T) {
|
||||||
|
status, _ := evaluateGameLines(tt.outcome, tt.score)
|
||||||
|
if status != tt.expected {
|
||||||
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirstTeamToScore(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Name string
|
||||||
|
outcome domain.BetOutcome
|
||||||
|
events []map[string]string
|
||||||
|
expected domain.OutcomeStatus
|
||||||
|
}{
|
||||||
|
{"HomeScoreFirst", domain.BetOutcome{OddName: "1", HomeTeamName: "Team A", AwayTeamName: "Team B"}, []map[string]string{
|
||||||
|
{"text": "1st Goal - Team A"},
|
||||||
|
}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
{"AwayScoreFirst", domain.BetOutcome{OddName: "2", HomeTeamName: "Team A", AwayTeamName: "Team B"}, []map[string]string{
|
||||||
|
{"text": "1st Goal - Team A"},
|
||||||
|
}, domain.OUTCOME_STATUS_LOSS},
|
||||||
|
{"AwayScoreFirst", domain.BetOutcome{OddName: "2", HomeTeamName: "Team A", AwayTeamName: "Team B"}, []map[string]string{
|
||||||
|
{"text": "1st Goal - Team B"},
|
||||||
|
}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.Name, func(t *testing.T) {
|
||||||
|
status, _ := evaluateFirstTeamToScore(tt.outcome, tt.events)
|
||||||
|
if status != tt.expected {
|
||||||
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user