overtime-live-trading-utils 2.1.22 → 2.1.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overtime-live-trading-utils",
3
- "version": "2.1.22",
3
+ "version": "2.1.24",
4
4
  "description": "",
5
5
  "main": "main.js",
6
6
  "scripts": {
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  detectCompletedPeriods,
3
- canResolveMarketForGameIdAndSport,
4
- canResolveMarketViaOpticOddsApi,
5
3
  canResolveMarketsForEvent,
4
+ canResolveMultipleTypeIdsForEvent,
5
+ filterMarketsThatCanBeResolved,
6
6
  } from '../../utils/resolution';
7
7
  import { SportPeriodType } from '../../types/resolution';
8
8
  import {
@@ -570,173 +570,6 @@ describe('Resolution Utils', () => {
570
570
  });
571
571
  });
572
572
 
573
- describe('canResolveMarketForGameIdAndSport', () => {
574
- it('Should return true when game can be resolved', () => {
575
- const event = {
576
- sport: {
577
- name: 'Soccer',
578
- },
579
- fixture: {
580
- id: 'match-123',
581
- status: 'live',
582
- is_live: true,
583
- },
584
- scores: {
585
- home: {
586
- total: 2.0,
587
- periods: {
588
- period_1: 1.0,
589
- },
590
- },
591
- away: {
592
- total: 1.0,
593
- periods: {
594
- period_1: 1.0,
595
- },
596
- },
597
- },
598
- in_play: {
599
- period: '2',
600
- },
601
- };
602
-
603
- const result = canResolveMarketForGameIdAndSport('match-123', event);
604
-
605
- expect(result).toBe(true);
606
- });
607
-
608
- it('Should return false when gameId does not match', () => {
609
- const event = {
610
- sport: {
611
- name: 'Soccer',
612
- },
613
- fixture: {
614
- id: 'match-456',
615
- status: 'live',
616
- is_live: true,
617
- },
618
- scores: {
619
- home: {
620
- total: 1.0,
621
- periods: {
622
- period_1: 1.0,
623
- },
624
- },
625
- away: {
626
- total: 0.0,
627
- periods: {
628
- period_1: 0.0,
629
- },
630
- },
631
- },
632
- in_play: {
633
- period: '2',
634
- },
635
- };
636
-
637
- const result = canResolveMarketForGameIdAndSport('match-999', event);
638
-
639
- expect(result).toBe(false);
640
- });
641
-
642
- it('Should return false when no periods are completed', () => {
643
- const event = {
644
- sport: {
645
- name: 'Basketball',
646
- },
647
- fixture: {
648
- id: 'match-789',
649
- status: 'live',
650
- is_live: true,
651
- },
652
- scores: {
653
- home: {
654
- total: 15.0,
655
- periods: {},
656
- },
657
- away: {
658
- total: 12.0,
659
- periods: {},
660
- },
661
- },
662
- in_play: {
663
- period: '1',
664
- },
665
- };
666
-
667
- const result = canResolveMarketForGameIdAndSport('match-789', event);
668
-
669
- expect(result).toBe(false);
670
- });
671
- });
672
-
673
- describe('canResolveMarketViaOpticOddsApi', () => {
674
- it('Should return period resolution data when available', () => {
675
- const event = {
676
- sport: {
677
- name: 'Tennis',
678
- },
679
- fixture: {
680
- id: 'api-test-1',
681
- status: 'live',
682
- is_live: true,
683
- },
684
- scores: {
685
- home: {
686
- total: 1.0,
687
- periods: {
688
- period_1: 7.0,
689
- },
690
- },
691
- away: {
692
- total: 0.0,
693
- periods: {
694
- period_1: 5.0,
695
- },
696
- },
697
- },
698
- in_play: {
699
- period: '2',
700
- },
701
- };
702
-
703
- const result = canResolveMarketViaOpticOddsApi(event);
704
-
705
- expect(result).not.toBeNull();
706
- expect(result?.completedPeriods).toEqual([1]);
707
- expect(result?.readyForResolution).toBe(true);
708
- });
709
-
710
- it('Should return null when no periods completed', () => {
711
- const event = {
712
- sport: {
713
- name: 'Soccer',
714
- },
715
- fixture: {
716
- id: 'api-test-2',
717
- status: 'live',
718
- is_live: true,
719
- },
720
- scores: {
721
- home: {
722
- total: 0.0,
723
- periods: {},
724
- },
725
- away: {
726
- total: 0.0,
727
- periods: {},
728
- },
729
- },
730
- in_play: {
731
- period: '1',
732
- },
733
- };
734
-
735
- const result = canResolveMarketViaOpticOddsApi(event);
736
-
737
- expect(result).toBeNull();
738
- });
739
- });
740
573
 
