health-data-standards 1.0.1 → 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 (50) hide show
  1. data/Gemfile +5 -5
  2. data/lib/health-data-standards.rb +8 -0
  3. data/lib/health-data-standards/export/ccda.rb +15 -0
  4. data/lib/health-data-standards/export/ccr.rb +24 -20
  5. data/lib/health-data-standards/export/html.rb +4 -8
  6. data/lib/health-data-standards/export/template_helper.rb +7 -1
  7. data/lib/health-data-standards/export/view_helper.rb +24 -4
  8. data/lib/health-data-standards/import/c32/allergy_importer.rb +2 -1
  9. data/lib/health-data-standards/import/c32/care_goal_importer.rb +2 -1
  10. data/lib/health-data-standards/import/c32/condition_importer.rb +23 -2
  11. data/lib/health-data-standards/import/c32/encounter_importer.rb +6 -5
  12. data/lib/health-data-standards/import/c32/immunization_importer.rb +1 -15
  13. data/lib/health-data-standards/import/c32/medication_importer.rb +1 -0
  14. data/lib/health-data-standards/import/c32/patient_importer.rb +13 -0
  15. data/lib/health-data-standards/import/c32/procedure_importer.rb +1 -0
  16. data/lib/health-data-standards/import/c32/provider_importer.rb +1 -1
  17. data/lib/health-data-standards/import/c32/result_importer.rb +1 -0
  18. data/lib/health-data-standards/import/c32/section_importer.rb +17 -22
  19. data/lib/health-data-standards/import/ccda/medical_equipment_importer.rb +1 -2
  20. data/lib/health-data-standards/import/ccda/result_importer.rb +1 -0
  21. data/lib/health-data-standards/import/ccr/provider_importer.rb +0 -1
  22. data/lib/health-data-standards/import/green_c32/immunization_importer.rb +1 -1
  23. data/lib/health-data-standards/import/green_c32/section_importer.rb +7 -5
  24. data/lib/health-data-standards/import/provider_import_utils.rb +1 -1
  25. data/lib/health-data-standards/models/coded_result_value.rb +3 -0
  26. data/lib/health-data-standards/models/condition.rb +7 -5
  27. data/lib/health-data-standards/models/encounter.rb +5 -2
  28. data/lib/health-data-standards/models/entry.rb +48 -68
  29. data/lib/health-data-standards/models/facility.rb +9 -0
  30. data/lib/health-data-standards/models/functional_status.rb +25 -0
  31. data/lib/health-data-standards/models/immunization.rb +4 -6
  32. data/lib/health-data-standards/models/medication.rb +6 -0
  33. data/lib/health-data-standards/models/physical_quantity_result_value.rb +4 -0
  34. data/lib/health-data-standards/models/record.rb +11 -2
  35. data/lib/health-data-standards/models/result_value.rb +4 -0
  36. data/lib/health-data-standards/models/thing_with_codes.rb +52 -0
  37. data/lib/health-data-standards/util/code_system_helper.rb +5 -2
  38. data/lib/health-data-standards/util/qrda_template_helper.rb +20 -0
  39. data/templates/_allergies_no_current.c32.erb +26 -4
  40. data/templates/_conditions_no_current.c32.erb +1 -2
  41. data/templates/_medications_no_current.c32.erb +1 -1
  42. data/templates/_narrative_block.c32.erb +14 -2
  43. data/templates/_result.gc32.erb +3 -1
  44. data/templates/_results.c32.erb +9 -8
  45. data/templates/_social_history.gc32.erb +1 -1
  46. data/templates/_vital_sign.gc32.erb +1 -1
  47. data/templates/_vital_signs.c32.erb +12 -11
  48. data/templates/show.c32.erb +1 -1
  49. metadata +55 -21
  50. data/templates/show.html.erb +0 -287
@@ -4,10 +4,9 @@ module HealthDataStandards
4
4
  class MedicalEquipmentImporter < C32::MedicalEquipmentImporter
5
5
 
6
6
  def initialize
7
- @entry_xpath = "//cda:section[cda:templateId/@root='2.16.840.1.113883.10.20.22.4.50']/cda:entry/cda:supply"
7
+ @entry_xpath = "//cda:section[cda:templateId/@root='2.16.840.1.113883.10.20.22.2.23']/cda:entry/cda:supply"
8
8
  @code_xpath = "./cda:participant/cda:participantRole/cda:playingDevice/cda:code"
9
9
  @description_xpath = "./cda:code/cda:originalText/cda:reference[@value] | ./cda:text/cda:reference[@value]"
10
-
11
10
  end
12
11
 
