ncs_mdes 0.11.0 → 0.12.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.
Files changed (35) hide show
  1. data/CHANGELOG.md +16 -0
  2. data/Gemfile +1 -1
  3. data/README.md +15 -5
  4. data/bin/mdes-console +3 -1
  5. data/documents/1.2/child_or_parent_instrument_tables.yml +28 -0
  6. data/documents/2.0/child_or_parent_instrument_tables.yml +68 -0
  7. data/documents/2.1/child_or_parent_instrument_tables.yml +74 -0
  8. data/documents/2.2/child_or_parent_instrument_tables.yml +98 -0
  9. data/documents/3.0/child_or_parent_instrument_tables.yml +119 -0
  10. data/documents/3.1/child_or_parent_instrument_tables.yml +187 -0
  11. data/documents/3.2/child_or_parent_instrument_tables.yml +192 -0
  12. data/documents/scan_p_ids_for_children.rb +39 -0
  13. data/lib/ncs_navigator/mdes/code_list.rb +94 -0
  14. data/lib/ncs_navigator/mdes/differences/collection.rb +47 -0
  15. data/lib/ncs_navigator/mdes/differences/collection_criterion.rb +59 -0
  16. data/lib/ncs_navigator/mdes/differences/entry.rb +48 -0
  17. data/lib/ncs_navigator/mdes/differences/value.rb +7 -0
  18. data/lib/ncs_navigator/mdes/differences/value_criterion.rb +58 -0
  19. data/lib/ncs_navigator/mdes/differences.rb +12 -0
  20. data/lib/ncs_navigator/mdes/source_documents.rb +27 -0
  21. data/lib/ncs_navigator/mdes/specification.rb +45 -0
  22. data/lib/ncs_navigator/mdes/transmission_table.rb +61 -0
  23. data/lib/ncs_navigator/mdes/variable.rb +58 -4
  24. data/lib/ncs_navigator/mdes/variable_type.rb +27 -62
  25. data/lib/ncs_navigator/mdes/version.rb +1 -1
  26. data/lib/ncs_navigator/mdes.rb +5 -1
  27. data/spec/differences_matchers.rb +40 -0
  28. data/spec/ncs_navigator/mdes/code_list_spec.rb +178 -0
  29. data/spec/ncs_navigator/mdes/source_documents_spec.rb +14 -0
  30. data/spec/ncs_navigator/mdes/specification_spec.rb +30 -0
  31. data/spec/ncs_navigator/mdes/transmission_table_spec.rb +77 -0
  32. data/spec/ncs_navigator/mdes/variable_spec.rb +244 -0
  33. data/spec/ncs_navigator/mdes/variable_type_spec.rb +161 -40
  34. data/spec/spec_helper.rb +6 -0
  35. metadata +24 -5
