Skip to content

Commit

Permalink
Extract characteristics for Logicals (#250)
Browse files Browse the repository at this point in the history
* Extract characteristics for Logicals

Logical models use special extensions to set their type characteristics.
Check for these extensions when extracting keywords. Do not create caret
rules for these extensions.

* Add non-characteristic extension to Logical with characteristics
  • Loading branch information
mint-thompson authored Jan 16, 2024
1 parent a8eee62 commit da09f9b
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 1 deletion.
17 changes: 16 additions & 1 deletion src/extractor/CaretValueRuleExtractor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { fhirtypes, fshtypes, utils } from 'fsh-sushi';
import { cloneDeep, isEqual, differenceWith, toPairs } from 'lodash';
import { ProcessableElementDefinition, ProcessableStructureDefinition } from '../processor';
import {
LOGICAL_TARGET_EXTENSION,
ProcessableElementDefinition,
ProcessableStructureDefinition,
TYPE_CHARACTERISTICS_EXTENSION
} from '../processor';
import { ExportableCaretValueRule } from '../exportable';
import { getFSHValue, getPath, getPathValuePairs, logger, isFSHValueEmpty } from '../utils';

Expand Down Expand Up @@ -125,6 +130,16 @@ export class CaretValueRuleExtractor {
delete parent.context;
}

// if this is a Logical, ignore characteristic-setting extensions, since those are covered by a keyword
if (sd.kind === 'logical' && sd.derivation === 'specialization') {
sd.extension = sd.extension?.filter((ext: fhirtypes.Extension) => {
return !(
ext.url === TYPE_CHARACTERISTICS_EXTENSION ||
(ext.url === LOGICAL_TARGET_EXTENSION && ext.valueBoolean === true)
);
});
}

// Remove properties that are covered by other extractors or keywords
RESOURCE_IGNORED_PROPERTIES['StructureDefinition'].forEach(prop => {
delete sd[prop];
Expand Down
21 changes: 21 additions & 0 deletions src/processor/StructureDefinitionProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ import { getAncestorSliceDefinition, getPath, logger } from '../utils';
import { fshifyString } from '../exportable/common';
import { isUri } from 'valid-url';

export const TYPE_CHARACTERISTICS_EXTENSION =
'http://hl7.org/fhir/StructureDefinition/structuredefinition-type-characteristics';
export const LOGICAL_TARGET_EXTENSION =
'http://hl7.org/fhir/tools/StructureDefinition/logical-target';

export class StructureDefinitionProcessor {
static process(
input: any,
Expand Down Expand Up @@ -144,6 +149,21 @@ export class StructureDefinitionProcessor {
}
});
}
if (target instanceof ExportableLogical && input.extension) {
// most characteristics use TYPE_CHARACTERISTICS_EXTENSION,
// but there may also be LOGICAL_TARGET_EXTENSION with valueBoolean true for the can-be-target characteristic.
const characteristics: string[] = [];
input.extension.forEach(ext => {
if (ext.url === TYPE_CHARACTERISTICS_EXTENSION && ext.valueCode != null) {
characteristics.push(ext.valueCode);
} else if (ext.url === LOGICAL_TARGET_EXTENSION && ext.valueBoolean === true) {
characteristics.push('can-be-target');
}
});
if (characteristics.length) {
target.characteristics = characteristics;
}
}
}

static extractRules(
Expand Down Expand Up @@ -283,6 +303,7 @@ export interface ProcessableStructureDefinition {
derivation?: string;
mapping?: fhirtypes.StructureDefinitionMapping[];
context?: fhirtypes.StructureDefinitionContext[];
extension?: fhirtypes.Extension[];
differential?: {
element: any[];
};
Expand Down
36 changes: 36 additions & 0 deletions test/extractor/CaretValueRuleExtractor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('CaretValueRuleExtractor', () => {
let looseBSSD: any;
let looseTPESD: any;
let looseExtSD: any;
let looseLogicalSD: any;
let config: fshtypes.Configuration;
let defs: FHIRDefinitions;

Expand Down Expand Up @@ -54,6 +55,14 @@ describe('CaretValueRuleExtractor', () => {
.readFileSync(path.join(__dirname, 'fixtures', 'extension-with-context.json'), 'utf-8')
.trim()
);
looseLogicalSD = JSON.parse(
fs
.readFileSync(
path.join(__dirname, 'fixtures', 'logical-with-characteristics.json'),
'utf-8'
)
.trim()
);
});

beforeEach(() => {
Expand All @@ -75,6 +84,33 @@ describe('CaretValueRuleExtractor', () => {
expect(caretRules).toEqual<ExportableCaretValueRule[]>([]);
});

it('should not extract any SD caret rules for characteristics on a Logical', () => {
const caretRules = CaretValueRuleExtractor.processStructureDefinition(
looseLogicalSD,
defs,
config
);
// the only "extension" rules should be for the non-characteristic extension
expect(caretRules).toContainEqual(
expect.objectContaining({
caretPath: expect.stringMatching(/^extension\[\d+\]\.url/),
value: 'http://hl7.org/fhir/sushi-test/StructureDefinition/other-extension'
})
);
expect(caretRules).not.toContainEqual(
expect.objectContaining({
caretPath: expect.stringMatching(/^extension\[\d+\]\.url/),
value: 'http://hl7.org/fhir/StructureDefinition/structuredefinition-type-characteristics'
})
);
expect(caretRules).not.toContainEqual(
expect.objectContaining({
caretPath: expect.stringMatching(/^extension\[\d+\]\.url/),
value: 'http://hl7.org/fhir/tools/StructureDefinition/logical-target'
})
);
});

it('should extract a url-setting caret rules when a non-standard url is included on a StructureDefinition', () => {
const urlSD = cloneDeep(looseSD);
urlSD.url = 'http://diferenturl.com';
Expand Down
30 changes: 30 additions & 0 deletions test/extractor/fixtures/logical-with-characteristics.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"resourceType": "StructureDefinition",
"id": "logical-with-characteristics",
"url": "http://hl7.org/fhir/sushi-test/StructureDefinition/logical-with-characteristics",
"kind": "logical",
"derivation": "specialization",
"type": "Base",
"name": "LogicalWithCharacteristics",
"title": "My Logical Model with Characteristics",
"description": "This is my fancy new logical model that has several characteristics.",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Base",
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-type-characteristics",
"valueCode": "has-units"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-type-characteristics",
"valueCode": "has-length"
},
{
"url": "http://hl7.org/fhir/tools/StructureDefinition/logical-target",
"valueBoolean": true
},
{
"url": "http://hl7.org/fhir/sushi-test/StructureDefinition/other-extension",
"valueString": "something else"
}
]
}
21 changes: 21 additions & 0 deletions test/processor/StructureDefinitionProcessor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,27 @@ describe('StructureDefinitionProcessor', () => {
expect(workingLogical.title).toBe('My Logical Model');
expect(workingLogical.description).toBe('This is my fancy new logical model.');
});

it('should get characteristics for a Logical with characteristics', () => {
const input = JSON.parse(
fs.readFileSync(
path.join(__dirname, 'fixtures', 'logical-with-characteristics.json'),
'utf-8'
)
);
const workingLogical = new ExportableLogical(input.name);
StructureDefinitionProcessor.extractKeywords(input, workingLogical);

expect(workingLogical.id).toBe('logical-with-characteristics');
expect(workingLogical.title).toBe('My Logical Model with Characteristics');
expect(workingLogical.description).toBe(
'This is my fancy new logical model that has several characteristics.'
);
expect(workingLogical.characteristics).toHaveLength(3);
expect(workingLogical.characteristics).toContain('can-be-target');
expect(workingLogical.characteristics).toContain('has-units');
expect(workingLogical.characteristics).toContain('has-length');
});
});

describe('#extractRules', () => {
Expand Down
30 changes: 30 additions & 0 deletions test/processor/fixtures/logical-with-characteristics.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"resourceType": "StructureDefinition",
"id": "logical-with-characteristics",
"url": "http://hl7.org/fhir/sushi-test/StructureDefinition/logical-with-characteristics",
"kind": "logical",
"derivation": "specialization",
"type": "Base",
"name": "LogicalWithCharacteristics",
"title": "My Logical Model with Characteristics",
"description": "This is my fancy new logical model that has several characteristics.",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Base",
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-type-characteristics",
"valueCode": "has-units"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-type-characteristics",
"valueCode": "has-length"
},
{
"url": "http://hl7.org/fhir/tools/StructureDefinition/logical-target",
"valueBoolean": true
},
{
"url": "http://hl7.org/fhir/sushi-test/StructureDefinition/other-extension",
"valueString": "something else"
}
]
}

0 comments on commit da09f9b

Please sign in to comment.