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