Data lake
The lake is a set of Parquet files partitioned by video_name, in
processing/lake/, generated by the Polars pipeline
(processing/pipelines/polars_pipeline.py) and queried in SQL by DuckDB
on the web side (web/src/lib/server/gold.ts and the analytics module).
Overview
flowchart TD
subgraph bronze["π§± BRONZE β raw"]
B["bronze/annotations/video_id=*/<br/>raw annotations CSV β parquet"]
end
subgraph silver["π₯ SILVER (normalized)"]
S["silver/windows/video_id=*/<br/>7 categories AβG per 15 s window<br/>+ rejects"]
end
subgraph gold["π₯ GOLD β analytical product"]
G1["gold/class_session"] --- G2["gold/student_session"]
G2 --- G3["gold/engagement_windows"]
G3 --- G4["gold/timeline"]
G4 --- G5["gold/student_history"]
R["ref/sessions.csv Β· students.csv"]
end
CSV["annotations CSV<br/>(merged)"] --> B
B --> S
S --> G1 & G2 & G3 & G4 & G5 --> API["API (DuckDB SQL)"]
R -. reads .-> API
Step by step
1. ingest_bronze β raw
Reads the merged annotations CSV and writes parquet partitioned by
video_name. Key functions: partitioning + writing, no business
transformation.
2. build_silver β normalization
- Normalizes free-text categories/labels β codes (
A1β¦G4) viascoring.normalise_category/label(tolerant to variants: βon taskβ βA1). - Segments β 15 s windows (
_segments_to_windows), pivot 7 columns AβG. - Fills missing windows:
- Posture C:
forward_fill(posture is sampled every 60 s); - defaults per category (rule R2):
AβA2, BβB4, CβC1, DβD5, EβE1, FβF1, GβG4. - Rejected rows (non-normalizable labels) are written to
silver/rejects.
3. build_gold β the product
_score_windows: score of each window (see Metrics), level,window_flags._student_session: phase-1 metrics (on-task, shifts, drift, recovery, sustained, flags)._class_session: class aggregates (engagement %, flagged students, averages)._timeline: per-minute average + engaged share (share_engaged).student_history: cumulative history per student.
Lake layout
processing/lake/
βββ bronze/
β βββ annotations/video_id=<lesson>.mp4/part-*.parquet
βββ silver/
β βββ windows/video_id=<lesson>.mp4/part-*.parquet
β βββ rejects/part-*.parquet
βββ gold/
β βββ class_session/part-*.parquet
β βββ student_session/video_id=<lesson>.mp4/part-*.parquet
β βββ engagement_windows/video_id=<lesson>.mp4/part-*.parquet
β βββ timeline/video_id=<lesson>.mp4/part-*.parquet
β βββ student_history/video_id=<lesson>.mp4/part-*.parquet
βββ ref/
βββ sessions.csv (15 lessons β the dashboard list)
βββ students.csv (31 students)
Important for testers
The dashboard course list is driven by ref/sessions.csv + the gold
layer (INNER JOIN on video_id) β not by the Postgres sessions table.
If a lesson has no gold row, it disappears from the dashboard.