Class: Aidp::Database::Repositories::PromptFeedbackRepository

Inherits:
Aidp::Database::Repository show all
Defined in:
lib/aidp/database/repositories/prompt_feedback_repository.rb

Overview

Repository for prompt_feedback table Tracks prompt template effectiveness for AGD evolution

Constant Summary collapse

VALID_OUTCOMES =
%w[success failure abandoned timeout].freeze
VALID_REACTIONS =
%w[positive negative neutral].freeze

Instance Attribute Summary

Attributes inherited from Aidp::Database::Repository

#project_dir, #table_name

Instance Method Summary collapse

Constructor Details

#initialize(project_dir: Dir.pwd) ⇒ PromptFeedbackRepository

Returns a new instance of PromptFeedbackRepository.



14
15
16
# File 'lib/aidp/database/repositories/prompt_feedback_repository.rb', line 14

def initialize(project_dir: Dir.pwd)
  super(project_dir: project_dir, table_name: "prompt_feedback")
end

Instance Method Details

#any?Boolean

Check if any feedback exists

Returns:

  • (Boolean)


222
223
224
225
226
227
228
# File 'lib/aidp/database/repositories/prompt_feedback_repository.rb', line 222

def any?
  count = query_value(
    "SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ?",
    [project_dir]
  ) || 0
  count.positive?
end

#clearHash

Clear all feedback

Returns:

  • (Hash)

    Result with count



207
208
209
210
211
212
213
214
215
216
217
# File 'lib/aidp/database/repositories/prompt_feedback_repository.rb', line 207

def clear
  count = query_value(
    "SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ?",
    [project_dir]
  ) || 0

  execute("DELETE FROM prompt_feedback WHERE project_dir = ?", [project_dir])

  Aidp.log_debug("prompt_feedback_repository", "cleared", count: count)
  {success: true, count: count}
end

#list(template_id: nil, outcome: nil, limit: 100) ⇒ Array<Hash>

List feedback entries with filtering

Parameters:

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

    Filter by template

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

    Filter by outcome

  • limit (Integer) (defaults to: 100)

    Maximum entries

Returns:

  • (Array<Hash>)

    Feedback entries



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/aidp/database/repositories/prompt_feedback_repository.rb', line 130

def list(template_id: nil, outcome: nil, limit: 100)
  conditions = ["project_dir = ?"]
  params = [project_dir]

  if template_id
    conditions << "template_id = ?"
    params << template_id
  end

  if outcome
    conditions << "outcome = ?"
    params << outcome.to_s
  end

  params << limit

  rows = query(
    <<~SQL,
      SELECT * FROM prompt_feedback
      WHERE #{conditions.join(" AND ")}
      ORDER BY created_at DESC, id DESC
      LIMIT ?
    SQL
    params
  )

  rows.map { |row| deserialize_feedback(row) }
end

#record(record) ⇒ Hash

Record feedback for a prompt template

Parameters:

  • record (Hash)

    Feedback data with keys:

    • template_id [String] Template identifier
    • outcome [String] success/failure/abandoned/timeout
    • iterations [Integer, nil] Number of iterations to completion
    • user_reaction [String, nil] positive/negative/neutral
    • suggestions [Array, nil] Improvement suggestions
    • context [Hash, nil] Additional context

Returns:

  • (Hash)

    Result with :success and :id



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/aidp/database/repositories/prompt_feedback_repository.rb', line 28

def record(record)
  execute(
    insert_sql([
      :project_dir, :template_id, :outcome, :iterations,
      :user_reaction, :suggestions, :context, :aidp_version
    ]),
    [
      project_dir,
      record[:template_id],
      record[:outcome].to_s,
      record[:iterations],
      record[:user_reaction]&.to_s,
      serialize_json(record[:suggestions]),
      serialize_json(record[:context] || {}),
      Aidp::VERSION
    ]
  )

  Aidp.log_debug("prompt_feedback_repository", "recorded",
    template_id: record[:template_id], outcome: record[:outcome])

  {success: true, id: last_insert_row_id}
rescue => e
  Aidp.log_debug("prompt_feedback_repository", "record_failed",
    template_id: record[:template_id], error: e.message)
  {success: false, error: e.message}
end

#summary(template_id:) ⇒ Hash