@@ -0,0 +1,192 @@
1
+ child_instrument_tables:
2
+ - birth_visit_baby_name_2
3
+ - birth_visit_baby_name_3
4
+ - birth_visit_baby_name_4
5
+ - birth_visit_li_baby_name
6
+ - birth_visit_li_baby_name_2
7
+ - bitsea_saq
8
+ - child_anthro
9
+ - child_blood
10
+ - child_bp
11
+ - child_saliva
12
+ - child_saliva_saq
13
+ - child_urine
14
+ - core_quest_child_care
15
+ - core_quest_concern
16
+ - core_quest_disability
17
+ - core_quest_emergency_room
18
+ - core_quest_health_care
19
+ - core_quest_hh
20
+ - core_quest_hospitalizations
21
+ - core_quest_housing
22
+ - core_quest_income
23
+ - core_quest_insurance
24
+ - core_quest_interim_med
25
+ - core_quest_media
26
+ - core_quest_medical
27
+ - core_quest_neighborhood
28
+ - core_quest_occupation
29
+ - core_quest_pesticide
30
+ - core_quest_pets
31
+ - core_quest_program
32
+ - core_quest_sleep
33
+ - core_quest_smoke
34
+ - core_quest_well_child_care
35
+ - eighteen_mth_mother_detail
36
+ - eighteen_mth_mother_detail_2
37
+ - eighteen_mth_mother_habits
38
+ - eighteen_mth_mother_habits_2
39
+ - eighteen_mth_mother_habits_3
40
+ - eighteen_mth_mother_mold
41
+ - eighteen_mth_mother_mold_2
42
+ - eighteen_mth_mother_mold_3
43
+ - eighteen_mth_mother_otc
44
+ - eighteen_mth_mother_otc_2
45
+ - eighteen_mth_mother_otc_3
46
+ - eighteen_mth_mother_prescr
47
+ - eighteen_mth_mother_prescr_2
48
+ - eighteen_mth_mother_prescr_3
49
+ - eighteen_mth_mother_saq
50
+ - eighteen_mth_mother_saq_2
51
+ - eighteen_mth_mother_suppl
52
+ - eighteen_mth_mother_suppl_2
53
+ - eighteen_mth_mother_suppl_3
54
+ - fourteen_mth_asq_saq
55
+ - itsp_saq
56
+ - m_chat_saq
57
+ - nine_mth_mother_detail
58
+ - nine_mth_mother_detail_2
59
+ - participant_verif_child
60
+ - reconsideration_ins
61
+ - six_mth_mother_detail
62
+ - six_mth_mother_detail_2
63
+ - six_mth_saq
64
+ - six_mth_saq_2
65
+ - six_mth_saq_3
66
+ - six_mth_saq_4
67
+ - sixteen_mth_asq_saq
68
+ - spec_cord_blood
69
+ - spec_cord_blood_2
70
+ - spec_cord_blood_3
71
+ - thirty_month_interview_child
72
+ - thirty_mth_asq_saq
73
+ - three_mth_mother_child_detail
74
+ - three_mth_mother_child_detail_2
75
+ - three_mth_mother_child_habits
76
+ - three_mth_mother_child_habits_2
77
+ - three_mth_mother_child_habits_3
78
+ - twelve_mth_mother_detail
79
+ - twelve_mth_mother_detail_2
80
+ - twelve_mth_mother_detail_3
81
+ - twelve_mth_mother_mold_3
82
+ - twenty_four_mth_mother_detail
83
+ - twenty_four_mth_mother_detail_2
84
+ - twenty_four_mth_mother_habits_2
85
+ - twenty_four_mth_mother_habits_3
86
+ - twenty_four_mth_mother_mold
87
+ - twenty_four_mth_mother_mold_2
88
+ - twenty_four_mth_mother_otc_2
89
+ - twenty_four_mth_mother_otc_3
90
+ - twenty_four_mth_mother_prescr_2
91
+ - twenty_four_mth_mother_prescr_3
92
+ - twenty_four_mth_mother_suppl_2
93
+ - twenty_four_mth_mother_suppl_3
94
+ - twenty_four_mth_saq
95
+ - twenty_four_mth_saq_2
96
+ - twenty_mth_asq_saq
97
+ - twenty_seven_mth_asq_saq
98
+ - twenty_two_mth_asq_saq
99
+ parent_instrument_tables:
100
+ - birth_visit
101
+ - birth_visit_2
102
+ - birth_visit_3
103
+ - birth_visit_4
104
+ - birth_visit_diagnose_2_3
105
+ - birth_visit_diagnose_2_4
106
+ - birth_visit_household_3
107
+ - birth_visit_household_4
108
+ - birth_visit_li
109
+ - birth_visit_li_2
110
+ - breast_milk_saq
111
+ - bsi_saq
112
+ - eighteen_mth_mother
113
+ - eighteen_mth_mother_2
114
+ - eighteen_mth_mother_3
115
+ - father_pv1
116
+ - father_pv1_2
117
+ - household_enumeration
118
+ - household_enumeration_age_elig
119
+ - household_enumeration_pregnant
120
+ - household_inventory
121
+ - household_inventory_age
122
+ - household_inventory_age_elig
123
+ - internet_usage
124
+ - low_high_script
125
+ - multi_mode
126
+ - nine_mth_mother
127
+ - nine_mth_mother_2
128
+ - non_interview_respondent
129
+ - participant_verif
130
+ - pbs_elig_screener
131
+ - pbs_participant_verif
132
+ - ppg_cati
133
+ - ppg_saq
134
+ - pre_preg
135
+ - pre_preg_saq
136
+ - preg_screen_eh
137
+ - preg_screen_eh_2
138
+ - preg_screen_hi
139
+ - preg_screen_hi_2
140
+ - preg_screen_pb
141
+ - preg_screen_pb_2
142
+ - preg_visit_1
143
+ - preg_visit_1_2
144
+ - preg_visit_1_3
145
+ - preg_visit_1_saq
146
+ - preg_visit_1_saq_2
147
+ - preg_visit_1_saq_3
148
+ - preg_visit_1_saq_4
149
+ - preg_visit_2
150
+ - preg_visit_2_2
151
+ - preg_visit_2_3
152
+ - preg_visit_2_saq
153
+ - preg_visit_2_saq_2
154
+ - preg_visit_2_saq_3
155
+ - preg_visit_2_saq_4
156
+ - preg_visit_li
157
+ - preg_visit_li_2
158
+ - sample_dist
159
+ - six_mth_mother
160
+ - six_mth_mother_2
161
+ - spec_blood
162
+ - spec_blood_2
163
+ - spec_urine
164
+ - tap_water_twf
165
+ - tap_water_twf_2
166
+ - tap_water_twf_saq
167
+ - tap_water_twq
168
+ - tap_water_twq_2
169
+ - tap_water_twq_saq
170
+ - thirty_month_interview
171
+ - three_mth_mother
172
+ - three_mth_mother_2
173
+ - three_mth_mother_3
174
+ - tracing_int
175
+ - twelve_mth_mother
176
+ - twelve_mth_mother_2
177
+ - twelve_mth_mother_3
178
+ - twelve_mth_saq
179
+ - twelve_mth_saq_2
180
+ - twelve_mth_saq_3
181
+ - twenty_four_mth_mother
182
+ - twenty_four_mth_mother_2
183
+ - twenty_four_mth_mother_3
184
+ - twenty_four_mth_mother_habits
185
+ - twenty_four_mth_mother_otc
186
+ - twenty_four_mth_mother_prescr
187
+ - twenty_four_mth_mother_suppl
188
+ - vacuum_bag
189
+ - vacuum_bag_2
190
+ - vacuum_bag_saq
191
+ - validation_ins
192
+ - validation_ins_2
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ##
4
+ # A script which scans an MDES spreadsheet for indications that an instrument
5
+ # table's p_id is for a child (vs. for a parent.)
6
+ #
7
+ # This script requires the 'roo' gem, which is not included in
8
+ # ncs_mdes's gemspec because it has a huge number of dependencies and
9
+ # is not needed at runtime.
10
+
11
+ require 'roo'
12
+ require 'yaml'
13
+
14
+ MDES_XLSX = ARGV.first or fail 'Please provide the path to the MDES spreadsheet'
15
+ SHEET_NAME = 'Data Elements'
16
+
17
+ COLUMNS = {
18
+ 'A' => :table_type,
19
+ 'B' => :table_label,
20
+ 'C' => :table_name,
21
+ 'D' => :variable_name,
22
+ 'I' => :variable_def
23
+ }
24
+
25
+ book = Excelx.new(MDES_XLSX)
26
+
27
+ 3.upto(book.last_row(SHEET_NAME)) do |row_number|
28
+ row = COLUMNS.keys.each_with_object({}) { |col, i| i[COLUMNS[col]] = book.cell(row_number, col, SHEET_NAME) }
29
+ next unless row[:table_type] =~ /instrument/i
30
+ next unless row[:variable_name] =~ /\Ap_id\Z/i
31
+
32
+ puts "#{row[:table_name].downcase}.#{row[:variable_name].downcase}"
33
+ if row[:variable_def] =~ /child/i
34
+ puts "- mentions child in variable def"
35
+ end
36
+ if row[:table_label] =~ /child/i
37
+ puts "- mentions child in table label"
38
+ end
39
+ end
@@ -0,0 +1,94 @@
1
+ require 'ncs_navigator/mdes'
2
+
3
+ module NcsNavigator::Mdes
4
+ ##
5
+ # A specialization of `Array` for code lists.
6
+ #
7
+ # @see VariableType#code_list
8
+ # @see CodeListEntry
9
+ class CodeList < Array
10
+ ##
11
+ # @return [String,nil] the description of the code list if any.
12
+ attr_accessor :description
13
+ end
14
+
15
+ ##
16
+ # A single entry in a code list.
17
+ #
18
+ # @see VariableType#code_list
19
+ # @see CodeList
20
+ class CodeListEntry
21
+ ##
22
+ # @return [String] the local code value for the entry.
23
+ attr_reader :value
24
+
25
+ ##
26
+ # @return [String] the human-readable label for the entry.
27
+ attr_accessor :label
28
+
29
+ ##
30
+ # @return [String] the MDES's globally-unique identifier for
31
+ # this coded value.
32
+ attr_accessor :global_value
33
+
34
+ ##
35
+ # @return [String] the name of MDES's master code list from
36
+ # which this value is derived.
37
+ attr_accessor :master_cl
38
+
39
+ class << self
40
+ ##
41
+ # Creates a new instance from a `xs:enumeration` simple type
42
+ # restriction subelement.
43
+ #
44
+ # @param [Nokogiri::XML::Element] enum the `xs:enumeration`
45
+ # element.
46
+ # @param [Hash] options
47
+ # @option options [#warn] :log the logger to which to direct warnings
48
+ #
49
+ # @return [CodeListEntry]
50
+ def from_xsd_enumeration(enum, options={})
51
+ log = options[:log] || NcsNavigator::Mdes.default_logger
52
+
53
+ log.warn("Missing value for code list entry on line #{enum.line}") unless enum['value']
54
+
55
+ new(enum['value'] && enum['value'].strip).tap do |cle|
56
+ cle.label = enum['ncsdoc:label']
57
+ cle.global_value = enum['ncsdoc:global_value']
58
+ cle.master_cl = enum['ncsdoc:master_cl']
59
+ end
60
+ end
61
+ end
62
+
63
+ def initialize(value)
64
+ @value = value
65
+ end
66
+
67
+ alias :to_s :value
68
+
69
+ def diff_criteria(diff_options)
70
+ if diff_options[:strict]
71
+ {
72
+ :value => Differences::ValueCriterion.new,
73
+ :label => Differences::ValueCriterion.new,
74
+ :global_value => Differences::ValueCriterion.new,
75
+ :master_cl => Differences::ValueCriterion.new
76
+ }
77
+ else
78
+ {
79
+ :value => Differences::ValueCriterion.new,
80
+ :label => Differences::ValueCriterion.new(:value_extractor => :word_chars_downcase)
81
+ }
82
+ end
83
+ end
84
+ protected :diff_criteria
85
+
86
+ ##
87
+ # Computes the differences between this code list entry and the other.
88
+ #
89
+ # @return [Differences::Entry,nil]
90
+ def diff(other, options={})
91
+ Differences::Entry.compute(self, other, diff_criteria(options), options)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,47 @@
1
+ require 'ncs_navigator/mdes'
2
+ require 'forwardable'
3
+
4
+ module NcsNavigator::Mdes::Differences
5
+ ##
6
+ # Captures the differences between two collections.
7
+ class Collection
8
+ extend Forwardable
9
+
10
+ def_delegators :entry_differences, :[]
11
+
12
+ def initialize(left_only, right_only, entry_differences)
13
+ @left_only = left_only
14
+ @right_only = right_only
15
+ @entry_differences = entry_differences
16
+ end
17
+
18
+ ##
19
+ # A list of those entries which are in the lefthand version of the
20
+ # collection only. Values are the characteristic (alignment) value for each
21
+ # entry.
22
+ #
23
+ # @return [Array<Object>]
24
+ def left_only
25
+ @left_only ||= []
26
+ end
27
+
28
+ ##
29
+ # A list of those entries which are in the righthand version of the
30
+ # collection only. Values are the characteristic (alignment) value for each
31
+ # entry.
32
+ #
33
+ # @return [Array<Object>]
34
+ def right_only
35
+ @right_only ||= []
36
+ end
37
+
38
+ ##
39
+ # Detailed differences for entries which are present in some form in each
40
+ # collection. Keys are the characteristic (alignment) value for the entry.
41
+ #
42
+ # @return [Hash<Object, Entry>]
43
+ def entry_differences
44
+ @entry_differences ||= {}
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,59 @@
1
+ require 'ncs_navigator/mdes'
2
+
3
+ module NcsNavigator::Mdes::Differences
4
+ ##
5
+ # @private implementation detail
6
+ class CollectionCriterion
7
+ ##
8
+ # @return [Symbol] the attribute in the object to which this criterion applies
9
+ attr_reader :attribute
10
+
11
+ def initialize(alignment_attribute, options={})
12
+ @alignment_attribute = alignment_attribute
13
+ @attribute = options.delete(:collection)
14
+ @alignment_value_extractor =
15
+ select_value_extractor(options.delete(:value_extractor))
16
+ end
17
+
18
+ def apply(c1, c2, diff_options)
19
+ c1_map = map_for_alignment(c1)
20
+ c2_map = map_for_alignment(c2)
21
+
22
+ left_only = c1_map.keys - c2_map.keys
23
+ right_only = c2_map.keys - c1_map.keys
24
+
25
+ both = c1_map.keys & c2_map.keys
26
+ entry_differences = both.each_with_object({}) do |key, differences|
27
+ diff = c1_map[key].diff(c2_map[key], diff_options)
28
+ differences[key] = diff if diff
29
+ end
30
+
31
+ if left_only.empty? && right_only.empty? && entry_differences.empty?
32
+ nil
33
+ else
34
+ Collection.new(left_only, right_only, entry_differences)
35
+ end
36
+ end
37
+
38
+ def map_for_alignment(c)
39
+ return {} unless c
40
+ c.each_with_object({}) do |element, map|
41
+ value = @alignment_value_extractor.call(element.send(@alignment_attribute))
42
+ map[value] = element
43
+ end
44
+ end
45
+ private :map_for_alignment
46
+
47
+ def select_value_extractor(param)
48
+ case param
49
+ when nil
50
+ ValueCriterion::VALUE_EXTRACTORS[:identity]
51
+ when Symbol
52
+ ValueCriterion::VALUE_EXTRACTORS[param] or fail "Unknown extractor #{param.inspect}"
53
+ else
54
+ param
55
+ end
56
+ end
57
+ private :select_value_extractor
58
+ end
59
+ end
@@ -0,0 +1,48 @@
1
+ require 'ncs_navigator/mdes'
2
+ require 'forwardable'
3
+
4
+ module NcsNavigator::Mdes::Differences
5
+ ##
6
+ # Captures the differences between two instances of one of the elements that
7
+ # makes up a specification. I.e., {TransmissionTable}, {Variable},
8
+ # {VariableType}, or {CodeListEntry}.
9
+ class Entry
10
+ ##
11
+ # @param [Object] o1 the left object
12
+ # @param [Object] o2 the right object
13
+ # @param [Hash<Symbol, #apply>] attribute_criteria a list of objects which produce difference objects
14
+ # @param [Hash] diff_options options to pass to nested calls to #diff
15
+ # @return [Entry, nil] the differences between o1 and o2 according to the
16
+ # criteria, or nil if there are no differences.
17
+ def self.compute(o1, o2, attribute_criteria, diff_options)
18
+ differences = attribute_criteria.each_with_object({}) do |(diff_attribute, criterion), diffs|
19
+ o_attribute = (criterion.respond_to?(:attribute) && criterion.attribute) || diff_attribute
20
+ d = criterion.apply(o1.send(o_attribute), o2.send(o_attribute), diff_options)
21
+ diffs[diff_attribute] = d if d
22
+ end
23
+
24
+ if differences.empty?
25
+ nil
26
+ else
27
+ Entry.new.tap { |e| e.attribute_differences = differences }
28
+ end
29
+ end
30
+
31
+ extend Forwardable
32
+
33
+ def_delegators :attribute_differences, :[]
34
+
35
+ ##
36
+ # Return the differences for each attribute. Each key is the name of the
37
+ # attribute and each value is an object describing the difference. It might
38
+ # be a {Value} diff, a {Collection} diff, or another {Entry} diff depending
39
+ # on the kind of attribute.
40
+ #
41
+ # @return [Hash<Symbol, Object>]
42
+ def attribute_differences
43
+ @attribute_differences ||= {}
44
+ end
45
+
46
+ attr_writer :attribute_differences
47
+ end
48
+ end
@@ -0,0 +1,7 @@
1
+ require 'ncs_navigator/mdes'
2
+
3
+ module NcsNavigator::Mdes::Differences
4
+ ##
5
+ # Captures the differences between a simple scalar value.
6
+ Value = Struct.new(:left, :right)
7
+ end
@@ -0,0 +1,58 @@
1
+ require 'ncs_navigator/mdes'
2
+
3
+ module NcsNavigator::Mdes::Differences
4
+ ##
5
+ # @private implementation detail
6
+ class ValueCriterion
7
+ COMPARATORS = {
8
+ :equality => lambda { |a, b| a == b },
9
+ :predicate => lambda { |a, b| !(a ^ b) }
10
+ }
11
+
12
+ VALUE_EXTRACTORS = {
13
+ :identity => lambda { |o| o },
14
+ :word_chars_downcase =>
15
+ lambda { |o| o ? o.downcase.gsub(/[^ \w]+/, ' ').gsub(/\s+/, ' ').strip : o }
16
+ }
17
+
18
+ attr_reader :comparator, :value_extractor
19
+
20
+ def initialize(options={})
21
+ @comparator = select_comparator(options.delete(:comparator))
22
+ @value_extractor = select_value_extractor(options.delete(:value_extractor))
23
+ end
24
+
25
+ def apply(v1, v2, diff_options)
26
+ cv1 = value_extractor.call(v1)
27
+ cv2 = value_extractor.call(v2)
28
+
29
+ unless comparator.call(cv1, cv2)
30
+ Value.new(cv1, cv2)
31
+ end
32
+ end
33
+
34
+ def select_comparator(param)
35
+ case param
36
+ when nil
37
+ COMPARATORS[:equality]
38
+ when Symbol
39
+ COMPARATORS[param] or fail "Unknown comparator #{param.inspect}"
40
+ else
41
+ param
42
+ end
43
+ end
44
+ private :select_comparator
45
+
46
+ def select_value_extractor(param)
47
+ case param
48
+ when nil
49
+ VALUE_EXTRACTORS[:identity]
50
+ when Symbol
51
+ VALUE_EXTRACTORS[param] or fail "Unknown extractor #{param.inspect}"
52
+ else
53
+ param
54
+ end
55
+ end
56
+ private :select_value_extractor
57
+ end
58
+ end
@@ -0,0 +1,12 @@
1
+ require 'ncs_navigator/mdes'
2
+
3
+ module NcsNavigator::Mdes
4
+ module Differences
5
+ autoload :Entry, 'ncs_navigator/mdes/differences/entry'
6
+ autoload :Collection, 'ncs_navigator/mdes/differences/collection'
7
+ autoload :Value, 'ncs_navigator/mdes/differences/value'
8
+
9
+ autoload :CollectionCriterion, 'ncs_navigator/mdes/differences/collection_criterion'
10
+ autoload :ValueCriterion, 'ncs_navigator/mdes/differences/value_criterion'
11
+ end
12
+ end
@@ -72,6 +72,7 @@ module NcsNavigator::Mdes
72
72
  sd.schema = schema
