cqm-parsers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +29 -0
  3. data/README.md +21 -0
  4. data/Rakefile +19 -0
  5. data/lib/ext/code.rb +10 -0
  6. data/lib/ext/data_element.rb +24 -0
  7. data/lib/hqmf-model/attribute.rb +63 -0
  8. data/lib/hqmf-model/data_criteria.rb +467 -0
  9. data/lib/hqmf-model/document.rb +253 -0
  10. data/lib/hqmf-model/population_criteria.rb +102 -0
  11. data/lib/hqmf-model/precondition.rb +94 -0
  12. data/lib/hqmf-model/types.rb +457 -0
  13. data/lib/hqmf-model/utilities.rb +52 -0
  14. data/lib/hqmf-parser.rb +116 -0
  15. data/lib/hqmf-parser/1.0/attribute.rb +121 -0
  16. data/lib/hqmf-parser/1.0/comparison.rb +34 -0
  17. data/lib/hqmf-parser/1.0/data_criteria.rb +92 -0
  18. data/lib/hqmf-parser/1.0/document.rb +195 -0
  19. data/lib/hqmf-parser/1.0/expression.rb +60 -0
  20. data/lib/hqmf-parser/1.0/observation.rb +61 -0
  21. data/lib/hqmf-parser/1.0/population_criteria.rb +75 -0
  22. data/lib/hqmf-parser/1.0/precondition.rb +90 -0
  23. data/lib/hqmf-parser/1.0/range.rb +76 -0
  24. data/lib/hqmf-parser/1.0/restriction.rb +162 -0
  25. data/lib/hqmf-parser/1.0/utilities.rb +55 -0
  26. data/lib/hqmf-parser/2.0/data_criteria.rb +372 -0
  27. data/lib/hqmf-parser/2.0/data_criteria_helpers/dc_base_extract.rb +80 -0
  28. data/lib/hqmf-parser/2.0/data_criteria_helpers/dc_definition_from_template_or_type_extract.rb +201 -0
  29. data/lib/hqmf-parser/2.0/data_criteria_helpers/dc_post_processing.rb +85 -0
  30. data/lib/hqmf-parser/2.0/data_criteria_helpers/dc_specific_occurrences_and_source_data_criteria_extract.rb +117 -0
  31. data/lib/hqmf-parser/2.0/document.rb +304 -0
  32. data/lib/hqmf-parser/2.0/document_helpers/doc_population_helper.rb +173 -0
  33. data/lib/hqmf-parser/2.0/document_helpers/doc_utilities.rb +131 -0
  34. data/lib/hqmf-parser/2.0/field_value_helper.rb +251 -0
  35. data/lib/hqmf-parser/2.0/population_criteria.rb +134 -0
  36. data/lib/hqmf-parser/2.0/precondition.rb +73 -0
  37. data/lib/hqmf-parser/2.0/source_data_criteria_helper.rb +112 -0
  38. data/lib/hqmf-parser/2.0/types.rb +448 -0
  39. data/lib/hqmf-parser/2.0/utilities.rb +45 -0
  40. data/lib/hqmf-parser/2.0/value_set_helper.rb +104 -0
  41. data/lib/hqmf-parser/converter/pass1/data_criteria_converter.rb +257 -0
  42. data/lib/hqmf-parser/converter/pass1/document_converter.rb +133 -0
  43. data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +185 -0
  44. data/lib/hqmf-parser/converter/pass1/precondition_converter.rb +173 -0
  45. data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +201 -0
  46. data/lib/hqmf-parser/converter/pass1/simple_data_criteria.rb +26 -0
  47. data/lib/hqmf-parser/converter/pass1/simple_operator.rb +89 -0
  48. data/lib/hqmf-parser/converter/pass1/simple_population_criteria.rb +10 -0
  49. data/lib/hqmf-parser/converter/pass1/simple_precondition.rb +51 -0
  50. data/lib/hqmf-parser/converter/pass1/simple_restriction.rb +64 -0
  51. data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +112 -0
  52. data/lib/hqmf-parser/converter/pass2/operator_converter.rb +102 -0
  53. data/lib/hqmf-parser/cql/data_criteria.rb +57 -0
  54. data/lib/hqmf-parser/cql/data_criteria_helpers/dc_definition_from_template_or_type_extract.rb +79 -0
  55. data/lib/hqmf-parser/cql/data_criteria_helpers/dc_post_processing.rb +43 -0
  56. data/lib/hqmf-parser/cql/document.rb +78 -0
  57. data/lib/hqmf-parser/cql/document_helpers/doc_population_helper.rb +124 -0
  58. data/lib/hqmf-parser/cql/value_set_helper.rb +103 -0
  59. data/lib/hqmf-parser/parser.rb +100 -0
  60. data/lib/qrda-export/catI-r5/qrda1_r5.rb +125 -0
  61. data/lib/qrda-export/helper/cat_1_view_helper.rb +142 -0
  62. data/lib/qrda-export/helper/code_system_helper.rb +77 -0
  63. data/lib/qrda-export/helper/date_helper.rb +81 -0
  64. data/lib/qrda-import/base-importers/demographics_importer.rb +47 -0
  65. data/lib/qrda-import/base-importers/medication_importer.rb +22 -0
  66. data/lib/qrda-import/base-importers/section_importer.rb +196 -0
  67. data/lib/qrda-import/cda_identifier.rb +19 -0
  68. data/lib/qrda-import/data-element-importers/adverse_event_importer.rb +23 -0
  69. data/lib/qrda-import/data-element-importers/allergy_intolerance_importer.rb +21 -0
  70. data/lib/qrda-import/data-element-importers/assessment_performed_importer.rb +23 -0
  71. data/lib/qrda-import/data-element-importers/communication_from_patient_to_provider_importer.rb +18 -0
  72. data/lib/qrda-import/data-element-importers/communication_from_provider_to_patient_importer.rb +18 -0
  73. data/lib/qrda-import/data-element-importers/communication_from_provider_to_provider_importer.rb +20 -0
  74. data/lib/qrda-import/data-element-importers/device_applied_importer.rb +23 -0
  75. data/lib/qrda-import/data-element-importers/device_order_importer.rb +18 -0
  76. data/lib/qrda-import/data-element-importers/diagnosis_importer.rb +23 -0
  77. data/lib/qrda-import/data-element-importers/diagnostic_study_order_importer.rb +20 -0
  78. data/lib/qrda-import/data-element-importers/diagnostic_study_performed_importer.rb +30 -0
  79. data/lib/qrda-import/data-element-importers/encounter_order_importer.rb +20 -0
  80. data/lib/qrda-import/data-element-importers/encounter_performed_importer.rb +41 -0
  81. data/lib/qrda-import/data-element-importers/immunization_administered_importer.rb +18 -0
  82. data/lib/qrda-import/data-element-importers/intervention_order_importer.rb +18 -0
  83. data/lib/qrda-import/data-element-importers/intervention_performed_importer.rb +22 -0
  84. data/lib/qrda-import/data-element-importers/laboratory_test_order_importer.rb +20 -0
  85. data/lib/qrda-import/data-element-importers/laboratory_test_performed_importer.rb +28 -0
  86. data/lib/qrda-import/data-element-importers/medication_active_importer.rb +17 -0
  87. data/lib/qrda-import/data-element-importers/medication_administered_importer.rb +17 -0
  88. data/lib/qrda-import/data-element-importers/medication_discharge_importer.rb +19 -0
  89. data/lib/qrda-import/data-element-importers/medication_dispensed_importer.rb +19 -0
  90. data/lib/qrda-import/data-element-importers/medication_order_importer.rb +16 -0
  91. data/lib/qrda-import/data-element-importers/patient_characteristic_expired.rb +21 -0
  92. data/lib/qrda-import/data-element-importers/physical_exam_performed_importer.rb +26 -0
  93. data/lib/qrda-import/data-element-importers/procedure_order_importer.rb +26 -0
  94. data/lib/qrda-import/data-element-importers/procedure_performed_importer.rb +34 -0
  95. data/lib/qrda-import/data-element-importers/substance_administered_importer.rb +16 -0
  96. data/lib/qrda-import/entry_finder.rb +20 -0
  97. data/lib/qrda-import/entry_package.rb +16 -0
  98. data/lib/qrda-import/narrative_reference_handler.rb +33 -0
  99. data/lib/qrda-import/patient_importer.rb +105 -0
  100. data/lib/util/code_system_helper.rb +76 -0
  101. data/lib/util/counter.rb +20 -0
  102. data/lib/util/hqmf_template_helper.rb +39 -0
  103. metadata +340 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6f44d77fe47f440f802946c4fc454de11d4fd69e
