Skip to content

Commit

Permalink
Push up hardcoding
Browse files Browse the repository at this point in the history
Refs #1712
  • Loading branch information
thewilkybarkid committed May 17, 2024
1 parent 1af1472 commit ce5fc83
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 19 deletions.
8 changes: 8 additions & 0 deletions src/openalex/ids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as O from 'fp-ts/Option'
import { flow } from 'fp-ts/function'
import { type FieldId, isFieldId } from '../types/field'

export const fieldIdFromOpenAlexId: (value: URL) => O.Option<FieldId> = flow(
O.fromNullableK(value => (/^https:\/\/openalex\.org\/fields\/(.+)$/.exec(value.href) ?? [])[1]),
O.filter(isFieldId),
)
24 changes: 5 additions & 19 deletions src/openalex/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
import type { Doi } from 'doi-ts'
import * as RA from 'fp-ts/ReadonlyArray'
import * as TE from 'fp-ts/TaskEither'
import { match } from 'ts-pattern'
import type { FieldId } from '../types/field'
import { flow } from 'fp-ts/function'
import { fieldIdFromOpenAlexId } from './ids'
import { getFields, getWorkByDoi } from './work'

export const getFieldsFromOpenAlex = (doi: Doi) =>
TE.right(
match(doi.toLowerCase())
.returnType<ReadonlyArray<FieldId>>()
.with('10.1101/2023.06.12.544578', () => ['13', '24'])
.with('10.1101/2024.04.19.590338', () => ['13', '24'])
.with('10.1101/2024.05.02.592279', () => ['27'])
.with('10.1101/2024.05.05.592045', () => ['13'])
.with('10.1101/2024.05.06.592752', () => ['27', '28'])
.with('10.1101/2024.05.11.592705', () => ['22', '21'])
.with('10.1590/scielopreprints.5909', () => ['12'])
.with('10.1590/scielopreprints.7901', () => ['12'])
.with('10.1590/scielopreprints.8479', () => ['36', '33'])
.with('10.1590/scielopreprints.8828', () => ['12'])
.otherwise(() => []),
)
export const getFieldsFromOpenAlex = flow(getWorkByDoi, TE.map(flow(getFields, RA.filterMap(fieldIdFromOpenAlexId))))
132 changes: 132 additions & 0 deletions src/openalex/work.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import type { Doi } from 'doi-ts'
import * as E from 'fp-ts/Either'
import * as Eq from 'fp-ts/Eq'
import * as RA from 'fp-ts/ReadonlyArray'
import * as TE from 'fp-ts/TaskEither'
import { flow, pipe } from 'fp-ts/function'
import * as s from 'fp-ts/string'
import * as C from 'io-ts/Codec'
import * as D from 'io-ts/Decoder'
import { match } from 'ts-pattern'

export type Work = C.TypeOf<typeof WorkC>

const UrlC = C.make(
pipe(
D.string,
D.parse(s =>
E.tryCatch(
() => new URL(s),
() => D.error(s, 'URL'),
),
),
),
{ encode: url => url.href },
)

export const WorkC = C.struct({
topics: C.array(
C.struct({
field: C.struct({ id: UrlC }),
}),
),
})

