If you run an online-learning or exam-prep platform, you have heard the same things. Course teams say completion is stuck in the teens and learners go quiet and drop before anyone notices. Exam-prep learners ask one question over and over: am I ready? The answer shows up in video data weeks before the missed exam or the refund request. Watch time falls, learners slip behind pace for their test date, and the hard lessons get rewatched. FastPix Video Data turns each of those into a forward-looking signal, and this article assembles them into a learner health and readiness dashboard, plus the early warning at-risk list you act on each week to protect learning outcomes.
The finished dashboard, built from the sample export below. Every number on it comes from running this article's code on that file.
Readiness is not a single metric, so this dashboard reads several measures together rather than showing one chart. Whether a learner will be ready is a blend of how much of each lesson they finish, whether they are keeping pace for their test date, how recently they studied, and how they score on the quizzes your platform already runs. This build assembles those signals into one readiness index per learner, rolls them into a cohort view, and ends with the trigger that turns the view into a nudge.
Why this is hard in video, and what Video Data gives you
Knowing whether a learner is ready is harder in video than on paper. A test hands you a score. A video hands you a play: you can see that someone opened a lesson, but not whether they finished it, fell behind their study plan, or replayed the one concept that will sink them on exam day. That blind spot is why most platforms can tell you a learner logged in but not whether they are actually prepared.
Building that visibility yourself is not a feature, it is an analytics stack: a beacon in every player, a collection endpoint that survives spikes, sessionization to stitch events into views, storage, and the dashboards on top. That is months of engineering with nothing to do with teaching, which is why most teams ship a completion percentage and stop. If you carry a pass guarantee, a renewal number, or a refund rate, that blind spot is where those numbers are won or lost.
FastPix Video Data is that stack as infrastructure. It is first-party data you own, captured from inside the player: how much of each lesson a learner finished, when they last studied, how far they got, which seconds they replayed, the playback quality they got, and more than fifty fields per view, ten of them yours to define (the docs list every field). You add a line to your player and read one clean row per view. There is no pipeline to build or run, and the data lands in your own product, so you can act on it or show it to learners under your brand.
Video Data supplies the exact engagement signal. The outcome, the quiz score, the exam result, the certificate, the renewal, lives in your platform, joined on the learner ID. Here is each signal, what it tells you, and the decision it informs.
| What Video Data captures | What it tells you | The decision it informs |
|---|---|---|
| Lesson completion (how much they finished) | Where the course loses people | Which lessons and learners to act on |
| Days since last watch (idle time) | Who is disengaging, and when | Who to re-engage before they drop |
| Watch time and pace vs the test date | Who is behind for the exam they booked | Who needs a catch-up plan or a tutor |
| Rewatched segments (replays) | Which concepts the cohort finds hard | Where to add practice or a worked example |
| Playback quality (startup, buffering) | Whether delivery is hurting viewing | Whether it is a content or an infrastructure fix |
| Device and location | Where and how learners study | Delivery and format fixes |
None of these is a grade. They are the behaviour that comes before the grade, which is what makes them worth acting on early. The rest of this article builds the dashboard above from exactly these columns.
Learner, course, lesson and test-date ride along in custom dimensions, so every view can be grouped by who watched, which course, and which lesson. Already collecting views with FastPix? Skip to Part 2. New here? The setup below takes a few minutes.
learner-analytics-sample-synthetic.csv: illustrative synthetic data (not real learners): 116 views across 16 learners, 2 courses and 10 lessons over an 8-week exam-prep window, with the full playback event stream on every view and the post-lesson quiz and latest mock-exam score joined in from the LMS. Columns mirror a real FastPix Video Data export. Every snippet below runs against it. No FastPix account yet? The sample works without one, or start free with $25 credit (no credit card) and build this on your own learners instead.
Download the sample video-data file (CSV) → See the exact rows every number below is built from.
Get view data flowing from your player
Playback happens in your player. FastPix Video Data captures every interaction from inside it: it dedupes the beacons, stitches events into sessions, adds geo and device data, and computes quality scores, across millions of concurrent views when you need it. What you export is one clean row per view, with a learner ID and the full playback event stream on every row.
You do not need to switch players to get this. FastPix Video Data works with the players you already run, hls.js, Shaka Player and Video.js on the web, and native AVPlayer on iOS and Media3 on Android, through the monitoring SDKs, or you can use the FastPix Player and get it built in. Either way, installation is a snippet per surface, and data is flowing in about thirty minutes.
Step 1: Add the player, or add monitoring to the one you have
Learners watch on the phone on a commute, the laptop at night, and sometimes the living-room TV, so cover every surface.
<!-- FastPix Player -->
<script src="https://unpkg.com/@fastpix/[email protected]/dist/player.js"></script>
<fastpix-player playback-id="YOUR_PLAYBACK_ID" stream-type="on-demand"></fastpix-player>Already have a player? The monitoring SDK captures the same data from hls.js, video.js, native mobile, and TV players:
import fastpixMetrix from "@fastpix/data-core";
fastpixMetrix.tracker(videoElement, {
hlsjs: hls, Hls: Hls, // both are required
data: { workspace_id: "YOUR_WORKSPACE_KEY" },
});Step 2: Tag each view with learner, course, lesson and test date
Every readiness question needs these. Pass them in custom dimensions so each view can be grouped by who watched, which course and lesson it was, and the exam date they are working toward.
fastpixMetrix.tracker(videoElement, {
hlsjs: hls, Hls: Hls,
data: {
workspace_id: "YOUR_WORKSPACE_KEY",
viewer_id: learner.id, // who is watching
custom_1: course.name, // which course
custom_2: lesson.title, // which lesson
custom_3: lesson.difficulty, // Intro / Core / Hard
custom_4: learner.test_date, // the exam they are prepping for
},
});Step 3: Export the views
Export from the Video Data dashboard (Views → Export CSV) or the export API, which is what a nightly job pulls. The columns this article uses: viewer_id, enrolled_at, cohort, custom_course, video_title, difficulty, test_date, video_duration, view_start, view_max_playhead_position (how far they got), view_total_content_playback_time (watch time), device_type, and events (the playback stream, used for the rewatch signal in Part 2). We joined two more columns from the LMS on learner and lesson: lesson_quiz_score and latest_mock_pct. Video Data measures the watching; your platform owns the quiz and exam scores, and a join on the learner ID brings them together.
If Excel opens the export and mangles the view_start column into its own date format, read the CSV straight into your analysis tool instead of saving it back out of a spreadsheet. The timestamp shift is a real source of wrong idle-day counts.
Build the readiness index and the health view
The stack is chdb (embedded ClickHouse, pip install chdb) with pandas. The same SQL runs later on a real ClickHouse when you graduate this off your laptop.
Step 4: Load the views and score each lesson's completion
import chdb, pandas as pd, numpy as np, json
END = pd.Timestamp("2026-03-02 09:00:00"); TOTAL_LESSONS = 10
v = chdb.query("""
SELECT viewer_id AS learner, enrolled_at, cohort, custom_course AS course,
video_title AS lesson, difficulty, test_date,
CAST(video_duration AS Int64) AS dur_ms, view_start,
CAST(view_max_playhead_position AS Int64) AS reach_ms,
CAST(view_total_content_playback_time AS Int64) AS watch_ms,
CAST(lesson_quiz_score AS Int64) AS quiz,
CAST(latest_mock_pct AS Int64) AS mock, events
FROM file('learner-analytics-sample-synthetic.csv', CSVWithNames)""", "DataFrame")
v["completion"] = v["reach_ms"] / v["dur_ms"] # how much of the lesson they finished
v["watch_min"] = v["watch_ms"] / 60000.0Step 5: Classify learner health by days since last watch
On a fixed exam timeline, idle time is the earliest drop-off signal you have. Sum watch time per learner, find the last view, and label each learner by how long they have been quiet.
g = v.groupby("learner")
per = g.agg(cohort=("cohort","first"), enrolled=("enrolled_at","first"),
test=("test_date","first"), watch_min=("watch_min","sum"),
lessons_reached=("lesson","nunique"), avg_completion=("completion","mean"),
avg_quiz=("quiz","mean"), mock=("mock","first"),
last_watch=("view_start","max")).reset_index()
per["days_idle"] = (END - pd.to_datetime(per.last_watch)).dt.days
per["health"] = per.days_idle.apply(
lambda d: "Active" if d<=9 else ("Cooling" if d<=20 else "At-risk"))On the sample: 44% Active, 44% Cooling, 12% At-risk. The Cooling group has not dropped yet; they have gone quiet, and they are where a nudge still changes the outcome.
Start using FastPix for free. $25 credit, no credit card required. Build this on your own learners this week. Create your workspace →
Step 6: Measure pace against each learner's test date
Health tells you who went quiet. Pace tells you who is behind for the exam they are actually sitting. Compare how far a learner has moved through the curriculum against how far they should be, given the time between enrolment and their test date.
per["days_to_test"] = (pd.to_datetime(per.test) - END).dt.days.clip(lower=1)
per["days_since_enroll"] = (END - pd.to_datetime(per.enrolled)).dt.days.clip(lower=1)
per["actual_prog"] = per.lessons_reached / TOTAL_LESSONS
per["expected_prog"] = per.days_since_enroll / (per.days_since_enroll + per.days_to_test)
per["pace_ratio"] = per.actual_prog / per.expected_prog # < 1.0 = behind
per["behind_pace"] = per.pace_ratio < 0.85On the sample, 19% of learners (3 of 16) are behind pace. One of them has the exam in under two weeks, which is the case the dashboard highlights in red.
Step 7: Turn the signals into one readiness index
No single number says "ready". A learner can finish every video and still fail if they never take the quizzes, and a strong quiz-taker who has gone quiet for three weeks is not ready either. Blend the four signals into one index from 0 to 100, then band it.
cov = per.avg_completion.clip(0,1) # finish rate
pace = (per.pace_ratio.clip(0,1.2) / 1.2) # on track for test date
recency = (1 - per.days_idle / per.days_idle.max()).clip(0,1) # still studying
quiz = (per.avg_quiz / 100).clip(0,1) # scoring on lesson quizzes
per["readiness"] = (0.35*cov + 0.30*pace + 0.15*recency + 0.20*quiz) * 100
per["band"] = per.readiness.apply(
lambda r: "Ready" if r>=70 else ("Building" if r>=50 else "At-risk"))On the sample this gives 56% Ready, 25% Building, 19% At-risk. Those bands are the coloured bars at the top of this page, and the At-risk band is the weekly action list. The weights are yours to set; tie them to what your own outcome data says matters, which is exactly what Step 9 checks.
Step 8: Find the concepts learners rewatch
The events column carries a playhead position and a wall-clock timestamp for every playback event. A backward jump in the playhead after real watching is a rewatch. Cluster those per lesson and you get the concepts the cohort finds hard.
def replays(js):
evs = [e for e in json.loads(js) if e.get("pt") is not None and e.get("vt")]
return sum(1 for a,b in zip(evs, evs[1:])
if b["vt"]-evs[0]["vt"] >= 2000 and (b["pt"]-a["pt"]) < -1000)
v["replay_events"] = v.events.apply(replays)
hard = v.groupby(["lesson","difficulty"]).replay_events.sum().sort_values(ascending=False)If replays come back as zero on every lesson but you expected some, the events column was probably truncated on the way out. Pull it through the export API rather than copying from the dashboard grid, which can clip long JSON cells.
On the sample, three lessons hold nearly every rewatch: Nervous system and action potentials (44), Endocrine and feedback loops (44), and Solutions, moles and dilutions (24). All three are Hard-tagged science lessons, and anatomy and physiology is one of the science topics nursing candidates most often call the hardest. The Intro and Core lessons show almost no rewatching. That contrast is the signal: a rewatch cluster is the cohort telling you which concept to re-teach.
Step 9: Check that the index predicts the real outcome
An index is only worth acting on if it tracks the outcome you care about. Correlate each learner's readiness against the latest mock-exam score your LMS recorded.
r = np.corrcoef(per.readiness, per.mock)[0,1]
# r = 0.87 on the sample
per.groupby("band").mock.mean().round()
# At-risk 57 · Building 66 · Ready 79On the sample the readiness index and the mock-exam score move together at r = 0.87, and average mock scores climb cleanly across the bands: 57 for At-risk, 66 for Building, 79 for Ready. That is the check that lets you send a "you may not be ready" nudge and trust it. Run it on your own learners and re-weight the index until it tracks your outcome.
What the dashboard tells you
How to measure learner readiness: blend completion, pace, recency and quiz into one score. Any single metric misleads. Completion alone rewards a learner who watches everything but never tests; a high quiz average hides a learner who has gone quiet for three weeks. A readiness index that combines how much of each lesson a learner finishes, whether they are keeping pace for their test date, how recently they studied, and how they score on quizzes gives one number you can rank a cohort by, the way you would build any analytics view into your product. On the sample it correlates with the mock exam at r = 0.87, which is what makes it safe to act on.
How to spot at-risk learners early: watch days-idle and pace, not just grades. A learner rarely announces they are about to drop. What changes first is behaviour: watch time falls, the gap between study sessions widens, and they fall behind the pace their test date demands. Learners idle for 10 to 20 days are Cooling, the window where a re-engagement nudge still works; beyond 20 days idle they are usually already gone. Reading idle time and pace catches the slide weeks before a failed quiz or a missed exam confirms it, which is the whole point of an early warning view.
Which lessons are students struggling with: the ones they rewatch. When learners jump back and replay the same stretch of a lesson, they are telling you the concept did not land. Clustering those rewatches per lesson surfaces the hard concepts without a survey. On the sample, three Hard-tagged science lessons hold nearly every rewatch while the intro material shows none, so the fix is targeted: re-cut or add a worked example to those three, not the whole course. This is the same idea as a video heatmap or audience-retention graph, read at the level of a curriculum.
Where in a lesson do learners drop off: read the retention curve. Completion as a single number hides the shape of the drop-off. Plotting the share of learners still watching at each point of a lesson shows exactly where they leave. On the sample, an intro lesson holds most of the cohort to the end, while a hard science lesson sheds them steadily, with barely half still watching at the midpoint. That shape tells you which lessons to shorten, re-cut, or split.
Does video engagement predict exam results: on this cohort, strongly. Engagement is only worth measuring if it maps to the outcome. Joining watch data to the mock-exam scores your platform already stores lets you test that directly, and here the readiness index and the exam score rise together, with Ready learners averaging 79 on the mock and At-risk learners 57. Combining engagement data with assessment results is how you measure training effectiveness and learning outcomes, and it is what turns "they watched a lot" into "they are likely to pass", and it is the number a founder can put in front of a school buyer.
Turn the dashboard into nudges
The dashboard earns its keep when it drives your messaging. FastPix supplies the signals; your push, email, or in-app engine sends the message. Here is the one trigger that matters most, the learner who is behind pace, near their test date, and has gone quiet:
for p in per.itertuples():
if p.behind_pace and p.days_to_test <= 14 and p.health != "Active":
escalate_to_tutor(p.learner,
f"Behind pace, exam in {p.days_to_test} days, {p.days_idle} days idle")
elif p.health == "Cooling":
nudge(p.learner, "Pick up where you left off") # re-engage before they drop
# On the sample: 1 tutor escalation, 7 cooling nudges.On the sample that surfaces one learner for a tutor to call, L-010, who is 32 days idle with the exam 13 days out, and seven Cooling learners for an automated re-engagement nudge. The same signals drive the rest of the workflows a learning platform needs:
| Signal | Condition | What you trigger |
|---|---|---|
| Learner cooling | 10–20 days since last watch | Re-engagement nudge, "pick up where you left off" |
| Behind pace near the exam | pace < 0.85 and test within 14 days | Escalate to a tutor, offer a catch-up plan |
| Not ready | readiness index < 50 | Recommend targeted practice on weak topics |
| Struggling on a concept | rewatches cluster in one lesson | Surface a worked example; flag the lesson to re-cut |
| Module finished | completion crosses your threshold | Open the quiz, write completion to the LMS record |
None of this makes weak content strong or takes the exam for the learner. It removes the guesswork about who needs help and on what, so the help arrives while it still changes the result.
Put this on a weekly schedule
A one-off run gives you a snapshot of today. Running it on a weekly schedule is what turns it into an early-warning system. Point the loader at a nightly or weekly export, and the output is a report a cohort owner reads every Monday: the At-risk action list, the Cooling learners to nudge, and the lessons the whole cohort is rewatching. The owner works the red rows, the nudges go out automatically, and the content team gets a short list of lessons to fix.
| Team | What they track | The signal that matters |
|---|---|---|
| Exam-prep platform (this article) | Learner readiness, at-risk list, hardest concepts | pace vs test date + days idle |
| Course / institute LMS | Course completion, cohort engagement, certification | completion + drop-off in the first weeks |
| Frontline / corporate training | Training completion, competency, on-the-job readiness | completion + post-lesson quiz |
Run this on your own data
Point Step 4 at your own export and set two things for your platform: the idle thresholds behind Active, Cooling and At-risk (tie them to your exam or renewal cycle), and which custom dimensions carry your learner, course and lesson IDs. Re-weight the readiness index in Step 7 until it tracks your own outcome data, the way Step 9 does here. Everything else, the health view, the pace check, the rewatch finder, and the trigger, runs unchanged. Every column in the export is a GROUP BY away from another cut, and the same SQL scales from this laptop file to a real ClickHouse when you are ready.
Frequently Asked Questions (FAQs)
How do I measure whether a learner is ready for an exam?
Combine four signals into one readiness score: lesson completion, pace against the test date, how recently the learner studied, and quiz results. No single metric is reliable on its own. In the sample, this composite readiness score tracked mock-exam performance with a correlation of 0.87, which was strong enough to act on.
How do I identify at-risk students early?
Track days since the learner's last watch and their progress pace, rather than relying only on grades. Behavioral changes often appear before results do: watch time falls and learners begin to fall behind the pace required by their exam date. Learners who have been inactive for 10 to 20 days are a useful group to target for re-engagement before they drop further behind.
Which lessons are students struggling with?
Look at which lessons learners repeatedly rewatch. A backward jump in the video after meaningful viewing can indicate a replay, and clusters of replays around a particular lesson can highlight concepts that learners find difficult. In the sample, three hard-science lessons accounted for nearly all rewatch activity, while introductory lessons had little to none.
Does video engagement predict exam results?
In this cohort, strongly. Joining video watch data with mock-exam scores showed that the readiness index and exam performance increased together: ready learners averaged 79, compared with 57 for at-risk learners. Combining engagement data with assessment results can provide a stronger prediction of outcomes than either data source alone.
What is a good course completion rate?
Most self-paced online courses have completion rates in the 5% to 15% range, while cohort-based and high-touch programs can achieve substantially higher rates. Instead of chasing an industry average, measure completion at the lesson level to identify where learners are dropping off and focus your interventions there.
How early can you identify at-risk learners?
You can identify warning signs within the first few weeks, before the exam. Falling watch time, longer gaps between study sessions, and learners falling behind their planned pace can appear before grades decline. Detecting these signals early gives you more time to intervene while the learner can still change their outcome.
Does watching more videos improve exam scores?
Not on its own. Exam performance is more closely tied to how learners engage with the material: completing lessons, rewatching difficult concepts, and maintaining study pace, rather than simply accumulating hours of viewing. In the sample, a readiness index based on these behaviors tracked mock-exam performance at 0.87, while total watch time alone was a weaker signal.
What does it cost to start?
Getting started is free. You get a $25 credit with no credit card required. The Video Data free tier includes 100,000 plays per month, and the FastPix Player is free for FastPix customers.