13
12
  end
@@ -6,6 +6,7 @@ module HealthDataStandards
6
6
  @entry_xpath = "//cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.22.4.2']"
7
7
  @code_xpath = "./cda:code"
8
8
  @description_xpath = "./cda:code/cda:originalText/cda:reference[@value] | ./cda:text/cda:reference[@value] "
9
+ @check_for_usable = true
9
10
  end
10
11
  end
11
12
  end
@@ -33,7 +33,6 @@ module HealthDataStandards
33
33
  provider[:npi] = npi if Provider.valid_npi?(npi)
34
34
  end
35
35
 
36
- # binding.pry
37
36
  find_or_create_provider(provider)
38
37
  end
39
38
 
@@ -13,7 +13,7 @@ module HealthDataStandards
13
13
  extract_code(immunization_element, immunization, "./gc32:refusalReason")
14
14
  series_number = extract_node_text(immunization_element.at_xpath("./gc32:seriesNumber"))
15
15
  immunization.series_number = series_number.to_i if series_number
16
- immunization.refusalInd = extract_node_attribute(immunization_element, :refused, true)
16
+ immunization.refusal_ind = extract_node_attribute(immunization_element, :refused, true)
17
17
  immunization
18
18
  end
19
19
 
@@ -71,7 +71,7 @@ module HealthDataStandards
71
71
 
72
72
  return unless datetime && datetime['value']
73
73
 
74
- entry.send("#{attribute}=", Time.parse(datetime['value']).to_i)
74
+ entry.send("#{attribute}=", Time.parse(datetime['value']).utc.to_i)
75
75
  end
76
76
 
77
77
  def extract_interval(element, entry, element_name="effectiveTime")
@@ -89,11 +89,14 @@ module HealthDataStandards
89
89
 
90
90
  return {} unless node_value
91
91
 
92
- {'scalar' => node_value, "unit" => node_units}
92
+ {"scalar" => node_value, "unit" => node_units}
93
93
  end
94
94
 
95
95
  def extract_value(element, entry)
96
- entry.value = extract_quantity(element, @value)
96
+ pq = extract_quantity(element, @value)
97
+ if pq.present?
98
+ entry.values << PhysicalQuantityResultValue.new(pq)
99
+ end
97
100
  end
98
101
 
99
102
  def extract_entry(element, entry)
@@ -142,8 +145,7 @@ module HealthDataStandards
142
145
  def extract_free_text(element, entry, free_text_element="freeText")
143
146
  entry.free_text = extract_node_text(element.at_xpath("./gc32:#{free_text_element}"))
144
147
  end
145
-
146
-
148
+
147
149
  private
148
150
 
149
151
  def build_code(code_element)
@@ -6,7 +6,7 @@ module ProviderImportUtils
6
6
  end
7
7
 
8
8
  def find_or_create_provider(provider_hash)
9
- provider = Provider.first(conditions: {npi: provider_hash[:npi]}) if provider_hash[:npi] && !provider_hash[:npi].empty?
9
+ provider = Provider.where(npi: provider_hash[:npi]).first if provider_hash[:npi] && !provider_hash[:npi].empty?
10
10
  provider ||= Provider.create(provider_hash)
11
11
  end
12
12
 
@@ -0,0 +1,3 @@
1
+ class CodedResultValue < ResultValue
2
+ include ThingWithCodes
3
+ end
@@ -1,9 +1,11 @@
1
1
  class Condition < Entry
2
- field :type, type: String
3
- field :causeOfDeath, type: Boolean
4
- field :priority, type: Integer
5
- field :name, type: String
6
- field :ordinality, type: String
2
+ field :type, type: String
3
+ field :causeOfDeath, type: Boolean
4
+ field :priority, type: Integer
5
+ field :name, type: String
6
+ field :ordinality, type: String
7
+ field :ordinality_code, type: Hash
8
+ field :severity, type: Hash # Currently unsupported by any importers
7
9
 
8
10
  embeds_many :treating_provider, class_name: "Provider"
9
11
 
@@ -1,8 +1,11 @@
1
1
  class Encounter < Entry
2
+
2
3
  field :admitType, type: Hash
3
4
  field :dischargeDisposition, type: Hash
4
-
5
- embeds_one :facility, class_name: "Organization"
5
+ field :admit_time, type: Integer
6
+ field :discharge_time, type: Integer
7
+
8
+ embeds_one :facility
6
9
  embeds_one :reason, class_name: "Entry"
7
10
 
8
11
  belongs_to :performer, class_name: "Provider"
@@ -1,61 +1,35 @@
1
1
  class Entry
