quality-measure-engine 0.2.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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]