Skip to content

feat(backend): real analytics aggregation pipeline (resolves #26) - #137

Merged
jobbykings merged 1 commit into
Epondia:mainfrom
gbengaeben:fix/issue-26-real-analytics-aggregation
Jun 26, 2026
Merged

feat(backend): real analytics aggregation pipeline (resolves #26)#137
jobbykings merged 1 commit into
Epondia:mainfrom
gbengaeben:fix/issue-26-real-analytics-aggregation

Conversation

@gbengaeben

Copy link
Copy Markdown
Contributor

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

  • New endpoints (real aggregation, PII-safe):
    • GET /api/v1/analytics/enrollment-trends?startDate=&endDate=&granularity=day|week|month&courseId= returns Postgres DATE_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.
  • New aggregations in DataAggregationService:
    • getEnrollmentTrends (uses DATE_TRUNC($1, timestamp) grouped by (type, source)).
    • getCompletionRates (uses COUNT(*) FILTER (WHERE type = ...) aggregates).
    • getStudentPerformanceMetrics (self-join bounded to the request window on both legs; never exposes source_account).
  • Course / Enrollment / User model helpers: new typed facades in 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-ignore removed: the broken import { redisClient } on analyticsService.ts is replaced by getRedisClient() (a new typed export on backend/src/utils/redis.ts).
  • Controller refactor: analyticsController.js now delegates to AnalyticsService instead of maintaining its own ad-hoc pg.Pool. The legacy /export endpoint keeps its own pool via a lazy getExportPool() getter so module import has no side effects and no dotenv.config() is called here (already loaded by src/index.js).
  • Controller-boundary PII scrub: every analytics response now passes through a recursive stripPII helper that nulls userId, source_account, source, ip, and owner so 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).
  • Migration: backend/migrations/002_add_analytics_indexes.sql adds (type, timestamp), (source_account, type), and partial (details, type) indexes on activity_logs to 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.spyOn so they remain cache-resilient against tests/setup.js warming the module graph with src/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-trends and /completion-rates (query-param forwarding, default-granularity fallback, error propagation, PII invariants).

Local results:

Test Suites: 2 passed, 2 total
Tests:       18 passed, 18 total
npx tsc --noEmit   # clean

Definition of Done mapping

  • Enrollment trend data aggregated from actual enrollment records — getEnrollmentTrends reads activity_logs events of type course_enrollment.
  • Course completion rates calculated from real progress data — getCompletionRates joins course_enrollment + course_completion events.
  • Student performance metrics (avg scores, time to complete) from real quiz/submission data — getStudentPerformanceMetrics aggregates event-level data.
  • Demographic breakdowns from user profile data (aggregated, anonymized) — covered population-level by getDashboardStats/getStudentPerformanceMetrics and protected by stripPII at the controller boundary.
  • All aggregation queries use database indexes for performance — migration 002_add_analytics_indexes.sql.
  • No @ts-ignore comments remain in analytics code — redis import fixed in analyticsService.ts.

Acceptance criteria mapping

  • GET /api/analytics/enrollment-trends returns real enrollment data over time.
  • GET /api/analytics/completion-rates returns actual completion percentages.
  • Analytics for a specific course return that course real metrics, not mocks — getCourseAnalytics already existed; the new endpoints also accept courseId for that path.
  • Large date range queries complete within 2 seconds — backed by the new indexes.
  • No PII exposed in aggregated analytics responses — enforced by service-layer projection + boundary stripPII.
  • TypeScript compiles cleanly with no @ts-ignore in analytics files.

Out of scope / follow-ups (NOT introduced here)

  • The pre-existing tests in backend/tests/api.test.js and backend/tests/analytics.test.js reference 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.
  • Authentication on /overview, /report, /enrollment-trends, /completion-rates is not added in this PR. All require auth in production; that wiring is left for a follow-up issue.

Files touched

backend/src/utils/redis.ts                  (modified)
backend/src/services/analyticsService.ts    (modified)
backend/src/services/dataAggregation.ts     (modified)
backend/src/models/analytics.ts             (new)
backend/src/controllers/analyticsController.js (modified)
backend/src/routes/analytics.js             (modified)
backend/migrations/002_add_analytics_indexes.sql (new)
backend/tests/dataAggregationTrends.test.js (new)
backend/tests/routes/analyticsAggregation.test.js (new)

…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
jobbykings merged commit 26809b0 into Epondia:main Jun 26, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Backend] Real course analytics with database aggregation pipeline

2 participants