# ADR-051: Learning Completion Is Threshold-Based — 100% Scenes or 80% Watch Time (Scrub-Proof)

## Status

Accepted, 2026-06-22.

## Status History

```yaml
status_history:
  - date: 2026-06-22
    status: Proposed
    changed_by: hkl
    reason: Formalising completion threshold decision for Learning Hub PWA
    changed_via: adr-kit (360lm)
  - date: 2026-06-22
    status: Accepted
    changed_by: hkl
    reason: Completion rules live in learn schema; two-RPC beacon (ADR-052) enforces thresholds
    changed_via: adr-kit (360lm)
```

## Context

The Learning Hub (`/learn/`) serves two types of content: Scene Guide (step-by-step instructional panels) and Screencast (video recording). A TM (Territory Manager) dashboard tracks which employees have completed which tutorials. For training compliance, "completed" must reflect genuine engagement, not just "opened the tutorial." Two patterns were rejected: marking complete on first open (easily gamed) and requiring 100% video watch (blocks completion if the last 5 seconds buffer-stall). A threshold approach balances integrity with practicality.

## Decision

A tutorial is marked `completed = true` only when:

- **Scene Guide:** `scenes_reached >= total_scenes` — all scenes have been visited. Scene navigation is tracked server-side (`scenes_reached` field in `learn.view_event`); the learner cannot skip to scene N without traversing 1 through N-1 in the client.
- **Screencast:** `watched_seconds >= total_seconds * 0.80` — at least 80% of total video duration accumulated. This is **scrub-proof**: `watched_seconds` is accumulated from play events only (time elapsed while `video.playing`), not derived from `currentTime` — scrubbing to the end does not count as watched.

Both thresholds are evaluated server-side in `end_view` RPC and written as `completed = true/false` in `learn.view_event`. The TM dashboard queries `view_event WHERE completed = true`.

**Decision Maker:** hkl

## Alternatives Considered

- **Mark complete on first open.** Rejected: trivially gamed by opening the tutorial and closing immediately; provides no signal of actual learning; defeats the purpose of completion tracking.
- **100% watch time for screencasts.** Rejected: video buffering, connection drops, or accidental tab close in the last few seconds would prevent completion after genuine full engagement; creates friction without proportionate integrity benefit.
- **Client-side completion detection (JS writes "completed" without server verification).** Rejected: client-side flags can be set manually via browser console; server-side `end_view` RPC is the authoritative source; the TM dashboard must trust the completion flag.
- **Quiz after content (answer questions to mark complete).** Rejected: requires creating and maintaining a question bank per tutorial; the platform serves operational how-to content, not certification training; watch-time threshold is proportionate to the content type.

## Consequences

**Positive:**
- Completion reflects genuine engagement (80% watch time, not "opened" or "scrubbed").
- TM dashboard shows reliable completion rates for team training compliance.
- Scrub-proof accumulation prevents the most obvious bypass attempt.
- Scene-by-scene tracking means the TM can also see "stuck at scene 3 of 8" for incomplete attempts.

**Negative / Trade-offs:**
- 80% threshold is a policy choice — if management wants 90%, the `end_view` RPC needs a config change.
- A learner who watches 79% of a video, closes, and reopens must reach 80% accumulated total across sessions (watched_seconds is cumulative) — this is the correct behavior but requires the beacon to carry the prior session's accumulated seconds.
- Scene Guide completion requires client to traverse all scenes — deep-linking to scene N bypasses prior scenes; deep-link must be blocked or scene counter must not increment for skipped scenes.

**Risks and mitigations:**
- Accumulated watched_seconds exceeds total_seconds (due to rewatching): mitigated by `MIN(watched_seconds, total_seconds)` cap in `end_view`; completion is still true at >= 80%.
- Scene traversal tracking misses a scene due to fast navigation: scene counter increments on scene render, not on read duration; fast navigation still increments correctly.

## Related Decisions

- ADR-052 (two-RPC view tracking beacon) — the start_view / end_view RPC pair that collects watched_seconds and scenes_reached.

## References

- `memory/dbt_learn.md` — completion rules: scenes_reached, watched_seconds * 0.80, scrub-proof note
- `learn/index.html` — completion evaluation logic, watched_seconds accumulation
- `learn schema` — view_event table: watched_seconds, scenes_reached, completed columns
