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.
Files changed (79) hide show
  1. data/Gemfile +9 -1
  2. data/Rakefile +14 -4
  3. data/lib/health-data-standards.rb +23 -0
  4. data/lib/health-data-standards/export/ccr.rb +55 -56
  5. data/lib/health-data-standards/export/hdata/metadata.rb +16 -0
  6. data/lib/health-data-standards/export/html.rb +21 -0
  7. data/lib/health-data-standards/export/template_helper.rb +3 -0
  8. data/lib/health-data-standards/export/view_helper.rb +29 -5
  9. data/lib/health-data-standards/import/c32/condition_importer.rb +31 -33
  10. data/lib/health-data-standards/import/c32/encounter_importer.rb +4 -6
  11. data/lib/health-data-standards/import/c32/medication_importer.rb +1 -5
  12. data/lib/health-data-standards/import/c32/organization_importer.rb +23 -0
  13. data/lib/health-data-standards/import/c32/patient_importer.rb +1 -4
  14. data/lib/health-data-standards/import/c32/provider_importer.rb +43 -30
  15. data/lib/health-data-standards/import/c32/section_importer.rb +20 -41
  16. data/lib/health-data-standards/import/ccr/patient_importer.rb +27 -10
  17. data/lib/health-data-standards/import/ccr/provider_importer.rb +29 -41
  18. data/lib/health-data-standards/import/ccr/section_importer.rb +38 -27
  19. data/lib/health-data-standards/import/green_c32/allergy_importer.rb +20 -0
  20. data/lib/health-data-standards/import/green_c32/condition_importer.rb +2 -3
  21. data/lib/health-data-standards/import/green_c32/encounter_importer.rb +42 -0
  22. data/lib/health-data-standards/import/green_c32/immunization_importer.rb +23 -0
  23. data/lib/health-data-standards/import/green_c32/medication_importer.rb +69 -0
  24. data/lib/health-data-standards/import/green_c32/procedure_importer.rb +35 -0
  25. data/lib/health-data-standards/import/green_c32/result_importer.rb +21 -8
  26. data/lib/health-data-standards/import/green_c32/section_importer.rb +55 -9
  27. data/lib/health-data-standards/import/green_c32/social_history_importer.rb +18 -0
  28. data/lib/health-data-standards/import/green_c32/vital_sign_importer.rb +21 -0
  29. data/lib/health-data-standards/import/hdata/metadata_importer.rb +82 -0
  30. data/lib/health-data-standards/import/provider_import_utils.rb +23 -0
  31. data/lib/health-data-standards/models/address.rb +11 -0
  32. data/lib/health-data-standards/models/allergy.rb +1 -0
  33. data/lib/health-data-standards/models/condition.rb +1 -1
  34. data/lib/health-data-standards/models/encounter.rb +11 -6
  35. data/lib/health-data-standards/models/entry.rb +16 -5
  36. data/lib/health-data-standards/models/fulfillment_history.rb +3 -5
  37. data/lib/health-data-standards/models/immunization.rb +7 -1
  38. data/lib/health-data-standards/models/medication.rb +4 -3
  39. data/lib/health-data-standards/models/metadata/author.rb +16 -0
  40. data/lib/health-data-standards/models/metadata/base.rb +20 -0
  41. data/lib/health-data-standards/models/metadata/change_info.rb +9 -0
  42. data/lib/health-data-standards/models/metadata/link_info.rb +9 -0
  43. data/lib/health-data-standards/models/metadata/pedigree.rb +15 -0
  44. data/lib/health-data-standards/models/organization.rb +8 -0
  45. data/lib/health-data-standards/models/procedure.rb +5 -2
  46. data/lib/health-data-standards/models/provider.rb +6 -1
  47. data/lib/health-data-standards/models/record.rb +13 -3
  48. data/lib/health-data-standards/models/social_history.rb +3 -0
  49. data/lib/health-data-standards/models/telecom.rb +9 -0
  50. data/lib/health-data-standards/models/vital_sign.rb +2 -0
  51. data/lib/health-data-standards/util/code_system_helper.rb +3 -1
  52. data/templates/_address.gc32.erb +9 -0
  53. data/templates/_allergies.c32.erb +2 -2
  54. data/templates/_allergy.gc32.erb +13 -0
  55. data/templates/_care_goals.c32.erb +1 -1
  56. data/templates/_condition.gc32.erb +6 -6
  57. data/templates/_conditions.c32.erb +2 -2
  58. data/templates/_encounter.gc32.erb +32 -0
  59. data/templates/_encounters.c32.erb +1 -1
  60. data/templates/_immunization.gc32.erb +9 -0
  61. data/templates/_immunizations.c32.erb +1 -1
  62. data/templates/_medical_equipment.c32.erb +1 -1
  63. data/templates/_medication.gc32.erb +60 -0
  64. data/templates/_medications.c32.erb +1 -1
  65. data/templates/_narrative_block.c32.erb +1 -1
  66. data/templates/_organization.gc32.erb +10 -0
  67. data/templates/_pedigree.hdata.erb +24 -0
  68. data/templates/_procedure.gc32.erb +8 -0
  69. data/templates/_procedures.c32.erb +1 -1
  70. data/templates/_provider.gc32.erb +19 -0
  71. data/templates/_results.c32.erb +1 -1
  72. data/templates/_social_history.c32.erb +1 -1
  73. data/templates/_social_history.gc32.erb +6 -0
  74. data/templates/_telecom.gc32.erb +1 -0
  75. data/templates/_vital_sign.gc32.erb +12 -0
  76. data/templates/_vital_signs.c32.erb +1 -1
  77. data/templates/metadata.hdata.erb +35 -0
  78. data/templates/show.html.erb +287 -0
  79. 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
