Skip to content
Open
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
35 changes: 35 additions & 0 deletions tests/ocl/ocl-cm-provider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,38 @@ describe('OCLConceptMapProvider', () => {
expect(getMock).toHaveBeenCalledWith('/mappings/map-1/');
});

it.each([
['SAME-AS', 'equivalent'],
['NARROWER-THAN', 'source-is-narrower-than-target'],
['BROADER-THAN', 'source-is-broader-than-target'],
['NOT-SAME-AS', 'not-related-to'],
['NOT-EQUIVALENT', 'not-related-to'],
['narrower than', 'source-is-narrower-than-target'],
['Q-AND-A', 'related-to'],
[undefined, 'related-to']
])('should translate OCL map_type %s to FHIR relationship %s', async (mapType, expected) => {
const getMock = jest.fn().mockResolvedValue({ data: makeMapping({ map_type: mapType }) });
const provider = createProvider({ get: getMock });

const result = await provider.fetchConceptMapById('map-1');
expect(result.jsonObj.group[0].element[0].target[0].relationship).toBe(expected);
});

it('should not declare a source/target scope from OCL repo paths', async () => {
const mapping = makeMapping({
from_collection_url: '/orgs/TestOrg/collections/ColA/',
to_collection_url: '/orgs/TestOrg/collections/ColB/'
});
const getMock = jest.fn().mockResolvedValue({ data: mapping });
const provider = createProvider({ get: getMock });

const result = await provider.fetchConceptMapById('map-1');
// $translate resolves a scope as a ValueSet; an OCL path would fail with not-found
expect(result.sourceScope).toBeUndefined();
expect(result.targetScope).toBeUndefined();
expect(result.jsonObj.group[0].source).toBe('/orgs/TestOrg/sources/SourceA/');
});

it('should return null on HTTP error', async () => {
const getMock = jest.fn().mockRejectedValue(new Error('404'));
const provider = createProvider({ get: getMock });
Expand Down Expand Up @@ -255,6 +287,9 @@ describe('OCLConceptMapProvider', () => {
expect(cm.jsonObj.group[0].element).toHaveLength(2);
expect(cm.jsonObj.group[0].source).toBe('http://example.org/SourceA');
expect(cm.jsonObj.group[0].target).toBe('http://example.org/SourceB');
// a CodeSystem canonical is not a ValueSet, so it must not be declared as the scope
expect(cm.sourceScope).toBeUndefined();
expect(cm.targetScope).toBeUndefined();
});

it('should handle target-system parameter', async () => {
Expand Down
19 changes: 12 additions & 7 deletions tx/ocl/cm-ocl.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ class OCLConceptMapProvider extends AbstractConceptMapProvider {
name: id,
title: `${sourceId} to ${targetId}`,
status: 'active',
sourceScopeUri: group.sourceCanonical,
targetScopeUri: group.targetCanonical,
// No sourceScope/targetScope: a scope is a ValueSet, and $translate resolves it and
// checks the code against it. The systems are carried by the group instead.
group: [{
source: group.sourceCanonical,
target: group.targetCanonical,
Expand Down Expand Up @@ -422,8 +422,10 @@ class OCLConceptMapProvider extends AbstractConceptMapProvider {
name: `mapping-${id}`,
title: mapping.name || `Mapping ${id}`,
status: 'active',
sourceScopeUri: mapping.from_collection_url || mapping.fromCollectionUrl || source,
targetScopeUri: mapping.to_collection_url || mapping.toCollectionUrl || target,
// No sourceScope/targetScope: a scope is a ValueSet canonical, which $translate resolves
// and checks the code against. OCL only offers repo paths here (from_source_url or
// from_collection_url), which resolve to no ValueSet and made every translation fail
// with not-found. The systems are carried by the group instead.
group: [
{
source: sourceCanonical,
Expand Down Expand Up @@ -453,15 +455,18 @@ class OCLConceptMapProvider extends AbstractConceptMapProvider {
return new ConceptMap(json, 'R5');
}

// OCL map_type expresses "from concept <relation> to concept", which lines up
// with the R5 ConceptMapRelationship codes (source <relation> target).
#toRelationship(mapType) {
switch ((mapType || '').toUpperCase()) {
switch (String(mapType || '').trim().toUpperCase().replace(/[\s_]+/g, '-')) {
case 'SAME-AS':
return 'equivalent';
case 'NARROWER-THAN':
return 'narrower-than';
return 'source-is-narrower-than-target';
case 'BROADER-THAN':
return 'broader-than';
return 'source-is-broader-than-target';
case 'NOT-EQUIVALENT':
case 'NOT-SAME-AS':
return 'not-related-to';
default:
return 'related-to';
Expand Down
Loading