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

Quantity may omit numeric value #1430

Merged
merged 2 commits into from
Mar 5, 2024
Merged
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
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
jafeltra marked this conversation as resolved.
Show resolved Hide resolved
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
Loading