Skip to content

Analytics Service API

Current Endpoints

  • GET /healthz
  • GET /readyz
  • GET /v1
  • GET /v1/analytics/results
  • PUT /v1/analytics/results/{attemptId}/snapshot
  • GET /v1/analytics/weak-topics
  • GET /v1/analytics/weak-topic-alert-intents
  • GET /v1/analytics/exams/{examId}/summary
  • GET /v1/analytics/classrooms/{classroomId}/summary
  • GET /v1/analytics/students/{studentId}/summary
  • POST /v1/analytics/events
  • GET /v1/analytics/events
  • POST /v1/analytics/rebuild-reports
  • GET /v1/analytics/rebuild-reports
  • POST /internal/v1/analytics/rebuild/attempt-results (internal only)
  • POST /internal/v1/analytics/rebuild/course-learning (internal only)
  • GET /internal/v1/analytics/courses/{courseId}/masteries (internal only)

Native Analytics Foundation

Phase 9 starts analytics-service with event ingestion and small result projections. Public /api/analytics*, /api/students*, /api/parents*, /api/exams/*/analytics, and classroom report routes remain legacy-proxied until gateway adapters can hydrate user/classroom/exam/question details and enforce the current role checks.

Legacy evidence:

  • node-platform/apps/api/src/modules/analytics/analytics.controller.ts:14-139 maps analytics result, weak-topic, exam, classroom, student, parent, and export routes.
  • node-platform/apps/api/src/modules/analytics/analytics.service.ts:7-51 delegates all analytics reads to AppDataService.
  • node-platform/apps/api/src/modules/app-data/app-data.exams-analytics.ts:173-355 implements analyticsResults and weakTopics from graded attempts, answers, exams, classroom assignments, subjects, topics, and chapters.
  • node-platform/apps/api/src/modules/app-data/app-data.progress-overview.ts:4-317 implements classroom progress, parent child listing, student progress, and parent progress by joining attempts, classroom membership, learning progress, notifications, assignments, and result visibility rules.
  • node-platform/apps/api/src/modules/progress/progress.controller.ts:11-49 records lesson/video/material progress.
  • node-platform/apps/api/src/modules/app-data/app-data.learning-progress.ts:20-154 clamps progress, maps status, auto-completes video at 95 percent, and upserts material views.
  • node-platform/apps/api/prisma/schema.prisma:868-923 defines classroom lesson/material/video progress tables.
  • node-platform/apps/api/prisma/schema.prisma:1080-1210 defines course progress, material view, video progress, and StudentMastery.
  • node-platform/apps/api/prisma/schema.prisma:2448-2534 defines ExamAttempt, ExamAttemptQuestion, ExamAnswer, and ExamEvent.
  • node-platform/apps/web/app/teacher/analytics/page.tsx:52-53 consumes /analytics/results?limit=80 and /analytics/weak-topics.
  • node-platform/apps/web/app/teacher/results/page.tsx:232-261 consumes filtered analytics result and weak-topic endpoints.
  • node-platform/apps/web/app/parent/dashboard/page.tsx:70-75 and node-platform/apps/web/app/parent/children/[id]/progress/page.tsx:28-33 consume parent progress endpoints.

Native contract:

  • PUT /v1/analytics/results/{attemptId}/snapshot upserts an already-hydrated attempt result projection from attempt-service/gateway/backfill. Analytics-service stores references and display snapshots only; it does not query exam, attempt, user, classroom, or question databases.
  • GET /v1/analytics/results supports classroomId, examId, subjectId, gradeLevel, difficulty, topic, from, to, page, and limit. Page defaults to 1, limit defaults to 20, and limit is clamped to 1..100.
  • Result rows preserve the frontend-facing legacy shape: attemptId, student, optional classroom, exam, score, correctCount, wrongCount, durationSeconds, status, submittedAt, and weakTopics. The exam snapshot also carries optional uppercase engine difficulties (EASY, MEDIUM, HARD, VERY_HARD) projected from the source exam's question difficulties. The difficulty query filter matches rows whose projected exam.difficulties contains the requested value.
  • GET /v1/analytics/weak-topics mirrors the legacy derivation from the first 100 matching analytics results: each topic in a result row increments wrongCount and attemptCount; accuracy is max(0, 1 - wrongCount / attemptCount); shouldAlert is true when wrongCount >= 2 or accuracy < 0.6. Native weak-topic filters use the same result filter parser, including difficulty against projected exam.difficulties, while public default routing stays legacy until access adapters are proven.
  • GET /v1/analytics/weak-topic-alert-intents reuses that weak-topic rollup and returns only topics whose shouldAlert is true. Each intent includes sourceService=analytics-service, a deterministic sourceEventId derived from the normalized filters plus topic, eventType=analytics.weak_topic.alert_intent, preferenceType=WEAK_TOPIC_ALERT, notificationType=WEAK_TOPIC_ALERT, topic counts, affectedCount, scope/filter metadata, and a payload that a notification-service event adapter can use after recipient enrichment.
  • Weak-topic alert intents are metadata only. Analytics-service does not resolve parent recipient ids, does not call notification-service, and does not read profile, classroom, exam, attempt, question-bank, or notification databases for this preview route.
  • Summary endpoints compute attemptCount, averageScore, correctCount, wrongCount, and weak topics from the service-owned result projection.
  • POST /v1/analytics/events records service-local analytics projection events. Producers may pass sourceService and sourceEventId together; the pair is replay-idempotent and re-sending the same producer event returns the original analytics event without mutation.
  • Current best-effort producers include attempt submitted/graded, exam published/assigned/results-released, course enrollment/lesson/video/material progress, and classroom membership snapshot events. Analytics still treats these as copied event inputs and does not read producer databases directly.
  • GET /v1/analytics/events supports sourceService alongside type, entity, entityId, from, to, page, and limit for backfill/rebuild audits.
  • POST /v1/analytics/rebuild-reports records internal rebuild/backfill metadata from owner-service event streams or read-only report jobs. Producers may pass sourceService and sourceEventId together; the pair is replay-idempotent and re-sending the same report returns the original report without mutation.
  • Rebuild report rows carry scope, optional scopeId, status=QUEUED|RUNNING|COMPLETED|FAILED, source ids, counters for inputEvents, projectedResults, skipped, and failed, optional error/metadata, and started/finished timestamps. They are evidence for projection maintenance; they do not run a rebuild worker by themselves.
  • GET /v1/analytics/rebuild-reports supports organizationId, sourceService, scope, status, from, to, page, and limit for cutover/backfill audit reports.
  • POST /internal/v1/analytics/rebuild/attempt-results materializes only stored attempt-service attempt.graded.v1 events for the caller's organization. It requires schema version 1, the copied attempt/student/exam/result/question snapshot, and a persisted student fullName; malformed events produce a failed rebuild report without a partial result. Each successful event writes an attempt-result-v1 receipt and its result projection in one transaction. The endpoint is not in any Gateway route table and is not a public frontend API. It returns the persisted report plus an (occurredAt,eventId) cursor for the next batch.
  • POST /internal/v1/analytics/rebuild/course-learning materializes only stored owner snapshots for course.learning_catalog.changed.v1, course.lesson_progress.saved.v1, and attempt.graded.v2 in one organization. It requires the Course catalog revision/placement snapshot, the latest bounded lesson progress payload, and a Course-validated context whose student and organization match the graded attempt. The materializer never calls Course, Attempt, Exam, Question, Profile, or legacy storage: it keeps local catalog/progress/attempt-fact rows, recalculates only from those rows, and writes a separate course-learning-v1 receipt for every accepted source event. Generic v1 attempts never create course mastery rows.
  • Course catalog snapshots originate from Course's owner-only GET /v1/courses/learning-catalog-events feed and must be copied into the Analytics event ledger by an explicit owner-feed replay adapter before this materializer runs. Analytics-service has no in-process poller in this slice; the materializer deliberately does not call that feed while calculating rows.
  • GET /internal/v1/analytics/courses/{courseId}/masteries returns only local mastery rows. STUDENT is forced to their own studentId; TEACHER and ADMIN can request a student within their organization; PARENT is denied. This is still not browser authorization: a Course/Gateway adapter must re-check active enrollment or current teacher visibility before exposing the response, and the endpoint is absent from all default and candidate Gateway tables.
  • The bounded SVC-022 workflow no longer requires an external hydrated result write for new attempt.graded.v1 events. Analytics uses only the copied payload, never fills missing data by joining owner databases, and still leaves course mastery, parent progress, classroom exports, and public route adapters as separate slices. See Analytics Rebuildable Projection Proof.
  • Analytics events are not admin audit logs and do not replace admin-service or audit-service ownership.
  • Actor/RBAC checks stay at gateway adapters for public cutover. Native endpoints accept X-Actor-* headers for future adapters but P9-002 treats result snapshots as internal service data.

Database:

  • services/analytics-service/migrations/000002_analytics_foundation.sql creates analytics_attempt_results and analytics_events.
  • services/analytics-service/migrations/000003_analytics_difficulty_filter.sql adds indexed result-projection exam_difficulties for legacy result filter parity.
  • services/analytics-service/migrations/000004_analytics_event_sources.sql adds replay metadata and a partial unique index for analytics producer events.
  • services/analytics-service/migrations/000005_analytics_rebuild_reports.sql creates replay-safe analytics_rebuild_reports for projection rebuild/backfill metadata.
  • services/analytics-service/migrations/000006_analytics_projection_receipts.sql creates the receipt table that makes attempt.graded.v1 result materialization replay-safe.
  • services/analytics-service/migrations/000007_course_learning_projection.sql creates Analytics-owned Course catalog, current lesson-progress, trusted v2 attempt-fact, and mastery tables. A newer catalog revision replaces its local lesson/quiz mappings deterministically; removed mappings remove their derived mastery rows without touching generic result projections.
  • Cross-service ids such as organization_id, student_id, classroom_id, exam_id, and subject_id are stored as public ids only.
  • The internal Course learning materializer now reproduces the legacy 0.5 * correctRate + 0.3 * latestQuizScore + 0.2 * lessonCompletion formula from copied snapshots, with FOUNDATION, REVIEW, PRACTICE, and ADVANCE thresholds. It remains an internal read model: no student/teacher mastery or recommendation endpoint is default-routed, and Analytics must not infer a course from examId alone.

Gateway route rehearsal:

The default gateway route table keeps /api/analytics*, /api/exams*, /api/classrooms*, /api/students*, and /api/parents* traffic on legacy.

Use deploy/gateway/routes.analytics-read-native-example.json for Compose or deploy/gateway/routes.analytics-read-native-localhost-example.json for local go run rehearsal. The non-default table rewrites only:

  • GET /api/analytics/results to /v1/analytics/results.
  • GET /api/analytics/weak-topics to /v1/analytics/weak-topics.

Both routes require gateway auth, global ADMIN/TEACHER role checks, and an organization id. Snapshot/event writes, rebuild reports, exam/classroom/student/ parent analytics, classroom export, and all broad analytics/exam/classroom/ student/parent route families remain legacy-proxied until the missing adapters are explicit.

GET /v1/analytics/weak-topic-alert-intents is not in the non-default public route rehearsal table yet. A gateway/profile adapter must first enrich the intent with recipient ids before calling notification-service.

Rollback:

  • Keep /api/analytics*, /api/students*, /api/parents*, /api/exams/*/results, /api/exams/*/analytics, /api/classrooms/*/analytics, and /api/classrooms/*/report/export routed to legacy.
  • Disable gateway callers for /v1/analytics*.
  • Drop analytics-service local tables with the migration down step if local test data must be reset.

Non-goals for P9-002:

  • Public gateway cutover.
  • Parent progress full parity.
  • Classroom report export.
  • Course mastery/recommendation.
  • Direct joins to exam, attempt, question-bank, user, classroom, notification, or school service databases.

Go-platform documentation is generated from repository Markdown.