yahl7 0.4.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ead354fddaf6834942e7a7e23d46a965adf5f318cb730278844fefb728ad0a16
4
- data.tar.gz: c6d4c697c4d32e1128401d37024d6518a57b798cb6400b73c4ec730fbf0085f4
3
+ metadata.gz: 51918fd00971add5c38a8b7755cdc818743073aae001707d0950644abb9bae83
4
+ data.tar.gz: 39e37efdceacaf25896afba0ba836e7e2db148f4f15b833aea78251043292179
5
5
  SHA512:
6
- metadata.gz: 3ff7e6dbfdc8b2a2c31c0025da6753c77a6b4c3dd400f57fd31010c6dca0439ecd17c3ad6e2c74140b3d16b970d9273f46d10efaa3c35204ef2b40b98765da71
7
- data.tar.gz: f2c9a7572f48cfbc898ba902f3c0415b68a4d3509deb54b855856815bad69e6e33b3c3d41ec0b92d8d36f977d6429177d3ce6f16d3a970ff49e164dc0c4064ff
6
+ metadata.gz: eb3b7c2633e691d91f499adfae90aa91fc0e408a272eabf5abe7b551556defece6cc6c5d2205d1792f776efee20cebe959ffcde18edb28638d9567c8b7bd72be
7
+ data.tar.gz: a28b49feca05c217d0335431eb11a16417a63fe7f166eef146d1d89744f66f14e4f37e1d416b7704bc8be1a7c7f8c4c52e5aa67f86406cae89404396a05ad12c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,64 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.0] - 2021-09-02
9
+
10
+ This release implements a lot of changes, new features, and bug fixes. These are
11
+ largely due to testing the library with a larger set of HL7 messages of various
12
+ versions (2.3 to 2.5).
13
+
14
+ This should clean up a number of rough edges, but the gem should not be
15
+ considered production-ready, yet.
16
+
17
+ ### Added
18
+
19
+ - HL7 data types:
20
+ - `XCN` for extended composite names
21
+ - The `CNN` data type now has a `#full_name` method to match `XPN` and `XCN`.
22
+ - The `NDL` data type now has aliases to the name methods in order to match the
23
+ API of other name elements.
24
+ - The segment type now overrides `#to_s`
25
+ - The `ORU` message type now has an `#observations_and_notes` method, which
26
+ returns all `OBX` and `NTE` segments in order for that message. This is useful
27
+ for building a complete report of test results for applicable messages.
28
+ - Utilize `FT` data type for `OBX.5` for now. This could be various data types,
29
+ so I will need to figure out a more appropriate way to handle this.
30
+
31
+ ### Changed
32
+
33
+ - Field values are now normalized so that empty values are returned as `nil`
34
+ whether they are strings or class instances.
35
+ - The `FT` data type can now handle repeated values.
36
+ - The `XAD` data type can now handle repeated values.
37
+ - The `XTN` data type can now handle repeated values.
38
+ - Mapped the following fields to the `XCN` data type:
39
+ - `DG1.16`
40
+ - `EVN.5`
41
+ - `IN1.30`
42
+ - `OBR.10`
43
+ - `OBR.16`
44
+ - `OBR.28`
45
+ - `OBX.16`
46
+ - `ORC.10`
47
+ - `ORC.11`
48
+ - `ORC.12`
49
+ - `ORC.19`
50
+ - `PD1.4`
51
+ - `PV1.7`
52
+ - `PV1.8`
53
+ - `PV1.9`
54
+ - `PV1.17`
55
+ - `PV1.52`
56
+
57
+ ### Fixed
58
+
59
+ - Messages now display body correctly.
60
+ - The `NDL` data type did not play well with some implementations that do not
61
+ include all field values.
62
+ - The `#suffixes` method in `YAHL7::V2::AliasPersonName` now deduplicates the
63
+ suffix entries (i.e., degree and suffix) so that names appear more natural if
64
+ the generating system uses the same item in both degree and suffix.
65
+
8
66
  ## [0.4.0] - 2021-09-01
9
67
 
10
68
  ### Added
data/ERRATA.md CHANGED
@@ -14,3 +14,5 @@ Cannot handle character escape sequence | Initial | N
14
14
  Cannot handle multi-byte character escape sequence | Initial | N/A
15
15
  Cannot handle hexadecimal character escape sequence | Initial | N/A