export const getWorkByDoi: (doi: Doi) => TE.TaskEither<'unavailable', Work> = flow(
doi =>
TE.fromEither(
match(doi.toLowerCase())
.with('10.1101/2023.06.12.544578', () =>
WorkC.decode({
topics: [
{ field: { id: 'https://openalex.org/fields/13' } },
{ field: { id: 'https://openalex.org/fields/24' } },
{ field: { id: 'https://openalex.org/fields/24' } },
],
}),
)
.with('10.1101/2024.04.19.590338', () =>
WorkC.decode({
topics: [
{ field: { id: 'https://openalex.org/fields/13' } },
{ field: { id: 'https://openalex.org/fields/24' } },
{ field: { id: 'https://openalex.org/fields/13' } },
],
}),
)
.with('10.1101/2024.05.02.592279', () =>
WorkC.decode({
topics: [{ field: { id: 'https://openalex.org/fields/27' } }],
}),
)
.with('10.1101/2024.05.05.592045', () =>
WorkC.decode({
topics: [
{ field: { id: 'https://openalex.org/fields/13' } },
{ field: { id: 'https://openalex.org/fields/13' } },
],
}),
)
.with('10.1101/2024.05.06.592752', () =>
WorkC.decode({
topics: [
{ field: { id: 'https://openalex.org/fields/27' } },
{ field: { id: 'https://openalex.org/fields/28' } },
{ field: { id: 'https://openalex.org/fields/28' } },
],
}),
)
.with('10.1101/2024.05.11.592705', () =>
WorkC.decode({
topics: [
{ field: { id: 'https://openalex.org/fields/22' } },
{ field: { id: 'https://openalex.org/fields/21' } },
{ field: { id: 'https://openalex.org/fields/22' } },
],
}),
)
.with('10.1590/scielopreprints.5909', () =>
WorkC.decode({
topics: [
{ field: { id: 'https://openalex.org/fields/12' } },
{ field: { id: 'https://openalex.org/fields/12' } },
],
}),
)
.with('10.1590/scielopreprints.7901', () =>
WorkC.decode({
topics: [
{ field: { id: 'https://openalex.org/fields/12' } },
{ field: { id: 'https://openalex.org/fields/12' } },
{ field: { id: 'https://openalex.org/fields/12' } },
],
}),
)
.with('10.1590/scielopreprints.8479', () =>
WorkC.decode({
topics: [
{ field: { id: 'https://openalex.org/fields/36' } },
{ field: { id: 'https://openalex.org/fields/33' } },
],
}),
)
.with('10.1590/scielopreprints.8828', () =>
WorkC.decode({
topics: [{ field: { id: 'https://openalex.org/fields/12' } }],
}),
)
.otherwise(() => WorkC.decode({ topics: [] })),
),
TE.mapLeft(() => 'unavailable' as const),
)

const eqUrl: Eq.Eq<URL> = pipe(
s.Eq,
Eq.contramap(url => url.href),
)

export const getFields: (work: Work) => ReadonlyArray<URL> = flow(
work => work.topics,
RA.map(topic => topic.field.id),
RA.uniq(eqUrl),
)
4 changes: 4 additions & 0 deletions src/types/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export function getFieldName(id: FieldId): string {
return fieldNames[id]
}

export function isFieldId(value: unknown): value is FieldId {
return typeof value === 'string' && (fieldIds as ReadonlyArray<string>).includes(value)
}

const fieldNames = {
'11': 'Agricultural and Biological Sciences',
'12': 'Arts and Humanities',
Expand Down
9 changes: 9 additions & 0 deletions test/openalex/fc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Work } from '../../src/openalex/work'
import * as fc from '../fc'

export * from '../fc'

export const work = ({ topics }: { topics?: fc.Arbitrary<Work['topics']> } = {}): fc.Arbitrary<Work> =>
fc.record({
topics: topics ?? fc.array(fc.record({ field: fc.record({ id: fc.url() }) })),
})
32 changes: 32 additions & 0 deletions test/openalex/ids.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { test } from '@fast-check/jest'
import { describe, expect } from '@jest/globals'
import * as O from 'fp-ts/Option'
import { not } from 'fp-ts/Predicate'
import * as _ from '../../src/openalex/ids'
import { isFieldId } from '../../src/types/field'
import * as fc from './fc'

describe('fieldIdFromOpenAlexId', () => {
test.prop([fc.fieldId().map(fieldId => [new URL(`https://openalex.org/fields/${fieldId}`), fieldId] as const)])(
'when it is a valid field ID',
([input, expected]) => {
const actual = _.fieldIdFromOpenAlexId(input)

expect(actual).toStrictEqual(O.some(expected))
},
)

test.prop([
fc.oneof(
fc
.string()
.filter(not(isFieldId))
.map(fieldId => new URL(`https://openalex.org/fields/${fieldId}`)),
fc.url().filter(not(url => url.href.startsWith('https://openalex.org/fields/'))),
),
])('when it is not a valid field ID', input => {
const actual = _.fieldIdFromOpenAlexId(input)

expect(actual).toStrictEqual(O.none)
})
})
21 changes: 21 additions & 0 deletions test/openalex/work.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { test } from '@fast-check/jest'
import { describe, expect } from '@jest/globals'
import * as _ from '../../src/openalex/work'
import * as fc from './fc'

describe('getFields', () => {
test.prop([
fc.tuple(fc.url(), fc.url()).chain(urls =>
fc.tuple(
fc.work({
topics: fc.constant([{ field: { id: urls[0] } }, { field: { id: urls[1] } }, { field: { id: urls[0] } }]),
}),
fc.constant(urls),
),
),
])('removes duplicates', ([work, expected]) => {
const actual = _.getFields(work)

expect(actual).toStrictEqual(expected)
})
})

0 comments on commit ce5fc83

Please sign in to comment.