health-data-standards 0.3.0 → 0.5.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 (42) hide show
  1. data/Gemfile +5 -2
  2. data/Rakefile +6 -1
  3. data/VERSION +1 -1
  4. data/lib/health-data-standards.rb +27 -2
  5. data/lib/health-data-standards/export/view_helper.rb +15 -16
  6. data/lib/health-data-standards/ext/string.rb +5 -0
  7. data/lib/health-data-standards/ext/symbol.rb +8 -0
  8. data/lib/health-data-standards/import/c32/allergy_importer.rb +42 -0
  9. data/lib/health-data-standards/import/c32/encounter_importer.rb +80 -0
  10. data/lib/health-data-standards/import/c32/immunization_importer.rb +61 -0
  11. data/lib/health-data-standards/import/c32/medication_importer.rb +138 -0
  12. data/lib/health-data-standards/import/c32/patient_importer.rb +139 -0
  13. data/lib/health-data-standards/import/c32/procedure_importer.rb +55 -0
  14. data/lib/health-data-standards/import/c32/result_importer.rb +58 -0
  15. data/lib/health-data-standards/import/c32/section_importer.rb +214 -0
  16. data/lib/health-data-standards/import/c32/vital_sign_importer.rb +12 -0
  17. data/lib/health-data-standards/models/allergy.rb +4 -0
  18. data/lib/health-data-standards/models/encounter.rb +7 -0
  19. data/lib/health-data-standards/models/entry.rb +131 -3
  20. data/lib/health-data-standards/models/fulfillment_history.rb +11 -0
  21. data/lib/health-data-standards/models/immunization.rb +5 -0
  22. data/lib/health-data-standards/models/lab_result.rb +4 -0
  23. data/lib/health-data-standards/models/medication.rb +25 -0
  24. data/lib/health-data-standards/models/order_information.rb +9 -0
  25. data/lib/health-data-standards/models/procedure.rb +4 -0
  26. data/lib/health-data-standards/models/record.rb +1 -0
  27. data/lib/health-data-standards/util/code_system_helper.rb +41 -0
  28. data/lib/health-data-standards/util/hl7_helper.rb +25 -0
  29. data/templates/_allergies.c32.erb +3 -1
  30. data/templates/_care_goals.c32.erb +1 -1
  31. data/templates/_code_with_reference.c32.erb +10 -5
  32. data/templates/_conditions.c32.erb +2 -2
  33. data/templates/_encounters.c32.erb +2 -1
  34. data/templates/_immunizations.c32.erb +2 -1
  35. data/templates/_medical_equipment.c32.erb +1 -1
  36. data/templates/_medications.c32.erb +2 -1
  37. data/templates/_narrative_block.c32.erb +14 -0
  38. data/templates/_procedures.c32.erb +2 -1
  39. data/templates/_results.c32.erb +2 -2
  40. data/templates/_social_history.c32.erb +1 -1
  41. data/templates/_vital_signs.c32.erb +2 -2
  42. metadata +37 -16
@@ -0,0 +1,12 @@
1
+ module HealthDataStandards
2
+ module Import
3
+ module C32
4
+ class VitalSignImporter < ResultImporter
5
+ def initialize
6
+ super
7
+ @entry_xpath = "//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.14']"
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ class Allergy < Entry
2
+ field :reaction, type: Hash
3
+ field :severity, type: Hash
4
+ end
@@ -0,0 +1,7 @@
1
+ class Encounter < Entry
2
+ field :performer, type: Hash
3
+ field :facility, type: Hash
4
+ field :admitType, type: Hash, as: 'admit_type'
5
+ field :dischargeDisp, type: Hash, as: 'discharge_disp'
6
+ embeds_one :reason, class_name: "Entry"
7
+ end
@@ -8,9 +8,9 @@ class Entry
8
8
  field :time, type: Integer
9
9
  field :start_time, type: Integer
10
10
  field :end_time, type: Integer
11
- field :status, type: Symbol
12
- field :codes, type: Hash
13
- field :value, type: Hash
11
+ field :status, type: String
12
+ field :codes, type: Hash, default: {}
13
+ field :value, type: Hash, default: {}
14
14
 
15
15
  def single_code_value?
16
16
  codes.size == 1 && codes.first[1].size == 1
@@ -20,6 +20,32 @@ class Entry
20
20
  codes.map {|code_set, codes| "#{code_set}: #{codes.join(', ')}"}.join(' ')
21
21
  end
22
22
 
