overtime-live-trading-utils 2.1.34 → 2.1.36

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.
@@ -15,6 +15,8 @@ import {
15
15
  MockMLBLiveSixthInning,
16
16
  MockSoccerLiveFirstHalfInProgress,
17
17
  MockNFLCompletedWithOvertime,
18
+ MockNBACompletedEvent,
19
+ MockNBALiveAtHalftime,
18
20
  } from '../mock/MockOpticOddsEvents';
19
21
 
20
22
  describe('Resolution Utils', () => {
@@ -95,6 +97,29 @@ describe('Resolution Utils', () => {
95
97
  expect(result?.currentPeriod).toBe(6);
96
98
  });
97
99
 
100
+ it('Should detect completed periods for real completed NBA game (Warriors vs Suns)', () => {
101
+ const result = detectCompletedPeriods(MockNBACompletedEvent);
102
+
103
+ expect(result).not.toBeNull();
104
+ expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
105
+ expect(result?.readyForResolution).toBe(true);
106
+ expect(result?.periodScores['period1']).toEqual({ home: 33.0, away: 19.0 });
107
+ expect(result?.periodScores['period2']).toEqual({ home: 35.0, away: 30.0 });
108
+ expect(result?.periodScores['period3']).toEqual({ home: 24.0, away: 34.0 });
109
+ expect(result?.periodScores['period4']).toEqual({ home: 26.0, away: 24.0 });
110
+ });
111
+
112
+ it('Should detect completed quarters at halftime for real NBA game (Warriors vs Suns)', () => {
113
+ const result = detectCompletedPeriods(MockNBALiveAtHalftime);
114
+
115
+ expect(result).not.toBeNull();
116
+ expect(result?.completedPeriods).toEqual([1, 2]); // Both quarters complete at halftime
117
+ expect(result?.readyForResolution).toBe(true);
118
+ expect(result?.periodScores['period1']).toEqual({ home: 33.0, away: 19.0 });
119
+ expect(result?.periodScores['period2']).toEqual({ home: 35.0, away: 30.0 });
120
+ expect(result?.currentPeriod).toBe(2); // Highest period with data at halftime
121
+ });
122
+
98
123
  it('Should return null for real live soccer game with non-numeric period indicator (1H)', () => {
99
124
  const result = detectCompletedPeriods(MockSoccerLiveFirstHalfInProgress);
100
125
 
@@ -626,8 +651,367 @@ describe('Resolution Utils', () => {
626
651
  expect(result?.currentPeriod).toBe(4);
627
652
  // Period 4 is currently in_play, so NOT complete yet
628
653
  });
629
- });
630
654
 
655
+ it('Basketball at halftime should mark periods 1 AND 2 as complete (quarters-based)', () => {
656
+ const event = {
657
+ sport: {
658
+ id: 'basketball',
659
+ name: 'Basketball',
660
+ },
661
+ fixture: {
662
+ id: 'nba-halftime-123',
663
+ status: 'half',
664
+ is_live: true,
665
+ },
666
+ scores: {
667
+ home: {
668
+ total: 52.0,
669
+ periods: {
670
+ period_1: 25.0,
671
+ period_2: 27.0,
672
+ },
673
+ },
674
+ away: {
675
+ total: 48.0,
676
+ periods: {
677
+ period_1: 22.0,
678
+ period_2: 26.0,
679
+ },
680
+ },
681
+ },
682
+ in_play: {
683
+ period: 'half',
684
+ },
685
+ };
686
+
687
+ const result = detectCompletedPeriods(event);
688
+
689
+ expect(result).not.toBeNull();
690
+ expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
691
+ expect(result?.readyForResolution).toBe(true);
692
+ expect(result?.periodScores['period1']).toEqual({ home: 25.0, away: 22.0 });
693
+ expect(result?.periodScores['period2']).toEqual({ home: 27.0, away: 26.0 });
694
+ });
695
+
696
+ it('Basketball at halftime with status "halftime" should mark periods 1 AND 2 as complete', () => {
697
+ const event = {
698
+ sport: {
699
+ id: 'basketball',
700
+ name: 'Basketball',
701
+ },
702
+ fixture: {
703
+ id: 'nba-halftime-456',
704
+ status: 'halftime',
705
+ is_live: true,
706
+ },
707
+ scores: {
708
+ home: {
709
+ total: 55.0,
710
+ periods: {
711
+ period_1: 28.0,
712
+ period_2: 27.0,
713
+ },
714
+ },
715
+ away: {
716
+ total: 50.0,
717
+ periods: {
718
+ period_1: 24.0,
719
+ period_2: 26.0,
720
+ },
721
+ },
722
+ },
723
+ in_play: {
724
+ period: '2',
725
+ },
726
+ };
727
+
728
+ const result = detectCompletedPeriods(event);
729
+
730
+ expect(result).not.toBeNull();
731
+ expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
732
+ expect(result?.readyForResolution).toBe(true);
733
+ });
734
+
735
+ it('Football at halftime should mark periods 1 AND 2 as complete (quarters-based)', () => {
736
+ const event = {
737
+ sport: {
738
+ id: 'football',
739
+ name: 'Football',
740
+ },
741
+ fixture: {
742
+ id: 'nfl-halftime-789',
743
+ status: 'half',
744
+ is_live: true,
745
+ },
746
+ scores: {
747
+ home: {
748
+ total: 21.0,
749
+ periods: {
750
+ period_1: 7.0,
751
+ period_2: 14.0,
752
+ },
753
+ },
754
+ away: {
755
+ total: 10.0,
756
+ periods: {
757
+ period_1: 3.0,
758
+ period_2: 7.0,
759
+ },
760
+ },
761
+ },
762
+ in_play: {
763
+ period: 'half',
764
+ },
765
+ };
766
+
767
+ const result = detectCompletedPeriods(event);
768
+
769
+ expect(result).not.toBeNull();
770
+ expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
771
+ expect(result?.readyForResolution).toBe(true);
772
+ expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
773
+ expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
774
+ });
775
+
776
+ it('Football at halftime with status "halftime" should mark periods 1 AND 2 as complete', () => {
777
+ const event = {
778
+ sport: {
779
+ id: 'football',
780
+ name: 'American Football',
781
+ },
782
+ fixture: {
783
+ id: 'nfl-halftime-890',
784
+ status: 'halftime',
785
+ is_live: true,
786
+ },
787
+ scores: {
788
+ home: {
789
+ total: 17.0,
790
+ periods: {
791
+ period_1: 10.0,
792
+ period_2: 7.0,
793
+ },
794
+ },
795
+ away: {
796
+ total: 14.0,
797
+ periods: {
798
+ period_1: 7.0,
799
+ period_2: 7.0,
800
+ },
801
+ },
802
+ },
803
+ in_play: {
804
+ period: '2',
805
+ },
806
+ };
807
+
808
+ const result = detectCompletedPeriods(event);
809
+
810
+ expect(result).not.toBeNull();
811
+ expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
812
+ expect(result?.readyForResolution).toBe(true);
813
+ });
814
+
815
+ it('Baseball at halftime should mark periods 1-5 as complete (innings-based)', () => {
816
+ const event = {
817
+ sport: {
818
+ id: 'baseball',
819
+ name: 'Baseball',
820
+ },
821
+ fixture: {
822
+ id: 'mlb-halftime-789',
823
+ status: 'half',
824
+ is_live: true,
825
+ },
826
+ scores: {
827
+ home: {
828
+ total: 3.0,
829
+ periods: {
830
+ period_1: 0.0,
831
+ period_2: 1.0,
832
+ period_3: 1.0,
833
+ period_4: 0.0,
834
+ period_5: 1.0,
835
+ },
836
+ },
837
+ away: {
838
+ total: 2.0,
839
+ periods: {
840
+ period_1: 1.0,
841
+ period_2: 0.0,
842
+ period_3: 0.0,
843
+ period_4: 1.0,
844
+ period_5: 0.0,
845
+ },
846
+ },
847
+ },
848
+ in_play: {
849
+ period: 'half',
850
+ },
851
+ };
852
+
853
+ const result = detectCompletedPeriods(event);
854
+
855
+ expect(result).not.toBeNull();
856
+ expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]); // First 5 innings complete at halftime
857
+ expect(result?.readyForResolution).toBe(true);
858
+ expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 1.0 });
859
+ expect(result?.periodScores['period5']).toEqual({ home: 1.0, away: 0.0 });
860
+ });
861
+
862
+ it('Soccer at halftime should mark only period 1 as complete (halves-based)', () => {
863
+ const event = {
864
+ sport: {
865
+ id: 'soccer',
866
+ name: 'Soccer',
867
+ },
868
+ fixture: {
869
+ id: 'soccer-halftime-101',
870
+ status: 'halftime',
871
+ is_live: true,
872
+ },
873
+ scores: {
874
+ home: {
875
+ total: 2.0,
876
+ periods: {
877
+ period_1: 2.0,
878
+ },
879
+ },
880
+ away: {
881
+ total: 1.0,
882
+ periods: {
883
+ period_1: 1.0,
884
+ },
885
+ },
886
+ },
887
+ in_play: {
888
+ period: 'half',
889
+ },
890
+ };
891
+
892
+ const result = detectCompletedPeriods(event);
893
+
894
+ expect(result).not.toBeNull();
895
+ expect(result?.completedPeriods).toEqual([1]); // Only first half complete at halftime
896
+ expect(result?.readyForResolution).toBe(true);
897
+ expect(result?.periodScores['period1']).toEqual({ home: 2.0, away: 1.0 });
898
+ });
899
+
900
+ it('Hockey at halftime should NOT mark any periods as complete (period-based, no halftime concept)', () => {
901
+ const event = {
902
+ sport: {
903
+ id: 'hockey',
904
+ name: 'Hockey',
905
+ },
906
+ fixture: {
907
+ id: 'nhl-halftime-202',
908
+ status: 'half',
909
+ is_live: true,
910
+ },
911
+ scores: {
912
+ home: {
913
+ total: 2.0,
914
+ periods: {
915
+ period_1: 1.0,
916
+ period_2: 1.0,
917
+ },
918
+ },
919
+ away: {
920
+ total: 1.0,
921
+ periods: {
922
+ period_1: 0.0,
923
+ period_2: 1.0,
924
+ },
925
+ },
926
+ },
927
+ in_play: {
928
+ period: 'half',
929
+ },
930
+ };
931
+
932
+ const result = detectCompletedPeriods(event);
933
+
934
+ // Hockey doesn't have traditional halftime, so halftime status shouldn't mark periods complete
935
+ // Periods should only be marked complete based on in_play.period progression
936
+ expect(result).toBeNull();
937
+ });
938
+
939
+ it('Unknown sport at halftime should default to PERIOD_BASED (no halftime processing)', () => {
940
+ const event = {
941
+ sport: {
942
+ id: 'unknown_sport',
943
+ name: 'Unknown Sport',
944
+ },
945
+ fixture: {
946
+ id: 'unknown-halftime-123',
947
+ status: 'half',
948
+ is_live: true,
949
+ },
950
+ scores: {
951
+ home: {
952
+ total: 50.0,
953
+ periods: {
954
+ period_1: 25.0,
955
+ period_2: 25.0,
956
+ },
957
+ },
958
+ away: {
959
+ total: 40.0,
960
+ periods: {
961
+ period_1: 20.0,
962
+ period_2: 20.0,
963
+ },
964
+ },
965
+ },
966
+ in_play: {
967
+ period: 'half',
968
+ },
969
+ };
970
+
971
+ const result = detectCompletedPeriods(event);
972
+
973
+ // Unknown sports default to PERIOD_BASED (no halftime processing)
974
+ // So halftime status should NOT mark any periods as complete
975
+ expect(result).toBeNull();
976
+ });
977
+
978
+ it('Event with missing sport.id should default to PERIOD_BASED (no halftime processing)', () => {
979
+ const event = {
980
+ sport: {
981
+ name: 'Some Sport',
982
+ },
983
+ fixture: {
984
+ id: 'no-sport-id-123',
985
+ status: 'half',
986
+ is_live: true,
987
+ },
988
+ scores: {
989
+ home: {
990
+ total: 50.0,
991
+ periods: {
992
+ period_1: 25.0,
993
+ period_2: 25.0,
994
+ },
995
+ },
996
+ away: {
997
+ total: 40.0,
998
+ periods: {
999
+ period_1: 20.0,
1000
+ period_2: 20.0,
1001
+ },
1002
+ },
1003
+ },
1004
+ in_play: {
1005
+ period: 'half',
1006
+ },
1007
+ };
1008
+
1009
+ const result = detectCompletedPeriods(event);
1010
+
1011
+ // Missing sport.id defaults to PERIOD_BASED (no halftime processing)
1012
+ expect(result).toBeNull();
1013
+ });
1014
+ });
631
1015
 
