quality-measure-engine 0.2.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +34 -6
- data/Rakefile +2 -0
- data/VERSION +1 -1
- data/js/{map-reduce-utils.js → map_reduce_utils.js} +50 -85
- data/js/{underscore-min.js → underscore_min.js} +3 -2
- data/lib/qme/database_access.rb +30 -0
- data/lib/qme/importer/code_system_helper.rb +3 -1
- data/lib/qme/importer/entry.rb +12 -22
- data/lib/qme/importer/generic_importer.rb +89 -30
- data/lib/qme/importer/patient_importer.rb +37 -21
- data/lib/qme/importer/property_matcher.rb +7 -2
- data/lib/qme/importer/section_importer.rb +31 -9
- data/lib/qme/map/map_reduce_builder.rb +14 -12
- data/lib/qme/map/map_reduce_executor.rb +34 -98
- data/lib/qme/map/measure_calculation_job.rb +33 -0
- data/lib/qme/measure/database_loader.rb +18 -25
- data/lib/qme/measure/measure_loader.rb +70 -37
- data/lib/qme/measure/properties_builder.rb +184 -0
- data/lib/qme/measure/properties_converter.rb +27 -0
- data/lib/qme/quality_measure.rb +41 -0
- data/lib/qme/quality_report.rb +61 -0
- data/lib/quality-measure-engine.rb +19 -16
- data/lib/tasks/fixtures.rake +91 -0
- data/lib/tasks/measure.rake +55 -36
- data/lib/tasks/mongo.rake +13 -19
- data/lib/tasks/patient_random.rake +2 -3
- metadata +174 -102
- data/lib/qme/mongo_helpers.rb +0 -15
data/README.md
CHANGED
@@ -1,7 +1,22 @@
|
|
1
|
-
This project
|
1
|
+
This project is a library designed to calculate clinical quality measures over a given population. Quality measures are described via JSON and provide the details on what information is needed from a patient record to calculate a quality measure. The logic of the measure is described in JavaScript using the MapReduce algorithmic framework.
|
2
|
+
|
3
|
+
Usage
|
4
|
+
=====
|
5
|
+
|
6
|
+
Extracting Measure Data from a HITSP C32
|
7
|
+
----------------------------------------
|
8
|
+
|
9
|
+
Each quality measure will need to extract specific information from a HITSP C32 for calculation. First, for each quality measure, a QME::Importer::GenericImporter should be created by passing in the JSON definition of the quality measure.
|
10
|
+
|
11
|
+
Next, an instance of QME::Importer::PatientImporter should be obtained by calling instance (it follows the singleton pattern). Add the GenericImporters for each desired measure with the add measure method. Finally, you can get a JSON representation of a patient record with the necessary information extracted by calling parse_c32.
|
12
|
+
|
13
|
+
Calculating Quality Measures
|
14
|
+
----------------------------
|
15
|
+
|
16
|
+
Results of quality measures are represented by QME::QualityReport. This class provides ways to determine if a report has been calculated for a population in the database as well as ways to create jobs to run the calculations.
|
2
17
|
|
3
18
|
Environment
|
4
|
-
|
19
|
+
===========
|
5
20
|
|
6
21
|
This project currently uses Ruby 1.9.2 and is built using [Bundler](http://gembundler.com/). To get all of the dependencies for the project, first install bundler:
|
7
22
|
|
@@ -11,12 +26,25 @@ Then run bundler to grab all of the necessary gems:
|
|
11
26
|
|
12
27
|
bundle install
|
13
28
|
|
14
|
-
The Quality Measure engine relies on a MongoDB [MongoDB](http://www.mongodb.org/) running a minimum of version 1.
|
29
|
+
The Quality Measure engine relies on a MongoDB [MongoDB](http://www.mongodb.org/) running a minimum of version 1.8.* or higher. To get and install Mongo refer to:
|
30
|
+
|
31
|
+
http://www.mongodb.org/display/DOCS/Quickstart
|
15
32
|
|
16
|
-
|
33
|
+
It also relies on [Redis](http://redis.io/) for background jobs via [Resque](https://github.com/defunkt/resque). To install Redis, please refer to:
|
34
|
+
|
35
|
+
http://redis.io/download
|
36
|
+
|
37
|
+
You can also find information on Redis at the [Resque homepage](https://github.com/defunkt/resque). Resque is used by this project to calculate quality measures in background jobs. We also use [resque-status](https://github.com/quirkey/resque-status). Please consult the resque-status instructions for working with the resque-web application if you would like to use it to monitor status.
|
38
|
+
|
39
|
+
Running Resque Workers
|
40
|
+
----------------------
|
41
|
+
|
42
|
+
QME::QualityReport will kick off background jobs with Resque. For these jobs to to actually get performed, you need to be running resque workers. This can be done with the following:
|
43
|
+
|
44
|
+
QUEUE=* bundle exec rake resque:work
|
17
45
|
|
18
46
|
Testing
|
19
|
-
|
47
|
+
=======
|
20
48
|
|
21
49
|
This project uses [RSpec](http://github.com/rspec/rspec-core) for testing. To run the suite, just enter the following:
|
22
50
|
|
@@ -56,6 +84,6 @@ This project uses [metric_fu](http://metric-fu.rubyforge.org/) for source code a
|
|
56
84
|
The project is currently configured to run Flay, Flog, Churn, Reek and Roodi
|
57
85
|
|
58
86
|
Project Practices
|
59
|
-
|
87
|
+
=================
|
60
88
|
|
61
89
|
Please try to follow our [Coding Style Guides](http://github.com/eedrummer/styleguide). Additionally, we will be using git in a pattern similar to [Vincent Driessen's workflow](http://nvie.com/posts/a-successful-git-branching-model/). While feature branches are encouraged, they are not required to work on the project.
|
data/Rakefile
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'rspec/core/rake_task'
|
2
2
|
require 'yard'
|
3
3
|
require 'metric_fu'
|
4
|
+
require 'resque/tasks'
|
4
5
|
|
5
6
|
ENV['MEASURE_DIR'] = ENV['MEASURE_DIR'] || File.join('fixtures', 'measure_defs')
|
7
|
+
ENV['MEASURE_PROPS'] = ENV['MEASURE_PROPS'] || File.join('fixtures', 'measure_props')
|
6
8
|
|
7
9
|
Dir['lib/tasks/*.rake'].sort.each do |ext|
|
8
10
|
load ext
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
@@ -1,16 +1,24 @@
|
|
1
|
+
function() {
|
1
2
|
// Adds common utility functions to the root JS object. These are then
|
2
3
|
// available for use by the map-reduce functions for each measure.
|
3
4
|
// lib/qme/mongo_helpers.rb executes this function on a database
|
4
5
|
// connection.
|
5
|
-
(function() {
|
6
6
|
|
7
7
|
var root = this;
|
8
8
|
|
9
|
+
// Takes an arbitrary number of arrays and single values and returns a flattened
|
10
|
+
// array of all of the elements with any null values removed. For efficiency, if
|
11
|
+
// called with just a single array then it simply returns that array
|
12
|
+
root.normalize = function() {
|
13
|
+
if (arguments.length==1 && _.isArray(arguments[0]))
|
14
|
+
return arguments[0];
|
15
|
+
return _.compact(_.flatten(arguments));
|
16
|
+
}
|
17
|
+
|
9
18
|
// returns the number of values which fall between the supplied limits
|
10
19
|
// value may be a number or an array of numbers
|
11
20
|
root.inRange = function(value, min, max) {
|
12
|
-
|
13
|
-
value = [value];
|
21
|
+
value = normalize(value);
|
14
22
|
var count = 0;
|
15
23
|
for (i=0;i<value.length;i++) {
|
16
24
|
if ((value[i]>=min) && (value[i]<=max))
|
@@ -21,8 +29,7 @@
|
|
21
29
|
|
22
30
|
// returns the largest member of value that is within the supplied range
|
23
31
|
root.maxInRange = function(value, min, max) {
|
24
|
-
|
25
|
-
return null;
|
32
|
+
value = normalize(value);
|
26
33
|
var allInRange = _.select(value, function(v) {return v>=min && v<=max;});
|
27
34
|
return _.max(allInRange);
|
28
35
|
}
|
@@ -30,77 +37,63 @@
|
|
30
37
|
// returns the number of values which are less than the supplied limit
|
31
38
|
// value may be a number or an array of numbers
|
32
39
|
root.lessThan = function(value, max) {
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
for (i=0;i<value.length;i++) {
|
37
|
-
if (value[i]<=max)
|
38
|
-
count++;
|
39
|
-
}
|
40
|
-
return count;
|
40
|
+
value = normalize(value);
|
41
|
+
var matching = _.select(value, function(v) {return v<=max;});
|
42
|
+
return matching.length;
|
41
43
|
};
|
42
44
|
|
43
|
-
// Returns
|
44
|
-
//
|
45
|
-
// function always returns false
|
45
|
+
// Returns true if any conditions[i].end is within the specified period,
|
46
|
+
// false otherwise
|
46
47
|
root.conditionResolved = function(conditions, startDate, endDate) {
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
});
|
51
|
-
} else {
|
52
|
-
return false
|
48
|
+
conditions = normalize(conditions);
|
49
|
+
var resolvedWithinPeriod = function(condition) {
|
50
|
+
return (condition.end>=startDate && condition.end<=endDate);
|
53
51
|
};
|
52
|
+
return _.any(conditions, resolvedWithinPeriod);
|
54
53
|
};
|
55
54
|
|
56
55
|
// Returns the minimum of readings[i].value where readings[i].date is in
|
57
56
|
// the supplied startDate and endDate. If no reading meet this criteria,
|
58
57
|
// returns defaultValue.
|
59
58
|
root.minValueInDateRange = function(readings, startDate, endDate, defaultValue) {
|
59
|
+
readings = normalize(readings);
|
60
|
+
|
60
61
|
var readingInDateRange = function(reading) {
|
61
|
-
|
62
|
-
return result;
|
62
|
+
return (reading.date>=startDate && reading.date<=endDate);
|
63
63
|
};
|
64
64
|
|
65
|
-
if (!readings || readings.length<1)
|
66
|
-
return defaultValue;
|
67
|
-
|
68
65
|
var allInDateRange = _.select(readings, readingInDateRange);
|
69
|
-
|
70
|
-
if (min)
|
71
|
-
return min.value;
|
72
|
-
else
|
66
|
+
if (allInDateRange.length==0)
|
73
67
|
return defaultValue;
|
68
|
+
var min = _.min(allInDateRange, function(reading) {return reading.value;});
|
69
|
+
return min.value;
|
74
70
|
};
|
75
71
|
|
76
72
|
// Returns the most recent readings[i].value where readings[i].date is in
|
77
73
|
// the supplied startDate and endDate. If no reading meet this criteria,
|
78
74
|
// returns defaultValue.
|
79
75
|
root.latestValueInDateRange = function(readings, startDate, endDate, defaultValue) {
|
76
|
+
readings = normalize(readings);
|
77
|
+
|
80
78
|
var readingInDateRange = function(reading) {
|
81
|
-
|
82
|
-
return result;
|
79
|
+
return (reading.date>=startDate && reading.date<=endDate);
|
83
80
|
};
|
84
81
|
|
85
|
-
if (!readings || readings.length<1)
|
86
|
-
return defaultValue;
|
87
|
-
|
88
82
|
var allInDateRange = _.select(readings, readingInDateRange);
|
89
|
-
|
90
|
-
if (latest)
|
91
|
-
return latest.value;
|
92
|
-
else
|
83
|
+
if (allInDateRange.length==0)
|
93
84
|
return defaultValue;
|
85
|
+
var latest = _.max(allInDateRange, function(reading) {return reading.date;});
|
86
|
+
return latest.value;
|
94
87
|
};
|
95
88
|
|
96
89
|
// Returns the number of actions that occur within the specified time period of
|
97
90
|
// something. The first two arguments are arrays or single-valued time stamps in
|
98
91
|
// seconds-since-the-epoch, timePeriod is in seconds.
|
99
92
|
root.actionFollowingSomething = function(something, action, timePeriod) {
|
100
|
-
|
101
|
-
|
102
|
-
if (
|
103
|
-
|
93
|
+
something = normalize(something);
|
94
|
+
action = normalize(action);
|
95
|
+
if (timePeriod===undefined)
|
96
|
+
timePeriod=Infinity;
|
104
97
|
|
105
98
|
var result = 0;
|
106
99
|
for (i=0; i<something.length; i++) {
|
@@ -114,61 +107,33 @@
|
|
114
107
|
return result;
|
115
108
|
}
|
116
109
|
|
117
|
-
// Returns the number of actions that occur after
|
118
|
-
// something. The first two arguments are arrays or single-valued time stamps in
|
119
|
-
// seconds-since-the-epoch.
|
120
|
-
root.actionAfterSomething = function(something, action) {
|
121
|
-
if (!_.isArray(something))
|
122
|
-
something = [something];
|
123
|
-
if (!_.isArray(action))
|
124
|
-
action = [action];
|
125
|
-
|
126
|
-
var result = 0;
|
127
|
-
for (i=0; i<something.length; i++) {
|
128
|
-
var timeStamp = something[i];
|
129
|
-
for (j=0; j<action.length;j++) {
|
130
|
-
if (action[j]>=timeStamp )
|
131
|
-
result++;
|
132
|
-
}
|
133
|
-
}
|
134
|
-
return result;
|
135
|
-
}
|
136
|
-
|
137
110
|
// Returns all members of the values array that fall between min and max inclusive
|
138
111
|
root.selectWithinRange = function(values, min, max) {
|
112
|
+
values = normalize(values);
|
139
113
|
return _.select(values, function(value) { return value<=max && value>=min; });
|
140
114
|
}
|
141
115
|
|
142
116
|
root.map = function(record, population, denominator, numerator, exclusion) {
|
143
|
-
var value = {population:
|
117
|
+
var value = {population: false, denominator: false, numerator: false,
|
118
|
+
exclusions: false, antinumerator: false, patient_id: record._id,
|
119
|
+
first: record.first, last: record.last, gender: record.gender,
|
120
|
+
birthdate: record.birthdate};
|
144
121
|
var patient = record._id;
|
145
122
|
if (population()) {
|
146
|
-
value.population
|
123
|
+
value.population = true;
|
147
124
|
if (denominator()) {
|
148
|
-
value.denominator
|
125
|
+
value.denominator = true;
|
149
126
|
if (numerator()) {
|
150
|
-
value.numerator
|
127
|
+
value.numerator = true;
|
151
128
|
} else if (exclusion()) {
|
152
|
-
value.exclusions
|
153
|
-
value.denominator
|
129
|
+
value.exclusions = true;
|
130
|
+
value.denominator = false;
|
154
131
|
} else {
|
155
|
-
value.antinumerator
|
132
|
+
value.antinumerator = true;
|
156
133
|
}
|
157
134
|
}
|
158
135
|
}
|
159
|
-
emit(
|
160
|
-
};
|
161
|
-
|
162
|
-
root.reduce = function (key, values) {
|
163
|
-
var total = {population: [], denominator: [], numerator: [], exclusions: [], antinumerator: []};
|
164
|
-
for (var i = 0; i < values.length; i++) {
|
165
|
-
total.population = total.population.concat(values[i].population);
|
166
|
-
total.denominator = total.denominator.concat(values[i].denominator);
|
167
|
-
total.numerator = total.numerator.concat(values[i].numerator);
|
168
|
-
total.exclusions = total.exclusions.concat(values[i].exclusions);
|
169
|
-
total.antinumerator = total.antinumerator.concat(values[i].antinumerator);
|
170
|
-
}
|
171
|
-
return total;
|
136
|
+
emit(ObjectId(), value);
|
172
137
|
};
|
173
138
|
|
174
|
-
}
|
139
|
+
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
function(){
|
1
2
|
// Underscore.js 1.1.2
|
2
3
|
// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
|
3
4
|
// Underscore is freely distributable under the MIT license.
|
@@ -5,7 +6,7 @@
|
|
5
6
|
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
6
7
|
// For all details and documentation:
|
7
8
|
// http://documentcloud.github.com/underscore
|
8
|
-
|
9
|
+
var o=this,A=o._,r=typeof StopIteration!=="undefined"?StopIteration:"__break__",k=Array.prototype,m=Object.prototype,i=k.slice,B=k.unshift,C=m.toString,p=m.hasOwnProperty,s=k.forEach,t=k.map,u=k.reduce,v=k.reduceRight,w=k.filter,x=k.every,y=k.some,n=k.indexOf,z=k.lastIndexOf;m=Array.isArray;var D=Object.keys,c=function(a){return new l(a)};if(typeof exports!=="undefined")exports._=c;o._=c;c.VERSION="1.1.2";var j=c.each=c.forEach=function(a,b,d){try{if(s&&a.forEach===s)a.forEach(b,d);else if(c.isNumber(a.length))for(var e=
|
9
10
|
0,f=a.length;e<f;e++)b.call(d,a[e],e,a);else for(e in a)p.call(a,e)&&b.call(d,a[e],e,a)}catch(g){if(g!=r)throw g;}return a};c.map=function(a,b,d){if(t&&a.map===t)return a.map(b,d);var e=[];j(a,function(f,g,h){e[e.length]=b.call(d,f,g,h)});return e};c.reduce=c.foldl=c.inject=function(a,b,d,e){var f=d!==void 0;if(u&&a.reduce===u){if(e)b=c.bind(b,e);return f?a.reduce(b,d):a.reduce(b)}j(a,function(g,h,E){d=!f&&h===0?g:b.call(e,d,g,h,E)});return d};c.reduceRight=c.foldr=function(a,b,d,e){if(v&&a.reduceRight===
|
10
11
|
v){if(e)b=c.bind(b,e);return d!==void 0?a.reduceRight(b,d):a.reduceRight(b)}a=(c.isArray(a)?a.slice():c.toArray(a)).reverse();return c.reduce(a,b,d,e)};c.find=c.detect=function(a,b,d){var e;j(a,function(f,g,h){if(b.call(d,f,g,h)){e=f;c.breakLoop()}});return e};c.filter=c.select=function(a,b,d){if(w&&a.filter===w)return a.filter(b,d);var e=[];j(a,function(f,g,h){if(b.call(d,f,g,h))e[e.length]=f});return e};c.reject=function(a,b,d){var e=[];j(a,function(f,g,h){b.call(d,f,g,h)||(e[e.length]=f)});return e};
|
11
12
|
c.every=c.all=function(a,b,d){b=b||c.identity;if(x&&a.every===x)return a.every(b,d);var e=true;j(a,function(f,g,h){(e=e&&b.call(d,f,g,h))||c.breakLoop()});return e};c.some=c.any=function(a,b,d){b=b||c.identity;if(y&&a.some===y)return a.some(b,d);var e=false;j(a,function(f,g,h){if(e=b.call(d,f,g,h))c.breakLoop()});return e};c.include=c.contains=function(a,b){if(n&&a.indexOf===n)return a.indexOf(b)!=-1;var d=false;j(a,function(e){if(d=e===b)c.breakLoop()});return d};c.invoke=function(a,b){var d=i.call(arguments,
|
@@ -21,4 +22,4 @@ b.multiline;if(d!=="object")return false;if(a.length&&a.length!==b.length)return
|
|
21
22
|
a.callee)};c.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};c.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};c.isNumber=function(a){return a===+a||C.call(a)==="[object Number]"};c.isBoolean=function(a){return a===true||a===false};c.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};c.isRegExp=function(a){return!!(a&&a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};c.isNaN=function(a){return c.isNumber(a)&&isNaN(a)};c.isNull=function(a){return a===
|
22
23
|
null};c.isUndefined=function(a){return typeof a=="undefined"};c.noConflict=function(){o._=A;return this};c.identity=function(a){return a};c.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};c.breakLoop=function(){throw r;};c.mixin=function(a){j(c.functions(a),function(b){F(b,c[b]=a[b])})};var G=0;c.uniqueId=function(a){var b=G++;return a?a+b:b};c.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g};c.template=function(a,b){var d=c.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+
|
23
24
|
a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(e,f){return"',"+f.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(e,f){return"');"+f.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return b?d(b):d};var l=function(a){this._wrapped=a};c.prototype=l.prototype;var q=function(a,b){return b?c(a).chain():a},F=function(a,b){l.prototype[a]=function(){var d=
|
24
|
-
i.call(arguments);B.call(d,this._wrapped);return q(b.apply(c,d),this._chain)}};c.mixin(c);j(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=k[a];l.prototype[a]=function(){b.apply(this._wrapped,arguments);return q(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];l.prototype[a]=function(){return q(b.apply(this._wrapped,arguments),this._chain)}});l.prototype.chain=function(){this._chain=true;return this};l.prototype.value=function(){return this._wrapped}}
|
25
|
+
i.call(arguments);B.call(d,this._wrapped);return q(b.apply(c,d),this._chain)}};c.mixin(c);j(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=k[a];l.prototype[a]=function(){b.apply(this._wrapped,arguments);return q(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];l.prototype[a]=function(){return q(b.apply(this._wrapped,arguments),this._chain)}});l.prototype.chain=function(){this._chain=true;return this};l.prototype.value=function(){return this._wrapped}}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module QME
|
2
|
+
module DatabaseAccess
|
3
|
+
|
4
|
+
# Set up the information to connect to the database. Database host
|
5
|
+
# and port may be set using the environment variables TEST_DB_HOST
|
6
|
+
# and TEST_DB_PORT which default to localhost and 27017 respectively.
|
7
|
+
# @param [String] db_name the name of the database to use
|
8
|
+
def determine_connection_information(db_name = nil)
|
9
|
+
@db_name = ENV['DB_NAME'] || db_name || 'test'
|
10
|
+
@db_host = ENV['TEST_DB_HOST'] || 'localhost'
|
11
|
+
@db_port = ENV['TEST_DB_PORT'] ? ENV['TEST_DB_PORT'].to_i : 27017
|
12
|
+
end
|
13
|
+
|
14
|
+
# Directly inject an instance of a database
|
15
|
+
# @param [Mongo::Database] db the database that you would like the object to use
|
16
|
+
def inject_db(db)
|
17
|
+
@db = db
|
18
|
+
end
|
19
|
+
|
20
|
+
# Lazily creates a connection to the database and initializes the
|
21
|
+
# JavaScript environment
|
22
|
+
# @return [Mongo::Connection]
|
23
|
+
def get_db
|
24
|
+
if @db == nil
|
25
|
+
@db = Mongo::Connection.new(@db_host, @db_port).db(@db_name)
|
26
|
+
end
|
27
|
+
@db
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -7,11 +7,13 @@ module QME
|
|
7
7
|
'2.16.840.1.113883.6.1' => 'LOINC',
|
8
8
|
'2.16.840.1.113883.6.96' => 'SNOMED-CT',
|
9
9
|
'2.16.840.1.113883.6.12' => 'CPT',
|
10
|
+
#'2.16.840.1.113883.3.88.12.80.32' => 'CPT',
|
10
11
|
'2.16.840.1.113883.6.88' => 'RxNorm',
|
11
12
|
'2.16.840.1.113883.6.103' => 'ICD-9-CM',
|
12
13
|
'2.16.840.1.113883.6.104' => 'ICD-9-CM',
|
13
14
|
'2.16.840.1.113883.6.90' => 'ICD-10-CM',
|
14
|
-
'2.16.840.1.113883.6.14' => 'HCPCS'
|
15
|
+
'2.16.840.1.113883.6.14' => 'HCPCS',
|
16
|
+
'2.16.840.1.113883.6.59' => 'CVX'
|
15
17
|
}
|
16
18
|
|
17
19
|
# Returns the name of a code system given an oid
|
data/lib/qme/importer/entry.rb
CHANGED
@@ -2,15 +2,14 @@ module QME
|
|
2
2
|
module Importer
|
3
3
|
# Object that represents a CDA Entry (or act, observation, etc.)
|
4
4
|
class Entry
|
5
|
-
attr_accessor :start_time, :end_time, :time
|
6
|
-
attr_reader :
|
7
|
-
|
5
|
+
attr_accessor :start_time, :end_time, :time, :status
|
6
|
+
attr_reader :codes, :value
|
7
|
+
|
8
8
|
def initialize
|
9
9
|
@codes = {}
|
10
|
-
@status = {}
|
11
10
|
@value = {}
|
12
11
|
end
|
13
|
-
|
12
|
+
|
14
13
|
def Entry.from_event_hash(event)
|
15
14
|
entry = Entry.new
|
16
15
|
entry.add_code(event['code'], event['code_set'])
|
@@ -18,7 +17,7 @@ module QME
|
|
18
17
|
entry.set_value(event['value'], event['unit'])
|
19
18
|
entry
|
20
19
|
end
|
21
|
-
|
20
|
+
|
22
21
|
# Add a code into the Entry
|
23
22
|
# @param [String] code the code to add
|
24
23
|
# @param [String] code_system the code system that the code belongs to
|
@@ -26,15 +25,7 @@ module QME
|
|
26
25
|
@codes[code_system] ||= []
|
27
26
|
@codes[code_system] << code
|
28
27
|
end
|
29
|
-
|
30
|
-
# Set a status for the Entry
|
31
|
-
# @param [String] status_code the code to set
|
32
|
-
# @param [String] code_system the code system that the status_code belongs to
|
33
|
-
def set_status(status_code, code_system)
|
34
|
-
@status[:code] = status_code
|
35
|
-
@status[:code_system] = code_system
|
36
|
-
end
|
37
|
-
|
28
|
+
|
38
29
|
# Sets the value for the entry
|
39
30
|
# @param [String] scalar the value
|
40
31
|
# @param [String] units the units of the scalar value
|
@@ -42,24 +33,23 @@ module QME
|
|
42
33
|
@value[:scalar] = scalar
|
43
34
|
@value[:units] = units
|
44
35
|
end
|
45
|
-
|
36
|
+
|
46
37
|
# Checks if a code is in the list of possible codes
|
47
38
|
# @param [Array] code_set an Array of Hashes that describe the values for code sets
|
48
39
|
# @return [true, false] whether the code is in the list of desired codes
|
49
40
|
def is_in_code_set?(code_set)
|
50
41
|
@codes.keys.each do |code_system|
|
51
|
-
|
52
|
-
|
42
|
+
all_codes_in_system = code_set.find_all {|set| set['set'] == code_system}
|
43
|
+
all_codes_in_system.each do |codes_in_system|
|
53
44
|
matching_codes = codes_in_system['values'] & @codes[code_system]
|
54
45
|
if matching_codes.length > 0
|
55
46
|
return true
|
56
47
|
end
|
57
48
|
end
|
58
49
|
end
|
59
|
-
|
60
50
|
false
|
61
51
|
end
|
62
|
-
|
52
|
+
|
63
53
|
# Tries to find a single point in time for this entry. Will first return time if it is present,
|
64
54
|
# then fall back to start_time and finally end_time
|
65
55
|
def as_point_in_time
|
@@ -71,13 +61,13 @@ module QME
|
|
71
61
|
@end_time
|
72
62
|
end
|
73
63
|
end
|
74
|
-
|
64
|
+
|
75
65
|
# Checks to see if this Entry can be used as a date range
|
76
66
|
# @return [true, false] If the Entry has a start and end time returns true, false otherwise.
|
77
67
|
def is_date_range?
|
78
68
|
(! @start_time.nil?) && (! @end_time.nil?)
|
79
69
|
end
|
80
|
-
|
70
|
+
|
81
71
|
# Checks to see if this Entry is usable for measure calculation. This means that it contains
|
82
72
|
# at least one code and has one of its time properties set (start, end or time)
|
83
73
|
# @return [true, false]
|