23
+ # Will return a single code and code set if one exists in the code sets that are
24
+ # passed in. Returns a hash with a key of code and code_set if found, nil otherwise
25
+ def preferred_code(preferred_code_sets)
26
+ matching_code_sets = preferred_code_sets & codes.keys
27
+ if matching_code_sets.present?
28
+ code_set = matching_code_sets.first
29
+ {'code' => codes[code_set].first, 'code_set' => code_set}
30
+ else
31
+ nil
32
+ end
33
+ end
34
+
35
+ # Will return an Array of code and code_set hashes for all codes for this entry
36
+ # except for the preferred_code. It is intended that these codes would be used in
37
+ # the translation elements as childern of a CDA code element
38
+ def translation_codes(preferred_code_sets)
39
+ tx_codes = []
40
+ codes.each_pair do |code_set, code_list|
41
+ code_list.each do |code|
42
+ tx_codes << {'code' => code, 'code_set' => code_set}
43
+ end
44
+ end
45
+
46
+ tx_codes - [preferred_code(preferred_code_sets)]
47
+ end
48
+
23
49
  def times_to_s
24
50
  if start_time.present? || end_time.present?
25
51
  start_string = start_time ? Time.at(start_time).to_formatted_s(:long_ordinal) : 'UNK'
@@ -48,4 +74,106 @@ class Entry
48
74
  end
49
75
  end
50
76
  end
77
+
78
+ def self.from_event_hash(event)
79
+ entry = Entry.new
80
+ if event['code']
81
+ entry.add_code(event['code'], event['code_set'])
82
+ end
83
+ entry.time = event['time']
84
+ if event['value']
85
+ entry.set_value(event['value'], event['unit'])
86
+ end
87
+ if event['description']
88
+ entry.description = event['description']
89
+ end
90
+ if event['status']
91
+ entry.status = event['status']
92
+ end
93
+ entry
94
+ end
95
+
96
+ # Add a code into the Entry
97
+ # @param [String] code the code to add
98
+ # @param [String] code_system the code system that the code belongs to
99
+ def add_code(code, code_system)
100
+ self.codes[code_system] ||= []
101
+ self.codes[code_system] << code
102
+ end
103
+
104
+ # Sets the value for the entry
105
+ # @param [String] scalar the value
106
+ # @param [String] units the units of the scalar value
107
+ def set_value(scalar, units=nil)
108
+ self.value[:scalar] = scalar
109
+ self.value[:units] = units
110
+ end
111
+
112
+ # Checks if a code is in the list of possible codes
113
+ # @param [Array] code_set an Array of Hashes that describe the values for code sets
114
+ # @return [true, false] whether the code is in the list of desired codes
115
+ def is_in_code_set?(code_set)
116
+ codes.keys.each do |code_system|
117
+ all_codes_in_system = code_set.find_all {|set| set['set'] == code_system}
118
+ all_codes_in_system.each do |codes_in_system|
119
+ matching_codes = codes_in_system['values'] & codes[code_system]
120
+ if matching_codes.length > 0
121
+ return true
122
+ end
123
+ end
124
+ end
125
+ false
126
+ end
127
+
128
+ # Tries to find a single point in time for this entry. Will first return time if it is present,
129
+ # then fall back to start_time and finally end_time
130
+ def as_point_in_time
131
+ if time
132
+ time
133
+ elsif start_time
134
+ start_time
135
+ else
136
+ end_time
137
+ end
138
+ end
139
+
140
+ # Checks to see if this Entry can be used as a date range
141
+ # @return [true, false] If the Entry has a start and end time returns true, false otherwise.
142
+ def is_date_range?
143
+ start_time.present? && end_time.present?
144
+ end
145
+
146
+ # Checks to see if this Entry is usable for measure calculation. This means that it contains
147
+ # at least one code and has one of its time properties set (start, end or time)
148
+ # @return [true, false]
149
+ def usable?
150
+ codes.present? && (start_time.present? || end_time.present? || time.present?)
151
+ end
152
+
153
+ # Creates a Hash for this Entry
154
+ # @return [Hash] a Hash representing the Entry
155
+ def to_hash
156
+ entry_hash = {}
157
+ entry_hash['codes'] = codes
158
+ unless value.empty?
159
+ entry_hash['value'] = value
160
+ end
161
+
162
+ if is_date_range?
163
+ entry_hash['start_time'] = start_time
164
+ entry_hash['end_time'] = end_time
165
+ else
166
+ entry_hash['time'] = as_point_in_time
167
+ end
168
+
169
+ if status
170
+ entry_hash['status'] = status
171
+ end
172
+
173
+ if description
174
+ entry_hash['description'] = description
175
+ end
176
+
177
+ entry_hash
178
+ end
51
179
  end
