Last active
June 28, 2016 11:52
-
-
Save anatomic/fb58dc98840ab59d25bd3f9bf8ba128e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { __, pick, compose, curry, concat, map, reduce, cond, | |
T, divide, multiply, add, subtract, lift, view, lensProp, lensPath, over, | |
prop, props, identity, findLast, keys, sort, split, apply } = require("ramda"); | |
const Either = require('data.either'); | |
const SCORECAST_CONVERSIONS = { | |
'1': '1/2000', | |
'1.001': '1/1000', | |
'1.002': '1/400', | |
'1.003': '1/300', | |
'1.004': '1/250', | |
'1.005': '1/200', | |
'1.007': '1/150', | |
'1.008': '1/125', | |
'1.01': '1/100', | |
'1.012': '1/80', | |
'1.015': '1/66', | |
'1.017': '1/60', | |
'1.02': '1/50', | |
'1.025': '1/40', | |
'1.03': '1/33', | |
'1.033': '1/30', | |
'1.036': '1/28', | |
'1.04': '1/25', | |
'1.046': '1/22', | |
'1.05': '1/20', | |
'1.053': '1/19', | |
'1.056': '1/18', | |
'1.062': '1/16', | |
'1.067': '1/15', | |
'1.071': '1/14', | |
'1.077': '1/13', | |
'1.083': '1/12', | |
'1.091': '1/11', | |
'1.1': '1/10', | |
'1.111': '1/9', | |
'1.118': '2/17', | |
'1.125': '1/8', | |
'1.133': '2/15', | |
'1.143': '1/7', | |
'1.154': '2/13', | |
'1.167': '1/6', | |
'1.182': '2/11', | |
'1.2': '1/5', | |
'1.222': '2/9', | |
'1.25': '1/4', | |
'1.286': '2/7', | |
'1.3': '30/100', | |
'1.333': '1/3', | |
'1.35': '7/20', | |
'1.364': '4/11', | |
'1.4': '2/5', | |
'1.444': '4/9', | |
'1.45': '9/20', | |
'1.471': '40/85', | |
'1.5': '1/2', | |
'1.533': '8/15', | |
'1.571': '4/7', | |
'1.6': '3/5', | |
'1.615': '8/13', | |
'1.625': '5/8', | |
'1.667': '4/6', | |
'1.7': '7/10', | |
'1.727': '8/11', | |
'1.8': '4/5', | |
'1.833': '5/6', | |
'1.9': '9/10', | |
'1.909': '10/11', | |
'1.952': '20/21', | |
'2': '1/1', | |
'2.05': '21/20', | |
'2.1': '11/10', | |
'2.2': '6/5', | |
'2.25': '5/4', | |
'2.3': '13/10', | |
'2.375': '11/8', | |
'2.4': '7/5', | |
'2.5': '6/4', | |
'2.6': '8/5', | |
'2.625': '13/8', | |
'2.7': '17/10', | |
'2.75': '7/4', | |
'2.8': '9/5', | |
'2.875': '15/8', | |
'2.9': '19/10', | |
'3': '2/1', | |
'3.1': '21/10', | |
'3.125': '85/40', | |
'3.2': '11/5', | |
'3.25': '9/4', | |
'3.3': '23/10', | |
'3.375': '95/40', | |
'3.4': '12/5', | |
'3.5': '5/2', | |
'3.6': '13/5', | |
'3.75': '11/4', | |
'3.8': '14/5', | |
'4': '3/1', | |
'4.2': '16/5', | |
'4.333': '100/30', | |
'4.5': '7/2', | |
'4.6': '18/5', | |
'5': '4/1', | |
'5.5': '9/2', | |
'6': '5/1', | |
'6.5': '11/2', | |
'7': '6/1', | |
'7.5': '13/2', | |
'8': '7/1', | |
'8.5': '15/2', | |
'9': '8/1', | |
'9.5': '17/2', | |
'10': '9/1', | |
'11': '10/1', | |
'12': '11/1', | |
'13': '12/1', | |
'14': '13/1', | |
'15': '14/1', | |
'16': '15/1', | |
'17': '16/1', | |
'18': '17/1', | |
'19': '18/1', | |
'20': '19/1', | |
'21': '20/1', | |
'23': '22/1', | |
'26': '25/1', | |
'29': '28/1', | |
'31': '30/1', | |
'34': '33/1', | |
'36': '35/1', | |
'41': '40/1', | |
'46': '45/1', | |
'51': '50/1', | |
'56': '55/1', | |
'61': '60/1', | |
'67': '66/1', | |
'71': '70/1', | |
'76': '75/1', | |
'81': '80/1', | |
'86': '85/1', | |
'91': '90/1', | |
'96': '95/1', | |
'101': '100/1', | |
'106': '105/1', | |
'111': '110/1', | |
'116': '115/1', | |
'121': '120/1', | |
'126': '125/1', | |
'131': '130/1', | |
'136': '135/1', | |
'141': '140/1', | |
'146': '145/1', | |
'151': '150/1', | |
'156': '155/1', | |
'161': '160/1', | |
'166': '165/1', | |
'171': '170/1', | |
'176': '175/1', | |
'181': '180/1', | |
'186': '185/1', | |
'191': '190/1', | |
'196': '195/1', | |
'201': '200/1', | |
'211': '210/1', | |
'221': '220/1', | |
'231': '230/1', | |
'241': '240/1', | |
'251': '250/1', | |
'261': '260/1', | |
'271': '270/1', | |
'281': '280/1', | |
'291': '290/1', | |
'301': '300/1', | |
'326': '325/1', | |
'351': '350/1', | |
'376': '375/1', | |
'401': '400/1', | |
'426': '425/1', | |
'451': '450/1', | |
'476': '475/1', | |
'501': '500/1', | |
'551': '550/1', | |
'601': '600/1', | |
'651': '650/1', | |
'701': '700/1', | |
'751': '750/1', | |
'801': '800/1', | |
'851': '850/1', | |
'901': '900/1', | |
'951': '950/1', | |
'1001': '1000/1', | |
'1101': '1100/1', | |
'1201': '1200/1', | |
'1251': '1250/1', | |
'1501': '1500/1', | |
'1601': '1600/1', | |
'1701': '1700/1', | |
'1801': '1800/1', | |
'1901': '1900/1', | |
'2001': '2000/1', | |
'2501': '2500/1', | |
'3001': '3000/1', | |
'4001': '4000/1', | |
'5001': '5000/1', | |
'6001': '6000/1', | |
'7001': '7000/1', | |
'8001': '8000/1', | |
'9001': '9000/1', | |
'10001': '10000/1' | |
}; | |
const buildOrderedKeys = compose(sort(subtract), map((p) => parseFloat(p)),keys); | |
const ORDERED_KEYS = buildOrderedKeys(SCORECAST_CONVERSIONS); | |
const scorer = { | |
price: { | |
num: 5, | |
den: 1 | |
}, | |
type: "home" | |
} | |
const correctScore = { | |
score: { | |
home: 1, | |
away: 0 | |
}, | |
price: { | |
num: 13, | |
den: 2 | |
}, | |
type: "home" | |
} | |
/** | |
* Check if the selection is actually possible | |
*/ | |
const validateSelectedOutcomes = (scorer, correctScore) => correctScore.score[scorer.type] > 0 ? | |
Either.Right( [scorer, correctScore] ) : | |
Either.Left("Invalid selection"); | |
/** | |
* Work out what the price is based on type of the outcomes and overall score | |
*/ | |
const isDraw = ([scorer, correctScore]) => correctScore.type === "draw"; | |
const scorerAndTeamMatch = ([ scorer, correctScore ]) => scorer.type === correctScore.type; | |
const calculatePriceWithScaleFactor = (factor) => compose(reduce(multiply, factor), map(prop('price'))) | |
const calculateTruePrice = cond( | |
[ | |
[isDraw, calculatePriceWithScaleFactor(0.8)], | |
[scorerAndTeamMatch, calculatePriceWithScaleFactor(0.6)], | |
[T, calculatePriceWithScaleFactor(1.25)] | |
] | |
); | |
/** | |
* Convert the outcomes' prices to decimals and scale appropriately | |
*/ | |
const convertToDecimal = over(lensProp('price'), compose(add(1), apply(divide), props([ 'num', 'den' ]))); | |
const buildPrice = compose(calculateTruePrice, map(convertToDecimal)); | |
/** | |
* Take the true price and identify the nearest neighbour from the original price map | |
*/ | |
const pickNearestPriceFromMap = (keys, map) => (decimalPrice) => prop(findLast((p) => parseFloat(p) < decimalPrice, keys), map) | |
const findNearestPrice = pickNearestPriceFromMap(ORDERED_KEYS, SCORECAST_CONVERSIONS); | |
/** | |
* Convert a string representation of a price to a Price object | |
*/ | |
const toPriceObject = compose(([num, den]) => ({num,den}), split('/')) | |
const getPrice = compose(toPriceObject, findNearestPrice, buildPrice) | |
const app = compose(map(getPrice), validateSelectedOutcomes) | |
// Price should be 25/1 | |
console.log('---'); | |
console.log(app(scorer, correctScore)); | |
console.log('---'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment