Appearance
Analytics Service API
Current Endpoints
GET /healthzGET /readyzGET /v1GET /v1/analytics/resultsPUT /v1/analytics/results/{attemptId}/snapshotGET /v1/analytics/weak-topicsGET /v1/analytics/weak-topic-alert-intentsGET /v1/analytics/exams/{examId}/summaryGET /v1/analytics/classrooms/{classroomId}/summaryGET /v1/analytics/students/{studentId}/summaryPOST /v1/analytics/eventsGET /v1/analytics/eventsPOST /v1/analytics/rebuild-reportsGET /v1/analytics/rebuild-reportsPOST /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-139maps analytics result, weak-topic, exam, classroom, student, parent, and export routes.node-platform/apps/api/src/modules/analytics/analytics.service.ts:7-51delegates all analytics reads toAppDataService.node-platform/apps/api/src/modules/app-data/app-data.exams-analytics.ts:173-355implementsanalyticsResultsandweakTopicsfrom graded attempts, answers, exams, classroom assignments, subjects, topics, and chapters.node-platform/apps/api/src/modules/app-data/app-data.progress-overview.ts:4-317implements 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-49records lesson/video/material progress.node-platform/apps/api/src/modules/app-data/app-data.learning-progress.ts:20-154clamps progress, maps status, auto-completes video at 95 percent, and upserts material views.node-platform/apps/api/prisma/schema.prisma:868-923defines classroom lesson/material/video progress tables.node-platform/apps/api/prisma/schema.prisma:1080-1210defines course progress, material view, video progress, andStudentMastery.node-platform/apps/api/prisma/schema.prisma:2448-2534definesExamAttempt,ExamAttemptQuestion,ExamAnswer, andExamEvent.node-platform/apps/web/app/teacher/analytics/page.tsx:52-53consumes/analytics/results?limit=80and/analytics/weak-topics.node-platform/apps/web/app/teacher/results/page.tsx:232-261consumes filtered analytics result and weak-topic endpoints.node-platform/apps/web/app/parent/dashboard/page.tsx:70-75andnode-platform/apps/web/app/parent/children/[id]/progress/page.tsx:28-33consume parent progress endpoints.
Native contract:
PUT /v1/analytics/results/{attemptId}/snapshotupserts 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/resultssupportsclassroomId,examId,subjectId,gradeLevel,difficulty,topic,from,to,page, andlimit. Page defaults to1, limit defaults to20, and limit is clamped to1..100.- Result rows preserve the frontend-facing legacy shape:
attemptId,student, optionalclassroom,exam,score,correctCount,wrongCount,durationSeconds,status,submittedAt, andweakTopics. Theexamsnapshot also carries optional uppercase enginedifficulties(EASY,MEDIUM,HARD,VERY_HARD) projected from the source exam's question difficulties. Thedifficultyquery filter matches rows whose projectedexam.difficultiescontains the requested value. GET /v1/analytics/weak-topicsmirrors the legacy derivation from the first 100 matching analytics results: each topic in a result row incrementswrongCountandattemptCount;accuracyismax(0, 1 - wrongCount / attemptCount);shouldAlertis true whenwrongCount >= 2oraccuracy < 0.6. Native weak-topic filters use the same result filter parser, includingdifficultyagainst projectedexam.difficulties, while public default routing stays legacy until access adapters are proven.GET /v1/analytics/weak-topic-alert-intentsreuses that weak-topic rollup and returns only topics whoseshouldAlertis true. Each intent includessourceService=analytics-service, a deterministicsourceEventIdderived 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/eventsrecords service-local analytics projection events. Producers may passsourceServiceandsourceEventIdtogether; 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/eventssupportssourceServicealongsidetype,entity,entityId,from,to,page, andlimitfor backfill/rebuild audits.POST /v1/analytics/rebuild-reportsrecords internal rebuild/backfill metadata from owner-service event streams or read-only report jobs. Producers may passsourceServiceandsourceEventIdtogether; the pair is replay-idempotent and re-sending the same report returns the original report without mutation.- Rebuild report rows carry
scope, optionalscopeId,status=QUEUED|RUNNING|COMPLETED|FAILED, source ids, counters forinputEvents,projectedResults,skipped, andfailed, 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-reportssupportsorganizationId,sourceService,scope,status,from,to,page, andlimitfor cutover/backfill audit reports.POST /internal/v1/analytics/rebuild/attempt-resultsmaterializes only storedattempt-serviceattempt.graded.v1events for the caller's organization. It requires schema version1, the copied attempt/student/exam/result/question snapshot, and a persisted studentfullName; malformed events produce a failed rebuild report without a partial result. Each successful event writes anattempt-result-v1receipt 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-learningmaterializes only stored owner snapshots forcourse.learning_catalog.changed.v1,course.lesson_progress.saved.v1, andattempt.graded.v2in 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 separatecourse-learning-v1receipt 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-eventsfeed 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}/masteriesreturns only local mastery rows.STUDENTis forced to their ownstudentId;TEACHERandADMINcan request a student within their organization;PARENTis 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.v1events. 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.sqlcreatesanalytics_attempt_resultsandanalytics_events.services/analytics-service/migrations/000003_analytics_difficulty_filter.sqladds indexed result-projectionexam_difficultiesfor legacy result filter parity.services/analytics-service/migrations/000004_analytics_event_sources.sqladds replay metadata and a partial unique index for analytics producer events.services/analytics-service/migrations/000005_analytics_rebuild_reports.sqlcreates replay-safeanalytics_rebuild_reportsfor projection rebuild/backfill metadata.services/analytics-service/migrations/000006_analytics_projection_receipts.sqlcreates the receipt table that makesattempt.graded.v1result materialization replay-safe.services/analytics-service/migrations/000007_course_learning_projection.sqlcreates 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, andsubject_idare stored as public ids only. - The internal Course learning materializer now reproduces the legacy
0.5 * correctRate + 0.3 * latestQuizScore + 0.2 * lessonCompletionformula from copied snapshots, withFOUNDATION,REVIEW,PRACTICE, andADVANCEthresholds. It remains an internal read model: no student/teacher mastery or recommendation endpoint is default-routed, and Analytics must not infer a course fromexamIdalone.
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/resultsto/v1/analytics/results.GET /api/analytics/weak-topicsto/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/exportrouted 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.