@@ -0,0 +1,11 @@
1
+ class FulfillmentHistory
2
+ include Mongoid::Document
3
+
4
+ field :prescriptionNumber, type: String, as: 'prescription_number'
5
+ field :provider, type: Hash
6
+ field :dispensingPharmacyLocation, type: Hash, as: 'dispensing_pharmacy_location'
7
+ field :dispenseDate, type: Integer, as: 'dispense_date'
8
+ field :quantityDispensed, type: Hash, as: 'quantity_dispensed'
9
+ field :fillNumber, type: Integer, as: 'fill_number'
10
+ field :fillStatus, type: Hash, as: 'fill_status'
11
+ end
@@ -0,0 +1,5 @@
1
+ class Immunization < Entry
2
+ field :refusalInd, type: Boolean, as: 'refusal_ind'
3
+ field :performer, type: Hash
4
+ field :refusalReason, type: Hash, as: 'refusal_reason'
5
+ end
@@ -0,0 +1,4 @@
1
+ class LabResult < Entry
2
+ field :referenceRange, type: String, as: 'reference_range'
3
+ field :interpretation, type: Hash
4
+ end
@@ -0,0 +1,25 @@
1
+ class Medication < Entry
2
+ field :administrationTiming, type: Hash, as: 'administration_timing'
3
+ field :freeTextSig, type: String, as: 'free_text_sig'
4
+ field :dose, type: Hash
5
+ field :brandName, type: String, as: 'brand_name'
6
+ field :typeOfMedication, type: Hash, as: 'type_of_medication'
7
+ field :statusOfMedication, type: Hash, as: 'status_of_medication'
8
+ embeds_many :fulfillmentHistory, class_name: 'FulfillmentHistory'
9
+ embeds_many :orderInformation, class_name: 'OrderInformation'
10
+ field :route, type: Hash
11
+ field :site, type: Hash
12
+ field :doseRestriction, type: Hash, as: 'dose_restriction'
13
+ field :fulfillmentInstructions, type: String, as: 'fulfillment_instructions'
14
+ field :indication, type: Hash
15
+ field :productForm, type: Hash, as: 'product_form'
16
+ field :vehicle, type: Hash
17
+ field :reaction, type: Hash
18
+ field :deliveryMethod, type: Hash, as: 'delivery_method'
19
+ field :patientInstructions, type: String, as: 'patient_instructions'
20
+
21
+ alias :fulfillment_history :fulfillmentHistory
22
+ alias :fulfillment_history= :fulfillmentHistory=
23
+ alias :order_information :orderInformation
24
+ alias :order_information= :orderInformation=
25
+ end
@@ -0,0 +1,9 @@
1
+ class OrderInformation
2
+ include Mongoid::Document
3
+
4
+ field :orderNumber, type: String, as: 'order_number'
5
+ field :fills, type: Integer
6
+ field :quantityOrdered, type: Hash, as: 'quantity_ordered'
7
+ field :orderExpirationDateTime, type: Integer, as: 'order_expiration_date_time'
8
+ field :orderDateTime, type: Integer, as: 'order_date_time'
9
+ end
@@ -0,0 +1,4 @@
1
+ class Procedure < Entry
2
+ field :performer, type: Hash
3
+ field :site, type: Hash
4
+ end
@@ -9,6 +9,7 @@ class Record
9
9
  field :race, type: String
10
10
  field :ethnicity, type: String
11
11
  field :test_id, type: BSON::ObjectId
12
+ field :medical_record_number, type: String
12
13
 
13
14
  [:allergies, :care_goals, :conditions, :encounters, :immunizations, :medical_equipment,
14
15
  :medications, :procedures, :results, :social_history, :vital_signs].each do |section|