4
+ data.tar.gz: e1f87b52ea314452ade5bfd6c4ef5cc17c819384
5
+ SHA512:
6
+ metadata.gz: 6feea91c08a04843dfad7119461b608986650fea25f69723a186397dc378727d0c5689e53ae56405f0762bb2f2320a12ee0f27c6b9b37c7243cc5b56760b7cb2
7
+ data.tar.gz: 1ed3943b864ec0c33c1041bf1c27d402e960e7f850eb5a494d6d84cc9217eb1ae266a339c6d83e1d200a235070f793ee1cc174b5b21996def39a04f910af0451
data/Gemfile ADDED
@@ -0,0 +1,29 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :development_group => :test
4
+
5
+ gem 'mustache'
6
+ gem 'cqm-models', '~> 0.8.2'
7
+ gem 'mongoid', '~> 5.0.0'
8
+
9
+ group :development, :test do
10
+ gem 'bundler-audit'
11
+ gem 'rubocop', '~> 0.52.1', require: false
12
+ end
13
+
14
+ group :development do
15
+ gem 'rake'
16
+ gem 'byebug', '~> 6.0.2', platforms: [:ruby_20, :ruby_21, :ruby_22, :ruby_23]
17
+ end
18
+
19
+ group :test do
20
+ gem 'factory_girl', '~> 4.1.0'
21
+ gem "tailor", '~> 1.1.2'
22
+ gem "cane", '~> 2.3.0'
23
+ gem 'simplecov', :require => false
24
+ gem 'webmock'
25
+ gem 'minitest', '~> 5.3'
26
+ gem 'minitest-reporters'
27
+ gem 'awesome_print', :require => 'ap'
28
+ gem 'simplexml_parser', :git => 'https://github.com/projecttacoma/simplexml_parser.git', :branch => 'master'
29
+ end
@@ -0,0 +1,21 @@
1
+ cqm-parsers
2
+ ===========
3
+
4
+ This project contains libraries for parsing HQMF documents.
5
+
6
+ License
7
+ =======
8
+
9
+ Copyright 2018 The MITRE Corporation
10
+
11
+ Licensed under the Apache License, Version 2.0 (the "License");
12
+ you may not use this file except in compliance with the License.
13
+ You may obtain a copy of the License at
14
+
15
+ http://www.apache.org/licenses/LICENSE-2.0
16
+
17
+ Unless required by applicable law or agreed to in writing, software
18
+ distributed under the License is distributed on an "AS IS" BASIS,
19
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ See the License for the specific language governing permissions and
21
+ limitations under the License.
@@ -0,0 +1,19 @@
1
+ require 'rake/testtask'
2
+ require 'cane/rake_task'
3
+ require "simplecov"
4
+
5
+ import 'lib/tasks/hqmf.rake'
6
+
7
+ Rake::TestTask.new(:test_unit) do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ t.verbose = false
11
+ t.warning = false
12
+ end
13
+
14
+
15
+
16
+ task :test => [:test_unit] do
17
+
18
+ system("open coverage/index.html")
19
+ end
@@ -0,0 +1,10 @@
1
+ module QDM
2
+ class Code
3
+ def ==(other)
4
+ return false unless other.is_a? QDM::Code
5
+ (code == other.code) && (codeSystem == other.codeSystem) && (codeSystemOid == other.codeSystemOid) && (version == other.version)
6
+ end
7
+
8
+ alias eql? ==
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ module QDM
2
+ class DataElement
3
+ def merge!(other)
4
+ # ensure they're the same category (e.g. 'encounter')
5
+ return unless category == other.category
6
+
7
+ # ensure they're the same status (e.g. 'performed'), and that they both have a status set (or that they both don't)
8
+ return if respond_to?(:qdmStatus) && !other.respond_to?(:qdmStatus)
9
+ return if !respond_to?(:qdmStatus) && other.respond_to?(:qdmStatus)
10
+ return if respond_to?(:qdmStatus) && other.respond_to?(:qdmStatus) && qdmStatus != other.qdmStatus
11
+
12
+ # iterate over non-code fields
13
+ fields.each_key do |field|
14
+ next if field[0] == '_' || %w[dataElementCodes category qdmVersion qdmStatus].include?(field)
15
+
16
+ if send(field).nil?
17
+ send(field + '=', other.send(field))
18
+ end
19
+ end
20
+
21
+ self.dataElementCodes = dataElementCodes.concat(other.dataElementCodes).uniq
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
1
+ module HQMF
2
+ class Attribute
3
+ include HQMF::Conversion::Utilities
4
+ attr_reader :id,:code,:value,:unit,:name,:id_obj,:code_obj,:value_obj
5
+ # @param [String] id
6
+ # @param [String] code
7
+ # @param [String] value
8
+ # @param [String] unit
9
+ # @param [String] name
10
+ # @param [HQMF::Identifier] id_obj
11
+ # @param [HQMF::Coded] code_obj
12
+ # @param [Object] value_obj
13
+ def initialize(id,code,value,unit,name,id_obj=nil,code_obj=nil,value_obj=nil)
14
+ @id = id
15
+ @code = code
16
+ @value = value
17
+ @unit = unit
18
+ @name = name
19
+ # enhanced model
20
+ @id_obj = id_obj
21
+ @code_obj = code_obj
22
+ @value_obj = value_obj
23
+ end
24
+
25
+ def self.from_json(json)
26
+ json = json.with_indifferent_access
27
+
28
+ id = json["id"] if json["id"]
29
+ code = json["code"] if json["code"]
30
+ value = json["value"] if json["value"]
31
+ unit = json["unit"] if json["unit"]
32
+ name = json["name"] if json["name"]
33
+ # enhanced model
34
+ id_obj = HQMF::Identifier::from_json(json["id_obj"]) if json["id_obj"]
35
+ code_obj = HQMF::Coded::from_json(json["code_obj"]) if json["code_obj"]
36
+ value_obj = nil
37
+ if (json["value_obj"])
38
+ json_value = json["value_obj"].with_indifferent_access
39
+ case json_value["type"]
40
+ when 'II'
41
+ value_obj = HQMF::Identifier::from_json(json_value)
42
+ when 'CD'
43
+ value_obj = HQMF::Coded::from_json(json_value)
44
+ when 'ED'
45
+ value_obj = HQMF::ED::from_json(json_value)
46
+ else
47
+ value_obj = json_value["value"].nil? ? HQMF::AnyValue::from_json(json_value) : HQMF::GenericValueContainer::from_json(json_value)
48
+ end
49
+ end
50
+
51
+ HQMF::Attribute.new(id,code,value,unit,name,id_obj,code_obj,value_obj)
52
+ end
53
+
54
+ def to_json
55
+ json = build_hash(self, [:id,:code,:value,:unit,:name])
56
+ json[:id_obj] = @id_obj.to_json if @id_obj
57
+ json[:code_obj] = @code_obj.to_json if @code_obj
58
+ json[:value_obj] = @value_obj.to_json if @value_obj
59
+ json
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,467 @@
1
+ module HQMF
2
+ # Represents a data criteria specification
3
+ class DataCriteria
4
+
5
+ include HQMF::Conversion::Utilities
6
+
7
+ SOURCE_DATA_CRITERIA_TEMPLATE_ID = '2.16.840.1.113883.3.100.1.1'
8
+ SOURCE_DATA_CRITERIA_TEMPLATE_TITLE = 'Source data criteria'
9
+
10
+ XPRODUCT = 'XPRODUCT'
11
+ UNION = 'UNION'
12
+ INTERSECT = 'INTERSECT'
13
+
14
+ SATISFIES_ALL = 'satisfies_all'
15
+ SATISFIES_ANY = 'satisfies_any'
16
+ VARIABLE = 'variable'
17
+
18
+ # An object containing metadata information for all attributes that are used within the measure data criteria being parsed.
19
+ #
20
+ # fields include:
21
+ # `title`: The QDM human readable title for the attribute.
22
+ # `coded_entry_method`: this appears to be a way that fields here are referenced within Bonnie.
23
+ # `field_type`: The type of whatever will be stored for this attribute. This will often be `:timestamp` or `:value`.
24
+ # `code`: The code for the entry. This should be included to make HQMF generation work properly. This is whatever code is dictated in the HQMF. For Diagnosis, this is in [HQMF QDM IG](http://www.hl7.org/implement/standards/product_brief.cfm?product_id=346) vol 2 page 155 and is `29308-4`.
25
+ # `code_system`: This is the oid for whatever code system contains `code`. For Diagnosis, this is LOINC: `2.16.840.1.113883.6.1`. This is also located at (http://www.hl7.org/implement/standards/product_brief.cfm?product_id=346) vol 2 page 155.
26
+ # `template_id`: These appear to be related to HQMFr1 template ids. These appear to be dangerously out of date. Don't use.
27
+ FIELDS = {'ABATEMENT_DATETIME' => {title:'Abatement Datetime', coded_entry_method: :end_date, field_type: :timestamp},
28
+ 'ACTIVE_DATETIME' => {title:'Active Date/Time', coded_entry_method: :active_date_time, field_type: :timestamp},
29
+ 'ADMISSION_DATETIME' => {title:'Admission Date/Time', coded_entry_method: :admit_time, code: '399423000', code_system:'2.16.840.1.113883.6.96', field_type: :timestamp},
30
+ # QDM 5.0 addition. This is the same as FACILITY_LOCATION.
31
+ # TODO: (LDY 10/5/2016) this is a new attribute from QDM 5.0. We do not yet have the code or template_id for this. This should be updated when we do.
32
+ 'ADMISSION_SOURCE' => {title:'Admission Source', coded_entry_method: :admission_source, field_type: :value},
33
+ 'ANATOMICAL_APPROACH_SITE' => {title:'Anatomical Approach Site', coded_entry_method: :anatomical_approach, field_type: :value},
34
+ 'ANATOMICAL_LOCATION_SITE' => {title:'Anatomical Location Site', coded_entry_method: :anatomical_location, field_type: :value},
35
+ 'ANATOMICAL_STRUCTURE' => {title:'Anatomical Structure', coded_entry_method: :anatomical_structure, code: '91723000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1000.2', field_type: :value},
36
+ 'CAUSE' => {title:'Cause', coded_entry_method: :cause_of_death, code: '42752001', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1017.2', field_type: :value},
37
+ # TODO: Determine actual code and code_system for component attribute
38
+ 'COMPONENT' => {title: 'Component', coded_entry_method: :components, field_type: :value},
39
+ 'CUMULATIVE_MEDICATION_DURATION' => {title:'Cumulative Medication Duration', coded_entry_method: :cumulative_medication_duration, code: '261773006', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1001.3', field_type: :value},
40
+ # MISSING Date - The date that the patient passed away. - Patient Characteristic Expired
41
+ 'DIAGNOSIS' => {title:'Diagnosis', coded_entry_method: :diagnosis, field_type: :value},
42
+ 'DISCHARGE_DATETIME' => {title:'Discharge Date/Time', coded_entry_method: :discharge_time, code: '442864001', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1025.1', field_type: :timestamp},
43
+ # TODO: (LDY 10/5/2016) this changed from "discharge status" to "discharge disposition". likely there is a code and template id change necessary. these are not yet known.
44
+ 'DISCHARGE_STATUS' => {title:'Discharge Disposition', coded_entry_method: :discharge_disposition, code: '309039003', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1003.2', field_type: :value},
45
+ # TODO: (LDY 10/4/2016) this changed from "dose" to "dosage". it's possible that there's another code associated with this. this code was not available at the time of this change.
46
+ 'DOSE' => {title:'Dosage', coded_entry_method: :dose, code: '398232005', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1004.1', field_type: :value},
47
+ 'FACILITY_LOCATION' => {title:'Facility Location', coded_entry_method: :facility, code: 'SDLOC', field_type: :value},
48
+ # TODO: (LDY 10/5/2016) this changed from 'facility arrival/departure' to 'location period'. likely there is a code and template id change necessary. these are not yet known.
49
+ 'FACILITY_LOCATION_ARRIVAL_DATETIME' => {title:'Location Period Start Date/Time', coded_entry_method: :facility_arrival, code: 'SDLOC_ARRIVAL', field_type: :nested_timestamp},
50
+ 'FACILITY_LOCATION_DEPARTURE_DATETIME' => {title:'Location Period End Date/Time', coded_entry_method: :facility_departure, code: 'SDLOC_DEPARTURE', field_type: :nested_timestamp},
51
+ 'FREQUENCY' => {title:'Frequency', coded_entry_method: :administration_timing, code: '307430002', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1006.1', field_type: :value},
52
+ 'HEALTH_RECORD_FIELD' => {title: 'Health Record Field', coded_entry_method: :health_record_field, code: '395676008', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.28.3.102:2014-11-24', field_type: :value},
53
+ 'INCISION_DATETIME' => {title:'Incision Date/Time', coded_entry_method: :incision_time, code: '34896006', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.89', field_type: :timestamp},
54
+ 'LATERALITY' => {title:'Laterality', coded_entry_method: :laterality, code: '272741003', code_system:'2.16.840.1.113883.6.96', template_id: '', field_type: :value},
55
+ 'LENGTH_OF_STAY' => {title:'Length of Stay', coded_entry_method: :length_of_stay, code: '183797002', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1029.3', field_type: :value},
56
+ 'METHOD' => {title:'Method', coded_entry_method: :method, template_id: '', field_type: :value},
57
+ # Negation Rationale isn't encoded
58
+ 'ONSET_AGE' => {title:'Onset Age', coded_entry_method: :onset_age, code: '445518008', code_system:'2.16.840.1.113883.6.96', template_id: '', field_type: :value},
59
+ 'ONSET_DATETIME' => {title:'Onset Datetime', coded_entry_method: :start_date, field_type: :timestamp},
60
+ 'ORDINAL' => {title:'Ordinality', coded_entry_method: :ordinality, code: '117363000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1012.2', field_type: :value}, # previous
61
+ 'ORDINALITY' => {title:'Ordinality', coded_entry_method: :ordinality, code: '117363000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1012.2', field_type: :value},
62
+ 'PATIENT_PREFERENCE' => {title:'Patient Preference', coded_entry_method: :patient_preference, code: 'PAT', code_system: '2.16.840.1.113883.5.8', template_id: '2.16.840.1.113883.10.20.24.3.83', field_type: :value},
63
+ 'PRINCIPAL_DIAGNOSIS' => {title:'Principal Diagnosis', coded_entry_method: :principal_diagnosis, field_type: :value},
64
+ 'PROVIDER_PREFERENCE' => {title:'Provider Preference', coded_entry_method: :provider_preference, code: '103323008', code_system: '2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.84', field_type: :value},
65
+ 'RADIATION_DOSAGE' => {title:'Radiation Dosage', coded_entry_method: :radiation_dose, code: '228815006', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.91', field_type: :value},
66
+ 'RADIATION_DURATION' => {title:'Radiation Duration', coded_entry_method: :radiation_duration, code: '306751006', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.91', field_type: :value},
67
+ 'REACTION'=> {title:'Reaction', coded_entry_method: :reaction, code: '263851003', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.85', field_type: :value},
68
+ 'REASON' => {title:'Reason', coded_entry_method: :reason, code: '410666004', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.88', field_type: :value},
69
+ 'RECORDED_DATETIME' => {title:'Recorded Datetime', coded_entry_method: :start_date, field_type: :timestamp},
70
+ 'REFERENCE_RANGE_HIGH' => {title:'Reference Range - High', coded_entry_method: :reference_range_high, field_type: :value},
71
+ 'REFERENCE_RANGE_LOW' => {title:'Reference Range - Low', coded_entry_method: :reference_range_low, field_type: :value},
72
+ 'REFILLS' => {title:'Refills', coded_entry_method: :refills, field_type: :value},
73
+ 'RELATED_TO' => {title:'Related To', coded_entry_method: :related_to, code: 'REL', codeSystem: '2.16.840.1.113883.1.11.11603', field_type: :value},
74
+ 'RELATIONSHIP' => {title:'Relationship', coded_entry_method: :relationship_to_patient, field_type: :value},
75
+ 'REMOVAL_DATETIME' => {title:'Removal Date/Time', coded_entry_method: :removal_time, code: '118292001', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1032.1', field_type: :timestamp},
76
+ # Result isn't encoded
77
+ # TODO: (LDY 10/4/2016) RESULT_DATETIME is a new attribute in QDM 5.0. We do not yet have codes/template information for this.
78
+ 'RESULT_DATETIME' => {title:'Result Date/Time', coded_entry_method: :result_date_time, field_type: :timestamp},
79
+ 'ROUTE' => {title:'Route', coded_entry_method: :route, code: '263513008', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1020.2', field_type: :value},
80
+ 'SEVERITY' => {title:'Severity', coded_entry_method: :severity, code: 'SEV', code_system:'2.16.840.1.113883.5.4', template_id: '2.16.840.1.113883.10.20.22.4.8', field_type: :value},
81
+ 'SIGNED_DATETIME' => {title:'Signed Date/Time', coded_entry_method: :signed_date_time, field_type: :timestamp},
82
+ 'START_DATETIME' => {title:'Start Date/Time', coded_entry_method: :start_date, code: '398201009', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1027.1', field_type: :timestamp},
83
+ # STATUS is referenced in the code as `qdm_status` because entry/Record already has a `status`/`status_code` field which has a different meaning
84
+ 'STATUS' => {title: 'Status', coded_entry_method: :qdm_status, code: '33999-4', code_system:'2.16.840.1.113883.6.1', field_type: :value},
85
+ 'STOP_DATETIME' => {title:'Stop Date/Time', coded_entry_method: :end_date, code: '397898000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1026.1', field_type: :timestamp},
86
+ # TODO: (LDY 10/4/2016) SUPPLY is a new attribute in QDM 5.0. We do not yet have codes/template information for this.
87
+ 'SUPPLY' => {title:'Supply', coded_entry_method: :supply, field_type: :value},
88
+ 'TARGET_OUTCOME' => {title:'Target Outcome', coded_entry_method: :target_outcome, code: '385676005', code_system:'2.16.840.1.113883.6.96', template_id: '', field_type: :value},
89
+ # MISSING Time - The time that the patient passed away
90
+
91
+ # Custom field values
92
+ # QDM 5.3 Update: "Related To" replaces Fulfills. We are keeping the fulfills code and only make changes to the UI.
93
+ 'FLFS' => {title:'Related To', coded_entry_method: :fulfills, code: 'FLFS', field_type: :reference},
94
+ 'SOURCE' => {title:'Source', coded_entry_method: :source, code: '260753009', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.2001.2', field_type: :value},
95
+ 'TRANSFER_FROM' => {title:'Transfer From', coded_entry_method: :transfer_from, code: 'TRANSFER_FROM', template_id: '2.16.840.1.113883.10.20.24.3.81', field_type: :value},
96
+ 'TRANSFER_FROM_DATETIME' => {title:'Transfer From Date/Time', coded_entry_method: :transfer_from_time, code: 'ORG_TIME', template_id: '2.16.840.1.113883.10.20.24.3.81', field_type: :nested_timestamp},
97
+ 'TRANSFER_TO' => {title:'Transfer To', coded_entry_method: :transfer_to, code: 'TRANSFER_TO', template_id: '2.16.840.1.113883.10.20.24.3.82', field_type: :value},
98
+ 'TRANSFER_TO_DATETIME' => {title:'Transfer To Date/Time', coded_entry_method: :transfer_to_time, code: 'DST_TIME', template_id: '2.16.840.1.113883.10.20.24.3.82', field_type: :nested_timestamp}
99
+ }
100
+
101
+ # maps attribute codes to the attribute keys
102
+ VALUE_FIELDS = {'399423000' => 'ADMISSION_DATETIME',
103
+ '42752001' => 'CAUSE',
104
+ '261773006' => 'CUMULATIVE_MEDICATION_DURATION',
105
+ '363819003' => 'CUMULATIVE_MEDICATION_DURATION', # previous
106
+ '442864001' => 'DISCHARGE_DATETIME',
107
+ '309039003' => 'DISCHARGE_STATUS',
108
+ '398232005' => 'DOSE',
109
+ 'SDLOC' => 'FACILITY_LOCATION',
110
+ 'SDLOC_ARRIVAL' => 'FACILITY_LOCATION_ARRIVAL_DATETIME',
111
+ 'SDLOC_DEPARTURE' => 'FACILITY_LOCATION_DEPARTURE_DATETIME',
112
+ '307430002' => 'FREQUENCY',
113
+ '260864003' => 'FREQUENCY', # previous
114
+ '395676008' => 'HEALTH_RECORD_FIELD',
115
+ '34896006' => 'INCISION_DATETIME',
116
+ '272741003' => 'LATERALITY',
117
+ '183797002' => 'LENGTH_OF_STAY',
118
+ '445518008' => 'ONSET_AGE',
119
+ '117363000' => 'ORDINALITY',
120
+ 'PAT' => 'PATIENT_PREFERENCE',
121
+ '103323008' => 'PROVIDER_PREFERENCE',
122
+ '228815006' => 'RADIATION_DOSAGE',
123
+ '306751006' => 'RADIATION_DURATION',
124
+ '263851003' => 'REACTION',
125
+ '410666004' => 'REASON',
126
+ 'REL' => 'RELATED_TO',
127
+ '118292001' => 'REMOVAL_DATETIME',
128
+ '263513008' => 'ROUTE',
129
+ 'SEV' => 'SEVERITY',
130
+ '398201009' => 'START_DATETIME',
131
+ '33999-4' => 'STATUS',
132
+ '397898000' => 'STOP_DATETIME',
133
+ '385676005' => 'TARGET_OUTCOME',
134
+
135
+ # Custom field values
136
+ '91723000' => 'ANATOMICAL_STRUCTURE',
137
+ 'FLFS' => 'FLFS',
138
+ '260753009' => 'SOURCE',
139
+ 'TRANSFER_FROM' => 'TRANSFER_FROM',
140
+ 'ORG_TIME' => 'TRANSFER_FROM_DATETIME',
141
+ 'TRANSFER_TO' => 'TRANSFER_TO',
142
+ 'DST_TIME' => 'TRANSFER_TO_DATETIME'
143
+
144
+ }
145
+
146
+
147
+ attr_reader :title, :description, :code_list_id, :derivation_operator , :specific_occurrence, :specific_occurrence_const, :source_data_criteria, :variable
148
+ attr_accessor :id, :value, :field_values, :children_criteria, :effective_time, :status, :temporal_references, :subset_operators, :definition, :inline_code_list, :negation_code_list_id, :negation, :display_name, :comments
149
+
150
+ # Create a new data criteria instance
151
+ # @param [String] id
152
+ # @param [String] title
153
+ # @param [String] display_name
154
+ # @param [String] description
155
+ # @param [String] code_list_id
156
+ # @param [String] negation_code_list_id
157
+ # @param [List<String>] children_criteria (ids of children data criteria)
158
+ # @param [String] derivation_operator
159
+ # @param [String] definition
160
+ # @param [String] status
161
+ # @param [Value|Range|Coded] value
162
+ # @param [Hash<String,Value|Range|Coded>] field_values
163
+ # @param [Range] effective_time
164
+ # @param [Hash<String,[String]>] inline_code_list
165
+ # @param [boolean] negation
166
+ # @param [String] negation_code_list_id
167
+ # @param [List<TemporalReference>] temporal_references
168
+ # @param [List<SubsetOperator>] subset_operators
169
+ # @param [String] specific_occurrence
170
+ # @param [String] specific_occurrence_const
171
+ # @param [String] source_data_criteria (id for the source data criteria, important for specific occurrences)
172
+ # @param [String] user comments for the criteria
173
+ # @param [Boolean] variable defines if the element is a QDM variable
174
+ def initialize(id, title, display_name, description, code_list_id, children_criteria, derivation_operator, definition, status, value, field_values, effective_time, inline_code_list, negation, negation_code_list_id, temporal_references, subset_operators, specific_occurrence, specific_occurrence_const, source_data_criteria=nil, comments=nil, variable=false)
175
+
176
+ status = normalize_status(definition, status)
177
+ @settings = HQMF::DataCriteria.get_settings_for_definition(definition, status)
178
+
179
+ @id = id
180
+ @title = title
181
+ @description = description
182
+ @code_list_id = code_list_id
183
+ @negation_code_list_id = negation_code_list_id
184
+ @children_criteria = children_criteria
185
+ @derivation_operator = derivation_operator
186
+ @definition = definition
187
+ @status = status
188
+ @value = value
189
+ @field_values = field_values
190
+ @effective_time = effective_time
191
+ @inline_code_list = inline_code_list
192
+ @negation = negation
193
+ @negation_code_list_id = negation_code_list_id
194
+ @temporal_references = temporal_references
195
+ @subset_operators = subset_operators
196
+ @specific_occurrence = specific_occurrence
197
+ @specific_occurrence_const = specific_occurrence_const
198
+ @source_data_criteria = source_data_criteria || id
199
+ @comments = comments
200
+ @variable = variable
201
+ end
202
+
203
+ # create a new data criteria given a category and sub_category. A sub category can either be a status or a sub category
204
+ def self.create_from_category(id, title, description, code_list_id, category, sub_category=nil, negation=false, negation_code_list_id=nil)
205
+ settings = HQMF::DataCriteria.get_settings_for_definition(category, sub_category)
206
+ HQMF::DataCriteria.new(id, title, nil, description, code_list_id, nil, nil, settings['definition'], settings['status'], nil, nil, nil, nil, negation, negation_code_list_id, nil, nil, nil,nil)
207
+ end
208
+ def type
209
+ @settings['category'].to_sym
210
+ end
211
+ def property
212
+ @settings['property'].to_sym unless @settings['property'].nil?
213
+ end
214
+ def patient_api_function
215
+ @settings['patient_api_function'].to_sym unless @settings['patient_api_function'].empty?
216
+ end
217
+ def hard_status
218
+ @settings['hard_status']
219
+ end
220
+ def update_copy(hard_status, title, description, derivation_operator, definition)
221
+ @settings['hard_status'] = hard_status
222
+ @title = title
223
+ @description = description
224
+ @derivation_operator = derivation_operator
225
+ @definition = definition
226
+ end
227
+ def definition=(definition)
228
+ @definition = definition
229
+ @settings = HQMF::DataCriteria.get_settings_for_definition(@definition, @status)
230
+ end
231
+ def status=(status)
232
+ @status = status
233
+ @settings = HQMF::DataCriteria.get_settings_for_definition(@definition, @status)
234
+ end
235
+
236
+ # Create a new data criteria instance from a JSON hash keyed with symbols
237
+ def self.from_json(id, json)
238
+
239
+ title = json["title"] if json["title"]
240
+ display_name = json["display_name"] if json["display_name"]
241
+ description = json["description"] if json["description"]
242
+ code_list_id = json["code_list_id"] if json["code_list_id"]
243
+ children_criteria = json["children_criteria"] if json["children_criteria"]
244
+ derivation_operator = json["derivation_operator"] if json["derivation_operator"]
245
+ definition = json["definition"] if json["definition"]
246
+ status = json["status"] if json["status"]
247
+ value = convert_value(json["value"]) if json["value"]
248
+ field_values = json["field_values"].inject({}){|memo,(k,v)| memo[k.to_s] = convert_value(v); memo} if json["field_values"]
249
+ effective_time = HQMF::Range.from_json(json["effective_time"]) if json["effective_time"]
250
+ inline_code_list = json["inline_code_list"].inject({}){|memo,(k,v)| memo[k.to_s] = v; memo} if json["inline_code_list"]
251
+ negation = json["negation"] || false
252
+ negation_code_list_id = json['negation_code_list_id'] if json['negation_code_list_id']
253
+ temporal_references = json["temporal_references"].map {|reference| HQMF::TemporalReference.from_json(reference)} if json["temporal_references"]
254
+ subset_operators = json["subset_operators"].map {|operator| HQMF::SubsetOperator.from_json(operator)} if json["subset_operators"]
255
+ specific_occurrence = json['specific_occurrence'] if json['specific_occurrence']
256
+ specific_occurrence_const = json['specific_occurrence_const'] if json['specific_occurrence_const']
257
+ source_data_criteria = json['source_data_criteria'] if json['source_data_criteria']
258
+ comments = json['comments'] if json['comments']
259
+ variable = json['variable'] || false
260
+
261
+ HQMF::DataCriteria.new(id, title, display_name, description, code_list_id, children_criteria, derivation_operator, definition, status, value, field_values,
262
+ effective_time, inline_code_list, negation, negation_code_list_id, temporal_references, subset_operators,specific_occurrence,specific_occurrence_const,source_data_criteria, comments, variable)
263
+ end
264
+
265
+ def is_same_type?(criteria)
266
+ return @definition == criteria.definition && @hard_status == criteria.hard_status &&
267
+ @negation == criteria.negation && all_code_set_oids.sort == criteria.all_code_set_oids.sort
268
+ end
269
+
270
+ def to_json
271
+ json = base_json
272
+ {self.id.to_s.to_sym => json}
273
+ end
274
+
275
+ def base_json
276
+ x = nil
277
+ json = build_hash(self, [:title,:display_name,:description,:code_list_id,:children_criteria, :derivation_operator, :property, :type, :definition, :status, :hard_status, :negation, :negation_code_list_id,:specific_occurrence,:specific_occurrence_const,:source_data_criteria,:variable])
278
+ json[:children_criteria] = @children_criteria unless @children_criteria.nil? || @children_criteria.empty?
279
+ json[:value] = ((@value.is_a? String) ? @value : @value.to_json) if @value
280
+ json[:field_values] = @field_values.inject({}) {|memo,(k,v)| memo[k] = (!v.nil? ? v.to_json : nil); memo} if @field_values
281
+ json[:effective_time] = @effective_time.to_json if @effective_time
282
+ json[:inline_code_list] = @inline_code_list if @inline_code_list
283
+ json[:temporal_references] = x if x = json_array(@temporal_references)
284
+ json[:subset_operators] = x if x = json_array(@subset_operators)
285
+ json[:comments] = @comments if @comments
286
+ json
287
+ end
288
+
289
+ def has_temporal(temporal_reference)
290
+ @temporal_references.reduce(false) {|found, item| found ||= item == temporal_reference }
291
+ end
292
+ def has_subset(subset_operator)
293
+ @subset_operators.reduce(false) {|found, item| found ||= item == subset_operator }
294
+ end
295
+
296
+ def self.statuses_by_definition
297
+ settings_file = File.expand_path('../data_criteria.json', __FILE__)
298
+ settings_map = JSON.parse(File.read(settings_file))
299
+ all_defs = (settings_map.map {|key, value| {category: value['category'],definition:value['definition'],status:(value['status'].empty? ? nil : value['status']), sub_category: value['sub_category'],title:value['title']} unless value['not_supported']}).compact
300
+ by_categories = {}
301
+ all_defs.each do |definition|
302
+ by_categories[definition[:category]]||={}
303
+ status = definition[:status]
304
+ def_key = definition[:definition]
305
+ if status.nil? and definition[:sub_category] and !definition[:sub_category].empty?
306
+ status = definition[:sub_category]
307
+ def_key = def_key.gsub("_#{status}",'')
308
+ end
309
+ by_categories[definition[:category]][def_key]||={category:def_key,statuses:[]}
310
+ by_categories[definition[:category]][def_key][:statuses] << status unless status.nil?
311
+ end
312
+ status_by_category = {}
313
+ by_categories.each {|key, value| status_by_category[key] = value.values}
314
+ status_by_category.delete('derived')
315
+ status_by_category.delete('variable')
316
+ status_by_category.delete('measurement_period')
317
+ status_by_category.values.flatten
318
+ end
319
+
320
+ def referenced_data_criteria(document)
321
+ referenced = []
322
+ if (@children_criteria)
323
+ @children_criteria.each do |id|
324
+ dc = document.data_criteria(id)
325
+ referenced << id
326
+ referenced.concat(dc.referenced_data_criteria(document))
327
+ end
328
+ end
329
+ if (@temporal_references)
330
+ @temporal_references.each do |tr|
331
+ id = tr.reference.id
332
+ if (id != HQMF::Document::MEASURE_PERIOD_ID)
333
+ dc = document.data_criteria(id)
334
+ referenced << id
335
+ referenced.concat(dc.referenced_data_criteria(document))
336
+ end
337
+ end
338
+ end
339
+ referenced
340
+ end
341
+
342
+ def all_code_set_oids
343
+
344
+ # root oid
345
+ referenced_oids = [code_list_id]
346
+
347
+ # value oid
348
+ referenced_oids << value.code_list_id if value != nil and value.type == 'CD'
349
+
350
+ # negation oid
351
+ referenced_oids << negation_code_list_id if negation_code_list_id != nil
352
+
353
+ # field oids
354
+ if field_values != nil
355
+ referenced_oids.concat (field_values.map {|key,field| field.code_list_id if field != nil and field.type == 'CD'})
356
+ end
357
+
358
+ referenced_oids
359
+
360
+ end
361
+
362
+ def self.get_settings_map
363
+ return @settings_map if @settings_map
364
+ settings_file = File.expand_path('../data_criteria.json', __FILE__)
365
+ @settings_map = JSON.parse(File.read(settings_file))
366
+ end
367
+
368
+ def self.get_settings_for_definition(definition, status)
369
+ settings_file = File.expand_path('../data_criteria.json', __FILE__)
370
+ settings_map = get_settings_map
371
+ key = definition + ((status.nil? || status.empty?) ? '' : "_#{status}")
372
+ settings = settings_map[key]
373
+
374
+ raise "data criteria is not supported #{key}" if settings.nil? || settings["not_supported"]
375
+
376
+ settings
377
+ end
378
+
379
+ def self.definition_for_template_id(template_id, version='r1')
380
+ get_template_id_map(version)[template_id]
381
+ end
382
+
383
+ def self.template_id_for_definition(definition, status, negation, version="r1")
384
+ # in r2 negation uses the same template as a positive assertion
385
+ negation = false if version == "r2"
386
+ get_template_id_map(version).key({'definition' => definition, 'status' => status || '', 'negation' => negation})
387
+ end
388
+
389
+ def self.title_for_template_id(template_id, version='r1')
390
+ value = get_template_id_map(version)[template_id]
391
+ if value
392
+ settings = self.get_settings_for_definition(value['definition'], value['status'])
393
+ if settings
394
+ settings['title']
395
+ else
396
+ 'Unknown data criteria'
397
+ end
398
+ else
399
+ 'Unknown template id'
400
+ end
401
+ end
402
+
403
+ def self.get_template_id_map(version="r1")
404
+ read_template_id_map(version)
405
+ end
406
+
407
+ private
408
+
409
+ def self.read_template_id_map(version)
410
+ HQMF::Util::HQMFTemplateHelper.template_id_map(version)
411
+ end
412
+
413
+ def normalize_status(definition, status)
414
+ return status if status.nil?
415
+ case status.downcase
416
+ when 'completed', 'complete'
417
+ case definition
418
+ when 'diagnosis'
419
+ 'active'
420
+ else
421
+ 'performed'
422
+ end
423
+ when 'order'
424
+ 'ordered'
425
+ else
426
+ status.downcase
427
+ end
428
+ end
429
+
430
+ def self.convert_value(json)
431
+ return nil unless json.present?
432
+ type = json["type"]
433
+ case type
434
+ when 'TS', 'PQ'
435
+ value = HQMF::Value.from_json(json)
436
+ when 'IVL_PQ', 'IVL_TS'
437
+ value = HQMF::Range.from_json(json)
438
+ when 'CD'
439
+ value = HQMF::Coded.from_json(json)
440
+ when 'ANYNonNull'
441
+ value = HQMF::AnyValue.from_json(json)
442
+ when 'FLFS'
443
+ value = HQMF::TypedReference.from_json(json)
444
+ when 'ACT'
445
+ # Currentlty forcing this as the SimpleXML reresentation contains a fulfills for these types
446
+ value = HQMF::TypedReference.new(json["reference"], 'FLFS', '')
447
+ when 'CMP'
448
+ # For ResultComponent, json will have a value or "" for referenceRangeLow_value, for Component, will be nil
449
+ if json['referenceRangeLow_value']
450
+ value = ResultComponent.new(json)
451
+ else
452
+ value = Component.new(json)
453
+ end
454
+ when 'COL'
455
+ value = HQMF::Collection.from_json(json)
456
+ when 'FAC'
457
+ value = Facility.new(json)
458
+ else
459
+ raise "Unknown value type [#{type}]"
460
+ end
461
+ value
462
+ end
463
+
464
+
465
+ end
466
+
467
+ end