16
16
  Cannot resolve timestamps to partial-second accuracy | Initial | N/A
17
+ Does not handle repeated fields in all cases | Initial | N/A
18
+ OBX.5 is always parsed as formatted text | 0.5.0 | N/A
@@ -22,17 +22,56 @@ module YAHL7
22
22
  base.extend(ClassMethods)
23
23
  end
24
24
 
25
+ def normalize_value(value)
26
+ value.nil? || value == '' ? nil : value
27
+ end
28
+
29
+ def make_field(klass, value)
30
+ value = normalize_value(value)
31
+ return nil if value.nil?
32
+
33
+ if klass.respond_to?(:repeated?) && klass.repeated?(value)
34
+ value.map { |v| new_class_value(klass, v) }
35
+ else
36
+ new_class_value(klass, value)
37
+ end
38
+ end
39
+
40
+ def new_class_value(klass, value)
41
+ case klass.name
42
+ when 'YAHL7::V2::DataType::FT' then klass.new(value, parse_options)
43
+ else klass.new(value)
44
+ end
45
+ end
46
+
25
47
  # This is the module that actually extends the segment.
26
48
  module ClassMethods
49
+ # This method is used to define the field name mappings for the data
50
+ # type / segment.
51
+ #
52
+ # It is possible to return a string, in which case you can use an
53
+ # integer value. If you want to return a specific data type, you must
54
+ # use a hash instead, in the form of (for example):
55
+ #
56
+ # define_field_names({
57
+ # some_field: 0,
58
+ # some_other_field: { index: 1, class: YAHL7::V2::DataType::TS }
59
+ # })
60
+ #
61
+ # This defines the method `some_field` which returns a string, while
62
+ # the other method `some_other_field` returns either a `nil` value or a
63
+ # `YAHL7::V2::DataType::TS` value.
64
+ #
65
+ # It is possible that the field is repeatable. If so, it is up to the
66
+ # data type to define a `::repeated?` method that receives the raw
67
+ # value and then it determines if the field was repeated or not. If it
68
+ # is repeatable and it is determined to be repeated, an array of that
69
+ # type is returned.
27
70
  def define_field_names(mappings)
28
71
  mappings.each do |name, mapping|
29
72
  case mapping
30
- when Integer then define_method(name) { self[mapping] }
31
- when Hash
32
- define_method(name) do
33
- value = self[mapping[:index]]
34
- value.nil? || value == '' ? nil : mapping[:class].new(value)
35
- end
73
+ when Integer then define_method(name) { normalize_value(self[mapping]) }
74
+ when Hash then define_method(name) { make_field(mapping[:class], self[mapping[:index]]) }
36
75
  end
37
76
  end
38
77
  end
@@ -46,6 +46,8 @@ module YAHL7
46
46
  def suffixes
47
47
  [suffix, degree]
48
48
  .reject { |p| p.nil? || p == '' }
49
+ .map(&:strip)
50
+ .uniq
49
51
  .join(', ')
50
52
  end
51
53
 
@@ -23,6 +23,14 @@ module YAHL7
23
23
  assigning_authority_universal_id: 9,
24
24
  assigning_authority_universal_id_type: 10
25
25
  })
26
+
27
+ def full_name
28
+ assemble_name_given_first
29
+ end
30
+
31
+ def self.repeated?(value)
32
+ !value.nil? && value[0].is_a?(Array)
33
+ end
26
34
  end
27
35
  end
28
36
  end
@@ -7,10 +7,19 @@ module YAHL7
7
7
  class DataType
8
8
  # This is the HL7 data type for formatted text
9
9
  class FT < YAHL7::V2::DataType
10
+ def initialize(value, parse_options)
11
+ @value = value
12
+ @formatter = YAHL7::V2::Formatter.new(parse_options)
13
+ end
14
+
10
15
  def formatted
11
16
  @formatted ||= parse_value
12
17
  end
13
18
 
19
+ def self.repeated?(value)
20
+ value.is_a?(Array)
21
+ end
22
+
14
23
  private
15
24
 
16
25
  def parse_value
@@ -18,7 +27,7 @@ module YAHL7
18
27
  end
19
28
 
20
29
  def parse_body(body)
21
- YAHL7::V2::Formatter.format(body)
30
+ @formatter.format(body)
22
31
  end
23
32
  end
24
33
  end
@@ -24,6 +24,32 @@ module YAHL7
24
24
  building: 9,
