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
@@ -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
@@ -0,0 +1,11 @@
1
+ class Address
2
+ include Mongoid::Document
3
+
4
+ field :street, type: Array
5
+ field :city, type: String
6
+ field :state, type: String
7
+ field :zip, type: String
8
+ field :country, type: String
9
+
10
+ embedded_in :locatable, polymorphic: true
11
+ end
@@ -1,4 +1,5 @@
1
1
  class Allergy < Entry
2
+ field :type, type: String
2
3
  field :reaction, type: Hash
3
4
  field :severity, type: Hash
4
5
  end
@@ -3,7 +3,7 @@ class Condition < Entry
3
3
  field :causeOfDeath, type: Boolean
4
4
  field :name, type: String
5
5
 
6
- # embeds_many :treating_provider, class_name: "Provider"
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 :dischargeDisp, type: Hash
6
- embeds_one :reason, class_name: "Entry"
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 :discharge_disp :dischargeDisp
11
- alias :discharge_disp= :dischargeDisp=
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
- matching_code_sets = preferred_code_sets & codes.keys
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' => codes[code_set].first, 'code_set' => code_set}
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: Hash
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,9 @@
1
+ module Metadata
2
+ class ChangeInfo
3
+ include Mongoid::Document
4
+
5
+ embeds_one :pedigree, class_name: "Metadata::Pedigree"
6
+
7
+ field :timestamp, type: Time
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Metadata
2
+ class LinkInfo
3
+ include Mongoid::Document
4
+
5
+ field :href, type: String
6
+ field :extension, type: String
7
+
8
+ end
9
+ 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
@@ -0,0 +1,8 @@
1
+ class Organization
2
+ include Mongoid::Document
3
+
4
+ field :name, type: String
5
+
6
+ embeds_many :addresses, as: :locatable
7
+ embeds_many :telecoms, as: :contactable
8
+ end
@@ -1,4 +1,7 @@
1
1
  class Procedure < Entry
2
- field :performer, type: Hash
3
- field :site, type: Hash
2
+ field :type, type: String
3
+ field :site, type: Hash
4
+ field :description, type: String
5
+
6
+ belongs_to :performer, class_name: "Provider"
4
7
  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, class_name: "Entry"
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, class_name: "Entry"
26
- embeds_many :vital_signs, class_name: "Entry"
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
@@ -0,0 +1,3 @@
1
+ class SocialHistory < Entry
2
+ field :type, type: Hash
3
+ end
@@ -0,0 +1,9 @@
1
+ class Telecom
2
+ include Mongoid::Document
3
+
4
+ field :use, type: String
5
+ field :value, type: String
6
+ field :preferred, type: Boolean
7
+
8
+ embedded_in :contactable, polymorphic: true
9
+ end
@@ -0,0 +1,2 @@
1
+ class VitalSign < LabResult
2
+ 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 value="<%= Time.at(entry.time).utc.to_formatted_s(:number) %>"/>
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 value="<%= Time.at(entry.time).utc.to_formatted_s(:number) %>"/>
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 value="<%= Time.at(entry.time).utc.to_formatted_s(:number) %>"/>
20
+ <effectiveTime <%= value_or_null_flavor(entry.as_point_in_time) %>/>
21
21
  </observation>
22
22
  </entry>
23
23
  <% end -%>