- encounter.facility['organizationName'] = participant_element.at_xpath("./cda:playingEntity/cda:name").try(:text)
55
- addresses = participant_element.xpath("./cda:addr").try(:map) {|ae| import_address(ae)}
56
- encounter.facility['addresses'] = addresses
57
- telecoms = participant_element.xpath("./cda:telecom").try(:map) {|te| import_telecom(te)}
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 << 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] = SectionImporter.new("//cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.103']/cda:entry/cda:act/cda:entryRelationship/cda:observation",
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, use_encounters=false)
16
- xpath_base = use_encounters ? "//cda:encounter/cda:performer" : "//cda:documentationOf/cda:serviceEvent/cda:performer"
17
-
18
- performers = doc.xpath(xpath_base)
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
- providers = performers.map do |performer|
21
- provider = {}
22
- entity = performer.xpath(performer, "./cda:assignedEntity")
23
- name = entity.xpath("./cda:assignedPerson/cda:name")
24
- provider[:title] = extract_data(name, "./cda:prefix")
25
- provider[:given_name] = extract_data(name, "./cda:given[1]")
26
- provider[:family_name] = extract_data(name, "./cda:family")
27
- provider[:phone] = extract_data(entity, "./cda:telecom/@value") { |text| text.gsub("tel:", "") }
28
- provider[:organization] = extract_data(entity, "./cda:representedOrganization/cda:name")
29
- provider[:specialty] = extract_data(entity, "./cda:code/@code")
30
- time = performer.xpath(performer, "./cda:time")
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
- actor_hash = {}
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
- person_hash = {}
159
- name_element = person_element.at_xpath("./cda:name")
160
- person_hash['name'] = name_element.try(:text)
161
- person_hash['first'] = name_element.at_xpath("./cda:given").try(:text)
162
- person_hash['last'] = name_element.at_xpath("./cda:family").try(:text)
163
- person_hash
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
- address_hash = {}
168
- address_hash['streetAddress'] = [address_element.at_xpath("./cda:streetAddressLine").try(:text)]
169
- address_hash['city'] = address_element.at_xpath("./cda:city").try(:text)
170
- address_hash['stateOrProvince'] = address_element.at_xpath("./cda:state").try(:text)
171
- address_hash['zip'] = address_element.at_xpath("./cda:postalCode").try(:text)
172
- address_hash['country'] = address_element.at_xpath("./cda:country").try(:text)
173
- address_hash
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
- telecom_hash = {}
178
- telecom_hash['value'] = telecom_element['value']
179
- telecom_hash['use'] = telecom_element['use']
180
- telecom_hash
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:Equpment/ccr:EquipmentElement",:medical_equipment)
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
- patientID = doc.at_xpath('//ccr:ContinuityOfCareRecord/ccr:Patient/ccr:ActorID').content
186
- patientActor = doc.at_xpath("//ccr:ContinuityOfCareRecord/ccr:Actors/ccr:Actor[ccr:ActorObjectID = \"#{patientID}\"]")
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.iso8601(birthdate).to_i
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
- patient['race'] = nil
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
- patient['ethnicity'] = nil
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
- def extract_providers(doc)
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
- # Providers are identified as the 'Source' for entries in the CCR. Sources can also include the patient, relatives, insurance companies, etc
18
- actorIDs = doc.xpath("//ccr:Source/ccr:Actor/ccr:ActorID")
19
- uniqueActorIDs = {}
20
- actorIDs.each do |actorID|
21
- uniqueActorIDs[actorID.content] = actorID.content
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
- actorIDs = uniqueActorIDs.keys
24
- providers = actorIDs.map do |actorID|
25
- provider = nil
26
- actor = doc.at_xpath("//ccr:ContinuityOfCareRecord/ccr:Actors/ccr:Actor[ccr:ActorObjectID = \"#{actorID}\"]")
27
- if(actor)
28
- # Differentiate care providers by content of this field
29
- if actor.at_xpath("./ccr:Source/ccr:Actor/ccr:ActorRole/ccr:Text") &&
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
- end #is a provider
55
- end #is an actor
56
- provider
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
- lookup = {
23
- "lnc" => "LOINC",
24
- "loinc" => "LOINC",
25
- "cpt" => "CPT",
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
- status = parent_element.at_xpath('./ccr:Status/ccr:Text').content.downcase
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
- value_element = parent_element.at_xpath('./ccr:TestResult')
99
- if value_element
100
- value = value_element.at_xpath('./ccr:Value').content
101
- unit = value_element.at_xpath('./ccr:Units/ccr:Unit').content
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