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 CHANGED
@@ -1,7 +1,22 @@
1
- This project will provide a library that can ingest HITSP C32's and ASTM CCR's and extract values needed to compute heath quality measures for a population. It will then be able to query over a population to compute how many people within a population conform to the measure.
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.6.* or higher. To get and install Mongo refer to :
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
- http://www.mongodb.org/display/DOCS/Quickstart
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.2
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
- if (!_.isArray(value))
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
- if (value==null)
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
- if (!_.isArray(value))
34
- value = [value];
35
- var count = 0;
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 the a boolean true when any entry within conditions[i].end is
44
- // ever less than the endDate. If no conditions meet this criteria, this
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
- if (conditions) {
48
- return _.any(conditions, function(condition) {
49
- return inRange(condition.end, startDate, endDate) > 0;
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
- var result = inRange(reading.date, startDate, endDate);
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
- var min = _.min(allInDateRange, function(reading) {return reading.value;});
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
- var result = inRange(reading.date, startDate, endDate);
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
- var latest = _.max(allInDateRange, function(reading) {return reading.date;});
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
- if (!_.isArray(something))
101
- something = [something];
102
- if (!_.isArray(action))
103
- action = [action];
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: [], denominator: [], numerator: [], exclusions: [], antinumerator: []};
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.push(patient);
123
+ value.population = true;
147
124
  if (denominator()) {
148
- value.denominator.push(patient);
125
+ value.denominator = true;
149
126
  if (numerator()) {
150
- value.numerator.push(patient);
127
+ value.numerator = true;
151
128
  } else if (exclusion()) {
152
- value.exclusions.push(patient);
153
- value.denominator.pop();
129
+ value.exclusions = true;
130
+ value.denominator = false;
154
131
  } else {
155
- value.antinumerator.push(patient);
132
+ value.antinumerator = true;
156
133
  }
157
134
  }
158
135
  }
159
- emit(null, value);
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
- (function(){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
+ 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
@@ -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 :status, :codes, :value
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
- codes_in_system = code_set.find {|set| set['set'] == code_system}
52
- if codes_in_system
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]