quality-measure-engine 1.1.5 → 2.0.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.
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