Spaces:
Paused
Paused
| ; | |
| 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 |