25
25
  floor: 10
26
26
  })
27
+
28
+ def initialize(value)
29
+ @value = if value.nil?
30
+ []
31
+ elsif value.is_a?(String) || value[0].is_a?(String)
32
+ [value]
33
+ else
34
+ value
35
+ end
36
+ end
37
+
38
+ def full_name
39
+ name&.full_name
40
+ end
41
+
42
+ def bare_name_given_first
43
+ name&.bare_name_given_first
44
+ end
45
+
46
+ def bare_name_family_first
47
+ name&.bare_name_family_first
48
+ end
49
+
50
+ def self.repeated?(value)
51
+ !value.nil? && value[0].is_a?(Array) && value[0][0].is_a?(Array)
52
+ end
27
53
  end
28
54
  end
29
55
  end
@@ -25,6 +25,10 @@ module YAHL7
25
25
  effective_date: { index: 12, class: YAHL7::V2::DataType::TS },
26
26
  expiration_date: { index: 13, class: YAHL7::V2::DataType::TS }
27
27
  })
28
+
29
+ def self.repeated?(value)
30
+ !value.nil? && value[0].is_a?(Array)
31
+ end
28
32
  end
29
33
  end
30
34
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module YAHL7
6
+ module V2
7
+ class DataType
8
+ # This is the HL7 data type for an extended composite name and ID number.
9
+ class XCN < YAHL7::V2::DataType
10
+ include YAHL7::V2::AliasPersonName
11
+ include YAHL7::V2::AliasFieldNames
12
+
13
+ define_field_names({
14
+ id_number: 0,
15
+ family_name: 1,
16
+ given_name: 2,
17
+ middle_name: 3,
18
+ suffix: 4,
19
+ prefix: 5,
20
+ degree: 6,
21
+ source_table: 7,
22
+ assigning_authority: 8,
23
+ name_type_code: 9,
24
+ identifier_check_digit: 10,
25
+ check_digit_scheme: 11,
26
+ identifier_type_code: 12,
27
+ assigning_facility: 13,
28
+ name_representation_code: 14,
29
+ name_context: 15,
30
+ name_validity_range: 16,
31
+ name_assembly_order: 17,
32
+ effective_date: 18,
33
+ expiration_date: { index: 19, class: YAHL7::V2::DataType::TS },
34
+ professional_suffix: { index: 20, class: YAHL7::V2::DataType::TS },
35
+ assigning_jurisdiction: 21,
36
+ assigning_agency_or_department: 22
37
+ })
38
+
39
+ def full_name
40
+ case name_assembly_order&.downcase
41
+ when 'f' then assemble_name_family_first
42
+ else assemble_name_given_first
43
+ end
44
+ end
45
+
46
+ def self.repeated?(value)
47
+ !value.nil? && value[0].is_a?(Array)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -23,6 +23,10 @@ module YAHL7
23
23
  speed_dial_code: 10,
24
24
  unformatted_telephone_number: 11
25
25
  })
26
+
27
+ def self.repeated?(value)
28
+ !value.nil? && value[0].is_a?(Array)
29
+ end
26
30
  end
27
31
  end
28
32
  end
@@ -5,6 +5,14 @@ module YAHL7
5
5
  class Message
6
6
  # ORU messages contain unsolicited observation results about a patient.
7
7
  class ORU < YAHL7::V2::Message
8
+ OBSERVATION_AND_NOTE_TYPES = %w[NTE OBX].freeze
9
+
10
+ # This method is used to return all NTE and OBX segments in their
11
+ # original order. This can be useful for building a report of test
12
+ # results in applicable messages.
13
+ def observations_and_notes
14
+ segments.filter { |s| OBSERVATION_AND_NOTE_TYPES.include?(s.type) }
15
+ end
8
16
  end
9
17
  end
10
18
  end
@@ -18,7 +18,7 @@ module YAHL7
18
18
  end
19
19
 
20
20
  def to_s
21
- @to_s ||= segments.join(parse_options.segment_sep)
21
+ @to_s ||= segments.map(&:to_s).join(parse_options.segment_sep)
22
22
  end
23
23
 
24
24
  def [](index)
@@ -23,7 +23,7 @@ module YAHL7
23
23
  outlier_cost: 13,
24
24
  grouper_version_and_type: 14,
25
25
  diagnostic_priority: 15,
