yahl7 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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