Skip to content

Commit

Permalink
fix: slash grades should have integer scores (#138)
Browse files Browse the repository at this point in the history
Non-letter'd high yds grades cannot be converted to scores and back to grades as their scores are not integers.

fix:
- return only integers from getScore in both YDS and French scales
- round ends of score range in opposite directions in `getGrade`
- apply same rounding logic to other grade scales

Fixes #137
  • Loading branch information
johncalvinroberts authored Aug 1, 2023
1 parent 5efa2ee commit ab39cd9
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 26 deletions.
12 changes: 12 additions & 0 deletions src/GradeScale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ export const findScoreRange = (compareFn, list): number | Tuple => {
}
return [low, high]
}

export function getAvgScore (score: number | Tuple): number {
return typeof score === 'number' ? score : (score[1] + score[0]) / 2
}

/**
* For getting a whole number/integer tuple of a grade which resides between two adjacent grades.
* Returns an integer tuple, rounded EXCLUSIVELY of the adjacent grade scores.
* Related discussion: https://github.com/OpenBeta/sandbag/issues/137
*/
export const getRoundedScoreTuple = (gradeAverage: number, nextGradeAverage: number): Tuple => {
const low = Math.ceil(Math.min(gradeAverage, nextGradeAverage))
const high = Math.floor(Math.max(gradeAverage, nextGradeAverage))
return [low, high]
}
4 changes: 2 additions & 2 deletions src/__tests__/readme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
describe('Convert grades to scores', () => {
test('returns the correct score', () => {
expect(French.getScore('8a')).toEqual([84, 85])
expect(French.getScore('7c+/8a')).toEqual([82.5, 84.5])
expect(YosemiteDecimal.getScore('5.12+')).toEqual([78.5, 80.5])
expect(French.getScore('7c+/8a')).toEqual([83, 84])
expect(YosemiteDecimal.getScore('5.12+')).toEqual([79, 80])
})
})

Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/scales/norwegian.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ describe('Norwegian', () => {
})

test('Slash grade provided', () => {
expect(Norwegian.getScore('4-/4')).toStrictEqual([30.5, 33.5])
expect(Norwegian.getScore('5/5+')).toStrictEqual([45.5, 51.5])
expect(Norwegian.getScore('7+/8-')).toStrictEqual([72.5, 75.5])
expect(Norwegian.getScore('7+/7')).toStrictEqual([72.5, 75.5])
expect(Norwegian.getScore('4-/4')).toStrictEqual([31, 33])
expect(Norwegian.getScore('5/5+')).toStrictEqual([46, 51])
expect(Norwegian.getScore('7+/8-')).toStrictEqual([73, 75])
expect(Norwegian.getScore('7+/7')).toStrictEqual([73, 75])
})
})

Expand Down
40 changes: 36 additions & 4 deletions src/__tests__/scales/yds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('YosemiteDecimal', () => {
const highGrade = YosemiteDecimal.getScore('5.12a')
expect(highGrade[0]).toBeGreaterThan(lowGrade[1])
})

