Class: Ecoportal::API::GraphQL::Diff::Pairing::Ledger

Inherits:
Object
  • Object
show all
Defined in:
lib/ecoportal/api/graphql/diff/pairing/ledger.rb

Overview

The LEARNING LEDGER — a first-class artifact that records CONFIRMED equivalences between objects that live in different id-spaces (UAT<->PROD, page<->template). Because MongoDB gives every object a distinct id and nothing is shared across counterparts, pairing is an equivalence / entity-resolution problem, not a lookup. Every decision the engine (or a human) makes is persisted here so pairing IMPROVES over time: on the next run the ledger is consulted FIRST and previously-resolved pairs auto-resolve, leaving only genuine novelty for the human to adjudicate.

An entry is keyed by (kind, source_id) and records the paired target_id plus HOW it was resolved (the method, the confidence, the signals, a timestamp). This log of how is the bridge data Product's Field-ID / template-entity-id effort needs.

ledger = Ledger.load('pairings.json') # or Ledger.new (in-memory) ledger.record(kind: :field, source_id: 'a', target_id: 'b', method: :genome, confidence: 0.98, signals: ...) ledger.lookup(:field, 'a') # => Entry or nil ledger.save # persist back to the same path

SAFETY — the ledger stores only decisions that were CONFIRMED (auto-accepted at high confidence, or human-adjudicated). Ambiguous/low-confidence candidates are never written; they are escalated. The ledger is thus a growing store of ground truth, never guesses.

Defined Under Namespace

Classes: Entry

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(entries: [], path: nil) ⇒ Ledger

Returns a new instance of Ledger.

Parameters:

  • entries (Array<Entry,Hash>) (defaults to: [])

    existing entries (e.g. from a loaded file).

  • path (String, nil) (defaults to: nil)

    where #save writes; defaults to the load path.



43
44
45
46
47
# File 'lib/ecoportal/api/graphql/diff/pairing/ledger.rb', line 43

def initialize(entries: [], path: nil)
  @path    = path
  @entries = {}
  Array(entries).each { |e| add_entry(coerce(e)) }
end

Class Method Details

.load(path) ⇒ Object

Load a ledger from a JSON file. A missing file yields an empty (still writable) ledger.



50
51
52
53
# File 'lib/ecoportal/api/graphql/diff/pairing/ledger.rb', line 50

def self.load(path)
  data = File.exist?(path) ? JSON.parse(File.read(path)) : {}
  new(entries: data['entries'] || [], path: path)
end

Instance Method Details

#entriesObject



78
79
80
# File 'lib/ecoportal/api/graphql/diff/pairing/ledger.rb', line 78

def entries
  @entries.values
end

#lookup(kind, source_id) ⇒ Object

The confirmed Entry for (kind, source_id), or nil if never resolved.



69
70
71
# File 'lib/ecoportal/api/graphql/diff/pairing/ledger.rb', line 69

def lookup(kind, source_id)
  @entries[[kind.to_sym, source_id]]
end

#record(kind:, source_id:, target_id:, matched_by:, confidence: nil, signals: nil) ⇒ Object

Record a CONFIRMED equivalence. matched_by names how it was resolved (:genome, :label, :ledger, :human, ...). Later records for the same (kind, source_id) supersede earlier ones (a human can correct an auto-accept). Returns the stored Entry.



58
59
60
61
62
63
64
65
66
# File 'lib/ecoportal/api/graphql/diff/pairing/ledger.rb', line 58

def record(kind:, source_id:, target_id:, matched_by:, confidence: nil, signals: nil)
  entry = Entry.new(
    kind: kind.to_sym, source_id: source_id, target_id: target_id,
    matched_by: matched_by&.to_sym, confidence: confidence, signals: signals,
    recorded_at: Time.now.utc.iso8601
  )
  add_entry(entry)
  entry
end

#save(path = @path) ⇒ Object

Persist to path (or the load path). Returns the path written.

Raises:

  • (ArgumentError)


91
92
93
94
95
96
# File 'lib/ecoportal/api/graphql/diff/pairing/ledger.rb', line 91

def save(path = @path)
  raise ArgumentError, 'no path to save the ledger to' if path.nil?

  File.write(path, JSON.pretty_generate(to_h))
  path
end

#sizeObject



82
83
84
# File 'lib/ecoportal/api/graphql/diff/pairing/ledger.rb', line 82

def size
  @entries.size
end

#target_for(kind, source_id) ⇒ Object

The confirmed target id for (kind, source_id), or nil.



74
75
76
# File 'lib/ecoportal/api/graphql/diff/pairing/ledger.rb', line 74

def target_for(kind, source_id)
  lookup(kind, source_id)&.target_id
end

#to_hObject



86
87
88
# File 'lib/ecoportal/api/graphql/diff/pairing/ledger.rb', line 86

def to_h
  { entries: entries.map(&:to_h) }
end