73
73
  sd.heuristic_overrides = "#{version}/heuristic_overrides.yml"
74
74
  sd.disposition_codes = "#{version}/disposition_codes.yml"
75
+ sd.child_or_parent_instrument_tables = "#{version}/child_or_parent_instrument_tables.yml"
75
76
  sd.specification_version = specification_version
76
77
  end
77
78
  end
@@ -171,6 +172,32 @@ module NcsNavigator::Mdes
171
172
  @disposition_codes = path
172
173
  end
173
174
 
175
+ ##
176
+ # The absolute path to a YAML-formatted document defining a hash with two
177
+ # keys: `child_instrument_tables` and `parent_instrument_tables`. The value
178
+ # for each should be a list of MDES table names (lower case) which are in
179
+ # that category.
180
+ #
181
+ # This is path is optional; if one is not provided
182
+ # {TransmissionTable#child_instrument_table?} and
183
+ # {TransmissionTable#parent_instrument_table?} will be nil for all tables.
184
+ #
185
+ # @return [String]
186
+ def child_or_parent_instrument_tables
187
+ absolutize(@child_or_parent_instrument_tables)
188
+ end
189
+
190
+ ##
191
+ # Set the path to the child-or-parent instrument tables document.
192
+ # If the path is relative (i.e., it does not begin with `/`), it
193
+ # will be interpreted relative to {#base}.
194
+ #
195
+ # @param [String] path
196
+ # @return [String] the provided path
197
+ def child_or_parent_instrument_tables=(path)
198
+ @child_or_parent_instrument_tables = path
199
+ end
200
+
174
201
  private