2
2
 
3
3
  include Mongoid::Document
4
+ include ThingWithCodes
4
5
 
5
6
  # embedded_in :entry_list, polymorphic: true
6
7
 
7
8
  embedded_in :record
8
9
 
10
+ embeds_many :values, class_name: "ResultValue"
11
+
9
12
  field :description, type: String
10
13
  field :specifics, type: String
11
14
  field :time, type: Integer
12
15
  field :start_time, type: Integer
13
16
  field :end_time, type: Integer
14
- field :status, type: String
15
- field :codes, type: Hash, default: {}
16
- field :value, type: Hash, default: {}
17
+ field :status_code, type: Hash
17
18
  field :free_text, type: String
18
19
  field :mood_code, type: String, default: "EVN"
20
+ field :negationInd, type: Boolean
21
+ field :negationReason, type: Hash
22
+ field :oid, type: String
23
+
24
+ alias :negation_ind :negationInd
25
+ alias :negation_ind= :negationInd=
26
+ alias :negation_reason :negationReason
27
+ alias :negation_reason= :negationReason=
19
28
 
20
29
  attr_protected :version
21
30
  attr_protected :_id
22
31
  attr_protected :created_at
23
32
  attr_protected :updated_at
24
-
25
- def single_code_value?
26
- codes.size == 1 && codes.first[1].size == 1
27
- end
28
-
29
- def codes_to_s
30
- codes.map {|code_set, codes| "#{code_set}: #{codes.join(', ')}"}.join(' ')
31
- end
32
-
33
- # Will return a single code and code set if one exists in the code sets that are
34
- # passed in. Returns a hash with a key of code and code_set if found, nil otherwise
35
- def preferred_code(preferred_code_sets, codes_attribute=:codes)
36
- codes_value = send(codes_attribute)
37
- matching_code_sets = preferred_code_sets & codes_value.keys
38
- if matching_code_sets.present?
39
- code_set = matching_code_sets.first
40
- {'code' => codes_value[code_set].first, 'code_set' => code_set}
41
- else
42
- nil
43
- end
44
- end
45
-
46
- # Will return an Array of code and code_set hashes for all codes for this entry
47
- # except for the preferred_code. It is intended that these codes would be used in
48
- # the translation elements as childern of a CDA code element
49
- def translation_codes(preferred_code_sets)
50
- tx_codes = []
51
- codes.each_pair do |code_set, code_list|
52
- code_list.each do |code|
53
- tx_codes << {'code' => code, 'code_set' => code_set}
54
- end
55
- end
56
-
57
- tx_codes - [preferred_code(preferred_code_sets)]
58
- end
59
33
 
60
34
  def times_to_s
61
35
  if start_time.present? || end_time.present?
@@ -67,24 +41,38 @@ class Entry
67
41
  end
68
42
  end
69
43
 
70
- # def to_effective_time(xml)
71
- # if time.present?
72
- # xml.effectiveTime("value" => Time.at(time).utc.to_formatted_s(:number))
73
- # else
74
- # xml.effectiveTime do
75
- # if start_time.present?
76
- # xml.low("value" => Time.at(start_time).utc.to_formatted_s(:number))
77
- # else
78
- # xml.low("nullFlavor" => "UNK")
79
- # end
80
- # if end_time.present?
81
- # xml.high("value" => Time.at(end_time).utc.to_formatted_s(:number))
82
- # else
83
- # xml.high("nullFlavor" => "UNK")
84
- # end
85
- # end
86
- # end
87
- # end
44
+ # Entry previously had a status field that dropped the code set and converted
45
+ # the status to a String. Entry now preserves the original code and code set.
46
+ # This method is here to maintain backwards compatibility.
47
+ def status
48
+ if status_code.present?
49
+ if status_code['HL7 ActStatus']
50
+ status_code['HL7 ActStatus'].first()
51
+ elsif status_code['SNOMED-CT']
52
+ case status_code['SNOMED-CT'].first()
53
+ when '55561003'
54
+ 'active'
55
+ when '73425007'
56
+ 'inactive'
57
+ when '413322009'
58
+ 'resolved'
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def status=(status_text)
65
+ case status_text.to_s # makes sure that any Symbols passed in are stringified
66
+ when 'active'
67
+ self.status_code = {'SNOMED-CT' => ['55561003'], 'HL7 ActStatus' => ['active']}
68
+ when 'inactive'
69
+ self.status_code = {'SNOMED-CT' => ['73425007']}
70
+ when 'resolved'
71
+ self.status_code = {'SNOMED-CT' => ['413322009']}
72
+ when 'completed'
73
+ self.status_code = {'HL7 ActStatus' => ['completed']}
74
+ end
75
+ end
88
76
 
