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) O3-4083 : Standardize lab result ranges: display absolute min/max values to match validation rules #2161

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,34 @@ interface ResultFormFieldProps {
const ResultFormField: React.FC<ResultFormFieldProps> = ({ concept, control, defaultValue, errors }) => {
const { t } = useTranslation();

const printValueRange = (concept: LabOrderConcept) => {
if (concept?.datatype?.display === 'Numeric') {
const maxVal = Math.max(concept?.hiAbsolute, concept?.hiCritical, concept?.hiNormal);
const minVal = Math.min(concept?.lowAbsolute, concept?.lowCritical, concept?.lowNormal);
return ` (${minVal ?? 0} - ${maxVal > 0 ? maxVal : '--'} ${concept?.units ?? ''})`;
// TODO: Reference ranges should be dynamically adjusted based on patient demographics:
// - Age-specific ranges (e.g., pediatric vs adult values)
// - Gender-specific ranges where applicable
const formatLabRange = (concept: LabOrderConcept) => {
const {
datatype: { hl7Abbreviation },
} = concept ?? {};
Comment on lines +22 to +24
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const {
datatype: { hl7Abbreviation },
} = concept ?? {};
const hl7Abbreviation = concept?.datatype?.hl7Abbreviation;

The current destructuring could throw an error if datatype is null/undefined, even with the nullish coalescing operator.

if (hl7Abbreviation !== 'NM') return '';

const { hiAbsolute, lowAbsolute, units } = concept;
const displayUnit = units ? ` ${units}` : '';

const hasLowerLimit = lowAbsolute !== null && lowAbsolute !== undefined;
const hasUpperLimit = hiAbsolute !== null && hiAbsolute !== undefined;
Comment on lines +30 to +31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const hasLowerLimit = lowAbsolute !== null && lowAbsolute !== undefined;
const hasUpperLimit = hiAbsolute !== null && hiAbsolute !== undefined;
const hasLowerLimit = !!lowAbsolute;
const hasUpperLimit = !!hiAbsolute;


if (hasLowerLimit && hasUpperLimit) {
return ` (${lowAbsolute} - ${hiAbsolute}) ${displayUnit}`;
}

if (hasUpperLimit) {
return ` (<= ${hiAbsolute}) ${displayUnit}`;
}

if (hasLowerLimit) {
return ` (>= ${lowAbsolute}) ${displayUnit}`;
}
Comment on lines +33 to 43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While not functionally different, this makes the logic a little clearer:

Suggested change
if (hasLowerLimit && hasUpperLimit) {
return ` (${lowAbsolute} - ${hiAbsolute}) ${displayUnit}`;
}
if (hasUpperLimit) {
return ` (<= ${hiAbsolute}) ${displayUnit}`;
}
if (hasLowerLimit) {
return ` (>= ${lowAbsolute}) ${displayUnit}`;
}
if (hasLowerLimit && hasUpperLimit) {
return ` (${lowAbsolute} - ${hiAbsolute}) ${displayUnit}`;
} else if (hasUpperLimit) {
return ` (<= ${hiAbsolute}) ${displayUnit}`;
} else if (hasLowerLimit) {
return ` (>= ${lowAbsolute}) ${displayUnit}`;
}

return '';

return ` (${displayUnit})`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return ` (${displayUnit})`;
return units ? ` (${displayUnit})` : '';

So we don't return a string with parentheses and spaces when there are no ranges and no units.

};

const getSavedMemberValue = (conceptUuid: string, dataType: string) => {
Expand Down Expand Up @@ -63,7 +84,7 @@ const ResultFormField: React.FC<ResultFormFieldProps> = ({ concept, control, def
hideSteppers
id={concept.uuid}
key={concept.uuid}
label={concept?.display + printValueRange(concept)}
label={concept?.display + formatLabRange(concept)}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue created by this PR, but I don't think this works the way we'd want if concept is actually null or undefined.

Suggested change
label={concept?.display + formatLabRange(concept)}
label={`${concept?.display ? concept.display + ' ' : ''}${formatLabRange(concept)}`}

onChange={(event) => field.onChange(parseFloat(event.target.value))}
value={field.value || ''}
invalidText={errors[concept.uuid]?.message}
Expand Down Expand Up @@ -133,7 +154,7 @@ const ResultFormField: React.FC<ResultFormFieldProps> = ({ concept, control, def
hideSteppers
id={`number-${member.uuid}`}
key={member.uuid}
label={member?.display + printValueRange(member)}
label={member?.display + formatLabRange(member)}
onChange={(event) => field.onChange(parseFloat(event.target.value))}
value={field.value || ''}
invalidText={errors[member.uuid]?.message}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ describe('LabResultsForm', () => {
const user = userEvent.setup();
render(<LabResultsForm {...testProps} />);

const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
const input = await screen.findByLabelText(`Test Concept (0 - 100) mg/dL`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See I actually think the original text here is better than this version, since the numbers should be grouped with the units.

await user.type(input, '150');

const saveButton = screen.getByRole('button', { name: /Save and close/i });
Expand Down Expand Up @@ -135,7 +135,9 @@ describe('LabResultsForm', () => {
});
render(<LabResultsForm {...testProps} />);

const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
const input = screen.getByRole('spinbutton', {
name: /Test Concept/i,
});
await user.type(input, '50.5');

const saveButton = screen.getByRole('button', { name: /Save and close/i });
Expand All @@ -150,7 +152,7 @@ describe('LabResultsForm', () => {
const user = userEvent.setup();
render(<LabResultsForm {...testProps} />);

const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
const input = await screen.findByLabelText(`Test Concept (0 - 100) mg/dL`);
await user.type(input, '50.5');

const saveButton = screen.getByRole('button', { name: /Save and close/i });
Expand All @@ -166,7 +168,7 @@ describe('LabResultsForm', () => {
const user = userEvent.setup();
render(<LabResultsForm {...testProps} />);

const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
const input = await screen.findByLabelText(`Test Concept (0 - 100) mg/dL`);
await user.type(input, '-50');

const saveButton = screen.getByRole('button', { name: /Save and close/i });
Expand All @@ -181,7 +183,7 @@ describe('LabResultsForm', () => {
const user = userEvent.setup();
render(<LabResultsForm {...testProps} />);

const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
const input = await screen.findByLabelText('Test Concept (0 - 100) mg/dL');
await user.type(input, '0');

await waitFor(() => {
Expand Down Expand Up @@ -212,7 +214,9 @@ describe('LabResultsForm', () => {
});
render(<LabResultsForm {...testProps} />);

const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
const input = screen.getByRole('spinbutton', {
name: /Test Concept/i,
});
await user.type(input, '150');

const saveButton = screen.getByRole('button', { name: /Save and close/i });
Expand Down Expand Up @@ -246,7 +250,9 @@ describe('LabResultsForm', () => {
});
render(<LabResultsForm {...testProps} />);

const input = await screen.findByLabelText(`Test Concept (0 - -- mg/dL)`);
const input = screen.getByRole('spinbutton', {
name: /Test Concept/i,
});
await user.type(input, '-50');

const saveButton = screen.getByRole('button', { name: /Save and close/i });
Expand All @@ -270,7 +276,7 @@ describe('LabResultsForm', () => {
/>,
);

const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
const input = await screen.findByLabelText(`Test Concept (0 - 100) mg/dL`);
await user.type(input, '50');

const saveButton = screen.getByRole('button', { name: /Save and close/i });
Expand Down Expand Up @@ -332,11 +338,11 @@ describe('LabResultsForm', () => {
render(<LabResultsForm {...testProps} />);

// Normal input range
const setMember1Input = screen.getByLabelText('Set Member (50 - 150 mg/dL)');
const setMember1Input = screen.getByLabelText('Set Member (50 - 150) mg/dL');
await user.type(setMember1Input, '50');
expect(screen.queryByText('Set Member must be between 50 and 150')).not.toBeInTheDocument();

const setMember2Input = screen.getByLabelText('Set Member 2 (5 - 30 mg/dL)');
const setMember2Input = screen.getByLabelText('Set Member 2 (5 - 30) mg/dL');
await user.type(setMember2Input, '10');
expect(screen.queryByText('Set Member must be between 5 and 30')).not.toBeInTheDocument();

Expand Down Expand Up @@ -453,7 +459,7 @@ describe('LabResultsForm', () => {
});

render(<LabResultsForm {...testProps} />);
const concept1Input = await screen.findByLabelText('Set Member 1 (0 - 100 mg/dL)');
const concept1Input = await screen.findByLabelText('Set Member 1 (0 - 100) mg/dL');
await user.type(concept1Input, '100');

const submitButton = screen.getByRole('button', { name: 'Save and close' });
Expand Down Expand Up @@ -557,10 +563,10 @@ describe('LabResultsForm', () => {
});

render(<LabResultsForm {...testProps} />);
const concept1Input = await screen.findByLabelText('Set Member 1 (0 - 100 mg/dL)');
const concept1Input = await screen.findByLabelText('Set Member 1 (0 - 100) mg/dL');
await user.type(concept1Input, '100');

const concept2Input = await screen.findByLabelText('Set Member 2 (0 - 80 mmol/L)');
const concept2Input = await screen.findByLabelText('Set Member 2 (0 - 80) mmol/L');
await user.type(concept2Input, '60');

const submitButton = screen.getByRole('button', { name: 'Save and close' });
Expand Down Expand Up @@ -638,7 +644,7 @@ describe('LabResultsForm', () => {
const user = userEvent.setup();
render(<LabResultsForm {...testProps} />);

const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
const input = await screen.findByLabelText(`Test Concept (0 - 100) mg/dL`);
await user.type(input, '150');

const saveButton = screen.getByRole('button', { name: /Save and close/i });
Expand Down
Loading