From 235bba94e1169670e204c5142b77530c1c3467c1 Mon Sep 17 00:00:00 2001 From: Mint Thompson Date: Wed, 15 Nov 2023 11:40:12 -0500 Subject: [PATCH 1/2] 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. --- src/extractor/CaretValueRuleExtractor.ts | 17 +++++++++++- src/processor/StructureDefinitionProcessor.ts | 21 +++++++++++++++ .../extractor/CaretValueRuleExtractor.test.ts | 24 +++++++++++++++++ .../logical-with-characteristics.json | 26 +++++++++++++++++++ .../StructureDefinitionProcessor.test.ts | 20 ++++++++++++++ .../logical-with-characteristics.json | 26 +++++++++++++++++++ 6 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 test/extractor/fixtures/logical-with-characteristics.json create mode 100644 test/processor/fixtures/logical-with-characteristics.json diff --git a/src/extractor/CaretValueRuleExtractor.ts b/src/extractor/CaretValueRuleExtractor.ts index 47d78fa9..34801284 100644 --- a/src/extractor/CaretValueRuleExtractor.ts +++ b/src/extractor/CaretValueRuleExtractor.ts @@ -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'; @@ -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]; diff --git a/src/processor/StructureDefinitionProcessor.ts b/src/processor/StructureDefinitionProcessor.ts index 8941aba8..8536a30c 100644 --- a/src/processor/StructureDefinitionProcessor.ts +++ b/src/processor/StructureDefinitionProcessor.ts @@ -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, @@ -143,6 +148,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( @@ -276,6 +296,7 @@ export interface ProcessableStructureDefinition { derivation?: string; mapping?: fhirtypes.StructureDefinitionMapping[]; context?: fhirtypes.StructureDefinitionContext[]; + extension?: fhirtypes.Extension[]; differential?: { element: any[]; }; diff --git a/test/extractor/CaretValueRuleExtractor.test.ts b/test/extractor/CaretValueRuleExtractor.test.ts index 9eaffe0b..f303f19a 100644 --- a/test/extractor/CaretValueRuleExtractor.test.ts +++ b/test/extractor/CaretValueRuleExtractor.test.ts @@ -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; @@ -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(() => { @@ -75,6 +84,21 @@ describe('CaretValueRuleExtractor', () => { expect(caretRules).toEqual([]); }); + it('should not extract any SD caret rules for characteristics on a Logical', () => { + const caretRules = CaretValueRuleExtractor.processStructureDefinition( + looseLogicalSD, + defs, + config + ); + // the rule on "kind" may be removed later by an optimizer, + // but there should not be any "extension" rules. + expect(caretRules).not.toContainEqual( + expect.objectContaining({ + caretPath: expect.stringMatching(/^extension/) + }) + ); + }); + 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'; diff --git a/test/extractor/fixtures/logical-with-characteristics.json b/test/extractor/fixtures/logical-with-characteristics.json new file mode 100644 index 00000000..5aae8a86 --- /dev/null +++ b/test/extractor/fixtures/logical-with-characteristics.json @@ -0,0 +1,26 @@ +{ + "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 + } + ] +} diff --git a/test/processor/StructureDefinitionProcessor.test.ts b/test/processor/StructureDefinitionProcessor.test.ts index a64f923a..e935a29d 100644 --- a/test/processor/StructureDefinitionProcessor.test.ts +++ b/test/processor/StructureDefinitionProcessor.test.ts @@ -354,6 +354,26 @@ 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).toContain('can-be-target'); + expect(workingLogical.characteristics).toContain('has-units'); + expect(workingLogical.characteristics).toContain('has-length'); + }); }); describe('#extractRules', () => { diff --git a/test/processor/fixtures/logical-with-characteristics.json b/test/processor/fixtures/logical-with-characteristics.json new file mode 100644 index 00000000..5aae8a86 --- /dev/null +++ b/test/processor/fixtures/logical-with-characteristics.json @@ -0,0 +1,26 @@ +{ + "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 + } + ] +} From d8158a493d73e1ddb7b6c188fcc91ee23b3736bf Mon Sep 17 00:00:00 2001 From: Mint Thompson Date: Tue, 16 Jan 2024 11:11:16 -0500 Subject: [PATCH 2/2] Add non-characteristic extension to Logical with characteristics --- test/extractor/CaretValueRuleExtractor.test.ts | 18 +++++++++++++++--- .../fixtures/logical-with-characteristics.json | 4 ++++ .../StructureDefinitionProcessor.test.ts | 1 + .../fixtures/logical-with-characteristics.json | 4 ++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/test/extractor/CaretValueRuleExtractor.test.ts b/test/extractor/CaretValueRuleExtractor.test.ts index 15fc3141..afb5230e 100644 --- a/test/extractor/CaretValueRuleExtractor.test.ts +++ b/test/extractor/CaretValueRuleExtractor.test.ts @@ -90,11 +90,23 @@ describe('CaretValueRuleExtractor', () => { defs, config ); - // the rule on "kind" may be removed later by an optimizer, - // but there should not be any "extension" rules. + // 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/) + caretPath: expect.stringMatching(/^extension\[\d+\]\.url/), + value: 'http://hl7.org/fhir/tools/StructureDefinition/logical-target' }) ); }); diff --git a/test/extractor/fixtures/logical-with-characteristics.json b/test/extractor/fixtures/logical-with-characteristics.json index 5aae8a86..069a6e98 100644 --- a/test/extractor/fixtures/logical-with-characteristics.json +++ b/test/extractor/fixtures/logical-with-characteristics.json @@ -21,6 +21,10 @@ { "url": "http://hl7.org/fhir/tools/StructureDefinition/logical-target", "valueBoolean": true + }, + { + "url": "http://hl7.org/fhir/sushi-test/StructureDefinition/other-extension", + "valueString": "something else" } ] } diff --git a/test/processor/StructureDefinitionProcessor.test.ts b/test/processor/StructureDefinitionProcessor.test.ts index 45bef63f..6760442d 100644 --- a/test/processor/StructureDefinitionProcessor.test.ts +++ b/test/processor/StructureDefinitionProcessor.test.ts @@ -370,6 +370,7 @@ describe('StructureDefinitionProcessor', () => { 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'); diff --git a/test/processor/fixtures/logical-with-characteristics.json b/test/processor/fixtures/logical-with-characteristics.json index 5aae8a86..069a6e98 100644 --- a/test/processor/fixtures/logical-with-characteristics.json +++ b/test/processor/fixtures/logical-with-characteristics.json @@ -21,6 +21,10 @@ { "url": "http://hl7.org/fhir/tools/StructureDefinition/logical-target", "valueBoolean": true + }, + { + "url": "http://hl7.org/fhir/sushi-test/StructureDefinition/other-extension", + "valueString": "something else" } ] }