Skip to content

Commit

Permalink
Quantity may omit numeric value (#1430)
Browse files Browse the repository at this point in the history
Update the grammar to allow a quantity to omit the numeric value
component. Since a ratio contains two quantities, this also affects the
allowed FSH for a ratio. A quantity without a numeric value is useful
for setting the unit.

Co-authored-by: Chris Moesel <[email protected]>
  • Loading branch information
mint-thompson and cmoesel authored Mar 5, 2024
1 parent 148d0be commit d9fdd64
Show file tree
Hide file tree
Showing 7 changed files with 441 additions and 255 deletions.
2 changes: 1 addition & 1 deletion antlr/src/main/antlr/FSH.g4
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ value: STRING | MULTILINE_STRING | NUMBER | DATETIME | TIME | refer
item: name (KW_NAMED name)? CARD flag*;
code: CODE STRING?;
concept: STAR CODE+ STRING? (STRING | MULTILINE_STRING)?;
quantity: NUMBER (UNIT | CODE) STRING?;
quantity: NUMBER? (UNIT | CODE) STRING?;
ratio: ratioPart COLON ratioPart;
reference: REFERENCE STRING?;
referenceType: REFERENCE;
Expand Down
13 changes: 8 additions & 5 deletions src/fshtypes/FshQuantity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ export class FshQuantity extends FshEntity {
}

toString(): string {
let str = this.value.toString();
const stringParts: string[] = [];
if (this.value != null) {
stringParts.push(this.value.toString());
}
if (this.unit?.code != null) {
if (this.unit?.system == 'http://unitsofmeasure.org') {
str += ` '${this.unit.code}'`;
stringParts.push(`'${this.unit.code}'`);
if (this.unit.display) {
str += ` "${fshifyString(this.unit.display)}"`;
stringParts.push(`"${fshifyString(this.unit.display)}"`);
}
} else {
str += ` ${this.unit.toString()}`;
stringParts.push(`${this.unit.toString()}`);
}
}
return str;
return stringParts.join(' ');
}

toFHIRQuantity(): Quantity {
Expand Down
5 changes: 4 additions & 1 deletion src/import/FSHImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1561,7 +1561,10 @@ export class FSHImporter extends FSHVisitor {
}

visitQuantity(ctx: pc.QuantityContext): FshQuantity {
const value = parseFloat(ctx.NUMBER().getText());
let value = null;
if (ctx.NUMBER()) {
value = parseFloat(ctx.NUMBER().getText());
}
let delimitedUnit = ctx.UNIT() ? ctx.UNIT().getText() : ''; // e.g., 'mm'
// We'll want to assume the UCUM code system unless another system is specified
let unitSystem = 'http://unitsofmeasure.org';
Expand Down
2 changes: 1 addition & 1 deletion src/import/generated/FSH.interp

Large diffs are not rendered by default.

501 changes: 254 additions & 247 deletions src/import/generated/FSHParser.js

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions test/fshtypes/FshQuantity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,39 @@ describe('FshQuantity', () => {
expect(result).toEqual('100 #mm');
});

it('should return string for basic unit code without value', () => {
const code = new FshCode('mm');
const quantity = new FshQuantity(null, code);
const result = quantity.toString();
expect(result).toEqual('#mm');
});

it('should return string for unit code with UCUM system', () => {
const code = new FshCode('mm', 'http://unitsofmeasure.org');
const quantity = new FshQuantity(100, code);
const result = quantity.toString();
expect(result).toEqual("100 'mm'");
});

it('should return string for unit code with UCUM system without value', () => {
const code = new FshCode('mm', 'http://unitsofmeasure.org');
const quantity = new FshQuantity(null, code);
const result = quantity.toString();
expect(result).toEqual("'mm'");
});

it('should return string for unit code with non-UCUM system', () => {
const code = new FshCode('bar', 'http://foo.com');
const quantity = new FshQuantity(100, code);
const result = quantity.toString();
expect(result).toEqual('100 http://foo.com#bar');
});
it('should return string for unit code with non-UCUM system without value', () => {
const code = new FshCode('bar', 'http://foo.com');
const quantity = new FshQuantity(null, code);
const result = quantity.toString();
expect(result).toEqual('http://foo.com#bar');
});

it('should return string for unit code with display', () => {
const code = new FshCode('mm', null, 'Display');
Expand All @@ -49,20 +69,41 @@ describe('FshQuantity', () => {
expect(result).toEqual('100 #mm "Display"');
});

it('should return string for unit code with display without value', () => {
const code = new FshCode('mm', null, 'Display');
const quantity = new FshQuantity(null, code);
const result = quantity.toString();
expect(result).toEqual('#mm "Display"');
});

it('should return string for unit code with UCUM system and display', () => {
const code = new FshCode('mm', 'http://unitsofmeasure.org', 'Display');
const quantity = new FshQuantity(100, code);
const result = quantity.toString();
expect(result).toEqual('100 \'mm\' "Display"');
});

it('should return string for unit code with UCUM system and display without value', () => {
const code = new FshCode('mm', 'http://unitsofmeasure.org', 'Display');
const quantity = new FshQuantity(null, code);
const result = quantity.toString();
expect(result).toEqual('\'mm\' "Display"');
});

it('should return string for unit code with non-UCUM system and display', () => {
const code = new FshCode('bar', 'http://foo.com', 'Display');
const quantity = new FshQuantity(100, code);
const result = quantity.toString();
expect(result).toEqual('100 http://foo.com#bar "Display"');
});

it('should return string for unit code with non-UCUM system and display without value', () => {
const code = new FshCode('bar', 'http://foo.com', 'Display');
const quantity = new FshQuantity(null, code);
const result = quantity.toString();
expect(result).toEqual('http://foo.com#bar "Display"');
});

it('should return string for unit code with code with spaces', () => {
const code = new FshCode('milli meters');
const quantity = new FshQuantity(100, code);
Expand Down
132 changes: 132 additions & 0 deletions test/import/FSHImporter.SDRules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,48 @@ describe('FSHImporter', () => {
assertAssignmentRule(profile.rules[0], 'valueQuantity', expectedQuantity);
});

it('should parse assigned value Quantity rule with UCUM units and no value', () => {
const input = leftAlign(`
Profile: ObservationProfile
Parent: Observation
* valueQuantity = 'mm'
`);

const result = importSingleText(input);
const profile = result.profiles.get('ObservationProfile');
expect(profile.rules).toHaveLength(1);
const expectedQuantity = new FshQuantity(
null,
new FshCode('mm', 'http://unitsofmeasure.org').withLocation([5, 19, 5, 22]).withFile('')
)
.withLocation([5, 19, 5, 22])
.withFile('');
assertAssignmentRule(profile.rules[0], 'valueQuantity', expectedQuantity);
});

it('should parse assigned value Quantity rule with UCUM units, display, and no value', () => {
const input = leftAlign(`
Profile: ObservationProfile
Parent: Observation
* valueQuantity = '[lb_av]' "lb"
`);

const result = importSingleText(input);
const profile = result.profiles.get('ObservationProfile');
expect(profile.rules).toHaveLength(1);
const expectedQuantity = new FshQuantity(
null,
new FshCode('[lb_av]', 'http://unitsofmeasure.org', 'lb')
.withLocation([5, 19, 5, 27])
.withFile('')
)
.withLocation([5, 19, 5, 32])
.withFile('');
assertAssignmentRule(profile.rules[0], 'valueQuantity', expectedQuantity);
});

it('should parse assigned value Ratio rule', () => {
const input = leftAlign(`
Expand Down Expand Up @@ -992,6 +1034,96 @@ describe('FSHImporter', () => {
assertAssignmentRule(profile.rules[0], 'valueRatio', expectedRatio);
});

it('should parse assigned value Ratio rule with non-numeric numerator', () => {
const input = leftAlign(`
Profile: ObservationProfile
Parent: Observation
* valueRatio = 'mg' : 1 'dL'
`);

const result = importSingleText(input);
const profile = result.profiles.get('ObservationProfile');
expect(profile.rules).toHaveLength(1);
const expectedRatio = new FshRatio(
new FshQuantity(
null,
new FshCode('mg', 'http://unitsofmeasure.org').withLocation([5, 16, 5, 19]).withFile('')
)
.withLocation([5, 16, 5, 19])
.withFile(''),
new FshQuantity(
1,
new FshCode('dL', 'http://unitsofmeasure.org').withLocation([5, 25, 5, 28]).withFile('')
)
.withLocation([5, 23, 5, 28])
.withFile('')
)
.withLocation([5, 16, 5, 28])
.withFile('');
assertAssignmentRule(profile.rules[0], 'valueRatio', expectedRatio);
});

it('should parse assigned value Ratio rule with non-numeric denominator', () => {
const input = leftAlign(`
Profile: ObservationProfile
Parent: Observation
* valueRatio = 130 'mg' : 'dL'
`);

const result = importSingleText(input);
const profile = result.profiles.get('ObservationProfile');
expect(profile.rules).toHaveLength(1);
const expectedRatio = new FshRatio(
new FshQuantity(
130,
new FshCode('mg', 'http://unitsofmeasure.org').withLocation([5, 20, 5, 23]).withFile('')
)
.withLocation([5, 16, 5, 23])
.withFile(''),
new FshQuantity(
null,
new FshCode('dL', 'http://unitsofmeasure.org').withLocation([5, 27, 5, 30]).withFile('')
)
.withLocation([5, 27, 5, 30])
.withFile('')
)
.withLocation([5, 16, 5, 30])
.withFile('');
assertAssignmentRule(profile.rules[0], 'valueRatio', expectedRatio);
});

it('should parse assigned value Ratio rule with non-numeric numerator and denominator', () => {
const input = leftAlign(`
Profile: ObservationProfile
Parent: Observation
* valueRatio = 'mg' : 'dL'
`);

const result = importSingleText(input);
const profile = result.profiles.get('ObservationProfile');
expect(profile.rules).toHaveLength(1);
const expectedRatio = new FshRatio(
new FshQuantity(
null,
new FshCode('mg', 'http://unitsofmeasure.org').withLocation([5, 16, 5, 19]).withFile('')
)
.withLocation([5, 16, 5, 19])
.withFile(''),
new FshQuantity(
null,
new FshCode('dL', 'http://unitsofmeasure.org').withLocation([5, 23, 5, 26]).withFile('')
)
.withLocation([5, 23, 5, 26])
.withFile('')
)
.withLocation([5, 16, 5, 26])
.withFile('');
assertAssignmentRule(profile.rules[0], 'valueRatio', expectedRatio);
});

it('should parse assigned value Reference rule', () => {
const input = leftAlign(`
Expand Down

0 comments on commit d9fdd64

Please sign in to comment.