Class: ClaudeMemory::Core::FactRanker

Inherits:
Object
  • Object
show all
Defined in:
lib/claude_memory/core/fact_ranker.rb

Overview

Pure business logic for ranking, sorting, and deduplicating facts Follows Functional Core pattern (Gary Bernhardt) - no I/O, just transformations

Class Method Summary collapse

Class Method Details

.dedupe_and_sort(results, limit) ⇒ Array<Hash>

Deduplicate full fact results by signature and sort by source + creation time

Parameters:

  • results (Array<Hash>)

    Results with :fact hash containing fact data, :source symbol

  • limit (Integer)

    Maximum results to return

Returns:

  • (Array<Hash>)

    Deduplicated and sorted results



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/claude_memory/core/fact_ranker.rb', line 35

def self.dedupe_and_sort(results, limit)
  seen_signatures = Set.new
  unique_results = []

  results.each do |result|
    fact = result[:fact]
    sig = "#{fact[:subject_name]}:#{fact[:predicate]}:#{fact[:object_literal]}"
    next if seen_signatures.include?(sig)

    seen_signatures.add(sig)
    unique_results << result
  end

  unique_results.sort_by do |item|
    source_priority = (item[:source] == :project) ? 0 : 1
    [source_priority, item[:fact][:created_at]]
  end.first(limit)
end

.dedupe_and_sort_index(results, limit) ⇒ Array<Hash>

Deduplicate index results by fact signature and sort by source priority

Parameters:

  • results (Array<Hash>)

    Results with :subject, :predicate, :object_preview, :source

  • limit (Integer)

    Maximum results to return

Returns:

  • (Array<Hash>)

    Deduplicated and sorted results



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/claude_memory/core/fact_ranker.rb', line 12

def self.dedupe_and_sort_index(results, limit)
  seen_signatures = Set.new
  unique_results = []

  results.each do |result|
    sig = "#{result[:subject]}:#{result[:predicate]}:#{result[:object_preview]}"
    next if seen_signatures.include?(sig)

    seen_signatures.add(sig)
    unique_results << result
  end

  # Sort by source priority (project first)
  unique_results.sort_by do |item|
    source_priority = (item[:source] == :project) ? 0 : 1
    [source_priority]
  end.first(limit)
end

.dedupe_by_fact_id(results, limit) ⇒ Array<Hash>

Deduplicate semantic search results by fact_id, keeping highest similarity

Parameters:

  • results (Array<Hash>)

    Results with :fact hash containing :id and :similarity score

  • limit (Integer)

    Maximum results to return

Returns:

  • (Array<Hash>)

    Deduplicated results sorted by similarity descending



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/claude_memory/core/fact_ranker.rb', line 72

def self.dedupe_by_fact_id(results, limit)
  seen = {}

  results.each do |result|
    fact_id = result[:fact][:id]
    # Keep the result with highest similarity for each fact
    if !seen[fact_id] || seen[fact_id][:similarity] < result[:similarity]
      seen[fact_id] = result
    end
  end

  seen.values.sort_by { |r| -r[:similarity] }.take(limit)
end

.merge_search_results(vector_results, text_results, limit, explain: false) ⇒ Array<Hash>

Merge vector and text search results using Reciprocal Rank Fusion

Parameters:

  • vector_results (Array<Hash>)

    Results from vector search with :fact and :similarity

  • text_results (Array<Hash>)

    Results from text search with :fact and :similarity

  • limit (Integer)

    Maximum results to return

Returns:

  • (Array<Hash>)

    Merged results sorted by RRF score descending



91
92
93
# File 'lib/claude_memory/core/fact_ranker.rb', line 91

def self.merge_search_results(vector_results, text_results, limit, explain: false)
  RRFusion.fuse(vector_results, text_results, limit, explain: explain)
end

.sort_by_scope_priority(facts_with_provenance, project_path) ⇒ Array<Hash>

Sort facts by scope priority: current project > global > other projects

Parameters:

  • facts_with_provenance (Array<Hash>)

    Facts with :fact hash containing scope and project_path

  • project_path (String)

    Current project path for comparison

Returns:

  • (Array<Hash>)

    Sorted facts



58
59
60
61
62
63
64
65
66
# File 'lib/claude_memory/core/fact_ranker.rb', line 58

def self.sort_by_scope_priority(facts_with_provenance, project_path)
  facts_with_provenance.sort_by do |item|
    fact = item[:fact]
    is_current_project = fact[:project_path] == project_path
    is_global = fact[:scope] == "global"

    [is_current_project ? 0 : 1, is_global ? 0 : 1]
  end
end