175
202
 
176
203
  def absolutize(path)
@@ -94,10 +94,44 @@ module NcsNavigator::Mdes
94
94
  v.resolve_foreign_key!(tables, fk_overrides[v.name], :log => @log, :in_table => t)
95
95
  }
96
96
  }
97
+ tables.each { |t| t.child_instrument_table = lookup_child_or_parent_table_status(t.name) }
97
98
  }
98
99
  end
99
100
  private :read_transmission_tables
100
101
 
102
+ def lookup_child_or_parent_table_status(name)
103
+ if child_or_parent_instrument_tables['child_instrument_tables'].include?(name)
104
+ true
105
+ elsif child_or_parent_instrument_tables['parent_instrument_tables'].include?(name)
106
+ false
107
+ else
108
+ nil
109
+ end
110
+ end
111
+ private :lookup_child_or_parent_table_status
112
+
113
+ def child_or_parent_instrument_tables
114
+ @child_or_parent_instrument_tables ||=
115
+ if source_documents.child_or_parent_instrument_tables
116
+ YAML.load(File.read(source_documents.child_or_parent_instrument_tables)).tap do |result|
117
+ check_child_or_parent_lists(result)
118
+ end
119
+ else
120
+ Hash.new([])
121
+ end
122
+ end
123
+ private :child_or_parent_instrument_tables
124
+
125
+ def check_child_or_parent_lists(lists)
126
+ parent_list = lists['parent_instrument_tables']
127
+ child_list = lists['child_instrument_tables']
128
+ overlap = parent_list & child_list
129
+ unless overlap.empty?
130
+ @log.warn("These tables appear in both the child instrument and parent instrument lists: #{overlap.inspect}")
131
+ end
132
+ end
133
+ private :check_child_or_parent_lists
134
+
101
135
  ##
102
136
  # A shortcut for accessing particular {#transmission_tables}.
103
137
  #
@@ -163,5 +197,16 @@ module NcsNavigator::Mdes
163
197
  def inspect
164
198
  "#<#{self.class} version=#{version.inspect}>"
165
199
  end
200
+
201
+ # @private
202
+ DIFF_CRITERIA = {
203
+ :specification_version => Differences::ValueCriterion.new,
204
+ :transmission_tables => Differences::CollectionCriterion.new(:name),
205
+ :types => Differences::CollectionCriterion.new(:name)
206
+ }
207
+
208
+ def diff(other, options={})
209
+ Differences::Entry.compute(self, other, DIFF_CRITERIA, options)
210
+ end
166
211
  end
167
212
  end