ice hockey eval

This commit is contained in:
Samuel Tariku 2025-05-07 12:57:50 +03:00
parent 30a6373990
commit 252bf04b1e
6 changed files with 327 additions and 81 deletions

View File

@ -122,6 +122,58 @@ type BasketballResultResponse struct {
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
type IceHockeyResultResponse 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:"5"`
} `json:"scores"`
Stats struct {
Shots []string `json:"shots"`
Penalties []string `json:"penalties"`
GoalsOnPowerPlay []string `json:"goals_on_power_play"`
SSeven []string `json:"s7"`
} `json:"stats"`
Extra struct {
HomePos string `json:"home_pos"`
AwayPos string `json:"away_pos"`
AwayManager map[string]string `json:"away_manager"`
HomeManager map[string]string `json:"home_manager"`
NumberOfPeriods string `json:"numberofperiods"`
PeriodLength string `json:"periodlength"`
StadiumData map[string]string `json:"stadium_data"`
Length string `json:"length"`
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"`
}
type Score struct {
Home string `json:"home"`

View File

@ -30,23 +30,35 @@ const (
BASKETBALL_DOUBLE_RESULT BasketBallMarket = 1517 //"double_result"
BASKETBALL_MATCH_RESULT_AND_TOTAL BasketBallMarket = 181125 //"match_result_and_total"
BASKETBALL_MATCH_HANDICAP_AND_TOTAL BasketBallMarket = 181126 //"match_handicap_and_total"
BASKETBALL_RACE_TO_20_POINTS BasketBallMarket = 1503 //"race_to_20_points"
BASKETBALL_TIED_AT_END_OF_REGULATION BasketBallMarket = 181127 //"tied_at_end_of_regulation"
BASKETBALL_QUARTER_CORRECT_SCORE BasketBallMarket = 181276 //"quarter_correct_score"
// Half Props
BASKETBALL_FIRST_HALF_TEAM_TOTALS BasketBallMarket = 181159 //"1st_half_team_totals"
BASKETBALL_FIRST_HALF_WINNING_MARGIN BasketBallMarket = 181185 //"1st_half_winning_margin"
BASKETBALL_FIRST_HALF_RESULT_AND_TOTAL BasketBallMarket = 181181 //"1st_half_result_and_total"
BASKETBALL_FIRST_HALF_HANDICAP_AND_TOTAL BasketBallMarket = 181182 //"1st_half_handicap_and_total"
BASKETBALL_FIRST_HALF_RACE_TO_POINTS BasketBallMarket = 181186 //"1st_half_race_to_(points)"
BASKETBALL_FIRST_HALF_BOTH_TEAMS_TO_SCORE_X_POINTS BasketBallMarket = 181195 //"1st_half_both_teams_to_score_x_points"
BASKETBALL_FIRST_HALF_TEAM_TO_SCORE_X_POINTS BasketBallMarket = 181198 //"1st_half_team_to_score_x_points"
BASKETBALL_FIRST_HALF_MONEY_LINE_3_WAY BasketBallMarket = 181183 //"1st_half_money_line_3_way"
// Others
BASKETBALL_GAME_TOTAL_ODD_EVEN BasketBallMarket = 180013 //"game_total_odd_even"
BASKETBALL_FIRST_QUARTER_TOTAL_ODD_EVEN BasketBallMarket = 180170 //"1st_quarter_total_odd_even"
BASKETBALL_HIGHEST_SCORING_HALF BasketBallMarket = 181131 //"highest_scoring_half"
BASKETBALL_HIGHEST_SCORING_QUARTER BasketBallMarket = 181132 //"highest_scoring_quarter"
BASKETBALL_FIRST_HALF_DOUBLE_CHANCE BasketBallMarket = 181184 //"1st_half_double_chance"
BASKETBALL_FIRST_HALF_TOTAL_ODD_EVEN BasketBallMarket = 181204 //"1st_half_total_odd_even"
BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL BasketBallMarket = 181243 //"1st_quarter_handicap_and_total"
BASKETBALL_FIRST_QUARTER_DOUBLE_CHANCE BasketBallMarket = 181245 //"1st_quarter_double_chance"
BASKETBALL_GAME_TOTAL_ODD_EVEN BasketBallMarket = 180013 //"game_total_odd_even"
BASKETBALL_FIRST_QUARTER_TOTAL_ODD_EVEN BasketBallMarket = 180170 //"1st_quarter_total_odd_even"
BASKETBALL_FIRST_QUARTER_MARGIN_OF_VICTORY BasketBallMarket = 180180 //"1st_quarter_margin_of_victory"
BASKETBALL_HIGHEST_SCORING_HALF BasketBallMarket = 181131 //"highest_scoring_half"
BASKETBALL_HIGHEST_SCORING_QUARTER BasketBallMarket = 181132 //"highest_scoring_quarter"
BASKETBALL_FIRST_HALF_DOUBLE_CHANCE BasketBallMarket = 181184 //"1st_half_double_chance"
BASKETBALL_FIRST_HALF_TOTAL_ODD_EVEN BasketBallMarket = 181204 //"1st_half_total_odd_even"
BASKETBALL_FIRST_QUARTER_3_WAY_LINES BasketBallMarket = 181212 //"1st_quarter_3_way_lines"
BASKETBALL_FIRST_QUARTER_RESULT_AND_TOTAL BasketBallMarket = 181242 //"1st_quarter_result_and_total"
BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL BasketBallMarket = 181243 //"1st_quarter_handicap_and_total"
BASKETBALL_FIRST_QUARTER_DOUBLE_CHANCE BasketBallMarket = 181245 //"1st_quarter_double_chance"
BASKETBALL_FIRST_QUARTER_RACE_TO_POINTS BasketBallMarket = 181248 //"1st_quarter_race_to_(points)"
BASKETBALL_FIRST_QUARTER_BOTH_TEAMS_TO_SCORE_X_POINTS BasketBallMarket = 181252 //"1st_quarter_both_teams_to_score_x_points"
BASKETBALL_FIRST_QUARTER_TEAM_TO_SCORE_X_POINTS BasketBallMarket = 181255 //"1st_quarter_team_to_score_x_points"
// Quarter Props
BASKETBALL_FIRST_QUARTER_TEAM_TOTALS BasketBallMarket = 181220 //"1st_quarter_team_totals"
@ -62,8 +74,10 @@ const (
type IceHockeyMarket int64
const (
// Main
ICE_HOCKEY_GAME_LINES IceHockeyMarket = 972
ICE_HOCKEY_FIRST_PERIOD IceHockeyMarket = 1531
ICE_HOCKEY_GAME_LINES IceHockeyMarket = 972
ICE_HOCKEY_THREE_WAY IceHockeyMarket = 170008
ICE_HOCKEY_DRAW_NO_BET IceHockeyMarket = 170447
ICE_HOCKEY_DOUBLE_CHANCE IceHockeyMarket = 170038
@ -78,70 +92,76 @@ const (
)
// TODO: Move this into the database so that it can be modified dynamically
var SupportedMarkets = map[string]MarketConfig{
"football": {
Sport: "football",
MarketCategories: map[string]bool{
"main": true,
"asian_lines": true,
"goals": true,
"half": true,
},
MarketTypes: map[int64]bool{
int64(FOOTBALL_FULL_TIME_RESULT): true, //"full_time_result"
int64(FOOTBALL_DOUBLE_CHANCE): true, //"double_chance"
int64(FOOTBALL_GOALS_OVER_UNDER): true, //"goals_over_under"
int64(FOOTBALL_CORRECT_SCORE): true, //"correct_score"
int64(FOOTBALL_ASIAN_HANDICAP): true, //"asian_handicap"
int64(FOOTBALL_GOAL_LINE): true, //"goal_line"
int64(FOOTBALL_HALF_TIME_RESULT): true, //"half_time_result"
int64(FOOTBALL_FIRST_HALF_ASIAN_HANDICAP): true, //"1st_half_asian_handicap"
int64(FOOTBALL_FIRST_HALF_GOAL_LINE): true, //"1st_half_goal_line"
int64(FOOTBALL_FIRST_TEAM_TO_SCORE): true, //"first_team_to_score"
int64(FOOTBALL_GOALS_ODD_EVEN): true, //"goals_odd_even"
int64(FOOTBALL_DRAW_NO_BET): true, //"draw_no_bet"
},
},
"basketball": {
Sport: "basketball",
MarketCategories: map[string]bool{
"main": true,
"main_props": true,
"others": true,
"quarter_props": true,
"team_props": true,
"half_props": true,
},
MarketTypes: map[int64]bool{
var SupportedMarkets = map[int64]bool{
int64(BASKETBALL_GAME_LINES): true,
int64(BASKETBALL_FIRST_HALF): true,
int64(BASKETBALL_FIRST_QUARTER): true,
int64(BASKETBALL_RESULT_AND_BOTH_TEAMS_TO_SCORE_X_POINTS): true,
int64(BASKETBALL_DOUBLE_RESULT): true,
int64(BASKETBALL_MATCH_RESULT_AND_TOTAL): true,
int64(BASKETBALL_MATCH_HANDICAP_AND_TOTAL): false,
int64(BASKETBALL_GAME_TOTAL_ODD_EVEN): true,
int64(BASKETBALL_TEAM_TOTALS): true,
int64(BASKETBALL_TEAM_TOTAL_ODD_EVEN): true,
// Football Markets
int64(FOOTBALL_FULL_TIME_RESULT): true, //"full_time_result"
int64(FOOTBALL_DOUBLE_CHANCE): true, //"double_chance"
int64(FOOTBALL_GOALS_OVER_UNDER): true, //"goals_over_under"
int64(FOOTBALL_CORRECT_SCORE): true, //"correct_score"
int64(FOOTBALL_ASIAN_HANDICAP): true, //"asian_handicap"
int64(FOOTBALL_GOAL_LINE): true, //"goal_line"
int64(FOOTBALL_HALF_TIME_RESULT): true, //"half_time_result"
int64(FOOTBALL_FIRST_HALF_ASIAN_HANDICAP): true, //"1st_half_asian_handicap"
int64(FOOTBALL_FIRST_HALF_GOAL_LINE): true, //"1st_half_goal_line"
int64(FOOTBALL_FIRST_TEAM_TO_SCORE): true, //"first_team_to_score"
int64(FOOTBALL_GOALS_ODD_EVEN): true, //"goals_odd_even"
int64(FOOTBALL_DRAW_NO_BET): true, //"draw_no_bet"
int64(BASKETBALL_FIRST_HALF_TEAM_TOTALS): true,
int64(BASKETBALL_FIRST_HALF_WINNING_MARGIN): false,
int64(BASKETBALL_FIRST_HALF_HANDICAP_AND_TOTAL): true,
int64(BASKETBALL_FIRST_HALF_BOTH_TEAMS_TO_SCORE_X_POINTS): true,
int64(BASKETBALL_FIRST_HALF_MONEY_LINE_3_WAY): true,
int64(BASKETBALL_FIRST_HALF_DOUBLE_CHANCE): true,
int64(BASKETBALL_FIRST_HALF_TOTAL_ODD_EVEN): true,
int64(BASKETBALL_HIGHEST_SCORING_HALF): true,
// Basketball Markets
int64(BASKETBALL_GAME_LINES): true,
int64(BASKETBALL_RESULT_AND_BOTH_TEAMS_TO_SCORE_X_POINTS): true,
int64(BASKETBALL_DOUBLE_RESULT): true,
int64(BASKETBALL_MATCH_RESULT_AND_TOTAL): true,
int64(BASKETBALL_MATCH_HANDICAP_AND_TOTAL): false,
int64(BASKETBALL_GAME_TOTAL_ODD_EVEN): true,
int64(BASKETBALL_TEAM_TOTALS): true,
int64(BASKETBALL_TEAM_TOTAL_ODD_EVEN): true,
int64(BASKETBALL_RACE_TO_20_POINTS): false,
int64(BASKETBALL_TIED_AT_END_OF_REGULATION): false,
int64(BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL): false,
int64(BASKETBALL_FIRST_QUARTER_DOUBLE_CHANCE): true,
int64(BASKETBALL_FIRST_QUARTER_TEAM_TOTALS): true,
int64(BASKETBALL_FIRST_QUARTER_WINNING_MARGIN): true,
int64(BASKETBALL_FIRST_QUARTER_TOTAL_ODD_EVEN): true,
int64(BASKETBALL_HIGHEST_SCORING_QUARTER): true,
int64(BASKETBALL_TEAM_WITH_HIGHEST_SCORING_QUARTER): true,
},
},
int64(BASKETBALL_FIRST_HALF): true,
int64(BASKETBALL_FIRST_HALF_TEAM_TOTALS): true,
int64(BASKETBALL_FIRST_HALF_WINNING_MARGIN): false,
int64(BASKETBALL_FIRST_HALF_HANDICAP_AND_TOTAL): true,
int64(BASKETBALL_FIRST_HALF_BOTH_TEAMS_TO_SCORE_X_POINTS): true,
int64(BASKETBALL_FIRST_HALF_MONEY_LINE_3_WAY): true,
int64(BASKETBALL_FIRST_HALF_DOUBLE_CHANCE): true,
int64(BASKETBALL_FIRST_HALF_TOTAL_ODD_EVEN): true,
int64(BASKETBALL_HIGHEST_SCORING_HALF): true,
int64(BASKETBALL_FIRST_HALF_RESULT_AND_TOTAL): false,
int64(BASKETBALL_FIRST_HALF_RACE_TO_POINTS): false,
int64(BASKETBALL_FIRST_HALF_TEAM_TO_SCORE_X_POINTS): false,
int64(BASKETBALL_FIRST_QUARTER): true,
int64(BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL): false,
int64(BASKETBALL_FIRST_QUARTER_DOUBLE_CHANCE): true,
int64(BASKETBALL_FIRST_QUARTER_TEAM_TOTALS): true,
int64(BASKETBALL_FIRST_QUARTER_WINNING_MARGIN): false,
int64(BASKETBALL_FIRST_QUARTER_TOTAL_ODD_EVEN): true,
int64(BASKETBALL_HIGHEST_SCORING_QUARTER): true,
int64(BASKETBALL_TEAM_WITH_HIGHEST_SCORING_QUARTER): true,
int64(BASKETBALL_QUARTER_CORRECT_SCORE): false,
int64(BASKETBALL_FIRST_QUARTER_3_WAY_LINES): false,
int64(BASKETBALL_FIRST_QUARTER_RESULT_AND_TOTAL): false,
int64(BASKETBALL_FIRST_QUARTER_RACE_TO_POINTS): false,
int64(BASKETBALL_FIRST_QUARTER_BOTH_TEAMS_TO_SCORE_X_POINTS): false,
int64(BASKETBALL_FIRST_QUARTER_TEAM_TO_SCORE_X_POINTS): false,
int64(BASKETBALL_FIRST_QUARTER_MARGIN_OF_VICTORY): false,
// Ice Hockey Markets
int64(ICE_HOCKEY_GAME_LINES): true,
int64(ICE_HOCKEY_FIRST_PERIOD): true,
int64(ICE_HOCKEY_THREE_WAY): true,
int64(ICE_HOCKEY_DRAW_NO_BET): true,
int64(ICE_HOCKEY_DOUBLE_CHANCE): true,
int64(ICE_HOCKEY_WINNING_MARGIN): true,
int64(ICE_HOCKEY_HIGHEST_SCORING_PERIOD): true,
int64(ICE_HOCKEY_TIED_AFTER_REGULATION): true,
int64(ICE_HOCKEY_WHEN_WILL_MATCH_END): false,
int64(ICE_HOCKEY_GAME_TOTAL_ODD_EVEN): true,
int64(ICE_HOCKEY_ALTERNATIVE_PUCK_LINE_TWO_WAY): false,
int64(ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY): false,
}

View File

@ -161,7 +161,7 @@ func evaluateGoalLine(outcome domain.BetOutcome, score struct{ Home, Away int })
func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
for _, event := range events {
if strings.Contains(event["text"], "1st Goal") {
if strings.Contains(event["text"], "1st Goal") || strings.Contains(event["text"], "Goal 1") {
if strings.Contains(event["text"], outcome.HomeTeamName) && outcome.OddName == "1" {
return domain.OUTCOME_STATUS_WIN, nil
} else if strings.Contains(event["text"], outcome.AwayTeamName) && outcome.OddName == "2" {
@ -228,7 +228,8 @@ func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }
switch outcome.OddName {
case "Money Line":
return evaluateMoneyLine(outcome, score)
case "Spread":
case "Spread", "Line":
// Since Spread betting is essentially the same thing
return evaluateAsianHandicap(outcome, score)
case "Total":
@ -251,7 +252,11 @@ func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
case "Tie", "Draw":
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 name: %s", outcome.OddName)
}
@ -283,6 +288,11 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
} else if overUnderStr[0] == "E" {
if totalScore == threshold {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
}
@ -553,7 +563,7 @@ func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{
return domain.OUTCOME_STATUS_WIN, nil
}
case "Tie":
if fourthQuarterTotal == firstQuarterTotal && fourthQuarterTotal == secondQuarterTotal && fourthQuarterTotal == thirdQuarterTotal {
if firstQuarterTotal == secondQuarterTotal || secondQuarterTotal == thirdQuarterTotal || thirdQuarterTotal == fourthQuarterTotal {
return domain.OUTCOME_STATUS_WIN, nil
}
default:
@ -612,3 +622,90 @@ func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Awa
}
}
func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
marginSplit := strings.Split(outcome.OddName, "")
if len(marginSplit) < 1 {
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
margin, err := strconv.ParseInt(marginSplit[0], 10, 64)
if err != nil {
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
isGtr := false
if len(marginSplit) == 2 {
isGtr = marginSplit[1] == "+"
}
switch outcome.OddHeader {
case "1":
if score.Home == (score.Away + int(margin)) {
return domain.OUTCOME_STATUS_WIN, nil
} else if isGtr && score.Home > (score.Away+int(margin)) {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
case "2":
if (score.Home + int(margin)) == score.Away {
return domain.OUTCOME_STATUS_WIN, nil
} else if isGtr && (score.Home+int(margin)) > score.Away {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddheader: %s", outcome.OddHeader)
}
func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
firstPeriodTotal := firstScore.Home + firstScore.Away
secondPeriodTotal := secondScore.Home + secondScore.Away
thirdPeriodTotal := thirdScore.Home + thirdScore.Away
switch outcome.OddName {
case "Period 1":
if firstPeriodTotal > secondPeriodTotal && firstPeriodTotal > thirdPeriodTotal {
return domain.OUTCOME_STATUS_WIN, nil
}
case "Period 2":
if secondPeriodTotal > firstPeriodTotal && secondPeriodTotal > thirdPeriodTotal {
return domain.OUTCOME_STATUS_WIN, nil
}
case "Period 3":
if thirdPeriodTotal > firstPeriodTotal && thirdPeriodTotal > secondPeriodTotal {
return domain.OUTCOME_STATUS_WIN, nil
}
case "Tie":
if firstPeriodTotal == secondPeriodTotal || secondPeriodTotal == thirdPeriodTotal {
return domain.OUTCOME_STATUS_WIN, nil
}
default:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
return domain.OUTCOME_STATUS_LOSS, nil
}
func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Home, Away int }) (domain.OutcomeStatus, error) {
totalScore := struct{ Home, Away int }{0, 0}
for _, score := range scores {
totalScore.Home += score.Home
totalScore.Away += score.Away
}
switch outcome.OddName {
case "Yes":
if totalScore.Home == totalScore.Away {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
case "No":
if totalScore.Home != totalScore.Away {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}

View File

@ -213,6 +213,12 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, spo
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
}
default:
s.logger.Error("Unsupported sport", "sport", sportID)
return domain.CreateResult{}, fmt.Errorf("unsupported sport: %v", sportID)
@ -283,6 +289,34 @@ func (s *Service) parseBasketball(response json.RawMessage, eventID, oddID, mark
}
func (s *Service) parseIceHockey(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
var iceHockeyRes domain.IceHockeyResultResponse
if err := json.Unmarshal(response, &iceHockeyRes); err != nil {
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if iceHockeyRes.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateIceHockeyOutcome(outcome, iceHockeyRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
return domain.CreateResult{
BetOutcomeID: 0,
EventID: eventID,
OddID: oddID,
MarketID: marketID,
Status: status,
Score: iceHockeyRes.SS,
}, nil
}
func parseScore(home string, away string) struct{ Home, Away int } {
homeVal, _ := strconv.Atoi(strings.TrimSpace(home))
awaVal, _ := strconv.Atoi(strings.TrimSpace(away))
@ -311,8 +345,7 @@ func parseStats(stats []string) struct{ Home, Away int } {
// evaluateOutcome determines the outcome status based on market type and odd
func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore, firstHalfScore struct{ Home, Away int }, corners struct{ Home, Away int }, events []map[string]string) (domain.OutcomeStatus, error) {
marketConfig := domain.SupportedMarkets["football"]
if !marketConfig.MarketTypes[outcome.MarketID] {
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)
}
@ -349,8 +382,7 @@ func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore,
}
func (s *Service) evaluateBasketballOutcome(outcome domain.BetOutcome, res domain.BasketballResultResponse) (domain.OutcomeStatus, error) {
marketConfig := domain.SupportedMarkets["basketball"]
if !marketConfig.MarketTypes[outcome.MarketID] {
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)
}
@ -416,3 +448,42 @@ func (s *Service) evaluateBasketballOutcome(outcome domain.BetOutcome, res domai
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
}
}
func (s *Service) evaluateIceHockeyOutcome(outcome domain.BetOutcome, res domain.IceHockeyResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
}
finalScore := parseSS(res.SS)
firstPeriod := parseScore(res.Scores.FirstPeriod.Home, res.Scores.FirstPeriod.Away)
secondPeriod := parseScore(res.Scores.SecondPeriod.Home, res.Scores.SecondPeriod.Away)
thirdPeriod := parseScore(res.Scores.ThirdPeriod.Home, res.Scores.ThirdPeriod.Away)
regulation := []struct{ Home, Away int }{
firstPeriod,
secondPeriod,
thirdPeriod,
}
switch outcome.MarketID {
case int64(domain.ICE_HOCKEY_GAME_LINES):
return evaluateGameLines(outcome, finalScore)
case int64(domain.ICE_HOCKEY_THREE_WAY):
return evaluateGameLines(outcome, finalScore)
case int64(domain.ICE_HOCKEY_FIRST_PERIOD):
return evaluateGameLines(outcome, firstPeriod)
case int64(domain.ICE_HOCKEY_DRAW_NO_BET):
return evaluateDrawNoBet(outcome, finalScore)
case int64(domain.ICE_HOCKEY_DOUBLE_CHANCE):
return evaluateDoubleChance(outcome, finalScore)
case int64(domain.ICE_HOCKEY_WINNING_MARGIN):
return evaluateWinningMargin(outcome, finalScore)
case int64(domain.ICE_HOCKEY_HIGHEST_SCORING_PERIOD):
return evaluateHighestScoringPeriod(outcome, firstPeriod, secondPeriod, thirdPeriod)
case int64(domain.ICE_HOCKEY_TIED_AFTER_REGULATION):
return evaluateTiedAfterRegulation(outcome, regulation)
case int64(domain.ICE_HOCKEY_GAME_TOTAL_ODD_EVEN):
return evaluateGoalsOddEven(outcome, finalScore)
}
return domain.OUTCOME_STATUS_PENDING, nil
}

View File

@ -38,8 +38,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
// },
// },
{
// spec: "0 */15 * * * *", // Every 15 minutes
spec: "0 0 * * * *", // TODO: Every hour because of the 3600 requests per hour limit
spec: "0 */15 * * * *", // Every 15 minutes
task: func() {
if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
log.Printf("FetchNonLiveOdds error: %v", err)

View File

@ -32,6 +32,13 @@ func (a *App) initAppRoutes() {
a.eventSvc,
)
a.fiber.Get("/", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "Welcome to the FortuneBet API",
"version": "1.0.1",
})
})
// Auth Routes
a.fiber.Post("/auth/login", h.LoginCustomer)
a.fiber.Post("/auth/refresh", h.RefreshToken)