Skip to content

Commit

Permalink
feat: Copy grade contexts from openbeta-graphql
Browse files Browse the repository at this point in the history
https://github.com/OpenBeta/openbeta-graphql has grade contexts which
define subsets of grades to be used in different regions.  These need to
be used in the front end (open-tacos) too, so move them to sandbag.

Contexts need to know about disciplines, so move those over too.

Version copied from:
OpenBeta/openbeta-graphql@9b0b35b
  • Loading branch information
musoke committed Jul 18, 2023
1 parent 5870f1f commit 1c07b80
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 3 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
"engines": {
"node": ">=14"
},
"dependencies": {},
"dependencies": {
"i18n-iso-countries": "^7.6.0"
},
"keywords": [
"rock climbing",
"climbing grades"
Expand Down
63 changes: 63 additions & 0 deletions src/Disciplines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* What sort of climb is this? Routes can combine these fields, which is why
* this is not an enumeration.
* For example, a route may be a sport route, but also a top rope route.
*/
export interface DisciplineType {
/** https://en.wikipedia.org/wiki/Traditional_climbing */
trad?: boolean
/** https://en.wikipedia.org/wiki/Sport_climbing */
sport?: boolean
/** https://en.wikipedia.org/wiki/Bouldering */
bouldering?: boolean
/** https://en.wikipedia.org/wiki/Deep-water_soloing */
deepwatersolo?: boolean
/** https://en.wikipedia.org/wiki/Alpine_climbing */
alpine?: boolean
/** https://en.wikipedia.org/wiki/Ice_climbing */
snow?: boolean
/** https://en.wikipedia.org/wiki/Ice_climbing */
ice?: boolean
/** https://en.wikipedia.org/wiki/Mixed_climbing */
mixed?: boolean
/** https://en.wikipedia.org/wiki/Aid_climbing */
aid?: boolean
/** https://en.wikipedia.org/wiki/Top_rope_climbing */
tr?: boolean
}

export const validDisciplines = [
'trad',
'sport',
'bouldering',
'deepwatersolo',
'alpine',
'snow',
'ice',
'mixed',
'aid',
'tr'
]

/**
* Perform runtime validation of climb discipline object
* @param disciplineObj IClimbType
*/
export const sanitizeDisciplines = (disciplineObj: Partial<DisciplineType> | undefined): DisciplineType | undefined => {
if (disciplineObj == null) return undefined

const output = validDisciplines.reduce((acc, current) => {
if (disciplineObj?.[current] != null) {
acc[current] = disciplineObj[current]
} else {
acc[current] = false
}
return acc
}, {})
// @ts-expect-error
if (disciplineObj?.boulder != null) {
// @ts-expect-error
output.bouldering = disciplineObj.boulder
}
return output as DisciplineType
}
224 changes: 224 additions & 0 deletions src/GradeContexts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { getScale } from './GradeParser'
import { GradeScales, GradeScalesTypes } from './GradeScale'
import isoCountries from 'i18n-iso-countries'
// import { DisciplineType, ClimbGradeContextType } from './db/ClimbTypes.js'
import { DisciplineType } from './Disciplines'

/**
* Grade systems have minor variations between countries. gradeContext is a
* short abbreviated string that identifies the context in which the grade was assigned
* and should signify a regional or national variation that may be considered within
* grade comparisons.
*/
export enum GradeContexts {
/** Alaska (United States) */
ALSK = 'ALSK',
/** Australia */
AU = 'AU',
BRZ = 'BRZ',
FIN = 'FIN',
FR = 'FR',
HK = 'HK',
NWG = 'NWG',
POL = 'POL',
SA = 'SA',
/** Sweden */
SWE = 'SWE',
SX = 'SX',
UIAA = 'UIAA',
/** United Kingdom */
UK = 'UK',
/** United States of Ameria */
US = 'US'
}

export type ClimbGradeContextType = Record<keyof DisciplineType, GradeScalesTypes>

/**
* A conversion from grade context to corresponding grade type / scale
* Todo: move this to @openbeta/sandbag
*/
export const gradeContextToGradeScales: Partial<Record<GradeContexts, ClimbGradeContextType>> = {
[GradeContexts.AU]: {
trad: GradeScales.EWBANK,
sport: GradeScales.EWBANK,
bouldering: GradeScales.VSCALE,
tr: GradeScales.EWBANK,
deepwatersolo: GradeScales.EWBANK,
alpine: GradeScales.YDS,
mixed: GradeScales.YDS,
aid: GradeScales.AID,
snow: GradeScales.YDS, // is this the same as alpine?
ice: GradeScales.WI
},
[GradeContexts.US]: {
trad: GradeScales.YDS,
sport: GradeScales.YDS,
bouldering: GradeScales.VSCALE,
tr: GradeScales.YDS,
deepwatersolo: GradeScales.YDS,
alpine: GradeScales.YDS,
mixed: GradeScales.YDS,
aid: GradeScales.AID,
snow: GradeScales.YDS, // is this the same as alpine?
ice: GradeScales.WI
},
[GradeContexts.FR]: {
trad: GradeScales.FRENCH,
sport: GradeScales.FRENCH,
bouldering: GradeScales.FONT,
tr: GradeScales.FRENCH,
deepwatersolo: GradeScales.FRENCH,
alpine: GradeScales.FRENCH,
mixed: GradeScales.FRENCH,
aid: GradeScales.AID,
snow: GradeScales.FRENCH, // is this the same as alpine?
ice: GradeScales.WI
},
[GradeContexts.SA]: {
trad: GradeScales.FRENCH,
sport: GradeScales.FRENCH,
bouldering: GradeScales.FONT,
tr: GradeScales.FRENCH,
deepwatersolo: GradeScales.FRENCH,
alpine: GradeScales.FRENCH,
mixed: GradeScales.FRENCH,
aid: GradeScales.AID,
snow: GradeScales.FRENCH, // SA does not have a whole lot of snow
ice: GradeScales.WI
},
[GradeContexts.UIAA]: {
trad: GradeScales.UIAA,
sport: GradeScales.UIAA,
bouldering: GradeScales.FONT,
tr: GradeScales.UIAA,
deepwatersolo: GradeScales.FRENCH,
alpine: GradeScales.UIAA,
mixed: GradeScales.UIAA, // TODO: change to MI scale, once added
aid: GradeScales.UIAA,
snow: GradeScales.UIAA, // TODO: remove `snow` since it duplicates `ice`
ice: GradeScales.WI
}
}

/**
* Convert a human-readable grade to the appropriate grade object.
* @param gradeStr human-readable, eg: '5.9' or '5c'.
* @param disciplines the climb disciplines
* @param context grade context
* @returns grade object
*/
export const createGradeObject = (gradeStr: string, disciplines: DisciplineType | undefined, context: ClimbGradeContextType): Partial<Record<GradeScalesTypes, string>> | undefined => {
if (disciplines == null) return undefined
return Object.keys(disciplines).reduce<Partial<Record<GradeScalesTypes, string>> | undefined>((acc, curr) => {
if (disciplines[curr] === true) {
const scaleTxt = context[curr]
const scaleApi = getScale(scaleTxt)
if (scaleApi != null && !(scaleApi.getScore(gradeStr) < 0)) {
// only assign valid grade
if (acc == null) {
acc = {
[scaleTxt]: gradeStr
}
} else {
acc[scaleTxt] = gradeStr
}
}
}
return acc
}, undefined)
}

/**
* A record of all countries with a default grade context that is not US
*/
const COUNTRIES_DEFAULT_NON_US_GRADE_CONTEXT: Record<string, GradeContexts> = {
AND: GradeContexts.FR,
ATF: GradeContexts.FR,
AUS: GradeContexts.AU,
AUT: GradeContexts.UIAA,
AZE: GradeContexts.UIAA,
BEL: GradeContexts.FR,
BGR: GradeContexts.UIAA,
BIH: GradeContexts.FR,
BLR: GradeContexts.UIAA,
BRA: GradeContexts.BRZ,
BWA: GradeContexts.SA,
CHE: GradeContexts.FR,
CUB: GradeContexts.FR,
CZE: GradeContexts.UIAA,
DEU: GradeContexts.UIAA,
DNK: GradeContexts.UIAA,
EGY: GradeContexts.FR,
ESP: GradeContexts.FR,
EST: GradeContexts.FR,
FIN: GradeContexts.FIN,
FRA: GradeContexts.FR,
GBR: GradeContexts.UK,
GRC: GradeContexts.FR,
GUF: GradeContexts.FR,
HKG: GradeContexts.HK,
HRV: GradeContexts.FR,
HUN: GradeContexts.UIAA,
IOT: GradeContexts.UK,
IRL: GradeContexts.UK,
ITA: GradeContexts.FR,
JEY: GradeContexts.UK,
JOR: GradeContexts.FR,
KEN: GradeContexts.UK,
KGZ: GradeContexts.FR,
LAO: GradeContexts.FR,
LIE: GradeContexts.FR,
LSO: GradeContexts.SA,
LTU: GradeContexts.FR,
LUX: GradeContexts.FR,
LVA: GradeContexts.FR,
MAR: GradeContexts.FR,
MCO: GradeContexts.FR,
MDA: GradeContexts.FR,
MDG: GradeContexts.FR,
MKD: GradeContexts.FR,
MLT: GradeContexts.FR,
MNE: GradeContexts.UIAA,
MYS: GradeContexts.FR,
NAM: GradeContexts.SA,
NCL: GradeContexts.FR,
NLD: GradeContexts.FR,
NOR: GradeContexts.NWG,
NZL: GradeContexts.AU,
PER: GradeContexts.FR,
PNG: GradeContexts.AU,
POL: GradeContexts.POL,
PRT: GradeContexts.FR,
PYF: GradeContexts.FR,
ROU: GradeContexts.FR,
RUS: GradeContexts.FR,
SGP: GradeContexts.FR,
SRB: GradeContexts.FR,
SVK: GradeContexts.UIAA,
SVN: GradeContexts.FR,
SWE: GradeContexts.SWE,
THA: GradeContexts.FR,
TON: GradeContexts.AU,
TUN: GradeContexts.FR,
TUR: GradeContexts.FR,
UGA: GradeContexts.SA,
UKR: GradeContexts.FR,
VNM: GradeContexts.FR,
ZAF: GradeContexts.SA
}

/**
*
* @returns all countries with their default grade context
*/
export const getCountriesDefaultGradeContext = (): { [x: string]: GradeContexts } => {
const countries = { ...COUNTRIES_DEFAULT_NON_US_GRADE_CONTEXT }
for (const alpha3Code in isoCountries.getAlpha3Codes()) {
// Any country not found will have a US Grade Context
if (!(alpha3Code in countries)) {
countries[alpha3Code] = GradeContexts.US
}
}
return countries
}
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { GradeScales, GradeScalesTypes } from './GradeScale'
import {
getScale,
Expand All @@ -10,6 +9,8 @@ import {
import { GradeBands, GradeBandTypes } from './GradeBands'
import { AI, Aid, Ewbank, Font, French, Norwegian, Saxon, UIAA, VScale, WI, YosemiteDecimal } from './scales'

import { GradeContexts, gradeContextToGradeScales, getCountriesDefaultGradeContext } from './GradeContexts'

// Free Climbing Grades
// YDS
// French
Expand Down Expand Up @@ -313,3 +314,5 @@ export {
WI,
YosemiteDecimal
}

export { GradeContexts, gradeContextToGradeScales, getCountriesDefaultGradeContext }
14 changes: 13 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3263,6 +3263,11 @@ detect-newline@^3.0.0:
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==

[email protected]:
version "1.3.0"
resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1"
integrity sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==

diff-sequences@^25.2.6:
version "25.2.6"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
Expand Down Expand Up @@ -4638,6 +4643,13 @@ husky@^7.0.4:
resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535"
integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==

i18n-iso-countries@^7.6.0:
version "7.6.0"
resolved "https://registry.yarnpkg.com/i18n-iso-countries/-/i18n-iso-countries-7.6.0.tgz#4e2eac7043210a5552e7fd116b74d4f36a90b960"
integrity sha512-HPKjOUKS0BkjiY4ayrsuFbu7Ock++pXLs+FAOYl4WfTL5L0ploEH68fiRAP6Zev5g/jvMFt54KcPGJcb942wbg==
dependencies:
diacritics "1.3.0"

[email protected], iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
Expand Down Expand Up @@ -8374,7 +8386,7 @@ typescript@^3.7.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==

typescript@^4.5.5:
typescript@^4.9.5:
version "4.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
Expand Down

0 comments on commit 1c07b80

Please sign in to comment.