@defai.digital/feedback-domain 13.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +38 -0
- package/dist/feedback-collector.d.ts +19 -0
- package/dist/feedback-collector.d.ts.map +1 -0
- package/dist/feedback-collector.js +239 -0
- package/dist/feedback-collector.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/score-adjuster.d.ts +24 -0
- package/dist/score-adjuster.d.ts.map +1 -0
- package/dist/score-adjuster.js +216 -0
- package/dist/score-adjuster.js.map +1 -0
- package/dist/types.d.ts +160 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +29 -0
- package/src/feedback-collector.ts +313 -0
- package/src/index.ts +29 -0
- package/src/score-adjuster.ts +275 -0
- package/src/types.ts +191 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Domain Types
|
|
3
|
+
*
|
|
4
|
+
* Port interfaces for the feedback learning system.
|
|
5
|
+
*/
|
|
6
|
+
import type { FeedbackRecord, SubmitFeedbackInput, AgentScoreAdjustment, TaskPattern, AgentFeedbackStats, FeedbackOverview, FeedbackLearningConfig } from '@defai.digital/contracts';
|
|
7
|
+
/**
|
|
8
|
+
* Port for storing feedback records
|
|
9
|
+
*/
|
|
10
|
+
export interface FeedbackStoragePort {
|
|
11
|
+
/**
|
|
12
|
+
* Store a feedback record
|
|
13
|
+
* INV-FBK-001: Records are immutable after storage
|
|
14
|
+
*/
|
|
15
|
+
store(record: FeedbackRecord): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Get feedback by ID
|
|
18
|
+
*/
|
|
19
|
+
get(feedbackId: string): Promise<FeedbackRecord | null>;
|
|
20
|
+
/**
|
|
21
|
+
* List feedback records with optional filters
|
|
22
|
+
*/
|
|
23
|
+
list(options?: {
|
|
24
|
+
agentId?: string;
|
|
25
|
+
limit?: number;
|
|
26
|
+
offset?: number;
|
|
27
|
+
since?: string;
|
|
28
|
+
}): Promise<FeedbackRecord[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Count feedback records
|
|
31
|
+
*/
|
|
32
|
+
count(options?: {
|
|
33
|
+
agentId?: string;
|
|
34
|
+
since?: string;
|
|
35
|
+
}): Promise<number>;
|
|
36
|
+
/**
|
|
37
|
+
* Delete old feedback records
|
|
38
|
+
* INV-FBK-301: Retention policy enforced
|
|
39
|
+
*/
|
|
40
|
+
deleteOlderThan(date: string): Promise<number>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Port for storing score adjustments
|
|
44
|
+
*/
|
|
45
|
+
export interface ScoreAdjustmentStoragePort {
|
|
46
|
+
/**
|
|
47
|
+
* Get adjustment for agent and pattern
|
|
48
|
+
*/
|
|
49
|
+
get(agentId: string, taskPattern: string): Promise<AgentScoreAdjustment | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Save or update adjustment
|
|
52
|
+
*/
|
|
53
|
+
set(adjustment: AgentScoreAdjustment): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* List all adjustments for an agent
|
|
56
|
+
*/
|
|
57
|
+
listForAgent(agentId: string): Promise<AgentScoreAdjustment[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Delete expired adjustments
|
|
60
|
+
*/
|
|
61
|
+
deleteExpired(): Promise<number>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Port for pattern matching
|
|
65
|
+
*/
|
|
66
|
+
export interface PatternMatcherPort {
|
|
67
|
+
/**
|
|
68
|
+
* Find matching patterns for a task
|
|
69
|
+
*/
|
|
70
|
+
findMatches(taskDescription: string, threshold: number): Promise<TaskPattern[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Create or update a pattern
|
|
73
|
+
*/
|
|
74
|
+
upsertPattern(pattern: TaskPattern): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Get pattern by ID
|
|
77
|
+
*/
|
|
78
|
+
getPattern(patternId: string): Promise<TaskPattern | null>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Feedback collector options
|
|
82
|
+
*/
|
|
83
|
+
export interface FeedbackCollectorOptions {
|
|
84
|
+
/**
|
|
85
|
+
* Feedback storage port
|
|
86
|
+
*/
|
|
87
|
+
storage: FeedbackStoragePort;
|
|
88
|
+
/**
|
|
89
|
+
* Learning configuration
|
|
90
|
+
*/
|
|
91
|
+
config?: FeedbackLearningConfig;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Score adjuster options
|
|
95
|
+
*/
|
|
96
|
+
export interface ScoreAdjusterOptions {
|
|
97
|
+
/**
|
|
98
|
+
* Feedback storage port
|
|
99
|
+
*/
|
|
100
|
+
feedbackStorage: FeedbackStoragePort;
|
|
101
|
+
/**
|
|
102
|
+
* Score adjustment storage port
|
|
103
|
+
*/
|
|
104
|
+
adjustmentStorage: ScoreAdjustmentStoragePort;
|
|
105
|
+
/**
|
|
106
|
+
* Pattern matcher port
|
|
107
|
+
*/
|
|
108
|
+
patternMatcher: PatternMatcherPort;
|
|
109
|
+
/**
|
|
110
|
+
* Learning configuration
|
|
111
|
+
*/
|
|
112
|
+
config?: FeedbackLearningConfig;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Feedback collector interface
|
|
116
|
+
*/
|
|
117
|
+
export interface FeedbackCollector {
|
|
118
|
+
/**
|
|
119
|
+
* Submit feedback
|
|
120
|
+
*/
|
|
121
|
+
submit(input: SubmitFeedbackInput): Promise<FeedbackRecord>;
|
|
122
|
+
/**
|
|
123
|
+
* Get feedback history
|
|
124
|
+
*/
|
|
125
|
+
getHistory(options?: {
|
|
126
|
+
agentId?: string;
|
|
127
|
+
limit?: number;
|
|
128
|
+
since?: string;
|
|
129
|
+
}): Promise<FeedbackRecord[]>;
|
|
130
|
+
/**
|
|
131
|
+
* Get feedback statistics for an agent
|
|
132
|
+
*/
|
|
133
|
+
getAgentStats(agentId: string): Promise<AgentFeedbackStats>;
|
|
134
|
+
/**
|
|
135
|
+
* Get overall feedback overview
|
|
136
|
+
*/
|
|
137
|
+
getOverview(): Promise<FeedbackOverview>;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Score adjuster interface
|
|
141
|
+
*/
|
|
142
|
+
export interface ScoreAdjuster {
|
|
143
|
+
/**
|
|
144
|
+
* Get score adjustment for an agent and task
|
|
145
|
+
*/
|
|
146
|
+
getAdjustment(agentId: string, taskDescription: string): Promise<number>;
|
|
147
|
+
/**
|
|
148
|
+
* Update adjustments based on new feedback
|
|
149
|
+
*/
|
|
150
|
+
processNewFeedback(record: FeedbackRecord): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* Get all adjustments for an agent
|
|
153
|
+
*/
|
|
154
|
+
getAgentAdjustments(agentId: string): Promise<AgentScoreAdjustment[]>;
|
|
155
|
+
/**
|
|
156
|
+
* Apply decay to all adjustments
|
|
157
|
+
*/
|
|
158
|
+
applyDecay(): Promise<void>;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@defai.digital/feedback-domain",
|
|
3
|
+
"version": "13.4.0",
|
|
4
|
+
"description": "Agent feedback collection and learning system",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@defai.digital/contracts": "13.4.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.6.3"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc --build",
|
|
27
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Collector
|
|
3
|
+
*
|
|
4
|
+
* Collect and store feedback records.
|
|
5
|
+
*
|
|
6
|
+
* Invariants:
|
|
7
|
+
* - INV-FBK-001: Feedback records immutable
|
|
8
|
+
* - INV-FBK-100: No duplicate feedback
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
FeedbackRecord,
|
|
13
|
+
SubmitFeedbackInput,
|
|
14
|
+
AgentFeedbackStats,
|
|
15
|
+
FeedbackOverview,
|
|
16
|
+
} from '@defai.digital/contracts';
|
|
17
|
+
import {
|
|
18
|
+
createDefaultFeedbackLearningConfig,
|
|
19
|
+
createTaskHash,
|
|
20
|
+
} from '@defai.digital/contracts';
|
|
21
|
+
import type {
|
|
22
|
+
FeedbackCollector,
|
|
23
|
+
FeedbackCollectorOptions,
|
|
24
|
+
FeedbackStoragePort,
|
|
25
|
+
} from './types.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create in-memory feedback storage
|
|
29
|
+
*/
|
|
30
|
+
export function createInMemoryFeedbackStorage(): FeedbackStoragePort {
|
|
31
|
+
const records = new Map<string, FeedbackRecord>();
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
async store(record: FeedbackRecord): Promise<void> {
|
|
35
|
+
// INV-FBK-001: Records are immutable - just store, never update
|
|
36
|
+
records.set(record.feedbackId, record);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async get(feedbackId: string): Promise<FeedbackRecord | null> {
|
|
40
|
+
return records.get(feedbackId) ?? null;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async list(options?: {
|
|
44
|
+
agentId?: string;
|
|
45
|
+
limit?: number;
|
|
46
|
+
offset?: number;
|
|
47
|
+
since?: string;
|
|
48
|
+
}): Promise<FeedbackRecord[]> {
|
|
49
|
+
let results = Array.from(records.values());
|
|
50
|
+
|
|
51
|
+
// Filter by agent
|
|
52
|
+
if (options?.agentId) {
|
|
53
|
+
results = results.filter((r) => r.selectedAgent === options.agentId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Filter by date
|
|
57
|
+
if (options?.since) {
|
|
58
|
+
const sinceDate = new Date(options.since);
|
|
59
|
+
results = results.filter((r) => new Date(r.timestamp) >= sinceDate);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Sort by timestamp (newest first)
|
|
63
|
+
results.sort(
|
|
64
|
+
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Apply pagination
|
|
68
|
+
const offset = options?.offset ?? 0;
|
|
69
|
+
const limit = options?.limit ?? 100;
|
|
70
|
+
return results.slice(offset, offset + limit);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async count(options?: { agentId?: string; since?: string }): Promise<number> {
|
|
74
|
+
let count = 0;
|
|
75
|
+
for (const record of records.values()) {
|
|
76
|
+
if (options?.agentId && record.selectedAgent !== options.agentId) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (options?.since && new Date(record.timestamp) < new Date(options.since)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
count++;
|
|
83
|
+
}
|
|
84
|
+
return count;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async deleteOlderThan(date: string): Promise<number> {
|
|
88
|
+
const threshold = new Date(date);
|
|
89
|
+
let deleted = 0;
|
|
90
|
+
for (const [id, record] of records) {
|
|
91
|
+
if (new Date(record.timestamp) < threshold) {
|
|
92
|
+
records.delete(id);
|
|
93
|
+
deleted++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return deleted;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create feedback collector
|
|
103
|
+
*/
|
|
104
|
+
export function createFeedbackCollector(
|
|
105
|
+
options: FeedbackCollectorOptions
|
|
106
|
+
): FeedbackCollector {
|
|
107
|
+
const { storage } = options;
|
|
108
|
+
// Config is available for future use - call it to get defaults
|
|
109
|
+
void (options.config ?? createDefaultFeedbackLearningConfig());
|
|
110
|
+
|
|
111
|
+
// Track recent submissions for deduplication
|
|
112
|
+
// INV-FBK-100: No duplicate feedback
|
|
113
|
+
const recentSubmissions = new Map<string, number>();
|
|
114
|
+
const DEDUP_WINDOW_MS = 60000; // 1 minute
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
async submit(input: SubmitFeedbackInput): Promise<FeedbackRecord> {
|
|
118
|
+
// INV-FBK-100: Check for duplicates
|
|
119
|
+
const dedupKey = `${input.selectedAgent}:${createTaskHash(input.taskDescription)}`;
|
|
120
|
+
const lastSubmit = recentSubmissions.get(dedupKey);
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
|
|
123
|
+
if (lastSubmit && now - lastSubmit < DEDUP_WINDOW_MS) {
|
|
124
|
+
throw new Error('Duplicate feedback submission detected');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Create feedback record
|
|
128
|
+
const record: FeedbackRecord = {
|
|
129
|
+
feedbackId: crypto.randomUUID(),
|
|
130
|
+
taskDescription: input.taskDescription,
|
|
131
|
+
taskHash: createTaskHash(input.taskDescription),
|
|
132
|
+
selectedAgent: input.selectedAgent,
|
|
133
|
+
feedbackType: input.feedbackType,
|
|
134
|
+
timestamp: new Date().toISOString(),
|
|
135
|
+
retryCount: 0,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Add optional fields only if defined
|
|
139
|
+
if (input.recommendedAgent !== undefined) {
|
|
140
|
+
record.recommendedAgent = input.recommendedAgent;
|
|
141
|
+
}
|
|
142
|
+
if (input.rating !== undefined) {
|
|
143
|
+
record.rating = input.rating;
|
|
144
|
+
}
|
|
145
|
+
if (input.outcome !== undefined) {
|
|
146
|
+
record.outcome = input.outcome;
|
|
147
|
+
}
|
|
148
|
+
if (input.durationMs !== undefined) {
|
|
149
|
+
record.durationMs = input.durationMs;
|
|
150
|
+
}
|
|
151
|
+
if (input.userComment !== undefined) {
|
|
152
|
+
record.userComment = input.userComment;
|
|
153
|
+
}
|
|
154
|
+
if (input.sessionId !== undefined) {
|
|
155
|
+
record.sessionId = input.sessionId;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Store the record
|
|
159
|
+
// INV-FBK-001: Immutable after this point
|
|
160
|
+
await storage.store(record);
|
|
161
|
+
|
|
162
|
+
// Track for deduplication
|
|
163
|
+
recentSubmissions.set(dedupKey, now);
|
|
164
|
+
|
|
165
|
+
// Cleanup old dedup entries - collect keys first to avoid concurrent modification
|
|
166
|
+
const keysToDelete: string[] = [];
|
|
167
|
+
for (const [key, time] of recentSubmissions) {
|
|
168
|
+
if (now - time > DEDUP_WINDOW_MS) {
|
|
169
|
+
keysToDelete.push(key);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const key of keysToDelete) {
|
|
173
|
+
recentSubmissions.delete(key);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return record;
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
async getHistory(options?: {
|
|
180
|
+
agentId?: string;
|
|
181
|
+
limit?: number;
|
|
182
|
+
since?: string;
|
|
183
|
+
}): Promise<FeedbackRecord[]> {
|
|
184
|
+
return storage.list(options);
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
async getAgentStats(agentId: string): Promise<AgentFeedbackStats> {
|
|
188
|
+
const records = await storage.list({ agentId });
|
|
189
|
+
|
|
190
|
+
const totalFeedback = records.length;
|
|
191
|
+
const ratings = records.filter((r) => r.rating !== undefined).map((r) => r.rating!);
|
|
192
|
+
const avgRating =
|
|
193
|
+
ratings.length > 0 ? ratings.reduce((a, b) => a + b, 0) / ratings.length : undefined;
|
|
194
|
+
|
|
195
|
+
const outcomes = records.filter((r) => r.outcome !== undefined);
|
|
196
|
+
const successCount = outcomes.filter((r) => r.outcome === 'success').length;
|
|
197
|
+
const successRate = outcomes.length > 0 ? successCount / outcomes.length : 1;
|
|
198
|
+
|
|
199
|
+
const recommended = records.filter((r) => r.recommendedAgent !== undefined);
|
|
200
|
+
const acceptedCount = recommended.filter(
|
|
201
|
+
(r) => r.recommendedAgent === r.selectedAgent
|
|
202
|
+
).length;
|
|
203
|
+
const recommendationAcceptRate =
|
|
204
|
+
recommended.length > 0 ? acceptedCount / recommended.length : 1;
|
|
205
|
+
|
|
206
|
+
const durations = records
|
|
207
|
+
.filter((r) => r.durationMs !== undefined)
|
|
208
|
+
.map((r) => r.durationMs!);
|
|
209
|
+
const avgDurationMs =
|
|
210
|
+
durations.length > 0
|
|
211
|
+
? durations.reduce((a, b) => a + b, 0) / durations.length
|
|
212
|
+
: undefined;
|
|
213
|
+
|
|
214
|
+
// Extract top patterns
|
|
215
|
+
const patternCounts = new Map<string, number>();
|
|
216
|
+
for (const record of records) {
|
|
217
|
+
const count = patternCounts.get(record.taskHash) ?? 0;
|
|
218
|
+
patternCounts.set(record.taskHash, count + 1);
|
|
219
|
+
}
|
|
220
|
+
const topPatterns = Array.from(patternCounts.entries())
|
|
221
|
+
.sort((a, b) => b[1] - a[1])
|
|
222
|
+
.slice(0, 10)
|
|
223
|
+
.map(([pattern]) => pattern);
|
|
224
|
+
|
|
225
|
+
const lastFeedback =
|
|
226
|
+
records.length > 0
|
|
227
|
+
? records.reduce((latest, r) =>
|
|
228
|
+
new Date(r.timestamp) > new Date(latest.timestamp) ? r : latest
|
|
229
|
+
).timestamp
|
|
230
|
+
: undefined;
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
agentId,
|
|
234
|
+
totalFeedback,
|
|
235
|
+
avgRating,
|
|
236
|
+
successRate,
|
|
237
|
+
recommendationAcceptRate,
|
|
238
|
+
avgDurationMs,
|
|
239
|
+
topPatterns,
|
|
240
|
+
lastFeedback,
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
async getOverview(): Promise<FeedbackOverview> {
|
|
245
|
+
const allRecords = await storage.list({ limit: 10000 });
|
|
246
|
+
const totalFeedback = allRecords.length;
|
|
247
|
+
|
|
248
|
+
// Count by type
|
|
249
|
+
const feedbackByType: Record<string, number> = {
|
|
250
|
+
explicit: 0,
|
|
251
|
+
implicit: 0,
|
|
252
|
+
outcome: 0,
|
|
253
|
+
};
|
|
254
|
+
for (const record of allRecords) {
|
|
255
|
+
feedbackByType[record.feedbackType] =
|
|
256
|
+
(feedbackByType[record.feedbackType] ?? 0) + 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Average rating
|
|
260
|
+
const ratings = allRecords
|
|
261
|
+
.filter((r) => r.rating !== undefined)
|
|
262
|
+
.map((r) => r.rating!);
|
|
263
|
+
const avgRating =
|
|
264
|
+
ratings.length > 0 ? ratings.reduce((a, b) => a + b, 0) / ratings.length : undefined;
|
|
265
|
+
|
|
266
|
+
// Success rate
|
|
267
|
+
const outcomes = allRecords.filter((r) => r.outcome !== undefined);
|
|
268
|
+
const successCount = outcomes.filter((r) => r.outcome === 'success').length;
|
|
269
|
+
const overallSuccessRate = outcomes.length > 0 ? successCount / outcomes.length : 1;
|
|
270
|
+
|
|
271
|
+
// Top agents
|
|
272
|
+
const agentScores = new Map<string, number>();
|
|
273
|
+
for (const record of allRecords) {
|
|
274
|
+
const score = agentScores.get(record.selectedAgent) ?? 0;
|
|
275
|
+
const delta = record.rating ?? (record.outcome === 'success' ? 5 : 1);
|
|
276
|
+
agentScores.set(record.selectedAgent, score + delta);
|
|
277
|
+
}
|
|
278
|
+
const topAgents = Array.from(agentScores.entries())
|
|
279
|
+
.sort((a, b) => b[1] - a[1])
|
|
280
|
+
.slice(0, 10)
|
|
281
|
+
.map(([agentId, score]) => ({ agentId, score }));
|
|
282
|
+
|
|
283
|
+
// Calculate trend (simplified)
|
|
284
|
+
const recentRecords = allRecords.slice(0, 20);
|
|
285
|
+
const olderRecords = allRecords.slice(20, 40);
|
|
286
|
+
const recentSuccessRate =
|
|
287
|
+
recentRecords.length > 0
|
|
288
|
+
? recentRecords.filter((r) => r.outcome === 'success').length / recentRecords.length
|
|
289
|
+
: 1;
|
|
290
|
+
const olderSuccessRate =
|
|
291
|
+
olderRecords.length > 0
|
|
292
|
+
? olderRecords.filter((r) => r.outcome === 'success').length / olderRecords.length
|
|
293
|
+
: 1;
|
|
294
|
+
|
|
295
|
+
let recentTrend: 'improving' | 'stable' | 'declining' = 'stable';
|
|
296
|
+
if (recentSuccessRate > olderSuccessRate + 0.1) {
|
|
297
|
+
recentTrend = 'improving';
|
|
298
|
+
} else if (recentSuccessRate < olderSuccessRate - 0.1) {
|
|
299
|
+
recentTrend = 'declining';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
totalFeedback,
|
|
304
|
+
feedbackByType: feedbackByType as Record<'explicit' | 'implicit' | 'outcome', number>,
|
|
305
|
+
avgRating,
|
|
306
|
+
overallSuccessRate,
|
|
307
|
+
topAgents,
|
|
308
|
+
recentTrend,
|
|
309
|
+
lastUpdated: new Date().toISOString(),
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Learning Domain
|
|
3
|
+
*
|
|
4
|
+
* Agent feedback collection and learning system.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type {
|
|
9
|
+
FeedbackStoragePort,
|
|
10
|
+
ScoreAdjustmentStoragePort,
|
|
11
|
+
PatternMatcherPort,
|
|
12
|
+
FeedbackCollectorOptions,
|
|
13
|
+
ScoreAdjusterOptions,
|
|
14
|
+
FeedbackCollector,
|
|
15
|
+
ScoreAdjuster,
|
|
16
|
+
} from './types.js';
|
|
17
|
+
|
|
18
|
+
// Feedback Collector
|
|
19
|
+
export {
|
|
20
|
+
createFeedbackCollector,
|
|
21
|
+
createInMemoryFeedbackStorage,
|
|
22
|
+
} from './feedback-collector.js';
|
|
23
|
+
|
|
24
|
+
// Score Adjuster
|
|
25
|
+
export {
|
|
26
|
+
createScoreAdjuster,
|
|
27
|
+
createInMemoryAdjustmentStorage,
|
|
28
|
+
createSimplePatternMatcher,
|
|
29
|
+
} from './score-adjuster.js';
|