describe('invalid grade format', () => {
jest.spyOn(console, 'warn').mockImplementation()
beforeEach(() => {
Expand Down Expand Up @@ -62,6 +63,10 @@ describe('YosemiteDecimal', () => {
expect(YosemiteDecimal.isType('5.11B')).toBeTruthy()
})

test('grade above 5.9 with no letter', () => {
expect(YosemiteDecimal.isType('5.11')).toBeTruthy()
})

test('grade plus modifier is accepted', () => {
expect(YosemiteDecimal.isType('5.12+')).toBeTruthy()
expect(YosemiteDecimal.isType('5.12-')).toBeTruthy()
Expand Down Expand Up @@ -124,14 +129,41 @@ describe('YosemiteDecimal', () => {

describe('Slash scores', () => {
test('scores of slash grades should be subset of neighboring scores', () => {
const slashScore = YosemiteDecimal.getScore('5.10a/b')
const lowScore = YosemiteDecimal.getScore('5.10a')
const highScore = YosemiteDecimal.getScore('5.10b')

const slashScore = YosemiteDecimal.getScore('5.11a/b')
const lowScore = YosemiteDecimal.getScore('5.11a')
const highScore = YosemiteDecimal.getScore('5.11b')
expect(slashScore[0]).toBeGreaterThan(lowScore[0])
expect(slashScore[1]).toBeGreaterThan(lowScore[1])
expect(slashScore[0]).toBeLessThan(highScore[0])
expect(slashScore[1]).toBeLessThan(highScore[1])
})
})
})

// see related discussion regarding handling of non-letter'd grades: https://github.com/OpenBeta/sandbag/issues/137
describe('grades above 5.9 with no letter', () => {
test('getScore returns an integer value', () => {
const score = YosemiteDecimal.getScore('5.11')
expect(Number.isInteger(score[0])).toBeTruthy()
expect(Number.isInteger(score[1])).toBeTruthy()
expect(score[1]).toBeGreaterThan(score[0])
})
test('5.11 > 5.11a', () => {
const lowGrade = YosemiteDecimal.getScore('5.11a')
const highGrade = YosemiteDecimal.getScore('5.11')
expect(highGrade[0]).toBeGreaterThan(lowGrade[1])
})
test('5.11d > 5.11', () => {
const lowGrade = YosemiteDecimal.getScore('5.11')
const highGrade = YosemiteDecimal.getScore('5.11d')
expect(highGrade[0]).toBeGreaterThan(lowGrade[1])
})
test('getScore and getGrade agree on value of grade', () => {
const grade = '5.11'
const score = YosemiteDecimal.getScore(grade)
const reParsedGrade = YosemiteDecimal.getGrade(score)
const reScoredParsedGrade = YosemiteDecimal.getScore(reParsedGrade)
expect(reParsedGrade).toEqual('5.11b/c')
expect(reScoredParsedGrade).toEqual(score)
})
})
7 changes: 5 additions & 2 deletions src/scales/ewbank.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple } from '../GradeScale'
import GradeScale, { findScoreRange, getAvgScore, GradeScales, getRoundedScoreTuple, Tuple } from '../GradeScale'
import routes from '../data/routes.json'
import { Route } from '.'
import { GradeBandTypes, routeScoreToBand } from '../GradeBands'
Expand Down Expand Up @@ -69,7 +69,10 @@ const getScore = (grade: string): number | Tuple => {
(r: Route) => r.ewbank === routes[otherGrade].ewbank,
routes
)
return [getAvgScore(basicScore), getAvgScore(nextGrade)].sort((a, b) => a - b) as Tuple
const basicAvg = getAvgScore(basicScore)
const nextGradeAvg = getAvgScore(nextGrade)
const tuple = getRoundedScoreTuple(basicAvg, nextGradeAvg)
return tuple
}
}
return basicScore
Expand Down
7 changes: 5 additions & 2 deletions src/scales/font.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import boulder from '../data/boulder.json'
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple } from '../GradeScale'
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple, getRoundedScoreTuple } from '../GradeScale'

import { Boulder } from '.'
import { boulderScoreToBand, GradeBandTypes } from '../GradeBands'
Expand Down Expand Up @@ -67,7 +67,10 @@ const getScore = (grade: string): number | Tuple => {
(r: Boulder) => r.font.toLowerCase() === boulder[otherGrade].font.toLowerCase(),
boulder
)
return [getAvgScore(basicScore), getAvgScore(nextGrade)].sort((a, b) => a - b) as Tuple
const basicAvg = getAvgScore(basicScore)
const nextGradeAvg = getAvgScore(nextGrade)
const tuple = getRoundedScoreTuple(basicAvg, nextGradeAvg)
return tuple
}
}
return basicScore
Expand Down
7 changes: 5 additions & 2 deletions src/scales/french.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple } from '../GradeScale'
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple, getRoundedScoreTuple } from '../GradeScale'
import routes from '../data/routes.json'
import { Route } from '.'
import { GradeBandTypes, routeScoreToBand } from '../GradeBands'
Expand Down Expand Up @@ -66,7 +66,10 @@ const getScore = (grade: string): number | Tuple => {
(r: Route) => r.french.toLowerCase() === routes[otherGrade].french.toLowerCase(),
routes
)
return [getAvgScore(basicScore), getAvgScore(nextGrade)].sort((a, b) => a - b) as Tuple
const basicAvg = getAvgScore(basicScore)
const nextGradeAvg = getAvgScore(nextGrade)
const tuple = getRoundedScoreTuple(basicAvg, nextGradeAvg)
return tuple
}
}
return basicScore
Expand Down
7 changes: 5 additions & 2 deletions src/scales/norwegian.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple } from '../GradeScale'
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple, getRoundedScoreTuple } from '../GradeScale'
import routes from '../data/routes.json'
import { Route } from '.'
import { GradeBandTypes, routeScoreToBand } from '../GradeBands'
Expand Down Expand Up @@ -66,7 +66,10 @@ const getScore = (grade: string): number | Tuple => {
(r: Route) => r.norwegian === routes[otherGrade].norwegian,
routes
)
return [getAvgScore(basicScore), getAvgScore(nextGrade)].sort((a, b) => a - b) as Tuple
const basicAvg = getAvgScore(basicScore)
const nextGradeAvg = getAvgScore(nextGrade)
const tuple = getRoundedScoreTuple(basicAvg, nextGradeAvg)
return tuple
}
}
return basicScore
Expand Down
7 changes: 5 additions & 2 deletions src/scales/saxon.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple } from '../GradeScale'
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple, getRoundedScoreTuple } from '../GradeScale'
import routes from '../data/routes.json'
import { Route } from '.'
import { GradeBandTypes, routeScoreToBand } from '../GradeBands'
Expand Down Expand Up @@ -66,7 +66,10 @@ const getScore = (grade: string): number | Tuple => {
(r: Route) => r.saxon.toLowerCase() === routes[otherGrade].saxon.toLowerCase(),
routes
)
return [getAvgScore(basicScore), getAvgScore(nextGrade)].sort((a, b) => a - b) as Tuple
const basicAvg = getAvgScore(basicScore)
const nextGradeAvg = getAvgScore(nextGrade)
const tuple = getRoundedScoreTuple(basicAvg, nextGradeAvg)
return tuple
}
}
return basicScore
Expand Down
7 changes: 5 additions & 2 deletions src/scales/uiaa.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple } from '../GradeScale'
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple, getRoundedScoreTuple } from '../GradeScale'
import routes from '../data/routes.json'
import { Route } from '.'
import { GradeBandTypes, routeScoreToBand } from '../GradeBands'
Expand Down Expand Up @@ -66,7 +66,10 @@ const getScore = (grade: string): number | Tuple => {
(r: Route) => r.uiaa.toLowerCase() === routes[otherGrade].uiaa.toLowerCase(),
routes
)
return [getAvgScore(basicScore), getAvgScore(nextGrade)].sort((a, b) => a - b) as Tuple
const basicAvg = getAvgScore(basicScore)
const nextGradeAvg = getAvgScore(nextGrade)
const tuple = getRoundedScoreTuple(basicAvg, nextGradeAvg)
return tuple
}
}
return basicScore
Expand Down
7 changes: 5 additions & 2 deletions src/scales/v.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple } from '../GradeScale'
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple, getRoundedScoreTuple } from '../GradeScale'
import boulder from '../data/boulder.json'
import { Boulder } from '.'
import { boulderScoreToBand, GradeBandTypes } from '../GradeBands'
Expand Down Expand Up @@ -99,7 +99,10 @@ const getScore = (grade: string): number | Tuple => {
(r: Boulder) => r.v.toLowerCase() === boulder[otherGrade].v.toLowerCase(),
boulder
)
return [getAvgScore(basicScore), getAvgScore(nextGrade)].sort((a, b) => a - b) as Tuple
const basicAvg = getAvgScore(basicScore)
const nextGradeAvg = getAvgScore(nextGrade)
const tuple = getRoundedScoreTuple(basicAvg, nextGradeAvg)
return tuple
}
}
return basicScore
Expand Down
7 changes: 5 additions & 2 deletions src/scales/yds.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple } from '../GradeScale'
import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple, getRoundedScoreTuple } from '../GradeScale'
import routes from '../data/routes.json'
import { Route } from '.'
import { GradeBandTypes, routeScoreToBand } from '../GradeBands'
Expand Down Expand Up @@ -96,7 +96,10 @@ const getScore = (grade: string): number | Tuple => {
const nextGrade = findScoreRange((r: Route) => {
return r.yds.toLowerCase() === routes[Math.max(otherScore, 0)].yds.toLowerCase()
}, routes)
return [getAvgScore(basicScore), getAvgScore(nextGrade)].sort((a, b) => a - b) as Tuple
const basicAvg = getAvgScore(basicScore)
const nextGradeAvg = getAvgScore(nextGrade)
const tuple = getRoundedScoreTuple(basicAvg, nextGradeAvg)
return tuple
}
}
return basicScore
Expand Down

0 comments on commit ab39cd9

Please sign in to comment.