@@ -0,0 +1,41 @@
1
+ module HealthDataStandards
2
+ module Util
3
+ # General helpers for working with codes and code systems
4
+ class CodeSystemHelper
5
+ CODE_SYSTEMS = {
6
+ '2.16.840.1.113883.6.1' => 'LOINC',
7
+ '2.16.840.1.113883.6.96' => 'SNOMED-CT',
8
+ '2.16.840.1.113883.6.12' => 'CPT',
9
+ #'2.16.840.1.113883.3.88.12.80.32' => 'CPT', # Encounter Type from C32, a subset of CPT
10
+ '2.16.840.1.113883.6.88' => 'RxNorm',
11
+ '2.16.840.1.113883.6.103' => 'ICD-9-CM',
12
+ '2.16.840.1.113883.6.104' => 'ICD-9-CM',
13
+ '2.16.840.1.113883.6.90' => 'ICD-10-CM',
14
+ '2.16.840.1.113883.6.14' => 'HCPCS',
15
+ '2.16.840.1.113883.6.59' => 'CVX',
16
+ '2.16.840.1.113883.5.83' => 'HITSP C80 Observation Status'
17
+ }
18
+
19
+ # Returns the name of a code system given an oid
20
+ # @param [String] oid of a code system
21
+ # @return [String] the name of the code system as described in the measure definition JSON
22
+ def self.code_system_for(oid)
23
+ CODE_SYSTEMS[oid] || "Unknown"
24
+ end
25
+
26
+ # Returns the oid for a code system given a codesystem name
27
+ # @param [String] the name of the code system
28
+ # @return [String] the oid of the code system
29
+ def self.oid_for_code_system(code_system)
30
+ CODE_SYSTEMS.invert[code_system]
31
+ end
32
+
33
+ # Returns the whole map of OIDs to code systems
34
+ # @terurn [Hash] oids as keys, code system names as values
35
+ def self.code_systems
36
+ CODE_SYSTEMS
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,25 @@
1
+ module HealthDataStandards
2
+ module Util
3
+ # General helpers for working with HL7 data types
4
+ class HL7Helper
5
+
6
+ # Converts an HL7 timestamp into an Integer
7
+ # @param [String] timestamp the HL7 timestamp. Expects YYYYMMDD format
8
+ # @return [Integer] Date in seconds since the epoch
9
+ def self.timestamp_to_integer(timestamp)
10
+ if timestamp && timestamp.length >= 4
11
+ year = timestamp[0..3].to_i
12
+ month = timestamp.length >= 6 ? timestamp[4..5].to_i : 1
13
+ day = timestamp.length >= 8 ? timestamp[6..7].to_i : 1
14
+ hour = timestamp.length >= 10 ? timestamp[8..9].to_i : 0
15
+ min = timestamp.length >= 12 ? timestamp[10..11].to_i : 0
16
+ sec = timestamp.length >= 14 ? timestamp[12..13].to_i : 0
17
+
18
+ Time.gm(year, month, day, hour, min, sec).to_i
19
+ else
20
+ nil
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -43,7 +43,9 @@
43
43
  <participant typeCode="CSM">
44
44
  <participantRole classCode="MANU">
45
45
  <playingEntity classCode="MMAT">
46
- <%== render :partial => 'code_with_reference', :locals => {:entry => entry, :i => i, :section => 'allergies'} %>
46
+ <%== render :partial => 'code_with_reference',
47
+ :locals => {:entry => entry, :i => i, :section => 'allergies',
48
+ :preferred_code_sets => ['RxNorm']} %>
47
49
  <name><%= entry.description %></name>
48
50
  </playingEntity>
49
51
  </participantRole>
@@ -14,7 +14,7 @@
14
14
  <templateId root="2.16.840.1.113883.10.20.1.25"/>
15
15
  <!-- Plan of Activity activity template -->
16
16
  <id root="<%= UUID.generate %>"/>
17
- <%== code_display(entry) %>
17
+ <%== code_display(entry, :preferred_code_sets => ['SNOMED-CT']) %>
18
18
  <statusCode code="new"/>
19
19
  <effectiveTime value="<%= Time.at(entry.time).utc.to_formatted_s(:number) %>"/>
20
20
  </observation>
@@ -1,8 +1,13 @@
1
- <% if entry.single_code_value?
2
- code = entry.codes.first[1].first
3
- code_system_oid = QME::Importer::CodeSystemHelper.oid_for_code_system(entry.codes.first[0])
1
+ <% preferred_code = entry.preferred_code(preferred_code_sets)
2
+ if preferred_code
3
+ code_system_oid = HealthDataStandards::Util::CodeSystemHelper.oid_for_code_system(preferred_code['code_set'])
4
4
  -%>
5
- <code code="<%= code %>" codeSystem="<%= code_system_oid %>">
5
+ <code code="<%= preferred_code['code'] %>" codeSystem="<%= code_system_oid %>" displayName="<%= entry.description %>">
6
+ <% else -%>
7
+ <code nullFlavor="UNK">
8
+ <% end -%>
6
9
  <originalText><reference value="#<%= section %>-desc-<%= i %>"/></originalText>
10
+ <% entry.translation_codes(preferred_code_sets).each do |translation| -%>
11
+ <translation code="<%= translation['code'] %>" codeSystem="<%= HealthDataStandards::Util::CodeSystemHelper.oid_for_code_system(translation['code_set']) %>" />
12
+ <% end -%>
7
13
  </code>
8
- <% end -%>
@@ -7,7 +7,7 @@
7
7
  <!--Problems section template-->
8
8
  <code code="11450-4" codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC" displayName="Problem list"/>
