Skip to content

Commit

Permalink
feat(calcs): cat, fes, ias
Browse files Browse the repository at this point in the history
  • Loading branch information
nckhell committed Jan 8, 2025
1 parent 885ba46 commit 432debb
Show file tree
Hide file tree
Showing 35 changed files with 857 additions and 1,130 deletions.
File renamed without changes.
File renamed without changes.
135 changes: 135 additions & 0 deletions src/scores/cat/cat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { ZodError } from 'zod'
import { Score } from '../../classes'
import { ScoreLibrary } from '../library'
import {
best_response,
median_response,
random_response,
worst_response,
} from './__testdata__/cat_test_responses'
import { cat } from './cat'

const BEST_SCORE = 0
const MEDIAN_SCORE = 20
const WORST_SCORE = 40

const cat_calculation = new Score(cat)

describe('cat', function () {
it('cat calculation function should be available as a calculation', function () {
expect(ScoreLibrary).toHaveProperty('cat')
})

describe('basic assumptions', function () {
const outcome = cat_calculation.calculate({ payload: best_response })

it('should return 2 calculation results', function () {
expect(Object.keys(outcome).length).toEqual(2)
})

it('should have the expected calculation result ids', function () {
const EXPECTED_CALCULATION_ID = ['CAT_POINTS', 'CAT_HEALTH_IMPACT']

const configured_calculation_id = Object.keys(outcome)

expect(configured_calculation_id).toEqual(EXPECTED_CALCULATION_ID)
})
})

describe('validation', function () {
describe('the score includes the correct input fields', function () {
it('should have all the expected input ids configured', function () {
const EXPECTED_INPUT_IDS = [
'1_COUGH',
'2_PHLEGM',
'3_CHEST_TIGHTNESS',
'4_BREATHLESSNESS',
'5_ACTIVITIES',
'6_CONFIDENCE',
'7_SLEEP',
'8_ENERGY',
]

const configured_input_ids = Object.keys(cat_calculation.inputSchema)

expect(EXPECTED_INPUT_IDS).toEqual(configured_input_ids)
})
})

describe('when an answer is not not one of the allowed answers', function () {
it('should throw an ZodError', function () {
expect(() =>
cat_calculation.calculate({
payload: {
'1_COUGH': -1,
},
}),
).toThrow(ZodError)
})
})

describe('when called with an empty response', function () {
it('should throw an error', function () {
expect(() =>
cat_calculation.calculate({
payload: {},
}),
).toThrow(ZodError)
})
})
})

describe('score calculation', function () {
describe('when called with the best response', function () {
const outcome = cat_calculation.calculate({ payload: best_response })

it('should return the best score', function () {
expect(outcome.CAT_POINTS).toEqual(BEST_SCORE)
})

it('should return the best health impact', function () {
expect(outcome.CAT_HEALTH_IMPACT).toEqual('LOW')
})
})

describe('when called with the median response', function () {
const outcome = cat_calculation.calculate({ payload: median_response })

it('should return the worst score', function () {
expect(outcome.CAT_POINTS).toEqual(MEDIAN_SCORE)
})

it('should return the median health impact', function () {
expect(outcome.CAT_HEALTH_IMPACT).toEqual('MEDIUM')
})
})

describe('when called with the worst response', function () {
const outcome = cat_calculation.calculate({ payload: worst_response })

it('should return the worst score', function () {
expect(outcome.CAT_POINTS).toEqual(WORST_SCORE)
})

it('should return the worst health impact', function () {
expect(outcome.CAT_HEALTH_IMPACT).toEqual('VERY HIGH')
})
})

describe('when called with a random response', function () {
const outcome = cat_calculation.calculate({ payload: random_response })

it('should return the expected score', function () {
const EXPECTED_SCORE = 16

expect(outcome.CAT_POINTS).toEqual(EXPECTED_SCORE)
})

it('should return the expected health impact', function () {
const EXPECTED_HEALTH_IMPACT = 'MEDIUM'

expect(outcome.CAT_HEALTH_IMPACT).toEqual(EXPECTED_HEALTH_IMPACT)
})
})
})
})
18 changes: 18 additions & 0 deletions src/scores/cat/cat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ScoreType } from '../../types'
import { CAT_OUTPUT, CAT_INPUTS, CAT_INTERPRATION_TABLE } from './definition'
import { sum } from 'lodash'

