Parse lab values that include a range (#452)
This commit is contained in:
parent
bcffbb4769
commit
1bcf4aaf7e
|
@ -15,7 +15,7 @@ const defaultChartEntryHeight = 30;
|
|||
styleUrls: ['./observation-bar-chart.component.scss']
|
||||
})
|
||||
export class ObservationBarChartComponent implements OnInit {
|
||||
@Input() observations: [ObservationModel]
|
||||
@Input() observations: ObservationModel[]
|
||||
|
||||
chartHeight = defaultChartEntryHeight;
|
||||
|
||||
|
@ -122,14 +122,21 @@ export class ObservationBarChartComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
let currentValues: number[] = []
|
||||
let currentValues = []
|
||||
let referenceRanges = []
|
||||
|
||||
for(let observation of this.observations) {
|
||||
let refRange = observation.reference_range;
|
||||
|
||||
referenceRanges.push([refRange.low || 0, refRange.high || 0]);
|
||||
currentValues.push(observation.value_quantity_value);
|
||||
|
||||
let value = observation.value_object;
|
||||
|
||||
if (value.range) {
|
||||
currentValues.push([value.range.low, value.range.high]);
|
||||
} else {
|
||||
currentValues.push([value.value, value.value])
|
||||
}
|
||||
|
||||
if (observation.effective_date) {
|
||||
this.barChartLabels.push(formatDate(observation.effective_date, "mediumDate", "en-US", undefined));
|
||||
|
@ -141,7 +148,7 @@ export class ObservationBarChartComponent implements OnInit {
|
|||
this.barChartData[1]['dataLabels'].push(observation.value_quantity_unit);
|
||||
}
|
||||
|
||||
let xAxisMax = Math.max(...currentValues) * 1.3;
|
||||
let xAxisMax = Math.max(...currentValues.map(set => set[1])) * 1.3;
|
||||
this.barChartOptions.scales['x']['max'] = xAxisMax
|
||||
|
||||
let updatedRefRanges = referenceRanges.map(range => {
|
||||
|
@ -154,7 +161,7 @@ export class ObservationBarChartComponent implements OnInit {
|
|||
|
||||
// @ts-ignore
|
||||
this.barChartData[0].data = updatedRefRanges
|
||||
this.barChartData[1].data = currentValues.map(v => [v, v])
|
||||
this.barChartData[1].data = currentValues
|
||||
|
||||
this.chartHeight = defaultChartHeight + (defaultChartEntryHeight * currentValues.length)
|
||||
}
|
||||
|
|
|
@ -33,6 +33,12 @@ export const NoRange: Story = {
|
|||
}
|
||||
};
|
||||
|
||||
export const ValueStringWithRange: Story = {
|
||||
args: {
|
||||
observations: [new ObservationModel(observationR4Factory.valueString('<10 IntlUnit/mL').referenceRangeOnlyHigh(50).build(), fhirVersions.R4)]
|
||||
}
|
||||
};
|
||||
|
||||
export const Range: Story = {
|
||||
args: {
|
||||
observations: [new ObservationModel(observationR4Factory.referenceRange().build(), fhirVersions.R4)]
|
||||
|
|
|
@ -9,6 +9,18 @@ class ObservationR4Factory extends Factory<{}> {
|
|||
})
|
||||
}
|
||||
|
||||
valueQuantity(params: {}) {
|
||||
return this.params({
|
||||
valueQuantity: {
|
||||
value: params['value'] || 6.3,
|
||||
unit: params['unit'] || 'mmol/l',
|
||||
system: 'http://unitsofmeasure.org',
|
||||
code: params['code'] || 'mmol/L',
|
||||
comparator: params['comparator']
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
referenceRange(high?: number, low?: number) {
|
||||
return this.params({
|
||||
referenceRange: [
|
||||
|
|
|
@ -8,6 +8,35 @@ describe('ObservationModel', () => {
|
|||
});
|
||||
|
||||
describe('parsing value', () => {
|
||||
it('reads from valueQuantity.value if set', () => {
|
||||
let observation = new ObservationModel(observationR4Factory.build(), fhirVersions.R4);
|
||||
|
||||
expect(observation.value_object.value).toEqual(6.3);
|
||||
});
|
||||
|
||||
it('parses valueString correctly when value is a number if valueQuantity.value not set', () => {
|
||||
let observation = new ObservationModel(observationR4Factory.valueString().build(), fhirVersions.R4);
|
||||
|
||||
expect(observation.value_object.value).toEqual(5.5);
|
||||
});
|
||||
|
||||
it('parses value correctly when valueQuantity.comparator is set', () => {
|
||||
let observation = new ObservationModel(observationR4Factory.valueQuantity({ comparator: '<', value: 8 }).build(), fhirVersions.R4);
|
||||
let observation2 = new ObservationModel(observationR4Factory.valueQuantity({ comparator: '>', value: 8 }).build(), fhirVersions.R4);
|
||||
|
||||
expect(observation.value_object).toEqual({ range: { low: null, high: 8 } });
|
||||
expect(observation2.value_object).toEqual({ range: { low: 8, high: null } });
|
||||
});
|
||||
|
||||
it('parses value correctly when valueString has a range', () => {
|
||||
let observation = new ObservationModel(observationR4Factory.valueString('<10 IntlUnit/mL').build(), fhirVersions.R4);
|
||||
let observation2 = new ObservationModel(observationR4Factory.valueString('>10 IntlUnit/mL').build(), fhirVersions.R4);
|
||||
|
||||
expect(observation.value_object).toEqual({ range: { low: null, high: 10 } });
|
||||
expect(observation2.value_object).toEqual({ range: { low: 10, high: null } });
|
||||
});
|
||||
|
||||
// following two tests being kept temporarily. will be removed in next PR when I remove value_quantity_value
|
||||
it('reads from valueQuantity.value if set', () => {
|
||||
let observation = new ObservationModel(observationR4Factory.build(), fhirVersions.R4);
|
||||
|
||||
|
@ -21,6 +50,7 @@ describe('ObservationModel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
describe('parsing unit', () => {
|
||||
it('reads from valueQuantity.unit if set', () => {
|
||||
let observation = new ObservationModel(observationR4Factory.build(), fhirVersions.R4);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {fhirVersions, ResourceType} from '../constants';
|
||||
import * as _ from "lodash";
|
||||
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
|
||||
import {CodableConceptModel} from '../datatypes/codable-concept-model';
|
||||
import {ReferenceModel} from '../datatypes/reference-model';
|
||||
import {FastenDisplayModel} from '../fasten/fasten-display-model';
|
||||
import {FastenOptions} from '../fasten/fasten-options';
|
||||
|
@ -10,12 +10,19 @@ interface referenceRangeHash {
|
|||
high: number | null
|
||||
}
|
||||
|
||||
// should have one or the other
|
||||
export interface ValueObject {
|
||||
range?: { low?: number | null, high?: number | null }
|
||||
value?: number | string | boolean | null
|
||||
}
|
||||
|
||||
export class ObservationModel extends FastenDisplayModel {
|
||||
code: CodableConceptModel | undefined
|
||||
effective_date: string
|
||||
code_coding_display: string
|
||||
code_text: string
|
||||
value_quantity_value: number
|
||||
value_object: ValueObject
|
||||
value_quantity_value
|
||||
value_quantity_unit: string
|
||||
status: string
|
||||
value_codeable_concept_text: string
|
||||
|
@ -34,7 +41,8 @@ export class ObservationModel extends FastenDisplayModel {
|
|||
this.code = _.get(fhirResource, 'code');
|
||||
this.code_coding_display = _.get(fhirResource, 'code.coding.0.display');
|
||||
this.code_text = _.get(fhirResource, 'code.text', '');
|
||||
this.value_quantity_value = this.parseValue();
|
||||
this.value_object = this.parseValue();
|
||||
this.value_quantity_value = this.value_object?.value;
|
||||
this.value_quantity_unit = this.parseUnit();
|
||||
this.status = _.get(fhirResource, 'status', '');
|
||||
this.value_codeable_concept_text = _.get(
|
||||
|
@ -55,9 +63,8 @@ export class ObservationModel extends FastenDisplayModel {
|
|||
this.subject = _.get(fhirResource, 'subject');
|
||||
}
|
||||
|
||||
private parseValue(): number {
|
||||
// TODO: parseFloat would return NaN if it can't parse. Need to check and make sure that doesn't cause issues
|
||||
return this.valueQuantity() || parseFloat(this.valueString())
|
||||
private parseValue(): ValueObject {
|
||||
return this.parseValueQuantity() || this.parseValueString()
|
||||
}
|
||||
|
||||
private parseUnit(): string {
|
||||
|
@ -65,9 +72,23 @@ export class ObservationModel extends FastenDisplayModel {
|
|||
}
|
||||
|
||||
// Look for the observation's numeric value. Use this first before valueString which is a backup if this can't be found.
|
||||
private valueQuantity(): number {
|
||||
// debugger
|
||||
return _.get(this.fhirResource, "valueQuantity.value");
|
||||
private parseValueQuantity(): ValueObject {
|
||||
let quantity = _.get(this.fhirResource, "valueQuantity");
|
||||
|
||||
if (!quantity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (quantity.comparator) {
|
||||
case '<':
|
||||
case '<=':
|
||||
return { range: { low: null, high: quantity.value } };
|
||||
case '>':
|
||||
case '>=':
|
||||
return { range: { low: quantity.value, high: null } };
|
||||
default:
|
||||
return { value: quantity.value }
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the observation's numeric value. Use this first before valueStringUnit which is a backup if this can't be found.
|
||||
|
@ -75,9 +96,44 @@ export class ObservationModel extends FastenDisplayModel {
|
|||
return _.get(this.fhirResource, "valueQuantity.unit");
|
||||
}
|
||||
|
||||
// Use if valueQuantity can't be found. This will check for valueString and attempt to parse the first number in the string
|
||||
private valueString(): string {
|
||||
return _.get(this.fhirResource, "valueString")?.match(/(?<value>[\d.]*)(?<text>.*)/).groups.value;
|
||||
private parseValueString(): ValueObject {
|
||||
let matches = _.get(this.fhirResource, "valueString")?.match(/(?<value1>[\d.]*)?(?<operator>[^\d]*)?(?<value2>[\d.]*)?/)
|
||||
|
||||
if(!matches) {
|
||||
return { range: { low: null, high: null } }
|
||||
}
|
||||
|
||||
if (!!matches.groups['value1'] && !!matches.groups['value2']) {
|
||||
return {
|
||||
range: {
|
||||
low: parseFloat(matches.groups['value1']),
|
||||
high: parseFloat(matches.groups['value2'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (['<', '<='].includes(matches.groups['operator'])) {
|
||||
return {
|
||||
range: {
|
||||
low: null,
|
||||
high: parseFloat(matches.groups['value2'])
|
||||
}
|
||||
}
|
||||
} else if (['>', '>='].includes(matches.groups['operator'])) {
|
||||
return {
|
||||
range: {
|
||||
low: parseFloat(matches.groups['value2']),
|
||||
high: null
|
||||
}
|
||||
}
|
||||
}
|
||||
let float = parseFloat(matches.groups['value1']);
|
||||
|
||||
if (Number.isNaN(float)) {
|
||||
return { value: matches.groups['value1'] }
|
||||
}
|
||||
|
||||
return { value: float };
|
||||
}
|
||||
|
||||
// Use if valueUnit can't be found.
|
||||
|
|
Loading…
Reference in New Issue