9
9
  <title>Problems</title>
10
- <%== render :partial => 'narrative_block', :locals => {:entries => entries, :section => 'conditions'} %>
10
+ <%== render :partial => 'narrative_block', :locals => {:entries => entries, :section => 'conditions', :status => true } %>
11
11
  <% entries.each_with_index do |entry, i| -%>
12
12
  <entry typeCode="DRIV">
13
13
  <act classCode="ACT" moodCode="EVN">
@@ -40,7 +40,7 @@
40
40
  <effectiveTime>
41
41
  <low value="<%= Time.at(entry.time).utc.to_formatted_s(:number) %>"/>
42
42
  </effectiveTime>
43
- <%== code_display(entry, 'value', 'xsi:type="CD"') %>
43
+ <%== code_display(entry, {'tag_name' => 'value', 'extra_content' => 'xsi:type="CD"', 'preferred_code_sets' => ['SNOMED-CT']}) %>
44
44
  <% if entry.status -%>
45
45
  <entryRelationship typeCode="REFR">
46
46
  <observation classCode="OBS" moodCode="EVN">
@@ -16,7 +16,8 @@
16
16
  <templateId root="1.3.6.1.4.1.19376.1.5.3.1.4.14" assigningAuthorityName="IHE PCC"/>
17
17
  <!-- Encounter activity template -->
18
18
  <id root="<%= UUID.generate %>"/>
19
- <%== render :partial => 'code_with_reference', :locals => {:entry => entry, :i => i, :section => 'encounters'} %>
19
+ <%== render :partial => 'code_with_reference', :locals => {:entry => entry, :i => i, :section => 'encounters',
20
+ :preferred_code_sets => ['CPT']} %>
20
21
  <text>
21
22
  <reference value="#encounters-desc-<%= i %>"/>
22
23
  </text>
@@ -29,7 +29,8 @@
29
29
  <templateId root="1.3.6.1.4.1.19376.1.5.3.1.4.7.2"/>
30
30
  <!-- Product template -->
31
31
  <manufacturedMaterial>
32
- <%== render :partial => 'code_with_reference', :locals => {:entry => entry, :i => i, :section => 'immunizations'} %>
32
+ <%== render :partial => 'code_with_reference', :locals => {:entry => entry, :i => i, :section => 'immunizations',
33
+ :preferred_code_sets => ['CVX']} %>
33
34
  <name><%= entry.description %></name>
34
35
  </manufacturedMaterial>
35
36
  </manufacturedProduct>
@@ -23,7 +23,7 @@
23
23
  <addr/>
24
24
  <telecom/>
25
25
  <playingDevice>
26
- <%== code_display(entry) %>
26
+ <%== code_display(entry, {:preferred_code_sets => ['SNOMED-CT']}) %>
27
27
  </playingDevice>
28
28
  </participantRole>
29
29
  </participant>
@@ -28,7 +28,8 @@
28
28
  <templateId root="2.16.840.1.113883.3.88.11.83.8.2"/>
29
29
  <templateId root="1.3.6.1.4.1.19376.1.5.3.1.4.7.2"/>
30
30
  <manufacturedMaterial>
31
- <%== render :partial => 'code_with_reference', :locals => {:entry => entry, :i => i, :section => 'medications'} %>
31
+ <%== render :partial => 'code_with_reference', :locals => {:entry => entry, :i => i, :section => 'medications',
32
+ :preferred_code_sets => ['RxNorm']} %>
32
33
  <name><%= entry.description %></name>
33
34
  </manufacturedMaterial>
34
35
  </manufacturedProduct>
@@ -5,6 +5,13 @@
5
5
  <th>Description</th>
6
6
  <th>Codes</th>
7
7
  <th>Time</th>
8
+ <% if status.present? %>
9
+ <th>Status</th>
10
+ <% end %>
11
+
12
+ <% if value.present? %>
13
+ <th>Value</th>
14
+ <% end %>
8
15
  </tr>
9
16
  </thead>
10
17
  <tbody>
@@ -13,6 +20,13 @@
13
20
  <td ID="<%= section %>-desc-<%= i %>"><%= entry.description %></td>
14
21
  <td ID="<%= section %>-code-<%= i %>"><%= entry.codes_to_s %></td>
15
22
  <td><%= entry.times_to_s %></td>
23
+ <% if status.present? %>
24
+ <td><%= entry.status %></td>
25
+ <% end %>
26
+
27
+ <% if value.present? %>
28
+ <td><%= entry.value["scalar"] %></td>
29
+ <% end %>
16
30
  </tr>
17
31
  <%- end -%>
18
32
  </tbody>