Module: ClaudeMemory::Dashboard::Efficacy::Reporter
- Defined in:
- lib/claude_memory/dashboard/efficacy.rb
Overview
Pure report calculator for recall activity events. Takes a list of already-loaded events and produces the shaped metrics the dashboard renders. No I/O, no database access — separating compute from loading keeps the aggregation fast-testable and portable across event sources.
Expected event shape (matches ActivityLog.recent output):
{
id:, event_type:, status:, duration_ms:, session_id:,
occurred_at:, details: {tool:, query:, result_count:,
results_by_scope: {"project" => N, "global" => M}, ...}
}
Constant Summary collapse
- RECALL_TRACE_LIMIT =
50- MEMORY_GAPS_LIMIT =
10
Class Method Summary collapse
-
.median(values) ⇒ Object
Sorted median — returns 0 for empty input, midpoint average for even counts.
- .memory_gaps(events) ⇒ Object
-
.percentage(part, whole) ⇒ Object
Percentage with zero-safe denominator, rounded to 1 decimal.
- .recall_trace(events) ⇒ Object
-
.report(events, timeframe: {}) ⇒ Hash
The efficacy payload.
-
.source_contribution(events) ⇒ Object
Aggregate results_by_scope across events.
- .tool_mix(events) ⇒ Object
Class Method Details
.median(values) ⇒ Object
Sorted median — returns 0 for empty input, midpoint average for even counts.
57 58 59 60 61 62 63 64 65 66 |
# File 'lib/claude_memory/dashboard/efficacy.rb', line 57 def median(values) return 0 if values.empty? sorted = values.sort mid = sorted.size / 2 if sorted.size.odd? sorted[mid] else ((sorted[mid - 1] + sorted[mid]) / 2.0).round(1) end end |
.memory_gaps(events) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/claude_memory/dashboard/efficacy.rb', line 95 def memory_gaps(events) events .select { |e| (e.dig(:details, :result_count) || 0).zero? && e.dig(:details, :query) } .first(MEMORY_GAPS_LIMIT) .map { |e| { tool: e.dig(:details, :tool), query: e.dig(:details, :query), occurred_at: e[:occurred_at], occurred_ago: Core::RelativeTime.format(e[:occurred_at]) } } end |
.percentage(part, whole) ⇒ Object
Percentage with zero-safe denominator, rounded to 1 decimal.
51 52 53 54 |
# File 'lib/claude_memory/dashboard/efficacy.rb', line 51 def percentage(part, whole) return 0 if whole.to_i.zero? (part.to_f / whole * 100).round(1) end |
.recall_trace(events) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/claude_memory/dashboard/efficacy.rb', line 109 def recall_trace(events) events.first(RECALL_TRACE_LIMIT).map { |e| { id: e[:id], tool: e.dig(:details, :tool), query: e.dig(:details, :query), result_count: e.dig(:details, :result_count) || 0, duration_ms: e[:duration_ms], session_id: e[:session_id], status: e[:status], occurred_at: e[:occurred_at], occurred_ago: Core::RelativeTime.format(e[:occurred_at]) } } end |
.report(events, timeframe: {}) ⇒ Hash
Returns the efficacy payload.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/claude_memory/dashboard/efficacy.rb', line 28 def report(events, timeframe: {}) result_counts = events.map { |e| e.dig(:details, :result_count) || 0 } latencies = events.map { |e| e[:duration_ms] }.compact successful = events.count { |e| e[:status] == "success" && (e.dig(:details, :result_count) || 0) > 0 } empty = events.count { |e| e[:status] == "success" && (e.dig(:details, :result_count) || 0) == 0 } { timeframe: {since: timeframe[:since], session_id: timeframe[:session_id]}, recall_events: events.size, successful_recalls: successful, empty_recalls: empty, hit_rate: percentage(successful, events.size), total_results_served: result_counts.sum, median_results_per_query: median(result_counts), median_latency_ms: median(latencies), tool_mix: tool_mix(events), source_contribution: source_contribution(events), memory_gaps: memory_gaps(events), recall_trace: recall_trace(events) } end |
.source_contribution(events) ⇒ Object
Aggregate results_by_scope across events. Reveals where returned facts actually came from — the one question only efficacy can answer.
85 86 87 88 89 90 91 92 93 |
# File 'lib/claude_memory/dashboard/efficacy.rb', line 85 def source_contribution(events) totals = Hash.new(0) events.each do |e| by_scope = e.dig(:details, :results_by_scope) next unless by_scope.is_a?(Hash) by_scope.each { |scope, n| totals[scope.to_s] += n.to_i } end totals.empty? ? [] : totals.map { |scope, count| {scope: scope, count: count} }.sort_by { |r| -r[:count] } end |
.tool_mix(events) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/claude_memory/dashboard/efficacy.rb', line 68 def tool_mix(events) events .group_by { |e| e.dig(:details, :tool) || "(unknown)" } .map { |tool, rows| hits = rows.count { |r| (r.dig(:details, :result_count) || 0) > 0 } { tool: tool, count: rows.size, hits: hits, hit_rate: percentage(hits, rows.size) } } .sort_by { |row| -row[:count] } end |