PRIX / lib /src /feedback-tracker.js
yamxxx1's picture
Upload 117 files
a2fe16e verified
Raw
History Blame
12.5 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.feedbackTracker = exports.FeedbackTracker = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const minimatch_1 = require("minimatch");
const info = console.log;
function getPersistBaseDir() {
if (process.env.PRIX_DATA_DIR)
return process.env.PRIX_DATA_DIR;
if (process.env.HF_HOME)
return (0, path_1.join)(process.env.HF_HOME, 'prix-data');
if (process.env.HUGGING_FACE_HUB_TOKEN)
return '/tmp/prix-hf';
return '/tmp/prix';
}
class FeedbackTracker {
feedbackStore = [];
teamInsights = new Map();
persistPath;
maxStoreSize = 10000;
initialized = false;
constructor(persistDir) {
const baseDir = persistDir || getPersistBaseDir();
this.persistPath = (0, path_1.join)(baseDir, 'feedback');
this.ensureStorageDir();
this.loadPersisted();
}
ensureStorageDir() {
try {
if (!(0, fs_1.existsSync)(this.persistPath)) {
(0, fs_1.mkdirSync)(this.persistPath, { recursive: true });
}
}
catch (e) {
info(`Failed to create feedback storage dir: ${e}`);
}
}
loadPersisted() {
try {
const feedbackFile = (0, path_1.join)(this.persistPath, 'feedback.json');
if ((0, fs_1.existsSync)(feedbackFile)) {
const data = JSON.parse((0, fs_1.readFileSync)(feedbackFile, 'utf8'));
if (Array.isArray(data)) {
this.feedbackStore = data.slice(-this.maxStoreSize);
this.rebuildTeamInsights();
this.initialized = true;
info(`Loaded ${this.feedbackStore.length} feedback entries`);
}
}
}
catch (e) {
info(`Failed to load feedback data: ${e}`);
}
}
persist() {
try {
const feedbackFile = (0, path_1.join)(this.persistPath, 'feedback.json');
(0, fs_1.writeFileSync)(feedbackFile, JSON.stringify(this.feedbackStore.slice(-this.maxStoreSize)));
}
catch (e) {
info(`Failed to persist feedback: ${e}`);
}
}
rebuildTeamInsights() {
const teamMap = new Map();
for (const fb of this.feedbackStore) {
const key = `${fb.teamId}:${fb.repoSlug}`;
if (!teamMap.has(key)) {
teamMap.set(key, []);
}
teamMap.get(key).push(fb);
}
for (const [key, feedbacks] of teamMap) {
const [teamId, repoSlug] = key.split(':');
const insights = this.computeInsights(teamId, repoSlug, feedbacks);
this.teamInsights.set(key, insights);
}
}
computeInsights(teamId, repoSlug, feedbacks) {
const patternMap = new Map();
for (const fb of feedbacks) {
if (!patternMap.has(fb.patternType)) {
patternMap.set(fb.patternType, []);
}
patternMap.get(fb.patternType).push(fb);
}
const patternStats = new Map();
for (const [patternType, pFeedbacks] of patternMap) {
const accepted = pFeedbacks.filter(f => f.feedbackType === 'accepted').length;
const rejected = pFeedbacks.filter(f => f.feedbackType === 'rejected').length;
const ignored = pFeedbacks.filter(f => f.feedbackType === 'ignored').length;
const total = pFeedbacks.length;
const avgConfidence = pFeedbacks.reduce((sum, f) => sum + f.confidence, 0) / total;
patternStats.set(patternType, {
patternType,
total,
accepted,
rejected,
ignored,
acceptanceRate: total > 0 ? accepted / total : 0,
avgConfidence
});
}
const allAccepted = feedbacks.filter(f => f.feedbackType === 'accepted').length;
const overallAcceptanceRate = feedbacks.length > 0 ? allAccepted / feedbacks.length : 0;
return {
teamId,
repoSlug,
patternStats,
overallAcceptanceRate,
lastUpdated: Date.now()
};
}
recordFeedback(feedback) {
const id = `fb_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const fullFeedback = {
...feedback,
id,
timestamp: Date.now()
};
this.feedbackStore.push(fullFeedback);
if (this.feedbackStore.length > this.maxStoreSize) {
this.feedbackStore = this.feedbackStore.slice(-this.maxStoreSize);
}
const key = `${feedback.teamId}:${feedback.repoSlug}`;
const existing = this.teamInsights.get(key);
if (existing) {
const updatedFeedbacks = [
...this.feedbackStore.filter(f => f.teamId === feedback.teamId && f.repoSlug === feedback.repoSlug)
];
const insights = this.computeInsights(feedback.teamId, feedback.repoSlug, updatedFeedbacks);
this.teamInsights.set(key, insights);
}
this.persist();
return id;
}
recordAcceptance(patternType, severity, filename, repoSlug, teamId, confidence, commentId, reviewId) {
return this.recordFeedback({
patternType,
severity,
filenamePattern: filename,
repoSlug,
teamId,
confidence,
feedbackType: 'accepted',
commentId,
reviewId
});
}
recordRejection(patternType, severity, filename, repoSlug, teamId, confidence, commentId, reviewId) {
return this.recordFeedback({
patternType,
severity,
filenamePattern: filename,
repoSlug,
teamId,
confidence,
feedbackType: 'rejected',
commentId,
reviewId
});
}
recordIgnored(patternType, severity, filename, repoSlug, teamId, confidence, commentId, reviewId) {
return this.recordFeedback({
patternType,
severity,
filenamePattern: filename,
repoSlug,
teamId,
confidence,
feedbackType: 'ignored',
commentId,
reviewId
});
}
getPatternAcceptanceRate(patternType, teamId, repoSlug) {
const key = `${teamId}:${repoSlug}`;
const insights = this.teamInsights.get(key);
if (!insights)
return 0.5;
const stats = insights.patternStats.get(patternType);
if (!stats)
return 0.5;
if (stats.total < 5)
return 0.5;
return stats.acceptanceRate;
}
getFilenameAcceptanceRate(filenamePattern, teamId, repoSlug) {
const feedbacks = this.feedbackStore.filter(f => f.teamId === teamId &&
f.repoSlug === repoSlug &&
f.feedbackType !== 'ignored');
if (feedbacks.length === 0)
return 0.5;
const matching = feedbacks.filter(f => (0, minimatch_1.minimatch)(f.filenamePattern, filenamePattern) ||
(0, minimatch_1.minimatch)(filenamePattern, f.filenamePattern));
if (matching.length === 0)
return 0.5;
const accepted = matching.filter(f => f.feedbackType === 'accepted').length;
return accepted / matching.length;
}
getTeamInsights(teamId, repoSlug) {
const key = `${teamId}:${repoSlug}`;
return this.teamInsights.get(key) || null;
}
getAdjustedConfidence(baseConfidence, patternType, filename, teamId, repoSlug) {
const patternRate = this.getPatternAcceptanceRate(patternType, teamId, repoSlug);
const filenameRate = this.getFilenameAcceptanceRate(filename, teamId, repoSlug);
const feedbackWeight = 0.3;
const teamRate = patternRate * 0.6 + filenameRate * 0.4;
const adjustedConfidence = baseConfidence * (1 - feedbackWeight) + teamRate * 100 * feedbackWeight;
return Math.max(0, Math.min(100, adjustedConfidence));
}
shouldSuppressPattern(patternType, teamId, repoSlug, threshold = 0.2, minSamples = 10) {
const key = `${teamId}:${repoSlug}`;
const insights = this.teamInsights.get(key);
if (!insights)
return false;
const stats = insights.patternStats.get(patternType);
if (!stats || stats.total < minSamples)
return false;
return stats.acceptanceRate < threshold;
}
getQuery(query) {
let results = [...this.feedbackStore];
if (query.teamId) {
results = results.filter(f => f.teamId === query.teamId);
}
if (query.repoSlug) {
results = results.filter(f => f.repoSlug === query.repoSlug);
}
if (query.patternType) {
results = results.filter(f => f.patternType === query.patternType);
}
if (query.filenamePattern) {
results = results.filter(f => (0, minimatch_1.minimatch)(f.filenamePattern, query.filenamePattern));
}
if (query.since) {
results = results.filter(f => f.timestamp >= query.since);
}
if (query.limit) {
results = results.slice(-query.limit);
}
return results;
}
getSummary(teamId, repoSlug) {
let feedbacks = [...this.feedbackStore];
if (teamId) {
feedbacks = feedbacks.filter(f => f.teamId === teamId);
}
if (repoSlug) {
feedbacks = feedbacks.filter(f => f.repoSlug === repoSlug);
}
const patternMap = new Map();
const repoMap = new Map();
const teamMap = new Map();
for (const fb of feedbacks) {
if (!patternMap.has(fb.patternType)) {
patternMap.set(fb.patternType, []);
}
patternMap.get(fb.patternType).push(fb);
repoMap.set(fb.repoSlug, (repoMap.get(fb.repoSlug) || 0) + 1);
teamMap.set(fb.teamId, (teamMap.get(fb.teamId) || 0) + 1);
}
const byPattern = new Map();
for (const [patternType, pFeedbacks] of patternMap) {
const accepted = pFeedbacks.filter(f => f.feedbackType === 'accepted').length;
const rejected = pFeedbacks.filter(f => f.feedbackType === 'rejected').length;
const ignored = pFeedbacks.filter(f => f.feedbackType === 'ignored').length;
const total = pFeedbacks.length;
const avgConfidence = pFeedbacks.reduce((sum, f) => sum + f.confidence, 0) / total;
byPattern.set(patternType, {
patternType,
total,
accepted,
rejected,
ignored,
acceptanceRate: total > 0 ? accepted / total : 0,
avgConfidence
});
}
const recentWindow = 30 * 24 * 60 * 60 * 1000;
const recentFeedbacks = feedbacks.filter(f => f.timestamp > Date.now() - recentWindow);
const olderFeedbacks = feedbacks.filter(f => f.timestamp <= Date.now() - recentWindow);
const recentAcceptance = recentFeedbacks.filter(f => f.feedbackType === 'accepted').length /
(recentFeedbacks.length || 1);
const olderAcceptance = olderFeedbacks.filter(f => f.feedbackType === 'accepted').length /
(olderFeedbacks.length || 1);
let recentTrend = 'stable';
if (recentAcceptance > olderAcceptance + 0.05) {
recentTrend = 'improving';
}
else if (recentAcceptance < olderAcceptance - 0.05) {
recentTrend = 'declining';
}
return {
totalFeedback: feedbacks.length,
byPattern,
byRepo: repoMap,
byTeam: teamMap,
recentTrend
};
}
exportForTraining() {
return this.feedbackStore
.filter(f => f.feedbackType !== 'ignored')
.map(f => ({
patternType: f.patternType,
severity: f.severity,
filenamePattern: f.filenamePattern,
confidence: f.confidence,
accepted: f.feedbackType === 'accepted',
repoSlug: f.repoSlug
}));
}
}
exports.FeedbackTracker = FeedbackTracker;
exports.feedbackTracker = new FeedbackTracker();
//# sourceMappingURL=feedback-tracker.js.map