Appearance
analytics-service Agent Tasks
Agent workflow: follow README.md for Audit -> Investigate -> Code -> Test -> Fix; keep this pack's writable scope and verification commands authoritative.
Dispatch type: per-service
Dispatch ID: SVC-022
Current implementation: services/analytics-service.
Owns: result/progress/mastery/dashboard projections, analytics events, weak-topic rollups, summary read models, and rebuild/backfill metadata.
Must read:
docs/agents/handoff-phase-9-analytics-foundation.md- legacy analytics/progress/result/dashboard modules:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/analytics/analytics.service.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/analytics/analytics.controller.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.exams-analytics.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.progress-overview.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.learning-progress.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/progress/progress.controller.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exams/exam-core.service.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/attempts/attempts.controller.ts.
Legacy source evidence:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/analytics/**- progress and parent dashboard inputs under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/** - result/progress inputs under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exams/** - attempt inputs under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/attempts/**.
Writable files:
services/analytics-service/**contracts/openapi/services/analytics-service.yamldocs/api/analytics-service.md
Orchestrator-owned files:
- attempt/exam/course/classroom event contracts, profile hydration contracts, gateway route cutovers, and shared deploy manifests unless delegated.
Delegated gateway verification scope:
- Only when the orchestrator assigns an analytics route/live/browser proof subtask, this service agent may update non-default
deploy/gateway/routes.analytics-*.jsonexamples,scripts/test/analytics-*.sh,docs/qa/analytics-*.md, and rootMakefiletest-analytics-*targets. - Producer event contracts, profile/classroom visibility adapters, default gateway routes, and frontend dashboard pages remain orchestrator-owned unless explicitly delegated. Public promotion still requires browser/runtime proof and rollback notes.
Initial tasks:
- Done: foundation workflow tests exist for result snapshot -> weak topics -> class/student/exam summaries. Add new tests only for new projections or route promotions.
- Done: result snapshots carry normalized source-exam question
difficulties(EASY,MEDIUM,HARD,VERY_HARD), and result list plus weak-topic filters match legacydifficultyqueries through memory, Postgres JSONB containment, OpenAPI, and docs. Public route promotion still depends on access adapters and runtime proof. - Done: analytics event ingestion accepts
sourceService+sourceEventId, stores replay metadata in memory/Postgres, filters bysourceService, and returns the original event on replay without mutation. - Add producer adapter callers from attempt/exam/course/classroom. Done: attempt-service now best-effort emits
attempt.submitted.v1andattempt.graded.v1; course-service now best-effort emitscourse.enrollment.granted.v1andcourse.lesson_progress.saved.v1,course.video_progress.saved.v1, andcourse.material_view.saved.v1; exam-service now best-effort emitsexam.published.v1,exam.assigned.v1, andexam.results_released.v1; classroom-service now best-effort emitsclassroom.membership.snapshot.v1after native membership mutations. Remaining slices: broader mastery projections and notification handoffs. Events must include enough display snapshots or projection refs to avoid cross-service DB joins. - Add result visibility and parent/teacher/admin access adapter tasks before public route promotion. Gateway/BFF or owner-service adapters must supply parent-child, classroom, and organization access decisions.
- Keep filter-parity checks for legacy result/weak-topic queries attached to route promotion, including
classroomId,examId,subjectId,gradeLevel,difficulty,topic,from,to,page, andlimit. - Add classroom report/export and parent progress task slices separately from the current teacher results/weak-topic read rehearsal.
- Done: analytics-service records replay-safe rebuild/backfill report metadata through
POST /v1/analytics/rebuild-reportsand lists it throughGET /v1/analytics/rebuild-reports. Reports store source idempotency,scope, status, counters, error/metadata, and started/finished timestamps for owner-service event streams or read-only backfill jobs. - Done:
POST /internal/v1/analytics/rebuild/attempt-resultsmaterializes organization-scoped storedattempt.graded.v1events into result, weak-topic, and summary inputs. It requires the v1 copied student/exam/result/question snapshot, rejects malformed or incomplete events without partial rows, uses a cursorable unmaterialized-event scan, and writes the result plusattempt-result-v1receipt atomically. The endpoint stays outside Gateway tables; no public analytics route is promoted.attempt-servicepersists and publishesstudent.fullNameso replay does not require profile joins. Broader generic attempt-result consumer workers remain separate slices. - Course mastery, recommendation, and report implementation is gated by
analytics-course-learning-projection.md. Do not infer course membership from anexamId: only a Course-validated context persisted byattempt-servicein a new graded event version can attach an attempt result to a course projection. - Done:
POST /internal/v1/analytics/rebuild/course-learningconsumes only stored Course catalog/progress snapshots and trustedattempt.graded.v2events for one organization.000007owns the local catalog, latest progress, course-attempt facts, and mastery rows; each accepted source event gets a separatecourse-learning-v1receipt. The materializer computes the legacy formula and recommendation thresholds from local copies only, drops stale catalog mappings deterministically, and never infers a Course from anexamId.make test-analytics-postgres-rebuildproves catalog/progress/v2, replay, and organization isolation in a disposable PostgreSQL schema. - Done: Course may opt in to a durable owner-side catalog delivery worker after
000006_course_learning_catalog_outbox.sql. It posts immutablecourse.learning_catalog.changed.v1source pairs through the existing Analytics ingestion endpoint with lease/retry/dead-letter ownership retained in Course. This is transport only: it neither makes Analytics poll Course nor starts the internal rebuild worker, and it does not add a Gateway route. - Done:
000008_course_learning_projection_outbox.sqlmakes Analytics ingest durable without joining owner storage. Its trigger enqueues only the Course catalog/progress and trusted Attempt v2 contracts; the opt-in PostgreSQL worker lease-claims them, materializes locally, then retries or dead-letters validation failures.ANALYTICS_COURSE_LEARNING_PROJECTION_WORKER_ENABLEDremains false by default. This worker never polls Course, runs outside HTTP ingestion latency, and does not add an endpoint or Gateway route.make test-analytics-course-learning-projection-worker-configkeeps the Compose, raw Kubernetes, and Helm defaults explicitly disabled until an orchestrator enables both delivery sides. - Done:
GET /internal/v1/analytics/courses/{courseId}/masteriesexposes the local course-learning rows only to an internal adapter.STUDENTis forced to self,TEACHER/ADMINare organization-scoped, andPARENTis denied. Course enrollment and current teacher visibility remain Course/Gateway owner decisions, so this endpoint is absent from all Gateway tables. - Done:
make test-analytics-postgres-rebuildstarts a disposable PostgreSQL container and runs the HTTP materializer against an isolated schema. It proves one result and receipt from a valid source event, no duplicate data after replay, and no partial result/receipt after a bad schema version. It does not use a configured service database or promote an analytics Gateway route. The runner writes a reproducible local bundle withsummary.jsonandgo-test.log; the latest isoutput/qa/analytics-postgres-rebuild-20260715-033318/. - Done: analytics-service exposes
GET /v1/analytics/weak-topic-alert-intentsas a bounded preview over the existing weak-topic rollup. It returns onlyshouldAlert=truetopics with deterministicsourceService=analytics-serviceandsourceEventIdmetadata, notification preference/type hints, topic counts, affected counts, scope filters, and adapter payload metadata. It does not resolve parent recipients, call notification-service, or read profile/classroom databases. These native intent rows are topic/filter metadata; any dispatch adapter must receive or derive concretestudentIdvalues before asking profile-service for parent recipients.
Acceptance:
- Analytics stores projections, not canonical exam/attempt/course/question state.
- Result visibility and parent/teacher/admin filters preserve legacy behavior.
- Projections can be rebuilt from events/backfill.
- Rebuild/backfill report metadata is service-owned and replay-safe.
- Cross-service display snapshots are by value or through explicit owner-service APIs; analytics-service never reads owner databases directly.
- Weak-topic alert production is an event to notification-service with resolved alert intent only; parent recipient resolution remains outside analytics.
Verification:
GOTOOLCHAIN=go1.25.11 go test ./services/analytics-service/...make test-analytics-routesANALYTICS_POSTGRES_REBUILD_CONFIRM=disposable-postgres make test-analytics-postgres-rebuild- Future projection slices must add focused producer/backfill worker tests, route guards for any promoted public paths, and browser/runtime evidence before default route cutover.