Prashikshak / API /src /controller /analytics.controller.ts
Abhisingh-18's picture
Initial commit: Prashikshak - disaster management training platform
9a92a42
Raw
History Blame Contribute Delete
14.6 kB
import { Request, Response } from 'express';
import Event from '../model/event.model';
import User from '../model/user.model';
import { Parser } from 'json2csv';
interface IAnalyticsRequest extends Request {
userId?: string;
}
// ==================== GET EVENT MAP DATA ====================
export const getEventMapData = async (req: IAnalyticsRequest, res: Response) => {
try {
const {
theme,
organization,
startDate,
endDate,
status = 'published'
} = req.query;
const filter: any = {
status,
location: { $exists: true } // Only events with location
};
if (theme) filter.theme = theme;
if (organization) filter.organization = organization;
if (startDate || endDate) {
filter.startDate = {};
if (startDate) filter.startDate.$gte = new Date(startDate as string);
if (endDate) filter.startDate.$lte = new Date(endDate as string);
}
const events = await Event.find(filter)
.populate('organization', 'username organizationType')
.populate('createdBy', 'username')
.select('title code theme location startDate endDate type participantCount venue');
// Format for map markers
const mapData = events.map(event => ({
id: event._id,
title: event.title,
code: event.code,
theme: event.theme,
organization: (event.organization as any).username,
coordinates: event.location?.coordinates,
latitude: event.location?.coordinates[1],
longitude: event.location?.coordinates[0],
startDate: event.startDate,
endDate: event.endDate,
type: event.type,
venue: event.venue,
participantCount: event.participants.length,
capacity: event.capacity
}));
return res.json({
totalEvents: mapData.length,
events: mapData
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== GET COVERAGE HEATMAP DATA ====================
export const getHeatmapData = async (req: IAnalyticsRequest, res: Response) => {
try {
const { theme, startDate, endDate } = req.query;
const filter: any = {
status: { $in: ['published', 'completed'] },
location: { $exists: true }
};
if (theme) filter.theme = theme;
if (startDate || endDate) {
filter.startDate = {};
if (startDate) filter.startDate.$gte = new Date(startDate as string);
if (endDate) filter.startDate.$lte = new Date(endDate as string);
}
const events = await Event.find(filter)
.select('location participants');
// Create heatmap points (lat, lng, weight)
const heatmapPoints = events.map(event => ({
lat: event.location?.coordinates[1],
lng: event.location?.coordinates[0],
weight: event.participants.length // Weight by attendance
}));
return res.json({ points: heatmapPoints });
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== GET THEME COVERAGE STATISTICS ====================
export const getThemeCoverage = async (req: IAnalyticsRequest, res: Response) => {
try {
const { startDate, endDate, organization } = req.query;
const filter: any = {
status: { $in: ['published', 'completed'] }
};
if (organization) filter.organization = organization;
if (startDate || endDate) {
filter.startDate = {};
if (startDate) filter.startDate.$gte = new Date(startDate as string);
if (endDate) filter.startDate.$lte = new Date(endDate as string);
}
// Aggregate by theme
const themeCoverage = await Event.aggregate([
{ $match: filter },
{
$group: {
_id: '$theme',
eventCount: { $sum: 1 },
totalParticipants: { $sum: { $size: '$participants' } },
totalCapacity: { $sum: '$capacity' },
avgAttendance: { $avg: { $size: '$participants' } }
}
},
{ $sort: { eventCount: -1 } }
]);
return res.json({ themeCoverage });
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== GET ORGANIZATION STATISTICS ====================
export const getOrganizationStats = async (req: IAnalyticsRequest, res: Response) => {
try {
const { startDate, endDate } = req.query;
const filter: any = {
status: { $in: ['published', 'completed'] }
};
if (startDate || endDate) {
filter.startDate = {};
if (startDate) filter.startDate.$gte = new Date(startDate as string);
if (endDate) filter.startDate.$lte = new Date(endDate as string);
}
const orgStats = await Event.aggregate([
{ $match: filter },
{
$group: {
_id: '$organization',
eventCount: { $sum: 1 },
totalParticipants: { $sum: { $size: '$participants' } },
totalCapacity: { $sum: '$capacity' },
themes: { $addToSet: '$theme' }
}
},
{
$lookup: {
from: 'users',
localField: '_id',
foreignField: '_id',
as: 'orgDetails'
}
},
{
$project: {
organization: { $arrayElemAt: ['$orgDetails.username', 0] },
organizationType: { $arrayElemAt: ['$orgDetails.organizationType', 0] },
eventCount: 1,
totalParticipants: 1,
totalCapacity: 1,
themeCount: { $size: '$themes' },
utilizationRate: {
$multiply: [
{ $divide: ['$totalParticipants', '$totalCapacity'] },
100
]
}
}
},
{ $sort: { eventCount: -1 } }
]);
return res.json({ organizationStats: orgStats });
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== GET TRAINER ACTIVITY ====================
export const getTrainerActivity = async (req: IAnalyticsRequest, res: Response) => {
try {
const { organization, startDate, endDate } = req.query;
const filter: any = {
status: { $in: ['published', 'completed'] }
};
if (organization) filter.organization = organization;
if (startDate || endDate) {
filter.startDate = {};
if (startDate) filter.startDate.$gte = new Date(startDate as string);
if (endDate) filter.startDate.$lte = new Date(endDate as string);
}
const trainerStats = await Event.aggregate([
{ $match: filter },
{
$group: {
_id: '$createdBy',
eventCount: { $sum: 1 },
totalParticipants: { $sum: { $size: '$participants' } },
themes: { $addToSet: '$theme' }
}
},
{
$lookup: {
from: 'users',
localField: '_id',
foreignField: '_id',
as: 'trainerDetails'
}
},
{
$project: {
trainer: { $arrayElemAt: ['$trainerDetails.username', 0] },
email: { $arrayElemAt: ['$trainerDetails.email', 0] },
workDesignation: { $arrayElemAt: ['$trainerDetails.workDesignation', 0] },
eventCount: 1,
totalParticipants: 1,
themeCount: { $size: '$themes' },
avgParticipantsPerEvent: {
$divide: ['$totalParticipants', '$eventCount']
}
}
},
{ $sort: { eventCount: -1 } }
]);
return res.json({ trainerActivity: trainerStats });
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== GET DISTRICT COVERAGE ====================
export const getDistrictCoverage = async (req: IAnalyticsRequest, res: Response) => {
try {
// This would require reverse geocoding to get district from coordinates
// For now, returning placeholder structure
// You'd integrate with a geocoding service (Google Maps, Mapbox, etc.)
const { state } = req.query;
// Placeholder - implement reverse geocoding
return res.json({
message: 'District coverage requires geocoding integration',
placeholder: {
state: state || 'Maharashtra',
districts: [
{ name: 'Mumbai', eventCount: 45, participantCount: 1200 },
{ name: 'Pune', eventCount: 32, participantCount: 890 },
{ name: 'Nagpur', eventCount: 18, participantCount: 450 }
]
}
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== EXPORT ANALYTICS TO CSV ====================
export const exportAnalyticsToCsv = async (req: IAnalyticsRequest, res: Response) => {
try {
const { startDate, endDate, organization, theme } = req.query;
const filter: any = {
status: { $in: ['published', 'completed'] }
};
if (organization) filter.organization = organization;
if (theme) filter.theme = theme;
if (startDate || endDate) {
filter.startDate = {};
if (startDate) filter.startDate.$gte = new Date(startDate as string);
if (endDate) filter.startDate.$lte = new Date(endDate as string);
}
const events = await Event.find(filter)
.populate('organization', 'username organizationType')
.populate('createdBy', 'username email')
.lean();
// Format data for CSV
const csvData = events.map(event => ({
'Event Code': event.code,
'Title': event.title,
'Theme': event.theme,
'Organization': (event.organization as any).username,
'Organization Type': (event.organization as any).organizationType,
'Trainer': (event.createdBy as any).username,
'Trainer Email': (event.createdBy as any).email,
'Type': event.type,
'Venue': event.venue || 'N/A',
'Start Date': new Date(event.startDate).toLocaleDateString(),
'End Date': new Date(event.endDate).toLocaleDateString(),
'Capacity': event.capacity,
'Registered': event.participants.length,
'Attendance': event.participants.filter((p: any) => p.attendance?.checkInTime).length,
'Utilization %': ((event.participants.length / event.capacity) * 100).toFixed(2),
'Status': event.status
}));
const parser = new Parser();
const csv = parser.parse(csvData);
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename=training_analytics.csv');
return res.send(csv);
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== GET DASHBOARD OVERVIEW ====================
export const getDashboardOverview = async (req: IAnalyticsRequest, res: Response) => {
try {
const { startDate, endDate, organization } = req.query;
const filter: any = {};
if (organization) filter.organization = organization;
if (startDate || endDate) {
filter.startDate = {};
if (startDate) filter.startDate.$gte = new Date(startDate as string);
if (endDate) filter.startDate.$lte = new Date(endDate as string);
}
const now = new Date();
// Total events
const totalEvents = await Event.countDocuments(filter);
// Upcoming events
const upcomingEvents = await Event.countDocuments({
...filter,
startDate: { $gt: now },
status: 'published'
});
// Completed events
const completedEvents = await Event.countDocuments({
...filter,
endDate: { $lt: now },
status: { $in: ['completed', 'published'] }
});
// Pending Verifications (Trainers + Organizations)
// Note: This is global unless filtered by organization (for trainers)
const verificationFilter: any = { role: 'organization', verificationStatus: 'pending' };
const pendingOrgVerifications = await User.countDocuments(verificationFilter);
const pendingTrainerFilter: any = { role: 'trainer', organizationVerificationStatus: 'pending' };
if (organization) pendingTrainerFilter.organization = organization;
const pendingTrainerVerifications = await User.countDocuments(pendingTrainerFilter);
const totalPendingVerifications = pendingOrgVerifications + pendingTrainerVerifications;
// Active Trainers
const trainerFilter: any = { role: 'trainer', isActive: true };
if (organization) trainerFilter.organization = organization;
const activeTrainers = await User.countDocuments(trainerFilter);
// Active Organizations
const activeOrganizations = await User.countDocuments({ role: 'organization', verificationStatus: 'approved' });
// Total participants across all events
const participantStats = await Event.aggregate([
{ $match: filter },
{
$group: {
_id: null,
totalParticipants: { $sum: { $size: '$participants' } },
totalCapacity: { $sum: '$capacity' },
totalAttended: {
$sum: {
$size: {
$filter: {
input: '$participants',
as: 'p',
cond: { $ne: ['$$p.attendance.checkInTime', null] }
}
}
}
}
}
}
]);
const stats = participantStats[0] || {
totalParticipants: 0,
totalCapacity: 0,
totalAttended: 0
};
// Unique themes
const themes = await Event.distinct('theme', filter);
return res.json({
overview: {
totalEvents,
upcomingEvents,
completedEvents,
totalParticipants: stats.totalParticipants,
totalCapacity: stats.totalCapacity,
totalAttended: stats.totalAttended,
utilizationRate: stats.totalCapacity > 0
? ((stats.totalParticipants / stats.totalCapacity) * 100).toFixed(2)
: 0,
attendanceRate: stats.totalParticipants > 0
? ((stats.totalAttended / stats.totalParticipants) * 100).toFixed(2)
: 0,
uniqueThemes: themes.length,
activeOrganizations,
activeTrainers,
pendingVerifications: totalPendingVerifications,
pendingOrgVerifications,
pendingTrainerVerifications
}
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};