Every Monday morning at NLS (Ntare-Louise Nlund School) in Kigali, Rwanda, the same scene played out: a coordinator hunched over a spreadsheet, manually matching 400+ students to 20+ co-curricular activities based on preference forms collected over the previous week. Teachers submitted paper attendance sheets. The nurse kept a notebook. The kitchen tracked meals on a clipboard. The administration had no real-time visibility into any of it — just a pile of paper that had to be manually compiled into a weekly report.
The question that started this project was simple: why are we doing any of this manually?
Every one of those processes was repetitive, error-prone, and consuming hours of skilled staff time every week. The data existed — it just lived in a dozen different places in a dozen different formats. We built NLS Operations Hub to collapse all of it into one platform.
What We Built
NLS Operations Hub is a full-stack internal operations platform for school administration. It replaces paper-based workflows across six departments with a single app, seven role-specific dashboards, and zero clipboards.
Core systems built:
Smart Activity Allocation Engine Students submit ranked preferences. The system allocates them to activities respecting per-activity capacity limits, detects conflicts, and logs every change in an audit trail. Manual overrides are supported. What previously took a coordinator 4–6 hours now runs in under 10 minutes.
Digital Attendance with Pre-Excuse Workflow Teachers open the app and submit attendance in seconds — QR scan or bulk tap. Sessions move through a formal state machine:
$$\text{draft} \rightarrow \text{submitted} \rightarrow \text{finalized}$$
Students can be pre-excused before a session even begins. Auto-mark-absent runs as a scheduled Edge Function to catch sessions where teachers forget to submit.
AI-Powered Weekly Summary A Supabase Edge Function pulls the week's attendance data, feeds it to an AI model, and returns a structured natural-language report: attendance rates, repeat absentees, problematic activities, trend flags. What used to take 2+ hours of manual compilation takes one click and ~30 seconds.
Multi-Role Dashboards
┌───────────┬─────────────────────────────────────────────────────────────────┐ │ Role │ Scope │ ├───────────┼─────────────────────────────────────────────────────────────────┤ │ Admin │ Full platform: users, allocations, meals, workouts, AI insights │ ├───────────┼─────────────────────────────────────────────────────────────────┤ │ Moderator │ Activity management, attendance oversight, weekly roster │ ├───────────┼─────────────────────────────────────────────────────────────────┤ │ Teacher │ Their activities' rosters and live attendance submission │ ├───────────┼─────────────────────────────────────────────────────────────────┤ │ RL Coach │ Workout sessions and performance reports │ ├───────────┼─────────────────────────────────────────────────────────────────┤ │ Medical │ Student visit log, conditions, clearance tracking │ ├───────────┼─────────────────────────────────────────────────────────────────┤ │ Kitchen │ Meal attendance via QR scan, daily/weekly reports │ ├───────────┼─────────────────────────────────────────────────────────────────┤ │ Student │ Schedule, streaks, leaderboard, QR student ID │ └───────────┴─────────────────────────────────────────────────────────────────┘
Edge Functions (12 total) allocate-activities, generate-weekly-summary, activity-chatbot, auto-mark-absent, generate-pdf-report, generate-allocations-pdf, google-calendar-sync, sync-google-sheets, import-students, import-teachers, process-student-request, send-push
One-Click Data Export Excel export with 5 sheets (Summary, Roster, Teachers, Allocations, Attendance) and CSV — no BI tool required. Leadership gets clean data on demand.
How We Built It
Stack:
- React 18 + TypeScript + Tailwind + shadcn/ui — frontend
- Supabase — PostgreSQL database, Auth, Realtime subscriptions, Edge Functions (Deno)
- Row-Level Security — every table protected; role checks enforced server-side via a has_role() Postgres function
- SheetJS (XLSX) — multi-sheet Excel report generation in the browser
- Web Push API — push notifications to staff and students
- Google Calendar & Sheets API — calendar sync and live spreadsheet export
- Vite — build tooling; deployed on Lovable with auto-deploy on main
The database has 37 tables and 111 versioned migrations — built iteratively as each new department's workflow was digitized. Security was hardened incrementally: RLS policies were written, tested, and tightened over multiple passes to ensure teachers only see their own students, medical staff only see their records, and no role can escalate its own permissions.
Challenges
- RLS policy complexity Row-Level Security across 37 tables with 7 roles is genuinely hard. One example: teachers can be assigned to activities either by teacher_id (UUID) or by teacher_in_charge (a free-text email field from an older data model). Writing a policy that handles both without opening data to the wrong users required careful ILIKE matching and multiple iterations:
CREATE POLICY "Teachers can view allocations for their activities" ON public.allocations FOR SELECT USING ( EXISTS ( SELECT 1 FROM public.activities a WHERE a.id = allocations.activity_id AND ( a.teacher_id = auth.uid() OR a.teacher_in_charge ILIKE '%' || ( SELECT email FROM public.profiles WHERE id = auth.uid() ) || '%' ) ) OR public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'moderator') );
Attendance session state machine The attendance_sessions table had a CHECK constraint that only allowed ('draft', 'finalized') — but the app's finalization flow sets status to 'submitted' as an intermediate state. This silently rejected all teacher submissions in production. The fix was a schema migration; the lesson was to keep the database constraint in sync with every application state transition.
Allocation fairness at scale Greedy allocation by submission order meant students who submitted preferences late consistently got their last choices. We added a randomized priority shuffle within each preference tier so no student is systematically disadvantaged by when they submitted.
Real-time without race conditions Supabase Realtime subscriptions for live attendance updates caused stale-read issues when multiple teachers submitted simultaneously. We moved to optimistic updates with server reconciliation and added a refresh-on-focus handler so moderators always see current state.
What We Learned
- Paper processes have hidden complexity. Every "simple" paper form had edge cases that took real design work to handle digitally — partial excuses, retroactive edits, multi-day activities, split slots.
- Security is an iterative process. We ran three separate security hardening passes, each finding something the last missed. RLS requires testing every role against every sensitive query, not just the happy path.
- AI is most valuable on aggregation tasks. The weekly summary feature isn't clever — it just replaces 2 hours of manually copy-pasting numbers into a narrative. That's exactly where AI earns its keep.
- The best internal tool is the one people actually open. We spent as much time on the student-facing dashboard (streaks, leaderboard, QR ID, push notifications) as the admin tools — because adoption from the ground up is what makes the data good enough to act on.
Impact
$$\text{Hours saved per week} \approx 11 \quad \Rightarrow \quad \text{Annual value} \approx $8{,}500+$$
But the real number is harder to quantify: decisions that used to wait for a Friday report now happen Monday morning, because the data is live.
Built With
- claude-ai
- deno
- edge-functions
- google-calendar-api
- google-sheets-api
- postgresql
- react
- row-level-security
- shadcn-ui
- sheetjs-xlsx
- supabase
- supabase-auth
- supabase-realtime
- tailwind-css
- typescript
- vite
- web-push-api
Log in or sign up for Devpost to join the conversation.