quality-measure-engine 1.1.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.gitignore +12 -0
  2. data/.travis.yml +16 -0
  3. data/Gemfile +5 -21
  4. data/Gemfile.lock +126 -0
  5. data/LICENSE.txt +13 -0
  6. data/README.md +23 -44
  7. data/Rakefile +6 -29
  8. data/lib/qme/bundle/bundle.rb +34 -0
  9. data/lib/qme/bundle/importer.rb +69 -0
  10. data/lib/qme/database_access.rb +7 -11
  11. data/lib/qme/map/map_reduce_builder.rb +4 -1
  12. data/lib/qme/map/map_reduce_executor.rb +55 -43
  13. data/lib/qme/map/measure_calculation_job.rb +24 -23
  14. data/lib/qme/quality_measure.rb +5 -5
  15. data/lib/qme/quality_report.rb +37 -19
  16. data/lib/qme/railtie.rb +7 -0
  17. data/lib/qme/tasks/bundle.rake +14 -0
  18. data/lib/qme/version.rb +3 -0
  19. data/lib/quality-measure-engine.rb +13 -25
  20. data/quality-measure-engine.gemspec +28 -0
  21. data/test/fixtures/bundles/just_measure_0002.zip +0 -0
  22. data/test/fixtures/delayed_backend_mongoid_jobs/queued_job.json +9 -0
  23. data/test/fixtures/measures/measure_metadata.json +52 -0
  24. data/test/fixtures/records/barry_berry.json +471 -0
  25. data/test/fixtures/records/billy_jones_ipp.json +78 -0
  26. data/test/fixtures/records/jane_jones_numerator.json +120 -0
  27. data/test/fixtures/records/jill_jones_denominator.json +78 -0
  28. data/test/simplecov_setup.rb +18 -0
  29. data/test/test_helper.rb +26 -0
  30. data/test/unit/qme/map/map_reduce_builder_test.rb +38 -0
  31. data/test/unit/qme/map/map_reduce_executor_test.rb +56 -0
  32. data/test/unit/qme/map/measure_calculation_job_test.rb +22 -0
  33. data/test/unit/qme/quality_measure_test.rb +14 -0
  34. data/{spec/qme/quality_report_spec.rb → test/unit/qme/quality_report_test.rb} +32 -20
  35. metadata +91 -115
  36. data/VERSION +0 -1
  37. data/js/map_reduce_utils.js +0 -173
  38. data/js/underscore_min.js +0 -25
  39. data/lib/qme/ext/record.rb +0 -43
  40. data/lib/qme/importer/entry.rb +0 -126
  41. data/lib/qme/importer/generic_importer.rb +0 -117
  42. data/lib/qme/importer/measure_properties_generator.rb +0 -39
  43. data/lib/qme/importer/property_matcher.rb +0 -110
  44. data/lib/qme/measure/database_loader.rb +0 -83
  45. data/lib/qme/measure/measure_loader.rb +0 -174
  46. data/lib/qme/measure/properties_builder.rb +0 -184
  47. data/lib/qme/measure/properties_converter.rb +0 -27
  48. data/lib/qme/randomizer/patient_randomization_job.rb +0 -47
  49. data/lib/qme/randomizer/patient_randomizer.rb +0 -250
  50. data/lib/qme/randomizer/random_patient_creator.rb +0 -47
  51. data/lib/qme_test.rb +0 -13
  52. data/lib/tasks/fixtures.rake +0 -91
  53. data/lib/tasks/measure.rake +0 -110
  54. data/lib/tasks/mongo.rake +0 -68
  55. data/lib/tasks/patient_random.rake +0 -45
  56. data/spec/qme/bundle_spec.rb +0 -37
  57. data/spec/qme/importer/generic_importer_spec.rb +0 -73
  58. data/spec/qme/importer/measure_properties_generator_spec.rb +0 -15
  59. data/spec/qme/importer/property_matcher_spec.rb +0 -174
  60. data/spec/qme/map/map_reduce_builder_spec.rb +0 -38
  61. data/spec/qme/map/measures_spec.rb +0 -38
  62. data/spec/qme/map/patient_mapper_spec.rb +0 -11
  63. data/spec/qme/measure_loader_spec.rb +0 -12
  64. data/spec/qme/properties_builder_spec.rb +0 -61
  65. data/spec/spec_helper.rb +0 -120
  66. data/spec/validate_measures_spec.rb +0 -21
