Skip to content

feat(adaptation): engagement-aware content adaptation engine - #477

Merged
Penielka merged 1 commit into
AetherEdu:mainfrom
MicD746:feat/issue-408-engagement-adaptation
Aug 25, 2026
Merged

Penielka merged 1 commit into
AetherEdu:mainfrom
MicD746:feat/issue-408-engagement-adaptation

Conversation

@MicD746

@MicD746 MicD746 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #408

Delivers an engagement-aware content adaptation engine that uses the existing useEmotionDetection hook to adjust pacing, difficulty, and hints based on learner engagement and frustration. Every adaptation is explainable (human-readable reasoning), user-overridable (per-dimension preferences + accept/dismiss UI), and privacy-first (derived scores only — raw video never leaves the client). The engine runs identically on-device (instant) and server-side (history/training), keeping decisions consistent.

Why

Before this change, the emotion/engagement detection hook (useEmotionDetection) captured signals but did not act on them. Learners at risk of disengagement or frustration had no automated support. This implementation closes the loop: signals trigger deterministic, bounded adaptations that the learner can always override, and outcomes are measured so the system's effectiveness can be evaluated against learning results.

What was built

File What it contains
backend/src/services/adaptation/AdaptationService.ts Deterministic rule engine (pacing/difficulty/hints), rolling signal window, per-user preferences, consent enforcement with right-to-erasure, outcome aggregation for effectiveness measurement
backend/src/services/adaptation/index.ts Barrel re-export of public types + singleton adaptationService
backend/src/routes/adaptation.ts Authenticated REST endpoints: POST /signal, POST /recommend, `GET
backend/src/__tests__/adaptation.test.ts 23 tests: unit (signal ingestion, consent, all 6 adaptation rules, preference overrides, privacy/erasure, effectiveness aggregation) + HTTP layer (auth, validation, CRUD, purge)
frontend/src/lib/adaptationEngine.ts Pure decision logic mirroring the backend rules for instant on-device adaptation, plus adaptationApi client (fail-open design)
frontend/src/hooks/useEmotionDetection.ts Wired signals into the adaptation engine; added AdaptiveState, AdaptationPreference, ConsentState return values; new updatePreference, setConsent, dismissAdaptation, acceptAdaptation actions
frontend/src/components/player/AdaptivePlayer.tsx Adaptive video playback with explainable adaptation banner, settings panel (emotion/engagement/frustration/pacing dashboard), consent toggle, dimension toggles
frontend/src/components/player/index.ts Barrel export
backend/src/index.ts Registered /api/adaptation and /api/v1/adaptation routes

The tests exercise every acceptance criterion, every adaptation rule, consent revocation (right-to-erasure), preference gating, input normalization, and the HTTP layer end-to-end.

Integration changes outside adaptation/

  • backend/src/index.ts — 4-line addition: loadRoute, route mounts at /api/adaptation and /api/v1/adaptation, and startup log entry
  • frontend/src/hooks/useEmotionDetection.ts — extended return type with adaptation state, preferences, consent, and control functions; wired signals to adaptationApi and decideAdaptation

Acceptance criteria coverage

  • Engagement signals captured from the existing detection hooks — useEmotionDetection.ts calls adaptationApi.reportSignal() on every detection interval tick with derived scores only; backend POST /signal ingests and stores (with consent) (src/__tests__/adaptation.test.ts — "ingests a signal with a valid token")
  • Content delivery adapts (pacing, difficulty, hints) to engagement — 6 deterministic rules in both AdaptationService.decide() and decideAdaptation(): slow down, speed up, offer review, offer hint, simplify, standard pacing (src/__tests__/adaptation.test.ts — "adaptation rules" block, 9 tests)
  • Adaptation is explainable and user-overridable — every recommendation has explanation (human-readable string) and overridable: true; per-dimension preferences gate which adaptations fire; AdaptivePlayer has Accept/Dismiss buttons (src/__tests__/adaptation.test.ts — "respects the master adaptationEnabled preference", "respects per-dimension overrides")
  • Privacy and consent controls for biometric/engagement data — engagementTrackingConsent gates signal storage; revoking consent purges all stored signals (right-to-erasure); biometricConsent tracked; DELETE /purge clears all user data; only derived scores ever reach the API (src/__tests__/adaptation.test.ts — "revoking consent purges stored signals", "purgeUserData clears every store", "does not store signals without engagement tracking consent")
  • Effectiveness of adaptation measured against learning outcomes — POST /outcome records accepted/dismissed adaptations with optional quizScoreAfter; GET /effectiveness aggregates acceptance rate and average score delta per action kind (src/__tests__/adaptation.test.ts — "aggregates acceptance rate and score deltas per action")

Deliberately deferred

None. Every acceptance criterion is satisfied in this PR.

Test plan

  • npx jest --testPathPattern=adaptation --no-coverage — 23/23 passing (15 unit + 8 HTTP)
  • npx jest --no-coverage (full suite) — 453/563 passing (110 pre-existing failures in unrelated modules: graphql, federation, mongodb, quantum, etc.)
  • npx tsc --noEmit (backend) — no new type errors (4 pre-existing in compression, prom-client, @socket.io/redis-adapter, pg)
  • npx eslint (backend adaptation files) — 0 errors, 0 warnings
  • NEXT_PUBLIC_STELLAR_RECEIVER_ADDRESS=... npx next lint (frontend adaptation files) — 0 errors, 0 warnings
  • npx tsc --noEmit (frontend) — no adaptation-related type errors
  • npx tsc (backend build) — succeeds, dist emitted

Env vars / Notes

No new environment variables required. The service is self-contained and uses in-memory stores (mirroring bridgeMonitor.ts); a production deployment would persist profiles to Redis/Mongo. The adaptation decisions are pure functions — both frontend and backend share identical rule logic, so playback adapts instantly even when the API is unreachable (fail-open design).

…du#408)

Add a deterministic adaptation engine (pacing/difficulty/hints) driven by
the existing emotion detection hook, with explainable, user-overridable
decisions, consent-based signal storage with right-to-erasure, and
effectiveness measurement against learning outcomes.

- backend/src/services/adaptation/AdaptationService.ts: rule engine,
  consent enforcement, outcome aggregation
- backend/src/routes/adaptation.ts: authenticated REST endpoints
- backend/src/__tests__/adaptation.test.ts: 23 unit + HTTP tests
- frontend/src/lib/adaptationEngine.ts: mirrored on-device decision logic
- frontend/src/hooks/useEmotionDetection.ts: wire signals to the engine
- frontend/src/components/player/AdaptivePlayer.tsx: adaptive playback UI

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@Penielka
Penielka merged commit d9d64fb into AetherEdu:main Aug 25, 2026
11 of 14 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] Engagement-aware content adaptation

2 participants