Files
gruperly/apps/backend/src/modules/analytics/lib/risk.ts
Jose Selesan 2e184845e1 feat: add absence notification and analytics features
- Implemented AbsenceNotifyView for notifying absence with session details.
- Created AnalyticsView for displaying group analytics and managing students at risk.
- Added ClassAttendanceView for marking attendance in classes.
- Defined new schemas for analytics and attendance in shared package.
2026-09-23 16:24:45 -03:00

56 lines
1.6 KiB
TypeScript

export type AttendanceStreamEntry = {
status: 'PRESENT' | 'ABSENT' | 'EXCUSED';
occurredAt: Date;
};
export type RiskLevel = 'HIGH' | 'MEDIUM';
// Cuenta las ausencias consecutivas sin justificar desde la clase más reciente.
// Recibe la lista ordenada de más reciente a más antigua; PRESENT y EXCUSED rompen la cadena.
export function trailingAbsentStreak(entries: AttendanceStreamEntry[]): number {
let streak = 0;
for (const entry of entries) {
if (entry.status !== 'ABSENT') {
break;
}
streak += 1;
}
return streak;
}
// Fecha de la última clase a la que el alumno asistió (PRESENT), o null.
export function lastAttendedAt(entries: AttendanceStreamEntry[]): Date | null {
for (const entry of entries) {
if (entry.status === 'PRESENT') {
return entry.occurredAt;
}
}
return null;
}
// Porcentaje de asistencia sobre el total de sesiones del período.
// Las sesiones sin registrar cuentan como no asistidas.
export function monthlyAttendanceRate(
entries: AttendanceStreamEntry[],
totalSessions: number,
): number {
if (totalSessions <= 0) {
return 0;
}
const present = entries.filter((entry) => entry.status === 'PRESENT').length;
return Math.round((present / totalSessions) * 100);
}
// HIGH >= 3 ausencias consecutivas; MEDIUM == 2 consecutivas o asistencia mensual < 50%.
export function classifyRisk(
consecutiveAbsences: number,
monthlyRate: number,
): RiskLevel | null {
if (consecutiveAbsences >= 3) {
return 'HIGH';
}
if (consecutiveAbsences === 2 || monthlyRate < 50) {
return 'MEDIUM';
}
return null;
}