overtime-live-trading-utils 2.1.30 → 2.1.32

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.
Files changed (38) hide show
  1. package/.circleci/config.yml +32 -32
  2. package/.prettierrc +9 -9
  3. package/codecov.yml +20 -20
  4. package/index.ts +26 -26
  5. package/jest.config.ts +16 -16
  6. package/main.js +1 -1
  7. package/package.json +30 -30
  8. package/src/constants/common.ts +7 -7
  9. package/src/constants/errors.ts +6 -6
  10. package/src/constants/sports.ts +78 -78
  11. package/src/enums/sports.ts +109 -109
  12. package/src/tests/mock/MockLeagueMap.ts +170 -170
  13. package/src/tests/mock/MockOpticOddsEvents.ts +518 -518
  14. package/src/tests/mock/MockOpticSoccer.ts +9378 -9378
  15. package/src/tests/mock/MockSoccerRedis.ts +2308 -2308
  16. package/src/tests/unit/bookmakers.test.ts +79 -79
  17. package/src/tests/unit/markets.test.ts +156 -156
  18. package/src/tests/unit/odds.test.ts +92 -92
  19. package/src/tests/unit/resolution.test.ts +935 -935
  20. package/src/tests/unit/sports.test.ts +58 -58
  21. package/src/tests/unit/spread.test.ts +131 -131
  22. package/src/types/missing-types.d.ts +2 -2
  23. package/src/types/odds.ts +61 -61
  24. package/src/types/resolution.ts +96 -96
  25. package/src/types/sports.ts +19 -19
  26. package/src/utils/bookmakers.ts +159 -159
  27. package/src/utils/constraints.ts +210 -210
  28. package/src/utils/gameMatching.ts +81 -81
  29. package/src/utils/markets.ts +119 -119
  30. package/src/utils/odds.ts +918 -912
  31. package/src/utils/opticOdds.ts +71 -71
  32. package/src/utils/resolution.ts +291 -291
  33. package/src/utils/sports.ts +51 -51
  34. package/src/utils/spread.ts +97 -97
  35. package/tsconfig.json +16 -16
  36. package/webpack.config.js +24 -30
  37. package/CLAUDE.md +0 -77
  38. package/resolution_live_markets.md +0 -351
@@ -1,935 +1,935 @@
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
- });
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
+ });