health-data-standards 0.7.0 → 0.7.1
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/lib/health-data-standards.rb +20 -1
- data/lib/health-data-standards/export/ccr.rb +30 -5
- data/lib/health-data-standards/export/green_c32/entry.rb +15 -0
- data/lib/health-data-standards/export/green_c32/export_generator.rb +23 -0
- data/lib/health-data-standards/export/template_helper.rb +0 -1
- data/lib/health-data-standards/import/c32/condition_importer.rb +66 -0
- data/lib/health-data-standards/import/c32/provider_importer.rb +66 -0
- data/lib/health-data-standards/import/c32/section_importer.rb +2 -0
- data/lib/health-data-standards/import/ccr/patient_importer.rb +208 -0
- data/lib/health-data-standards/import/ccr/product_importer.rb +60 -0
- data/lib/health-data-standards/import/ccr/provider_importer.rb +62 -0
- data/lib/health-data-standards/import/ccr/result_importer.rb +49 -0
- data/lib/health-data-standards/import/ccr/section_importer.rb +124 -0
- data/lib/health-data-standards/import/ccr/simple_importer.rb +30 -0
- data/lib/health-data-standards/import/green_c32/condition_importer.rb +45 -0
- data/lib/health-data-standards/import/green_c32/patient_importer.rb +14 -0
- data/lib/health-data-standards/import/green_c32/result_importer.rb +30 -0
- data/lib/health-data-standards/import/green_c32/section_importer.rb +93 -0
- data/lib/health-data-standards/models/comment.rb +2 -0
- data/lib/health-data-standards/models/condition.rb +10 -0
- data/lib/health-data-standards/models/entry.rb +5 -0
- data/lib/health-data-standards/models/fulfillment_history.rb +2 -0
- data/lib/health-data-standards/models/lab_result.rb +1 -0
- data/lib/health-data-standards/models/provider.rb +51 -0
- data/lib/health-data-standards/models/provider_performance.rb +10 -0
- data/lib/health-data-standards/models/record.rb +21 -4
- data/lib/health-data-standards/models/treating_provider.rb +3 -0
- data/lib/health-data-standards/util/hl7_helper.rb +1 -0
- data/templates/_condition.gc32.erb +11 -0
- data/templates/_result.gc32.erb +20 -0
- data/templates/show.c32.erb +16 -5
- metadata +33 -12
@@ -0,0 +1,60 @@
|
|
1
|
+
module HealthDataStandards
|
2
|
+
module Import
|
3
|
+
module CCR
|
4
|
+
class ProductImporter < SectionImporter
|
5
|
+
|
6
|
+
# Traverses that ASTM CCR document passed in using XPath and creates an Array of Entry
|
7
|
+
# objects based on what it finds
|
8
|
+
# @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
|
9
|
+
# will have the "ccr" namespace registered to "urn:astm-org:CCR"
|
10
|
+
# measure definition
|
11
|
+
# @return [Array] will be a list of Entry objects
|
12
|
+
def create_entries(doc)
|
13
|
+
entry_list = []
|
14
|
+
entry_elements = doc.xpath(@entry_xpath)
|
15
|
+
entry_elements.each do |entry_element|
|
16
|
+
entry = Entry.new
|
17
|
+
product = entry_element.at_xpath("./ccr:Product")
|
18
|
+
process_product(product,entry)
|
19
|
+
extract_dates(entry_element, entry)
|
20
|
+
extract_status(entry_element, entry)
|
21
|
+
if @check_for_usable
|
22
|
+
entry_list << entry if entry.usable?
|
23
|
+
else
|
24
|
+
entry_list << entry
|
25
|
+
end
|
26
|
+
end
|
27
|
+
entry_list
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add the codes from a <Product> block subsection to an Entry
|
31
|
+
def process_product_codes(node, entry)
|
32
|
+
codes = node.xpath("./ccr:Code")
|
33
|
+
if codes.size > 0
|
34
|
+
found_code = true
|
35
|
+
codes.each do |code|
|
36
|
+
normalize_coding_system(code)
|
37
|
+
codetext = code.at_xpath("./ccr:CodingSystem").content
|
38
|
+
entry.add_code(code.at_xpath("./ccr:Value").content, codetext)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Special handling for the medications section
|
44
|
+
def process_product (product, entry)
|
45
|
+
productName = product.at_xpath("./ccr:ProductName")
|
46
|
+
brandName = product.at_xpath("./ccr:BrandName")
|
47
|
+
productNameText = productName.at_xpath("./ccr:Text")
|
48
|
+
brandNameText = brandName.at_xpath("./ccr:Text") if brandName
|
49
|
+
entry.description = productNameText.content
|
50
|
+
process_product_codes(productName, entry) # we throw any codes found within the productName and brandName into the same entry
|
51
|
+
process_product_codes(brandName, entry) if brandName
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def create_product_entries(doc)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "date"
|
2
|
+
require "date/delta"
|
3
|
+
|
4
|
+
module HealthDataStandards
|
5
|
+
module Import
|
6
|
+
module CCR
|
7
|
+
class ProviderImporter
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
# Extract Healthcare Providers from CCR
|
11
|
+
#
|
12
|
+
# @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
|
13
|
+
# will have the "ccr" namespace registered to "urn:astm-org:CCR"
|
14
|
+
# @return [Array] an array of providers found in the document
|
15
|
+
def extract_providers(doc)
|
16
|
+
|
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
|
22
|
+
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
|
53
|
+
|
54
|
+
end #is a provider
|
55
|
+
end #is an actor
|
56
|
+
provider
|
57
|
+
end.compact
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module HealthDataStandards
|
2
|
+
module Import
|
3
|
+
module CCR
|
4
|
+
class ResultImporter < SectionImporter
|
5
|
+
|
6
|
+
# Traverses that ASTM CCR document passed in using XPath and creates an Array of Entry
|
7
|
+
# objects based on what it finds
|
8
|
+
# @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
|
9
|
+
# will have the "ccr" namespace registered to "urn:astm-org:CCR"
|
10
|
+
# measure definition
|
11
|
+
# @return [Array] will be a list of Entry objects
|
12
|
+
def create_entries(doc)
|
13
|
+
entry_list = []
|
14
|
+
entry_elements = doc.xpath(@entry_xpath)
|
15
|
+
entry_elements.each do |entry_element|
|
16
|
+
# Grab the time and the description from the Result node
|
17
|
+
dummy_entry = Entry.new
|
18
|
+
extract_dates(entry_element, dummy_entry)
|
19
|
+
dummy_entry.description = ""
|
20
|
+
if entry_element.at_xpath("./ccr:Description/ccr:Text")
|
21
|
+
dummy_entry.description = entry_element.at_xpath("./ccr:Description/ccr:Text").content
|
22
|
+
end
|
23
|
+
# Iterate over embedded tests
|
24
|
+
# Grab the values and the description from the Test nodes
|
25
|
+
# For each test, create an entry with the time from the Result, the description a concatenation of the Result and Test descriptions,
|
26
|
+
# and the value from the Test
|
27
|
+
|
28
|
+
tests = entry_element.xpath("./ccr:Test")
|
29
|
+
tests.each do |test|
|
30
|
+
entry = Entry.new
|
31
|
+
entry = dummy_entry.clone # copies time and description
|
32
|
+
extract_codes(test, entry)
|
33
|
+
extract_value(test, entry)
|
34
|
+
extract_status(test, entry)
|
35
|
+
extract_dates(test, entry)
|
36
|
+
entry.description = dummy_entry.description + ": " + entry.description
|
37
|
+
if @check_for_usable
|
38
|
+
entry_list << entry if entry.usable?
|
39
|
+
else
|
40
|
+
entry_list << entry
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
entry_list
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module HealthDataStandards
|
2
|
+
module Import
|
3
|
+
module CCR
|
4
|
+
# Class that can be used to create an importer for a section of a ASTM CCR document. It usually
|
5
|
+
# operates by selecting all CCR entries in a section and then creates entries for them.
|
6
|
+
class SectionImporter
|
7
|
+
attr_accessor :check_for_usable
|
8
|
+
# Creates a new SectionImporter
|
9
|
+
# @param [String] entry_xpath An XPath expression that can be used to find the desired entries
|
10
|
+
# @param [String] section_name name of the section. There is some section-dependent processing
|
11
|
+
def initialize(entry_xpath, section_name)
|
12
|
+
@entry_xpath = entry_xpath
|
13
|
+
@section_name = section_name
|
14
|
+
@check_for_usable = true # Pilot tools will set this to false
|
15
|
+
end
|
16
|
+
|
17
|
+
# normalize_coding_system attempts to simplify analysis of the XML doc by
|
18
|
+
# normalizing the names of the coding systems. Input is a single "Code" node
|
19
|
+
# in the tree, and the side effect is to edit the CodingSystem subnode.
|
20
|
+
# @param [String] code - Input is a single "Code" node
|
21
|
+
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
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def extract_status(parent_element, entry)
|
45
|
+
status_element = parent_element.at_xpath('./ccr:Status')
|
46
|
+
if status_element
|
47
|
+
status = parent_element.at_xpath('./ccr:Status/ccr:Text').content.downcase
|
48
|
+
|
49
|
+
if %w(active inactive resolved).include?(status)
|
50
|
+
entry.status = status.to_sym
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Add the codes from a <Code> block to an Entry
|
57
|
+
def extract_codes(parent_element, entry)
|
58
|
+
codes = parent_element.xpath("./ccr:Description/ccr:Code")
|
59
|
+
entry.description = ""
|
60
|
+
if (parent_element.at_xpath("./ccr:Description/ccr:Text") )
|
61
|
+
entry.description = parent_element.at_xpath("./ccr:Description/ccr:Text").content
|
62
|
+
end
|
63
|
+
if codes.size > 0
|
64
|
+
found_code = true
|
65
|
+
codes.each do |code|
|
66
|
+
normalize_coding_system(code)
|
67
|
+
entry.add_code(code.at_xpath("./ccr:Value").content, code.at_xpath("./ccr:CodingSystem").content)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Time is supposed to be in iso8601, but seems like we need to handle simple YYYY-MM-DD as well
|
73
|
+
def extract_time(datetime)
|
74
|
+
return unless datetime
|
75
|
+
Time.parse(datetime).to_i
|
76
|
+
end
|
77
|
+
|
78
|
+
def extract_dates(parent_element, entry)
|
79
|
+
datetime = parent_element.at_xpath('./ccr:DateTime')
|
80
|
+
if !datetime
|
81
|
+
return
|
82
|
+
end
|
83
|
+
if datetime.at_xpath('./ccr:ExactDateTime')
|
84
|
+
entry.time = extract_time(datetime.at_xpath('./ccr:ExactDateTime').content)
|
85
|
+
end
|
86
|
+
if datetime.at_xpath('./ccr:ApproximateDateTime')
|
87
|
+
entry.time = extract_time(datetime.at_xpath('./ccr:ApproximateDateTime').content)
|
88
|
+
end
|
89
|
+
if datetime.at_xpath('./ccr:DateTimeRange/ccr:BeginRange')
|
90
|
+
entry.start_time = extract_time(datetime.at_xpath('./ccr:DateTimeRange/ccr:BeginRange').content)
|
91
|
+
end
|
92
|
+
if datetime.at_xpath('./ccr:DateTimeRange/ccr:EndRange')
|
93
|
+
entry.end_time = extract_time(datetime.at_xpath('./ccr:DateTimeRange/ccr:EndRange').content)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
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
|
102
|
+
if value
|
103
|
+
entry.set_value(value, unit)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end # extract_value
|
107
|
+
|
108
|
+
# Traverses that ASTM CCR document passed in using XPath and creates an Array of Entry
|
109
|
+
# objects based on what it finds
|
110
|
+
# @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
|
111
|
+
# will have the "ccr" namespace registered to "urn:astm-org:CCR"
|
112
|
+
# measure definition
|
113
|
+
# @return [Array] will be a list of Entry objects
|
114
|
+
def create_entries(doc)
|
115
|
+
return nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
end # Importer
|
121
|
+
end
|
122
|
+
end # QME
|
123
|
+
|
124
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module HealthDataStandards
|
2
|
+
module Import
|
3
|
+
module CCR
|
4
|
+
class SimpleImporter < SectionImporter
|
5
|
+
# Traverses that ASTM CCR document passed in using XPath and creates an Array of Entry
|
6
|
+
# objects based on what it finds
|
7
|
+
# @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
|
8
|
+
# will have the "ccr" namespace registered to "urn:astm-org:CCR"
|
9
|
+
# measure definition
|
10
|
+
# @return [Array] will be a list of Entry objects
|
11
|
+
def create_entries(doc)
|
12
|
+
entry_list = []
|
13
|
+
entry_elements = doc.xpath(@entry_xpath)
|
14
|
+
entry_elements.each do |entry_element|
|
15
|
+
entry = Entry.new
|
16
|
+
extract_codes(entry_element, entry)
|
17
|
+
extract_dates(entry_element, entry)
|
18
|
+
extract_status(entry_element, entry)
|
19
|
+
if @check_for_usable
|
20
|
+
entry_list << entry if entry.usable?
|
21
|
+
else
|
22
|
+
entry_list << entry
|
23
|
+
end
|
24
|
+
end
|
25
|
+
entry_list
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module HealthDataStandards
|
2
|
+
module Import
|
3
|
+
module GreenC32
|
4
|
+
class ConditionImporter < SectionImporter
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def import(condition_xml)
|
12
|
+
condition_xml.root.add_namespace_definition('gc32', "urn:hl7-org:greencda:c32")
|
13
|
+
|
14
|
+
condition_element = condition_xml.xpath("./gc32:condition")
|
15
|
+
|
16
|
+
condition = Condition.new
|
17
|
+
|
18
|
+
extract_entry(condition_element, condition)
|
19
|
+
extract_name(condition_element, condition)
|
20
|
+
extract_interval(condition_element, condition)
|
21
|
+
extract_cause_of_death(condition_element, condition)
|
22
|
+
extract_type(condition_element, condition)
|
23
|
+
condition
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def extract_type(condition_xml, condition)
|
29
|
+
type = condition_xml.xpath("./gc32:type").first
|
30
|
+
condition.type = extract_node_text(type)
|
31
|
+
end
|
32
|
+
|
33
|
+
def extract_name(condition_xml, condition)
|
34
|
+
name = condition_xml.xpath("./gc32:name").first
|
35
|
+
condition.name = extract_node_text(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def extract_cause_of_death(condition_xml, condition)
|
39
|
+
condition.cause_of_death = extract_node_attribute(condition_xml, "causeOfDeath")
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module HealthDataStandards
|
2
|
+
module Import
|
3
|
+
module GreenC32
|
4
|
+
class ResultImporter < SectionImporter
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
@range = "./gc32:referenceRange"
|
10
|
+
@interpretation = "./gc32:interpretation"
|
11
|
+
end
|
12
|
+
|
13
|
+
def import(result)
|
14
|
+
result.root.add_namespace_definition('gc32', "urn:hl7-org:greencda:c32")
|
15
|
+
|
16
|
+
result_element = result.xpath("./gc32:result")
|
17
|
+
lab_result = LabResult.new(reference_range: extract_node_text(result_element.xpath(@range)))
|
18
|
+
|
19
|
+
extract_entry(result_element, lab_result)
|
20
|
+
extract_time(result_element, lab_result)
|
21
|
+
extract_value(result_element, lab_result)
|
22
|
+
extract_code(result_element, lab_result, @interpretation, :interpretation)
|
23
|
+
|
24
|
+
lab_result
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
module HealthDataStandards
|
4
|
+
module Import
|
5
|
+
module GreenC32
|
6
|
+
class SectionImporter
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@description = "./gc32:code/gc32:originalText"
|
10
|
+
@status = "./gc32:status"
|
11
|
+
@value = "./gc32:value"
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract_code(element, entry, xpath="./gc32:code", attribute=:codes)
|
15
|
+
|
16
|
+
code_element = element.xpath(xpath).first
|
17
|
+
|
18
|
+
return unless code_element
|
19
|
+
|
20
|
+
codes = build_code(code_element)
|
21
|
+
|
22
|
+
code_element.xpath("./gc32:translation").each do |trans|
|
23
|
+
codes.merge!(build_code(trans))
|
24
|
+
end
|
25
|
+
|
26
|
+
entry.send("#{attribute}=", codes)
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_description(element, entry)
|
30
|
+
description = element.xpath(@description).first
|
31
|
+
entry.description = extract_node_text(description)
|
32
|
+
end
|
33
|
+
|
34
|
+
def extract_status(element, entry)
|
35
|
+
status = extract_node_text(element.xpath(@status).first)
|
36
|
+
return unless status
|
37
|
+
entry.status = status
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_time(element, entry, xpath = "./gc32:effectiveTime", attribute = "time")
|
41
|
+
datetime = element.xpath(xpath).first
|
42
|
+
return unless datetime && !datetime.inner_text.empty?
|
43
|
+
entry.send("#{attribute}=", Time.parse(datetime.inner_text).to_i)
|
44
|
+
end
|
45
|
+
|
46
|
+
def extract_interval(element, entry)
|
47
|
+
extract_time(element, entry, "./gc32:effectiveTime/gc32:start", "start_time")
|
48
|
+
extract_time(element, entry, "./gc32:effectiveTime/gc32:end", "end_time")
|
49
|
+
end
|
50
|
+
|
51
|
+
def extract_value(element, entry)
|
52
|
+
|
53
|
+
value_element = element.xpath(@value).first
|
54
|
+
|
55
|
+
return unless value_element
|
56
|
+
|
57
|
+
node_value = extract_node_attribute(value_element, "amount", true)
|
58
|
+
node_units = extract_node_attribute(value_element, "unit")
|
59
|
+
|
60
|
+
entry.value = {'scalar' => node_value, "unit" => node_units} if node_value
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_entry(element, entry)
|
65
|
+
extract_code(element, entry)
|
66
|
+
extract_description(element, entry)
|
67
|
+
extract_status(element, entry)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def build_code(code_element)
|
73
|
+
code_system_oid = extract_node_attribute(code_element, "codeSystem")
|
74
|
+
code = extract_node_attribute(code_element, "code")
|
75
|
+
code_system = HealthDataStandards::Util::CodeSystemHelper.code_system_for(code_system_oid)
|
76
|
+
{code_system => [code]}
|
77
|
+
end
|
78
|
+
|
79
|
+
def extract_node_attribute(node, attribute_name, to_num=false)
|
80
|
+
return if node.nil? || (node.respond_to?(:empty?) && node.empty?)
|
81
|
+
attribute = node.attribute(attribute_name.to_s)
|
82
|
+
value = attribute ? attribute.value : nil
|
83
|
+
return unless value && value != ""
|
84
|
+
to_num ? value.to_f : value
|
85
|
+
end
|
86
|
+
|
87
|
+
def extract_node_text(node)
|
88
|
+
node ? node.inner_text : nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|