Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Copy grade contexts from openbeta-graphql #134

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
223 changes: 223 additions & 0 deletions src/GradeContexts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import { getScale } from './GradeParser'
import { GradeScales, GradeScalesTypes } from './GradeScale'
import isoCountries from 'i18n-iso-countries'
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