Inspiration
Picture it: you're at lunch, joking with your friends, and a teacher shouts a code word — the one you were taught means run. So you run. You end up behind a desk in the nearest classroom, and when you look around, half your group isn't there. Two of them were in the bathroom. Nobody knows if this is a drill.
Miles away, your parents have heard something happened at your school. They're missing the one piece of information that actually matters: were you safe?
That question is the whole problem. In a real school emergency, the school knows roughly what happened, and parents know roughly nothing. The gap between those two facts is where the panic lives — and it gets filled with rumors, group chats, and hundreds of parents driving to a campus that isn't ready to receive them.
Existing systems mostly send a broadcast: there was an incident, we'll update you. That's not an answer. We wanted to build the thing that answers the actual question, for one specific child, and we wanted it to be true — not a guess, not an algorithm's best estimate. Verifi is our attempt at that.
What it does
Verifi is a school emergency accountability and family reunification app. It has four roles, and each one sees only what that person needs:
- Student — one line of text, one button, and a rotating six-digit code. No roster, no count, no map. A frightened fourteen-year-old should not be handed a dashboard.
- Teacher — their roster, with With me / Not with me, the ability to add an off-roster student who ran into their room, and an offline queue so marks are captured even with no signal.
- Parent — one of three states: waiting, verified, or reunification. They see their own child and nothing else. No location, no map, no other family's kid.
- Admin — the live count, an accountability field showing every student as a tile, the evidence table for anyone still unconfirmed, and the ability to request a verification at a checkpoint.
The core loop is deliberately physical: a staff member scans a student's code with the camera and vouches for the person standing in front of them. That confirmation propagates to every phone in the building at once. The count moves in real time — 98, 99, and eventually 100 — and Declare all clear stays blocked until the last student is confirmed.
There's one AI-assisted screen: the admin's Suggestion block. It reads the evidence table and tells a human where to go look for a missing student. That is the entire job. It cannot mark anyone safe.
That constraint is the product. AI helps find the student; a human confirms the truth.
How we built it
Stack: Expo / React Native for the app, Supabase for the shared board, messaging, and auth, and OpenRouter's free-tier models for the assistant.
Realtime. The shared count is Postgres plus Supabase Realtime. When a teacher marks a student, that write lands in the database and gets pushed to every subscribed device, so the admin's number moves without anyone refreshing anything. Event state — statuses, counts, the confirm sequence, drill vs. live mode — lives in src/store.js as a single scoped object.
The AI layer is all in src/ai.js, and it's less than it sounds like on purpose: a five-model fallback chain over OpenRouter's free tier, a twelve-call budget per session, a twenty-second timeout, and a per-student cache. The written fallback suggestion is on screen from the very first frame, and the model's line swaps in only if and when it answers. Nobody waits on a model during an emergency. With no API key at all, the app runs exactly as it does with one.
The guardrails are in code, not in the prompt. Model output is rejected outright if it claims a student is safe, found, confirmed, or accounted for. Every suggestion must end by requiring a person to go verify — if the model forgets, we append it. scripts/ai-check.js runs 15 assertions against a list of deliberately bad outputs, with no network and no quota burned, so we can prove the guardrail works on demand.
The design system enforces the same rule visually. Three typefaces with three jobs, defined in src/theme.js. Source Serif 4 appears only on statements a human vouched for and on the all-clear — model text renders in Archivo, under a label reading SUGGESTION, NOT A CONFIRMATION. You can tell what's trustworthy without reading a word.
Other pieces: src/data.js holds 106 seeded students in six clusters plus the evidence table and staff list. src/components/Field.js is the accountability field — 28px tiles, a 400ms crossfade, one ring. scripts/qr.js detects the current network IP, checks the dev server actually answers on it, and generates scannable QR codes, which is what makes the multi-phone demo work on a hotspot.
Challenges we ran into
Building an AI feature that isn't allowed to be the answer. Our first instinct was the obvious one — let the model reconcile the evidence and tell you who's probably fine. We killed it. A model that is confidently wrong about a missing child is worse than no model at all. Rewriting it as suggestion only, and then enforcing that in code rather than trusting a system prompt, took longer than building the feature would have.
Realtime sync across networks we don't control. School Wi-Fi blocks phone-to-phone traffic, tunnels change URL on every restart, and demo Wi-Fi is never reliable. We ended up supporting three modes — LAN, tunnel, and a personal-hotspot path where a phone provides the network — and wrote the QR script because manually re-typing an IP that changes every time you switch networks was eating our build time.
Designing without red. We banned red from the entire app early on and then had to solve every urgency problem without it. Ochre carries all of it. It was harder than expected and it's now one of our favorite decisions: red means failure, and a student who simply hasn't been scanned yet has not failed at anything.
Free-tier quotas. Provider throttling on OpenRouter's free tier meant a single flaky model could stall a demo. Hence the fallback chain, the cache, and the rule that the UI never blocks on a model response.
Scope. We are two high schoolers and there were more roles, screens, and edge cases here than either of us had built before. We cut a lot. What survived is the rule that every screen is reachable and nothing dead-ends.
Accomplishments that we're proud of
- A safety rule you can actually verify.
npm run ai:checkproves in seconds, offline, that the model cannot claim a student is safe. Most "responsible AI" claims are a paragraph in a README. Ours is a test suite. - Typography as a trust signal. Serif means a human vouched for it. Never AI. It works instantly and requires no explanation.
- It runs with no internet. Two phones on a hotspot, marks queuing offline, the board syncing when the network returns.
- Declare all clear is blocked while anyone is unconfirmed. The button simply does not work. The whole product would be worthless if the last one percent could be waved away.
- Parents never see a location. We record an adult confirmed this student, not this student is here. Getting that distinction right meant the app is an accountability system and not a tracking system.
- No student photographs anywhere. Every piece of art in the app is drawn from design tokens.
What we learned
The biggest one: if a rule matters, put it in the code. Prompts are suggestions. A validator that rejects bad output is a guarantee. Once we internalized that, a lot of our design questions turned into engineering questions with checkable answers.
We also learned that constraints generate the good ideas. "No red," "serif only for human confirmations," and "AI can never change a status" all started as restrictions and all three ended up being the most distinctive parts of the product.
Technically, this was our first real work with Supabase Realtime and row-level security, with Expo's camera and offline storage, and with building a model fallback chain that degrades gracefully instead of failing loudly. And we learned that a demo that depends on someone else's Wi-Fi is a demo that will break — build the offline path first.
What's next for Verifi
- Proxy the API key server-side.
EXPO_PUBLIC_*values are inlined into the client bundle, so the current key setup is fine for a prototype and not fine for distribution. This is the first thing we'd fix. - Roster import. Right now the 106 students are seeded. Real deployment means pulling from a school's SIS (PowerSchool, Infinite Campus) rather than a JSON file.
- District multi-tenancy. The unit of scale is the event, not the app — each school runs its own scoped event, so a district is more rows, not more infrastructure. Row-level security is most of the way there.
- Drill mode as the adoption path. The version of this that gets used on the worst day is the version that's already familiar from ordinary fire drills. We want Verifi to be boring on a Tuesday.
- Accessibility and language support.
prefers-reduced-motionis already handled, but parent-facing text needs to exist in every language spoken by the families of a given school. That isn't a nice-to-have in a reunification tool. - Talk to people who've done this for real. We built from a scenario. Before we build further, we want feedback from school safety coordinators and emergency responders who've run an actual reunification.
Built by Aadit Mehta and Karsten Lee.
Log in or sign up for Devpost to join the conversation.