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

Allow caret and insert rules to be applied to concepts within a ValueSet #1315

Merged
merged 8 commits into from
Aug 4, 2023
5 changes: 2 additions & 3 deletions antlr/src/main/antlr/FSH.g4
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ invariantRule: fixedValueRule | insertRule | pathRule;

valueSet: KW_VALUESET name vsMetadata* vsRule*;
vsMetadata: id | title | description;
vsRule: vsComponent | caretValueRule | insertRule;
vsRule: vsComponent | caretValueRule | codeCaretValueRule | insertRule | codeInsertRule;
codeSystem: KW_CODESYSTEM name csMetadata* csRule*;
csMetadata: id | title | description;
csRule: concept | codeCaretValueRule | codeInsertRule;
Expand Down Expand Up @@ -90,8 +90,7 @@ pathRule: STAR path;

// VALUESET COMPONENTS
vsComponent: STAR ( KW_INCLUDE | KW_EXCLUDE )? ( vsConceptComponent | vsFilterComponent );
vsConceptComponent: code vsComponentFrom?
| (code KW_AND)+ code vsComponentFrom;
vsConceptComponent: code vsComponentFrom?;
vsFilterComponent: KW_CODES vsComponentFrom (KW_WHERE vsFilterList)?;
vsComponentFrom: KW_FROM (vsFromSystem (KW_AND vsFromValueset)? | vsFromValueset (KW_AND vsFromSystem)?);
vsFromSystem: KW_SYSTEM name;
Expand Down
71 changes: 67 additions & 4 deletions src/export/ValueSetExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
assignInstanceFromRawValue
} from '../fhirtypes/common';
import { isUri } from 'valid-url';
import { flatMap } from 'lodash';
import { flatMap, partition } from 'lodash';

export class ValueSetExporter {
constructor(private readonly tank: FSHTank, private pkg: Package, private fisher: MasterFisher) {}
Expand Down Expand Up @@ -210,6 +210,65 @@ export class ValueSetExporter {
}
}

private setConceptCaretRules(vs: ValueSet, rules: CaretValueRule[]) {
resolveSoftIndexing(rules);
for (const rule of rules) {
const splitConcept = rule.pathArray[0].split('#');
const system = splitConcept[0];
const code = splitConcept.slice(1).join('#');
const systemMeta = this.fisher.fishForMetadata(system, Type.CodeSystem);
let composeIndex =
vs.compose?.include?.findIndex(composeElement => {
return composeElement.system === system || composeElement.system === systemMeta?.url;
}) ?? -1;
let composeArray: string;
let composeElement: ValueSetComposeIncludeOrExclude;
let conceptIndex = -1;
if (composeIndex !== -1) {
composeArray = 'include';
composeElement = vs.compose.include[composeIndex];
conceptIndex = composeElement?.concept?.findIndex(concept => {
return concept.code === code;
});
}
if (conceptIndex === -1) {
composeIndex =
vs.compose?.exclude?.findIndex(composeElement => {
return composeElement.system === system;
}) ?? -1;
if (composeIndex !== -1) {
composeArray = 'exclude';
composeElement = vs.compose.exclude[composeIndex];
conceptIndex = composeElement?.concept?.findIndex(concept => {
return concept.code === code;
});
}
}

if (conceptIndex !== -1) {
if (rule.isInstance) {
const instanceExporter = new InstanceExporter(this.tank, this.pkg, this.fisher);
const instance = instanceExporter.fishForFHIR(rule.value as string);
if (instance == null) {
logger.error(
`Cannot find definition for Instance: ${rule.value}. Skipping rule.`,
rule.sourceInfo
);
continue;
}
rule.value = instance;
}
const fullPath = `compose.${composeArray}[${composeIndex}].concept[${conceptIndex}].${rule.caretPath}`;
setPropertyOnDefinitionInstance(vs, fullPath, rule.value, this.fisher);
} else {
logger.error(
`Could not find concept ${rule.pathArray[0]}, skipping rule.`,
rule.sourceInfo
);
}
}
}

private filterValueToString(value: ValueSetFilterValue): string {
if (value instanceof RegExp) {
return value.source;
Expand Down Expand Up @@ -248,16 +307,20 @@ export class ValueSetExporter {
}
const vs = new ValueSet();
this.setMetadata(vs, fshDefinition);
this.setCaretRules(
vs,
fshDefinition.rules.filter(rule => rule instanceof CaretValueRule) as CaretValueRule[]
const [conceptCaretRules, otherCaretRules] = partition(
fshDefinition.rules.filter(rule => rule instanceof CaretValueRule) as CaretValueRule[],
caretRule => {
return caretRule.pathArray.length > 0;
}
);
this.setCaretRules(vs, otherCaretRules);
this.setCompose(
vs,
fshDefinition.rules.filter(
rule => rule instanceof ValueSetComponentRule
) as ValueSetComponentRule[]
);
this.setConceptCaretRules(vs, conceptCaretRules);
if (vs.compose && vs.compose.include.length == 0) {
throw new ValueSetComposeError(fshDefinition.name);
}
Expand Down
12 changes: 8 additions & 4 deletions src/import/FSHErrorListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,17 @@ export class FSHErrorListener extends ErrorListener<Token> {
// * onset[x], abatement[x] MS
// > extraneous input 'abatement[x]' expecting {<EOF>, KW_ALIAS, KW_PROFILE, KW_EXTENSION,
// > KW_INSTANCE, KW_INVARIANT, KW_VALUESET, KW_CODESYSTEM, KW_RULESET, KW_MAPPING, KW_LOGICAL, KW_RESOURCE}
// * #hippo, #crocodile , #emu from system ZOO
// > extraneous input '#crocodile' expecting {<EOF>, KW_ALIAS, KW_PROFILE, KW_EXTENSION,
// > KW_INSTANCE, KW_INVARIANT, KW_VALUESET, KW_CODESYSTEM, KW_RULESET, KW_MAPPING, KW_LOGICAL, KW_RESOURCE}
// * codes from valueset FirstZooVS, SecondZooVS
// > extraneous input 'SecondZooVS' expecting {<EOF>, KW_ALIAS, KW_PROFILE, KW_EXTENSION,
// > KW_INSTANCE, KW_INVARIANT, KW_VALUESET, KW_CODESYSTEM, KW_RULESET, KW_MAPPING, KW_LOGICAL, KW_RESOURCE}
else if (/^extraneous input/.test(msg) && /,$/.test(oneTokenBack?.text)) {
// * #hippo, #crocodile , #emu from system ZOO
// > no viable alternative at input '\n* #hippo, #crocodile ,'
// * #hippo, #crocodile, #emu from system ZOO
// > no viable alternative at input '\n* #hippo, #crocodile, #emu from'
else if (
(/^extraneous input/.test(msg) || /^no viable alternative at input '/.test(msg)) &&
(/,$/.test(oneTokenBack?.text) || /,$/.test(twoTokensBack?.text))
) {
message = "Using ',' to list items is no longer supported. Use 'and' to list multiple items.";
}

Expand Down
Loading
Loading