Skip to content

Commit

Permalink
Allow ValueSet to reference contained inline CodeSystem (#1520)
Browse files Browse the repository at this point in the history
* Allow ValueSet to reference contained inline CodeSystem

If a ValueSetComponentRule specifies a system, check if that system is
present in the list of contained resources. If so, add the
valueset-system extension to the system, using the relative reference as
the extension's value. If the system is not present in the list of
contained resources, and the system is an inline Instance, log an error
and do not add the component to the ValueSet.

Fishing in the tank for a CodeSystem will now return inline instances of
CodeSystem. This matches the operation of fishing in the tank for a
ValueSet.

Add _system.extension to the type definition for elements of a
ValueSet's include and exclude lists. Use the Extension fhirtype as part
of this definition. Replace the Extension fshtype with the Extension
fhirtype in other parts of the ValueSet definition.

* Better type for ValueSet._system

Add link to zulip thread with information about the contained code
system extension.

* Improve handling case when referenced system is not found.

Add a test showing that an inline example instance of a CodeSystem will
not work.
Add a currently-skipped test that shows what the inline ValueSet
behavior should be, based on currently available information from this
zulip thread:
https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/Contained.20code.20system.20in.20the.20value.20set/near/475597931
  • Loading branch information
mint-thompson authored Oct 11, 2024
1 parent 55ef7c3 commit d6e2c7f
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 5 deletions.
24 changes: 24 additions & 0 deletions src/export/ValueSetExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,30 @@ export class ValueSetExporter {
.replace(/^([^|]+)/, csMetadata?.url ?? '$1')
.split('|');
composeElement.system = foundSystem[0];
// if the code system is also a contained resource, add the valueset-system extension
// this zulip thread contains a discussion of the issue and an example using this extension:
// https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/Contained.20code.20system.20in.20the.20value.20set/near/424938537
// additionally, if it's not a contained resource, and the system we found is an inline instance, that's a problem
const containedSystem = valueSet.contained?.find((resource: any) => {
return resource?.id === csMetadata?.id && resource.resourceType === 'CodeSystem';
});
if (containedSystem != null) {
composeElement._system = {
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/valueset-system',
valueCanonical: `#${csMetadata.id}`
}
]
};
} else if (csMetadata?.instanceUsage === 'Inline') {
logger.error(
`Can not reference CodeSystem ${component.from.system}: this CodeSystem is an inline instance, but it is not present in the list of contained resources.`,
component.sourceInfo
);
return;
}

// if the rule specified a version, use that version.
composeElement.version = systemParts.slice(1).join('|') || undefined;
if (!isUri(composeElement.system)) {
Expand Down
12 changes: 10 additions & 2 deletions src/fhirtypes/ValueSet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import sanitize from 'sanitize-filename';
import { Meta } from './specialTypes';
import { Extension } from '../fshtypes';
import { Narrative, Resource, Identifier, CodeableConcept, Coding } from './dataTypes';
import {
Narrative,
Resource,
Identifier,
CodeableConcept,
Coding,
Extension,
Element
} from './dataTypes';
import { ContactDetail, UsageContext } from './metaDataTypes';
import { HasName, HasId } from './mixins';
import { applyMixins } from '../utils/Mixin';
Expand Down Expand Up @@ -83,6 +90,7 @@ export type ValueSetCompose = {

export type ValueSetComposeIncludeOrExclude = {
system?: string;
_system?: Element;
version?: string;
valueSet?: string[];
concept?: ValueSetComposeConcept[];
Expand Down
2 changes: 1 addition & 1 deletion src/import/FSHTank.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ export class FSHTank implements Fishable {
result = this.getAllInstances().find(
csInstance =>
csInstance?.instanceOf === 'CodeSystem' &&
csInstance?.usage === 'Definition' &&
(csInstance?.usage === 'Definition' || csInstance?.usage === 'Inline') &&
(csInstance?.name === base ||
csInstance.id === base ||
getUrlFromFshDefinition(csInstance, this.config.canonical) === base ||
Expand Down
65 changes: 63 additions & 2 deletions test/export/FHIRExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { exportFHIR, Package, FHIRExporter } from '../../src/export';
import { FSHTank, FSHDocument } from '../../src/import';
import { FHIRDefinitions } from '../../src/fhirdefs';
import { minimalConfig } from '../utils/minimalConfig';
import { FshValueSet, Instance, Profile } from '../../src/fshtypes';
import { AssignmentRule, BindingRule, CaretValueRule } from '../../src/fshtypes/rules';
import { FshCodeSystem, FshValueSet, Instance, Profile } from '../../src/fshtypes';
import {
AssignmentRule,
BindingRule,
CaretValueRule,
ValueSetConceptComponentRule
} from '../../src/fshtypes/rules';
import { TestFisher, loggerSpy } from '../testhelpers';

describe('FHIRExporter', () => {
Expand Down Expand Up @@ -507,6 +512,62 @@ describe('FHIRExporter', () => {
});
});

it('should export a value set that includes a component from a contained FSH code system and add the valueset-system extension', () => {
// CodeSystem: FoodCS
// Id: food
const foodCS = new FshCodeSystem('FoodCS');
foodCS.id = 'food';
doc.codeSystems.set(foodCS.name, foodCS);
// ValueSet: DinnerVS
// * ^contained[0] = FoodCS
// * include codes from system food
const valueSet = new FshValueSet('DinnerVS');
const containedCS = new CaretValueRule('');
containedCS.caretPath = 'contained[0]';
containedCS.value = 'FoodCS';
containedCS.isInstance = true;
const component = new ValueSetConceptComponentRule(true);
component.from = { system: 'FoodCS' };
valueSet.rules.push(containedCS, component);
doc.valueSets.set(valueSet.name, valueSet);

const exported = exporter.export();
expect(exported.valueSets.length).toBe(1);
expect(exported.valueSets[0]).toEqual({
resourceType: 'ValueSet',
name: 'DinnerVS',
id: 'DinnerVS',
status: 'draft',
url: 'http://hl7.org/fhir/us/minimal/ValueSet/DinnerVS',
contained: [
{
content: 'complete',
id: 'food',
name: 'FoodCS',
resourceType: 'CodeSystem',
status: 'draft',
url: 'http://hl7.org/fhir/us/minimal/CodeSystem/food'
}
],
compose: {
include: [
{
system: 'http://hl7.org/fhir/us/minimal/CodeSystem/food',
_system: {
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/valueset-system',
valueCanonical: '#food'
}
]
}
}
]
}
});
expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
});

it('should log a message when trying to assign a value that is numeric and refers to an Instance, but both types are wrong', () => {
// Profile: MyObservation
// Parent: Observation
Expand Down
Loading

0 comments on commit d6e2c7f

Please sign in to comment.