741
574
  describe('canResolveMarketsForEvent', () => {
742
575
  describe('Single typeId checks', () => {
@@ -796,35 +629,35 @@ describe('Resolution Utils', () => {
796
629
  describe('Batch typeIds checks', () => {
797
630
  it('Should return only resolvable typeIds for live soccer in 2nd half', () => {
798
631
  const typeIds = [10021, 10022, 10031, 10001];
799
- const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
632
+ const result = filterMarketsThatCanBeResolved(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
800
633
 
801
634
  expect(result).toEqual([10021, 10031]); // Only period 1 typeIds
802
635
  });
803
636
 
804
637
  it('Should exclude full game typeIds during live game', () => {
805
638
  const typeIds = [10021, 10001, 10002, 10003];
806
- const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
639
+ const result = filterMarketsThatCanBeResolved(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
807
640
 
808
641
  expect(result).toEqual([10021]); // Full game typeIds excluded
809
642
  });
810
643
 
811
644
  it('Should include full game typeIds when game is completed', () => {
812
645
  const typeIds = [10021, 10022, 10001, 10002];
813
- const result = canResolveMarketsForEvent(MockSoccerCompletedEvent, typeIds, SportPeriodType.HALVES_BASED);
646
+ const result = filterMarketsThatCanBeResolved(MockSoccerCompletedEvent, typeIds, SportPeriodType.HALVES_BASED);
814
647
 
815
648
  expect(result).toEqual([10021, 10022, 10001, 10002]);
816
649
  });
817
650
 
818
651
  it('Should return empty array when no typeIds are resolvable', () => {
819
652
  const typeIds = [10022, 10023, 10024];
820
- const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
653
+ const result = filterMarketsThatCanBeResolved(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
821
654
 
822
655
  expect(result).toEqual([]);
823
656
  });
824
657
 
825
658
  it('Should return multiple period typeIds for NFL game in 3rd quarter', () => {
826
659
  const typeIds = [10021, 10022, 10023, 10024, 10031, 10032, 10051];
827
- const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
660
+ const result = filterMarketsThatCanBeResolved(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
828
661
 
829
662
  // Periods 1 and 2 are complete (period 2 also completes 1st half = 10051)
830
663
  expect(result).toEqual([10021, 10022, 10031, 10032, 10051]);
@@ -832,19 +665,78 @@ describe('Resolution Utils', () => {
832
665
 
833
666
  it('Should handle all 9 periods for completed MLB game', () => {
834
667
  const typeIds = [10021, 10022, 10023, 10024, 10025, 10026, 10027, 10028, 10029];
835
- const result = canResolveMarketsForEvent(MockMLBCompletedEvent, typeIds, SportPeriodType.INNINGS_BASED);
668
+ const result = filterMarketsThatCanBeResolved(MockMLBCompletedEvent, typeIds, SportPeriodType.INNINGS_BASED);
836
669
 
837
670
  expect(result).toEqual(typeIds); // All 9 innings complete
838
671
  });
839
672
 
840
673
  it('Should return empty array when no periods complete', () => {
841
674
  const typeIds = [10021, 10022, 10031];
842
- const result = canResolveMarketsForEvent(MockSoccerLiveFirstHalf, typeIds, SportPeriodType.HALVES_BASED);
675
+ const result = filterMarketsThatCanBeResolved(MockSoccerLiveFirstHalf, typeIds, SportPeriodType.HALVES_BASED);
843
676
 
844
677
  expect(result).toEqual([]);
845
678
  });
846
679
  });
847
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
+
848
740
  describe('Edge cases', () => {
849
741
  it('Should handle event with no completed periods', () => {
850
742
  const result = canResolveMarketsForEvent(MockSoccerLiveFirstHalfInProgress, 10021, SportPeriodType.HALVES_BASED);
@@ -867,7 +759,7 @@ describe('Resolution Utils', () => {
867
759
  });
868
760
 
869
761
  it('Should work with sport type parameter for batch typeIds', () => {
870
- const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, [10021, 10022], SportPeriodType.HALVES_BASED);
762
+ const result = filterMarketsThatCanBeResolved(MockSoccerLiveSecondHalf, [10021, 10022], SportPeriodType.HALVES_BASED);
871
763
  expect(result).toEqual([10021]);
872
764
  });
873
765
  });
@@ -901,7 +793,7 @@ describe('Resolution Utils', () => {
901
793
 
902
794
  it('Should return all resolvable typeIds including overtime in batch check', () => {
903
795
  const typeIds = [10021, 10022, 10023, 10024, 10025, 10001];
904
- const result = canResolveMarketsForEvent(MockNFLCompletedWithOvertime, typeIds, SportPeriodType.QUARTERS_BASED);
796
+ const result = filterMarketsThatCanBeResolved(MockNFLCompletedWithOvertime, typeIds, SportPeriodType.QUARTERS_BASED);
905
797
 
906
798
  expect(result).toEqual(typeIds); // All should be resolvable (game completed with overtime)
907
799
  });
@@ -918,7 +810,7 @@ describe('Resolution Utils', () => {
918
810
 
919
811
  it('Should not include non-existent periods in batch check', () => {
920
812
  const typeIds = [10021, 10022, 10025, 10028, 10029];
921
- const result = canResolveMarketsForEvent(MockNFLCompletedWithOvertime, typeIds, SportPeriodType.QUARTERS_BASED);
813
+ const result = filterMarketsThatCanBeResolved(MockNFLCompletedWithOvertime, typeIds, SportPeriodType.QUARTERS_BASED);
922
814
 
923
815
  // Only periods 1, 2, and 5 occurred, so only their typeIds should be returned
924
816
  expect(result).toEqual([10021, 10022, 10025]);
@@ -92,37 +92,6 @@ export const detectCompletedPeriods = (
92
92
  }
93
93
  : null;
94
94
  };
95
-
96
- /**
97
- * Checks if a market can be resolved based on game and sport
98
- * @param gameId - The game/fixture ID
99
- * @param event - Event object from OpticOdds API
100
- * @returns boolean indicating if market can be resolved
101
- */
102
- export const canResolveMarketForGameIdAndSport = (
103
- gameId: string,
104
- event: OpticOddsEvent
105
- ): boolean => {
106
- const eventId = event.fixture?.id || event.id || '';
107
- if (eventId !== gameId) {
108
- return false;
109
- }
110
-
111
- const periodData = detectCompletedPeriods(event);
112
- return periodData !== null && periodData.readyForResolution;
113
- };
114
-
115
- /**
116
- * Convenience function to check resolution status via OpticOdds API event
117
- * @param event - Event object from OpticOdds API
118
- * @returns PeriodResolutionData if periods are complete, null otherwise
119
- */
120
- export const canResolveMarketViaOpticOddsApi = (
121
- event: OpticOddsEvent
122
- ): PeriodResolutionData | null => {
123
- return detectCompletedPeriods(event);
124
- };
125
-
126
95
  /**
127
96
  * Maps a numeric value to a SportPeriodType enum
128
97
  * @param sportTypeNum - Numeric representation of sport type (0 = halves, 1 = quarters, 2 = innings, 3 = period)
@@ -163,32 +132,87 @@ function selectMappingForSportType(sportType: SportPeriodType): { [period: numbe
163
132
  }
164
133
 
165
134
  /**
166
- * Implementation - checks if specific market type(s) can be resolved based on completed periods
135
+ * Checks if a specific market type can be resolved based on completed periods (Enum version)
167
136
  *
168
137
  * @example
169
138
  * // Check single typeId for NFL (quarters-based)
170
139
  * const canResolve = canResolveMarketsForEvent(event, 10021, SportPeriodType.QUARTERS_BASED);
171
- * // Or using number
172
- * const canResolve = canResolveMarketsForEvent(event, 10021, 1);
173
- *
174
- * // Check batch of typeIds for MLB (innings-based)
175
- * const resolvable = canResolveMarketsForEvent(event, [10021, 10051], SportPeriodType.INNINGS_BASED);
176
- * // Returns: [10021] if only period 1-4 complete, [10021, 10051] if period 5 complete
177
140
  *
178
141
  * // Check with period-based (no halves/secondary moneyline)
179
142
  * const canResolve = canResolveMarketsForEvent(event, 10021, SportPeriodType.PERIOD_BASED);
180
- * // Or using number
181
- * const canResolve = canResolveMarketsForEvent(event, 10021, 3);
143
+ *
144
+ * @param event - OpticOdds event object containing fixture data
145
+ * @param typeId - The market type identifier
146
+ * @param sportType - The sport period type enum
147
+ * @returns true if the market can be resolved based on completed periods
182
148
  */
183
149
  export function canResolveMarketsForEvent(
184
150
  event: OpticOddsEvent,
185
- typeIdOrTypeIds: number | number[],
151
+ typeId: number,
152
+ sportType: SportPeriodType
153
+ ): boolean {
154
+ const periodData = detectCompletedPeriods(event);
155
+ if (!periodData) return false;
156
+
157
+ const status = (event.fixture?.status || event.status || '').toLowerCase();
158
+ const isCompleted = status === 'completed' || status === 'complete' || status === 'finished';
159
+
160
+ // Full game typeIds can only be resolved when game is completed
161
+ if (FULL_GAME_TYPE_IDS.includes(typeId)) {
162
+ return isCompleted;
163
+ }
164
+
165
+ // Select appropriate mapping based on sport type
166
+ const mapping = selectMappingForSportType(sportType);
167
+
168
+ const resolvableTypeIds = new Set<number>();
169
+ for (const period of periodData.completedPeriods) {
170
+ const typeIdsForPeriod = mapping[period] || [];
171
+ typeIdsForPeriod.forEach((id) => resolvableTypeIds.add(id));
172
+ }
173
+
174
+ return resolvableTypeIds.has(typeId);
175
+ }
176
+
177
+ /**
178
+ * Checks if a specific market type can be resolved based on completed periods (Number version)
179
+ *
180
+ * @example
181
+ * // Check single typeId for NFL (quarters-based)
182
+ * const canResolve = canResolveMarketsForEventNumber(event, 10021, 1);
183
+ *
184
+ * @param event - OpticOdds event object containing fixture data
185
+ * @param typeId - The market type identifier
186
+ * @param sportTypeNumber - Numeric value representing the sport period type
187
+ * @returns true if the market can be resolved based on completed periods
188
+ */
189
+ export function canResolveMarketsForEventNumber(
190
+ event: OpticOddsEvent,
191
+ typeId: number,
192
+ sportTypeNumber: number
193
+ ): boolean {
194
+ const sportTypeEnum = mapNumberToSportPeriodType(sportTypeNumber);
195
+ return canResolveMarketsForEvent(event, typeId, sportTypeEnum);
196
+ }
197
+
198
+
199
+ /**
200
+ * Checks if multiple market types can be resolved, returning a boolean for each
201
+ *
202
+ * @example
203
+ * // Check multiple typeIds for MLB (innings-based)
204
+ * const canResolve = canResolveMultipleTypeIdsForEvent(event, [10021, 10051], SportPeriodType.INNINGS_BASED);
205
+ * // Returns: [true, false] if only 10021 can be resolved
206
+ */
207
+ export function canResolveMultipleTypeIdsForEvent(
208
+ event: OpticOddsEvent,
209
+ typeIds: number[],
186
210
  sportType: SportPeriodType | number
187
- ): boolean | number[] {
211
+ ): boolean[] {
188
212
  // Get completed periods
189
213
  const periodData = detectCompletedPeriods(event);
190
214
  if (!periodData) {
191
- return Array.isArray(typeIdOrTypeIds) ? [] : false;
215
+ return typeIds.map(() => false);
192
216
  }
193
217
 
194
218
  // Check if game is fully completed
@@ -209,21 +233,59 @@ export function canResolveMarketsForEvent(
209
233
  typeIdsForPeriod.forEach((id) => resolvableTypeIds.add(id));
210
234
  }
211
235
 
212
- // Single typeId check
213
- if (typeof typeIdOrTypeIds === 'number') {
236
+ // Map each typeId to a boolean
237
+ return typeIds.map((id) => {
214
238
  // Full game typeIds can only be resolved when game is completed
215
- if (FULL_GAME_TYPE_IDS.includes(typeIdOrTypeIds)) {
239
+ if (FULL_GAME_TYPE_IDS.includes(id)) {
216
240
  return isCompleted;
217
241
  }
218
- return resolvableTypeIds.has(typeIdOrTypeIds);
242
+ return resolvableTypeIds.has(id);
243
+ });
244
+ }
245
+
246
+ /**
247
+ * Filters a list of market types to only those that can be resolved
248
+ *
249
+ * @example
250
+ * // Filter typeIds for MLB (innings-based)
251
+ * const resolvable = filterMarketsThatCanBeResolved(event, [10021, 10051, 10061], SportPeriodType.INNINGS_BASED);
252
+ * // Returns: [10021] if only period 1-4 complete, [10021, 10051] if period 5 complete
253
+ */
254
+ export function filterMarketsThatCanBeResolved(
255
+ event: OpticOddsEvent,
256
+ typeIds: number[],
257
+ sportType: SportPeriodType | number
258
+ ): number[] {
259
+ // Get completed periods
260
+ const periodData = detectCompletedPeriods(event);
261
+ if (!periodData) {
262
+ return [];
219
263
  }
220
264
 
221
- // Batch typeIds check
222
- return typeIdOrTypeIds.filter((id) => {
265
+ // Check if game is fully completed
266
+ const status = (event.fixture?.status || event.status || '').toLowerCase();
267
+ const isCompleted = status === 'completed' || status === 'complete' || status === 'finished';
268
+
269
+ // Convert number to SportPeriodType if needed
270
+ const sportTypeEnum = typeof sportType === 'number' ? mapNumberToSportPeriodType(sportType) : sportType;
271
+
272
+ // Select appropriate mapping based on sport type
273
+ const mapping = selectMappingForSportType(sportTypeEnum);
274
+
275
+ // Collect all resolvable typeIds based on completed periods
276
+ const resolvableTypeIds = new Set<number>();
277
+
278
+ for (const period of periodData.completedPeriods) {
279
+ const typeIdsForPeriod = mapping[period] || [];
280
+ typeIdsForPeriod.forEach((id) => resolvableTypeIds.add(id));
281
+ }
282
+
283
+ // Filter typeIds to only those that can be resolved
284
+ return typeIds.filter((id) => {
223
285
  // Full game typeIds can only be resolved when game is completed
224
286
  if (FULL_GAME_TYPE_IDS.includes(id)) {
225
287
  return isCompleted;
226
288
  }
227
289
  return resolvableTypeIds.has(id);
228
290
  });
229
- };
291
+ }