Module: Ecoportal::API::GraphQL::Diff::Pairing::Signals
- Defined in:
- lib/ecoportal/api/graphql/diff/pairing/signals.rb
Overview
Weak pairing SIGNALS combined into a confidence score. Each signal scores a candidate
(source-field-doc, target-field-doc) pair in 0.0..1.0. NONE is ground truth on its own —
per the domain reference genomeSignature is strong-but-fallible (6 documented failure
modes: manual/no-platform-support, new features never added, wrong for select options,
wrongly syncs checklists, re-used/re-purposed fields, incomplete data-loss accounting).
So genome is ONE signal among several, weighted highest but able to be outvoted /
confirmed by type+label+options. The Engine aggregates these with WEIGHTS.
Constant Summary collapse
- WEIGHTS =
Relative weights (need not sum to 1; the Engine normalises by the weights that applied).
{ genome: 0.5, type: 0.2, label: 0.2, options: 0.1 }.freeze
Class Method Summary collapse
- .blank?(value) ⇒ Boolean
-
.genome(source, target) ⇒ Object
genomeSignature match.
-
.jaccard(list_a, list_b) ⇒ Object
Jaccard similarity of two token collections (|∩| / |∪|).
-
.label(source, target) ⇒ Object
Label similarity — exact (case/space-insensitive) = 1.0, else a token-overlap (Jaccard) score, so
Date loggedvsDate of sign-offstill gets partial credit (a re-purpose candidate the human should adjudicate rather than an auto-accept). - .normalise(value) ⇒ Object
- .option_keys(field) ⇒ Object
-
.options(source, target) ⇒ Object
Select-option overlap by VALUE (primary) / label (auxiliary) — the CORRECT option identity, never genome.
-
.type(source, target) ⇒ Object
Field type (__typename) equality.
- .type_of(field) ⇒ Object
Class Method Details
.blank?(value) ⇒ Boolean
96 97 98 |
# File 'lib/ecoportal/api/graphql/diff/pairing/signals.rb', line 96 def blank?(value) value.nil? || value.to_s.strip.empty? end |
.genome(source, target) ⇒ Object
genomeSignature match. Strong but fallible, so a MATCH scores high (not 1.0) and a MISMATCH scores 0 rather than vetoing (the field may have been re-purposed keeping its genome, or genome may be absent on newer field types). Returns nil when neither side carries a genome — the signal simply does not apply and is excluded from the average.
28 29 30 31 32 33 34 35 |
# File 'lib/ecoportal/api/graphql/diff/pairing/signals.rb', line 28 def genome(source, target) a = source['genomeSignature'] b = target['genomeSignature'] return nil if blank?(a) && blank?(b) return 0.0 if blank?(a) || blank?(b) a == b ? 1.0 : 0.0 end |
.jaccard(list_a, list_b) ⇒ Object
Jaccard similarity of two token collections (|∩| / |∪|). 0.0 when both empty.
86 87 88 89 90 91 92 93 94 |
# File 'lib/ecoportal/api/graphql/diff/pairing/signals.rb', line 86 def jaccard(list_a, list_b) set_a = list_a.to_a.uniq set_b = list_b.to_a.uniq return 0.0 if set_a.empty? && set_b.empty? inter = (set_a & set_b).size.to_f union = (set_a | set_b).size union.zero? ? 0.0 : inter / union end |
.label(source, target) ⇒ Object
Label similarity — exact (case/space-insensitive) = 1.0, else a token-overlap
(Jaccard) score, so Date logged vs Date of sign-off still gets partial credit
(a re-purpose candidate the human should adjudicate rather than an auto-accept).
50 51 52 53 54 55 56 57 |
# File 'lib/ecoportal/api/graphql/diff/pairing/signals.rb', line 50 def label(source, target) a = normalise(source['label']) b = normalise(target['label']) return nil if a.empty? && b.empty? return 1.0 if a == b && !a.empty? jaccard(a.split, b.split) end |
.normalise(value) ⇒ Object
73 74 75 |
# File 'lib/ecoportal/api/graphql/diff/pairing/signals.rb', line 73 def normalise(value) value.to_s.strip.downcase.gsub(/\s+/, ' ') end |
.option_keys(field) ⇒ Object
77 78 79 80 81 82 83 |
# File 'lib/ecoportal/api/graphql/diff/pairing/signals.rb', line 77 def option_keys(field) Array(field['options']).filter_map do |o| next unless o.is_a?(Hash) normalise(o['value'] || o['label'] || o['name']) end.reject(&:empty?) end |
.options(source, target) ⇒ Object
Select-option overlap by VALUE (primary) / label (auxiliary) — the CORRECT option identity, never genome. Returns nil for non-select fields (no options either side).
61 62 63 64 65 66 67 |
# File 'lib/ecoportal/api/graphql/diff/pairing/signals.rb', line 61 def (source, target) a = option_keys(source) b = option_keys(target) return nil if a.empty? && b.empty? jaccard(a, b) end |
.type(source, target) ⇒ Object
Field type (__typename) equality. A type change is a strong DISQUALIFIER for a data pairing (you cannot faithfully migrate PlainText data into a Gauge), so a mismatch is 0.
39 40 41 42 43 44 45 |
# File 'lib/ecoportal/api/graphql/diff/pairing/signals.rb', line 39 def type(source, target) ta = type_of(source) tb = type_of(target) return nil if ta.nil? && tb.nil? ta == tb ? 1.0 : 0.0 end |
.type_of(field) ⇒ Object
69 70 71 |
# File 'lib/ecoportal/api/graphql/diff/pairing/signals.rb', line 69 def type_of(field) field['__typename'] || field['type'] end |