Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions models/CategoryAnalytics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
const mongoose = require('mongoose');

const categoryAnalyticsSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
date: {
type: Date,
default: Date.now
},
totalPredictions: {
type: Number,
default: 0
},
correctPredictions: {
type: Number,
default: 0
},
accuracy: {
type: Number,
default: 0,
min: 0,
max: 1
},
methodBreakdown: {
tensorflow: {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
},
pattern: {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
},
'rule-based': {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
}
},
categoryBreakdown: {
food: {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
},
transport: {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
},
entertainment: {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
},
utilities: {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
},
healthcare: {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
},
shopping: {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
},
other: {
predictions: { type: Number, default: 0 },
correct: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 }
}
},
averageConfidence: {
type: Number,
default: 0
},
trainingDataUsed: {
type: Number,
default: 0
}
}, {
timestamps: true
});

// Indexes
categoryAnalyticsSchema.index({ user: 1, date: -1 });
categoryAnalyticsSchema.index({ user: 1, date: 1 });

// Static method to record prediction
categoryAnalyticsSchema.statics.recordPrediction = async function(userId, prediction, actualCategory, confidence) {
const today = new Date();
today.setHours(0, 0, 0, 0);

let analytics = await this.findOne({
user: userId,
date: today
});

if (!analytics) {
analytics = new this({
user: userId,
date: today
});
}

analytics.totalPredictions += 1;
const isCorrect = prediction.category === actualCategory;
if (isCorrect) {
analytics.correctPredictions += 1;
}

// Update method breakdown
if (analytics.methodBreakdown[prediction.method]) {
analytics.methodBreakdown[prediction.method].predictions += 1;
if (isCorrect) {
analytics.methodBreakdown[prediction.method].correct += 1;
}
analytics.methodBreakdown[prediction.method].accuracy =
analytics.methodBreakdown[prediction.method].correct /
analytics.methodBreakdown[prediction.method].predictions;
}

// Update category breakdown
if (analytics.categoryBreakdown[actualCategory]) {
analytics.categoryBreakdown[actualCategory].predictions += 1;
if (isCorrect) {
analytics.categoryBreakdown[actualCategory].correct += 1;
}
analytics.categoryBreakdown[actualCategory].accuracy =
analytics.categoryBreakdown[actualCategory].correct /
analytics.categoryBreakdown[actualCategory].predictions;
}

// Update overall accuracy
analytics.accuracy = analytics.correctPredictions / analytics.totalPredictions;

// Update average confidence
analytics.averageConfidence = (
(analytics.averageConfidence * (analytics.totalPredictions - 1)) + confidence
) / analytics.totalPredictions;

return await analytics.save();
};

// Static method to get user analytics
categoryAnalyticsSchema.statics.getUserAnalytics = async function(userId, days = 30) {
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);

const analytics = await this.find({
user: userId,
date: { $gte: startDate }
}).sort({ date: -1 });

if (analytics.length === 0) {
return {
totalPredictions: 0,
overallAccuracy: 0,
averageConfidence: 0,
methodBreakdown: {},
categoryBreakdown: {},
dailyStats: []
};
}

// Aggregate stats
const totalPredictions = analytics.reduce((sum, day) => sum + day.totalPredictions, 0);
const totalCorrect = analytics.reduce((sum, day) => sum + day.correctPredictions, 0);
const overallAccuracy = totalCorrect / totalPredictions;

const averageConfidence = analytics.reduce((sum, day) => sum + day.averageConfidence, 0) / analytics.length;

// Aggregate method breakdown
const methodBreakdown = {};
analytics.forEach(day => {
Object.keys(day.methodBreakdown).forEach(method => {
if (!methodBreakdown[method]) {
methodBreakdown[method] = { predictions: 0, correct: 0 };
}
methodBreakdown[method].predictions += day.methodBreakdown[method].predictions;
methodBreakdown[method].correct += day.methodBreakdown[method].correct;
});
});

Object.keys(methodBreakdown).forEach(method => {
methodBreakdown[method].accuracy = methodBreakdown[method].correct / methodBreakdown[method].predictions;
});

// Aggregate category breakdown
const categoryBreakdown = {};
analytics.forEach(day => {
Object.keys(day.categoryBreakdown).forEach(category => {
if (!categoryBreakdown[category]) {
categoryBreakdown[category] = { predictions: 0, correct: 0 };
}
categoryBreakdown[category].predictions += day.categoryBreakdown[category].predictions;
categoryBreakdown[category].correct += day.categoryBreakdown[category].correct;
});
});

