cqm-parsers 0.1.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 (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