Inspiration Everyone on the team has had the same day: sit down at the laptop at 9 a.m. with a full to-do list, look up at 6 p.m., and realize most of the day went to a phone screen. Attention spans are getting shorter and the tools built to "help" mostly add another notification to ignore. What actually works is a person a TA in a lab, a supervisor walking past your desk who notices you've drifted and says something. We wanted to build that person, and make them funny enough that you don't turn them off.
What it does The TA watches you through your webcam while you work. When it catches you distracted on your phone, chatting, dozing off, gone from the frame it calls you out loud, in character.
You pick your TA on the title screen:
The Termtestinator: a relentless exam-enforcing machine. Every distraction is a threat to the term test. Mr. President: a pompous head of state who treats your focus as a matter of national importance. The Torontonian: aggressively polite. Apologizes while scolding you, somehow works in the TTC and the Leafs. John Resident: an exhausted hospital resident 30 hours into a shift, who diagnoses your scrolling like a symptom. The camera view shows a live status line ("Focused (0.12): Good posture, eyes on the screen…"), a motion readout, and the TA's latest remark. Distracted verdicts are spoken out loud via text-to-speech, with a cooldown so the TA nags rather than screams. Brief glances and normal fidgeting are deliberately not flagged.
How we built it The pipeline is Python end to end:
Capture — OpenCV grabs webcam frames in a tight loop, flushing the driver buffer so we always analyze a current frame, not one from three seconds ago. Motion gate — Every frame is shrunk to 160×90, grayscaled, blurred, and diffed against the last captured frame. We only send a frame onward when ≥3% of pixels changed, at most once every 2 seconds, with a heartbeat capture after 2 minutes of stillness. A still person costs zero API calls. Detector — The gated frame (downscaled to 640×360) goes to Gemini via the google-genai SDK with a strict JSON response schema: {distracted, confidence, script}. The system prompt is the selected TA's persona plus house rules (3 sentences, under 40 words, plain text, never comment on someone's body or appearance). One call returns both the verdict and the line the TA says. Speaker — A worker thread runs the API call and pyttsx3 speech, so the video loop never stutters. Distracted results trigger speech at most once per 45-second cooldown, and recent lines are fed back into the prompt so the TA doesn't repeat itself. Front end — A Tkinter title screen (with our yelling-TA artwork) handles persona selection, the instruction manual, and the project description, then hands off to the OpenCV window. We also built a gaze gate using MediaPipe Face Mesh iris landmarks — it fires when your eyes shift direction rather than when your body moves — as an alternative trigger for the "phone in lap" case where the motion gate sees nothing.
Challenges we ran into Quota. The Gemini free tier gave us on the order of 20 requests per day per model, and the model we started with went "temporarily unavailable" mid-hackathon. That single constraint drove most of our architecture: it's why the motion gate exists, why we downscale frames, why we detect the daily-cap error and stop hammering the API instead of spamming retries, and why we hunted through the model list for ones with quota left.
Two calls became one. Our first design used two Gemini calls per check — one to detect, one to write the TA's line. That doubled quota burn and added latency. We merged them: the detector's JSON schema grew a script field, the persona moved into the system prompt, and the separate script generator was deleted. Half the calls, same output.
Merging four branches with two different designs. Camera, title screen, scorer, and break mode were built in parallel. When they came together, main still had the two-call design and the scorer branch had the one-call design — same filenames, incompatible imports (one direction created a circular import), a renamed field (explanation → script) that git couldn't see as a conflict, and a hand-resolved file that silently referenced variables that no longer existed. We resolved it by reading both sides in full rather than trusting the auto-merge.
Keeping the video smooth. A blocking API call or TTS playback freezes an OpenCV window. Threading fixed it, but then we had to make sure the motion gate wasn't "consuming" a change event while a request was in flight, which would drop the check entirely.
Accomplishments that we're proud of A working end-to-end loop — webcam → gate → vision model → structured verdict → spoken persona — built in a single day. The one-call design. Verdict and dialogue from one structured response, no regex parsing, no second round trip. The motion gate. It makes a hard quota survivable and it's 50 lines of OpenCV. The personas are actually funny. Watching the Torontonian apologize for interrupting your phone scrolling never got old. A title screen that looks like a product, not a hackathon script. What we learned Structured outputs are worth it. A JSON schema with typed fields turned a flaky text-parsing problem into a reliable one and let us bolt the script onto the verdict for free. Constraints design the system. We would never have built the gate or merged the calls without the quota wall, and the result is better for it. Prompt persona ≠ prompt permission. Getting a character to be funny and stay inside guardrails (redirect, don't insult; never comment on appearance) took explicit rules in the system prompt, not vibes. Semantic merge conflicts are the dangerous ones. The merge that compiles but crashes on the first result is worse than the one with <<<<<<< markers. MediaPipe iris tracking is very doable for a hackathon, and gaze is a much better signal than gross motion for the "looked down at phone" case. What's next for The TA Break mode — already on a branch: press B for a 5-minute timer, the TA stops watching, and an alarm brings you back. The end goal is telling the TA "I need a break" out loud and having it answer. Gaze gating in the main loop — wire the MediaPipe eye gate in alongside the motion gate so a still person staring at their lap still gets caught. Distinct voices per persona — a drill-sergeant robot and a sleep-deprived resident should not sound the same. A session report — how long you were focused, when you drifted, what the TA said, so the nagging has a scoreboard. Getting off the free tier — paid quota or a local vision model so the gate becomes an optimization rather than a survival mechanism. Custom personas — the persona table is one dict; a UI to add your own TA (your actual professor, your mom) is a small step.
Log in or sign up for Devpost to join the conversation.