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.
- package/CLAUDE.md +26 -19
- package/main.js +1 -1
- package/package.json +1 -1
- package/resolution_live_markets.md +93 -88
- package/src/tests/mock/MockOpticOddsEvents.ts +144 -0
- package/src/tests/unit/resolution.test.ts +566 -71
- package/src/types/resolution.ts +4 -4
- package/src/utils/resolution.ts +43 -22
- package/src/utils/sportPeriodMapping.ts +36 -0
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
expect(canResolveMarketsForEvent(MockNFLCompletedEvent,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
expect(
|
|
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(
|
|
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(
|
|
849
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
});
|