Every student deserves to feel understood before they feel alone.
Otta is a campus-aware mental wellness platform powered by Ollie the Otter β an AI companion who knows your college, your season, and how you actually feel. Built for Indian engineering campuses where placement anxiety, hostel loneliness, and midsem burnout are realities no generic wellness app addresses.
Add screenshots here once the demo is recorded.
| Home & Mood Log | Ollie Chat | Peer Circles | Counselor Portal |
|---|---|---|---|
| (screenshot) | (screenshot) | (screenshot) | (screenshot) |
| Mood Patterns | Missions | Level & XP | Login |
|---|---|---|---|
| (screenshot) | (screenshot) | (screenshot) | (screenshot) |
npm install
npm start # Expo dev serverOpen the QR code in Expo Go on a phone connected to the same Wi-Fi.
For a full native build:
expo run:android # USB-connected deviceType checks:
npm run typecheck
npx expo install --check- Problem
- Solution
- Competitor Landscape
- Core Features
- Tech Stack
- High-Level Architecture
- Low-Level Design
- Data Models
- AI System Design
- Anonymous Peer Circles
- Counselor Intelligence
- Gamification System
- Privacy Architecture
- Screen Inventory
- Roadmap
Indian engineering students face a silent mental health crisis:
- 5.4 crore students enrolled in higher education (UGC 2023)
- 64% report significant academic stress (NIMHANS 2022)
- <5% ever seek professional help β stigma, cost, and lack of access are barriers
- Average wait time for on-campus counseling: 2β3 weeks
By the time a student reaches a counselor, the situation is often already severe. Existing solutions fail at three levels:
| Problem | Why apps fail |
|---|---|
| Campus blindness | Generic apps don't know what "compres" or "placement season" means |
| Friction | Mood tracking that takes more than 10 seconds gets abandoned |
| Isolation | No peer support; no shared struggle; no community |
| Invisible to institutions | Counselors have no visibility into campus-wide trends |
Otta provides before-crisis support through four pillars:
mindmap
root((Otta))
Ollie AI
Campus-aware chat
Mood pattern memory
Gemini Flash backbone
OpenRouter fallback
Mood Intelligence
One-tap logging
Calendar heatmap
Distribution analytics
Streak tracking
Peer Circles
Anonymous rooms
Optional identity reveal
Daily AI prompts
Ollie warmth sends
Counselor Portal
Anonymous trend cards
Risk alert triage
Campus pulse signals
Culture knowledge base
| Product | Campus Context | Peer Support | Counselor Tools | Anonymous | India-first | Gamified |
|---|---|---|---|---|---|---|
| Otta | β Deep | β Circles | β Dashboard | β Full | β | β |
| Wysa | β | β | β | Partial | β | β |
| Headspace | β | β | β | β | β | β |
| YourDOST | β | β | Limited | β | β | β |
| iCall | β | β | β | β | β | β |
| Intellect | Partial | β | Limited | β | β | β |
| Finch | β | β | β | β | β | β |
What competitors lack that Otta provides:
- Knowledge of midsems, placement season, hostel culture, wing dynamics
- Anonymous peer matching by emotional need
- Counselor-facing campus intelligence without exposing student identity
- Gamification that rewards showing up β not punishing absence
Ollie is an animated otter mascot with emotional states (idle, wave, smile, celebrate, think, worry). Every conversation is grounded in:
- Institution context β college name, season, lingo, and cultural knowledge set by the counselor
- Mood history β recent check-ins and emotional patterns
- Conversation memory β summarized per-session, stored in SQLite
- Peer circle state β what circles the student is in
Ollie uses Gemini Flash as the primary model with OpenRouter as fallback. Responses are streamed token-by-token for a natural feel.
- 5-mood palette: Calm, Stressed, Lonely, Tired, Hopeful
- Optional note with intelligent keyboard handling (safe area aware)
- Mood stored in SQLite with timestamp and reason
- Mood Patterns modal: calendar heatmap, distribution bar chart, day-of-week frequency, weekly trend delta, recent check-in timeline
Three pre-seeded circles per tenant, each with:
- Emoji-free anonymous names (Green Kite, Silver Leaf, etc.)
- Daily rotating AI-generated prompts
- Text messaging + reactions
- Ollie warmth sends (costs 5 coins, sends a generated supportive message)
- Optional identity reveal β mutual, peer-to-peer, with reciprocal reveal simulation
Daily rotating set of 4β5 missions (2 core + 2β3 rotating):
- Core: Log mood, 5-min breathing
- Rotating: Chat with Ollie, visit a Circle, 10-min walk, write one moment, sleep before midnight, etc.
- Counter missions (e.g. "Drink 8 glasses") with dot progress
- Bonus mission unlock for 10 coins
- All mission state persisted in SQLite
- Coins earned per mood log, daily check-in, mission completion
- XP = coins; level computed via
floor(sqrt(xp/25)) + 1 - Level perks (Mood Pioneer β Campus Legend)
- Closet screen (items, cosmetics β coin-gated)
Six tabs accessible after org creation:
- Overview β active students, avg mood, high-risk count, weekly check-in chart, stress sources
- Students β searchable anonymous list with risk badges, mood icons, bar metrics
- Student Detail β score cards, timeline, wellness activity log
- Risk Alerts β filter by status, action triage via proper Modal
- Campus Pulse β AI campus insight, trend grid, early warning signals
- Culture β set lingo, key timings, events, campus culture for Ollie
- Settings β knowledge bases, save
Counselor role persisted to SQLite (app_state table) so returning counselors skip onboarding.
| Layer | Technology |
|---|---|
| Framework | Expo SDK 56 (React Native) |
| Language | TypeScript |
| Navigation | Custom tab system (no expo-router) |
| Local DB | expo-sqlite β moods, conversations, missions, app state |
| AI | Gemini Flash (primary) + OpenRouter (fallback) |
| Fonts | Baloo 2 (headings) + Nunito (body) |
| Icons | @expo/vector-icons β Ionicons exclusively |
| Animations | react-native Animated API |
| Safe Area | react-native-safe-area-context |
| Widgets | expo-widgets (native build only) |
| Notifications | expo-notifications |
flowchart TD
subgraph Client["Mobile App (Expo)"]
UI["React Native UI"]
SQLite["SQLite (expo-sqlite)"]
WB["Widget Bridge"]
end
subgraph AI["AI Layer"]
Gemini["Gemini Flash"]
OR["OpenRouter Fallback"]
end
subgraph Persistence["Local Persistence"]
Moods["mood_entries"]
Chats["conversations"]
Missions["mission_states"]
AppState["app_state"]
Circles["circle_messages"]
end
UI --> SQLite
SQLite --> Persistence
UI --> Gemini
Gemini -- "fails/rate-limited" --> OR
UI --> WB
WB --> NativeWidget["Native Home Widget"]
- Fully offline-first: All mood data, mission state, and conversations live in SQLite β no backend required for the demo
- No navigation library: Custom
tabstate inApp.tsx, screen transitions viaScreenTransitioncomponent - State lifting for shared concerns: Reveal state, keyboard height, and bottom insets flow down from
AppContentto all screens - Role persistence: Counselor role stored in
app_stateKV table so the app auto-restores the session on relaunch
sequenceDiagram
actor Student
participant HomeScreen
participant BottomSheet
participant SQLite
participant OllieContext
Student->>HomeScreen: Taps "Log Mood"
HomeScreen->>BottomSheet: Opens modal (animates up)
Student->>BottomSheet: Selects mood + optional note
BottomSheet->>SQLite: insertMood(tenantId, mood, note)
SQLite-->>HomeScreen: mood saved
HomeScreen->>OllieContext: Updates ollieState β "wave"
HomeScreen->>HomeScreen: Confetti animation + toast
HomeScreen->>SQLite: recordDailyCheckin()
HomeScreen->>SQLite: insertConversation(system context update)
sequenceDiagram
actor Student
participant ChatComposer
participant App
participant generateOllieReply
participant Gemini
participant OpenRouter
Student->>ChatComposer: Types message
ChatComposer->>App: onSend(text)
App->>SQLite: insertConversation(role=user)
App->>generateOllieReply: messages[], userMemory, campusKnowledge
generateOllieReply->>Gemini: POST /generateContent (streaming)
alt Gemini success
Gemini-->>generateOllieReply: streamed tokens
else Gemini fails
generateOllieReply->>OpenRouter: POST /chat/completions
OpenRouter-->>generateOllieReply: response
end
generateOllieReply-->>App: full reply text
App->>SQLite: insertConversation(role=assistant)
App->>ChatComposer: renders streamed reply
flowchart LR
DeviceInsets["useSafeAreaInsets()"] --> bottomInset
KeyboardListener["Keyboard.addListener(keyboardDidShow)"] --> keyboardHeight
bottomInset --> navBottom["navBottom = max(16, bottomInset)"]
bottomInset --> sheetPadding["sheetPaddingBottom = max(bottomInset, 20) + 8"]
keyboardHeight --> composerBottom["composerBottom = kbHeight > 0 ? kbHeight+10 : navBottom+80"]
keyboardHeight --> BottomNavVisible["BottomNav hidden only during chat/groups with active room"]
stateDiagram-v2
[*] --> LoginScreen
LoginScreen --> CounselorPortalScreen : Create Org / Auto-login
CounselorPortalScreen --> Overview
CounselorPortalScreen --> Students
Students --> StudentDetail
StudentDetail --> Students
CounselorPortalScreen --> RiskAlerts
CounselorPortalScreen --> CampusPulse
CounselorPortalScreen --> Culture
CounselorPortalScreen --> Settings
CounselorPortalScreen --> LoginScreen : Logout (clears SQLite role)
All data lives in SQLite via expo-sqlite. Tables are created in src/db/localDb.ts.
erDiagram
mood_entries {
int id PK
text tenant_id
text mood
text reason
text note
text created_at
}
conversations {
int id PK
text tenant_id
text session_id
text role
text content
int tokens
text created_at
text last_interaction_at
}
daily_checkins {
int id PK
text tenant_id
text date
int completed
}
mission_states {
int id PK
text title
text status
int progress
text date
}
circle_messages {
int id PK
text circle_name
text sender
text content
int revealed
text created_at
}
circle_reactions {
int id PK
int message_id FK
text reactor
text created_at
}
circle_checkins {
int id PK
text circle_name
text mood
text date
}
app_state {
text key PK
text value
}
user_memory {
int id PK
text tenant_id
text key
text value
text updated_at
}
| Key | Value | Purpose |
|---|---|---|
role |
"counselor" |
Persists counselor login across sessions |
coins |
"42" |
Student coin balance |
totalXp |
"120" |
Total XP earned |
joinedCircles |
JSON array | Which circles the student has joined |
bonusMissionUnlocked |
"true" |
Bonus mission state |
tenant_id |
tenant ID string | Selected college |
Every Ollie message is assembled from five context layers:
flowchart TD
A["Institution Context\n(college, season, lingo, events)"] --> F
B["Mood History\n(last 5 moods + reasons)"] --> F
C["User Memory\n(personality traits, goals, patterns)"] --> F
D["Conversation History\n(sliding window, last 10 messages)"] --> F
E["Current Message"] --> F
F["System Prompt Builder"] --> G["Gemini Flash\ngeneraOllieReply()"]
G --> H["Streamed Response"]
Ollie is not a therapist. The system prompt enforces:
- Never diagnose conditions
- Never use clinical language
- Always validate before advising
- Keep responses concise (2β4 sentences unless asked)
- Reference campus context naturally ("placement week", "midsem prep")
- Suggest professional help for severe distress
// Primary: Gemini Flash via direct API
// Fallback: OpenRouter (any configured model)
// Decision: if Gemini throws, catch and route to OpenRouterEach student in a circle has a randomly assigned two-word alias (e.g. "Green Kite", "Silver Leaf"). Real names are never shown unless explicitly revealed.
sequenceDiagram
actor River as River (Green Pebble)
actor Jamie as Jamie (Green Kite)
River->>Circle: Posts message
Circle-->>Jamie: Shows as "Green Pebble said..."
River->>Circle: Taps "Reveal Identity to Green Kite"
Circle->>River: Confirms reveal intent
River-->>Jamie: "Green Pebble revealed: River"
Jamie->>Circle: Reciprocal reveal (simulated after 2s)
Circle-->>River: "Green Kite revealed: Jamie"
- Join/leave state:
app_state.joinedCircles - Reveal state: lifted to
App.tsx(revealedToPeers/revealedFromPeers) - Messages:
circle_messagestable - Reactions:
circle_reactionstable - Check-in mood:
circle_checkinstable
flowchart LR
subgraph Visible["Counselor CAN see"]
A["Anonymous student IDs\ne.g. #A1C9"]
B["Aggregated mood trends"]
C["Risk score bands"]
D["Participation rates"]
E["Campus-wide patterns"]
end
subgraph Hidden["Counselor CANNOT see"]
F["Real names"]
G["Chat history"]
H["Circle messages"]
I["Journal entries"]
J["Reveal decisions"]
end
Risk levels are computed from composite signals:
| Level | Score Range | Signals |
|---|---|---|
| π’ Normal | 0β40 | Consistent mood, high participation |
| π‘ Watch | 41β60 | Mild mood dip, some absence |
| π Alert | 61β79 | Sustained negative mood, isolation signals |
| π΄ Critical | 80β100 | Rapid decline, no check-ins, high stress |
flowchart LR
MoodLog["Log Mood\n+10 coins"] --> Coins
DailyCheckin["Daily Check-in\n+15 coins"] --> Coins
Mission["Complete Mission\n+15β40 coins"] --> Coins
Coins --> XP["XP = Coins"]
XP --> Level["Level = floor(sqrt(XP/25)) + 1"]
Level --> Perks["Level Perks unlock"]
Coins --> Closet["Closet items"]
Coins --> BonusMission["Bonus Mission unlock (-10)"]
Coins --> WarmthSend["Circle warmth send (-5)"]
Design principle: No punishment, no streak anxiety. Missing a day costs nothing. Showing up always rewards something.
flowchart TD
Student["Student Data"] --> LocalDB["SQLite (on-device only)"]
LocalDB --> AI["AI Context Builder"]
AI --> Gemini["Gemini API\n(stateless, no storage)"]
LocalDB --> CounselorView["Counselor View\n(anonymized only)"]
CounselorView -- "shows" --> AnonId["Anonymous ID\n#A1C9"]
CounselorView -- "NEVER shows" --> RealName["Real name / chat / journals"]
- No backend in demo β all data stays on device
- AI calls are stateless β Gemini receives context only for the current request
- Counselor screens show only anonymous IDs derived from internal hashes
- Reveal is always opt-in, mutual, and logged locally
| Screen | Role | Description |
|---|---|---|
LoginScreen |
Both | Tenant selection, student anonymous entry, counselor org creation |
HomeScreen |
Student | Mood log, Ollie greeting, mood patterns link, profile modal, logout |
ChatScreen |
Student | Multi-session Ollie chat with sidebar, streaming replies |
GroupsScreen |
Student | Join/leave circles, daily prompts, messaging, reveal identities |
MissionsScreen |
Student | Daily quest board, counter missions, bonus unlock |
LevelScreen |
Student | XP progress, level perks, coin balance, closet |
MoodCalendarScreen |
Student | Calendar heatmap, distribution bar, DoW heatmap, weekly delta |
CounselorPortalScreen |
Counselor | Tab shell with logout |
OverviewScreen |
Counselor | Stats, weekly chart, stress sources, circles activity |
StudentListScreen |
Counselor | Search, filter, risk badges, mood icons |
StudentDetailScreen |
Counselor | Score cards, mood timeline, wellness log |
RiskAlertScreen |
Counselor | Alert list, action triage modal |
CampusPulseScreen |
Counselor | AI insight card, trend grid, warning signals |
CultureScreen |
Counselor | Campus culture, lingo, timings, calendar |
SettingsScreen |
Counselor | Knowledge bases, save |
gantt
title Otta Product Roadmap
dateFormat YYYY-MM
section Phase 1 β Core
AI Companion (Ollie) :done, 2026-01, 2026-03
Mood Tracking + Calendar :done, 2026-02, 2026-04
Peer Circles :done, 2026-03, 2026-05
Gamification :done, 2026-03, 2026-06
section Phase 2 β Growth
Voice-first Ollie :2026-07, 2026-09
Institution SSO / LMS :2026-08, 2026-10
Advanced risk models :2026-09, 2026-11
section Phase 3 β Scale
Predictive burnout detection :2027-01, 2027-04
Cross-campus benchmark index :2027-03, 2027-06
White-label counselor platform :2027-05, 2027-09
section Phase 4 β Vision
Pan-India student network :2027-10, 2028-06
flowchart TD
App["Expo App"] --> API["REST API (FastAPI)"]
API --> PG["PostgreSQL\n(mood, users, convos)"]
API --> Redis["Redis\n(sessions, rate limits)"]
API --> Gemini["Gemini API"]
API --> NS["Notification Service\n(FCM/APNs)"]
API --> AE["Analytics Engine"]
AE --> CD["Counselor Dashboard"]
PG --> PGV["pgvector\n(semantic mood search)"]
Indian students in engineering colleges face compounding pressures β placements, academic competition, distance from family, and an environment that often rewards silence over vulnerability. The existing mental health infrastructure cannot scale to meet this demand.
Otta sits in the gap: not a replacement for counselors, but the bridge that gets students to safety before they need one. Every mood logged, every Ollie conversation, every circle message is a small act of self-awareness β and Otta makes those acts frictionless, private, and even rewarding.
The metric that matters most: students who feel heard don't wait until crisis. Otta exists to make that the default.
Built with Expo SDK 56 Β· Gemini Flash Β· React Native Β· SQLite Β· Ionicons