export const cat: ScoreType<typeof CAT_INPUTS, typeof CAT_OUTPUT> = {
name: 'COPD Assessment Test (CAT)',
readmeLocation: __dirname,
inputSchema: CAT_INPUTS,
outputSchema: CAT_OUTPUT,
calculate: ({ data }) => {
const score = sum(Object.values(data))

return {
CAT_POINTS: score,
CAT_HEALTH_IMPACT: CAT_INTERPRATION_TABLE[score.toString()],
}
},
}
199 changes: 199 additions & 0 deletions src/scores/cat/definition/cat_inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { z } from 'zod'
import { ScoreInputSchemaType } from '../../../types'

export const CAT_INPUTS = {
'1_COUGH': {
label: { en: 'Cough' },
type: z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
z.literal(5),
]),
uiOptions: {
options: [
{
label: { en: 'I never cough' },
value: 0,
},
{
label: { en: 'I cough all the time' },
value: 5,
},
],
},
},
'2_PHLEGM': {
label: { en: 'Phlegm' },
type: z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
z.literal(5),
]),
uiOptions: {
options: [
{
label: { en: 'I have no phlegm in my chest at all' },
value: 0,
},
{
label: { en: 'My chest is completely full of pleghm' },
value: 5,
},
],
},
},
'3_CHEST_TIGHTNESS': {
label: { en: 'Chest tightness' },
type: z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
z.literal(5),
]),
uiOptions: {
options: [
{
label: { en: 'My chest does not feel tight at all' },
value: 0,
},
{
label: { en: 'My chest feels very tight' },
value: 5,
},
],
},
},
'4_BREATHLESSNESS': {
label: { en: 'Breathlessness' },
type: z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
z.literal(5),
]),
uiOptions: {
options: [
{
label: {
en: 'When I walk up a hill or one flight of stairs I am not breathless',
},
value: 0,
},
{
label: {
en: 'When I walk up a hill or one flight of stairs I am very breathless',
},
value: 5,
},
],
},
},
'5_ACTIVITIES': {
label: { en: 'Activities' },
type: z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
z.literal(5),
]),
uiOptions: {
options: [
{
label: { en: 'I am not limited doing any activities at home' },
value: 0,
},
{
label: { en: 'I am very limited doing any activities at home' },
value: 5,
},
],
},
},
'6_CONFIDENCE': {
label: {
en: 'Confidence',
},
type: z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
z.literal(5),
]),
uiOptions: {
options: [
{
label: {
en: 'I am confident leaving my home despite my lung condition',
},
value: 0,
},
{
label: {
en: 'I am not at all confident leaving my home because of my lung condition',
},
value: 5,
},
],
},
},
'7_SLEEP': {
label: { en: 'Sleep' },
type: z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
z.literal(5),
]),
uiOptions: {
options: [
{
label: { en: 'I sleep soundly' },
value: 0,
},
{
label: { en: 'I don’t sleep soundly because of my lung condition' },
value: 5,
},
],
},
},
'8_ENERGY': {
label: { en: 'Energy' },
type: z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
z.literal(5),
]),
uiOptions: {
options: [
{
label: { en: 'I have lots of energy' },
value: 0,
},
{
label: { en: 'I have no energy at all' },
value: 5,
},
],
},
},
} satisfies ScoreInputSchemaType
File renamed without changes.
13 changes: 13 additions & 0 deletions src/scores/cat/definition/cat_output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { z } from 'zod'
import { ScoreOutputSchemaType } from '../../../types'

export const CAT_OUTPUT = {
CAT_POINTS: {
label: { en: 'CAT points' },
type: z.number(),
},
CAT_HEALTH_IMPACT: {
label: { en: 'Health impact' },
type: z.string(),
},
} satisfies ScoreOutputSchemaType
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 432debb

Please sign in to comment.