health-data-standards 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|