Object.keys(categoryBreakdown).forEach(category => {
categoryBreakdown[category].accuracy = categoryBreakdown[category].correct / categoryBreakdown[category].predictions;
});

return {
totalPredictions,
overallAccuracy,
averageConfidence,
methodBreakdown,
categoryBreakdown,
dailyStats: analytics.map(day => ({
date: day.date,
predictions: day.totalPredictions,
accuracy: day.accuracy,
confidence: day.averageConfidence
}))
};
};

module.exports = mongoose.model('CategoryAnalytics', categoryAnalyticsSchema);
93 changes: 93 additions & 0 deletions models/CategoryModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const mongoose = require('mongoose');

const categoryModelSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
modelData: {
type: Buffer,
required: true
},
modelType: {
type: String,
enum: ['tensorflow', 'brainjs'],
default: 'tensorflow'
},
version: {
type: Number,
default: 1
},
accuracy: {
type: Number,
default: 0,
min: 0,
max: 1
},
trainingSamples: {
type: Number,
default: 0
},
lastTrained: {
type: Date,
default: Date.now
},
isActive: {
type: Boolean,
default: true
},
metadata: {
layers: Number,
inputSize: Number,
outputSize: Number,
trainingTime: Number,
epochs: Number
}
}, {
timestamps: true
});

// Indexes
categoryModelSchema.index({ user: 1, isActive: 1 });
categoryModelSchema.index({ user: 1, version: -1 });

// Static method to get active model for user
categoryModelSchema.statics.getActiveModel = async function(userId) {
return await this.findOne({
user: userId,
isActive: true
}).sort({ version: -1 });
};

// Static method to save model
categoryModelSchema.statics.saveModel = async function(userId, modelData, metadata = {}) {
// Deactivate previous models
await this.updateMany(
{ user: userId, isActive: true },
{ $set: { isActive: false } }
);

// Get next version
const lastModel = await this.findOne({ user: userId }).sort({ version: -1 });
const nextVersion = lastModel ? lastModel.version + 1 : 1;

const newModel = new this({
user: userId,
modelData,
version: nextVersion,
metadata,
lastTrained: new Date()
});

return await newModel.save();
};

// Static method to get model history
categoryModelSchema.statics.getModelHistory = async function(userId, limit = 10) {
return await this.find({ user: userId })
.sort({ version: -1 })
.limit(limit);
};

module.exports = mongoose.model('CategoryModel', categoryModelSchema);
Empty file added public/ai-insights-dashboard.js
Empty file.
60 changes: 59 additions & 1 deletion routes/categorization.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ router.delete('/patterns', auth, async (req, res) => {
router.get('/stats', auth, async (req, res) => {
try {
const stats = await categorizationService.getUserStats(req.user._id);

res.json({
success: true,
data: stats
Expand All @@ -232,4 +232,62 @@ router.get('/stats', auth, async (req, res) => {
}
});

/**
* @route GET /api/categorization/analytics
* @desc Get user's categorization analytics
* @access Private
*/
router.get('/analytics', auth, async (req, res) => {
try {
const { days = 30 } = req.query;
const CategoryAnalytics = require('../models/CategoryAnalytics');

const analytics = await CategoryAnalytics.getUserAnalytics(req.user._id, parseInt(days));

res.json({
success: true,
data: analytics
});
} catch (error) {
console.error('Get analytics error:', error);
res.status(500).json({
success: false,
message: 'Error fetching analytics',
error: error.message
});
}
});

/**
* @route POST /api/categorization/record-prediction
* @desc Record a prediction result for analytics
* @access Private
*/
router.post('/record-prediction', auth, async (req, res) => {
try {
const { prediction, actualCategory, confidence } = req.body;
const CategoryAnalytics = require('../models/CategoryAnalytics');

const analytics = await CategoryAnalytics.recordPrediction(
req.user._id,
prediction,
actualCategory,
confidence
);

res.json({
success: true,
message: 'Prediction recorded',
data: analytics
});
} catch (error) {
console.error('Record prediction error:', error);
res.status(500).json({
success: false,
message: 'Error recording prediction',
error: error.message
});
}
});

module.exports = router;
Loading