feat: added new odd markets to football

This commit is contained in:
Samuel Tariku 2025-10-30 00:46:47 +03:00
parent 485cba3c9c
commit aa893ddf74
5 changed files with 872 additions and 37 deletions

View File

@ -36,12 +36,18 @@ type OddsMarket struct {
}
type FootballOddsResponse struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
Main OddsSection `json:"main"`
AsianLines OddsSection `json:"asian_lines"`
Goals OddsSection `json:"goals"`
Half OddsSection `json:"half"`
EventID string `json:"event_id"`
FI string `json:"FI"`
AsianLines OddsSection `json:"asian_lines"`
Cards OddsSection `json:"cards"`
Corners OddsSection `json:"corners"`
Goals OddsSection `json:"goals"`
Half OddsSection `json:"half"`
Main OddsSection `json:"main"`
Minutes OddsSection `json:"minutes"`
Others []OddsSection `json:"others"`
Player OddsSection `json:"player"`
Specials OddsSection `json:"specials"`
}
type BasketballOddsResponse struct {

View File

@ -3,6 +3,8 @@ package domain
type FootballMarket int64
const (
// Main
FOOTBALL_FULL_TIME_RESULT FootballMarket = 40 //"full_time_result"
FOOTBALL_DOUBLE_CHANCE FootballMarket = 10114 //"double_chance"
FOOTBALL_GOALS_OVER_UNDER FootballMarket = 981 //"goals_over_under"
@ -10,21 +12,122 @@ const (
FOOTBALL_ASIAN_HANDICAP FootballMarket = 938 //"asian_handicap"
FOOTBALL_GOAL_LINE FootballMarket = 10143 //"goal_line"
FOOTBALL_HALF_TIME_RESULT FootballMarket = 1579 //"half_time_result"
FOOTBALL_FIRST_HALF_ASIAN_HANDICAP FootballMarket = 50137 //"1st_half_asian_handicap"
FOOTBALL_FIRST_HALF_GOAL_LINE FootballMarket = 50136 //"1st_half_goal_line"
FOOTBALL_FIRST_TEAM_TO_SCORE FootballMarket = 1178 //"first_team_to_score"
FOOTBALL_GOALS_ODD_EVEN FootballMarket = 10111 //"goals_odd_even"
FOOTBALL_DRAW_NO_BET FootballMarket = 10544 //"draw_no_bet"
// Main New
FOOTBALL_FULL_TIME_RESULT_ENHANCED FootballMarket = 4001 //"full_time_result__enhanced_prices"
FOOTBALL_BOTH_TEAMS_TO_SCORE FootballMarket = 10150 //"both_teams_to_score"
FOOTBALL_RESULT_BOTH_TEAMS_TO_SCORE FootballMarket = 50404 //"result_both_teams_to_score"
FOOTBALL_MATCH_GOAL_RANGE FootballMarket = 177816 //"match_goals_range"
FOOTBALL_TEAM_GOAL_RANGE FootballMarket = 177817 //"team_goals_range"
FOOTBALL_BOTH_TEAMS_TO_RECEIVE_CARDS FootballMarket = 50942 //"both_teams_to_receive_cards"
FOOTBALL_FIRST_HALF_GOAL_RANGE FootballMarket = 177819 //"1st_half_goals_range"
FOOTBALL_SECOND_HALF_GOAL_RANGE FootballMarket = 177820 //"2nd_half_goals_range"
FOOTBALL_RESULT_GOAL_RANGE FootballMarket = 177821 //"results_goals_range"
FOOTBALL_DOUBLE_CHANCE_GOAL_RANGE FootballMarket = 177822 //"double_chance_goals_range"
FOOTBALL_CORNERS FootballMarket = 760 //"corners"
FOOTBALL_CORNERS_TWO_WAY FootballMarket = 10235 //"corners_2_way"
FOOTBALL_FIRST_HALF_CORNERS FootballMarket = 10539 //"first_half_corners"
FOOTBALL_ASIAN_TOTAL_CORNERS FootballMarket = 10164 //"asian_total_corners"
FOOTBALL_FIRST_HALF_ASIAN_CORNERS FootballMarket = 10233 //"1st_half_asian_corners"
FOOTBALL_FIRST_HALF_GOALS_ODD_EVEN FootballMarket = 10206 //"1st_half_goals_odd_even"
FOOTBALL_SECOND_HALF_GOALS_ODD_EVEN FootballMarket = 50433 //"2nd_half_goals_odd_even"
// Half
FOOTBALL_HALF_TIME_RESULT FootballMarket = 1579 //"half_time_result"
FOOTBALL_FIRST_HALF_ASIAN_HANDICAP FootballMarket = 50137 //"1st_half_asian_handicap"
FOOTBALL_FIRST_HALF_GOAL_LINE FootballMarket = 50136 //"1st_half_goal_line"
FOOTBALL_FIRST_TEAM_TO_SCORE FootballMarket = 1178 //"first_team_to_score"
FOOTBALL_GOALS_ODD_EVEN FootballMarket = 10111 //"goals_odd_even"
FOOTBALL_DRAW_NO_BET FootballMarket = 10544 //"draw_no_bet"
FOOTBALL_HALF_TIME_DOUBLE_CHANCE FootballMarket = 10257 //"half_time_double_chance"
FOOTBALL_HALF_TIME_RESULT_BOTH_TEAMS_TO_SCORE FootballMarket = 50425 //"half_time_result_both_teams_to_score"
FOOTBALL_ALTERNATE_FIRST_HALF_ASIAN_HANDICAP FootballMarket = 50265 //"alternative_1st_half_asian_handicap"
FOOTBALL_ALTERNATE_FIRST_HALF_GOAL_LINE FootballMarket = 50266 //"alternative_1st_half_goal_line"
FOOTBALL_FIRST_HALF_HANDICAP FootballMarket = 50264 //"1st_half_handicap"
FOOTBALL_ALTERNATE_FIRST_HALF_HANDICAP FootballMarket = 10207 //"alternative_1st_half_handicap_result"
FOOTBALL_FIRST_HALF_GOAL FootballMarket = 10538 //"first_half_goals"
FOOTBALL_FIRST_HALF_GOALS_ODD_EVEN FootballMarket = 10206 //"1st_half_goals_odd_even"
FOOTBALL_SECOND_HALF_GOALS_ODD_EVEN FootballMarket = 50433 //"2nd_half_goals_odd_even"
FOOTBALL_HALF_TIME_CORRECT_SCORE FootballMarket = 10540 //"half_time_correct_score"
FOOTBALL_BOTH_TEAMS_TO_SCORE_FIRST_HALF FootballMarket = 50424 //"both_teams_to_score_in_1st_half"
FOOTBALL_BOTH_TEAMS_TO_SCORE_SECOND_HALF FootballMarket = 50432 //"both_teams_to_score_in_2nd_half"
FOOTBALL_TO_SCORE_IN_HALF FootballMarket = 50419 //"to_score_in_half"
FOOTBALL_HALF_WITH_MOST_GOALS FootballMarket = 10537 //"half_with_most_goals"
FOOTBALL_HOME_TEAM_WITH_HIGHEST_SCORING_HALF FootballMarket = 50417 //"home_team_highest_scoring_half"
FOOTBALL_AWAY_TEAM_WITH_HIGHEST_SCORING_HALF FootballMarket = 50418 //"away_team_highest_scoring_half"
FOOTBALL_SECOND_HALF_RESULT FootballMarket = 10208 //"2nd_half_result"
FOOTBALL_SECOND_HALF_GOALS FootballMarket = 10209 //"2nd_half_goals"
// Minutes
FOOTBALL_TEN_MINUTE_RESULT FootballMarket = 10244 //"10_minute_result"
FOOTBALL_FIRST_TEN_MINUTE FootballMarket = 10245 //"first_10_minutes_(00:00_09:59)"
// Others
FOOTBALL_TEAM_PERFORMANCE FootballMarket = 10110 //"team_performances"
FOOTBALL_TEAM_TOTAL_GOALS FootballMarket = 10127 //"team_total_goals"
FOOTBALL_ASIAN_TOTAL_CARDS FootballMarket = 10166 //"asian_total_cards"
FOOTBALL_EXACT_TOTAL_GOALS FootballMarket = 10203 //"asian_total_cards"
FOOTBALL_ALTERNATIVE_HANDICAP_RESULT FootballMarket = 10204 //"alternative_handicap_result"
FOOTBALL_EXACT_FIRST_HALF_GOALS FootballMarket = 10205 //"exact_1st_half_goals"
FOOTBALL_CLEAN_SHEET FootballMarket = 10210 //"clean_sheet"
FOOTBALL_TEAMS_TO_SCORE FootballMarket = 10211 //"teams_to_score"
FOOTBALL_TIME_OF_FIRST_TEAM_GOAL FootballMarket = 10214 //"time_of_1st_team_goal"
FOOTBALL_FIRST_GOAL_METHOD FootballMarket = 10216 //"first_goal_method"
FOOTBALL_MULTI_SCORERS FootballMarket = 10217 //"multi_scorers"
FOOTBALL_OWN_GOAL FootballMarket = 10223 //"own_goal"
FOOTBALL_TO_SCORE_PENALTY FootballMarket = 10229 //"to_score_a_penalty"
FOOTBALL_TO_MISS_PENALTY FootballMarket = 10230 //"to_miss_a_penalty"
FOOTBALL_ASIAN_HANDICAP_CARDS FootballMarket = 10239 //"asian_handicap_cards"
FOOTBALL_CARD_HANDICAP FootballMarket = 10240 //"card_handicap"
FOOTBALL_ALTERNATIVE_CARD_HANDICAP FootballMarket = 10241 //"alternative_card_handicap"
FOOTBALL_TEAM_CARDS FootballMarket = 10242 //"team_cards"
FOOTBALL_EXACT_SECOND_HALF_GOALS FootballMarket = 10252 //"exact_2nd_half_goals"
FOOTBALL_EARLY_GOAL FootballMarket = 10258 //"early_goal"
FOOTBALL_LATE_GOAL FootballMarket = 10259 //"late_goal"
FOOTBALL_FIRST_MATCH_CORNER FootballMarket = 10519 //"first_match_corner"
FOOTBALL_LAST_MATCH_CORNER FootballMarket = 10520 //"last_match_corner"
FOOTBALL_LAST_TEAM_TO_SCORE FootballMarket = 10534 //"last_team_to_score"
FOOTBALL_CORNER_HANDICAP FootballMarket = 10535 //"corner_handicap"
FOOTBALL_NUMBER_OF_GOALS_IN_MATCH FootballMarket = 10536 //"number_of_goals_in_match"
FOOTBALL_TIME_OF_FIRST_GOAL_BRACKETS FootballMarket = 10541 //"time_of_first_goal_brackets"
FOOTBALL_CORNER_MATCH_BET FootballMarket = 1175 //"corner_match_bet"
FOOTBALL_MULTI_CORNERS FootballMarket = 1181 //"Multicorners"
FOOTBALL_TIME_OF_FIRST_CARD FootballMarket = 1183 //"time_of_first_card"
FOOTBALL_HANDICAP_RESULT FootballMarket = 171 //"handicap_result"
FOOTBALL_TOTAL_GOAL_MINUTES FootballMarket = 1776 //"total_goal_minutes"
FOOTBALL_PLAYER_TO_SCORE_ASSIST FootballMarket = 177704 //"player_to_score_or_assist"
FOOTBALL_TEAM_TO_GET_MOST FootballMarket = 177790 //"team_to_get_most"
FOOTBALL_GOALSCORER FootballMarket = 45 //"goalscorers"
FOOTBALL_FIRST_CARD_RECEIVED FootballMarket = 476 //"first_card_received"
FOOTBALL_PLAYER_CARD FootballMarket = 50135 //"player_cards"
FOOTBALL_ALTERNATIVE_ASIAN_HANDICAP FootballMarket = 50138 //"alternative_asian_handicap"
FOOTBALL_ALTERNATIVE_GOAL_LINE FootballMarket = 50139 //"alternative_goal_line"
FOOTBALL_HOME_TEAM_ODD_EVEN_GOALS FootballMarket = 50406 //"home_team_odd_even_goals"
FOOTBALL_AWAY_TEAM_ODD_EVEN_GOALS FootballMarket = 50407 //"away_team_odd_even_goals"
FOOTBALL_HOME_TEAM_EXACT_GOALS FootballMarket = 50415 //"home_team_exact_goals"
FOOTBALL_AWAY_TEAM_EXACT_GOALS FootballMarket = 50416 //"away_team_exact_goals"
FOOTBALL_HALF_TIME_RESULT_TOTAL_GOALS FootballMarket = 50426 //"half_time_result_total_goals"
FOOTBALL_BOTH_TEAMS_TO_SCORE_FIRST_HALF_SECOND_HALF FootballMarket = 50435 //"both_teams_to_score_1st_half_2nd_half"
FOOTBALL_MATCH_SHOTS_ON_TARGET FootballMarket = 50527 //"match_shots_on_target"
FOOTBALL_MATCH_SHOTS FootballMarket = 50528 //"match_shots"
FOOTBALL_TEAM_SHOTS_ON_TARGET FootballMarket = 50530 //"team_shots_on_target"
FOOTBALL_TEAM_SHOTS FootballMarket = 50532 //"team_shots"
FOOTBALL_GOAL_METHOD FootballMarket = 50962 //"goal_method"
FOOTBALL_WINNING_MARGIN FootballMarket = 56 //"winning_margin"
FOOTBALL_TIME_OF_FIRST_CORNER FootballMarket = 761 //"time_of_first_corner"
// Player
FOOTBALL_TEAM_GOALSCORER FootballMarket = 10151 //"team_goalscorer"
FOOTBALL_PLAYER_SHOTS_ON_TARGET FootballMarket = 50920 //"player_shots_on_target"
FOOTBALL_PLAYER_SHOTS FootballMarket = 50921 //"player_shots"
// Specials
FOOTBALL_SPECIALS FootballMarket = 10224 //"specials
// Corner
FOOTBALL_CORNERS FootballMarket = 760 //"corners"
FOOTBALL_CORNERS_TWO_WAY FootballMarket = 10235 //"corners_2_way"
FOOTBALL_FIRST_HALF_CORNERS FootballMarket = 10539 //"first_half_corners"
FOOTBALL_ASIAN_TOTAL_CORNERS FootballMarket = 10164 //"asian_total_corners"
FOOTBALL_FIRST_HALF_ASIAN_CORNERS FootballMarket = 10233 //"1st_half_asian_corners"
FOOTBALL_ASIAN_HANDICAP_CORNERS FootballMarket = 10165 //"asian_handicap_corners"
FOOTBALL_ALTERNATIVE_CORNER FootballMarket = 10234 //"alternative_corners"
FOOTBALL_CORNER_RACE FootballMarket = 10238 //"corners_race"
// Cards
FOOTBALL_NUMBER_OF_CARDS_IN_MATCH FootballMarket = 10542 //"number_of_cards_in_match"
)
type BasketBallMarket int64

View File

@ -328,11 +328,17 @@ func (s *ServiceImpl) ParseOddSections(ctx context.Context, res json.RawMessage,
return domain.ParseOddSectionsRes{}, err
}
eventFI = footballRes.FI
OtherRes = footballRes.Others
sections = map[string]domain.OddsSection{
"main": footballRes.Main,
"asian_lines": footballRes.AsianLines,
"goals": footballRes.Goals,
"half": footballRes.Half,
"cards": footballRes.Cards,
"corners": footballRes.Corners,
"player": footballRes.Player,
"minutes": footballRes.Minutes,
"specials": footballRes.Specials,
}
case domain.BASKETBALL:
var basketballRes domain.BasketballOddsResponse

View File

@ -10,6 +10,25 @@ import (
// Football evaluations
// helper function to parse minute from event string like "77'" or "90+1'"
func parseEventMinute(eventText string) (int, error) {
parts := strings.Split(eventText, "'")
if len(parts) == 0 {
return 0, fmt.Errorf("invalid event text format")
}
timeStr := strings.TrimSpace(parts[0])
if strings.Contains(timeStr, "+") {
timeParts := strings.Split(timeStr, "+")
base, err1 := strconv.Atoi(timeParts[0])
extra, err2 := strconv.Atoi(timeParts[1])
if err1 != nil || err2 != nil {
return 0, fmt.Errorf("invalid injury time format")
}
return base + extra, nil
}
return strconv.Atoi(timeStr)
}
// Full Time Result betting is a type of bet where the bettor predicts the outcome of a match at the end of the full 90 minutes of play.
func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
switch outcome.OddName {
@ -428,6 +447,585 @@ func evaluateCorners(outcome domain.BetOutcome, corners struct{ Home, Away int }
}
}
// evaluateBothTeamsToScore checks if both teams scored in the match.
func evaluateBothTeamsToScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
btts := score.Home > 0 && score.Away > 0
switch outcome.OddName {
case "Yes":
if btts {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
case "No":
if !btts {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name for BTTS: %s", outcome.OddName)
}
}
// evaluateResultAndBTTS checks for a combination of the match result and if both teams scored.
func evaluateResultAndBTTS(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
btts := score.Home > 0 && score.Away > 0
var bttsMatch bool
switch outcome.OddHeader {
case "Yes":
bttsMatch = btts
case "No":
bttsMatch = !btts
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header for Result/BTTS: %s", outcome.OddHeader)
}
var resultMatch bool
switch outcome.OddName {
case "1":
resultMatch = score.Home > score.Away
case "2":
resultMatch = score.Away > score.Home
case "Draw":
resultMatch = score.Home == score.Away
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name for Result/BTTS: %s", outcome.OddName)
}
if bttsMatch && resultMatch {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateCleanSheet checks if a selected team did not concede any goals.
func evaluateCleanSheet(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
var cleanSheet bool
switch outcome.OddHeader {
case "1": // Corresponds to Home team
cleanSheet = (score.Away == 0)
case "2": // Corresponds to Away team
cleanSheet = (score.Home == 0)
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header for Clean Sheet: %s", outcome.OddHeader)
}
betOnYes := outcome.OddHandicap == "Yes"
if cleanSheet == betOnYes {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateLastTeamToScore finds the last team that scored by checking events in reverse.
func evaluateLastTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
var lastGoalTeam string
for i := len(events) - 1; i >= 0; i-- {
event := events[i]
// A simple check for "Goal" in the event text
if strings.Contains(event["text"], "Goal") && !strings.Contains(event["text"], "disallowed") {
if strings.Contains(event["text"], outcome.HomeTeamName) {
lastGoalTeam = "1"
break
} else if strings.Contains(event["text"], outcome.AwayTeamName) {
lastGoalTeam = "2"
break
}
}
}
switch outcome.OddName {
case "1":
if lastGoalTeam == "1" {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
case "2":
if lastGoalTeam == "2" {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
case "No Goal", "No Goals":
if lastGoalTeam == "" {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name for Last Team to Score: %s", outcome.OddName)
}
}
// evaluateWinningMargin checks the margin of victory.
func evaluateFootballWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
homeWin := score.Home > score.Away
awayWin := score.Away > score.Home
margin := score.Home - score.Away
switch outcome.OddName {
case "Score Draw":
if margin == 0 && score.Home > 0 {
return domain.OUTCOME_STATUS_WIN, nil
}
case "No Goal":
if margin == 0 && score.Home == 0 {
return domain.OUTCOME_STATUS_WIN, nil
}
default:
// Handles margins like "1", "2", "3", "4+"
var expectedMargin int
isPlus := strings.HasSuffix(outcome.OddName, "+")
marginStr := strings.TrimSuffix(outcome.OddName, "+")
_, err := fmt.Sscanf(marginStr, "%d", &expectedMargin)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("could not parse winning margin: %s", outcome.OddName)
}
teamWon := (outcome.OddHeader == "1" && homeWin) || (outcome.OddHeader == "2" && awayWin)
if !teamWon {
return domain.OUTCOME_STATUS_LOSS, nil
}
actualMargin := abs(margin)
if isPlus {
if actualMargin >= expectedMargin {
return domain.OUTCOME_STATUS_WIN, nil
}
} else {
if actualMargin == expectedMargin {
return domain.OUTCOME_STATUS_WIN, nil
}
}
}
return domain.OUTCOME_STATUS_LOSS, nil
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
// evaluateBothTeamsToReceiveCards checks if both teams received at least one card.
func evaluateBothTeamsToReceiveCards(outcome domain.BetOutcome, yellowCards, redCards struct{ Home, Away int }) (domain.OutcomeStatus, error) {
homeCards := yellowCards.Home + redCards.Home
awayCards := yellowCards.Away + redCards.Away
var conditionMet bool
switch outcome.OddName {
case "a Card":
conditionMet = homeCards > 0 && awayCards > 0
case "2+ Cards":
conditionMet = homeCards >= 2 && awayCards >= 2
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name for Both Teams To Receive Cards: %s", outcome.OddName)
}
isBetOnYes := outcome.OddHeader == "Yes"
if conditionMet == isBetOnYes {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateHalfWithMostGoals compares total goals in each half.
func evaluateHalfWithMostGoals(outcome domain.BetOutcome, firstHalf, secondHalf struct{ Home, Away int }) (domain.OutcomeStatus, error) {
firstHalfGoals := firstHalf.Home + firstHalf.Away
secondHalfGoals := secondHalf.Home + secondHalf.Away
var won bool
switch outcome.OddName {
case "1st Half":
won = firstHalfGoals > secondHalfGoals
case "2nd Half":
won = secondHalfGoals > firstHalfGoals
case "Tie":
won = firstHalfGoals == secondHalfGoals
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name for Half with Most Goals: %s", outcome.OddName)
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateTeamHighestScoringHalf checks which half a specific team scored more goals in.
func evaluateTeamHighestScoringHalf(outcome domain.BetOutcome, firstHalf, secondHalf struct{ Home, Away int }, team string) (domain.OutcomeStatus, error) {
var first, second int
if team == "home" {
first = firstHalf.Home
second = secondHalf.Home
} else {
first = firstHalf.Away
second = secondHalf.Away
}
var won bool
switch outcome.OddName {
case "1st Half":
won = first > second
case "2nd Half":
won = second > first
case "Tie":
won = first == second
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name for Team Highest Scoring Half: %s", outcome.OddName)
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateTeamTotalGoals checks the total goals for a single team.
func evaluateTeamTotalGoals(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
var teamGoals int
if outcome.OddHeader == "1" {
teamGoals = score.Home
} else if outcome.OddHeader == "2" {
teamGoals = score.Away
} else {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header for Team Total Goals: %s", outcome.OddHeader)
}
handicapStr := strings.TrimPrefix(outcome.OddHandicap, "Over ")
handicapStr = strings.TrimPrefix(handicapStr, "Under ")
handicap, err := strconv.ParseFloat(handicapStr, 64)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap for Team Total Goals: %s", outcome.OddHandicap)
}
var won bool
if strings.HasPrefix(outcome.OddHandicap, "Over") {
won = float64(teamGoals) > handicap
} else if strings.HasPrefix(outcome.OddHandicap, "Under") {
won = float64(teamGoals) < handicap
} else {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap type for Team Total Goals: %s", outcome.OddHandicap)
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateExactTotalGoals checks for the exact number of goals scored.
func evaluateExactTotalGoals(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
totalGoals := score.Home + score.Away
betGoalsStr := strings.TrimSuffix(strings.Fields(outcome.OddName)[0], "+")
betGoals, err := strconv.Atoi(betGoalsStr)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid bet value for Exact Total Goals: %s", outcome.OddName)
}
var won bool
if strings.HasSuffix(outcome.OddName, "+") {
won = totalGoals >= betGoals
} else {
won = totalGoals == betGoals
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateTeamsToScore checks which teams scored in the match.
func evaluateTeamsToScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
homeScored := score.Home > 0
awayScored := score.Away > 0
var won bool
switch outcome.OddName {
case "Both Teams":
won = homeScored && awayScored
case "No Goal":
won = !homeScored && !awayScored
default:
if strings.HasSuffix(outcome.OddName, "Only") {
teamName := strings.TrimSuffix(outcome.OddName, " Only")
if teamName == outcome.HomeTeamName {
won = homeScored && !awayScored
} else if teamName == outcome.AwayTeamName {
won = !homeScored && awayScored
}
} else {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name for Teams to Score: %s", outcome.OddName)
}
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateFirstMatchCorner checks which team took the first corner.
func evaluateFirstMatchCorner(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
for _, event := range events {
if strings.Contains(event["text"], "Corner") {
var firstCornerTeam string
if strings.Contains(event["text"], outcome.HomeTeamName) {
firstCornerTeam = "1"
} else if strings.Contains(event["text"], outcome.AwayTeamName) {
firstCornerTeam = "2"
}
if outcome.OddName == firstCornerTeam {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
}
return domain.OUTCOME_STATUS_LOSS, nil // No corners in the match
}
// evaluateLastMatchCorner checks which team took the last corner.
func evaluateLastMatchCorner(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
for i := len(events) - 1; i >= 0; i-- {
event := events[i]
if strings.Contains(event["text"], "Corner") {
var lastCornerTeam string
if strings.Contains(event["text"], outcome.HomeTeamName) {
lastCornerTeam = "1"
} else if strings.Contains(event["text"], outcome.AwayTeamName) {
lastCornerTeam = "2"
}
if outcome.OddName == lastCornerTeam {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
}
return domain.OUTCOME_STATUS_LOSS, nil // No corners in the match
}
// evaluateCornerMatchBet determines which team had more corners.
func evaluateCornerMatchBet(outcome domain.BetOutcome, corners struct{ Home, Away int }) (domain.OutcomeStatus, error) {
var won bool
switch outcome.OddName {
case "1":
won = corners.Home > corners.Away
case "Tie":
won = corners.Home == corners.Away
case "2":
won = corners.Away > corners.Home
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name for Corner Match Bet: %s", outcome.OddName)
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateMultiCorners multiplies 1st half corners by 2nd half corners.
func evaluateMultiCorners(outcome domain.BetOutcome, totalCorners, halfTimeCorners struct{ Home, Away int }) (domain.OutcomeStatus, error) {
firstHalfTotal := halfTimeCorners.Home + halfTimeCorners.Away
secondHalfTotal := (totalCorners.Home + totalCorners.Away) - firstHalfTotal
multiCornerValue := firstHalfTotal * secondHalfTotal
handicap, err := strconv.ParseFloat(outcome.OddName, 64)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap for Multi Corners: %s", outcome.OddName)
}
var won bool
if outcome.OddHeader == "Over" {
won = float64(multiCornerValue) > handicap
} else if outcome.OddHeader == "Under" {
won = float64(multiCornerValue) < handicap
} else {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header for Multi Corners: %s", outcome.OddHeader)
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateMatchShotsOnTarget evaluates over/under for total shots on target.
func evaluateMatchShotsOnTarget(outcome domain.BetOutcome, onTarget struct{ Home, Away int }) (domain.OutcomeStatus, error) {
totalSOT := onTarget.Home + onTarget.Away
handicap, err := strconv.ParseFloat(outcome.OddName, 64)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap for Match Shots on Target: %s", outcome.OddName)
}
var won bool
if outcome.OddHeader == "Over" {
won = float64(totalSOT) > handicap
} else if outcome.OddHeader == "Under" {
won = float64(totalSOT) < handicap
} else {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header for Match Shots on Target: %s", outcome.OddHeader)
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateTeamShotsOnTarget evaluates over/under for a single team's shots on target.
func evaluateTeamShotsOnTarget(outcome domain.BetOutcome, onTarget struct{ Home, Away int }) (domain.OutcomeStatus, error) {
var teamSOT int
if outcome.OddHeader == "1" {
teamSOT = onTarget.Home
} else if outcome.OddHeader == "2" {
teamSOT = onTarget.Away
} else {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header for Team Shots on Target: %s", outcome.OddHeader)
}
handicapStr := strings.TrimPrefix(outcome.OddHandicap, "Over ")
handicapStr = strings.TrimPrefix(handicapStr, "Under ")
handicap, err := strconv.ParseFloat(handicapStr, 64)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap for Team Shots on Target: %s", outcome.OddHandicap)
}
var won bool
if strings.HasPrefix(outcome.OddHandicap, "Over") {
won = float64(teamSOT) > handicap
} else if strings.HasPrefix(outcome.OddHandicap, "Under") {
won = float64(teamSOT) < handicap
} else {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap type for Team Shots on Target: %s", outcome.OddHandicap)
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// evaluateTimeOfFirstGoal checks if the first goal's timing matches the bet.
func evaluateTimeOfFirstGoal(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
firstGoalMin := -1
for _, event := range events {
if strings.Contains(event["text"], "1st Goal") {
min, err := parseEventMinute(event["text"])
if err == nil {
firstGoalMin = min
break
}
}
}
// Logic for Late Goal / Early Goal
if strings.Contains(outcome.OddName, "before") || strings.Contains(outcome.OddName, "after") {
var timeMarker int
var isBefore, betOnYes bool
if _, err := fmt.Sscanf(outcome.OddName, "Goal before %d:%d", &timeMarker, new(int)); err == nil {
isBefore = true
} else if _, err := fmt.Sscanf(outcome.OddName, "No Goal before %d:%d", &timeMarker, new(int)); err == nil {
isBefore = true
betOnYes = false
} else if _, err := fmt.Sscanf(outcome.OddName, "Goal after %d:%d", &timeMarker, new(int)); err == nil {
isBefore = false
} else if _, err := fmt.Sscanf(outcome.OddName, "No Goal after %d:%d", &timeMarker, new(int)); err == nil {
isBefore = false
betOnYes = false
} else {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("could not parse time from odd name: %s", outcome.OddName)
}
var conditionMet bool
if isBefore {
conditionMet = firstGoalMin != -1 && firstGoalMin <= timeMarker
} else { // isAfter
// This requires finding the last goal, not just the first. This implementation is simplified for first goal.
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("late goal logic not fully implemented for all cases")
}
if conditionMet == betOnYes {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// Logic for Time Brackets
if firstGoalMin == -1 { // No Goal
if outcome.OddName == "No Goal" {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
parts := strings.Split(outcome.OddName, "-")
if len(parts) == 2 {
start, _ := strconv.Atoi(strings.TrimSpace(parts[0]))
endStr := strings.Fields(parts[1])[0]
end, _ := strconv.Atoi(endStr)
if firstGoalMin >= start && firstGoalMin <= end {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unhandled time of goal format: %s", outcome.OddName)
}
// evaluateSpecials handles various markets grouped under the "Specials" ID.
func evaluateSpecials(outcome domain.BetOutcome, finalScore, firstHalfScore, secondHalfScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
var team, betType string
team = outcome.OddHeader // "1" for home, "2" for away
betType = outcome.OddName
var won bool
switch betType {
case "To Win From Behind":
if team == "1" {
won = firstHalfScore.Home < firstHalfScore.Away && finalScore.Home > finalScore.Away
} else {
won = firstHalfScore.Away < firstHalfScore.Home && finalScore.Away > finalScore.Home
}
case "To Win to Nil":
if team == "1" {
won = finalScore.Home > finalScore.Away && finalScore.Away == 0
} else {
won = finalScore.Away > finalScore.Home && finalScore.Home == 0
}
case "To Win Either Half":
if team == "1" {
won = (firstHalfScore.Home > firstHalfScore.Away) || (secondHalfScore.Home > secondHalfScore.Away)
} else {
won = (firstHalfScore.Away > firstHalfScore.Home) || (secondHalfScore.Away > secondHalfScore.Home)
}
case "To Win Both Halves":
if team == "1" {
won = (firstHalfScore.Home > firstHalfScore.Away) && (secondHalfScore.Home > secondHalfScore.Away)
} else {
won = (firstHalfScore.Away > firstHalfScore.Home) && (secondHalfScore.Away > secondHalfScore.Home)
}
case "To Score in Both Halves":
if team == "1" {
won = firstHalfScore.Home > 0 && secondHalfScore.Home > 0
} else {
won = firstHalfScore.Away > 0 && secondHalfScore.Away > 0
}
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unsupported special market: %s", betType)
}
if won {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
// Basketball evaluations
// Game Lines is an aggregate of money line, spread and total betting markets in one
@ -629,6 +1227,9 @@ func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }
}
}
// Result and Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points
// and also the result fo the match
func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {

View File

@ -1027,7 +1027,13 @@ func (s *Service) parseFootball(resultRes json.RawMessage, outcome domain.BetOut
corners := parseStats(result.Stats.Corners)
halfTimeCorners := parseStats(result.Stats.HalfTimeCorners)
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, result.Events)
yellowCards := parseStats(result.Stats.YellowCards)
redCards := parseStats(result.Stats.RedCards)
onTarget := parseStats(result.Stats.OnTarget)
offTarget := parseStats(result.Stats.OffTarget)
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, yellowCards, redCards, onTarget, offTarget, result.Events)
if err != nil {
s.mongoLogger.Error(
@ -1478,10 +1484,15 @@ func parseStats(stats []string) struct{ Home, Away int } {
return struct{ Home, Away int }{Home: home, Away: away}
}
// evaluateOutcome determines the outcome status based on market type and odd
// evaluateFootballOutcome determines the outcome status based on market type and odd.
// It uses helper functions to process the logic for each specific market.
func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore,
firstHalfScore struct{ Home, Away int }, secondHalfScore struct{ Home, Away int },
corners struct{ Home, Away int }, halfTimeCorners struct{ Home, Away int },
firstHalfScore, secondHalfScore struct{ Home, Away int },
corners, halfTimeCorners struct{ Home, Away int },
yellowCards struct{ Home, Away int },
redCards struct{ Home, Away int },
onTarget struct{ Home, Away int },
offTarget struct{ Home, Away int },
events []map[string]string) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
@ -1494,7 +1505,7 @@ func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore,
}
switch outcome.MarketID {
case int64(domain.FOOTBALL_FULL_TIME_RESULT):
case int64(domain.FOOTBALL_FULL_TIME_RESULT), int64(domain.FOOTBALL_FULL_TIME_RESULT_ENHANCED):
return evaluateFullTimeResult(outcome, finalScore)
case int64(domain.FOOTBALL_GOALS_OVER_UNDER):
return evaluateGoalsOverUnder(outcome, finalScore)
@ -1502,13 +1513,13 @@ func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore,
return evaluateCorrectScore(outcome, finalScore)
case int64(domain.FOOTBALL_HALF_TIME_RESULT):
return evaluateHalfTimeResult(outcome, firstHalfScore)
case int64(domain.FOOTBALL_ASIAN_HANDICAP):
case int64(domain.FOOTBALL_ASIAN_HANDICAP), int64(domain.FOOTBALL_ALTERNATIVE_ASIAN_HANDICAP):
return evaluateAsianHandicap(outcome, finalScore)
case int64(domain.FOOTBALL_GOAL_LINE):
case int64(domain.FOOTBALL_GOAL_LINE), int64(domain.FOOTBALL_ALTERNATIVE_GOAL_LINE):
return evaluateGoalLine(outcome, finalScore)
case int64(domain.FOOTBALL_FIRST_HALF_ASIAN_HANDICAP):
case int64(domain.FOOTBALL_FIRST_HALF_ASIAN_HANDICAP), int64(domain.FOOTBALL_ALTERNATE_FIRST_HALF_ASIAN_HANDICAP):
return evaluateAsianHandicap(outcome, firstHalfScore)
case int64(domain.FOOTBALL_FIRST_HALF_GOAL_LINE):
case int64(domain.FOOTBALL_FIRST_HALF_GOAL_LINE), int64(domain.FOOTBALL_ALTERNATE_FIRST_HALF_GOAL_LINE):
return evaluateGoalLine(outcome, firstHalfScore)
case int64(domain.FOOTBALL_FIRST_TEAM_TO_SCORE):
return evaluateFirstTeamToScore(outcome, events)
@ -1518,20 +1529,128 @@ func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore,
return evaluateDoubleChance(outcome, finalScore)
case int64(domain.FOOTBALL_DRAW_NO_BET):
return evaluateDrawNoBet(outcome, finalScore)
case int64(domain.FOOTBALL_CORNERS):
case int64(domain.FOOTBALL_CORNERS), int64(domain.FOOTBALL_CORNERS_TWO_WAY), int64(domain.FOOTBALL_ASIAN_TOTAL_CORNERS), int64(domain.FOOTBALL_ALTERNATIVE_CORNER):
return evaluateCorners(outcome, corners)
case int64(domain.FOOTBALL_CORNERS_TWO_WAY):
return evaluateCorners(outcome, corners)
case int64(domain.FOOTBALL_FIRST_HALF_CORNERS):
return evaluateCorners(outcome, halfTimeCorners)
case int64(domain.FOOTBALL_ASIAN_TOTAL_CORNERS):
return evaluateCorners(outcome, corners)
case int64(domain.FOOTBALL_FIRST_HALF_ASIAN_CORNERS):
case int64(domain.FOOTBALL_FIRST_HALF_CORNERS), int64(domain.FOOTBALL_FIRST_HALF_ASIAN_CORNERS):
return evaluateCorners(outcome, halfTimeCorners)
case int64(domain.FOOTBALL_FIRST_HALF_GOALS_ODD_EVEN):
return evaluateGoalsOddEven(outcome, firstHalfScore)
case int64(domain.FOOTBALL_SECOND_HALF_GOALS_ODD_EVEN):
return evaluateGoalsOddEven(outcome, secondHalfScore)
case int64(domain.FOOTBALL_BOTH_TEAMS_TO_SCORE):
return evaluateBothTeamsToScore(outcome, finalScore)
case int64(domain.FOOTBALL_RESULT_BOTH_TEAMS_TO_SCORE):
return evaluateResultAndBTTS(outcome, finalScore)
case int64(domain.FOOTBALL_HALF_TIME_CORRECT_SCORE):
return evaluateCorrectScore(outcome, firstHalfScore)
case int64(domain.FOOTBALL_BOTH_TEAMS_TO_SCORE_FIRST_HALF):
return evaluateBothTeamsToScore(outcome, firstHalfScore)
case int64(domain.FOOTBALL_BOTH_TEAMS_TO_SCORE_SECOND_HALF):
return evaluateBothTeamsToScore(outcome, secondHalfScore)
case int64(domain.FOOTBALL_SECOND_HALF_RESULT):
return evaluateFullTimeResult(outcome, secondHalfScore)
case int64(domain.FOOTBALL_CLEAN_SHEET):
return evaluateCleanSheet(outcome, finalScore)
case int64(domain.FOOTBALL_LAST_TEAM_TO_SCORE):
return evaluateLastTeamToScore(outcome, events)
case int64(domain.FOOTBALL_WINNING_MARGIN):
return evaluateFootballWinningMargin(outcome, finalScore)
case int64(domain.FOOTBALL_BOTH_TEAMS_TO_RECEIVE_CARDS):
return evaluateBothTeamsToReceiveCards(outcome, yellowCards, redCards)
case int64(domain.FOOTBALL_HALF_TIME_DOUBLE_CHANCE):
return evaluateDoubleChance(outcome, firstHalfScore)
case int64(domain.FOOTBALL_HALF_TIME_RESULT_BOTH_TEAMS_TO_SCORE):
return evaluateResultAndBTTS(outcome, firstHalfScore)
case int64(domain.FOOTBALL_HALF_WITH_MOST_GOALS):
return evaluateHalfWithMostGoals(outcome, firstHalfScore, secondHalfScore)
case int64(domain.FOOTBALL_HOME_TEAM_WITH_HIGHEST_SCORING_HALF):
return evaluateTeamHighestScoringHalf(outcome, firstHalfScore, secondHalfScore, "home")
case int64(domain.FOOTBALL_AWAY_TEAM_WITH_HIGHEST_SCORING_HALF):
return evaluateTeamHighestScoringHalf(outcome, firstHalfScore, secondHalfScore, "away")
case int64(domain.FOOTBALL_SECOND_HALF_GOALS):
return evaluateGoalsOverUnder(outcome, secondHalfScore)
case int64(domain.FOOTBALL_TEAM_TOTAL_GOALS):
return evaluateTeamTotalGoals(outcome, finalScore)
case int64(domain.FOOTBALL_EXACT_TOTAL_GOALS):
return evaluateExactTotalGoals(outcome, finalScore)
case int64(domain.FOOTBALL_EXACT_FIRST_HALF_GOALS):
return evaluateExactTotalGoals(outcome, firstHalfScore)
case int64(domain.FOOTBALL_TEAMS_TO_SCORE):
return evaluateTeamsToScore(outcome, finalScore)
case int64(domain.FOOTBALL_EXACT_SECOND_HALF_GOALS):
return evaluateExactTotalGoals(outcome, secondHalfScore)
case int64(domain.FOOTBALL_FIRST_MATCH_CORNER):
return evaluateFirstMatchCorner(outcome, events)
case int64(domain.FOOTBALL_LAST_MATCH_CORNER):
return evaluateLastMatchCorner(outcome, events)
case int64(domain.FOOTBALL_CORNER_MATCH_BET):
return evaluateCornerMatchBet(outcome, corners)
case int64(domain.FOOTBALL_MULTI_CORNERS):
return evaluateMultiCorners(outcome, corners, halfTimeCorners)
case int64(domain.FOOTBALL_MATCH_SHOTS_ON_TARGET):
return evaluateMatchShotsOnTarget(outcome, onTarget)
case int64(domain.FOOTBALL_TEAM_SHOTS_ON_TARGET):
return evaluateTeamShotsOnTarget(outcome, onTarget)
case int64(domain.FOOTBALL_SPECIALS):
return evaluateSpecials(outcome, finalScore, firstHalfScore, secondHalfScore)
case int64(domain.FOOTBALL_ASIAN_HANDICAP_CORNERS), int64(domain.FOOTBALL_CORNER_HANDICAP):
return evaluateAsianHandicap(outcome, corners) // Re-use AsianHandicap logic with corner data
case int64(domain.FOOTBALL_ASIAN_TOTAL_CARDS), int64(domain.FOOTBALL_NUMBER_OF_CARDS_IN_MATCH):
// Assuming 1 point for a yellow card and 2 for a red card.
totalCards := yellowCards.Home + yellowCards.Away + redCards.Home + redCards.Away
cardScore := struct{ Home, Away int }{totalCards, 0}
return evaluateGoalLine(outcome, cardScore) // Re-use GoalLine logic
case int64(domain.FOOTBALL_TIME_OF_FIRST_GOAL_BRACKETS), int64(domain.FOOTBALL_EARLY_GOAL), int64(domain.FOOTBALL_LATE_GOAL):
return evaluateTimeOfFirstGoal(outcome, events)
// --- Unimplemented Markets ---
// Reason: The logic for these markets often requires specific data points that are not available
// or not clearly structured in the provided result JSON and BetOutcome data structure.
case int64(domain.FOOTBALL_MATCH_GOAL_RANGE),
int64(domain.FOOTBALL_TEAM_GOAL_RANGE),
int64(domain.FOOTBALL_FIRST_HALF_GOAL_RANGE),
int64(domain.FOOTBALL_SECOND_HALF_GOAL_RANGE),
int64(domain.FOOTBALL_RESULT_GOAL_RANGE),
int64(domain.FOOTBALL_DOUBLE_CHANCE_GOAL_RANGE):
// Unimplemented: The logic requires a goal range (e.g., "1-2", "3-4"), often specified in odds data as 'ED'.
// This range data is not available as a field in the domain.BetOutcome struct provided to this function.
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: data for range (ED) not in BetOutcome")
case int64(domain.FOOTBALL_FIRST_HALF_HANDICAP),
int64(domain.FOOTBALL_ALTERNATE_FIRST_HALF_HANDICAP),
int64(domain.FOOTBALL_ALTERNATIVE_HANDICAP_RESULT),
int64(domain.FOOTBALL_HANDICAP_RESULT):
// Unimplemented: Standard handicap markets (3-way) require specific logic to handle the "Tie" outcome based on the handicap,
// which differs from Asian Handicap. This logic needs to be built out.
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: 3-way handicap logic is not yet built")
case int64(domain.FOOTBALL_TO_SCORE_IN_HALF),
int64(domain.FOOTBALL_BOTH_TEAMS_TO_SCORE_FIRST_HALF_SECOND_HALF):
// Unimplemented: The BetOutcome struct does not clearly distinguish which half the bet applies to for this market type.
// A field indicating "1st Half", "2nd Half", or "Yes/No" for each half is needed.
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: BetOutcome struct lacks required fields for half-specific bets")
case int64(domain.FOOTBALL_TEN_MINUTE_RESULT),
int64(domain.FOOTBALL_FIRST_TEN_MINUTE),
int64(domain.FOOTBALL_TIME_OF_FIRST_TEAM_GOAL),
int64(domain.FOOTBALL_TIME_OF_FIRST_CARD),
int64(domain.FOOTBALL_TIME_OF_FIRST_CORNER):
// Unimplemented: Requires parsing event timestamps and comparing them to specific minute markers.
// While event data is available, the specific logic for each of these time-based markets needs to be implemented.
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: requires detailed time-based event parsing logic")
case int64(domain.FOOTBALL_FIRST_GOAL_METHOD),
int64(domain.FOOTBALL_OWN_GOAL),
int64(domain.FOOTBALL_TO_MISS_PENALTY):
// Unimplemented: The event result data does not specify the method of the goal (e.g., Shot, Header, Penalty, Own Goal)
// or provide details about penalties that were missed.
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: result data lacks goal method or missed penalty info")
case int64(domain.FOOTBALL_GOALSCORER),
int64(domain.FOOTBALL_MULTI_SCORERS),
int64(domain.FOOTBALL_PLAYER_TO_SCORE_ASSIST),
int64(domain.FOOTBALL_PLAYER_CARD),
int64(domain.FOOTBALL_PLAYER_SHOTS_ON_TARGET),
int64(domain.FOOTBALL_PLAYER_SHOTS),
int64(domain.FOOTBALL_TEAM_GOALSCORER):
// Unimplemented: All player-specific markets require data mapping player names to actions (goals, cards, shots).
// The provided event result data is anonymous (e.g., "Goal - (Temperley)") and does not contain this information.
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("unimplemented market: requires player-specific data not present in results")
default:
s.mongoLogger.Warn(
@ -1540,7 +1659,7 @@ func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore,
zap.Int64("market_id", outcome.MarketID),
zap.Int64("outcome_id", outcome.ID),
)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
}
}