forked from Epondia/starked-education
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathanalytics.ts
More file actions
131 lines (118 loc) · 3.79 KB
/
Copy pathanalytics.ts
File metadata and controls
131 lines (118 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* Analytics model facades for Course, Enrollment, User.
*
* Issue #26 ("Real course analytics with database aggregation pipeline")
* requires aggregation helper methods on the Course, Enrollment, and User
* models. In this codebase those TypeScript interfaces (`backend/src/models/
* Course.ts`, `Enrollment.ts`, `User.ts`) are interface-only — the source of
* truth for course events is the `activity_logs` PostgreSQL table.
*
* To satisfy the issue without duplicating SQL strings across the codebase,
* these facades expose typed, PII-safe aggregation helpers that delegate to
* the existing `AnalyticsService`. New endpoints AND tests should prefer
* these classes over the raw service to keep model semantics consistent.
*/
import { AnalyticsService } from '../services/analyticsService';
export interface TrendQuery {
startDate?: Date;
endDate?: Date;
granularity?: 'day' | 'week' | 'month';
}
export interface CompletionQuery {
startDate?: Date;
endDate?: Date;
courseId?: string;
}
export interface EnrollmentTrendPoint {
/** ISO date (YYYY-MM-DD) for the bucket boundary. */
bucket: string;
/** Course identifier (NOT a user identifier - safe to expose). */
courseId: string | null;
enrollments: number;
}
export interface EnrollmentTrendResult {
granularity: 'day' | 'week' | 'month';
points: EnrollmentTrendPoint[];
}
export interface CompletionRateBucket {
courseId: string;
totalEnrollments: number;
completedCount: number;
/** 0–100, rounded to nearest integer. */
completionRate: number;
}
export interface CompletionRateResult {
totalEnrollments: number;
completedCount: number;
/** 0–100, rounded to nearest integer. */
completionRate: number;
byCourse?: CompletionRateBucket[];
}
export interface StudentPerformanceResult {
activeUsers: number;
averageEventsPerUser: number;
/** Null when no enrollment/completion pairs were available. */
courseCompletionAverageDays: number | null;
period: { start: string; end: string };
}
/**
* Course-scoped analytics helpers.
*
* Every method returns aggregates only — never an individual enrollment or
* user record, so PII is never present in responses.
*/
export class CourseAnalytics {
/**
* Bucketed enrollment counts for a single course.
*/
static async getEnrollmentTrends(
courseId: string,
query: TrendQuery = {}
): Promise<EnrollmentTrendResult> {
return AnalyticsService.getEnrollmentTrends({ ...query, courseId });
}
/**
* Overall course completion-rate (enrollments → completions) over a window.
*/
static async getCompletionRate(
courseId: string,
query: Omit<TrendQuery, 'granularity'> = {}
): Promise<CompletionRateResult> {
return AnalyticsService.getCompletionRates({ ...query, courseId });
}
}
/**
* Enrollment-scoped analytics helpers.
*/
export class EnrollmentAnalytics {
/**
* Platform-wide bucketed enrollment counts.
*/
static async getEnrollmentTrends(query: TrendQuery = {}): Promise<EnrollmentTrendResult> {
return AnalyticsService.getEnrollmentTrends(query);
}
/**
* Completion-rate aggregates either globally (per-course breakdown) or for
* a single course.
*/
static async getCompletionRates(query: CompletionQuery = {}): Promise<CompletionRateResult> {
return AnalyticsService.getCompletionRates(query);
}
}
/**
* User-scoped analytics helpers.
* All outputs are anonymized aggregates — no user identifier is ever returned.
*/
export class UserAnalytics {
/**
* Aggregated, anonymized student performance snapshot over a date window.
*/
static async getStudentPerformanceMetrics(
query: TrendQuery = {}
): Promise<StudentPerformanceResult> {
return AnalyticsService.getStudentPerformanceMetrics({
startDate: query.startDate,
endDate: query.endDate,
});
}
}