forked from personamanagmentlayer/pcl
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoutcome-tracker.ts
More file actions
220 lines (184 loc) · 5.79 KB
/
Copy pathoutcome-tracker.ts
File metadata and controls
220 lines (184 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
55
56
57
58
59
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
123
124
125
126
127
128
129
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
158
159
160
161
162
163
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/**
* Merge outcome tracking for weight adaptation
* Part of Q2 2025 Adaptive Intelligence - Phase 3
*/
import type { MergeOutcome, MemberPerformance } from './types.js';
/**
* Tracks merge outcomes for performance analysis
*/
export class OutcomeTracker {
private outcomes: Map<string, MergeOutcome[]>;
private readonly maxOutcomesPerTeam: number = 1000;
constructor() {
this.outcomes = new Map();
}
/**
* Record a merge outcome
*/
recordOutcome(outcome: MergeOutcome): void {
if (!this.outcomes.has(outcome.teamId)) {
this.outcomes.set(outcome.teamId, []);
}
const teamOutcomes = this.outcomes.get(outcome.teamId)!;
teamOutcomes.push(outcome);
// Keep only recent outcomes
if (teamOutcomes.length > this.maxOutcomesPerTeam) {
this.outcomes.set(
outcome.teamId,
teamOutcomes.slice(-this.maxOutcomesPerTeam)
);
}
}
/**
* Get history of outcomes for a team
*/
getHistory(teamId: string, limit: number = 100): MergeOutcome[] {
const outcomes = this.outcomes.get(teamId) || [];
return outcomes.slice(-limit);
}
/**
* Analyze performance of a specific member
*/
analyzeMemberPerformance(
teamId: string,
personaId: string
): MemberPerformance {
const history = this.getHistory(teamId);
const memberOutcomes = history.filter((o) =>
o.memberResponses.some((r) => r.personaId === personaId)
);
if (memberOutcomes.length === 0) {
return {
totalResponses: 0,
avgConfidence: 0.5,
selectionRate: 0,
avgQuality: 0.5,
};
}
return {
totalResponses: memberOutcomes.length,
avgConfidence: this.avgConfidence(memberOutcomes, personaId),
selectionRate: this.selectionRate(memberOutcomes, personaId),
avgQuality: this.avgQuality(memberOutcomes, personaId),
trend: this.detectTrend(memberOutcomes, personaId),
};
}
/**
* Get all member performances for a team
*/
getAllMemberPerformances(teamId: string): Map<string, MemberPerformance> {
const history = this.getHistory(teamId);
const performances = new Map<string, MemberPerformance>();
// Get unique member IDs
const memberIds = new Set<string>();
for (const outcome of history) {
for (const response of outcome.memberResponses) {
memberIds.add(response.personaId);
}
}
// Analyze each member
for (const memberId of memberIds) {
performances.set(
memberId,
this.analyzeMemberPerformance(teamId, memberId)
);
}
return performances;
}
/**
* Clear outcomes for a team
*/
clear(teamId?: string): void {
if (teamId) {
this.outcomes.delete(teamId);
} else {
this.outcomes.clear();
}
}
/**
* Export outcome data
*/
export(): Map<string, MergeOutcome[]> {
return new Map(this.outcomes);
}
/**
* Import outcome data
*/
import(data: Map<string, MergeOutcome[]>): void {
this.outcomes = new Map(data);
}
/**
* Compute average confidence for a member
*/
private avgConfidence(outcomes: MergeOutcome[], personaId: string): number {
const confidences = outcomes
.flatMap((o) => o.memberResponses)
.filter((r) => r.personaId === personaId)
.map((r) => r.confidence);
if (confidences.length === 0) return 0.5;
return confidences.reduce((sum, c) => sum + c, 0) / confidences.length;
}
/**
* Compute selection rate (how often this member was chosen)
*/
private selectionRate(outcomes: MergeOutcome[], personaId: string): number {
const withFeedback = outcomes.filter((o) => o.feedback?.selected);
if (withFeedback.length === 0) {
// If no explicit selection feedback, use confidence as proxy
const memberOutcomes = outcomes.filter((o) =>
o.memberResponses.some((r) => r.personaId === personaId)
);
if (memberOutcomes.length === 0) return 0;
// Count how often this member had highest confidence
let highestCount = 0;
for (const outcome of memberOutcomes) {
const memberResponse = outcome.memberResponses.find(
(r) => r.personaId === personaId
);
if (!memberResponse) continue;
const isHighest = outcome.memberResponses.every(
(r) =>
r.personaId === personaId ||
r.confidence <= memberResponse.confidence
);
if (isHighest) highestCount++;
}
return highestCount / memberOutcomes.length;
}
const selected = withFeedback.filter(
(o) => o.feedback!.selected === personaId
).length;
return selected / withFeedback.length;
}
/**
* Compute average quality from user feedback
*/
private avgQuality(outcomes: MergeOutcome[], personaId: string): number {
const qualities = outcomes
.filter((o) => o.feedback?.quality !== undefined)
.filter((o) => o.memberResponses.some((r) => r.personaId === personaId))
.map((o) => o.feedback!.quality!);
if (qualities.length === 0) return 0.5; // Neutral default
return qualities.reduce((sum, q) => sum + q, 0) / qualities.length;
}
/**
* Detect performance trend
*/
private detectTrend(
outcomes: MergeOutcome[],
personaId: string
): 'improving' | 'stable' | 'degrading' | undefined {
if (outcomes.length < 20) return undefined; // Not enough data
// Split into first half and second half
const mid = Math.floor(outcomes.length / 2);
const firstHalf = outcomes.slice(0, mid);
const secondHalf = outcomes.slice(mid);
// Compare average confidence
const firstAvg = this.avgConfidence(firstHalf, personaId);
const secondAvg = this.avgConfidence(secondHalf, personaId);
const diff = secondAvg - firstAvg;
if (diff > 0.05) return 'improving';
if (diff < -0.05) return 'degrading';
return 'stable';
}
}