26
- diagnosing_clinician: 16,
26
+ diagnosing_clinician: { index: 16, class: YAHL7::V2::DataType::XCN },
27
27
  diagnosis_classification: 17,
28
28
  confidential_indicator: 18,
29
29
  attestation_datetime: { index: 19, class: YAHL7::V2::DataType::TS },
@@ -13,7 +13,7 @@ module YAHL7
13
13
  recorded_datetime: { index: 2, class: YAHL7::V2::DataType::TS },
14
14
  planned_event_datetime: { index: 3, class: YAHL7::V2::DataType::TS },
15
15
  event_reason_code: 4,
16
- operator_id: 5,
16
+ operator_id: { index: 5, class: YAHL7::V2::DataType::XCN },
17
17
  event_occurred: { index: 6, class: YAHL7::V2::DataType::TS },
18
18
  event_facility: 7
19
19
  })
@@ -37,7 +37,7 @@ module YAHL7
37
37
  release_information_code: 27,
38
38
  preadmit_certification: 28,
39
39
  verification_datetime: 29,
40
- verification_by: 30,
40
+ verification_by: { index: 30, class: YAHL7::V2::DataType::XCN },
41
41
  type_of_agreement_code: 31,
42
42
  billing_status: 32,
43
43
  lifetime_reserve_days: 33,
@@ -17,13 +17,13 @@ module YAHL7
17
17
  observation_datetime: { index: 7, class: YAHL7::V2::DataType::TS },
18
18
  observation_end_datetime: { index: 8, class: YAHL7::V2::DataType::TS },
19
19
  collection_volume: 9,
20
- collector_identifier: 10,
20
+ collector_identifier: { index: 10, class: YAHL7::V2::DataType::XCN },
21
21
  specimen_action_code: 11,
22
22
  danger_code: 12,
23
23
  relevant_clinical_information: 13,
24
24
  specimen_received_datetime: { index: 14, class: YAHL7::V2::DataType::TS },
25
25
  specimen_source: 15,
26
- ordering_provider: 16,
26
+ ordering_provider: { index: 16, class: YAHL7::V2::DataType::XCN },
27
27
  order_callback_phone_number: { index: 17, class: YAHL7::V2::DataType::XTN },
28
28
  placer_field1: 18,
29
29
  placer_field2: 19,
@@ -35,7 +35,7 @@ module YAHL7
35
35
  result_status: 25,
36
36
  parent_result: 26,
37
37
  quantity_timing: { index: 27, class: YAHL7::V2::DataType::TQ },
38
- result_copies_to: 28,
38
+ result_copies_to: { index: 28, class: YAHL7::V2::DataType::XCN },
39
39
  parent: 29,
40
40
  transportation_mode: 30,
41
41
  reason_for_study: 31,
@@ -12,8 +12,7 @@ module YAHL7
12
12
  value_type: 2,
13
13
  observation_identifier: 3,
14
14
  observation_sub_id: 4,
15
- # Skip observation_value; the logic here is
16
- # somewhat complex
15
+ observation_value: { index: 5, class: YAHL7::V2::DataType::FT },
17
16
  units: 6,
18
17
  reference_range: 7,
19
18
  abnormal_flags: 8,
@@ -24,15 +23,11 @@ module YAHL7
24
23
  user_defined_access_checks: 13,
25
24
  observation_datetime: { index: 14, class: YAHL7::V2::DataType::TS },
26
25
  producer_id: 15,
27
- responsible_observer: 16,
26
+ responsible_observer: { index: 16, class: YAHL7::V2::DataType::XCN },
28
27
  observation_method: 17,
29
28
  equipment_instance_identifier: 18,
30
29
  analysis_datetime: { index: 19, class: YAHL7::V2::DataType::TS }
31
30
  })
32
-
33
- def observation_value
34
- self[5]
35
- end
36
31
  end
37
32
  end
38
33
  end
@@ -17,16 +17,16 @@ module YAHL7
17
17
  quantity_timing: { index: 7, class: YAHL7::V2::DataType::TQ },
18
18
  parent_order: 8,
19
19
  transaction_datetime: 9,
20
- entered_by: 10,
21
- verified_by: 11,
22
- ordering_provider: 12,
20
+ entered_by: { index: 10, class: YAHL7::V2::DataType::XCN },
21
+ verified_by: { index: 11, class: YAHL7::V2::DataType::XCN },
22
+ ordering_provider: { index: 12, class: YAHL7::V2::DataType::XCN },
23
23
  enterer_location: 13,