data/js/underscore_min.js DELETED
@@ -1,25 +0,0 @@
1
- function(){
2
- // Underscore.js 1.1.2
3
- // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
4
- // Underscore is freely distributable under the MIT license.
5
- // Portions of Underscore are inspired or borrowed from Prototype,
6
- // Oliver Steele's Functional, and John Resig's Micro-Templating.
7
- // For all details and documentation:
8
- // http://documentcloud.github.com/underscore
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=
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===
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};
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,
13
- 2);return c.map(a,function(e){return(b?e[b]:e).apply(e,d)})};c.pluck=function(a,b){return c.map(a,function(d){return d[b]})};c.max=function(a,b,d){if(!b&&c.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};j(a,function(f,g,h){g=b?b.call(d,f,g,h):f;g>=e.computed&&(e={value:f,computed:g})});return e.value};c.min=function(a,b,d){if(!b&&c.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};j(a,function(f,g,h){g=b?b.call(d,f,g,h):f;g<e.computed&&(e={value:f,computed:g})});
14
- return e.value};c.sortBy=function(a,b,d){return c.pluck(c.map(a,function(e,f,g){return{value:e,criteria:b.call(d,e,f,g)}}).sort(function(e,f){var g=e.criteria,h=f.criteria;return g<h?-1:g>h?1:0}),"value")};c.sortedIndex=function(a,b,d){d=d||c.identity;for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(b)?e=g+1:f=g}return e};c.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(c.isArray(a))return a;if(c.isArguments(a))return i.call(a);return c.values(a)};c.size=function(a){return c.toArray(a).length};
15
- c.first=c.head=function(a,b,d){return b&&!d?i.call(a,0,b):a[0]};c.rest=c.tail=function(a,b,d){return i.call(a,c.isUndefined(b)||d?1:b)};c.last=function(a){return a[a.length-1]};c.compact=function(a){return c.filter(a,function(b){return!!b})};c.flatten=function(a){return c.reduce(a,function(b,d){if(c.isArray(d))return b.concat(c.flatten(d));b[b.length]=d;return b},[])};c.without=function(a){var b=i.call(arguments,1);return c.filter(a,function(d){return!c.include(b,d)})};c.uniq=c.unique=function(a,
16
- b){return c.reduce(a,function(d,e,f){if(0==f||(b===true?c.last(d)!=e:!c.include(d,e)))d[d.length]=e;return d},[])};c.intersect=function(a){var b=i.call(arguments,1);return c.filter(c.uniq(a),function(d){return c.every(b,function(e){return c.indexOf(e,d)>=0})})};c.zip=function(){for(var a=i.call(arguments),b=c.max(c.pluck(a,"length")),d=Array(b),e=0;e<b;e++)d[e]=c.pluck(a,""+e);return d};c.indexOf=function(a,b){if(n&&a.indexOf===n)return a.indexOf(b);for(var d=0,e=a.length;d<e;d++)if(a[d]===b)return d;
17
- return-1};c.lastIndexOf=function(a,b){if(z&&a.lastIndexOf===z)return a.lastIndexOf(b);for(var d=a.length;d--;)if(a[d]===b)return d;return-1};c.range=function(a,b,d){var e=i.call(arguments),f=e.length<=1;a=f?0:e[0];b=f?e[0]:e[1];d=e[2]||1;e=Math.max(Math.ceil((b-a)/d),0);f=0;for(var g=Array(e);f<e;){g[f++]=a;a+=d}return g};c.bind=function(a,b){var d=i.call(arguments,2);return function(){return a.apply(b||{},d.concat(i.call(arguments)))}};c.bindAll=function(a){var b=i.call(arguments,1);if(b.length==
18
- 0)b=c.functions(a);j(b,function(d){a[d]=c.bind(a[d],a)});return a};c.memoize=function(a,b){var d={};b=b||c.identity;return function(){var e=b.apply(this,arguments);return e in d?d[e]:d[e]=a.apply(this,arguments)}};c.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};c.defer=function(a){return c.delay.apply(c,[a,1].concat(i.call(arguments,1)))};c.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments));return b.apply(b,d)}};c.compose=
19
- function(){var a=i.call(arguments);return function(){for(var b=i.call(arguments),d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};c.keys=D||function(a){if(c.isArray(a))return c.range(0,a.length);var b=[],d;for(d in a)if(p.call(a,d))b[b.length]=d;return b};c.values=function(a){return c.map(a,c.identity)};c.functions=c.methods=function(a){return c.filter(c.keys(a),function(b){return c.isFunction(a[b])}).sort()};c.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});
20
- return a};c.clone=function(a){return c.isArray(a)?a.slice():c.extend({},a)};c.tap=function(a,b){b(a);return a};c.isEqual=function(a,b){if(a===b)return true;var d=typeof a;if(d!=typeof b)return false;if(a==b)return true;if(!a&&b||a&&!b)return false;if(a.isEqual)return a.isEqual(b);if(c.isDate(a)&&c.isDate(b))return a.getTime()===b.getTime();if(c.isNaN(a)&&c.isNaN(b))return false;if(c.isRegExp(a)&&c.isRegExp(b))return a.source===b.source&&a.global===b.global&&a.ignoreCase===b.ignoreCase&&a.multiline===
21
- b.multiline;if(d!=="object")return false;if(a.length&&a.length!==b.length)return false;d=c.keys(a);var e=c.keys(b);if(d.length!=e.length)return false;for(var f in a)if(!(f in b)||!c.isEqual(a[f],b[f]))return false;return true};c.isEmpty=function(a){if(c.isArray(a)||c.isString(a))return a.length===0;for(var b in a)if(p.call(a,b))return false;return true};c.isElement=function(a){return!!(a&&a.nodeType==1)};c.isArray=m||function(a){return!!(a&&a.concat&&a.unshift&&!a.callee)};c.isArguments=function(a){return!!(a&&
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===
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('"+
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=
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}}
@@ -1,43 +0,0 @@
1
- # Extensions to the Record model in health-data-standards to support
2
- # quality measure calculation
3
- class Record
4
-
5
- def procedures_performed
6
- @procedures_performed = procedures.to_a + immunizations.to_a + medications.to_a
7
- end
8
-
9
- def procedure_results
10
- @procedure_results ||= results.to_a + vital_signs.to_a + procedures.to_a
11
- end
12
-
13
- def laboratory_tests
14
- @laboratory_tests ||= results.to_a + vital_signs.to_a
15
- end
16
-
17
- def all_meds
18
- @all_meds ||= medications.to_a + immunizations.to_a
19
- end
20
-
21
- def active_diagnosis
22
- @active_diagnosis ||= conditions.any_of({:status => 'active'}, {:status => nil}, {:ordinality => 'principal'}).to_a +
23
- social_history.any_of({:status => 'active'}, {:status => nil}).to_a
24
- end
25
-
26
- def inactive_diagnosis
27
- @inactive_diagnosis ||= conditions.any_of({:status => 'inactive'}).to_a +
28
- social_history.any_of({:status => 'inactive'}).to_a
29
- end
30
-
31
- def resolved_diagnosis
32
- @resolved_diagnosis ||= conditions.any_of({:status => 'resolved'}).to_a +
33
- social_history.any_of({:status => 'resolved'}).to_a
34
- end
35
-
36
- def all_problems
37
- @all_problems ||= conditions.to_a + social_history.to_a
38
- end
39
-
40
- def all_devices
41
- @all_devices ||= conditions.to_a + procedures.to_a + care_goals.to_a + medical_equipment.to_a
42
- end
43
- end
@@ -1,126 +0,0 @@
1
- module QME
2
- module Importer
3
- # Object that represents a CDA Entry (or act, observation, etc.)
4
- class Entry
5
- attr_accessor :start_time, :end_time, :time, :status, :ordinality, :description
6
- attr_reader :codes, :value
7
-
8
- def initialize
9
- @codes = {}
10
- @value = {}
11
- end
12
-
13
- def self.from_event_hash(event)
14
- entry = Entry.new
15
- if event['code']
16
- entry.add_code(event['code'], event['code_set'])
17
- elsif event['codes']
18
- entry.instance_eval { @codes = event['codes'] }
19
- end
20
-
21
- entry.time = event['time']
22
- if event['value']
23
- entry.set_value(event['value'], event['unit'])
24
- end
25
- if event['description']
26
- entry.description = event['description']
27
- end
28
- if event['status']
29
- entry.status = event['status']
30
- end
31
- if event['ordinality']
32
- entry.ordinality = event['ordinality']
33
- end
34
- entry
35
- end
36
-
37
- # Add a code into the Entry
38
- # @param [String] code the code to add
39
- # @param [String] code_system the code system that the code belongs to
40
- def add_code(code, code_system)
41
- @codes[code_system] ||= []
42
- @codes[code_system] << code
43
- end
44
-
45
- # Sets the value for the entry
46
- # @param [String] scalar the value
47
- # @param [String] units the units of the scalar value
48
- def set_value(scalar, units=nil)
49
- @value[:scalar] = scalar
50
- @value[:units] = units
51
- end
52
-
53
- # Checks if a code is in the list of possible codes
54
- # @param [Array] code_set an Array of Hashes that describe the values for code sets
55
- # @return [true, false] whether the code is in the list of desired codes
56
- def is_in_code_set?(code_set)
57
- @codes.keys.each do |code_system|
58
- all_codes_in_system = code_set.find_all {|set| set['set'] == code_system}
59
- all_codes_in_system.each do |codes_in_system|
60
- matching_codes = codes_in_system['values'] & @codes[code_system]
61
- if matching_codes.length > 0
62
- return true
63
- end
64
- end
65
- end
66
- false
67
- end
68
-
69
- # Tries to find a single point in time for this entry. Will first return time if it is present,
70
- # then fall back to start_time and finally end_time
71
- def as_point_in_time
72
- if @time
73
- @time
74
- elsif @start_time
75
- @start_time
76
- else
77
- @end_time
78
- end
79
- end
80
-
81
- # Checks to see if this Entry can be used as a date range
82
- # @return [true, false] If the Entry has a start and end time returns true, false otherwise.
83
- def is_date_range?
84
- (! @start_time.nil?) && (! @end_time.nil?)
85
- end
86
-
87
- # Checks to see if this Entry is usable for measure calculation. This means that it contains
88
- # at least one code and has one of its time properties set (start, end or time)
89
- # @return [true, false]
90
- def usable?
91
- (! @codes.empty?) && ((! @start_time.nil?) || (! @end_time.nil?) || (! @time.nil?))
92
- end
93
-
94
- # Creates a Hash for this Entry
95
- # @return [Hash] a Hash representing the Entry
96
- def to_hash
97
- entry_hash = {'_id' => BSON::ObjectId.new}
98
- entry_hash['codes'] = @codes
99
- unless @value.empty?
100
- entry_hash['value'] = @value
101
- end
102
-
103
- if is_date_range?
104
- entry_hash['start_time'] = @start_time
105
- entry_hash['end_time'] = @end_time
106
- else
107
- entry_hash['time'] = as_point_in_time
108
- end
109
-
110
- if @status
111
- entry_hash['status'] = @status
112
- end
113
-
114
- if @ordinality
115
- entry_hash['ordinality'] = @ordinality
116
- end
117
-
118
- if @description
119
- entry_hash['description'] = @description
120
- end
121
-
122
- entry_hash
123
- end
124
- end
125
- end
126
- end
@@ -1,117 +0,0 @@
1
- module QME
2
- module Importer
3
- # Class that can be used to create a HITSP C32 importer for any quality measure. This class will construct
4
- # several SectionImporter for the various sections of the C32. When initialized with a JSON measure definition
5
- # it can then be passed a C32 document and will return a Hash with all of the information needed to calculate the measure.
6
- class GenericImporter
7
-
8
- class << self
9
- attr_accessor :warnings
10
- end
11
-
12
- @warnings = []
13
-
14
- # Creates a generic importer for any quality measure.
15
- #
16
- # @param [Hash] definition A measure definition described in JSON
17
- def initialize(definition)
18
- @definition = definition
19
- @warnings = []
20
- end
21
-
22
- # Parses a HITSP C32 document and returns a Hash of information related to the measure
23
- #
24
- # @param [Hash] patient_hash representation of a patient
25
- # @return [Hash] measure information
26
- def parse(patient)
27
- measure_info = {}
28
- @definition['measure'].each_pair do |property, description|
29
- raise "No standard_category for #{property}" if !description['standard_category']
30
- matcher = PropertyMatcher.new(description)
31
- entry_list = filter_for_property(description['standard_category'], description['qds_data_type'], patient)
32
- if ! entry_list.empty?
33
- matched_list = matcher.match(entry_list)
34
- measure_info[property] = matched_list if matched_list.length > 0
35
- end
36
- end
37
- measure_info
38
- end
39
-
40
- private
41
-
42
- def filter_for_property(standard_category, qds_data_type, patient)
43
- # Currently unsupported categories: negation_rationale, risk_category_assessment
44
- case standard_category
45
- when 'encounter'
46
- patient.encounters
47
- when 'immunization'
48
- patient.immunizations
49
- when 'procedure'
50
- case qds_data_type
51
- when 'procedure_performed'
52
- patient.procedures_performed
53
- when 'procedure_adverse_event', 'procedure_intolerance'
54
- patient.allergies
55
- when 'procedure_result'
56
- patient.procedure_results
57
- end
58
- when 'risk_category_assessment'
59
- patient.procedures
60
- when 'communication'
61
- patient.procedures
62
- when 'laboratory_test'
63
- patient.laboratory_tests
64
- when 'physical_exam'
65
- patient.procedure_results
66
- when 'medication'
67
- case qds_data_type
68
- when 'medication_dispensed', 'medication_order', 'medication_active', 'medication_administered'
69
- patient.all_meds
70
- when 'medication_allergy', 'medication_intolerance', 'medication_adverse_event'
71
- patient.allergies
72
- end
73
- when 'diagnosis_condition_problem'
74
- case qds_data_type
75
- when 'diagnosis_active'
76
- patient.active_diagnosis
77
- when 'diagnosis_active_priority_principal'
78
- patient.active_diagnosis
79
- when 'diagnosis_inactive'
80
- patient.inactive_diagnosis
81
- when 'diagnosis_resolved'
82
- patient.resolved_diagnosis
83
- end
84
- when 'symptom'
85
- patient.all_problems
86
- when 'individual_characteristic'
87
- patient.all_problems
88
- when 'device'
89
- case qds_data_type
90
- when 'device_applied'
91
- patient.all_devices
92
- when 'device_allergy'
93
- patient.allergies
94
- end
95
- when 'care_goal'
96
- patient.care_goals
97
- when 'diagnostic_study'
98
- case qds_data_type
99
-
100
- when 'diagnostic_study_performed'
101
- patient.procedures
102
- when 'diagnostic_study_result'
103
- patient.procedure_results
104
- end
105
- when 'substance'
106
- patient.allergies
107
- else
108
- unless self.class.warnings.include?(standard_category)
109
- puts "Warning: Unsupported standard_category (#{standard_category})"
110
- self.class.warnings << standard_category
111
- end
112
- []
113
- end
114
- end
115
- end
116
- end
117
- end
@@ -1,39 +0,0 @@
1
- module QME
2
- module Importer
3
- class MeasurePropertiesGenerator
4
- include Singleton
5
-
6
- def initialize
7
- @measure_importers = {}
8
- end
9
-
10
- # Adds a GenericImporter that will be used to extract
11
- # information about a measure from a patient Record.
12
- def add_measure(measure_id, importer)
13
- @measure_importers[measure_id] = importer
14
- end
15
-
16
- # Generates denormalized measure information
17
- # Measure information is contained in a Hash that hash
18
- # the measure id as a key, and the denormalized
19
- # measure information as a value
20
- #
21
- # @param [Record] patient - populated patient record
22
- # @returns Hash with denormalized measure information
23
- def generate_properties(patient)
24
- measures = {}
25
- @measure_importers.each_pair do |measure_id, importer|
26
- measures[measure_id] = importer.parse(patient)
27
- end
28
- measures
29
- end
30
-
31
- # The same as generate_properties but addes the denormalized
32
- # measure information into the Record and saves it.
33
- def generate_properties!(patient)
34
- patient['measures'] = generate_properties(patient)
35
- patient.save!
36
- end
37
- end
38
- end
39
- end
@@ -1,110 +0,0 @@
1
- module QME
2
- module Importer
3
- # Compares Entry objects to measure definition properties.
4
- class PropertyMatcher
5
-
6
- def initialize(property_description)
7
- @property_description = property_description
8
- end
9
-
10
- # Looks through an Array of Entry objects to see if any of them match the codes needed
11
- # for a measure property. Will return different types of Arrays depending on the schema
12
- # of the property
13
- # @param [Array] entry_list an Array of Entry objects
14
- # @return An Array of goodness that is ready to be inserted into a measure property on a patient record
15
- def match(entry_list)
16
- if is_date_list_property?
17
- extract_date_list(entry_list)
18
- elsif is_value_date_property?
19
- extract_value_date_list(entry_list)
20
- elsif is_date_range_property?
21
- extract_date_range_list(entry_list)
22
- else
23
- raise "Unknown property schema for property #{@property_description['description']}"
24
- end
25
- end
26
-
27
- # Extracts the dates of any CDA entries that meet the code set defined for measure property.
28
- #
29
- # @param [Array] entry_list an Array of Entry objects
30
- # @return [Array] Provides an Array of dates for entries that have codes inside of the measure code set
31
- # Dates will be represented as an Integer in seconds since the epoch
32
- def extract_date_list(entry_list)
33
- basic_extractor(entry_list) do |entry, matching_values|
34
- matching_values << entry.as_point_in_time
35
- end
36
- end
37
-
38
- # Extracts the dates of any CDA entries that meet the code set defined for measure property.
39
- #
40
- # @param [Array] entry_list an Array of Entry objects
41
- # @return [Array] Provides an Array of Hashes for entries that have codes inside of the measure code set
42
- # Hashes will have a "value" and "date" property containing the respective data
43
- def extract_value_date_list(entry_list)
44
- basic_extractor(entry_list) do |entry, matching_values|
45
- value = entry.value[:scalar]
46
- if value
47
- if @property_description['items']['properties']['value']['type'] == 'number'
48
- value = value.to_f
49
- elsif @property_description['items']['properties']['value']['type'] == 'boolean'
50
- value = value.to_boolean
51
- end
52
-
53
- matching_values << {'date' => entry.as_point_in_time, 'value' => value}
54
- end
55
- end
56
- end
57
-
58
- # Extracts the dates of any CDA entries that meet the code set defined for measure property.
59
- #
60
- # @param [Array] entry_list an Array of Entry objects
61
- # @return [Array] Provides an Array of Hashes for entries that have codes inside of the measure code set
62
- # Hashes will have a "start" and "end" property containing the respective data
63
- def extract_date_range_list(entry_list)
64
- basic_extractor(entry_list) do |entry, matching_values|
65
- if entry.is_date_range?
66
- matching_values << {'start' => entry.start_time, 'end' => entry.end_time}
67
- end
68
- end
69
- end
70
-
71
- # Determines if the property is a list of dates
72
- # @return [Boolean] true of false depending on the property
73
- def is_date_list_property?
74
- @property_description['type'] == 'array' && @property_description['items']['type'] == 'number'
75
- end
76
-
77
- # Determines if the property is a list of date and value hashes
78
- # @return [Boolean] true of false depending on the property
79
- def is_value_date_property?
80
- @property_description['type'] == 'array' && @property_description['items']['type'] == 'object' &&
81
- @property_description['items']['properties']['value'] &&
82
- @property_description['items']['properties']['date']
83
- end
84
-
85
- # Determines if the property is a list of date ranges represented by a Hash with start and end
86
- # keys
87
- # @return [Boolean] true of false depending on the property
88
- def is_date_range_property?
89
- @property_description['type'] == 'array' && @property_description['items']['type'] == 'object' &&
90
- @property_description['items']['properties']['start'] &&
91
- @property_description['items']['properties']['end']
92
- end
93
-
94
- private
95
-
96
- def basic_extractor(entry_list)
97
- matching_values = []
98
- entry_list.each do |entry|
99
- if entry.usable?
100
- if entry.is_in_code_set?(@property_description['codes'])
101
- yield entry, matching_values
102
- end
103
- end
104
- end
105
-
106
- matching_values
107
- end
108
- end
109
- end
110
- end