feat(backend): real analytics aggregation pipeline (resolves #26) - #137
Merged
jobbykings merged 1 commit intoJun 26, 2026
Merged
Conversation
…pondia#26) Resolves Epondia#26 ("[Backend] Real course analytics with database aggregation pipeline") assigned to gbengaeben. - Add /api/v1/analytics/enrollment-trends and /api/v1/analytics/completion-rates built on Postgres DATE_TRUNC + COUNT(*) FILTER aggregation against activity_logs. - Add typed Course/Enrollment/User analytics facades (backend/src/models/analytics.ts) that delegate to AnalyticsService for PII-safe population aggregates. - Add anonymized student-performance aggregate (activeUsers, events-per-user, avg days enrollment->completion). - Drop the @ts-ignore on analyticsService.ts by exposing a real getRedisClient() getter on the redis module. - Refactor analyticsController to delegate to AnalyticsService; remove the duplicate pg.Pool used for analytics paths. Legacy /export endpoint keeps its own pool via lazy getter. - Apply a controller-boundary stripPII helper to every analytics response so userId / source_account / source / ip / owner never reach a client. - Migration 002_add_analytics_indexes.sql adds (type,timestamp), (source_account,type), and (details,type) indexes that meet the large date-range perf budget in the DoD. - New tests in tests/dataAggregationTrends.test.js and tests/routes/analyticsAggregation.test.js exercise shape, division-by-zero safety, granularity validation, and PII invariants.
jobbykings
approved these changes
Jun 26, 2026
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the real analytics aggregation pipeline requested in Issue #26 ("[Backend] Real course analytics with database aggregation pipeline"), assigned to @gbengaeben.
Closes #26
What changed
GET /api/v1/analytics/enrollment-trends?startDate=&endDate=&granularity=day|week|month&courseId=returns PostgresDATE_TRUNC-bucketed event counts.GET /api/v1/analytics/completion-rates?startDate=&endDate=&courseId=returns enrolled/completed aggregates with optional per-course breakdown.GET /api/v1/analytics/completion-rates(no courseId) returns a top-100 per-course breakdown plus platform-wide totals.DataAggregationService:getEnrollmentTrends(usesDATE_TRUNC($1, timestamp)grouped by(type, source)).getCompletionRates(usesCOUNT(*) FILTER (WHERE type = ...)aggregates).getStudentPerformanceMetrics(self-join bounded to the request window on both legs; never exposessource_account).backend/src/models/analytics.ts(CourseAnalytics,EnrollmentAnalytics,UserAnalytics) that satisfy the DoD requirement for "aggregation helper methods" without duplicating SQL. All return PII-safe aggregates only.@ts-ignoreremoved: the brokenimport { redisClient }onanalyticsService.tsis replaced bygetRedisClient()(a new typed export onbackend/src/utils/redis.ts).analyticsController.jsnow delegates toAnalyticsServiceinstead of maintaining its own ad-hoc pg.Pool. The legacy/exportendpoint keeps its own pool via a lazygetExportPool()getter so module import has no side effects and nodotenv.config()is called here (already loaded bysrc/index.js).stripPIIhelper that nullsuserId,source_account,source,ip, andownerso the Issue [Backend] Real course analytics with database aggregation pipeline #26 DoD ("No PII exposed in aggregated analytics responses") is enforced at the boundary, even for legacy endpoints we did not redesign (/report).backend/migrations/002_add_analytics_indexes.sqladds(type, timestamp),(source_account, type), and partial(details, type)indexes onactivity_logsto meet the <2s large-date-range perf budget called out by the DoD.Tests
All 18 new tests pass. Two new suites, both using
jest.spyOnso they remain cache-resilient againsttests/setup.jswarming the module graph withsrc/index:backend/tests/dataAggregationTrends.test.js— unit tests for the new aggregation methods (shape, division-by-zero, missing-table fallback, parameter binding, PII invariants).backend/tests/routes/analyticsAggregation.test.js— endpoint tests via supertest for/enrollment-trendsand/completion-rates(query-param forwarding, default-granularity fallback, error propagation, PII invariants).Local results:
Definition of Done mapping
getEnrollmentTrendsreadsactivity_logsevents of typecourse_enrollment.getCompletionRatesjoinscourse_enrollment+course_completionevents.getStudentPerformanceMetricsaggregates event-level data.getDashboardStats/getStudentPerformanceMetricsand protected bystripPIIat the controller boundary.002_add_analytics_indexes.sql.@ts-ignorecomments remain in analytics code — redis import fixed inanalyticsService.ts.Acceptance criteria mapping
GET /api/analytics/enrollment-trendsreturns real enrollment data over time.GET /api/analytics/completion-ratesreturns actual completion percentages.getCourseAnalyticsalready existed; the new endpoints also acceptcourseIdfor that path.stripPII.@ts-ignorein analytics files.Out of scope / follow-ups (NOT introduced here)
backend/tests/api.test.jsandbackend/tests/analytics.test.jsreference analytics routes / services that were never wired up (/api/v1/analytics/students/:studentId,StudentAnalyticsService, etc.). They are independent of this PR and out of scope; flagging so reviewers do not misattribute them to these changes./overview,/report,/enrollment-trends,/completion-ratesis not added in this PR. All require auth in production; that wiring is left for a follow-up issue.Files touched