Class: Browserctl::Replay::FingerprintMatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/browserctl/replay/fingerprint_matcher.rb

Overview

Scores candidate snapshot entries against a recorded fingerprint and returns the best match above a configurable threshold.

Inputs are the wire-shape fingerprints emitted by Snapshot::Fingerprint:

{ text:, role:, neighbors: [...], position: { index:, depth: } }

Score is a weighted sum in [0.0, 1.0]:

text      0.40   (exact match; case-insensitive)
role      0.20   (exact match)
neighbors 0.25   (Jaccard over the neighbor sets)
position  0.15   (proximity in (index, depth) space)

Defaults reflect the v0.11 acceptance bar: text + role together (0.60) are enough to clear the default threshold, so a renamed neighbor or a shifted index doesn’t break replay.

Defined Under Namespace

Classes: Match

Constant Summary collapse

DEFAULT_THRESHOLD =
0.6
WEIGHTS =
{ text: 0.40, role: 0.20, neighbors: 0.25, position: 0.15 }.freeze

Instance Method Summary collapse

Constructor Details

#initialize(threshold: DEFAULT_THRESHOLD, weights: WEIGHTS) ⇒ FingerprintMatcher

Returns a new instance of FingerprintMatcher.



26
27
28
29
# File 'lib/browserctl/replay/fingerprint_matcher.rb', line 26

def initialize(threshold: DEFAULT_THRESHOLD, weights: WEIGHTS)
  @threshold = threshold
  @weights = weights
end

Instance Method Details

#best(target_fp, candidates) ⇒ Object

Returns the highest-scoring candidate entry above the threshold, or nil if no candidate qualifies. ‘candidates` must be an array of snapshot entries (hashes with a :fingerprint key). The returned Match wraps the candidate hash and the numeric score.



35
36
37
38
39
40
41
42
43
44
# File 'lib/browserctl/replay/fingerprint_matcher.rb', line 35

def best(target_fp, candidates)
  scored = candidates
           .map { |c| Match.new(candidate: c, score: score(target_fp, c[:fingerprint])) }
           .sort_by { |m| -m.score }

  winner = scored.first
  return nil unless winner && winner.score >= @threshold

  winner
end

#score(target, candidate) ⇒ Object



46
47
48
49
50
51
52
53
# File 'lib/browserctl/replay/fingerprint_matcher.rb', line 46

def score(target, candidate)
  return 0.0 unless target && candidate

  (@weights[:text] * text_score(target[:text], candidate[:text])) +
    (@weights[:role]      * bool_score(target[:role] == candidate[:role])) +
    (@weights[:neighbors] * jaccard(target[:neighbors], candidate[:neighbors])) +
    (@weights[:position] * position_score(target[:position], candidate[:position]))
end