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.
- data/CHANGELOG.md +16 -0
- data/Gemfile +1 -1
- data/README.md +15 -5
- data/bin/mdes-console +3 -1
- data/documents/1.2/child_or_parent_instrument_tables.yml +28 -0
- data/documents/2.0/child_or_parent_instrument_tables.yml +68 -0
- data/documents/2.1/child_or_parent_instrument_tables.yml +74 -0
- data/documents/2.2/child_or_parent_instrument_tables.yml +98 -0
- data/documents/3.0/child_or_parent_instrument_tables.yml +119 -0
- data/documents/3.1/child_or_parent_instrument_tables.yml +187 -0
- data/documents/3.2/child_or_parent_instrument_tables.yml +192 -0
- data/documents/scan_p_ids_for_children.rb +39 -0
- data/lib/ncs_navigator/mdes/code_list.rb +94 -0
- data/lib/ncs_navigator/mdes/differences/collection.rb +47 -0
- data/lib/ncs_navigator/mdes/differences/collection_criterion.rb +59 -0
- data/lib/ncs_navigator/mdes/differences/entry.rb +48 -0
- data/lib/ncs_navigator/mdes/differences/value.rb +7 -0
- data/lib/ncs_navigator/mdes/differences/value_criterion.rb +58 -0
- data/lib/ncs_navigator/mdes/differences.rb +12 -0
- data/lib/ncs_navigator/mdes/source_documents.rb +27 -0
- data/lib/ncs_navigator/mdes/specification.rb +45 -0
- data/lib/ncs_navigator/mdes/transmission_table.rb +61 -0
- data/lib/ncs_navigator/mdes/variable.rb +58 -4
- data/lib/ncs_navigator/mdes/variable_type.rb +27 -62
- data/lib/ncs_navigator/mdes/version.rb +1 -1
- data/lib/ncs_navigator/mdes.rb +5 -1
- data/spec/differences_matchers.rb +40 -0
- data/spec/ncs_navigator/mdes/code_list_spec.rb +178 -0
- data/spec/ncs_navigator/mdes/source_documents_spec.rb +14 -0
- data/spec/ncs_navigator/mdes/specification_spec.rb +30 -0
- data/spec/ncs_navigator/mdes/transmission_table_spec.rb +77 -0
- data/spec/ncs_navigator/mdes/variable_spec.rb +244 -0
- data/spec/ncs_navigator/mdes/variable_type_spec.rb +161 -40
- data/spec/spec_helper.rb +6 -0
- 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,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
|