health-data-standards 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -2
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/lib/health-data-standards.rb +27 -2
- data/lib/health-data-standards/export/view_helper.rb +15 -16
- data/lib/health-data-standards/ext/string.rb +5 -0
- data/lib/health-data-standards/ext/symbol.rb +8 -0
- data/lib/health-data-standards/import/c32/allergy_importer.rb +42 -0
- data/lib/health-data-standards/import/c32/encounter_importer.rb +80 -0
- data/lib/health-data-standards/import/c32/immunization_importer.rb +61 -0
- data/lib/health-data-standards/import/c32/medication_importer.rb +138 -0
- data/lib/health-data-standards/import/c32/patient_importer.rb +139 -0
- data/lib/health-data-standards/import/c32/procedure_importer.rb +55 -0
- data/lib/health-data-standards/import/c32/result_importer.rb +58 -0
- data/lib/health-data-standards/import/c32/section_importer.rb +214 -0
- data/lib/health-data-standards/import/c32/vital_sign_importer.rb +12 -0
- data/lib/health-data-standards/models/allergy.rb +4 -0
- data/lib/health-data-standards/models/encounter.rb +7 -0
- data/lib/health-data-standards/models/entry.rb +131 -3
- data/lib/health-data-standards/models/fulfillment_history.rb +11 -0
- data/lib/health-data-standards/models/immunization.rb +5 -0
- data/lib/health-data-standards/models/lab_result.rb +4 -0
- data/lib/health-data-standards/models/medication.rb +25 -0
- data/lib/health-data-standards/models/order_information.rb +9 -0
- data/lib/health-data-standards/models/procedure.rb +4 -0
- data/lib/health-data-standards/models/record.rb +1 -0
- data/lib/health-data-standards/util/code_system_helper.rb +41 -0
- data/lib/health-data-standards/util/hl7_helper.rb +25 -0
- data/templates/_allergies.c32.erb +3 -1
- data/templates/_care_goals.c32.erb +1 -1
- data/templates/_code_with_reference.c32.erb +10 -5
- data/templates/_conditions.c32.erb +2 -2
- data/templates/_encounters.c32.erb +2 -1
- data/templates/_immunizations.c32.erb +2 -1
- data/templates/_medical_equipment.c32.erb +1 -1
- data/templates/_medications.c32.erb +2 -1
- data/templates/_narrative_block.c32.erb +14 -0
- data/templates/_procedures.c32.erb +2 -1
- data/templates/_results.c32.erb +2 -2
- data/templates/_social_history.c32.erb +1 -1
- data/templates/_vital_signs.c32.erb +2 -2
- metadata +37 -16
@@ -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:
|
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,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
|
@@ -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',
|
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
|
-
<%
|
2
|
-
|
3
|
-
code_system_oid =
|
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>
|
@@ -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>
|