Repository navigation
feat(adaptation): engagement-aware content adaptation engine - #477
Merged
Penielka merged 1 commit intoAug 25, 2026
Merged
Conversation
…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>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #408
Delivers an engagement-aware content adaptation engine that uses the existing
useEmotionDetectionhook 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
backend/src/services/adaptation/AdaptationService.tsbackend/src/services/adaptation/index.tsadaptationServicebackend/src/routes/adaptation.tsPOST /signal,POST /recommend, `GETbackend/src/__tests__/adaptation.test.tsfrontend/src/lib/adaptationEngine.tsadaptationApiclient (fail-open design)frontend/src/hooks/useEmotionDetection.tsAdaptiveState,AdaptationPreference,ConsentStatereturn values; newupdatePreference,setConsent,dismissAdaptation,acceptAdaptationactionsfrontend/src/components/player/AdaptivePlayer.tsxfrontend/src/components/player/index.tsbackend/src/index.ts/api/adaptationand/api/v1/adaptationroutesThe 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/adaptationand/api/v1/adaptation, and startup log entryfrontend/src/hooks/useEmotionDetection.ts— extended return type with adaptation state, preferences, consent, and control functions; wired signals toadaptationApianddecideAdaptationAcceptance criteria coverage
useEmotionDetection.tscallsadaptationApi.reportSignal()on every detection interval tick with derived scores only; backendPOST /signalingests and stores (with consent) (src/__tests__/adaptation.test.ts— "ingests a signal with a valid token")AdaptationService.decide()anddecideAdaptation(): slow down, speed up, offer review, offer hint, simplify, standard pacing (src/__tests__/adaptation.test.ts— "adaptation rules" block, 9 tests)explanation(human-readable string) andoverridable: true; per-dimension preferences gate which adaptations fire;AdaptivePlayerhas Accept/Dismiss buttons (src/__tests__/adaptation.test.ts— "respects the master adaptationEnabled preference", "respects per-dimension overrides")engagementTrackingConsentgates signal storage; revoking consent purges all stored signals (right-to-erasure);biometricConsenttracked;DELETE /purgeclears 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")POST /outcomerecords accepted/dismissed adaptations with optionalquizScoreAfter;GET /effectivenessaggregates 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 incompression,prom-client,@socket.io/redis-adapter,pg)npx eslint(backend adaptation files) — 0 errors, 0 warningsNEXT_PUBLIC_STELLAR_RECEIVER_ADDRESS=... npx next lint(frontend adaptation files) — 0 errors, 0 warningsnpx tsc --noEmit(frontend) — no adaptation-related type errorsnpx tsc(backend build) — succeeds, dist emittedEnv 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).