health-data-standards 0.7.1 → 0.8.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.
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