Class: Woods::Feedback::Store

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/feedback/store.rb

Overview

Append-only JSONL file for retrieval feedback: ratings and gap reports.

Each line is a JSON object with a ‘type` field (“rating” or “gap”) plus type-specific fields.

Examples:

store = Store.new(path: '/tmp/feedback.jsonl')
store.record_rating(query: "How does User work?", score: 4)
store.record_gap(query: "payments", missing_unit: "PaymentService", unit_type: "service")
store.average_score  # => 4.0

Instance Method Summary collapse

Constructor Details

#initialize(path:) ⇒ Store

Returns a new instance of Store.

Parameters:

  • path (String)

    Path to the JSONL file



21
22
23
# File 'lib/woods/feedback/store.rb', line 21

def initialize(path:)
  @path = path
end

Instance Method Details

#all_entries(limit: nil) ⇒ Array<Hash>

Read all feedback entries.

Parameters:

  • limit (Integer, nil) (defaults to: nil)

    Maximum number of entries to return. Returns all if nil.

Returns:

  • (Array<Hash>)


67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/woods/feedback/store.rb', line 67

def all_entries(limit: nil)
  return [] unless File.exist?(@path)

  entries = []
  File.foreach(@path) do |line|
    entry = JSON.parse(line.strip)
    entries << entry
    break if limit && entries.size >= limit
  rescue JSON::ParserError
    next
  end
  entries
end

#average_scoreFloat?

Average score across all ratings.

Returns:

  • (Float, nil)

    Average score, or nil if no ratings



98
99
100
101
102
103
# File 'lib/woods/feedback/store.rb', line 98

def average_score
  scores = ratings.map { |r| r['score'] }
  return nil if scores.empty?

  scores.sum.to_f / scores.size
end

#gapsArray<Hash>

Filter to gap report entries only.

Returns:

  • (Array<Hash>)


91
92
93
# File 'lib/woods/feedback/store.rb', line 91

def gaps
  all_entries.select { |e| e['type'] == 'gap' }
end

#ratingsArray<Hash>

Filter to rating entries only.

Returns:

  • (Array<Hash>)


84
85
86
# File 'lib/woods/feedback/store.rb', line 84

def ratings
  all_entries.select { |e| e['type'] == 'rating' }
end

#record_gap(query:, missing_unit:, unit_type:) ⇒ void

This method returns an undefined value.

Record a missing unit gap report.

Parameters:

  • query (String)

    The query that had poor results

  • missing_unit (String)

    Identifier of the expected but missing unit

  • unit_type (String)

    Expected type of the missing unit



52
53
54
55
56
57
58
59
60
61
# File 'lib/woods/feedback/store.rb', line 52

def record_gap(query:, missing_unit:, unit_type:)
  entry = {
    type: 'gap',
    query: query,
    missing_unit: missing_unit,
    unit_type: unit_type,
    timestamp: Time.now.iso8601
  }
  append(entry)
end

#record_rating(query:, score:, comment: nil) ⇒ void

This method returns an undefined value.

Record a retrieval quality rating.

Parameters:

  • query (String)

    The original query

  • score (Integer)

    Rating 1-5

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

    Optional comment



31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/woods/feedback/store.rb', line 31

def record_rating(query:, score:, comment: nil)
  unless score.is_a?(Integer) && (1..5).cover?(score)
    raise ArgumentError, "score must be an Integer between 1 and 5, got: #{score.inspect}"
  end

  entry = {
    type: 'rating',
    query: query,
    score: score,
    comment: comment,
    timestamp: Time.now.iso8601
  }
  append(entry)
end