This commit is contained in:
wchino
2026-06-13 17:38:25 +08:00
parent e7e938f261
commit 7b33d9f9fa
190 changed files with 23222 additions and 4336 deletions

View File

@@ -9,12 +9,24 @@ export interface ScoreInput {
ftAway: number;
}
export interface MatchStatsInput {
homeCorners?: number | null;
awayCorners?: number | null;
homeYellowCards?: number | null;
awayYellowCards?: number | null;
homeRedCards?: number | null;
awayRedCards?: number | null;
homeCards?: number | null;
awayCards?: number | null;
}
export interface SettlementInput {
marketType: string;
selectionCode: string;
handicapLine?: number | null;
totalLine?: number | null;
score: ScoreInput;
stats?: MatchStatsInput | null;
templateScores?: string[];
/** 冠军盘:获胜球队 code如 FRA、BRA */
winnerTeamCode?: string | null;
@@ -143,8 +155,41 @@ function settleCorrectScore(
return 'LOSE';
}
function resultSide(home: number, away: number): 'HOME' | 'DRAW' | 'AWAY' {
if (home > away) return 'HOME';
if (home < away) return 'AWAY';
return 'DRAW';
}
function settleHtFt(score: ScoreInput, selectionCode: string): SelectionResult {
const expected = `${resultSide(score.htHome, score.htAway)}_${resultSide(score.ftHome, score.ftAway)}`;
return selectionCode === expected ? 'WIN' : 'LOSE';
}
function settleTotalGoalsRange(totalGoals: number, selectionCode: string): SelectionResult {
if (selectionCode === 'TG_0_1') return totalGoals <= 1 ? 'WIN' : 'LOSE';
if (selectionCode === 'TG_2_3') return totalGoals >= 2 && totalGoals <= 3 ? 'WIN' : 'LOSE';
if (selectionCode === 'TG_4_6') return totalGoals >= 4 && totalGoals <= 6 ? 'WIN' : 'LOSE';
if (selectionCode === 'TG_7_PLUS') return totalGoals >= 7 ? 'WIN' : 'LOSE';
const exact = selectionCode.match(/^GOALS_(\d+)$/);
if (exact) return totalGoals === Number(exact[1]) ? 'WIN' : 'LOSE';
const exactPlus = selectionCode.match(/^GOALS_(\d+)_PLUS$/);
if (exactPlus) return totalGoals >= Number(exactPlus[1]) ? 'WIN' : 'LOSE';
return 'LOSE';
}
function requireStat(value: number | null | undefined, field: keyof MatchStatsInput): number {
if (value == null || Number.isNaN(value)) {
throw new Error(`SETTLEMENT_STAT_MISSING:${field}`);
}
return value;
}
export function settleSelection(input: SettlementInput): SelectionResult {
const { marketType, selectionCode, handicapLine, totalLine, score } = input;
const stats = input.stats ?? {};
const templates = input.templateScores ?? [];
switch (marketType) {
@@ -189,6 +234,35 @@ export function settleSelection(input: SettlementInput): SelectionResult {
const total = score.htHome + score.htAway;
return settleOverUnder(total, totalLine ?? 0, selectionCode === 'OVER');
}
case 'FT_TEAM_TOTAL_HOME':
return settleOverUnder(score.ftHome, totalLine ?? 0, selectionCode === 'OVER');
case 'FT_TEAM_TOTAL_AWAY':
return settleOverUnder(score.ftAway, totalLine ?? 0, selectionCode === 'OVER');
case 'HT_FT':
return settleHtFt(score, selectionCode);
case 'FT_TOTAL_GOALS':
return settleTotalGoalsRange(score.ftHome + score.ftAway, selectionCode);
case 'FT_CORNERS_HANDICAP': {
const homeCorners = requireStat(stats.homeCorners, 'homeCorners');
const awayCorners = requireStat(stats.awayCorners, 'awayCorners');
const line = handicapLine ?? 0;
const isHome = selectionCode === 'HOME';
const corners = isHome ? homeCorners : awayCorners;
const opp = isHome ? awayCorners : homeCorners;
return settleHandicap(corners, opp, isHome ? line : -line, isHome);
}
case 'FT_CORNERS_OVER_UNDER': {
const total =
requireStat(stats.homeCorners, 'homeCorners') +
requireStat(stats.awayCorners, 'awayCorners');
return settleOverUnder(total, totalLine ?? 0, selectionCode === 'OVER');
}
case 'FT_CARDS_OVER_UNDER': {
const total =
requireStat(stats.homeCards, 'homeCards') +
requireStat(stats.awayCards, 'awayCards');
return settleOverUnder(total, totalLine ?? 0, selectionCode === 'OVER');
}
case 'FT_CORRECT_SCORE':
return settleCorrectScore(score.ftHome, score.ftAway, selectionCode, templates);
case 'HT_CORRECT_SCORE':
@@ -262,18 +336,8 @@ export function calculateParlayPayout(
return { betResult: 'WON', payout: s.mul(combinedOdds), effectiveOdds: combinedOdds };
}
export { isQuarterHandicapOrTotal } from '@thebet365/shared';
export const FT_CORRECT_SCORE_TEMPLATE = [
'SCORE_0_0', 'SCORE_1_1', 'SCORE_2_2', 'SCORE_3_3', 'SCORE_4_4', 'OTHER_DRAW',
'SCORE_1_0', 'SCORE_2_0', 'SCORE_2_1', 'SCORE_3_0', 'SCORE_3_1', 'SCORE_3_2',
'SCORE_4_0', 'SCORE_4_1', 'SCORE_4_2', 'SCORE_4_3', 'OTHER_HOME',
'SCORE_0_1', 'SCORE_0_2', 'SCORE_1_2', 'SCORE_0_3', 'SCORE_1_3', 'SCORE_2_3',
'SCORE_0_4', 'SCORE_1_4', 'SCORE_2_4', 'SCORE_3_4', 'OTHER_AWAY',
];
export const HT_CORRECT_SCORE_TEMPLATE = [
'SCORE_0_0', 'SCORE_1_1', 'SCORE_2_2', 'OTHER_DRAW',
'SCORE_1_0', 'SCORE_2_0', 'SCORE_2_1', 'SCORE_3_0', 'OTHER_HOME',
'SCORE_0_1', 'SCORE_0_2', 'SCORE_1_2', 'SCORE_0_3', 'OTHER_AWAY',
];
export {
FT_CORRECT_SCORE_TEMPLATE,
HT_CORRECT_SCORE_TEMPLATE,
isQuarterHandicapOrTotal,
} from '@thebet365/shared';