24
24
  callback_phone_number: { index: 14, class: YAHL7::V2::DataType::XTN },
25
25
  order_effective_datetime: { index: 15, class: YAHL7::V2::DataType::TS },
26
26
  order_control_code_reason: 16,
27
27
  entering_organization: 17,
28
28
  entering_device: 18,
29
- action_by: 19,
29
+ action_by: { index: 19, class: YAHL7::V2::DataType::XCN },
30
30
  advanced_beneficiary_notice_code: 20,
31
31
  ordering_facility_name: 21,
32
32
  ordering_facility_address: { index: 22, class: YAHL7::V2::DataType::XAD },
@@ -11,7 +11,7 @@ module YAHL7
11
11
  living_dependency: 1,
12
12
  living_arrangement: 2,
13
13
  patient_primary_facility: 3,
14
- patient_primary_care_provider: 4,
14
+ patient_primary_care_provider: { index: 4, class: YAHL7::V2::DataType::XCN },
15
15
  student_indicator: 5,
16
16
  handicap: 6,
17
17
  living_will_code: 7,
@@ -14,9 +14,9 @@ module YAHL7
14
14
  admission_type: 4,
15
15
  preadmit_number: 5,
16
16
  prior_patient_location: 6,
17
- attending_doctor: 7,
18
- referring_doctor: 8,
19
- consulting_doctor: 9,
17
+ attending_doctor: { index: 7, class: YAHL7::V2::DataType::XCN },
18
+ referring_doctor: { index: 8, class: YAHL7::V2::DataType::XCN },
19
+ consulting_doctor: { index: 9, class: YAHL7::V2::DataType::XCN },
20
20
  hospital_service: 10,
21
21
  temporary_location: 11,
22
22
  preadmit_test_indicator: 12,
@@ -24,7 +24,7 @@ module YAHL7
24
24
  admit_source: 14,
25
25
  ambulatory_status: 15,
26
26
  vip_indicator: 16,
27
- admitting_doctor: 17,
27
+ admitting_doctor: { index: 17, class: YAHL7::V2::DataType::XCN },
28
28
  patient_type: 18,
29
29
  visit_number: 19,
30
30
  financial_class: 20,
@@ -59,7 +59,7 @@ module YAHL7
59
59
  total_payouts: 49,
60
60
  alternate_visit_id: 50,
61
61
  visit_indicator: 51,
62
- other_healthcare_provider: 52
62
+ other_healthcare_provider: { index: 52, class: YAHL7::V2::DataType::XCN }
63
63
  })
64
64
  end
65
65
  end
@@ -16,16 +16,22 @@ module YAHL7
16
16
  # Subsequent data access will be cached, however, so the performance hit of
17
17
  # subsequent calls should be negligable.
18
18
  class Segment
19
- attr_accessor :parts, :field_parser
19
+ attr_accessor :body, :parts, :field_parser, :parse_options
20
20
 
21
21
  def initialize(body, parse_options)
22
- @parts = body.split(parse_options.repetition_sep)
22
+ @body = body
23
+ @parts = @body.split(parse_options.repetition_sep)
23
24
  @parsed = Array.new(@parts.count)
24
25
  @field_parser = FieldParser.new(parse_options)
26
+ @parse_options = parse_options
25
27
 
26
28
  return unless defined?(FIELD_MAPPING)
27
29
  end
28
30
 
31
+ def to_s
32
+ @body
33
+ end
34
+
29
35
  # This method allows users to use an index to get a field out of a segment
30
36
  # at a given position. This allows data to be fetched from segments like
31
37
  # arrays as shown below:
data/lib/yahl7/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module YAHL7
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yahl7
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mylan Connolly
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-01 00:00:00.000000000 Z
11
+ date: 2021-09-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A simple HL7 parser that focuses on being robust and easy to use
14
14
  email:
@@ -46,6 +46,7 @@ files:
46
46
  - lib/yahl7/v2/data_type/tq.rb
47
47
  - lib/yahl7/v2/data_type/ts.rb
48
48
  - lib/yahl7/v2/data_type/xad.rb
49
+ - lib/yahl7/v2/data_type/xcn.rb
49
50
  - lib/yahl7/v2/data_type/xpn.rb
50
51
  - lib/yahl7/v2/data_type/xtn.rb
51
52
  - lib/yahl7/v2/date_time.rb