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
66 changes: 59 additions & 7 deletions examples/browser/cql4browsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class CodeService {
for (const oid in valueSetsJson) {
this.valueSets[oid] = {};
for (const version in valueSetsJson[oid]) {
const codes = valueSetsJson[oid][version].map((code) => new datatypes_1.Code(code.code, code.system, code.version));
const codes = valueSetsJson[oid][version].map((code) => new datatypes_1.Code(code.code, code.system, code.version, code.display));
this.valueSets[oid][version] = new datatypes_1.ValueSet(oid, version, codes);
}
}
Expand Down Expand Up @@ -192,7 +192,18 @@ class Record {
getCode(field) {
const val = this._recursiveGet(field);
if (val != null && typeof val === 'object') {
return new DT.Code(val.code, val.system, val.version);
return new DT.Code(val.code, val.system, val.version, val.display);
}
}
getCodeOrCodes(field) {
const val = this._recursiveGet(field);
if (val != null) {
if (Array.isArray(val)) {
return val.map(v => new DT.Code(v.code, v.system, v.version, v.display));
}
else if (typeof val === 'object') {
return [new DT.Code(val.code, val.system, val.version, val.display)];
}
}
}
}
Expand Down Expand Up @@ -401,6 +412,9 @@ class CodeSystem extends Vocabulary {
this.version = version;
this.name = name;
}
hasMatch(_code) {
throw new Error('In CodeSystem operation not yet supported.');
}
}
exports.CodeSystem = CodeSystem;
class CQLValueSet extends Vocabulary {
Expand Down Expand Up @@ -4856,23 +4870,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.Retrieve = void 0;
const expression_1 = require("./expression");
const util_1 = require("../util/util");
const comparison_1 = require("../util/comparison");
const builder_1 = require("./builder");
const clinical_1 = require("../datatypes/clinical");
class Retrieve extends expression_1.Expression {
constructor(json) {
super(json);
this.datatype = json.dataType;
this.templateId = json.templateId;
this.codeProperty = json.codeProperty;
this.codeComparator = json.codeComparator;
this.codes = (0, builder_1.build)(json.codes);
this.dateProperty = json.dateProperty;
this.dateRange = (0, builder_1.build)(json.dateRange);
}
async exec(ctx) {
// Object with retrieve information to pass back to patient source
// Always assign datatype. Assign codeProperty and dateProperty if present
// Always assign datatype. Assign the others only if present
const retrieveDetails = {
datatype: this.datatype,
...(this.codeProperty ? { codeProperty: this.codeProperty } : {}),
...(this.codeComparator ? { codeComparator: this.codeComparator } : {}),
...(this.dateProperty ? { dateProperty: this.dateProperty } : {})
};
if (this.codes) {
Expand Down Expand Up @@ -4910,17 +4928,51 @@ class Retrieve extends expression_1.Expression {
return records;
}
recordMatchesCodesOrVS(record, codes) {
if ((0, util_1.typeIsArray)(codes)) {
return codes.some(c => c.hasMatch(record.getCode(this.codeProperty)));
const recordCodeValue = record.getCodeOrCodes(this.codeProperty);
if (!recordCodeValue || recordCodeValue.length == 0) {
return false;
}
switch (this.codeComparator) {
case 'in':
return this._in(recordCodeValue, codes);
case '~':
return this._equivalent(recordCodeValue, codes);
case '=':
return this._equal(recordCodeValue, codes);
}
}
_in(lhs, rhs) {
if (rhs instanceof clinical_1.ValueSet || rhs instanceof clinical_1.CodeSystem) {
return rhs.hasMatch(lhs);
}
else {
// for code lists, fallback to the implementation in ~
return this._equivalent(lhs, rhs);
}
}
_equivalent(lhs, rhs) {
if (rhs instanceof clinical_1.CodeSystem) {
throw new Error("Operator '~' is not defined for Code ~ CodeSystem");
}
else if (rhs instanceof clinical_1.ValueSet) {
// TODO: explain
return rhs.hasMatch(lhs);
}
else {
return codes.hasMatch(record.getCode(this.codeProperty));
return rhs.some(c => c.hasMatch(lhs));
}
}
_equal(lhs, rhs) {
if (rhs instanceof clinical_1.CodeSystem) {
throw new Error("Operator '=' is not defined for Code = CodeSystem");
}
const rhsCodes = rhs instanceof clinical_1.ValueSet ? rhs.expand() : rhs;
return rhsCodes.some(c => lhs.some(v => (0, comparison_1.equals)(c, v)));
}
}
exports.Retrieve = Retrieve;

},{"../util/util":59,"./builder":17,"./expression":23}],26:[function(require,module,exports){
},{"../datatypes/clinical":6,"../util/comparison":53,"../util/util":59,"./builder":17,"./expression":23}],26:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Instance = void 0;
Expand Down
2 changes: 1 addition & 1 deletion src/cql-code-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CodeService implements TerminologyProvider {
this.valueSets[oid] = {};
for (const version in valueSetsJson[oid]) {
const codes = valueSetsJson[oid][version].map(
(code: any) => new Code(code.code, code.system, code.version)
(code: any) => new Code(code.code, code.system, code.version, code.display)
);
this.valueSets[oid][version] = new ValueSet(oid, version, codes);
}
Expand Down
13 changes: 12 additions & 1 deletion src/cql-patient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,18 @@ export class Record implements RecordObject {
getCode(field: any) {
const val = this._recursiveGet(field);
if (val != null && typeof val === 'object') {
return new DT.Code(val.code, val.system, val.version);
return new DT.Code(val.code, val.system, val.version, val.display);
}
}

getCodeOrCodes(field: any): DT.Code[] | undefined {
const val = this._recursiveGet(field);
if (val != null) {
if (Array.isArray(val)) {
return val.map(v => new DT.Code(v.code, v.system, v.version, v.display));
} else if (typeof val === 'object') {
return [new DT.Code(val.code, val.system, val.version, val.display)];
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/datatypes/clinical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export class CodeSystem extends Vocabulary {
) {
super(id, version, name);
}

hasMatch(_code: any) {
throw new Error('In CodeSystem operation not yet supported.');
}
}

export class CQLValueSet extends Vocabulary {
Expand Down
54 changes: 49 additions & 5 deletions src/elm/external.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Expression } from './expression';
import { resolveValueSet, typeIsArray } from '../util/util';
import { equals } from '../util/comparison';
import { Context } from '../runtime/context';
import { build } from './builder';
import { RetrieveDetails } from '../types/cql-patient.interfaces';
import { Code, CQLValueSet } from '../datatypes/clinical';
import { Code, CodeSystem, CQLValueSet, ValueSet } from '../datatypes/clinical';

export class Retrieve extends Expression {
datatype: string;
templateId?: string;
codeProperty?: string;
codeComparator?: 'in' | '=' | '~';
codes?: Expression | null;
dateProperty?: string;
dateRange?: Expression | null;
Expand All @@ -18,17 +20,19 @@ export class Retrieve extends Expression {
this.datatype = json.dataType;
this.templateId = json.templateId;
this.codeProperty = json.codeProperty;
this.codeComparator = json.codeComparator;
this.codes = build(json.codes) as Expression;
this.dateProperty = json.dateProperty;
this.dateRange = build(json.dateRange) as Expression;
}

async exec(ctx: Context) {
// Object with retrieve information to pass back to patient source
// Always assign datatype. Assign codeProperty and dateProperty if present
// Always assign datatype. Assign the others only if present
const retrieveDetails: RetrieveDetails = {
datatype: this.datatype,
...(this.codeProperty ? { codeProperty: this.codeProperty } : {}),
...(this.codeComparator ? { codeComparator: this.codeComparator } : {}),
...(this.dateProperty ? { dateProperty: this.dateProperty } : {})
};

Expand Down Expand Up @@ -79,10 +83,50 @@ export class Retrieve extends Expression {
}

recordMatchesCodesOrVS(record: any, codes: any) {
if (typeIsArray(codes)) {
return (codes as any[]).some(c => c.hasMatch(record.getCode(this.codeProperty)));
const recordCodeValue = record.getCodeOrCodes(this.codeProperty);

if (!recordCodeValue || recordCodeValue.length == 0) {
return false;
}

switch (this.codeComparator) {
case 'in':
return this._in(recordCodeValue, codes);

case '~':
return this._equivalent(recordCodeValue, codes);

case '=':
return this._equal(recordCodeValue, codes);
}
}

_in(lhs: Code[], rhs: any) {
if (rhs instanceof ValueSet || rhs instanceof CodeSystem) {
return rhs.hasMatch(lhs);
} else {
return codes.hasMatch(record.getCode(this.codeProperty));
// for code lists, fallback to the implementation in ~
return this._equivalent(lhs, rhs);
}
}

_equivalent(lhs: Code[], rhs: any) {
if (rhs instanceof CodeSystem) {
throw new Error("Operator '~' is not defined for Code ~ CodeSystem");
} else if (rhs instanceof ValueSet) {
// TODO: explain
return rhs.hasMatch(lhs);
} else {
return (rhs as Code[]).some(c => c.hasMatch(lhs));
}
}

_equal(lhs: Code[], rhs: any) {
if (rhs instanceof CodeSystem) {
throw new Error("Operator '=' is not defined for Code = CodeSystem");
}

const rhsCodes = rhs instanceof ValueSet ? rhs.expand() : (rhs as Code[]);
return rhsCodes.some(c => lhs.some(v => equals(c, v)));
}
}
2 changes: 2 additions & 0 deletions src/types/cql-patient.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface RecordObject {
get(field: any): any;
getId(): any;
getCode(field: any): any;
getCodeOrCodes(field: any): Code[] | undefined;
getDate(field: any): any;
getDateOrInterval(field: any): any;
_is?(typeSpecifier: AnyTypeSpecifier): boolean;
Expand All @@ -31,6 +32,7 @@ export interface RetrieveDetails {
datatype: string;
templateId?: string;
codeProperty?: string;
codeComparator?: 'in' | '=' | '~';
codes?: Code[] | ValueSet;
dateProperty?: string;
dateRange?: Interval;
Expand Down
9 changes: 8 additions & 1 deletion test/cql-patient-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,14 @@ describe('Record', () => {
it('should get codes', () => {
encRecord
.getCode('code')
.should.eql(new DT.Code('185349003', '2.16.840.1.113883.6.96', '2013-09'));
.should.eql(
new DT.Code(
'185349003',
'2.16.840.1.113883.6.96',
'2013-09',
'Encounter for "check-up" (procedure)'
)
);
});

it('should get dates', () => {
Expand Down
51 changes: 48 additions & 3 deletions test/elm/external/data.cql
Original file line number Diff line number Diff line change
@@ -1,20 +1,65 @@
// @Test: Retrieve
include Included version '1' called included

codesystem "SNOMED": '2.16.840.1.113883.6.96'
codesystem "SNOMED": '2.16.840.1.113883.6.96' version '2013-09'
valueset "Ambulatory/ED Visit": '2.16.840.1.113883.3.464.1003.101.12.1061'
valueset "Annual Wellness Visit": '2.16.840.1.113883.3.526.3.1240'
code "Viral pharyngitis code": '1532007' from "SNOMED" display 'Viral pharyngitis (disorder)'
concept "Viral pharyngitis": { "Viral pharyngitis code" } display 'Viral pharyngitis (disorder)'

valueset "Body locations": '2.16.840.1.113762.1.4.1181.77' // Pain body location reference set
code "Nose code": '45206002' from "SNOMED" display 'Nasal structure (body structure)'

// duplicated definitions purely for readability of the large cross-product of test cases
codesystem "myCodesystem": '2.16.840.1.113883.6.96' version '2013-09'
valueset "myValueSet": '2.16.840.1.113883.3.464.1003.102.12.1011'
code "myCode": '1532007' from "myCodesystem" display 'Viral pharyngitis (disorder)'
concept "myConcept": { "myCode" } display 'Viral pharyngitis (disorder)'
define myCodeList: { myCode }

define Conditions: [Condition]
define ConditionsByVS: [Condition: "myValueSet"]
define ConditionsByCS: [Condition: "myCodesystem"]
define ConditionsByCode: [Condition: "myCode"]
define ConditionsByConcept: [Condition: "myConcept"]
define ConditionsByCodeList: [Condition: "myCodeList"]

define ConditionsByCodeInCS: [Condition: code in "myCodesystem"]
define ConditionsByCodeEqualsCS: [Condition: code = "myCodesystem"]
define ConditionsByCodeEquivCS: [Condition: code ~ "myCodesystem"]

define ConditionsByCodeInVS: [Condition: code in "myValueSet"]
define ConditionsByCodeEqualsVS: [Condition: code = "myValueSet"]
define ConditionsByCodeEquivVS: [Condition: code ~ "myValueSet"]

define ConditionsByCodeInCode: [Condition: code in "myCode"]
define ConditionsByCodeEqualsCode: [Condition: code = "myCode"]
define ConditionsByCodeEquivCode: [Condition: code ~ "myCode"]

define ConditionsByCodeInConcept: [Condition: code in "myConcept"]
define ConditionsByCodeEqualsConcept: [Condition: code = "myConcept"]
define ConditionsByCodeEquivConcept: [Condition: code ~ "myConcept"]

define ConditionsByCodeInCodeList: [Condition: code in "myCodeList"]
define ConditionsByCodeEqualsCodeList: [Condition: code = "myCodeList"]
define ConditionsByCodeEquivCodeList: [Condition: code ~ "myCodeList"]


define ConditionsByBodySiteInVS: [Condition: bodySite in "Body locations"]
define ConditionsByBodySiteEqualsVS: [Condition: bodySite = "Body locations"]
define ConditionsByBodySiteEquivVS: [Condition: bodySite ~ "Body locations"]

define ConditionsByBodySiteInCode: [Condition: bodySite in "Nose code"]
define ConditionsByBodySiteEqualsCode: [Condition: bodySite = "Nose code"]
define ConditionsByBodySiteEquivCode: [Condition: bodySite ~ "Nose code"]


define Encounters: [Encounter]
define PharyngitisConditions: [Condition: included."Acute Pharyngitis"]
define AmbulatoryEncounters: [Encounter: "Ambulatory/ED Visit"]
define EncountersByCode: [Encounter: code in "Ambulatory/ED Visit"]
define WrongValueSet: [Condition: "Ambulatory/ED Visit"]
define WrongCodeProperty: [Encounter: status in "Ambulatory/ED Visit"]
define ConditionsByCode: [Condition: "Viral pharyngitis code"]
define ConditionsByConcept: [Condition: "Viral pharyngitis"]
define ConditionsByDate: [Condition] C where C.period during Interval[@2013-03-01T00:00:00.0, @2013-03-31T00:00:00.0)

// @Test: Included
Expand Down
Loading
Loading