overtime-live-trading-utils 2.1.22 → 2.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +32 -32
- package/.prettierrc +9 -9
- package/CLAUDE.md +77 -0
- package/codecov.yml +20 -20
- package/index.ts +26 -26
- package/jest.config.ts +16 -16
- package/main.js +1 -1
- package/package.json +30 -30
- package/resolution_live_markets.md +351 -0
- package/src/constants/common.ts +7 -7
- package/src/constants/errors.ts +6 -6
- package/src/constants/sports.ts +78 -78
- package/src/enums/sports.ts +109 -109
- package/src/tests/mock/MockLeagueMap.ts +170 -170
- package/src/tests/mock/MockOpticOddsEvents.ts +518 -518
- package/src/tests/mock/MockOpticSoccer.ts +9378 -9378
- package/src/tests/mock/MockSoccerRedis.ts +2308 -2308
- package/src/tests/unit/bookmakers.test.ts +79 -79
- package/src/tests/unit/markets.test.ts +156 -156
- package/src/tests/unit/odds.test.ts +92 -92
- package/src/tests/unit/resolution.test.ts +935 -1043
- package/src/tests/unit/sports.test.ts +58 -58
- package/src/tests/unit/spread.test.ts +131 -131
- package/src/types/missing-types.d.ts +2 -2
- package/src/types/odds.ts +61 -61
- package/src/types/resolution.ts +96 -96
- package/src/types/sports.ts +19 -19
- package/src/utils/bookmakers.ts +159 -159
- package/src/utils/constraints.ts +210 -210
- package/src/utils/gameMatching.ts +81 -81
- package/src/utils/markets.ts +119 -119
- package/src/utils/odds.ts +674 -674
- package/src/utils/opticOdds.ts +71 -71
- package/src/utils/resolution.ts +275 -229
- package/src/utils/sports.ts +51 -51
- package/src/utils/spread.ts +97 -97
- package/tsconfig.json +16 -16
- package/webpack.config.js +24 -24
|
@@ -1,1043 +1,935 @@
|
|
|
1
|
-
import {
|
|
2
|
-
detectCompletedPeriods,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from '../../utils/resolution';
|
|
7
|
-
import { SportPeriodType } from '../../types/resolution';
|
|
8
|
-
import {
|
|
9
|
-
MockSoccerCompletedEvent,
|
|
10
|
-
MockSoccerLiveSecondHalf,
|
|
11
|
-
MockSoccerLiveFirstHalf,
|
|
12
|
-
MockNFLCompletedEvent,
|
|
13
|
-
MockNFLLiveThirdQuarter,
|
|
14
|
-
MockMLBCompletedEvent,
|
|
15
|
-
MockMLBLiveSixthInning,
|
|
16
|
-
MockSoccerLiveFirstHalfInProgress,
|
|
17
|
-
MockNFLCompletedWithOvertime,
|
|
18
|
-
} from '../mock/MockOpticOddsEvents';
|
|
19
|
-
|
|
20
|
-
describe('Resolution Utils', () => {
|
|
21
|
-
describe('detectCompletedPeriods', () => {
|
|
22
|
-
it('Should detect completed periods for real completed soccer game (UEFA Europa League)', () => {
|
|
23
|
-
const result = detectCompletedPeriods(MockSoccerCompletedEvent);
|
|
24
|
-
|
|
25
|
-
expect(result).not.toBeNull();
|
|
26
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
27
|
-
expect(result?.readyForResolution).toBe(true);
|
|
28
|
-
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 0.0 });
|
|
29
|
-
expect(result?.periodScores['period2']).toEqual({ home: 1.0, away: 0.0 });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('Should detect completed first half in real live soccer game (2nd half)', () => {
|
|
33
|
-
const result = detectCompletedPeriods(MockSoccerLiveSecondHalf);
|
|
34
|
-
|
|
35
|
-
expect(result).not.toBeNull();
|
|
36
|
-
expect(result?.completedPeriods).toEqual([1]);
|
|
37
|
-
expect(result?.readyForResolution).toBe(true);
|
|
38
|
-
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 1.0 });
|
|
39
|
-
expect(result?.currentPeriod).toBe(2);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('Should return null for real live soccer game in first half (no completed periods)', () => {
|
|
43
|
-
const result = detectCompletedPeriods(MockSoccerLiveFirstHalf);
|
|
44
|
-
|
|
45
|
-
expect(result).toBeNull();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('Should detect completed periods for real completed NFL game (Patriots vs Panthers)', () => {
|
|
49
|
-
const result = detectCompletedPeriods(MockNFLCompletedEvent);
|
|
50
|
-
|
|
51
|
-
expect(result).not.toBeNull();
|
|
52
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
53
|
-
expect(result?.readyForResolution).toBe(true);
|
|
54
|
-
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 6.0 });
|
|
55
|
-
expect(result?.periodScores['period2']).toEqual({ home: 21.0, away: 0.0 });
|
|
56
|
-
expect(result?.periodScores['period3']).toEqual({ home: 7.0, away: 0.0 });
|
|
57
|
-
expect(result?.periodScores['period4']).toEqual({ home: 7.0, away: 7.0 });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('Should detect completed quarters in real live NFL game (3rd quarter)', () => {
|
|
61
|
-
const result = detectCompletedPeriods(MockNFLLiveThirdQuarter);
|
|
62
|
-
|
|
63
|
-
expect(result).not.toBeNull();
|
|
64
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
65
|
-
expect(result?.readyForResolution).toBe(true);
|
|
66
|
-
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
67
|
-
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
68
|
-
expect(result?.currentPeriod).toBe(3);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('Should detect completed innings for real completed MLB game (Tigers vs Guardians)', () => {
|
|
72
|
-
const result = detectCompletedPeriods(MockMLBCompletedEvent);
|
|
73
|
-
|
|
74
|
-
expect(result).not.toBeNull();
|
|
75
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
76
|
-
expect(result?.readyForResolution).toBe(true);
|
|
77
|
-
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 0.0 });
|
|
78
|
-
expect(result?.periodScores['period3']).toEqual({ home: 0.0, away: 1.0 });
|
|
79
|
-
expect(result?.periodScores['period7']).toEqual({ home: 0.0, away: 4.0 });
|
|
80
|
-
expect(result?.periodScores['period8']).toEqual({ home: 2.0, away: 0.0 });
|
|
81
|
-
expect(result?.periodScores['period9']).toEqual({ home: 0.0, away: 0.0 });
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('Should detect completed innings in real live MLB game (6th inning)', () => {
|
|
85
|
-
const result = detectCompletedPeriods(MockMLBLiveSixthInning);
|
|
86
|
-
|
|
87
|
-
expect(result).not.toBeNull();
|
|
88
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]);
|
|
89
|
-
expect(result?.readyForResolution).toBe(true);
|
|
90
|
-
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 1.0 });
|
|
91
|
-
expect(result?.periodScores['period2']).toEqual({ home: 2.0, away: 0.0 });
|
|
92
|
-
expect(result?.periodScores['period3']).toEqual({ home: 1.0, away: 0.0 });
|
|
93
|
-
expect(result?.periodScores['period4']).toEqual({ home: 0.0, away: 0.0 });
|
|
94
|
-
expect(result?.periodScores['period5']).toEqual({ home: 1.0, away: 1.0 });
|
|
95
|
-
expect(result?.currentPeriod).toBe(6);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('Should return null for real live soccer game with non-numeric period indicator (1H)', () => {
|
|
99
|
-
const result = detectCompletedPeriods(MockSoccerLiveFirstHalfInProgress);
|
|
100
|
-
|
|
101
|
-
// Period 1 exists in data but period is "1H" (non-numeric) meaning first half is still in progress
|
|
102
|
-
// Period 1 is NOT complete until we see period_2 in the data or status becomes completed
|
|
103
|
-
expect(result).toBeNull();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('Should detect completed periods for completed American Football game', () => {
|
|
107
|
-
const event = {
|
|
108
|
-
sport: {
|
|
109
|
-
id: 'football',
|
|
110
|
-
name: 'Football',
|
|
111
|
-
},
|
|
112
|
-
fixture: {
|
|
113
|
-
id: '20250930BEED03AA',
|
|
114
|
-
status: 'completed',
|
|
115
|
-
is_live: false,
|
|
116
|
-
},
|
|
117
|
-
scores: {
|
|
118
|
-
home: {
|
|
119
|
-
total: 28.0,
|
|
120
|
-
periods: {
|
|
121
|
-
period_1: 7.0,
|
|
122
|
-
period_2: 14.0,
|
|
123
|
-
period_3: 0.0,
|
|
124
|
-
period_4: 7.0,
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
away: {
|
|
128
|
-
total: 3.0,
|
|
129
|
-
periods: {
|
|
130
|
-
period_1: 3.0,
|
|
131
|
-
period_2: 0.0,
|
|
132
|
-
period_3: 0.0,
|
|
133
|
-
period_4: 0.0,
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
in_play: {
|
|
138
|
-
period: '4',
|
|
139
|
-
},
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const result = detectCompletedPeriods(event);
|
|
143
|
-
|
|
144
|
-
expect(result).not.toBeNull();
|
|
145
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
146
|
-
expect(result?.readyForResolution).toBe(true);
|
|
147
|
-
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
148
|
-
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 0.0 });
|
|
149
|
-
expect(result?.periodScores['period3']).toEqual({ home: 0.0, away: 0.0 });
|
|
150
|
-
expect(result?.periodScores['period4']).toEqual({ home: 7.0, away: 0.0 });
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('Should detect completed quarters in live American Football game', () => {
|
|
154
|
-
const event = {
|
|
155
|
-
sport: {
|
|
156
|
-
name: 'Football',
|
|
157
|
-
},
|
|
158
|
-
fixture: {
|
|
159
|
-
id: 'nfl-live-123',
|
|
160
|
-
status: 'live',
|
|
161
|
-
is_live: true,
|
|
162
|
-
},
|
|
163
|
-
scores: {
|
|
164
|
-
home: {
|
|
165
|
-
total: 21.0,
|
|
166
|
-
periods: {
|
|
167
|
-
period_1: 7.0,
|
|
168
|
-
period_2: 14.0,
|
|
169
|
-
period_3: 0.0,
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
away: {
|
|
173
|
-
total: 10.0,
|
|
174
|
-
periods: {
|
|
175
|
-
period_1: 3.0,
|
|
176
|
-
period_2: 7.0,
|
|
177
|
-
period_3: 0.0,
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
in_play: {
|
|
182
|
-
period: '3',
|
|
183
|
-
},
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const result = detectCompletedPeriods(event);
|
|
187
|
-
|
|
188
|
-
expect(result).not.toBeNull();
|
|
189
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
190
|
-
expect(result?.readyForResolution).toBe(true);
|
|
191
|
-
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
192
|
-
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
193
|
-
expect(result?.currentPeriod).toBe(3);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('Should detect all quarters complete in American Football overtime', () => {
|
|
197
|
-
const event = {
|
|
198
|
-
sport: {
|
|
199
|
-
name: 'American Football',
|
|
200
|
-
},
|
|
201
|
-
fixture: {
|
|
202
|
-
id: 'nfl-ot-456',
|
|
203
|
-
status: 'live',
|
|
204
|
-
is_live: true,
|
|
205
|
-
},
|
|
206
|
-
scores: {
|
|
207
|
-
home: {
|
|
208
|
-
total: 24.0,
|
|
209
|
-
periods: {
|
|
210
|
-
period_1: 7.0,
|
|
211
|
-
period_2: 7.0,
|
|
212
|
-
period_3: 3.0,
|
|
213
|
-
period_4: 7.0,
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
away: {
|
|
217
|
-
total: 24.0,
|
|
218
|
-
periods: {
|
|
219
|
-
period_1: 10.0,
|
|
220
|
-
period_2: 7.0,
|
|
221
|
-
period_3: 0.0,
|
|
222
|
-
period_4: 7.0,
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
in_play: {
|
|
227
|
-
period: 'overtime',
|
|
228
|
-
},
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const result = detectCompletedPeriods(event);
|
|
232
|
-
|
|
233
|
-
expect(result).not.toBeNull();
|
|
234
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
235
|
-
expect(result?.readyForResolution).toBe(true);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('Should detect completed first half in live soccer game', () => {
|
|
239
|
-
const event = {
|
|
240
|
-
sport: {
|
|
241
|
-
name: 'Soccer',
|
|
242
|
-
},
|
|
243
|
-
fixture: {
|
|
244
|
-
id: 'soccer-123',
|
|
245
|
-
status: 'live',
|
|
246
|
-
is_live: true,
|
|
247
|
-
},
|
|
248
|
-
scores: {
|
|
249
|
-
home: {
|
|
250
|
-
total: 2.0,
|
|
251
|
-
periods: {
|
|
252
|
-
period_1: 1.0,
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
away: {
|
|
256
|
-
total: 1.0,
|
|
257
|
-
periods: {
|
|
258
|
-
period_1: 0.0,
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
in_play: {
|
|
263
|
-
period: '2',
|
|
264
|
-
},
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const result = detectCompletedPeriods(event);
|
|
268
|
-
|
|
269
|
-
expect(result).not.toBeNull();
|
|
270
|
-
expect(result?.completedPeriods).toEqual([1]);
|
|
271
|
-
expect(result?.readyForResolution).toBe(true);
|
|
272
|
-
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 0.0 });
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('Should detect completed quarters in live basketball game', () => {
|
|
276
|
-
const event = {
|
|
277
|
-
sport: {
|
|
278
|
-
name: 'Basketball',
|
|
279
|
-
},
|
|
280
|
-
fixture: {
|
|
281
|
-
id: 'nba-789',
|
|
282
|
-
status: 'live',
|
|
283
|
-
is_live: true,
|
|
284
|
-
},
|
|
285
|
-
scores: {
|
|
286
|
-
home: {
|
|
287
|
-
total: 65.0,
|
|
288
|
-
periods: {
|
|
289
|
-
period_1: 25.0,
|
|
290
|
-
period_2: 20.0,
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
away: {
|
|
294
|
-
total: 62.0,
|
|
295
|
-
periods: {
|
|
296
|
-
period_1: 22.0,
|
|
297
|
-
period_2: 18.0,
|
|
298
|
-
},
|
|
299
|
-
},
|
|
300
|
-
},
|
|
301
|
-
in_play: {
|
|
302
|
-
period: '3',
|
|
303
|
-
},
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const result = detectCompletedPeriods(event);
|
|
307
|
-
|
|
308
|
-
expect(result).not.toBeNull();
|
|
309
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
310
|
-
expect(result?.readyForResolution).toBe(true);
|
|
311
|
-
expect(result?.periodScores['period1']).toEqual({ home: 25.0, away: 22.0 });
|
|
312
|
-
expect(result?.periodScores['period2']).toEqual({ home: 20.0, away: 18.0 });
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it('Should detect completed sets in live tennis match', () => {
|
|
316
|
-
const event = {
|
|
317
|
-
sport: {
|
|
318
|
-
name: 'Tennis',
|
|
319
|
-
},
|
|
320
|
-
fixture: {
|
|
321
|
-
id: 'tennis-101',
|
|
322
|
-
status: 'live',
|
|
323
|
-
is_live: true,
|
|
324
|
-
},
|
|
325
|
-
scores: {
|
|
326
|
-
home: {
|
|
327
|
-
total: 1.0,
|
|
328
|
-
periods: {
|
|
329
|
-
period_1: 6.0,
|
|
330
|
-
period_2: 3.0,
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
away: {
|
|
334
|
-
total: 1.0,
|
|
335
|
-
periods: {
|
|
336
|
-
period_1: 4.0,
|
|
337
|
-
period_2: 6.0,
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
in_play: {
|
|
342
|
-
period: '3',
|
|
343
|
-
},
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
const result = detectCompletedPeriods(event);
|
|
347
|
-
|
|
348
|
-
expect(result).not.toBeNull();
|
|
349
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
350
|
-
expect(result?.readyForResolution).toBe(true);
|
|
351
|
-
expect(result?.periodScores['period1']).toEqual({ home: 6.0, away: 4.0 });
|
|
352
|
-
expect(result?.periodScores['period2']).toEqual({ home: 3.0, away: 6.0 });
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
it('Should detect completed periods in live hockey game', () => {
|
|
356
|
-
const event = {
|
|
357
|
-
sport: {
|
|
358
|
-
name: 'Ice Hockey',
|
|
359
|
-
},
|
|
360
|
-
fixture: {
|
|
361
|
-
id: 'nhl-202',
|
|
362
|
-
status: 'live',
|
|
363
|
-
is_live: true,
|
|
364
|
-
},
|
|
365
|
-
scores: {
|
|
366
|
-
home: {
|
|
367
|
-
total: 2.0,
|
|
368
|
-
periods: {
|
|
369
|
-
period_1: 1.0,
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
away: {
|
|
373
|
-
total: 1.0,
|
|
374
|
-
periods: {
|
|
375
|
-
period_1: 1.0,
|
|
376
|
-
},
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
in_play: {
|
|
380
|
-
period: '2',
|
|
381
|
-
},
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
const result = detectCompletedPeriods(event);
|
|
385
|
-
|
|
386
|
-
expect(result).not.toBeNull();
|
|
387
|
-
expect(result?.completedPeriods).toEqual([1]);
|
|
388
|
-
expect(result?.readyForResolution).toBe(true);
|
|
389
|
-
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 1.0 });
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it('Should detect all periods complete in hockey overtime', () => {
|
|
393
|
-
const event = {
|
|
394
|
-
sport: {
|
|
395
|
-
name: 'Hockey',
|
|
396
|
-
},
|
|
397
|
-
fixture: {
|
|
398
|
-
id: 'nhl-303',
|
|
399
|
-
status: 'live',
|
|
400
|
-
is_live: true,
|
|
401
|
-
},
|
|
402
|
-
scores: {
|
|
403
|
-
home: {
|
|
404
|
-
total: 3.0,
|
|
405
|
-
periods: {
|
|
406
|
-
period_1: 1.0,
|
|
407
|
-
period_2: 1.0,
|
|
408
|
-
period_3: 1.0,
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
away: {
|
|
412
|
-
total: 3.0,
|
|
413
|
-
periods: {
|
|
414
|
-
period_1: 0.0,
|
|
415
|
-
period_2: 2.0,
|
|
416
|
-
period_3: 1.0,
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
},
|
|
420
|
-
in_play: {
|
|
421
|
-
period: 'overtime',
|
|
422
|
-
},
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
const result = detectCompletedPeriods(event);
|
|
426
|
-
|
|
427
|
-
expect(result).not.toBeNull();
|
|
428
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3]);
|
|
429
|
-
expect(result?.readyForResolution).toBe(true);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
it('Should detect completed innings in live baseball game', () => {
|
|
433
|
-
const event = {
|
|
434
|
-
sport: {
|
|
435
|
-
name: 'Baseball',
|
|
436
|
-
},
|
|
437
|
-
fixture: {
|
|
438
|
-
id: 'mlb-404',
|
|
439
|
-
status: 'live',
|
|
440
|
-
is_live: true,
|
|
441
|
-
},
|
|
442
|
-
scores: {
|
|
443
|
-
home: {
|
|
444
|
-
total: 4.0,
|
|
445
|
-
periods: {
|
|
446
|
-
period_1: 1.0,
|
|
447
|
-
period_2: 0.0,
|
|
448
|
-
period_3: 2.0,
|
|
449
|
-
period_4: 1.0,
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
away: {
|
|
453
|
-
total: 3.0,
|
|
454
|
-
periods: {
|
|
455
|
-
period_1: 0.0,
|
|
456
|
-
period_2: 1.0,
|
|
457
|
-
period_3: 1.0,
|
|
458
|
-
period_4: 1.0,
|
|
459
|
-
},
|
|
460
|
-
},
|
|
461
|
-
},
|
|
462
|
-
in_play: {
|
|
463
|
-
period: '5',
|
|
464
|
-
},
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
const result = detectCompletedPeriods(event);
|
|
468
|
-
|
|
469
|
-
expect(result).not.toBeNull();
|
|
470
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
471
|
-
expect(result?.readyForResolution).toBe(true);
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it('Should detect completed sets in live volleyball match', () => {
|
|
475
|
-
const event = {
|
|
476
|
-
sport: {
|
|
477
|
-
name: 'Volleyball',
|
|
478
|
-
},
|
|
479
|
-
fixture: {
|
|
480
|
-
id: 'volleyball-505',
|
|
481
|
-
status: 'live',
|
|
482
|
-
is_live: true,
|
|
483
|
-
},
|
|
484
|
-
scores: {
|
|
485
|
-
home: {
|
|
486
|
-
total: 1.0,
|
|
487
|
-
periods: {
|
|
488
|
-
period_1: 25.0,
|
|
489
|
-
period_2: 22.0,
|
|
490
|
-
},
|
|
491
|
-
},
|
|
492
|
-
away: {
|
|
493
|
-
total: 1.0,
|
|
494
|
-
periods: {
|
|
495
|
-
period_1: 23.0,
|
|
496
|
-
period_2: 25.0,
|
|
497
|
-
},
|
|
498
|
-
},
|
|
499
|
-
},
|
|
500
|
-
in_play: {
|
|
501
|
-
period: '3',
|
|
502
|
-
},
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
const result = detectCompletedPeriods(event);
|
|
506
|
-
|
|
507
|
-
expect(result).not.toBeNull();
|
|
508
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
509
|
-
expect(result?.readyForResolution).toBe(true);
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
it('Should return null for game not started', () => {
|
|
513
|
-
const event = {
|
|
514
|
-
sport: {
|
|
515
|
-
name: 'Soccer',
|
|
516
|
-
},
|
|
517
|
-
fixture: {
|
|
518
|
-
id: 'future-game',
|
|
519
|
-
status: 'scheduled',
|
|
520
|
-
is_live: false,
|
|
521
|
-
},
|
|
522
|
-
scores: {
|
|
523
|
-
home: {
|
|
524
|
-
total: 0.0,
|
|
525
|
-
periods: {},
|
|
526
|
-
},
|
|
527
|
-
away: {
|
|
528
|
-
total: 0.0,
|
|
529
|
-
periods: {},
|
|
530
|
-
},
|
|
531
|
-
},
|
|
532
|
-
in_play: {
|
|
533
|
-
period: null,
|
|
534
|
-
},
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
const result = detectCompletedPeriods(event);
|
|
538
|
-
|
|
539
|
-
expect(result).toBeNull();
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
it('Should return null for game in first period with no completed periods', () => {
|
|
543
|
-
const event = {
|
|
544
|
-
sport: {
|
|
545
|
-
name: 'Basketball',
|
|
546
|
-
},
|
|
547
|
-
fixture: {
|
|
548
|
-
id: 'early-game',
|
|
549
|
-
status: 'live',
|
|
550
|
-
is_live: true,
|
|
551
|
-
},
|
|
552
|
-
scores: {
|
|
553
|
-
home: {
|
|
554
|
-
total: 12.0,
|
|
555
|
-
periods: {},
|
|
556
|
-
},
|
|
557
|
-
away: {
|
|
558
|
-
total: 10.0,
|
|
559
|
-
periods: {},
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
in_play: {
|
|
563
|
-
period: '1',
|
|
564
|
-
},
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
const result = detectCompletedPeriods(event);
|
|
568
|
-
|
|
569
|
-
expect(result).toBeNull();
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
it('Should
|
|
769
|
-
const result =
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
expect(canResolveMarketsForEvent(
|
|
780
|
-
expect(canResolveMarketsForEvent(
|
|
781
|
-
expect(canResolveMarketsForEvent(
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
expect(result).
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
it('Should include
|
|
812
|
-
const typeIds = [10021, 10022,
|
|
813
|
-
const result =
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
expect(result).toBe(true);
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
it('NFL (QUARTERS_BASED): Should resolve typeId 10051 after period 2', () => {
|
|
940
|
-
// NFL: Period 2 completes 1st half (quarters 1+2)
|
|
941
|
-
const result = canResolveMarketsForEvent(
|
|
942
|
-
MockNFLLiveThirdQuarter,
|
|
943
|
-
10051,
|
|
944
|
-
SportPeriodType.QUARTERS_BASED
|
|
945
|
-
);
|
|
946
|
-
expect(result).toBe(true);
|
|
947
|
-
});
|
|
948
|
-
|
|
949
|
-
it('NFL (QUARTERS_BASED): Should NOT resolve typeId 10051 after only period 1', () => {
|
|
950
|
-
// Create mock with only period 1 complete
|
|
951
|
-
const mockNFLFirstQuarter = {
|
|
952
|
-
...MockNFLLiveThirdQuarter,
|
|
953
|
-
scores: {
|
|
954
|
-
home: { total: 7.0, periods: { period_1: 7.0 } },
|
|
955
|
-
away: { total: 3.0, periods: { period_1: 3.0 } },
|
|
956
|
-
},
|
|
957
|
-
in_play: { period: '2', clock: '5:00' },
|
|
958
|
-
};
|
|
959
|
-
|
|
960
|
-
const result = canResolveMarketsForEvent(
|
|
961
|
-
mockNFLFirstQuarter,
|
|
962
|
-
10051,
|
|
963
|
-
SportPeriodType.QUARTERS_BASED
|
|
964
|
-
);
|
|
965
|
-
expect(result).toBe(false);
|
|
966
|
-
});
|
|
967
|
-
|
|
968
|
-
it('MLB (INNINGS_BASED): Should resolve typeId 10051 after period 5', () => {
|
|
969
|
-
// MLB: Period 5 completes 1st half (innings 1-5)
|
|
970
|
-
const result = canResolveMarketsForEvent(
|
|
971
|
-
MockMLBLiveSixthInning,
|
|
972
|
-
10051,
|
|
973
|
-
SportPeriodType.INNINGS_BASED
|
|
974
|
-
);
|
|
975
|
-
expect(result).toBe(true);
|
|
976
|
-
});
|
|
977
|
-
|
|
978
|
-
it('MLB (INNINGS_BASED): Should NOT resolve typeId 10051 after only period 4', () => {
|
|
979
|
-
// Create mock with only period 1-4 complete
|
|
980
|
-
const mockMLBFourthInning = {
|
|
981
|
-
...MockMLBLiveSixthInning,
|
|
982
|
-
scores: {
|
|
983
|
-
home: {
|
|
984
|
-
total: 3.0,
|
|
985
|
-
periods: {
|
|
986
|
-
period_1: 0.0,
|
|
987
|
-
period_2: 2.0,
|
|
988
|
-
period_3: 1.0,
|
|
989
|
-
period_4: 0.0,
|
|
990
|
-
},
|
|
991
|
-
},
|
|
992
|
-
away: {
|
|
993
|
-
total: 1.0,
|
|
994
|
-
periods: {
|
|
995
|
-
period_1: 1.0,
|
|
996
|
-
period_2: 0.0,
|
|
997
|
-
period_3: 0.0,
|
|
998
|
-
period_4: 0.0,
|
|
999
|
-
},
|
|
1000
|
-
},
|
|
1001
|
-
},
|
|
1002
|
-
in_play: { period: '5', clock: null },
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
const result = canResolveMarketsForEvent(
|
|
1006
|
-
mockMLBFourthInning,
|
|
1007
|
-
10051,
|
|
1008
|
-
SportPeriodType.INNINGS_BASED
|
|
1009
|
-
);
|
|
1010
|
-
expect(result).toBe(false);
|
|
1011
|
-
});
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
describe('Sport-type-specific resolution for typeId 10052 (2nd half)', () => {
|
|
1015
|
-
it('Soccer (HALVES_BASED): Should resolve typeId 10052 after period 2', () => {
|
|
1016
|
-
const result = canResolveMarketsForEvent(
|
|
1017
|
-
MockSoccerCompletedEvent,
|
|
1018
|
-
10052,
|
|
1019
|
-
SportPeriodType.HALVES_BASED
|
|
1020
|
-
);
|
|
1021
|
-
expect(result).toBe(true);
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
it('NFL (QUARTERS_BASED): Should resolve typeId 10052 after period 4', () => {
|
|
1025
|
-
const result = canResolveMarketsForEvent(
|
|
1026
|
-
MockNFLCompletedEvent,
|
|
1027
|
-
10052,
|
|
1028
|
-
SportPeriodType.QUARTERS_BASED
|
|
1029
|
-
);
|
|
1030
|
-
expect(result).toBe(true);
|
|
1031
|
-
});
|
|
1032
|
-
|
|
1033
|
-
it('MLB (INNINGS_BASED): Should resolve typeId 10052 after period 9', () => {
|
|
1034
|
-
const result = canResolveMarketsForEvent(
|
|
1035
|
-
MockMLBCompletedEvent,
|
|
1036
|
-
10052,
|
|
1037
|
-
SportPeriodType.INNINGS_BASED
|
|
1038
|
-
);
|
|
1039
|
-
expect(result).toBe(true);
|
|
1040
|
-
});
|
|
1041
|
-
});
|
|
1042
|
-
});
|
|
1043
|
-
});
|
|
1
|
+
import {
|
|
2
|
+
detectCompletedPeriods,
|
|
3
|
+
canResolveMarketsForEvent,
|
|
4
|
+
canResolveMultipleTypeIdsForEvent,
|
|
5
|
+
filterMarketsThatCanBeResolved,
|
|
6
|
+
} from '../../utils/resolution';
|
|
7
|
+
import { SportPeriodType } from '../../types/resolution';
|
|
8
|
+
import {
|
|
9
|
+
MockSoccerCompletedEvent,
|
|
10
|
+
MockSoccerLiveSecondHalf,
|
|
11
|
+
MockSoccerLiveFirstHalf,
|
|
12
|
+
MockNFLCompletedEvent,
|
|
13
|
+
MockNFLLiveThirdQuarter,
|
|
14
|
+
MockMLBCompletedEvent,
|
|
15
|
+
MockMLBLiveSixthInning,
|
|
16
|
+
MockSoccerLiveFirstHalfInProgress,
|
|
17
|
+
MockNFLCompletedWithOvertime,
|
|
18
|
+
} from '../mock/MockOpticOddsEvents';
|
|
19
|
+
|
|
20
|
+
describe('Resolution Utils', () => {
|
|
21
|
+
describe('detectCompletedPeriods', () => {
|
|
22
|
+
it('Should detect completed periods for real completed soccer game (UEFA Europa League)', () => {
|
|
23
|
+
const result = detectCompletedPeriods(MockSoccerCompletedEvent);
|
|
24
|
+
|
|
25
|
+
expect(result).not.toBeNull();
|
|
26
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
27
|
+
expect(result?.readyForResolution).toBe(true);
|
|
28
|
+
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 0.0 });
|
|
29
|
+
expect(result?.periodScores['period2']).toEqual({ home: 1.0, away: 0.0 });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('Should detect completed first half in real live soccer game (2nd half)', () => {
|
|
33
|
+
const result = detectCompletedPeriods(MockSoccerLiveSecondHalf);
|
|
34
|
+
|
|
35
|
+
expect(result).not.toBeNull();
|
|
36
|
+
expect(result?.completedPeriods).toEqual([1]);
|
|
37
|
+
expect(result?.readyForResolution).toBe(true);
|
|
38
|
+
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 1.0 });
|
|
39
|
+
expect(result?.currentPeriod).toBe(2);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('Should return null for real live soccer game in first half (no completed periods)', () => {
|
|
43
|
+
const result = detectCompletedPeriods(MockSoccerLiveFirstHalf);
|
|
44
|
+
|
|
45
|
+
expect(result).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('Should detect completed periods for real completed NFL game (Patriots vs Panthers)', () => {
|
|
49
|
+
const result = detectCompletedPeriods(MockNFLCompletedEvent);
|
|
50
|
+
|
|
51
|
+
expect(result).not.toBeNull();
|
|
52
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
53
|
+
expect(result?.readyForResolution).toBe(true);
|
|
54
|
+
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 6.0 });
|
|
55
|
+
expect(result?.periodScores['period2']).toEqual({ home: 21.0, away: 0.0 });
|
|
56
|
+
expect(result?.periodScores['period3']).toEqual({ home: 7.0, away: 0.0 });
|
|
57
|
+
expect(result?.periodScores['period4']).toEqual({ home: 7.0, away: 7.0 });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('Should detect completed quarters in real live NFL game (3rd quarter)', () => {
|
|
61
|
+
const result = detectCompletedPeriods(MockNFLLiveThirdQuarter);
|
|
62
|
+
|
|
63
|
+
expect(result).not.toBeNull();
|
|
64
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
65
|
+
expect(result?.readyForResolution).toBe(true);
|
|
66
|
+
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
67
|
+
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
68
|
+
expect(result?.currentPeriod).toBe(3);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('Should detect completed innings for real completed MLB game (Tigers vs Guardians)', () => {
|
|
72
|
+
const result = detectCompletedPeriods(MockMLBCompletedEvent);
|
|
73
|
+
|
|
74
|
+
expect(result).not.toBeNull();
|
|
75
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
76
|
+
expect(result?.readyForResolution).toBe(true);
|
|
77
|
+
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 0.0 });
|
|
78
|
+
expect(result?.periodScores['period3']).toEqual({ home: 0.0, away: 1.0 });
|
|
79
|
+
expect(result?.periodScores['period7']).toEqual({ home: 0.0, away: 4.0 });
|
|
80
|
+
expect(result?.periodScores['period8']).toEqual({ home: 2.0, away: 0.0 });
|
|
81
|
+
expect(result?.periodScores['period9']).toEqual({ home: 0.0, away: 0.0 });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('Should detect completed innings in real live MLB game (6th inning)', () => {
|
|
85
|
+
const result = detectCompletedPeriods(MockMLBLiveSixthInning);
|
|
86
|
+
|
|
87
|
+
expect(result).not.toBeNull();
|
|
88
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]);
|
|
89
|
+
expect(result?.readyForResolution).toBe(true);
|
|
90
|
+
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 1.0 });
|
|
91
|
+
expect(result?.periodScores['period2']).toEqual({ home: 2.0, away: 0.0 });
|
|
92
|
+
expect(result?.periodScores['period3']).toEqual({ home: 1.0, away: 0.0 });
|
|
93
|
+
expect(result?.periodScores['period4']).toEqual({ home: 0.0, away: 0.0 });
|
|
94
|
+
expect(result?.periodScores['period5']).toEqual({ home: 1.0, away: 1.0 });
|
|
95
|
+
expect(result?.currentPeriod).toBe(6);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('Should return null for real live soccer game with non-numeric period indicator (1H)', () => {
|
|
99
|
+
const result = detectCompletedPeriods(MockSoccerLiveFirstHalfInProgress);
|
|
100
|
+
|
|
101
|
+
// Period 1 exists in data but period is "1H" (non-numeric) meaning first half is still in progress
|
|
102
|
+
// Period 1 is NOT complete until we see period_2 in the data or status becomes completed
|
|
103
|
+
expect(result).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('Should detect completed periods for completed American Football game', () => {
|
|
107
|
+
const event = {
|
|
108
|
+
sport: {
|
|
109
|
+
id: 'football',
|
|
110
|
+
name: 'Football',
|
|
111
|
+
},
|
|
112
|
+
fixture: {
|
|
113
|
+
id: '20250930BEED03AA',
|
|
114
|
+
status: 'completed',
|
|
115
|
+
is_live: false,
|
|
116
|
+
},
|
|
117
|
+
scores: {
|
|
118
|
+
home: {
|
|
119
|
+
total: 28.0,
|
|
120
|
+
periods: {
|
|
121
|
+
period_1: 7.0,
|
|
122
|
+
period_2: 14.0,
|
|
123
|
+
period_3: 0.0,
|
|
124
|
+
period_4: 7.0,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
away: {
|
|
128
|
+
total: 3.0,
|
|
129
|
+
periods: {
|
|
130
|
+
period_1: 3.0,
|
|
131
|
+
period_2: 0.0,
|
|
132
|
+
period_3: 0.0,
|
|
133
|
+
period_4: 0.0,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
in_play: {
|
|
138
|
+
period: '4',
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = detectCompletedPeriods(event);
|
|
143
|
+
|
|
144
|
+
expect(result).not.toBeNull();
|
|
145
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
146
|
+
expect(result?.readyForResolution).toBe(true);
|
|
147
|
+
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
148
|
+
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 0.0 });
|
|
149
|
+
expect(result?.periodScores['period3']).toEqual({ home: 0.0, away: 0.0 });
|
|
150
|
+
expect(result?.periodScores['period4']).toEqual({ home: 7.0, away: 0.0 });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('Should detect completed quarters in live American Football game', () => {
|
|
154
|
+
const event = {
|
|
155
|
+
sport: {
|
|
156
|
+
name: 'Football',
|
|
157
|
+
},
|
|
158
|
+
fixture: {
|
|
159
|
+
id: 'nfl-live-123',
|
|
160
|
+
status: 'live',
|
|
161
|
+
is_live: true,
|
|
162
|
+
},
|
|
163
|
+
scores: {
|
|
164
|
+
home: {
|
|
165
|
+
total: 21.0,
|
|
166
|
+
periods: {
|
|
167
|
+
period_1: 7.0,
|
|
168
|
+
period_2: 14.0,
|
|
169
|
+
period_3: 0.0,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
away: {
|
|
173
|
+
total: 10.0,
|
|
174
|
+
periods: {
|
|
175
|
+
period_1: 3.0,
|
|
176
|
+
period_2: 7.0,
|
|
177
|
+
period_3: 0.0,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
in_play: {
|
|
182
|
+
period: '3',
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const result = detectCompletedPeriods(event);
|
|
187
|
+
|
|
188
|
+
expect(result).not.toBeNull();
|
|
189
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
190
|
+
expect(result?.readyForResolution).toBe(true);
|
|
191
|
+
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
192
|
+
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
193
|
+
expect(result?.currentPeriod).toBe(3);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('Should detect all quarters complete in American Football overtime', () => {
|
|
197
|
+
const event = {
|
|
198
|
+
sport: {
|
|
199
|
+
name: 'American Football',
|
|
200
|
+
},
|
|
201
|
+
fixture: {
|
|
202
|
+
id: 'nfl-ot-456',
|
|
203
|
+
status: 'live',
|
|
204
|
+
is_live: true,
|
|
205
|
+
},
|
|
206
|
+
scores: {
|
|
207
|
+
home: {
|
|
208
|
+
total: 24.0,
|
|
209
|
+
periods: {
|
|
210
|
+
period_1: 7.0,
|
|
211
|
+
period_2: 7.0,
|
|
212
|
+
period_3: 3.0,
|
|
213
|
+
period_4: 7.0,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
away: {
|
|
217
|
+
total: 24.0,
|
|
218
|
+
periods: {
|
|
219
|
+
period_1: 10.0,
|
|
220
|
+
period_2: 7.0,
|
|
221
|
+
period_3: 0.0,
|
|
222
|
+
period_4: 7.0,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
in_play: {
|
|
227
|
+
period: 'overtime',
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const result = detectCompletedPeriods(event);
|
|
232
|
+
|
|
233
|
+
expect(result).not.toBeNull();
|
|
234
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
235
|
+
expect(result?.readyForResolution).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('Should detect completed first half in live soccer game', () => {
|
|
239
|
+
const event = {
|
|
240
|
+
sport: {
|
|
241
|
+
name: 'Soccer',
|
|
242
|
+
},
|
|
243
|
+
fixture: {
|
|
244
|
+
id: 'soccer-123',
|
|
245
|
+
status: 'live',
|
|
246
|
+
is_live: true,
|
|
247
|
+
},
|
|
248
|
+
scores: {
|
|
249
|
+
home: {
|
|
250
|
+
total: 2.0,
|
|
251
|
+
periods: {
|
|
252
|
+
period_1: 1.0,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
away: {
|
|
256
|
+
total: 1.0,
|
|
257
|
+
periods: {
|
|
258
|
+
period_1: 0.0,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
in_play: {
|
|
263
|
+
period: '2',
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const result = detectCompletedPeriods(event);
|
|
268
|
+
|
|
269
|
+
expect(result).not.toBeNull();
|
|
270
|
+
expect(result?.completedPeriods).toEqual([1]);
|
|
271
|
+
expect(result?.readyForResolution).toBe(true);
|
|
272
|
+
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 0.0 });
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('Should detect completed quarters in live basketball game', () => {
|
|
276
|
+
const event = {
|
|
277
|
+
sport: {
|
|
278
|
+
name: 'Basketball',
|
|
279
|
+
},
|
|
280
|
+
fixture: {
|
|
281
|
+
id: 'nba-789',
|
|
282
|
+
status: 'live',
|
|
283
|
+
is_live: true,
|
|
284
|
+
},
|
|
285
|
+
scores: {
|
|
286
|
+
home: {
|
|
287
|
+
total: 65.0,
|
|
288
|
+
periods: {
|
|
289
|
+
period_1: 25.0,
|
|
290
|
+
period_2: 20.0,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
away: {
|
|
294
|
+
total: 62.0,
|
|
295
|
+
periods: {
|
|
296
|
+
period_1: 22.0,
|
|
297
|
+
period_2: 18.0,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
in_play: {
|
|
302
|
+
period: '3',
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const result = detectCompletedPeriods(event);
|
|
307
|
+
|
|
308
|
+
expect(result).not.toBeNull();
|
|
309
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
310
|
+
expect(result?.readyForResolution).toBe(true);
|
|
311
|
+
expect(result?.periodScores['period1']).toEqual({ home: 25.0, away: 22.0 });
|
|
312
|
+
expect(result?.periodScores['period2']).toEqual({ home: 20.0, away: 18.0 });
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('Should detect completed sets in live tennis match', () => {
|
|
316
|
+
const event = {
|
|
317
|
+
sport: {
|
|
318
|
+
name: 'Tennis',
|
|
319
|
+
},
|
|
320
|
+
fixture: {
|
|
321
|
+
id: 'tennis-101',
|
|
322
|
+
status: 'live',
|
|
323
|
+
is_live: true,
|
|
324
|
+
},
|
|
325
|
+
scores: {
|
|
326
|
+
home: {
|
|
327
|
+
total: 1.0,
|
|
328
|
+
periods: {
|
|
329
|
+
period_1: 6.0,
|
|
330
|
+
period_2: 3.0,
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
away: {
|
|
334
|
+
total: 1.0,
|
|
335
|
+
periods: {
|
|
336
|
+
period_1: 4.0,
|
|
337
|
+
period_2: 6.0,
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
in_play: {
|
|
342
|
+
period: '3',
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const result = detectCompletedPeriods(event);
|
|
347
|
+
|
|
348
|
+
expect(result).not.toBeNull();
|
|
349
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
350
|
+
expect(result?.readyForResolution).toBe(true);
|
|
351
|
+
expect(result?.periodScores['period1']).toEqual({ home: 6.0, away: 4.0 });
|
|
352
|
+
expect(result?.periodScores['period2']).toEqual({ home: 3.0, away: 6.0 });
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('Should detect completed periods in live hockey game', () => {
|
|
356
|
+
const event = {
|
|
357
|
+
sport: {
|
|
358
|
+
name: 'Ice Hockey',
|
|
359
|
+
},
|
|
360
|
+
fixture: {
|
|
361
|
+
id: 'nhl-202',
|
|
362
|
+
status: 'live',
|
|
363
|
+
is_live: true,
|
|
364
|
+
},
|
|
365
|
+
scores: {
|
|
366
|
+
home: {
|
|
367
|
+
total: 2.0,
|
|
368
|
+
periods: {
|
|
369
|
+
period_1: 1.0,
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
away: {
|
|
373
|
+
total: 1.0,
|
|
374
|
+
periods: {
|
|
375
|
+
period_1: 1.0,
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
in_play: {
|
|
380
|
+
period: '2',
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const result = detectCompletedPeriods(event);
|
|
385
|
+
|
|
386
|
+
expect(result).not.toBeNull();
|
|
387
|
+
expect(result?.completedPeriods).toEqual([1]);
|
|
388
|
+
expect(result?.readyForResolution).toBe(true);
|
|
389
|
+
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 1.0 });
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('Should detect all periods complete in hockey overtime', () => {
|
|
393
|
+
const event = {
|
|
394
|
+
sport: {
|
|
395
|
+
name: 'Hockey',
|
|
396
|
+
},
|
|
397
|
+
fixture: {
|
|
398
|
+
id: 'nhl-303',
|
|
399
|
+
status: 'live',
|
|
400
|
+
is_live: true,
|
|
401
|
+
},
|
|
402
|
+
scores: {
|
|
403
|
+
home: {
|
|
404
|
+
total: 3.0,
|
|
405
|
+
periods: {
|
|
406
|
+
period_1: 1.0,
|
|
407
|
+
period_2: 1.0,
|
|
408
|
+
period_3: 1.0,
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
away: {
|
|
412
|
+
total: 3.0,
|
|
413
|
+
periods: {
|
|
414
|
+
period_1: 0.0,
|
|
415
|
+
period_2: 2.0,
|
|
416
|
+
period_3: 1.0,
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
in_play: {
|
|
421
|
+
period: 'overtime',
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const result = detectCompletedPeriods(event);
|
|
426
|
+
|
|
427
|
+
expect(result).not.toBeNull();
|
|
428
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3]);
|
|
429
|
+
expect(result?.readyForResolution).toBe(true);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('Should detect completed innings in live baseball game', () => {
|
|
433
|
+
const event = {
|
|
434
|
+
sport: {
|
|
435
|
+
name: 'Baseball',
|
|
436
|
+
},
|
|
437
|
+
fixture: {
|
|
438
|
+
id: 'mlb-404',
|
|
439
|
+
status: 'live',
|
|
440
|
+
is_live: true,
|
|
441
|
+
},
|
|
442
|
+
scores: {
|
|
443
|
+
home: {
|
|
444
|
+
total: 4.0,
|
|
445
|
+
periods: {
|
|
446
|
+
period_1: 1.0,
|
|
447
|
+
period_2: 0.0,
|
|
448
|
+
period_3: 2.0,
|
|
449
|
+
period_4: 1.0,
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
away: {
|
|
453
|
+
total: 3.0,
|
|
454
|
+
periods: {
|
|
455
|
+
period_1: 0.0,
|
|
456
|
+
period_2: 1.0,
|
|
457
|
+
period_3: 1.0,
|
|
458
|
+
period_4: 1.0,
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
in_play: {
|
|
463
|
+
period: '5',
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const result = detectCompletedPeriods(event);
|
|
468
|
+
|
|
469
|
+
expect(result).not.toBeNull();
|
|
470
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
471
|
+
expect(result?.readyForResolution).toBe(true);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('Should detect completed sets in live volleyball match', () => {
|
|
475
|
+
const event = {
|
|
476
|
+
sport: {
|
|
477
|
+
name: 'Volleyball',
|
|
478
|
+
},
|
|
479
|
+
fixture: {
|
|
480
|
+
id: 'volleyball-505',
|
|
481
|
+
status: 'live',
|
|
482
|
+
is_live: true,
|
|
483
|
+
},
|
|
484
|
+
scores: {
|
|
485
|
+
home: {
|
|
486
|
+
total: 1.0,
|
|
487
|
+
periods: {
|
|
488
|
+
period_1: 25.0,
|
|
489
|
+
period_2: 22.0,
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
away: {
|
|
493
|
+
total: 1.0,
|
|
494
|
+
periods: {
|
|
495
|
+
period_1: 23.0,
|
|
496
|
+
period_2: 25.0,
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
in_play: {
|
|
501
|
+
period: '3',
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const result = detectCompletedPeriods(event);
|
|
506
|
+
|
|
507
|
+
expect(result).not.toBeNull();
|
|
508
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
509
|
+
expect(result?.readyForResolution).toBe(true);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('Should return null for game not started', () => {
|
|
513
|
+
const event = {
|
|
514
|
+
sport: {
|
|
515
|
+
name: 'Soccer',
|
|
516
|
+
},
|
|
517
|
+
fixture: {
|
|
518
|
+
id: 'future-game',
|
|
519
|
+
status: 'scheduled',
|
|
520
|
+
is_live: false,
|
|
521
|
+
},
|
|
522
|
+
scores: {
|
|
523
|
+
home: {
|
|
524
|
+
total: 0.0,
|
|
525
|
+
periods: {},
|
|
526
|
+
},
|
|
527
|
+
away: {
|
|
528
|
+
total: 0.0,
|
|
529
|
+
periods: {},
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
in_play: {
|
|
533
|
+
period: null,
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const result = detectCompletedPeriods(event);
|
|
538
|
+
|
|
539
|
+
expect(result).toBeNull();
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('Should return null for game in first period with no completed periods', () => {
|
|
543
|
+
const event = {
|
|
544
|
+
sport: {
|
|
545
|
+
name: 'Basketball',
|
|
546
|
+
},
|
|
547
|
+
fixture: {
|
|
548
|
+
id: 'early-game',
|
|
549
|
+
status: 'live',
|
|
550
|
+
is_live: true,
|
|
551
|
+
},
|
|
552
|
+
scores: {
|
|
553
|
+
home: {
|
|
554
|
+
total: 12.0,
|
|
555
|
+
periods: {},
|
|
556
|
+
},
|
|
557
|
+
away: {
|
|
558
|
+
total: 10.0,
|
|
559
|
+
periods: {},
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
in_play: {
|
|
563
|
+
period: '1',
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
const result = detectCompletedPeriods(event);
|
|
568
|
+
|
|
569
|
+
expect(result).toBeNull();
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
describe('canResolveMarketsForEvent', () => {
|
|
575
|
+
describe('Single typeId checks', () => {
|
|
576
|
+
it('Should return true for 1st period typeId when period 1 is complete (Soccer 2nd half)', () => {
|
|
577
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10021, SportPeriodType.HALVES_BASED);
|
|
578
|
+
expect(result).toBe(true);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it('Should return false for 2nd period typeId when only period 1 is complete', () => {
|
|
582
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10022, SportPeriodType.HALVES_BASED);
|
|
583
|
+
expect(result).toBe(false);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('Should return false for full game typeId during live game', () => {
|
|
587
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10001, SportPeriodType.HALVES_BASED);
|
|
588
|
+
expect(result).toBe(false);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('Should return true for full game typeId when game is completed', () => {
|
|
592
|
+
const result = canResolveMarketsForEvent(MockSoccerCompletedEvent, 10001, SportPeriodType.HALVES_BASED);
|
|
593
|
+
expect(result).toBe(true);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('Should return true for 1st quarter typeId when quarter 1 complete (NFL)', () => {
|
|
597
|
+
const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, 10021, SportPeriodType.QUARTERS_BASED);
|
|
598
|
+
expect(result).toBe(true);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('Should return true for 2nd quarter typeId when quarters 1-2 complete (NFL)', () => {
|
|
602
|
+
const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, 10022, SportPeriodType.QUARTERS_BASED);
|
|
603
|
+
expect(result).toBe(true);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('Should return false for 3rd quarter typeId during 3rd quarter (NFL)', () => {
|
|
607
|
+
const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, 10023, SportPeriodType.QUARTERS_BASED);
|
|
608
|
+
expect(result).toBe(false);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('Should return true for all quarter typeIds when game is completed (NFL)', () => {
|
|
612
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10021, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
613
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10022, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
614
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10023, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
615
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10024, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('Should return false when no periods are complete', () => {
|
|
619
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveFirstHalf, 10021, SportPeriodType.HALVES_BASED);
|
|
620
|
+
expect(result).toBe(false);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it('Should return false for non-existent typeId', () => {
|
|
624
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 99999, SportPeriodType.HALVES_BASED);
|
|
625
|
+
expect(result).toBe(false);
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
describe('Batch typeIds checks', () => {
|
|
630
|
+
it('Should return only resolvable typeIds for live soccer in 2nd half', () => {
|
|
631
|
+
const typeIds = [10021, 10022, 10031, 10001];
|
|
632
|
+
const result = filterMarketsThatCanBeResolved(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
|
|
633
|
+
|
|
634
|
+
expect(result).toEqual([10021, 10031]); // Only period 1 typeIds
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('Should exclude full game typeIds during live game', () => {
|
|
638
|
+
const typeIds = [10021, 10001, 10002, 10003];
|
|
639
|
+
const result = filterMarketsThatCanBeResolved(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
|
|
640
|
+
|
|
641
|
+
expect(result).toEqual([10021]); // Full game typeIds excluded
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('Should include full game typeIds when game is completed', () => {
|
|
645
|
+
const typeIds = [10021, 10022, 10001, 10002];
|
|
646
|
+
const result = filterMarketsThatCanBeResolved(MockSoccerCompletedEvent, typeIds, SportPeriodType.HALVES_BASED);
|
|
647
|
+
|
|
648
|
+
expect(result).toEqual([10021, 10022, 10001, 10002]);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it('Should return empty array when no typeIds are resolvable', () => {
|
|
652
|
+
const typeIds = [10022, 10023, 10024];
|
|
653
|
+
const result = filterMarketsThatCanBeResolved(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
|
|
654
|
+
|
|
655
|
+
expect(result).toEqual([]);
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it('Should return multiple period typeIds for NFL game in 3rd quarter', () => {
|
|
659
|
+
const typeIds = [10021, 10022, 10023, 10024, 10031, 10032, 10051];
|
|
660
|
+
const result = filterMarketsThatCanBeResolved(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
|
|
661
|
+
|
|
662
|
+
// Periods 1 and 2 are complete (period 2 also completes 1st half = 10051)
|
|
663
|
+
expect(result).toEqual([10021, 10022, 10031, 10032, 10051]);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it('Should handle all 9 periods for completed MLB game', () => {
|
|
667
|
+
const typeIds = [10021, 10022, 10023, 10024, 10025, 10026, 10027, 10028, 10029];
|
|
668
|
+
const result = filterMarketsThatCanBeResolved(MockMLBCompletedEvent, typeIds, SportPeriodType.INNINGS_BASED);
|
|
669
|
+
|
|
670
|
+
expect(result).toEqual(typeIds); // All 9 innings complete
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('Should return empty array when no periods complete', () => {
|
|
674
|
+
const typeIds = [10021, 10022, 10031];
|
|
675
|
+
const result = filterMarketsThatCanBeResolved(MockSoccerLiveFirstHalf, typeIds, SportPeriodType.HALVES_BASED);
|
|
676
|
+
|
|
677
|
+
expect(result).toEqual([]);
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
describe('Multiple typeIds boolean array checks', () => {
|
|
682
|
+
it('Should return boolean array for live soccer in 2nd half', () => {
|
|
683
|
+
const typeIds = [10021, 10022, 10031, 10001];
|
|
684
|
+
const result = canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
|
|
685
|
+
|
|
686
|
+
expect(result).toEqual([true, false, true, false]); // Period 1 typeIds are true, period 2 and full game are false
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it('Should return false for full game typeIds during live game', () => {
|
|
690
|
+
const typeIds = [10021, 10001, 10002, 10003];
|
|
691
|
+
const result = canResolveMultipleTypeIdsForEvent(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
|
|
692
|
+
|
|
693
|
+
expect(result).toEqual([true, false, false, false]); // Only period 1 is true
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it('Should return all true for completed game', () => {
|
|
697
|
+
const typeIds = [10021, 10022, 10001, 10002];
|
|
698
|
+
const result = canResolveMultipleTypeIdsForEvent(MockSoccerCompletedEvent, typeIds, SportPeriodType.HALVES_BASED);
|
|
699
|
+
|
|
700
|
+
expect(result).toEqual([true, true, true, true]); // All complete
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('Should return all false when no typeIds are resolvable', () => {
|
|
704
|
+
const typeIds = [10022, 10023, 10024];
|
|
705
|
+
const result = canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
|
|
706
|
+
|
|
707
|
+
expect(result).toEqual([false, false, false]);
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it('Should return mixed booleans for NFL game in 3rd quarter', () => {
|
|
711
|
+
const typeIds = [10021, 10022, 10023, 10024, 10031, 10032, 10051];
|
|
712
|
+
const result = canResolveMultipleTypeIdsForEvent(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
|
|
713
|
+
|
|
714
|
+
// Periods 1 and 2 are complete (period 2 also completes 1st half = 10051)
|
|
715
|
+
expect(result).toEqual([true, true, false, false, true, true, true]);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
it('Should handle all 9 periods for completed MLB game', () => {
|
|
719
|
+
const typeIds = [10021, 10022, 10023, 10024, 10025, 10026, 10027, 10028, 10029];
|
|
720
|
+
const result = canResolveMultipleTypeIdsForEvent(MockMLBCompletedEvent, typeIds, SportPeriodType.INNINGS_BASED);
|
|
721
|
+
|
|
722
|
+
expect(result).toEqual([true, true, true, true, true, true, true, true, true]); // All 9 innings complete
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it('Should return all false when no periods complete', () => {
|
|
726
|
+
const typeIds = [10021, 10022, 10031];
|
|
727
|
+
const result = canResolveMultipleTypeIdsForEvent(MockSoccerLiveFirstHalf, typeIds, SportPeriodType.HALVES_BASED);
|
|
728
|
+
|
|
729
|
+
expect(result).toEqual([false, false, false]);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('Should work with numeric sport type parameter', () => {
|
|
733
|
+
const typeIds = [10021, 10022];
|
|
734
|
+
const result = canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, 0); // 0 = HALVES_BASED
|
|
735
|
+
|
|
736
|
+
expect(result).toEqual([true, false]);
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
describe('Edge cases', () => {
|
|
741
|
+
it('Should handle event with no completed periods', () => {
|
|
742
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveFirstHalfInProgress, 10021, SportPeriodType.HALVES_BASED);
|
|
743
|
+
expect(result).toBe(false);
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it('Should respect full game typeIds list', () => {
|
|
747
|
+
// Test all full game typeIds
|
|
748
|
+
const fullGameTypeIds = [0, 10001, 10002, 10003, 10004, 10010, 10011, 10012];
|
|
749
|
+
|
|
750
|
+
fullGameTypeIds.forEach((typeId) => {
|
|
751
|
+
const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, typeId, SportPeriodType.QUARTERS_BASED);
|
|
752
|
+
expect(result).toBe(false);
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
it('Should work with sport type parameter for single typeId', () => {
|
|
757
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10021, SportPeriodType.HALVES_BASED);
|
|
758
|
+
expect(result).toBe(true);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it('Should work with sport type parameter for batch typeIds', () => {
|
|
762
|
+
const result = filterMarketsThatCanBeResolved(MockSoccerLiveSecondHalf, [10021, 10022], SportPeriodType.HALVES_BASED);
|
|
763
|
+
expect(result).toEqual([10021]);
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
describe('Real overtime NFL game (Rams vs 49ers)', () => {
|
|
768
|
+
it('Should detect all 5 periods complete including overtime', () => {
|
|
769
|
+
const result = detectCompletedPeriods(MockNFLCompletedWithOvertime);
|
|
770
|
+
|
|
771
|
+
expect(result).not.toBeNull();
|
|
772
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]);
|
|
773
|
+
expect(result?.readyForResolution).toBe(true);
|
|
774
|
+
expect(result?.periodScores['period5']).toEqual({ home: 0.0, away: 3.0 });
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it('Should resolve all quarter typeIds (10021-10024) for completed overtime game', () => {
|
|
778
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10021, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
779
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10022, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
780
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10023, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
781
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10024, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
it('Should resolve overtime period typeId (10025)', () => {
|
|
785
|
+
const result = canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10025, SportPeriodType.QUARTERS_BASED);
|
|
786
|
+
expect(result).toBe(true);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('Should resolve full game typeIds for completed overtime game', () => {
|
|
790
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10001, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
791
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10002, SportPeriodType.QUARTERS_BASED)).toBe(true);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
it('Should return all resolvable typeIds including overtime in batch check', () => {
|
|
795
|
+
const typeIds = [10021, 10022, 10023, 10024, 10025, 10001];
|
|
796
|
+
const result = filterMarketsThatCanBeResolved(MockNFLCompletedWithOvertime, typeIds, SportPeriodType.QUARTERS_BASED);
|
|
797
|
+
|
|
798
|
+
expect(result).toEqual(typeIds); // All should be resolvable (game completed with overtime)
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('Should return false for 8th period typeId (period did not occur)', () => {
|
|
802
|
+
const result = canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10028, SportPeriodType.QUARTERS_BASED);
|
|
803
|
+
expect(result).toBe(false);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it('Should return false for 9th period typeId (period did not occur)', () => {
|
|
807
|
+
const result = canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10029, SportPeriodType.QUARTERS_BASED);
|
|
808
|
+
expect(result).toBe(false);
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('Should not include non-existent periods in batch check', () => {
|
|
812
|
+
const typeIds = [10021, 10022, 10025, 10028, 10029];
|
|
813
|
+
const result = filterMarketsThatCanBeResolved(MockNFLCompletedWithOvertime, typeIds, SportPeriodType.QUARTERS_BASED);
|
|
814
|
+
|
|
815
|
+
// Only periods 1, 2, and 5 occurred, so only their typeIds should be returned
|
|
816
|
+
expect(result).toEqual([10021, 10022, 10025]);
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
describe('Sport-type-specific resolution for typeId 10051 (1st half)', () => {
|
|
821
|
+
it('Soccer (HALVES_BASED): Should resolve typeId 10051 after period 1', () => {
|
|
822
|
+
// Soccer: Period 1 = 1st half
|
|
823
|
+
const result = canResolveMarketsForEvent(
|
|
824
|
+
MockSoccerLiveSecondHalf,
|
|
825
|
+
10051,
|
|
826
|
+
SportPeriodType.HALVES_BASED
|
|
827
|
+
);
|
|
828
|
+
expect(result).toBe(true);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it('NFL (QUARTERS_BASED): Should resolve typeId 10051 after period 2', () => {
|
|
832
|
+
// NFL: Period 2 completes 1st half (quarters 1+2)
|
|
833
|
+
const result = canResolveMarketsForEvent(
|
|
834
|
+
MockNFLLiveThirdQuarter,
|
|
835
|
+
10051,
|
|
836
|
+
SportPeriodType.QUARTERS_BASED
|
|
837
|
+
);
|
|
838
|
+
expect(result).toBe(true);
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
it('NFL (QUARTERS_BASED): Should NOT resolve typeId 10051 after only period 1', () => {
|
|
842
|
+
// Create mock with only period 1 complete
|
|
843
|
+
const mockNFLFirstQuarter = {
|
|
844
|
+
...MockNFLLiveThirdQuarter,
|
|
845
|
+
scores: {
|
|
846
|
+
home: { total: 7.0, periods: { period_1: 7.0 } },
|
|
847
|
+
away: { total: 3.0, periods: { period_1: 3.0 } },
|
|
848
|
+
},
|
|
849
|
+
in_play: { period: '2', clock: '5:00' },
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
const result = canResolveMarketsForEvent(
|
|
853
|
+
mockNFLFirstQuarter,
|
|
854
|
+
10051,
|
|
855
|
+
SportPeriodType.QUARTERS_BASED
|
|
856
|
+
);
|
|
857
|
+
expect(result).toBe(false);
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
it('MLB (INNINGS_BASED): Should resolve typeId 10051 after period 5', () => {
|
|
861
|
+
// MLB: Period 5 completes 1st half (innings 1-5)
|
|
862
|
+
const result = canResolveMarketsForEvent(
|
|
863
|
+
MockMLBLiveSixthInning,
|
|
864
|
+
10051,
|
|
865
|
+
SportPeriodType.INNINGS_BASED
|
|
866
|
+
);
|
|
867
|
+
expect(result).toBe(true);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it('MLB (INNINGS_BASED): Should NOT resolve typeId 10051 after only period 4', () => {
|
|
871
|
+
// Create mock with only period 1-4 complete
|
|
872
|
+
const mockMLBFourthInning = {
|
|
873
|
+
...MockMLBLiveSixthInning,
|
|
874
|
+
scores: {
|
|
875
|
+
home: {
|
|
876
|
+
total: 3.0,
|
|
877
|
+
periods: {
|
|
878
|
+
period_1: 0.0,
|
|
879
|
+
period_2: 2.0,
|
|
880
|
+
period_3: 1.0,
|
|
881
|
+
period_4: 0.0,
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
away: {
|
|
885
|
+
total: 1.0,
|
|
886
|
+
periods: {
|
|
887
|
+
period_1: 1.0,
|
|
888
|
+
period_2: 0.0,
|
|
889
|
+
period_3: 0.0,
|
|
890
|
+
period_4: 0.0,
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
in_play: { period: '5', clock: null },
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
const result = canResolveMarketsForEvent(
|
|
898
|
+
mockMLBFourthInning,
|
|
899
|
+
10051,
|
|
900
|
+
SportPeriodType.INNINGS_BASED
|
|
901
|
+
);
|
|
902
|
+
expect(result).toBe(false);
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
describe('Sport-type-specific resolution for typeId 10052 (2nd half)', () => {
|
|
907
|
+
it('Soccer (HALVES_BASED): Should resolve typeId 10052 after period 2', () => {
|
|
908
|
+
const result = canResolveMarketsForEvent(
|
|
909
|
+
MockSoccerCompletedEvent,
|
|
910
|
+
10052,
|
|
911
|
+
SportPeriodType.HALVES_BASED
|
|
912
|
+
);
|
|
913
|
+
expect(result).toBe(true);
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it('NFL (QUARTERS_BASED): Should resolve typeId 10052 after period 4', () => {
|
|
917
|
+
const result = canResolveMarketsForEvent(
|
|
918
|
+
MockNFLCompletedEvent,
|
|
919
|
+
10052,
|
|
920
|
+
SportPeriodType.QUARTERS_BASED
|
|
921
|
+
);
|
|
922
|
+
expect(result).toBe(true);
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it('MLB (INNINGS_BASED): Should resolve typeId 10052 after period 9', () => {
|
|
926
|
+
const result = canResolveMarketsForEvent(
|
|
927
|
+
MockMLBCompletedEvent,
|
|
928
|
+
10052,
|
|
929
|
+
SportPeriodType.INNINGS_BASED
|
|
930
|
+
);
|
|
931
|
+
expect(result).toBe(true);
|
|
932
|
+
});
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
});
|