632
1016
  describe('canResolveMarketsForEvent', () => {
633
1017
  describe('Single typeId checks', () => {
@@ -652,25 +1036,45 @@ describe('Resolution Utils', () => {
652
1036
  });
653
1037
 
654
1038
  it('Should return true for 1st quarter typeId when quarter 1 complete (NFL)', () => {
655
- const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, 10021, SportPeriodType.QUARTERS_BASED);
1039
+ const result = canResolveMarketsForEvent(
1040
+ MockNFLLiveThirdQuarter,
1041
+ 10021,
1042
+ SportPeriodType.QUARTERS_BASED
1043
+ );
656
1044
  expect(result).toBe(true);
657
1045
  });
658
1046
 
659
1047
  it('Should return true for 2nd quarter typeId when quarters 1-2 complete (NFL)', () => {
660
- const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, 10022, SportPeriodType.QUARTERS_BASED);
1048
+ const result = canResolveMarketsForEvent(
1049
+ MockNFLLiveThirdQuarter,
1050
+ 10022,
1051
+ SportPeriodType.QUARTERS_BASED
1052
+ );
661
1053
  expect(result).toBe(true);
662
1054
  });
663
1055
 
664
1056
  it('Should return false for 3rd quarter typeId during 3rd quarter (NFL)', () => {
665
- const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, 10023, SportPeriodType.QUARTERS_BASED);
1057
+ const result = canResolveMarketsForEvent(
1058
+ MockNFLLiveThirdQuarter,
1059
+ 10023,
1060
+ SportPeriodType.QUARTERS_BASED
1061
+ );
666
1062
  expect(result).toBe(false);
667
1063
  });
668
1064
 
669
1065
  it('Should return true for all quarter typeIds when game is completed (NFL)', () => {
670
- expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10021, SportPeriodType.QUARTERS_BASED)).toBe(true);
671
- expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10022, SportPeriodType.QUARTERS_BASED)).toBe(true);
672
- expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10023, SportPeriodType.QUARTERS_BASED)).toBe(true);
673
- expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10024, SportPeriodType.QUARTERS_BASED)).toBe(true);
1066
+ expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10021, SportPeriodType.QUARTERS_BASED)).toBe(
1067
+ true
1068
+ );
1069
+ expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10022, SportPeriodType.QUARTERS_BASED)).toBe(
1070
+ true
1071
+ );
1072
+ expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10023, SportPeriodType.QUARTERS_BASED)).toBe(
1073
+ true
1074
+ );
1075
+ expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10024, SportPeriodType.QUARTERS_BASED)).toBe(
1076
+ true
1077
+ );
674
1078
  });