Get summary statistics for a template

Parameters:

  • template_id (String)

    Template identifier

Returns:

  • (Hash)

    Summary statistics



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/aidp/database/repositories/prompt_feedback_repository.rb', line 60

def summary(template_id:)
  total = query_value(
    "SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ?",
    [project_dir, template_id]
  ) || 0

  return empty_summary(template_id) if total.zero?

  success_count = query_value(
    "SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND outcome = 'success'",
    [project_dir, template_id]
  ) || 0

  failure_count = query_value(
    "SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND outcome = 'failure'",
    [project_dir, template_id]
  ) || 0

  avg_iterations = query_value(
    "SELECT AVG(iterations) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND iterations IS NOT NULL",
    [project_dir, template_id]
  )

  positive_reactions = query_value(
    "SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND user_reaction = 'positive'",
    [project_dir, template_id]
  ) || 0

  negative_reactions = query_value(
    "SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND user_reaction = 'negative'",
    [project_dir, template_id]
  ) || 0

  # Get suggestions
  suggestion_rows = query(
    "SELECT suggestions FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND suggestions IS NOT NULL",
    [project_dir, template_id]
  )
  all_suggestions = suggestion_rows.flat_map { |r| deserialize_json(r["suggestions"]) || [] }.compact.uniq

  first_row = query_one(
    "SELECT created_at FROM prompt_feedback WHERE project_dir = ? AND template_id = ? ORDER BY created_at ASC LIMIT 1",
    [project_dir, template_id]
  )
  last_row = query_one(
    "SELECT created_at FROM prompt_feedback WHERE project_dir = ? AND template_id = ? ORDER BY created_at DESC LIMIT 1",
    [project_dir, template_id]
  )

  {
    template_id: template_id,
    total_uses: total,
    success_rate: (total > 0) ? (success_count.to_f / total * 100).round(1) : 0,
    success_count: success_count,
    failure_count: failure_count,
    avg_iterations: avg_iterations&.round(1),
    positive_reactions: positive_reactions,
    negative_reactions: negative_reactions,
    common_suggestions: all_suggestions.take(5),
    first_use: first_row&.dig("created_at"),
    last_use: last_row&.dig("created_at")
  }
end

#templates_needing_improvement(min_uses: 5, max_success_rate: 70.0) ⇒ Array<Hash>

Find templates that need improvement

Parameters:

  • min_uses (Integer) (defaults to: 5)

    Minimum uses to consider

  • max_success_rate (Float) (defaults to: 70.0)

    Success rate threshold

Returns:

  • (Array<Hash>)

    Templates needing improvement



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/aidp/database/repositories/prompt_feedback_repository.rb', line 164

def templates_needing_improvement(min_uses: 5, max_success_rate: 70.0)
  # Use a single aggregated query instead of N+1 queries
  rows = query(
    <<~SQL,
      SELECT
        template_id,
        COUNT(*) as total_uses,
        SUM(CASE WHEN outcome = 'success' THEN 1 ELSE 0 END) as success_count,
        SUM(CASE WHEN outcome = 'failure' THEN 1 ELSE 0 END) as failure_count,
        AVG(CASE WHEN iterations IS NOT NULL THEN iterations END) as avg_iterations,
        SUM(CASE WHEN user_reaction = 'positive' THEN 1 ELSE 0 END) as positive_reactions,
        SUM(CASE WHEN user_reaction = 'negative' THEN 1 ELSE 0 END) as negative_reactions
      FROM prompt_feedback
      WHERE project_dir = ?
      GROUP BY template_id
      HAVING COUNT(*) >= ?
    SQL
    [project_dir, min_uses]
  )

  rows.filter_map do |row|
    total = row["total_uses"]
    success_count = row["success_count"]
    success_rate = (total > 0) ? (success_count.to_f / total * 100).round(1) : 0

    next if success_rate > max_success_rate

    {
      template_id: row["template_id"],
      total_uses: total,
      success_rate: success_rate,
      success_count: success_count,
      failure_count: row["failure_count"],
      avg_iterations: row["avg_iterations"]&.round(1),
      positive_reactions: row["positive_reactions"],
      negative_reactions: row["negative_reactions"]
    }
  end.sort_by { |s| s[:success_rate] }
end