health-data-standards 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -1
- data/Rakefile +14 -4
- data/lib/health-data-standards.rb +23 -0
- data/lib/health-data-standards/export/ccr.rb +55 -56
- data/lib/health-data-standards/export/hdata/metadata.rb +16 -0
- data/lib/health-data-standards/export/html.rb +21 -0
- data/lib/health-data-standards/export/template_helper.rb +3 -0
- data/lib/health-data-standards/export/view_helper.rb +29 -5
- data/lib/health-data-standards/import/c32/condition_importer.rb +31 -33
- data/lib/health-data-standards/import/c32/encounter_importer.rb +4 -6
- data/lib/health-data-standards/import/c32/medication_importer.rb +1 -5
- data/lib/health-data-standards/import/c32/organization_importer.rb +23 -0
- data/lib/health-data-standards/import/c32/patient_importer.rb +1 -4
- data/lib/health-data-standards/import/c32/provider_importer.rb +43 -30
- data/lib/health-data-standards/import/c32/section_importer.rb +20 -41
- data/lib/health-data-standards/import/ccr/patient_importer.rb +27 -10
- data/lib/health-data-standards/import/ccr/provider_importer.rb +29 -41
- data/lib/health-data-standards/import/ccr/section_importer.rb +38 -27
- data/lib/health-data-standards/import/green_c32/allergy_importer.rb +20 -0
- data/lib/health-data-standards/import/green_c32/condition_importer.rb +2 -3
- data/lib/health-data-standards/import/green_c32/encounter_importer.rb +42 -0
- data/lib/health-data-standards/import/green_c32/immunization_importer.rb +23 -0
- data/lib/health-data-standards/import/green_c32/medication_importer.rb +69 -0
- data/lib/health-data-standards/import/green_c32/procedure_importer.rb +35 -0
- data/lib/health-data-standards/import/green_c32/result_importer.rb +21 -8
- data/lib/health-data-standards/import/green_c32/section_importer.rb +55 -9
- data/lib/health-data-standards/import/green_c32/social_history_importer.rb +18 -0
- data/lib/health-data-standards/import/green_c32/vital_sign_importer.rb +21 -0
- data/lib/health-data-standards/import/hdata/metadata_importer.rb +82 -0
- data/lib/health-data-standards/import/provider_import_utils.rb +23 -0
- data/lib/health-data-standards/models/address.rb +11 -0
- data/lib/health-data-standards/models/allergy.rb +1 -0
- data/lib/health-data-standards/models/condition.rb +1 -1
- data/lib/health-data-standards/models/encounter.rb +11 -6
- data/lib/health-data-standards/models/entry.rb +16 -5
- data/lib/health-data-standards/models/fulfillment_history.rb +3 -5
- data/lib/health-data-standards/models/immunization.rb +7 -1
- data/lib/health-data-standards/models/medication.rb +4 -3
- data/lib/health-data-standards/models/metadata/author.rb +16 -0
- data/lib/health-data-standards/models/metadata/base.rb +20 -0
- data/lib/health-data-standards/models/metadata/change_info.rb +9 -0
- data/lib/health-data-standards/models/metadata/link_info.rb +9 -0
- data/lib/health-data-standards/models/metadata/pedigree.rb +15 -0
- data/lib/health-data-standards/models/organization.rb +8 -0
- data/lib/health-data-standards/models/procedure.rb +5 -2
- data/lib/health-data-standards/models/provider.rb +6 -1
- data/lib/health-data-standards/models/record.rb +13 -3
- data/lib/health-data-standards/models/social_history.rb +3 -0
- data/lib/health-data-standards/models/telecom.rb +9 -0
- data/lib/health-data-standards/models/vital_sign.rb +2 -0
- data/lib/health-data-standards/util/code_system_helper.rb +3 -1
- data/templates/_address.gc32.erb +9 -0
- data/templates/_allergies.c32.erb +2 -2
- data/templates/_allergy.gc32.erb +13 -0
- data/templates/_care_goals.c32.erb +1 -1
- data/templates/_condition.gc32.erb +6 -6
- data/templates/_conditions.c32.erb +2 -2
- data/templates/_encounter.gc32.erb +32 -0
- data/templates/_encounters.c32.erb +1 -1
- data/templates/_immunization.gc32.erb +9 -0
- data/templates/_immunizations.c32.erb +1 -1
- data/templates/_medical_equipment.c32.erb +1 -1
- data/templates/_medication.gc32.erb +60 -0
- data/templates/_medications.c32.erb +1 -1
- data/templates/_narrative_block.c32.erb +1 -1
- data/templates/_organization.gc32.erb +10 -0
- data/templates/_pedigree.hdata.erb +24 -0
- data/templates/_procedure.gc32.erb +8 -0
- data/templates/_procedures.c32.erb +1 -1
- data/templates/_provider.gc32.erb +19 -0
- data/templates/_results.c32.erb +1 -1
- data/templates/_social_history.c32.erb +1 -1
- data/templates/_social_history.gc32.erb +6 -0
- data/templates/_telecom.gc32.erb +1 -0
- data/templates/_vital_sign.gc32.erb +12 -0
- data/templates/_vital_signs.c32.erb +1 -1
- data/templates/metadata.hdata.erb +35 -0
- data/templates/show.html.erb +287 -0
- metadata +50 -15
@@ -49,13 +49,11 @@ module HealthDataStandards
|
|
49
49
|
|
50
50
|
def extract_facility(parent_element, encounter)
|
51
51
|
participant_element = parent_element.at_xpath("./cda:participant[@typeCode='LOC']/cda:participantRole[@classCode='SDLOC']")
|
52
|
-
encounter.facility = {}
|
53
52
|
if (participant_element)
|
54
|
-
|
55
|
-
addresses = participant_element.xpath("./cda:addr").try(:map) {|ae| import_address(ae)}
|
56
|
-
|
57
|
-
|
58
|
-
encounter.facility['telcoms'] = telecoms
|
53
|
+
org = Organization.new(name: participant_element.at_xpath("./cda:playingEntity/cda:name").try(:text))
|
54
|
+
org.addresses = participant_element.xpath("./cda:addr").try(:map) {|ae| import_address(ae)}
|
55
|
+
org.telecoms = participant_element.xpath("./cda:telecom").try(:map) {|te| import_telecom(te)}
|
56
|
+
encounter.facility = org
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
@@ -63,7 +63,7 @@ module HealthDataStandards
|
|
63
63
|
if @check_for_usable
|
64
64
|
medication_list << medication if medication.usable?
|
65
65
|
else
|
66
|
-
medication_list <<
|
66
|
+
medication_list << medication
|
67
67
|
end
|
68
68
|
end
|
69
69
|
medication_list
|
@@ -80,10 +80,6 @@ module HealthDataStandards
|
|
80
80
|
actor_element = fh_element.at_xpath('./cda:performer')
|
81
81
|
if actor_element
|
82
82
|
fulfillment_history.provider = import_actor(actor_element)
|
83
|
-
addr_element = actor_element.at_xpath("./cda:assignedEntity/cda:addr")
|
84
|
-
if addr_element
|
85
|
-
fulfillment_history.dispensing_pharmacy_location = import_address(addr_element)
|
86
|
-
end
|
87
83
|
end
|
88
84
|
hl7_timestamp = fh_element.at_xpath('./cda:effectiveTime').try(:[], 'value')
|
89
85
|
fulfillment_history.dispense_date = HL7Helper.timestamp_to_integer(hl7_timestamp) if hl7_timestamp
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module HealthDataStandards
|
2
|
+
module Import
|
3
|
+
module C32
|
4
|
+
class OrganizationImporter < SectionImporter
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
def extract_organization(org_element)
|
12
|
+
return unless org_element
|
13
|
+
org = Organization.new
|
14
|
+
org.name = org_element.at_xpath("./name").try(:text)
|
15
|
+
org.addresses = org_element.xpath("./cda:addr").map { |addr| import_address(addr) }
|
16
|
+
org.telecoms = org_element.xpath("./cda:telecom").map { |tele| import_telecom(tele) }
|
17
|
+
org
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -59,10 +59,7 @@ module HealthDataStandards
|
|
59
59
|
@section_importers[:results] = ResultImporter.new
|
60
60
|
@section_importers[:vital_signs] = VitalSignImporter.new
|
61
61
|
@section_importers[:medications] = MedicationImporter.new
|
62
|
-
@section_importers[:conditions] =
|
63
|
-
"./cda:value",
|
64
|
-
"./cda:entryRelationship/cda:observation[cda:templateId/@root='2.16.840.1.1 13883.10.20.1.50']/cda:value",
|
65
|
-
"./cda:text/cda:reference[@value]")
|
62
|
+
@section_importers[:conditions] = ConditionImporter.new
|
66
63
|
@section_importers[:social_history] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.19']")
|
67
64
|
@section_importers[:care_goals] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.25']")
|
68
65
|
@section_importers[:medical_equipment] = SectionImporter.new("//cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.128']/cda:entry/cda:supply",
|
@@ -1,47 +1,62 @@
|
|
1
1
|
require "date"
|
2
|
-
require "date/delta"
|
2
|
+
# require "date/delta"
|
3
3
|
|
4
4
|
module HealthDataStandards
|
5
5
|
module Import
|
6
6
|
module C32
|
7
|
-
class ProviderImporter
|
7
|
+
class ProviderImporter < SectionImporter
|
8
|
+
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
|
12
|
+
end
|
13
|
+
|
8
14
|
include Singleton
|
9
|
-
|
15
|
+
include ProviderImportUtils
|
10
16
|
# Extract Healthcare Providers from C32
|
11
17
|
#
|
12
18
|
# @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
|
13
19
|
# will have the "cda" namespace registered to "urn:hl7-org:v3"
|
14
20
|
# @return [Array] an array of providers found in the document
|
15
|
-
def extract_providers(doc
|
16
|
-
|
17
|
-
|
18
|
-
|
21
|
+
def extract_providers(doc)
|
22
|
+
performers = doc.xpath("//cda:documentationOf/cda:serviceEvent/cda:performer")
|
23
|
+
performers.map do |performer|
|
24
|
+
provider_perf = extract_provider_data(performer, true)
|
25
|
+
ProviderPerformance.new(start_date: provider_perf.delete(:start), end_date: provider_perf.delete(:end), provider: find_or_create_provider(provider_perf))
|
26
|
+
end
|
27
|
+
end
|
19
28
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
private
|
30
|
+
|
31
|
+
def extract_provider_data(performer, use_dates=true)
|
32
|
+
provider = {}
|
33
|
+
entity = performer.xpath("./cda:assignedEntity")
|
34
|
+
name = entity.xpath("./cda:assignedPerson/cda:name")
|
35
|
+
provider[:title] = extract_data(name, "./cda:prefix")
|
36
|
+
provider[:given_name] = extract_data(name, "./cda:given[1]")
|
37
|
+
provider[:family_name] = extract_data(name, "./cda:family")
|
38
|
+
provider[:organization] = OrganizationImporter.instance.extract_organization(entity.at_xpath("./cda:representedOrganization"))
|
39
|
+
provider[:specialty] = extract_data(entity, "./cda:code/@code")
|
40
|
+
time = performer.xpath(performer, "./cda:time")
|
41
|
+
|
42
|
+
if use_dates
|
31
43
|
provider[:start] = extract_date(time, "./cda:low/@value")
|
32
44
|
provider[:end] = extract_date(time, "./cda:high/@value")
|
33
|
-
# NIST sample C32s use different OID for NPI vs C83, support both
|
34
|
-
npi = extract_data(entity, "./cda:id[@root='2.16.840.1.113883.4.6' or @root='2.16.840.1.113883.3.72.5.2']/@extension")
|
35
|
-
if Provider.valid_npi?(npi)
|
36
|
-
provider[:npi] = npi
|
37
|
-
else
|
38
|
-
puts "Warning: Invalid NPI (#{npi})"
|
39
|
-
end
|
40
|
-
provider
|
41
45
|
end
|
46
|
+
|
47
|
+
# NIST sample C32s use different OID for NPI vs C83, support both
|
48
|
+
npi = extract_data(entity, "./cda:id[@root='2.16.840.1.113883.4.6' or @root='2.16.840.1.113883.3.72.5.2']/@extension")
|
49
|
+
provider[:addresses] = performer.xpath("./cda:assignedEntity/cda:addr").try(:map) {|ae| import_address(ae)}
|
50
|
+
provider[:telecoms] = performer.xpath("./cda:assignedEntity/cda:telecom").try(:map) {|te| import_telecom(te)}
|
51
|
+
|
52
|
+
provider[:npi] = npi if Provider.valid_npi?(npi)
|
53
|
+
provider
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_or_create_provider(provider_hash)
|
57
|
+
provider = Provider.first(conditions: {npi: provider_hash[:npi]}) if provider_hash[:npi]
|
58
|
+
provider ||= Provider.create(provider_hash)
|
42
59
|
end
|
43
|
-
|
44
|
-
private
|
45
60
|
|
46
61
|
def extract_date(subject,query)
|
47
62
|
date = extract_data(subject,query)
|
@@ -53,8 +68,6 @@ module HealthDataStandards
|
|
53
68
|
result = subject.xpath(query).text
|
54
69
|
if result == ""
|
55
70
|
nil
|
56
|
-
elsif block_given?
|
57
|
-
yield(result)
|
58
71
|
else
|
59
72
|
result
|
60
73
|
end
|
@@ -135,54 +135,33 @@ module HealthDataStandards
|
|
135
135
|
end
|
136
136
|
|
137
137
|
def import_actor(actor_element)
|
138
|
-
|
139
|
-
npi = actor_element.xpath("./cda:assignedEntity/cda:id[@root='2.16.840.1.113883.4.6' or @root='2.16.840.1.113883.3.72.5.2']/@extension")
|
140
|
-
actor_hash[:npi] = npi if npi
|
141
|
-
addresses = actor_element.xpath("./cda:assignedEntity/cda:addr").try(:map) {|ae| import_address(ae)}
|
142
|
-
telecoms = actor_element.xpath("./cda:assignedEntity/cda:telecom").try(:map) {|te| import_telecom(te)}
|
143
|
-
person_element = actor_element.at_xpath("./cda:assignedEntity/cda:assignedPerson")
|
144
|
-
if person_element
|
145
|
-
actor_hash['person'] = import_person(person_element)
|
146
|
-
actor_hash['person']['addresses'] = addresses
|
147
|
-
actor_hash['person']['telecoms'] = telecoms
|
148
|
-
end
|
149
|
-
organization_element = actor_element.at_xpath("./cda:assignedEntity/cda:assignedOrganization")
|
150
|
-
if organization_element
|
151
|
-
actor_hash['organization'] = import_organization(organization_element)
|
152
|
-
end
|
153
|
-
|
154
|
-
actor_hash
|
138
|
+
return ProviderImporter.instance.extract_provider(actor_element)
|
155
139
|
end
|
156
140
|
|
157
|
-
def import_person(person_element)
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
141
|
+
# def import_person(person_element)
|
142
|
+
# person_hash = {}
|
143
|
+
# name_element = person_element.at_xpath("./cda:name")
|
144
|
+
# person_hash['name'] = name_element.try(:text)
|
145
|
+
# person_hash['first'] = name_element.at_xpath("./cda:given").try(:text)
|
146
|
+
# person_hash['last'] = name_element.at_xpath("./cda:family").try(:text)
|
147
|
+
# person_hash
|
148
|
+
# end
|
165
149
|
|
166
150
|
def import_address(address_element)
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
151
|
+
address = Address.new
|
152
|
+
address.street = [address_element.at_xpath("./cda:streetAddressLine").try(:text)]
|
153
|
+
address.city = address_element.at_xpath("./cda:city").try(:text)
|
154
|
+
address.state = address_element.at_xpath("./cda:state").try(:text)
|
155
|
+
address.zip = address_element.at_xpath("./cda:postalCode").try(:text)
|
156
|
+
address.country = address_element.at_xpath("./cda:country").try(:text)
|
157
|
+
address
|
174
158
|
end
|
175
159
|
|
176
160
|
def import_telecom(telecom_element)
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
end
|
182
|
-
|
183
|
-
def import_organization
|
184
|
-
# TODO: Implement when the Patient API has an implementation of
|
185
|
-
# organization
|
161
|
+
tele = Telecom.new
|
162
|
+
tele.value = telecom_element['value']
|
163
|
+
tele.use = telecom_element['use']
|
164
|
+
tele
|
186
165
|
end
|
187
166
|
|
188
167
|
def extract_code(parent_element, code_xpath, code_system=nil)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "time"
|
2
|
+
|
1
3
|
module HealthDataStandards
|
2
4
|
module Import
|
3
5
|
module CCR
|
@@ -61,7 +63,7 @@ module HealthDataStandards
|
|
61
63
|
@section_importers[:conditions] = SimpleImporter.new("//ccr:Problems/ccr:Problem",:conditions)
|
62
64
|
@section_importers[:social_history] = SimpleImporter.new("//ccr:SocialHistory/ccr:SocialHistoryElement", :social_history)
|
63
65
|
@section_importers[:care_goals] = SimpleImporter.new("//ccr:Goals/ccr:Goal",:care_goals)
|
64
|
-
@section_importers[:medical_equipment] = ProductImporter.new("//ccr:
|
66
|
+
@section_importers[:medical_equipment] = ProductImporter.new("//ccr:Equipment/ccr:EquipmentElement",:medical_equipment)
|
65
67
|
@section_importers[:allergies] = SimpleImporter.new("//ccr:Alerts/ccr:Alert",:allergies)
|
66
68
|
@section_importers[:immunizations] = ProductImporter.new("//ccr:Immunizations/ccr:Immunization",:immunizations)
|
67
69
|
end
|
@@ -79,10 +81,10 @@ module HealthDataStandards
|
|
79
81
|
# @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
|
80
82
|
# will have the "ccr" namespace registered to ""urn:astm-org:CCR""
|
81
83
|
# @return [Hash] a representation of the patient that can be inserted into MongoDB
|
82
|
-
def parse_ccr(doc)
|
84
|
+
def parse_ccr(doc, patient_id_xpath="//ccr:ContinuityOfCareRecord/ccr:Patient/ccr:ActorID")
|
83
85
|
ccr_patient = {}
|
84
86
|
entries = create_hash(doc)
|
85
|
-
get_demographics(ccr_patient, doc)
|
87
|
+
get_demographics(ccr_patient, doc, patient_id_xpath)
|
86
88
|
process_events(ccr_patient, entries)
|
87
89
|
Record.new(ccr_patient)
|
88
90
|
end
|
@@ -181,21 +183,36 @@ module HealthDataStandards
|
|
181
183
|
#
|
182
184
|
# @param [Hash] patient A hash that is used to represent the patient
|
183
185
|
# @param [Nokogiri::XML::Node] doc The CCR document parsed by Nokogiri
|
184
|
-
def get_demographics(patient, doc)
|
185
|
-
|
186
|
-
patientActor = doc.at_xpath("//ccr:ContinuityOfCareRecord/ccr:Actors/ccr:Actor[ccr:ActorObjectID = \"#{
|
186
|
+
def get_demographics(patient, doc, patient_id_xpath)
|
187
|
+
patientActorID = doc.at_xpath("//ccr:ContinuityOfCareRecord/ccr:Patient/ccr:ActorID").content
|
188
|
+
patientActor = doc.at_xpath("//ccr:ContinuityOfCareRecord/ccr:Actors/ccr:Actor[ccr:ActorObjectID = \"#{patientActorID}\"]")
|
189
|
+
patientID = patientActor.at_xpath(patient_id_xpath).try(:content)
|
190
|
+
patientID ||= patientActorID
|
191
|
+
|
187
192
|
patient['first'] = patientActor.at_xpath('./ccr:Person/ccr:Name/ccr:CurrentName/ccr:Given').content
|
188
193
|
patient['last'] = patientActor.at_xpath('./ccr:Person/ccr:Name/ccr:CurrentName/ccr:Family').content
|
189
194
|
birthdate = patientActor.at_xpath('./ccr:Person//ccr:DateOfBirth/ccr:ExactDateTime | ./ccr:Person//ccr:DateOfBirth/ccr:ApproximateDateTime')
|
190
|
-
patient['birthdate'] = Time.
|
195
|
+
patient['birthdate'] = Time.parse(birthdate.content).to_i if birthdate
|
191
196
|
|
192
197
|
gender_string = patientActor.at_xpath('./ccr:Person/ccr:Gender/ccr:Text').content.downcase
|
193
198
|
patient['gender'] = Gender[gender_string.downcase]
|
194
|
-
|
195
199
|
#race_node = doc.at_xpath('/ccr:placeholder') #how do you find this?
|
196
|
-
|
200
|
+
race = doc.at_xpath('//ccr:SocialHistory/ccr:SocialHistoryElement[./ccr:Type/ccr:Text = "Race"]/ccr:Description/ccr:Code[./ccr:CodingSystem = "CDC-RE"]/ccr:Value')
|
201
|
+
ethnicity = doc.at_xpath('//ccr:SocialHistory/ccr:SocialHistoryElement[./ccr:Type/ccr:Text = "Ethnicity"]/ccr:Description/ccr:Code[./ccr:CodingSystem = "CDC-RE"]/ccr:Value')
|
202
|
+
|
203
|
+
if ethnicity
|
204
|
+
patient[:ethnicity] = {"code" => ethnicity.text, "codeSystem" => 'CDC-RE'}
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
if race
|
209
|
+
patient[:race] = {"code" => race.text, "codeSystem" => 'CDC-RE'}
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
|
197
214
|
#ethnicity_node = doc.at_xpath()
|
198
|
-
|
215
|
+
|
199
216
|
|
200
217
|
# languages = doc.at_xpath()
|
201
218
|
patient['languages'] = nil
|
@@ -1,60 +1,48 @@
|
|
1
1
|
require "date"
|
2
|
-
require "date/delta"
|
2
|
+
# require "date/delta"
|
3
3
|
|
4
4
|
module HealthDataStandards
|
5
5
|
module Import
|
6
6
|
module CCR
|
7
7
|
class ProviderImporter
|
8
8
|
include Singleton
|
9
|
+
include ProviderImportUtils
|
9
10
|
|
10
11
|
# Extract Healthcare Providers from CCR
|
11
12
|
#
|
12
13
|
# @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
|
13
14
|
# will have the "ccr" namespace registered to "urn:astm-org:CCR"
|
14
15
|
# @return [Array] an array of providers found in the document
|
15
|
-
|
16
|
+
|
17
|
+
def create_provider(actor)
|
18
|
+
# Differentiate care providers by content of this field
|
19
|
+
provider = {}
|
20
|
+
if actor.at_xpath('./ccr:Person/ccr:Name/ccr:CurrentName/ccr:Given')
|
21
|
+
provider[:given_name] = extract_data(actor, './ccr:Person/ccr:Name/ccr:CurrentName/ccr:Given')
|
22
|
+
provider[:family_name] = extract_data(actor, './ccr:Person/ccr:Name/ccr:CurrentName/ccr:Family')
|
23
|
+
provider[:specialty] = extract_data(actor, './ccr:Specialty/ccr:Text')
|
24
|
+
end
|
25
|
+
|
26
|
+
provider[:specialty] = extract_data(actor, './ccr:Specialty/ccr:Text')
|
16
27
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
28
|
+
|
29
|
+
npi_ids = actor.at_xpath("./ccr:IDs[ccr:Type/ccr:Text = \"NPI\"]")
|
30
|
+
if npi_ids
|
31
|
+
npi_id = npi_ids.at_xpath("./ccr:ID")
|
32
|
+
npi = npi_id.content
|
33
|
+
provider[:npi] = npi if Provider.valid_npi?(npi)
|
22
34
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
actor.at_xpath("./ccr:Source/ccr:Actor/ccr:ActorRole/ccr:Text").content.downcase =~ /care provider/
|
31
|
-
provider = {}
|
32
|
-
if actor.at_xpath('./ccr:Person/ccr:Name/ccr:CurrentName/ccr:Given')
|
33
|
-
provider[:given_name] = actor.at_xpath('./ccr:Person/ccr:Name/ccr:CurrentName/ccr:Given').content
|
34
|
-
provider[:family_name] = actor.at_xpath('./ccr:Person/ccr:Name/ccr:CurrentName/ccr:Family').content
|
35
|
-
end
|
36
|
-
if actor.at_xpath('./ccr:Specialty/ccr:Text')
|
37
|
-
provider[:specialty] = actor.at_xpath('./ccr:Specialty/ccr:Text').content
|
38
|
-
end
|
39
|
-
if actor.at_xpath("ccr:Telephone/ccr:Value")
|
40
|
-
provider[:phone] = actor.at_xpath("ccr:Telephone/ccr:Value").content
|
41
|
-
end
|
42
|
-
# Not clear precisely how NPI would be specified
|
43
|
-
npi_ids = actor.at_xpath("./ccr:IDs[ccr:Type/ccr:Text = \"NPI\"]")
|
44
|
-
if npi_ids
|
45
|
-
npi_id = npi_ids.at_xpath("./ccr:ID")
|
46
|
-
npi = npi_id.content
|
47
|
-
if Provider.valid_npi?(npi)
|
48
|
-
provider[:npi] = npi
|
49
|
-
else
|
50
|
-
puts "Warning: Invalid NPI (#{npi})"
|
51
|
-
end #valid NPI
|
52
|
-
end #has NPI
|
35
|
+
|
36
|
+
# binding.pry
|
37
|
+
find_or_create_provider(provider)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def extract_providers(doc)
|
53
42
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end.compact
|
43
|
+
# Providers are identified as the 'Source' for entries in the CCR. Sources can also include the patient, relatives, insurance companies, etc
|
44
|
+
provider_elements = doc.xpath("//ccr:ContinuityOfCareRecord/ccr:Actors/ccr:Actor[ccr:IDs/ccr:Type/ccr:Text=\"NPI\"]")
|
45
|
+
provider_elements.map { |pv| ProviderPerformance.new(provider: create_provider(pv)) }
|
58
46
|
end
|
59
47
|
end
|
60
48
|
end
|
@@ -4,6 +4,29 @@ module HealthDataStandards
|
|
4
4
|
# Class that can be used to create an importer for a section of a ASTM CCR document. It usually
|
5
5
|
# operates by selecting all CCR entries in a section and then creates entries for them.
|
6
6
|
class SectionImporter
|
7
|
+
|
8
|
+
CODE_SYSTEM_MAP = {
|
9
|
+
"lnc" => "LOINC",
|
10
|
+
"loinc" => "LOINC",
|
11
|
+
"cpt" => "CPT",
|
12
|
+
"cpt-4" => "CPT",
|
13
|
+
"sct" => "SNOMED-CT",
|
14
|
+
"snomedct" => "SNOMED-CT",
|
15
|
+
"snomed-ct" => "SNOMED-CT",
|
16
|
+
"rxnorm" => "RxNorm",
|
17
|
+
"i9cdx" => "ICD-9-CM",
|
18
|
+
"icd-9-cm" => "ICD-9-CM",
|
19
|
+
"icd9-cm" => "ICD-9-CM",
|
20
|
+
"icd9" => "ICD-9-CM",
|
21
|
+
"icd10-cm" => "ICD-9-CM",
|
22
|
+
"icd10" => "ICD-9-CM",
|
23
|
+
"cvx" => "CVX",
|
24
|
+
"hcpcs" => "HCPCS",
|
25
|
+
"cdc" => "CDC-RE",
|
26
|
+
"CDC" => "CDC-RE",
|
27
|
+
"cdc-re" => "CDC-RE"
|
28
|
+
|
29
|
+
}
|
7
30
|
attr_accessor :check_for_usable
|
8
31
|
# Creates a new SectionImporter
|
9
32
|
# @param [String] entry_xpath An XPath expression that can be used to find the desired entries
|
@@ -19,37 +42,23 @@ module HealthDataStandards
|
|
19
42
|
# in the tree, and the side effect is to edit the CodingSystem subnode.
|
20
43
|
# @param [String] code - Input is a single "Code" node
|
21
44
|
def normalize_coding_system(code)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
"cpt-4" => "CPT",
|
27
|
-
"snomedct" => "SNOMED-CT",
|
28
|
-
"snomed-ct" => "SNOMED-CT",
|
29
|
-
"rxnorm" => "RxNorm",
|
30
|
-
"icd9-cm" => "ICD-9-CM",
|
31
|
-
"icd9" => "ICD-9-CM",
|
32
|
-
"icd10-cm" => "ICD-9-CM",
|
33
|
-
"icd10" => "ICD-9-CM",
|
34
|
-
"cvx" => "CVX",
|
35
|
-
"hcpcs" => "HCPCS"
|
36
|
-
|
37
|
-
}
|
38
|
-
codingsystem = lookup[code.xpath('./ccr:CodingSystem')[0].content.downcase]
|
39
|
-
if(codingsystem)
|
40
|
-
code.xpath('./ccr:CodingSystem')[0].content = codingsystem
|
45
|
+
coding_system = code.xpath('./ccr:CodingSystem')[0].content.downcase
|
46
|
+
coding_system_value = CODE_SYSTEM_MAP[coding_system]
|
47
|
+
if(coding_system_value)
|
48
|
+
code.xpath('./ccr:CodingSystem')[0].content = coding_system_value
|
41
49
|
end
|
42
50
|
end
|
43
51
|
|
44
52
|
def extract_status(parent_element, entry)
|
45
53
|
status_element = parent_element.at_xpath('./ccr:Status')
|
46
54
|
if status_element
|
47
|
-
|
48
|
-
|
55
|
+
status_text = parent_element.at_xpath('./ccr:Status/ccr:Text')
|
56
|
+
return unless status_text
|
57
|
+
status = status_text.content.downcase
|
49
58
|
if %w(active inactive resolved).include?(status)
|
50
59
|
entry.status = status.to_sym
|
51
60
|
end
|
52
|
-
end
|
61
|
+
end
|
53
62
|
end
|
54
63
|
|
55
64
|
|
@@ -72,7 +81,7 @@ module HealthDataStandards
|
|
72
81
|
# Time is supposed to be in iso8601, but seems like we need to handle simple YYYY-MM-DD as well
|
73
82
|
def extract_time(datetime)
|
74
83
|
return unless datetime
|
75
|
-
Time.parse(datetime).to_i
|
84
|
+
Time.parse(datetime).to_i rescue nil
|
76
85
|
end
|
77
86
|
|
78
87
|
def extract_dates(parent_element, entry)
|
@@ -95,10 +104,12 @@ module HealthDataStandards
|
|
95
104
|
end
|
96
105
|
|
97
106
|
def extract_value(parent_element, entry)
|
98
|
-
|
99
|
-
if
|
100
|
-
|
101
|
-
|
107
|
+
result_element = parent_element.at_xpath('./ccr:TestResult')
|
108
|
+
if result_element
|
109
|
+
value_element = result_element.at_xpath('./ccr:Value')
|
110
|
+
value = value_element ? value_element.content : nil
|
111
|
+
unit_element = result_element.at_xpath('./ccr:Units/ccr:Unit')
|
112
|
+
unit = unit_element ? unit_element.content : nil
|
102
113
|
if value
|
103
114
|
entry.set_value(value, unit)
|
104
115
|
end
|