675
1079
 
676
1080
  it('Should return false when no periods are complete', () => {
@@ -687,35 +1091,55 @@ describe('Resolution Utils', () => {
687
1091
  describe('Batch typeIds checks', () => {
688
1092
  it('Should return only resolvable typeIds for live soccer in 2nd half', () => {
689
1093
  const typeIds = [10021, 10022, 10031, 10001];
690
- const result = filterMarketsThatCanBeResolved(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
1094
+ const result = filterMarketsThatCanBeResolved(
1095
+ MockSoccerLiveSecondHalf,
1096
+ typeIds,
1097
+ SportPeriodType.HALVES_BASED
1098
+ );
691
1099
 
692
1100
  expect(result).toEqual([10021, 10031]); // Only period 1 typeIds
693
1101
  });
694
1102
 
695
1103
  it('Should exclude full game typeIds during live game', () => {
696
1104
  const typeIds = [10021, 10001, 10002, 10003];
697
- const result = filterMarketsThatCanBeResolved(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
1105
+ const result = filterMarketsThatCanBeResolved(
1106
+ MockNFLLiveThirdQuarter,
1107
+ typeIds,
1108
+ SportPeriodType.QUARTERS_BASED
1109
+ );
698
1110
 
699
1111
  expect(result).toEqual([10021]); // Full game typeIds excluded
700
1112
  });
701
1113
 
702
1114
  it('Should include full game typeIds when game is completed', () => {
703
1115
  const typeIds = [10021, 10022, 10001, 10002];
704
- const result = filterMarketsThatCanBeResolved(MockSoccerCompletedEvent, typeIds, SportPeriodType.HALVES_BASED);
1116
+ const result = filterMarketsThatCanBeResolved(
1117
+ MockSoccerCompletedEvent,
1118
+ typeIds,
1119
+ SportPeriodType.HALVES_BASED
1120
+ );
705
1121
 
706
1122
  expect(result).toEqual([10021, 10022, 10001, 10002]);
707
1123
  });
708
1124
 
709
1125
  it('Should return empty array when no typeIds are resolvable', () => {
710
1126
  const typeIds = [10022, 10023, 10024];
711
- const result = filterMarketsThatCanBeResolved(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
1127
+ const result = filterMarketsThatCanBeResolved(
1128
+ MockSoccerLiveSecondHalf,
1129
+ typeIds,
1130
+ SportPeriodType.HALVES_BASED
1131
+ );
712
1132
 
713
1133
  expect(result).toEqual([]);
714
1134
  });
715
1135
 
716
1136
  it('Should return multiple period typeIds for NFL game in 3rd quarter', () => {
717
1137
  const typeIds = [10021, 10022, 10023, 10024, 10031, 10032, 10051];
718
- const result = filterMarketsThatCanBeResolved(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
1138
+ const result = filterMarketsThatCanBeResolved(
1139
+ MockNFLLiveThirdQuarter,
1140
+ typeIds,
1141
+ SportPeriodType.QUARTERS_BASED
1142
+ );
719
1143
 
720
1144
  // Periods 1 and 2 are complete (period 2 also completes 1st half = 10051)
721
1145
  expect(result).toEqual([10021, 10022, 10031, 10032, 10051]);
@@ -723,14 +1147,22 @@ describe('Resolution Utils', () => {
723
1147
 
724
1148
  it('Should handle all 9 periods for completed MLB game', () => {
725
1149
  const typeIds = [10021, 10022, 10023, 10024, 10025, 10026, 10027, 10028, 10029];
726
- const result = filterMarketsThatCanBeResolved(MockMLBCompletedEvent, typeIds, SportPeriodType.INNINGS_BASED);
1150
+ const result = filterMarketsThatCanBeResolved(
1151
+ MockMLBCompletedEvent,
1152
+ typeIds,
1153
+ SportPeriodType.INNINGS_BASED
1154
+ );
727
1155
 
728
1156
  expect(result).toEqual(typeIds); // All 9 innings complete
729
1157
  });
730
1158
 
731
1159
  it('Should return empty array when no periods complete', () => {
732
1160
  const typeIds = [10021, 10022, 10031];
733
- const result = filterMarketsThatCanBeResolved(MockSoccerLiveFirstHalf, typeIds, SportPeriodType.HALVES_BASED);
1161
+ const result = filterMarketsThatCanBeResolved(
1162
+ MockSoccerLiveFirstHalf,
1163
+ typeIds,
1164
+ SportPeriodType.HALVES_BASED
1165
+ );
734
1166
 
735
1167
  expect(result).toEqual([]);
736
1168
  });
@@ -739,35 +1171,55 @@ describe('Resolution Utils', () => {
739
1171
  describe('Multiple typeIds boolean array checks', () => {
740
1172
  it('Should return boolean array for live soccer in 2nd half', () => {
741
1173
  const typeIds = [10021, 10022, 10031, 10001];
742
- const result = canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
1174
+ const result = canResolveMultipleTypeIdsForEvent(
1175
+ MockSoccerLiveSecondHalf,
1176
+ typeIds,
1177
+ SportPeriodType.HALVES_BASED
1178
+ );
743
1179
 
744
1180
  expect(result).toEqual([true, false, true, false]); // Period 1 typeIds are true, period 2 and full game are false
745
1181
  });
746
1182
 
747
1183
  it('Should return false for full game typeIds during live game', () => {
748
1184
  const typeIds = [10021, 10001, 10002, 10003];
749
- const result = canResolveMultipleTypeIdsForEvent(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
1185
+ const result = canResolveMultipleTypeIdsForEvent(
1186
+ MockNFLLiveThirdQuarter,
1187
+ typeIds,
1188
+ SportPeriodType.QUARTERS_BASED
1189
+ );
750
1190
 
751
1191
  expect(result).toEqual([true, false, false, false]); // Only period 1 is true
752
1192
  });
753
1193
 
754
1194
  it('Should return all true for completed game', () => {
755
1195
  const typeIds = [10021, 10022, 10001, 10002];
756
- const result = canResolveMultipleTypeIdsForEvent(MockSoccerCompletedEvent, typeIds, SportPeriodType.HALVES_BASED);
1196
+ const result = canResolveMultipleTypeIdsForEvent(
1197
+ MockSoccerCompletedEvent,
1198
+ typeIds,
1199
+ SportPeriodType.HALVES_BASED
1200
+ );
757
1201
 
758
1202
  expect(result).toEqual([true, true, true, true]); // All complete
759
1203
  });
760
1204
 
761
1205
  it('Should return all false when no typeIds are resolvable', () => {
762
1206
  const typeIds = [10022, 10023, 10024];
763
- const result = canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, SportPeriodType.HALVES_BASED);
1207
+ const result = canResolveMultipleTypeIdsForEvent(
1208
+ MockSoccerLiveSecondHalf,
1209
+ typeIds,
1210
+ SportPeriodType.HALVES_BASED
1211
+ );
764
1212
 
765
1213
  expect(result).toEqual([false, false, false]);
766
1214
  });
767
1215
 
768
1216
  it('Should return mixed booleans for NFL game in 3rd quarter', () => {
769
1217
  const typeIds = [10021, 10022, 10023, 10024, 10031, 10032, 10051];
770
- const result = canResolveMultipleTypeIdsForEvent(MockNFLLiveThirdQuarter, typeIds, SportPeriodType.QUARTERS_BASED);
1218
+ const result = canResolveMultipleTypeIdsForEvent(
1219
+ MockNFLLiveThirdQuarter,
1220
+ typeIds,
1221
+ SportPeriodType.QUARTERS_BASED
1222
+ );
771
1223
 
772
1224
  // Periods 1 and 2 are complete (period 2 also completes 1st half = 10051)
773
1225
  expect(result).toEqual([true, true, false, false, true, true, true]);
@@ -775,14 +1227,22 @@ describe('Resolution Utils', () => {
775
1227
 
776
1228
  it('Should handle all 9 periods for completed MLB game', () => {
777
1229
  const typeIds = [10021, 10022, 10023, 10024, 10025, 10026, 10027, 10028, 10029];
778
- const result = canResolveMultipleTypeIdsForEvent(MockMLBCompletedEvent, typeIds, SportPeriodType.INNINGS_BASED);
1230
+ const result = canResolveMultipleTypeIdsForEvent(
1231
+ MockMLBCompletedEvent,
1232
+ typeIds,
1233
+ SportPeriodType.INNINGS_BASED
1234
+ );
779
1235
 
780
1236
  expect(result).toEqual([true, true, true, true, true, true, true, true, true]); // All 9 innings complete
781
1237
  });
782
1238
 
783
1239
  it('Should return all false when no periods complete', () => {
784
1240
  const typeIds = [10021, 10022, 10031];
785
- const result = canResolveMultipleTypeIdsForEvent(MockSoccerLiveFirstHalf, typeIds, SportPeriodType.HALVES_BASED);
1241
+ const result = canResolveMultipleTypeIdsForEvent(
1242
+ MockSoccerLiveFirstHalf,
1243
+ typeIds,
1244
+ SportPeriodType.HALVES_BASED
1245
+ );
786
1246
 
787
1247
  expect(result).toEqual([false, false, false]);
788
1248
  });
@@ -797,7 +1257,11 @@ describe('Resolution Utils', () => {
797
1257
 
798
1258
  describe('Edge cases', () => {
799
1259
  it('Should handle event with no completed periods', () => {
800
- const result = canResolveMarketsForEvent(MockSoccerLiveFirstHalfInProgress, 10021, SportPeriodType.HALVES_BASED);
1260
+ const result = canResolveMarketsForEvent(
1261
+ MockSoccerLiveFirstHalfInProgress,
1262
+ 10021,
1263
+ SportPeriodType.HALVES_BASED
1264
+ );
801
1265
  expect(result).toBe(false);
802
1266
  });
803
1267
 
@@ -806,7 +1270,11 @@ describe('Resolution Utils', () => {
806
1270
  const fullGameTypeIds = [0, 10001, 10002, 10003, 10004, 10010, 10011, 10012];
807
1271
 
808
1272
  fullGameTypeIds.forEach((typeId) => {
809
- const result = canResolveMarketsForEvent(MockNFLLiveThirdQuarter, typeId, SportPeriodType.QUARTERS_BASED);
1273
+ const result = canResolveMarketsForEvent(
1274
+ MockNFLLiveThirdQuarter,
1275
+ typeId,
1276
+ SportPeriodType.QUARTERS_BASED
1277
+ );
810
1278
  expect(result).toBe(false);
811
1279
  });
812
1280
  });
@@ -817,7 +1285,11 @@ describe('Resolution Utils', () => {
817
1285
  });
818
1286
 
819
1287
  it('Should work with sport type parameter for batch typeIds', () => {
820
- const result = filterMarketsThatCanBeResolved(MockSoccerLiveSecondHalf, [10021, 10022], SportPeriodType.HALVES_BASED);
1288
+ const result = filterMarketsThatCanBeResolved(
1289
+ MockSoccerLiveSecondHalf,
1290
+ [10021, 10022],
1291
+ SportPeriodType.HALVES_BASED
1292
+ );
821
1293
  expect(result).toEqual([10021]);
822
1294
  });
823
1295
  });
@@ -833,42 +1305,74 @@ describe('Resolution Utils', () => {
833
1305
  });
834
1306
 
835
1307
  it('Should resolve all quarter typeIds (10021-10024) for completed overtime game', () => {
836
- expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10021, SportPeriodType.QUARTERS_BASED)).toBe(true);
837
- expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10022, SportPeriodType.QUARTERS_BASED)).toBe(true);
838
- expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10023, SportPeriodType.QUARTERS_BASED)).toBe(true);
839
- expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10024, SportPeriodType.QUARTERS_BASED)).toBe(true);
1308
+ expect(
1309
+ canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10021, SportPeriodType.QUARTERS_BASED)
1310
+ ).toBe(true);
1311
+ expect(
1312
+ canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10022, SportPeriodType.QUARTERS_BASED)
1313
+ ).toBe(true);
1314
+ expect(
1315
+ canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10023, SportPeriodType.QUARTERS_BASED)
1316
+ ).toBe(true);
1317
+ expect(
1318
+ canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10024, SportPeriodType.QUARTERS_BASED)
1319
+ ).toBe(true);
840
1320
  });
841
1321
 
842
1322
  it('Should resolve overtime period typeId (10025)', () => {
843
- const result = canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10025, SportPeriodType.QUARTERS_BASED);
1323
+ const result = canResolveMarketsForEvent(
1324
+ MockNFLCompletedWithOvertime,
1325
+ 10025,
1326
+ SportPeriodType.QUARTERS_BASED
1327
+ );
844
1328
  expect(result).toBe(true);
845
1329
  });
846
1330
 
847
1331
  it('Should resolve full game typeIds for completed overtime game', () => {
848
- expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10001, SportPeriodType.QUARTERS_BASED)).toBe(true);
849
- expect(canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10002, SportPeriodType.QUARTERS_BASED)).toBe(true);
1332
+ expect(
1333
+ canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10001, SportPeriodType.QUARTERS_BASED)
1334
+ ).toBe(true);
1335
+ expect(
1336
+ canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10002, SportPeriodType.QUARTERS_BASED)
1337
+ ).toBe(true);
850
1338
  });
851
1339
 
852
1340
  it('Should return all resolvable typeIds including overtime in batch check', () => {
853
1341
  const typeIds = [10021, 10022, 10023, 10024, 10025, 10001];
854
- const result = filterMarketsThatCanBeResolved(MockNFLCompletedWithOvertime, typeIds, SportPeriodType.QUARTERS_BASED);
1342
+ const result = filterMarketsThatCanBeResolved(
1343
+ MockNFLCompletedWithOvertime,
1344
+ typeIds,
1345
+ SportPeriodType.QUARTERS_BASED
1346
+ );
855
1347
 
856
1348
  expect(result).toEqual(typeIds); // All should be resolvable (game completed with overtime)
857
1349
  });
858
1350
 
859
1351
  it('Should return false for 8th period typeId (period did not occur)', () => {
860
- const result = canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10028, SportPeriodType.QUARTERS_BASED);
1352
+ const result = canResolveMarketsForEvent(
1353
+ MockNFLCompletedWithOvertime,
1354
+ 10028,
1355
+ SportPeriodType.QUARTERS_BASED
1356
+ );
861
1357
  expect(result).toBe(false);
862
1358
  });
863
1359
 
864
1360
  it('Should return false for 9th period typeId (period did not occur)', () => {
865
- const result = canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10029, SportPeriodType.QUARTERS_BASED);
1361
+ const result = canResolveMarketsForEvent(
1362
+ MockNFLCompletedWithOvertime,
1363
+ 10029,
1364
+ SportPeriodType.QUARTERS_BASED
1365
+ );
866
1366
  expect(result).toBe(false);
867
1367
  });
868
1368
 
869
1369
  it('Should not include non-existent periods in batch check', () => {
870
1370
  const typeIds = [10021, 10022, 10025, 10028, 10029];
871
- const result = filterMarketsThatCanBeResolved(MockNFLCompletedWithOvertime, typeIds, SportPeriodType.QUARTERS_BASED);
1371
+ const result = filterMarketsThatCanBeResolved(
1372
+ MockNFLCompletedWithOvertime,
1373
+ typeIds,
1374
+ SportPeriodType.QUARTERS_BASED
1375
+ );
872
1376
 
873
1377
  // Only periods 1, 2, and 5 occurred, so only their typeIds should be returned
874
1378
  expect(result).toEqual([10021, 10022, 10025]);
@@ -878,11 +1382,7 @@ describe('Resolution Utils', () => {
878
1382
  describe('Sport-type-specific resolution for typeId 10051 (1st half)', () => {
879
1383
  it('Soccer (HALVES_BASED): Should resolve typeId 10051 after period 1', () => {
880
1384
  // Soccer: Period 1 = 1st half
881
- const result = canResolveMarketsForEvent(
882
- MockSoccerLiveSecondHalf,
883
- 10051,
884
- SportPeriodType.HALVES_BASED
885
- );
1385
+ const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10051, SportPeriodType.HALVES_BASED);
886
1386
  expect(result).toBe(true);
887
1387
  });
888
1388
 
@@ -907,21 +1407,13 @@ describe('Resolution Utils', () => {
907
1407
  in_play: { period: '2', clock: '5:00' },
908
1408
  };
909
1409
 
910
- const result = canResolveMarketsForEvent(
911
- mockNFLFirstQuarter,
912
- 10051,
913
- SportPeriodType.QUARTERS_BASED
914
- );
1410
+ const result = canResolveMarketsForEvent(mockNFLFirstQuarter, 10051, SportPeriodType.QUARTERS_BASED);
915
1411
  expect(result).toBe(false);
916
1412
  });
917
1413
 
918
1414
  it('MLB (INNINGS_BASED): Should resolve typeId 10051 after period 5', () => {
919
1415
  // MLB: Period 5 completes 1st half (innings 1-5)
920
- const result = canResolveMarketsForEvent(
921
- MockMLBLiveSixthInning,
922
- 10051,
923
- SportPeriodType.INNINGS_BASED
924
- );
1416
+ const result = canResolveMarketsForEvent(MockMLBLiveSixthInning, 10051, SportPeriodType.INNINGS_BASED);
925
1417
  expect(result).toBe(true);
926
1418
  });
927
1419
 
@@ -952,42 +1444,45 @@ describe('Resolution Utils', () => {
952
1444
  in_play: { period: '5', clock: null },
953
1445
  };
954
1446
 
955
- const result = canResolveMarketsForEvent(
956
- mockMLBFourthInning,
957
- 10051,
958
- SportPeriodType.INNINGS_BASED
959
- );
1447
+ const result = canResolveMarketsForEvent(mockMLBFourthInning, 10051, SportPeriodType.INNINGS_BASED);
960
1448
  expect(result).toBe(false);
961
1449
  });
962
1450
  });
963
1451
 
964
1452
  describe('Sport-type-specific resolution for typeId 10052 (2nd half)', () => {
965
1453
  it('Soccer (HALVES_BASED): Should resolve typeId 10052 after period 2', () => {
966
- const result = canResolveMarketsForEvent(
967
- MockSoccerCompletedEvent,
968
- 10052,
969
- SportPeriodType.HALVES_BASED
970
- );
1454
+ const result = canResolveMarketsForEvent(MockSoccerCompletedEvent, 10052, SportPeriodType.HALVES_BASED);
971
1455
  expect(result).toBe(true);
972
1456
  });
973
1457
 
974
1458
  it('NFL (QUARTERS_BASED): Should resolve typeId 10052 after period 4', () => {
975
- const result = canResolveMarketsForEvent(
976
- MockNFLCompletedEvent,
977
- 10052,
978
- SportPeriodType.QUARTERS_BASED
979
- );
1459
+ const result = canResolveMarketsForEvent(MockNFLCompletedEvent, 10052, SportPeriodType.QUARTERS_BASED);
980
1460
  expect(result).toBe(true);
981
1461
  });
982
1462
 
983
1463
  it('MLB (INNINGS_BASED): Should resolve typeId 10052 after period 9', () => {
984
- const result = canResolveMarketsForEvent(
985
- MockMLBCompletedEvent,
986
- 10052,
987
- SportPeriodType.INNINGS_BASED
988
- );
1464
+ const result = canResolveMarketsForEvent(MockMLBCompletedEvent, 10052, SportPeriodType.INNINGS_BASED);
989
1465
  expect(result).toBe(true);
990
1466
  });
991
1467
  });
1468
+
1469
+ describe('Error handling for invalid sport type numbers', () => {
1470
+ it('Should throw error for invalid sport type number when using numeric parameter', () => {
1471
+ const typeIds = [10021, 10022];
1472
+
1473
+ // Invalid sport type number (must be 0-3)
1474
+ expect(() => {
1475
+ canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, 99);
1476
+ }).toThrow('Invalid sport type number: 99. Must be 0 (halves), 1 (quarters), 2 (innings), or 3 (period).');
1477
+ });
1478
+
1479
+ it('Should throw error for negative sport type number', () => {
1480
+ const typeIds = [10021];
1481
+
1482
+ expect(() => {
1483
+ canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, -1);
1484
+ }).toThrow('Invalid sport type number: -1. Must be 0 (halves), 1 (quarters), 2 (innings), or 3 (period).');
1485
+ });
1486
+ });
992
1487
  });
993
1488
  });