apps/recallassess/recallassess-api/src/api/admin/report/report-data.service.ts
Methods |
|
constructor(prisma: BNestPrismaService)
|
||||||
|
Parameters :
|
| Async getPostBatAnalysisGrouped | ||||||
getPostBatAnalysisGrouped(options: literal type)
|
||||||
|
Get Post-BAT analysis with hierarchical grouping (Learning Group → Admin → Participants)
Parameters :
Returns :
unknown
|
| Async getPreBatAnalysisGrouped | ||||||
getPreBatAnalysisGrouped(options: literal type)
|
||||||
|
Get Pre-BAT analysis with hierarchical grouping (Learning Group → Admin → Participants)
Parameters :
Returns :
unknown
|
import { BNestPrismaService } from "@bish-nest/core/services";
import { Injectable } from "@nestjs/common";
@Injectable()
export class ReportDataService {
constructor(private readonly prisma: BNestPrismaService) {}
/**
* Get Pre-BAT analysis with hierarchical grouping (Learning Group → Admin → Participants)
*/
async getPreBatAnalysisGrouped(options: { companyId?: number }) {
const where: any = {};
if (options.companyId) {
where.participant = { company_id: options.companyId };
}
// Get all assessment participants with PRE_BAT
// Note: We include participants with or without learning groups
// Those without learning groups will be grouped under "Ungrouped"
const assessmentParticipants = await this.prisma.client.assessmentParticipant.findMany({
where: {
...where,
assessment_type: "PRE_BAT",
},
include: {
participant: true,
course: true,
learningGroup: {
include: {
learningGroupParticipants: {
include: {
invitedBy: {
select: {
id: true,
first_name: true,
last_name: true,
email: true,
},
},
},
},
},
},
assessmentResults: true,
},
orderBy: { created_at: "desc" },
});
// Group by Learning Group → Admin → Participant
const learningGroupMap = new Map<number, any>();
assessmentParticipants.forEach((ap) => {
// Handle participants with or without learning groups
const learningGroupId = ap.learning_group_id || 0; // Use 0 for ungrouped
const learningGroup = ap.learningGroup;
if (!learningGroupMap.has(learningGroupId)) {
learningGroupMap.set(learningGroupId, {
learningGroupId,
learningGroupName:
learningGroup?.name || (learningGroupId === 0 ? "Ungrouped Participants" : "Unknown Learning Group"),
admins: new Map<number, any>(),
});
}
const lgEntry = learningGroupMap.get(learningGroupId)!;
// Find the admin who invited this participant
// Only look for admin if participant is in a learning group
let adminId = 0;
let admin = null;
if (learningGroup && learningGroup.learningGroupParticipants) {
const lgParticipant = learningGroup.learningGroupParticipants.find(
(lgp) => lgp.participant_id === ap.participant_id && lgp.course_id === ap.course_id,
);
adminId = lgParticipant?.participant_id_invited_by || 0;
admin = lgParticipant?.invitedBy;
}
if (!lgEntry.admins.has(adminId)) {
lgEntry.admins.set(adminId, {
adminId,
adminName: admin ? `${admin.first_name} ${admin.last_name}` : "Unknown Admin",
adminEmail: admin?.email || null,
participants: [],
stats: {
total: 0,
completed: 0,
pending: 0,
completionRate: 0,
averageScore: 0,
},
});
}
const adminEntry = lgEntry.admins.get(adminId)!;
const hasScore =
ap.individual_quotient !== null &&
ap.individual_quotient !== undefined &&
!isNaN(Number(ap.individual_quotient));
const participantData = {
participantId: ap.participant_id,
participantName: `${ap.participant.first_name} ${ap.participant.last_name}`,
participantEmail: ap.participant.email,
courseId: ap.course_id,
courseName: ap.course.title,
completed: hasScore,
completionDate: ap.assessment_completion_date
? new Date(ap.assessment_completion_date).toISOString().split("T")[0]
: null,
score: hasScore ? Number(ap.individual_quotient) : null,
ipq: hasScore ? Number(ap.individual_quotient) : null,
};
adminEntry.participants.push(participantData);
adminEntry.stats.total++;
if (hasScore) {
adminEntry.stats.completed++;
} else {
adminEntry.stats.pending++;
}
});
// Calculate stats for each admin and learning group
const learningGroups: any[] = [];
let totalParticipants = 0;
let totalCompleted = 0;
let totalPending = 0;
learningGroupMap.forEach((lgEntry) => {
const admins: any[] = [];
let lgTotal = 0;
let lgCompleted = 0;
let lgPending = 0;
lgEntry.admins.forEach((adminEntry: any) => {
// Calculate admin stats
adminEntry.stats.completionRate =
adminEntry.stats.total > 0
? Number(((adminEntry.stats.completed / adminEntry.stats.total) * 100).toFixed(1))
: 0;
const completedScores = adminEntry.participants
.filter((p: any) => p.completed && p.score !== null)
.map((p: any) => p.score);
adminEntry.stats.averageScore =
completedScores.length > 0
? Number(
(completedScores.reduce((a: number, b: number) => a + b, 0) / completedScores.length).toFixed(2),
)
: 0;
admins.push({
...adminEntry,
participants: adminEntry.participants.sort((a: any, b: any) =>
a.participantName.localeCompare(b.participantName),
),
});
lgTotal += adminEntry.stats.total;
lgCompleted += adminEntry.stats.completed;
lgPending += adminEntry.stats.pending;
});
learningGroups.push({
learningGroupId: lgEntry.learningGroupId,
learningGroupName: lgEntry.learningGroupName,
admins: admins.sort((a, b) => a.adminName.localeCompare(b.adminName)),
stats: {
total: lgTotal,
completed: lgCompleted,
pending: lgPending,
completionRate: lgTotal > 0 ? Number(((lgCompleted / lgTotal) * 100).toFixed(1)) : 0,
},
});
totalParticipants += lgTotal;
totalCompleted += lgCompleted;
totalPending += lgPending;
});
const result = {
summary: {
totalParticipants,
completed: totalCompleted,
pending: totalPending,
completionRate:
totalParticipants > 0 ? Number(((totalCompleted / totalParticipants) * 100).toFixed(1)) : 0,
},
data: learningGroups.sort((a, b) => a.learningGroupName.localeCompare(b.learningGroupName)),
};
console.log(
`[Pre-BAT Grouped Report] Found ${assessmentParticipants.length} assessment participants, grouped into ${learningGroups.length} learning groups, total participants: ${totalParticipants}`,
);
return result;
}
/**
* Get Post-BAT analysis with hierarchical grouping (Learning Group → Admin → Participants)
*/
async getPostBatAnalysisGrouped(options: { companyId?: number }) {
const where: any = {};
if (options.companyId) {
where.participant = { company_id: options.companyId };
}
// Get all assessment participants with POST_BAT
const assessmentParticipants = await this.prisma.client.assessmentParticipant.findMany({
where: {
...where,
learning_group_id: { not: null },
assessment_type: "POST_BAT",
},
include: {
participant: true,
course: true,
learningGroup: {
include: {
learningGroupParticipants: {
include: {
invitedBy: {
select: {
id: true,
first_name: true,
last_name: true,
email: true,
},
},
},
},
},
},
assessmentResults: true,
},
orderBy: { created_at: "desc" },
});
// Also get PRE_BAT scores for comparison
const preBatParticipants = await this.prisma.client.assessmentParticipant.findMany({
where: {
...where,
learning_group_id: { not: null },
assessment_type: "PRE_BAT",
},
include: {
participant: true,
course: true,
},
});
// Create a map of pre-BAT scores
const preBatScoreMap = new Map<string, number>();
preBatParticipants.forEach((ap) => {
const key = `${ap.participant_id}-${ap.course_id}`;
if (ap.individual_quotient !== null && ap.individual_quotient !== undefined) {
preBatScoreMap.set(key, Number(ap.individual_quotient));
}
});
// Group by Learning Group → Admin → Participant
const learningGroupMap = new Map<number, any>();
assessmentParticipants.forEach((ap) => {
const learningGroupId = ap.learning_group_id!;
const learningGroup = ap.learningGroup;
if (!learningGroupMap.has(learningGroupId)) {
learningGroupMap.set(learningGroupId, {
learningGroupId,
learningGroupName: learningGroup?.name || "Unknown Learning Group",
admins: new Map<number, any>(),
});
}
const lgEntry = learningGroupMap.get(learningGroupId)!;
// Find the admin who invited this participant
const lgParticipant = learningGroup?.learningGroupParticipants?.find(
(lgp) => lgp.participant_id === ap.participant_id && lgp.course_id === ap.course_id,
);
const adminId = lgParticipant?.participant_id_invited_by || 0;
const admin = lgParticipant?.invitedBy;
if (!lgEntry.admins.has(adminId)) {
lgEntry.admins.set(adminId, {
adminId,
adminName: admin ? `${admin.first_name} ${admin.last_name}` : "Unknown Admin",
adminEmail: admin?.email || null,
participants: [],
stats: {
total: 0,
completed: 0,
pending: 0,
completionRate: 0,
averageScore: 0,
averageImprovement: 0,
},
});
}
const adminEntry = lgEntry.admins.get(adminId)!;
const hasScore =
ap.individual_quotient !== null &&
ap.individual_quotient !== undefined &&
!isNaN(Number(ap.individual_quotient));
const key = `${ap.participant_id}-${ap.course_id}`;
const preScore = preBatScoreMap.get(key) || null;
const postScore = hasScore ? Number(ap.individual_quotient) : null;
const improvement =
preScore !== null && postScore !== null
? Number(((postScore - preScore) / Math.abs(preScore)) * 100).toFixed(2)
: null;
const participantData = {
participantId: ap.participant_id,
participantName: `${ap.participant.first_name} ${ap.participant.last_name}`,
participantEmail: ap.participant.email,
courseId: ap.course_id,
courseName: ap.course.title,
completed: hasScore,
completionDate: ap.assessment_completion_date
? new Date(ap.assessment_completion_date).toISOString().split("T")[0]
: null,
preScore,
postScore,
improvement: improvement !== null ? Number(improvement) : null,
};
adminEntry.participants.push(participantData);
adminEntry.stats.total++;
if (hasScore) {
adminEntry.stats.completed++;
} else {
adminEntry.stats.pending++;
}
});
// Calculate stats for each admin and learning group
const learningGroups: any[] = [];
let totalParticipants = 0;
let totalCompleted = 0;
let totalPending = 0;
learningGroupMap.forEach((lgEntry) => {
const admins: any[] = [];
let lgTotal = 0;
let lgCompleted = 0;
let lgPending = 0;
lgEntry.admins.forEach((adminEntry: any) => {
// Calculate admin stats
adminEntry.stats.completionRate =
adminEntry.stats.total > 0
? Number(((adminEntry.stats.completed / adminEntry.stats.total) * 100).toFixed(1))
: 0;
const completedScores = adminEntry.participants
.filter((p: any) => p.completed && p.postScore !== null)
.map((p: any) => p.postScore);
adminEntry.stats.averageScore =
completedScores.length > 0
? Number(
(completedScores.reduce((a: number, b: number) => a + b, 0) / completedScores.length).toFixed(2),
)
: 0;
const improvements = adminEntry.participants
.filter((p: any) => p.improvement !== null)
.map((p: any) => p.improvement);
adminEntry.stats.averageImprovement =
improvements.length > 0
? Number((improvements.reduce((a: number, b: number) => a + b, 0) / improvements.length).toFixed(2))
: 0;
admins.push({
...adminEntry,
participants: adminEntry.participants.sort((a: any, b: any) =>
a.participantName.localeCompare(b.participantName),
),
});
lgTotal += adminEntry.stats.total;
lgCompleted += adminEntry.stats.completed;
lgPending += adminEntry.stats.pending;
});
learningGroups.push({
learningGroupId: lgEntry.learningGroupId,
learningGroupName: lgEntry.learningGroupName,
admins: admins.sort((a, b) => a.adminName.localeCompare(b.adminName)),
stats: {
total: lgTotal,
completed: lgCompleted,
pending: lgPending,
completionRate: lgTotal > 0 ? Number(((lgCompleted / lgTotal) * 100).toFixed(1)) : 0,
},
});
totalParticipants += lgTotal;
totalCompleted += lgCompleted;
totalPending += lgPending;
});
return {
summary: {
totalParticipants,
completed: totalCompleted,
pending: totalPending,
completionRate:
totalParticipants > 0 ? Number(((totalCompleted / totalParticipants) * 100).toFixed(1)) : 0,
},
data: learningGroups.sort((a, b) => a.learningGroupName.localeCompare(b.learningGroupName)),
};
}
}