shufflecom-calculations 6.0.0 → 6.1.0
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/lib/airdrop/airdrop-calculator.d.ts +0 -1
- package/lib/airdrop/airdrop-calculator.js +0 -3
- package/lib/airdrop/airdrop-calculator.js.map +1 -1
- package/lib/fiat-document/index.d.ts +0 -13
- package/lib/fiat-document/index.js +1 -82
- package/lib/fiat-document/index.js.map +1 -1
- package/lib/regex/index.d.ts +0 -3
- package/lib/regex/index.js +1 -4
- package/lib/regex/index.js.map +1 -1
- package/lib/sports/calculate-sports-payout-odds.d.ts +1 -12
- package/lib/sports/calculate-sports-payout-odds.js +1 -63
- package/lib/sports/calculate-sports-payout-odds.js.map +1 -1
- package/lib/sports/sports.types.d.ts +3 -2
- package/lib/sports/sports.types.js +1 -0
- package/lib/sports/sports.types.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -4
- package/src/airdrop/airdrop-calculator.ts +0 -4
- package/src/fiat-document/index.ts +0 -97
- package/src/regex/index.ts +0 -7
- package/src/regex/regex.spec.ts +0 -20
- package/src/sports/calculate-sports-payout-odd.spec.ts +101 -110
- package/src/sports/calculate-sports-payout-odds.ts +1 -98
- package/src/sports/sports.types.ts +2 -3
- package/src/fiat-document/fiat-document.spec.ts +0 -137
|
@@ -4,9 +4,7 @@ import {
|
|
|
4
4
|
OutcomeResult,
|
|
5
5
|
SportsBetLegInterface,
|
|
6
6
|
SportsBetLegType,
|
|
7
|
-
SportsBetSelectionInterface,
|
|
8
7
|
SportsBetSelectionStatus,
|
|
9
|
-
SportsBetType,
|
|
10
8
|
SportsMarketSelectionInterface,
|
|
11
9
|
} from './sports.types';
|
|
12
10
|
import { SportsSystemBetType, SportsSystemBetTypeCombinations, SportsSystemBetTypeSelectionCountsToBetCounts } from './system-bet.types';
|
|
@@ -46,72 +44,6 @@ export function generateSystemBetIndexCombinations(length: number, size: number)
|
|
|
46
44
|
return combinations;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
export function getSystemBetSubBetSelectionsAndIndex(
|
|
50
|
-
selections: SportsBetSelectionInterface[],
|
|
51
|
-
sportsSystemBetType: SportsSystemBetType,
|
|
52
|
-
): { subBetSelection: SportsBetSelectionInterface[]; combinationIndex: string }[] {
|
|
53
|
-
const systemBetSizes = SportsSystemBetTypeCombinations[sportsSystemBetType];
|
|
54
|
-
if (!systemBetSizes) {
|
|
55
|
-
throw new Error('Invalid system bet type');
|
|
56
|
-
}
|
|
57
|
-
if (!SportsSystemBetTypeSelectionCountsToBetCounts[selections.length]?.[sportsSystemBetType]) {
|
|
58
|
-
throw new Error('Invalid selection count');
|
|
59
|
-
}
|
|
60
|
-
const combinations = systemBetSizes.flatMap(size => generateSystemBetIndexCombinations(selections.length, size));
|
|
61
|
-
const combinationSelections = combinations.map(combination => {
|
|
62
|
-
return {
|
|
63
|
-
subBetSelection: combination.map(index => selections[index]),
|
|
64
|
-
combinationIndex: combination.map(index => index + 1).join('/'),
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
return combinationSelections;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function getSystemBetSubBetSelections(
|
|
72
|
-
selections: SportsBetSelectionInterface[],
|
|
73
|
-
sportsSystemBetType: SportsSystemBetType,
|
|
74
|
-
): SportsBetSelectionInterface[][] {
|
|
75
|
-
const subBetSelectionsAndIndex = getSystemBetSubBetSelectionsAndIndex(selections, sportsSystemBetType);
|
|
76
|
-
return subBetSelectionsAndIndex.map(({ subBetSelection }) => subBetSelection);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Selection-shaped façade over `calculateSportsPayoutOddsFromLegs`. Builds legs from the input
|
|
80
|
-
// selections, maps subBetSelections to subBetLegs, and delegates. Callers that already have legs
|
|
81
|
-
// deprecated function and is only used for cashout atm, as only regular bet / system bets are allowed for cashouts
|
|
82
|
-
export function calculateSportsPayoutOdds(
|
|
83
|
-
selections: SportsBetSelectionInterface[],
|
|
84
|
-
marketSelections: SportsMarketSelectionInterface[],
|
|
85
|
-
opt: {
|
|
86
|
-
sportsCustomBetTotalOddsDecimal: BigNumber | null;
|
|
87
|
-
subBetSelections: SportsBetSelectionInterface[][] | null;
|
|
88
|
-
isEstimation: boolean; // treats PENDING leg as WON
|
|
89
|
-
},
|
|
90
|
-
): BigNumber {
|
|
91
|
-
const sportsBetType = opt.sportsCustomBetTotalOddsDecimal !== null ? SportsBetType.CUSTOM_BET : SportsBetType.REGULAR;
|
|
92
|
-
const legs = deriveLegsFromSelections(sportsBetType, selections, opt.sportsCustomBetTotalOddsDecimal ?? new BigNumber(1));
|
|
93
|
-
const subBetLegs = opt.subBetSelections ? mapSubBetSelectionsToLegs(opt.subBetSelections, legs) : null;
|
|
94
|
-
return calculateSportsPayoutOddsFromLegs(legs, marketSelections, { subBetLegs, isEstimation: opt.isEstimation });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function mapSubBetSelectionsToLegs(subBetSelections: SportsBetSelectionInterface[][], legs: SportsBetLegInterface[]): SportsBetLegInterface[][] {
|
|
98
|
-
const legByMarketSelectionId = new Map<string, SportsBetLegInterface>();
|
|
99
|
-
for (const leg of legs) {
|
|
100
|
-
for (const selection of leg.selections) {
|
|
101
|
-
legByMarketSelectionId.set(selection.marketSelectionId, leg);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return subBetSelections.map(subBet =>
|
|
105
|
-
subBet.map(selection => {
|
|
106
|
-
const leg = legByMarketSelectionId.get(selection.marketSelectionId);
|
|
107
|
-
if (!leg) {
|
|
108
|
-
throw new Error('Sub bet leg not found');
|
|
109
|
-
}
|
|
110
|
-
return leg;
|
|
111
|
-
}),
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
47
|
function calculateSelectionCashoutOdds(
|
|
116
48
|
totalOddsDecimal: BigNumber,
|
|
117
49
|
selectionOddsDecimal: BigNumber,
|
|
@@ -188,35 +120,6 @@ function calculateSelectionCashoutOdds(
|
|
|
188
120
|
.dividedBy(oddsFractionToDecimal(marketSelection.oddsNumerator, marketSelection.oddsDenominator));
|
|
189
121
|
}
|
|
190
122
|
|
|
191
|
-
/**
|
|
192
|
-
* Structural mapping from (bet type, selections) → legs.
|
|
193
|
-
*
|
|
194
|
-
* @deprecated Reconstructing legs from a flat selection list only works for regular/system bets and
|
|
195
|
-
* single-custom-leg bets — it cannot represent a multi-custom-leg (multi-SGM) bet and throws for
|
|
196
|
-
* one. Confined to the cashout odds path; anywhere persisted legs are available, pass them to
|
|
197
|
-
* `calculateSportsPayoutOddsFromLegs` instead of calling this.
|
|
198
|
-
*/
|
|
199
|
-
export function deriveLegsFromSelections(
|
|
200
|
-
sportsBetType: SportsBetType,
|
|
201
|
-
selections: SportsBetSelectionInterface[],
|
|
202
|
-
customBetTotalOddsDecimal: BigNumber,
|
|
203
|
-
): SportsBetLegInterface[] {
|
|
204
|
-
if (sportsBetType === SportsBetType.CUSTOM_BET) {
|
|
205
|
-
const distinctLegIds = new Set(selections.map(selection => selection.sportsBetLegId).filter(Boolean));
|
|
206
|
-
if (distinctLegIds.size > 1) {
|
|
207
|
-
throw new Error(
|
|
208
|
-
'deriveLegsFromSelections cannot reconstruct a multi-custom-leg bet from a flat selection list; use persisted legs with calculateSportsPayoutOddsFromLegs',
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
return [{ type: SportsBetLegType.CUSTOM, oddsDecimal: customBetTotalOddsDecimal, selections }];
|
|
212
|
-
}
|
|
213
|
-
return selections.map(selection => ({
|
|
214
|
-
type: SportsBetLegType.REGULAR,
|
|
215
|
-
oddsDecimal: oddsFractionToDecimal(selection.oddsNumerator, selection.oddsDenominator),
|
|
216
|
-
selections: [selection],
|
|
217
|
-
}));
|
|
218
|
-
}
|
|
219
|
-
|
|
220
123
|
export function getSystemBetSubBetLegs<TLeg>(legs: TLeg[], sportsSystemBetType: SportsSystemBetType): TLeg[][] {
|
|
221
124
|
return getSystemBetSubBetLegsAndIndex(legs, sportsSystemBetType).map(({ subBetLegs }) => subBetLegs);
|
|
222
125
|
}
|
|
@@ -292,7 +195,7 @@ function getLegPayoutOddsDecimal(
|
|
|
292
195
|
return leg.oddsDecimal;
|
|
293
196
|
}
|
|
294
197
|
|
|
295
|
-
// REGULAR leg: exactly one selection
|
|
198
|
+
// REGULAR leg: exactly one selection, so the leg resolves to that selection's own outcome.
|
|
296
199
|
const [selection] = leg.selections;
|
|
297
200
|
const selectionOddsDecimal = oddsFractionToDecimal(selection.oddsNumerator, selection.oddsDenominator);
|
|
298
201
|
const marketSelection = marketSelectionMap[selection.marketSelectionId];
|
|
@@ -96,6 +96,7 @@ export enum SportsCashoutFailedReason {
|
|
|
96
96
|
SPORTS_MARKET_CLOSED = 'SPORTS_MARKET_CLOSED',
|
|
97
97
|
SPORTS_MARKET_CASHOUT_NOT_ENABLED = 'SPORTS_MARKET_CASHOUT_NOT_ENABLED',
|
|
98
98
|
BOOSTED_BET_NOT_ALLOWED_CASHOUT = 'BOOSTED_BET_NOT_ALLOWED_CASHOUT',
|
|
99
|
+
CUSTOM_BET_NOT_ALLOWED_CASHOUT = 'CUSTOM_BET_NOT_ALLOWED_CASHOUT',
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
export enum SportsBetType {
|
|
@@ -169,9 +170,7 @@ export interface SportsBetSelectionInterface {
|
|
|
169
170
|
status: SportsBetSelectionStatus;
|
|
170
171
|
oddsNumerator: BigNumber;
|
|
171
172
|
oddsDenominator: BigNumber;
|
|
172
|
-
|
|
173
|
-
// list cannot faithfully represent (see deriveLegsFromSelections).
|
|
174
|
-
sportsBetLegId?: string | null;
|
|
173
|
+
sportsBetLegId: string;
|
|
175
174
|
boostId?: string | null;
|
|
176
175
|
}
|
|
177
176
|
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { FIAT_DOCUMENT_SPECS, FiatDocumentType, compactFiatDocument, formatFiatDocument, isFiatDocumentValid } from '.';
|
|
2
|
-
|
|
3
|
-
const { CPF, CURP, CUIL_CUIT } = FiatDocumentType;
|
|
4
|
-
|
|
5
|
-
describe('fiat documents', () => {
|
|
6
|
-
describe('isFiatDocumentValid', () => {
|
|
7
|
-
it('accepts each document formatted or bare', () => {
|
|
8
|
-
expect(isFiatDocumentValid(CPF, '123.456.789-09')).toBe(true);
|
|
9
|
-
expect(isFiatDocumentValid(CPF, '12345678909')).toBe(true);
|
|
10
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '20-32456789-6')).toBe(true);
|
|
11
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '20324567896')).toBe(true);
|
|
12
|
-
expect(isFiatDocumentValid(CURP, 'BOXW310820HNERXN09')).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('rejects wrong length, stray characters and bad prefixes', () => {
|
|
16
|
-
expect(isFiatDocumentValid(CPF, '1234567890')).toBe(false);
|
|
17
|
-
expect(isFiatDocumentValid(CPF, '1234567890a')).toBe(false);
|
|
18
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '19-12345678-9')).toBe(false);
|
|
19
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '2012345678')).toBe(false);
|
|
20
|
-
expect(isFiatDocumentValid(CURP, 'ABCD123456HDFXYZ')).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("tolerates a document's own separators in any position", () => {
|
|
24
|
-
expect(isFiatDocumentValid(CPF, '123-456-789-09')).toBe(true);
|
|
25
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '20-3245678-96')).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('rejects a separator the document does not use — a dotted CUIT is not a CUIT', () => {
|
|
29
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '20.32456789.6')).toBe(false);
|
|
30
|
-
expect(isFiatDocumentValid(CURP, 'BOXW-310820-HNERXN09')).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('compactFiatDocument', () => {
|
|
35
|
-
it('strips separators and upper-cases, per document column', () => {
|
|
36
|
-
expect(compactFiatDocument('cpf', '123.456.789-09')).toBe('12345678909');
|
|
37
|
-
expect(compactFiatDocument('cuilCuit', '20-32456789-6')).toBe('20324567896');
|
|
38
|
-
expect(compactFiatDocument('curp', 'boxw310820hnerxn09')).toBe('BOXW310820HNERXN09');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('is idempotent on an already-canonical value', () => {
|
|
42
|
-
expect(compactFiatDocument('cpf', '12345678909')).toBe('12345678909');
|
|
43
|
-
expect(compactFiatDocument('curp', 'BOXW310820HNERXN09')).toBe('BOXW310820HNERXN09');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('passes a non-document column through untouched', () => {
|
|
47
|
-
expect(compactFiatDocument('email', 'Fiat.User@Example.TEST')).toBe('Fiat.User@Example.TEST');
|
|
48
|
-
expect(compactFiatDocument('phoneNumber', '+14165550100')).toBe('+14165550100');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe('formatFiatDocument', () => {
|
|
53
|
-
it('re-punctuates a stored document into the shape the product spec names', () => {
|
|
54
|
-
expect(formatFiatDocument('cpf', '12345678909')).toBe('123.456.789-09');
|
|
55
|
-
expect(formatFiatDocument('cuilCuit', '20324567896')).toBe('20-32456789-6');
|
|
56
|
-
expect(formatFiatDocument('curp', 'BOXW310820HNERXN09')).toBe('BOXW310820HNERXN09');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('round-trips against compactFiatDocument and is idempotent', () => {
|
|
60
|
-
for (const [column, stored] of [
|
|
61
|
-
['cpf', '12345678909'],
|
|
62
|
-
['cuilCuit', '20324567896'],
|
|
63
|
-
['curp', 'BOXW310820HNERXN09'],
|
|
64
|
-
] as const) {
|
|
65
|
-
const formatted = formatFiatDocument(column, stored);
|
|
66
|
-
expect(compactFiatDocument(column, formatted)).toBe(stored);
|
|
67
|
-
expect(formatFiatDocument(column, formatted)).toBe(formatted);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('passes a non-document column through untouched', () => {
|
|
72
|
-
expect(formatFiatDocument('email', 'a@b.test')).toBe('a@b.test');
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('CUIT prefixes', () => {
|
|
77
|
-
// AFIP check digit over base '<prefix>32456789', so each case is a genuinely valid CUIT.
|
|
78
|
-
const W = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
|
|
79
|
-
const validCuitFor = (prefix: string) => {
|
|
80
|
-
const base = `${prefix}32456789`;
|
|
81
|
-
const sum = [...base].reduce((acc, d, i) => acc + Number(d) * W[i], 0);
|
|
82
|
-
const remainder = 11 - (sum % 11);
|
|
83
|
-
return base + (remainder === 11 ? 0 : remainder === 10 ? 9 : remainder);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// AFIP: 20 men, 27 women, 23-26 either (when the usual prefix is already taken), 30/33
|
|
87
|
-
// legal persons. 34 is not in AFIP's summary but is in stdnum's list and long-standing
|
|
88
|
-
// practice, so we keep it — a superset can only fail at the PSP, never lock a user out.
|
|
89
|
-
// stdnum's own list omits 25 and 26, which is why we compute the check digit ourselves.
|
|
90
|
-
it.each(['20', '23', '24', '25', '26', '27', '30', '33', '34'])('accepts a valid CUIT with the %s prefix', prefix => {
|
|
91
|
-
expect(isFiatDocumentValid(CUIL_CUIT, validCuitFor(prefix))).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// An individual's 8 middle digits are their DNI, zero-padded when shorter.
|
|
95
|
-
it('accepts a zero-padded DNI', () => {
|
|
96
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '20051234562')).toBe(true);
|
|
97
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '27000512341')).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('still rejects a prefix that is not issued at all', () => {
|
|
101
|
-
expect(isFiatDocumentValid(CUIL_CUIT, validCuitFor('19'))).toBe(false);
|
|
102
|
-
expect(isFiatDocumentValid(CUIL_CUIT, validCuitFor('28'))).toBe(false);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('issuer checksums', () => {
|
|
107
|
-
// Each value below is well-formed but carries wrong check digits — a mistyped document,
|
|
108
|
-
// which is exactly what the checksum exists to catch before it reaches the PSP.
|
|
109
|
-
it.each([
|
|
110
|
-
[CUIL_CUIT, '20-32456789-4'],
|
|
111
|
-
[CURP, 'BOXW310820HNERXN08'],
|
|
112
|
-
[CPF, '12345678900'],
|
|
113
|
-
])('rejects a well-formed but checksum-invalid %s', (documentType, value) => {
|
|
114
|
-
expect(isFiatDocumentValid(documentType, value)).toBe(false);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('rejects an all-zero DNI, which the CUIT check digit alone would let through', () => {
|
|
118
|
-
// 20-00000000-1 and 27-00000000-6 both carry correct check digits.
|
|
119
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '20000000001')).toBe(false);
|
|
120
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '27000000006')).toBe(false);
|
|
121
|
-
// A real DNI that merely starts with zeros still passes.
|
|
122
|
-
expect(isFiatDocumentValid(CUIL_CUIT, '27000512341')).toBe(true);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('rejects every repeated-digit CPF, which mod-11 alone would let through', () => {
|
|
126
|
-
for (let digit = 0; digit <= 9; digit++) {
|
|
127
|
-
expect(isFiatDocumentValid(CPF, String(digit).repeat(11))).toBe(false);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('keeps a checksum on every document, so a new one is a deliberate omission', () => {
|
|
132
|
-
for (const spec of Object.values(FIAT_DOCUMENT_SPECS)) {
|
|
133
|
-
expect(typeof spec.checksum).toBe('function');
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
});
|