89
77
  def self.from_event_hash(event)
90
78
  entry = Entry.new
@@ -107,20 +95,12 @@ class Entry
107
95
  entry
108
96
  end
109
97
 
110
- # Add a code into the Entry
111
- # @param [String] code the code to add
112
- # @param [String] code_system the code system that the code belongs to
113
- def add_code(code, code_system)
114
- self.codes[code_system] ||= []
115
- self.codes[code_system] << code
116
- end
117
-
118
98
  # Sets the value for the entry
119
99
  # @param [String] scalar the value
120
100
  # @param [String] units the units of the scalar value
121
101
  def set_value(scalar, units=nil)
122
- self.value[:scalar] = scalar
123
- self.value[:units] = units
102
+ pq_value = PhysicalQuantityResultValue.new(scalar: scalar, units: units)
103
+ self.values << pq_value
124
104
  end
125
105
 
126
106
  # Checks if a code is in the list of possible codes
@@ -186,8 +166,8 @@ class Entry
186
166
  def to_hash
187
167
  entry_hash = {}
188
168
  entry_hash['codes'] = codes
189
- unless value.empty?
190
- entry_hash['value'] = value
169
+ unless values.empty?
170
+ entry_hash['value'] = values
191
171
  end
192
172
 
193
173
  if is_date_range?
@@ -0,0 +1,9 @@
1
+ class Facility
2
+ include Mongoid::Document
3
+
4
+ field :name, type: String
5
+ field :code, type: Hash
6
+
7
+ embeds_many :addresses, as: :locatable
8
+ embeds_many :telecoms, as: :contactable
9
+ end
@@ -0,0 +1,25 @@
1
+ # This class can be used to represnt a functional status for a patient. Currently,
2
+ # it is not a very close representation of functional status as it is represented
3
+ # in the HL7 CCD, HITSP C32 or Consolidated CDA.
4
+ #
5
+ # In the previously mentioned specifications, functional status may represented
6
+ # using either a condition or result. Having "mixed" types of entries in a section
7
+ # is currently not well supported in the existing Record class
8
+ #
9
+ # Additionally, there is a mismatch between the data needed to calculate Stage 2
10
+ # Meaningful Use Quailty Measures and the data contained in patient summary
11
+ # standards. The CQMs are checking to see if a functional status represented by
12
+ # a result was patient supplied. Right now, results do not have a source, and
13
+ # even if we were to use Provider as a source, it would need to be extended
14
+ # to support patients.
15
+ #
16
+ # To avoid this, the patient sumamry style functional status has been "flattened"
17
+ # into this class. This model supports the information needed to calculate
18
+ # Stage 2 MU CQMs. If importers are created from C32 or CCDA, the information
19
+ # can be stored here, but it will be a lossy transformation.
20
+ class FunctionalStatus < Entry
21
+ # Either "condition" or "result"
22
+ field :type, type: String
23
+ # A coded value. Like a code for patient supplied.
24
+ field :source, type: Hash
25
+ end
@@ -1,16 +1,14 @@
1
1
  class Immunization < Entry
2
- field :refusalInd, type: Boolean
3
- field :refusalReason, type: Hash
4
2
  field :seriesNumber, type: Integer
5
3
 
6
4
  belongs_to :performer, class_name: "Provider"
7
5
 
8
6
  embeds_one :medication_product
9
7
 
10
- alias :refusal_ind :refusalInd
11
- alias :refusal_ind= :refusalInd=
12
- alias :refusal_reason :refusalReason
13
- alias :refusal_reason= :refusalReason=
8
+ alias :refusal_ind :negationInd
9
+ alias :refusal_ind= :negationInd=
10
+ alias :refusal_reason :negationReason
11
+ alias :refusal_reason= :negationReason=
14
12
  alias :series_number :seriesNumber
15
13
  alias :series_number= :seriesNumber=
16
14
  end
@@ -18,6 +18,10 @@ class Medication < Entry
18
18
  field :deliveryMethod, type: Hash
19
19
  field :patientInstructions, type: String
20
20
  field :doseIndicator, type: String
21
+
22
+ # There are currently no importers that support this field
23
+ # It is expected to be a scalar and value, such as 7 days
24
+ field :cumulativeMedicationDuration, type: Hash
21
25
 
22
26
  alias :administration_timing :administrationTiming
23
27
  alias :administration_timing= :administrationTiming=
@@ -41,4 +45,6 @@ class Medication < Entry
41
45
  alias :patient_instructions= :patientInstructions=
