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.
This commit is contained in:
56
apps/backend/src/modules/analytics/lib/risk.ts
Normal file
56
apps/backend/src/modules/analytics/lib/risk.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user