File

apps/recallassess/recallassess-api/src/api/admin/report/report-data.service.ts

Index

Methods

Constructor

constructor(prisma: BNestPrismaService)
Parameters :
Name Type Optional
prisma BNestPrismaService No

Methods

Async getPostBatAnalysisGrouped
getPostBatAnalysisGrouped(options: literal type)

Get Post-BAT analysis with hierarchical grouping (Learning Group → Admin → Participants)

Parameters :
Name Type Optional
options literal type No
Returns : unknown
Async getPreBatAnalysisGrouped
getPreBatAnalysisGrouped(options: literal type)

Get Pre-BAT analysis with hierarchical grouping (Learning Group → Admin → Participants)

Parameters :
Name Type Optional
options literal type No
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)),
    };
  }
}

results matching ""

    No results matching ""