health-data-standards 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -1
- data/Rakefile +14 -4
- data/lib/health-data-standards.rb +23 -0
- data/lib/health-data-standards/export/ccr.rb +55 -56
- data/lib/health-data-standards/export/hdata/metadata.rb +16 -0
- data/lib/health-data-standards/export/html.rb +21 -0
- data/lib/health-data-standards/export/template_helper.rb +3 -0
- data/lib/health-data-standards/export/view_helper.rb +29 -5
- data/lib/health-data-standards/import/c32/condition_importer.rb +31 -33
- data/lib/health-data-standards/import/c32/encounter_importer.rb +4 -6
- data/lib/health-data-standards/import/c32/medication_importer.rb +1 -5
- data/lib/health-data-standards/import/c32/organization_importer.rb +23 -0
- data/lib/health-data-standards/import/c32/patient_importer.rb +1 -4
- data/lib/health-data-standards/import/c32/provider_importer.rb +43 -30
- data/lib/health-data-standards/import/c32/section_importer.rb +20 -41
- data/lib/health-data-standards/import/ccr/patient_importer.rb +27 -10
- data/lib/health-data-standards/import/ccr/provider_importer.rb +29 -41
- data/lib/health-data-standards/import/ccr/section_importer.rb +38 -27
- data/lib/health-data-standards/import/green_c32/allergy_importer.rb +20 -0
- data/lib/health-data-standards/import/green_c32/condition_importer.rb +2 -3
- data/lib/health-data-standards/import/green_c32/encounter_importer.rb +42 -0
- data/lib/health-data-standards/import/green_c32/immunization_importer.rb +23 -0
- data/lib/health-data-standards/import/green_c32/medication_importer.rb +69 -0
- data/lib/health-data-standards/import/green_c32/procedure_importer.rb +35 -0
- data/lib/health-data-standards/import/green_c32/result_importer.rb +21 -8
- data/lib/health-data-standards/import/green_c32/section_importer.rb +55 -9
- data/lib/health-data-standards/import/green_c32/social_history_importer.rb +18 -0
- data/lib/health-data-standards/import/green_c32/vital_sign_importer.rb +21 -0
- data/lib/health-data-standards/import/hdata/metadata_importer.rb +82 -0
- data/lib/health-data-standards/import/provider_import_utils.rb +23 -0
- data/lib/health-data-standards/models/address.rb +11 -0
- data/lib/health-data-standards/models/allergy.rb +1 -0
- data/lib/health-data-standards/models/condition.rb +1 -1
- data/lib/health-data-standards/models/encounter.rb +11 -6
- data/lib/health-data-standards/models/entry.rb +16 -5
- data/lib/health-data-standards/models/fulfillment_history.rb +3 -5
- data/lib/health-data-standards/models/immunization.rb +7 -1
- data/lib/health-data-standards/models/medication.rb +4 -3
- data/lib/health-data-standards/models/metadata/author.rb +16 -0
- data/lib/health-data-standards/models/metadata/base.rb +20 -0
- data/lib/health-data-standards/models/metadata/change_info.rb +9 -0
- data/lib/health-data-standards/models/metadata/link_info.rb +9 -0
- data/lib/health-data-standards/models/metadata/pedigree.rb +15 -0
- data/lib/health-data-standards/models/organization.rb +8 -0
- data/lib/health-data-standards/models/procedure.rb +5 -2
- data/lib/health-data-standards/models/provider.rb +6 -1
- data/lib/health-data-standards/models/record.rb +13 -3
- data/lib/health-data-standards/models/social_history.rb +3 -0
- data/lib/health-data-standards/models/telecom.rb +9 -0
- data/lib/health-data-standards/models/vital_sign.rb +2 -0
- data/lib/health-data-standards/util/code_system_helper.rb +3 -1
- data/templates/_address.gc32.erb +9 -0
- data/templates/_allergies.c32.erb +2 -2
- data/templates/_allergy.gc32.erb +13 -0
- data/templates/_care_goals.c32.erb +1 -1
- data/templates/_condition.gc32.erb +6 -6
- data/templates/_conditions.c32.erb +2 -2
- data/templates/_encounter.gc32.erb +32 -0
- data/templates/_encounters.c32.erb +1 -1
- data/templates/_immunization.gc32.erb +9 -0
- data/templates/_immunizations.c32.erb +1 -1
- data/templates/_medical_equipment.c32.erb +1 -1
- data/templates/_medication.gc32.erb +60 -0
- data/templates/_medications.c32.erb +1 -1
- data/templates/_narrative_block.c32.erb +1 -1
- data/templates/_organization.gc32.erb +10 -0
- data/templates/_pedigree.hdata.erb +24 -0
- data/templates/_procedure.gc32.erb +8 -0
- data/templates/_procedures.c32.erb +1 -1
- data/templates/_provider.gc32.erb +19 -0
- data/templates/_results.c32.erb +1 -1
- data/templates/_social_history.c32.erb +1 -1
- data/templates/_social_history.gc32.erb +6 -0
- data/templates/_telecom.gc32.erb +1 -0
- data/templates/_vital_sign.gc32.erb +12 -0
- data/templates/_vital_signs.c32.erb +1 -1
- data/templates/metadata.hdata.erb +35 -0
- data/templates/show.html.erb +287 -0
- metadata +50 -15
@@ -0,0 +1,23 @@
|
|
1
|
+
module ProviderImportUtils
|
2
|
+
|
3
|
+
def extract_provider(performer)
|
4
|
+
provider_data = extract_provider_data(performer, false)
|
5
|
+
find_or_create_provider(provider_data)
|
6
|
+
end
|
7
|
+
|
8
|
+
def find_or_create_provider(provider_hash)
|
9
|
+
provider = Provider.first(conditions: {npi: provider_hash[:npi]}) if provider_hash[:npi] && !provider_hash[:npi].empty?
|
10
|
+
provider ||= Provider.create(provider_hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns nil if result is an empty string, block allows text munging of result if there is one
|
14
|
+
def extract_data(subject, query)
|
15
|
+
result = subject.at_xpath(query)
|
16
|
+
if result.nil? || result.content.empty?
|
17
|
+
nil
|
18
|
+
else
|
19
|
+
result.content
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -3,7 +3,7 @@ class Condition < Entry
|
|
3
3
|
field :causeOfDeath, type: Boolean
|
4
4
|
field :name, type: String
|
5
5
|
|
6
|
-
|
6
|
+
embeds_many :treating_provider, class_name: "Provider"
|
7
7
|
|
8
8
|
alias :cause_of_death :causeOfDeath
|
9
9
|
alias :cause_of_death= :causeOfDeath=
|
@@ -1,12 +1,17 @@
|
|
1
1
|
class Encounter < Entry
|
2
|
-
field :performer, type: Hash
|
3
|
-
field :facility, type: Hash
|
4
2
|
field :admitType, type: Hash
|
5
|
-
field :
|
6
|
-
|
3
|
+
field :dischargeDisposition, type: Hash
|
4
|
+
field :free_text, type: String
|
7
5
|
|
6
|
+
embeds_one :facility, class_name: "Organization"
|
7
|
+
embeds_one :reason, class_name: "Entry"
|
8
|
+
|
9
|
+
belongs_to :performer, class_name: "Provider"
|
10
|
+
|
8
11
|
alias :admit_type :admitType
|
9
12
|
alias :admit_type= :admitType=
|
10
|
-
alias :
|
11
|
-
alias :
|
13
|
+
alias :discharge_disposition :dischargeDisposition
|
14
|
+
alias :discharge_disposition= :dischargeDisposition=
|
15
|
+
alias :freeText :free_text
|
16
|
+
alias :freeText= :free_text=
|
12
17
|
end
|
@@ -2,9 +2,12 @@ class Entry
|
|
2
2
|
|
3
3
|
include Mongoid::Document
|
4
4
|
|
5
|
-
embedded_in :entry_list, polymorphic: true
|
6
|
-
|
5
|
+
# embedded_in :entry_list, polymorphic: true
|
6
|
+
|
7
|
+
embedded_in :record
|
8
|
+
|
7
9
|
field :description, type: String
|
10
|
+
field :specifics, type: String
|
8
11
|
field :time, type: Integer
|
9
12
|
field :start_time, type: Integer
|
10
13
|
field :end_time, type: Integer
|
@@ -27,11 +30,12 @@ class Entry
|
|
27
30
|
|
28
31
|
# Will return a single code and code set if one exists in the code sets that are
|
29
32
|
# passed in. Returns a hash with a key of code and code_set if found, nil otherwise
|
30
|
-
def preferred_code(preferred_code_sets)
|
31
|
-
|
33
|
+
def preferred_code(preferred_code_sets, codes_attribute=:codes)
|
34
|
+
codes_value = send(codes_attribute)
|
35
|
+
matching_code_sets = preferred_code_sets & codes_value.keys
|
32
36
|
if matching_code_sets.present?
|
33
37
|
code_set = matching_code_sets.first
|
34
|
-
{'code' =>
|
38
|
+
{'code' => codes_value[code_set].first, 'code_set' => code_set}
|
35
39
|
else
|
36
40
|
nil
|
37
41
|
end
|
@@ -92,6 +96,9 @@ class Entry
|
|
92
96
|
if event['description']
|
93
97
|
entry.description = event['description']
|
94
98
|
end
|
99
|
+
if event['specifics']
|
100
|
+
entry.specifics = event['specifics']
|
101
|
+
end
|
95
102
|
if event['status']
|
96
103
|
entry.status = event['status']
|
97
104
|
end
|
@@ -195,6 +202,10 @@ class Entry
|
|
195
202
|
if description
|
196
203
|
entry_hash['description'] = description
|
197
204
|
end
|
205
|
+
|
206
|
+
if specifics
|
207
|
+
entry_hash['specifics'] = specifics
|
208
|
+
end
|
198
209
|
|
199
210
|
entry_hash
|
200
211
|
end
|
@@ -2,17 +2,15 @@ class FulfillmentHistory
|
|
2
2
|
include Mongoid::Document
|
3
3
|
|
4
4
|
field :prescriptionNumber, type: String
|
5
|
-
field :provider, type: Hash
|
6
|
-
field :dispensingPharmacyLocation, type: Hash
|
7
5
|
field :dispenseDate, type: Integer
|
8
6
|
field :quantityDispensed, type: Hash
|
9
7
|
field :fillNumber, type: Integer
|
10
|
-
field :fillStatus, type:
|
8
|
+
field :fillStatus, type: String
|
11
9
|
|
10
|
+
belongs_to :provider, class_name: "Provider"
|
11
|
+
|
12
12
|
alias :prescription_number :prescriptionNumber
|
13
13
|
alias :prescription_number= :prescriptionNumber=
|
14
|
-
alias :dispensing_pharmacy_location :dispensingPharmacyLocation
|
15
|
-
alias :dispensing_pharmacy_location= :dispensingPharmacyLocation=
|
16
14
|
alias :dispense_date :dispenseDate
|
17
15
|
alias :dispense_date= :dispenseDate=
|
18
16
|
alias :quantity_dispensed :quantityDispensed
|
@@ -1,10 +1,16 @@
|
|
1
1
|
class Immunization < Entry
|
2
2
|
field :refusalInd, type: Boolean
|
3
|
-
field :performer, type: Hash
|
4
3
|
field :refusalReason, type: Hash
|
4
|
+
field :seriesNumber, type: Integer
|
5
|
+
|
6
|
+
belongs_to :performer, class_name: "Provider"
|
7
|
+
|
8
|
+
embeds_one :medication_product
|
5
9
|
|
6
10
|
alias :refusal_ind :refusalInd
|
7
11
|
alias :refusal_ind= :refusalInd=
|
8
12
|
alias :refusal_reason :refusalReason
|
9
13
|
alias :refusal_reason= :refusalReason=
|
14
|
+
alias :series_number :seriesNumber
|
15
|
+
alias :series_number= :seriesNumber=
|
10
16
|
end
|
@@ -2,11 +2,11 @@ class Medication < Entry
|
|
2
2
|
field :administrationTiming, type: Hash
|
3
3
|
field :freeTextSig, type: String
|
4
4
|
field :dose, type: Hash
|
5
|
-
field :brandName, type: String
|
6
5
|
field :typeOfMedication, type: Hash
|
7
6
|
field :statusOfMedication, type: Hash
|
8
7
|
embeds_many :fulfillmentHistory, class_name: 'FulfillmentHistory'
|
9
8
|
embeds_many :orderInformation, class_name: 'OrderInformation'
|
9
|
+
|
10
10
|
field :route, type: Hash
|
11
11
|
field :site, type: Hash
|
12
12
|
field :doseRestriction, type: Hash
|
@@ -17,13 +17,12 @@ class Medication < Entry
|
|
17
17
|
field :reaction, type: Hash
|
18
18
|
field :deliveryMethod, type: Hash
|
19
19
|
field :patientInstructions, type: String
|
20
|
+
field :doseIndicator, type: String
|
20
21
|
|
21
22
|
alias :administration_timing :administrationTiming
|
22
23
|
alias :administration_timing= :administrationTiming=
|
23
24
|
alias :free_text_sig :freeTextSig
|
24
25
|
alias :free_text_sig= :freeTextSig=
|
25
|
-
alias :brand_name :brandName
|
26
|
-
alias :brand_name= :brandName=
|
27
26
|
alias :type_of_medication :typeOfMedication
|
28
27
|
alias :type_of_medication= :typeOfMedication=
|
29
28
|
alias :status_of_medication :statusOfMedication
|
@@ -42,4 +41,6 @@ class Medication < Entry
|
|
42
41
|
alias :delivery_method= :deliveryMethod=
|
43
42
|
alias :patient_instructions :patientInstructions
|
44
43
|
alias :patient_instructions= :patientInstructions=
|
44
|
+
alias :dose_indicator :doseIndicator
|
45
|
+
alias :dose_indicator= :doseIndicator=
|
45
46
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Metadata
|
2
|
+
class Author
|
3
|
+
include Mongoid::Document
|
4
|
+
|
5
|
+
embedded_in :pedigree, class_name: "Metadata::Pedigree"
|
6
|
+
|
7
|
+
Types = %w(authenticator authorcustodiandataEnterer informant
|
8
|
+
legalAuthenticator participant performer recordTarget)
|
9
|
+
|
10
|
+
field :name, type: String
|
11
|
+
field :type, type: String
|
12
|
+
field :role, type: String
|
13
|
+
|
14
|
+
validates_inclusion_of :type, in: Types, allow_nil: true
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Represents the metadata associated with a hData section
|
2
|
+
module Metadata
|
3
|
+
NS = 'http://www.hl7.org/schemas/hdata/2009/11/metadata'
|
4
|
+
|
5
|
+
class Base
|
6
|
+
include Mongoid::Document
|
7
|
+
|
8
|
+
field :mime_types, type: Array
|
9
|
+
field :confidentiality, type: String
|
10
|
+
field :original_creation_time, type: Time
|
11
|
+
|
12
|
+
embedded_in :entry
|
13
|
+
|
14
|
+
embeds_many :pedigrees, class_name: "Metadata::Pedigree"
|
15
|
+
embeds_many :modified_dates, class_name: "Metadata::ChangeInfo"
|
16
|
+
embeds_many :copied_dates, class_name: "Metadata::ChangeInfo"
|
17
|
+
embeds_many :linked_documents, class_name: "Metadata::LinkInfo"
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Metadata
|
2
|
+
class Pedigree
|
3
|
+
include Mongoid::Document
|
4
|
+
|
5
|
+
field :organization, type: String
|
6
|
+
field :signature, type: String
|
7
|
+
field :document_method, type: String
|
8
|
+
field :derived, type: Boolean
|
9
|
+
|
10
|
+
embeds_one :author, class_name: "Metadata::Author"
|
11
|
+
embeds_many :source_pedigrees, class_name: "Metadata::Pedigree"
|
12
|
+
embeds_many :source_documents, class_name: "Metadata::LinkInfo"
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -12,7 +12,11 @@ class Provider
|
|
12
12
|
|
13
13
|
validates_uniqueness_of :npi, allow_blank: true
|
14
14
|
|
15
|
-
|
15
|
+
embeds_many :addresses, as: :locatable
|
16
|
+
embeds_many :telecoms, as: :contactable
|
17
|
+
embeds_one :organization
|
18
|
+
|
19
|
+
|
16
20
|
def records(effective_date=nil)
|
17
21
|
Record.by_provider(self, effective_date)
|
18
22
|
end
|
@@ -21,6 +25,7 @@ class Provider
|
|
21
25
|
# checksum using the Luhn algorithm with additional special handling as described in
|
22
26
|
# https://www.cms.gov/NationalProvIdentStand/Downloads/NPIcheckdigit.pdf
|
23
27
|
def self.valid_npi?(npi)
|
28
|
+
return false unless npi
|
24
29
|
return false if npi.length != 10 and npi.length != 15
|
25
30
|
return false if npi.gsub(/\d/, '').length > 0 # npi must be all digits
|
26
31
|
return false if npi.length == 15 and (npi =~ /^80840/)==nil # 15 digit npi must start with 80840
|
@@ -15,15 +15,15 @@ class Record
|
|
15
15
|
|
16
16
|
embeds_many :allergies
|
17
17
|
embeds_many :care_goals, class_name: "Entry"
|
18
|
-
embeds_many :conditions
|
18
|
+
embeds_many :conditions
|
19
19
|
embeds_many :encounters
|
20
20
|
embeds_many :immunizations
|
21
21
|
embeds_many :medical_equipment, class_name: "Entry"
|
22
22
|
embeds_many :medications
|
23
23
|
embeds_many :procedures
|
24
24
|
embeds_many :results, class_name: "LabResult"
|
25
|
-
embeds_many :social_history
|
26
|
-
embeds_many :vital_signs
|
25
|
+
embeds_many :social_history
|
26
|
+
embeds_many :vital_signs
|
27
27
|
|
28
28
|
Sections = [:allergies, :care_goals, :conditions, :encounters, :immunizations, :medical_equipment,
|
29
29
|
:medications, :procedures, :results, :social_history, :vital_signs]
|
@@ -40,4 +40,14 @@ class Record
|
|
40
40
|
def over_18?
|
41
41
|
Time.at(birthdate) < Time.now.years_ago(18)
|
42
42
|
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def self.provider_queries(provider_id, effective_date)
|
47
|
+
{'$or' => [provider_query(provider_id, effective_date,effective_date), provider_query(provider_id, nil,effective_date), provider_query(provider_id, effective_date,nil)]}
|
48
|
+
end
|
49
|
+
def self.provider_query(provider_id, start_before, end_after)
|
50
|
+
{'provider_performances' => {'$elemMatch' => {'provider_id' => provider_id, '$and'=>[{'$or'=>[{'start_date'=>nil},{'start_date'=>{'$lt'=>start_before}}]}, {'$or'=>[{'end_date'=>nil},{'end_date'=> {'$gt'=>end_after}}]}] } }}
|
51
|
+
end
|
52
|
+
|
43
53
|
end
|
@@ -13,7 +13,9 @@ module HealthDataStandards
|
|
13
13
|
'2.16.840.1.113883.6.90' => 'ICD-10-CM',
|
14
14
|
'2.16.840.1.113883.6.14' => 'HCPCS',
|
15
15
|
'2.16.840.1.113883.6.59' => 'CVX',
|
16
|
-
'2.16.840.1.113883.5.83' => 'HITSP C80 Observation Status'
|
16
|
+
'2.16.840.1.113883.5.83' => 'HITSP C80 Observation Status',
|
17
|
+
"2.16.840.1.113883.3.26.1.1" => "NCI Thesaurus",
|
18
|
+
"2.16.840.1.113883.3.88.12.80.20" => "FDA"
|
17
19
|
}
|
18
20
|
|
19
21
|
# Returns the name of a code system given an oid
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% tag_name ||= "address"%>
|
2
|
+
<<%=tag_name%>>
|
3
|
+
<% address.street.each do |street| %>
|
4
|
+
<streetAddressLine><%= street %></streetAddressLine>
|
5
|
+
<% end %>
|
6
|
+
<city><%= address.city %></city>
|
7
|
+
<state><%= address.state %></state>
|
8
|
+
<postalCode><%= address.zip %></postalCode>
|
9
|
+
</<%=tag_name%>>
|
@@ -20,7 +20,7 @@
|
|
20
20
|
<code nullFlavor="NA"/>
|
21
21
|
<statusCode code="active"/>
|
22
22
|
<effectiveTime>
|
23
|
-
<low
|
23
|
+
<low <%= value_or_null_flavor(entry.as_point_in_time) %>/>
|
24
24
|
</effectiveTime>
|
25
25
|
<entryRelationship typeCode="SUBJ" inversionInd="false">
|
26
26
|
<observation classCode="OBS" moodCode="EVN">
|
@@ -37,7 +37,7 @@
|
|
37
37
|
</text>
|
38
38
|
<statusCode code="completed"/>
|
39
39
|
<effectiveTime>
|
40
|
-
<low
|
40
|
+
<low <%= value_or_null_flavor(entry.as_point_in_time) %>/>
|
41
41
|
</effectiveTime>
|
42
42
|
<value xsi:type="CD" nullFlavor="UNK"/>
|
43
43
|
<participant typeCode="CSM">
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<allergy xmlns="urn:hl7-org:greencda:c32">
|
2
|
+
<id><%= allergy.id%></id>
|
3
|
+
<status><%= allergy.status%></status>
|
4
|
+
<%== code_display allergy, "tag_name" => "code", 'preferred_code_sets' => ['SNOMED-CT', "RxNorm", "FDA"] %>
|
5
|
+
<type><%= allergy.type%></type>
|
6
|
+
<effectiveTime>
|
7
|
+
<start><%= time_if_not_nil(allergy.start_time, allergy.time) %></start>
|
8
|
+
<end><%= time_if_not_nil(allergy.end_time) %></end>
|
9
|
+
</effectiveTime>
|
10
|
+
<%== code_display allergy, "tag_name" => "reaction", 'preferred_code_sets' => ['SNOMED-CT'] %>
|
11
|
+
<%== code_display allergy, "tag_name" => "severity", 'preferred_code_sets' => ['SNOMED-CT'] %>
|
12
|
+
</allergy>
|
13
|
+
|
@@ -17,7 +17,7 @@
|
|
17
17
|
<id root="<%= UUID.generate %>"/>
|
18
18
|
<%== code_display(entry, :preferred_code_sets => ['SNOMED-CT']) %>
|
19
19
|
<statusCode code="new"/>
|
20
|
-
<effectiveTime
|
20
|
+
<effectiveTime <%= value_or_null_flavor(entry.as_point_in_time) %>/>
|
21
21
|
</observation>
|
22
22
|
</entry>
|
23
23
|
<% end -%>
|