42
46
  alias :dose_indicator :doseIndicator
43
47
  alias :dose_indicator= :doseIndicator=
48
+ alias :cumulative_medication_duration :cumulativeMedicationDuration
49
+ alias :cumulative_medication_duration= :cumulativeMedicationDuration=
44
50
  end
@@ -0,0 +1,4 @@
1
+ class PhysicalQuantityResultValue < ResultValue
2
+ field :scalar
3
+ field :units, type: String
4
+ end
@@ -12,9 +12,13 @@ class Record
12
12
  field :race, type: Hash
13
13
  field :ethnicity, type: Hash
14
14
  field :languages, type: Array
15
- field :test_id, type: BSON::ObjectId
15
+ field :test_id, type: Moped::BSON::ObjectId
16
16
  field :marital_status, type: Hash # TODO
17
17
  field :medical_record_number, type: String
18
+ field :expired, type: Boolean
19
+ field :clinicalTrialParticipant, type: Boolean # Currently not implemented in the C32 importer
20
+ # because it cannot be easily represented in a
21
+ # HITSP C32
18
22
 
19
23
  embeds_many :allergies
20
24
  embeds_many :care_goals, class_name: "Entry" # This can be any number of different entry types
@@ -30,9 +34,11 @@ class Record
30
34
  embeds_many :support
31
35
  embeds_many :advance_directives, class_name: "Entry"
32
36
  embeds_many :insurance_providers
37
+ embeds_many :functional_statuses
33
38
 
34
39
  Sections = [:allergies, :care_goals, :conditions, :encounters, :immunizations, :medical_equipment,
35
- :medications, :procedures, :results, :social_history, :vital_signs, :support, :advanced_directives]
40
+ :medications, :procedures, :results, :social_history, :vital_signs, :support, :advanced_directives,
41
+ :functional_statuses]
36
42
 
37
43
  embeds_many :provider_performances
38
44
 
@@ -47,6 +53,9 @@ class Record
47
53
  Time.at(birthdate) < Time.now.years_ago(18)
48
54
  end
49
55
 
56
+ alias :clinical_trial_participant :clinicalTrialParticipant
57
+ alias :clinical_trial_participant= :clinicalTrialParticipant=
58
+
50
59
  private
51
60
 
52
61
  def self.provider_queries(provider_id, effective_date)
@@ -0,0 +1,4 @@
1
+ class ResultValue
2
+ include Mongoid::Document
3
+ embedded_in :entry
4
+ end
@@ -0,0 +1,52 @@
1
+ module ThingWithCodes
2
+ def self.included(receiver)
3
+ receiver.field :codes, type: Hash, default: {}
4
+ end
5
+
6
+ def single_code_value?
7
+ codes.size == 1 && codes.first[1].size == 1
8
+ end
9
+
10
+ def codes_to_s
11
+ ThingWithCodes.convert_codes_to_s(codes)
12
+ end
13
+
14
+ def self.convert_codes_to_s(codes)
15
+ codes.map {|code_set, codes| "#{code_set}: #{codes.join(', ')}"}.join(' ')
16
+ end
17
+
18
+ # Will return a single code and code set if one exists in the code sets that are
19
+ # passed in. Returns a hash with a key of code and code_set if found, nil otherwise
20
+ def preferred_code(preferred_code_sets, codes_attribute=:codes)
21
+ codes_value = send(codes_attribute)
22
+ matching_code_sets = preferred_code_sets & codes_value.keys
23
+ if matching_code_sets.present?
24
+ code_set = matching_code_sets.first
25
+ {'code' => codes_value[code_set].first, 'code_set' => code_set}
26
+ else
27
+ nil
28
+ end
29
+ end
30
+
31
+ # Will return an Array of code and code_set hashes for all codes for this entry
32
+ # except for the preferred_code. It is intended that these codes would be used in
33
+ # the translation elements as childern of a CDA code element
34
+ def translation_codes(preferred_code_sets)
35
+ tx_codes = []
36
+ codes.each_pair do |code_set, code_list|
37
+ code_list.each do |code|
38
+ tx_codes << {'code' => code, 'code_set' => code_set}
39
+ end
40
+ end
41
+
42
+ tx_codes - [preferred_code(preferred_code_sets)]
43
+ end
44
+
45
+ # Add a code into the Entry
46
+ # @param [String] code the code to add
47
+ # @param [String] code_system the code system that the code belongs to
48
+ def add_code(code, code_system)
49
+ self.codes[code_system] ||= []
50
+ self.codes[code_system] << code
51
+ end
52
+ end