health-data-standards 1.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -5
- data/lib/health-data-standards.rb +8 -0
- data/lib/health-data-standards/export/ccda.rb +15 -0
- data/lib/health-data-standards/export/ccr.rb +24 -20
- data/lib/health-data-standards/export/html.rb +4 -8
- data/lib/health-data-standards/export/template_helper.rb +7 -1
- data/lib/health-data-standards/export/view_helper.rb +24 -4
- data/lib/health-data-standards/import/c32/allergy_importer.rb +2 -1
- data/lib/health-data-standards/import/c32/care_goal_importer.rb +2 -1
- data/lib/health-data-standards/import/c32/condition_importer.rb +23 -2
- data/lib/health-data-standards/import/c32/encounter_importer.rb +6 -5
- data/lib/health-data-standards/import/c32/immunization_importer.rb +1 -15
- data/lib/health-data-standards/import/c32/medication_importer.rb +1 -0
- data/lib/health-data-standards/import/c32/patient_importer.rb +13 -0
- data/lib/health-data-standards/import/c32/procedure_importer.rb +1 -0
- data/lib/health-data-standards/import/c32/provider_importer.rb +1 -1
- data/lib/health-data-standards/import/c32/result_importer.rb +1 -0
- data/lib/health-data-standards/import/c32/section_importer.rb +17 -22
- data/lib/health-data-standards/import/ccda/medical_equipment_importer.rb +1 -2
- data/lib/health-data-standards/import/ccda/result_importer.rb +1 -0
- data/lib/health-data-standards/import/ccr/provider_importer.rb +0 -1
- data/lib/health-data-standards/import/green_c32/immunization_importer.rb +1 -1
- data/lib/health-data-standards/import/green_c32/section_importer.rb +7 -5
- data/lib/health-data-standards/import/provider_import_utils.rb +1 -1
- data/lib/health-data-standards/models/coded_result_value.rb +3 -0
- data/lib/health-data-standards/models/condition.rb +7 -5
- data/lib/health-data-standards/models/encounter.rb +5 -2
- data/lib/health-data-standards/models/entry.rb +48 -68
- data/lib/health-data-standards/models/facility.rb +9 -0
- data/lib/health-data-standards/models/functional_status.rb +25 -0
- data/lib/health-data-standards/models/immunization.rb +4 -6
- data/lib/health-data-standards/models/medication.rb +6 -0
- data/lib/health-data-standards/models/physical_quantity_result_value.rb +4 -0
- data/lib/health-data-standards/models/record.rb +11 -2
- data/lib/health-data-standards/models/result_value.rb +4 -0
- data/lib/health-data-standards/models/thing_with_codes.rb +52 -0
- data/lib/health-data-standards/util/code_system_helper.rb +5 -2
- data/lib/health-data-standards/util/qrda_template_helper.rb +20 -0
- data/templates/_allergies_no_current.c32.erb +26 -4
- data/templates/_conditions_no_current.c32.erb +1 -2
- data/templates/_medications_no_current.c32.erb +1 -1
- data/templates/_narrative_block.c32.erb +14 -2
- data/templates/_result.gc32.erb +3 -1
- data/templates/_results.c32.erb +9 -8
- data/templates/_social_history.gc32.erb +1 -1
- data/templates/_vital_sign.gc32.erb +1 -1
- data/templates/_vital_signs.c32.erb +12 -11
- data/templates/show.c32.erb +1 -1
- metadata +55 -21
- 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.
|
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
|
@@ -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.
|
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
|
-
{
|
92
|
+
{"scalar" => node_value, "unit" => node_units}
|
93
93
|
end
|
94
94
|
|
95
95
|
def extract_value(element, entry)
|
96
|
-
|
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.
|
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
|
|
@@ -1,9 +1,11 @@
|
|
1
1
|
class Condition < Entry
|
2
|
-
field :type,
|
3
|
-
field :causeOfDeath,
|
4
|
-
field :priority,
|
5
|
-
field :name,
|
6
|
-
field :ordinality,
|
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
|
-
|
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 :
|
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
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
123
|
-
self.
|
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
|
190
|
-
entry_hash['value'] =
|
169
|
+
unless values.empty?
|
170
|
+
entry_hash['value'] = values
|
191
171
|
end
|
192
172
|
|
193
173
|
if is_date_range?
|
@@ -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 :
|
11
|
-
alias :refusal_ind= :
|
12
|
-
alias :refusal_reason :
|
13
|
-
alias :refusal_reason= :
|
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
|
@@ -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,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
|