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.
- 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
|