Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions submissions/T112_TheUnderground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Smart Attender

## Team

- **Team Name:** The Underground
- **Team ID:** T-112

| Member | Role |
| --- | --- |
| Hemanth | Backend architect and developer |
| Vishwaharthaj | Idea and Presentation |
| Akhil reddy | Frontend |
| Satwik | Data analysis on student records |
| Vishnu Prabhas | Designing and presentation |

## Problem Statement

Many educational institutions still depend on manual attendance systems, which are time-consuming and error-prone. Teachers spend a significant portion of class time marking attendance, reducing valuable instructional hours. Additionally, students often wa`ste free periods with unproductive activities due to a lack of structured guidance. This leads to poor time management and reduced alignment with long-term academic or career goals. There is also a gap in personalized learning support during idle classroom hours. Institutions currently lack tools that integrate daily schedules with individual student planning and automated tracking.

## Solution Overview

1. **Session setup:** The teacher portal generates a QR code bound to subject, schedule, and location.
2. **Student check-in:** The mobile app scans the QR code, verifies the trusted device, and gathers GPS coordinates.
3. **Proximity validation:** We calculate the distance between the student and session coordinates and adjust for GPS accuracy.
4. **Attendance logging:** Firestore transactions record the result in both the teacher’s session document and the student’s personal log.
5. **Enrichment:** Students receive short, AI-generated tasks to use their free period productively.

## Tech Stack

- **Mobile:** Expo React Native (TypeScript), Expo SecureStore, geolocation APIs
- **Web:** Next.js 14, Tailwind CSS, React Server Components
- **Backend & Data:** Firebase Auth, Firestore, Firestore security rules, Gemini 2.5 Flash API
- **Tooling:** Bun package manager, Node.js ≥ 18, ESLint & TypeScript

## How to Run the Project

### Prerequisites

- [Bun](https://bun.sh) ≥ 1.2
- Node.js ≥ 18
- Expo CLI (installed globally via `npm install -g expo-cli`)
- Firebase project with Web and Native app credentials

### Environment Variables

1. Copy the provided examples:

```bash
cp frontend/.env.example frontend/.env.local
cp smart-attender-student/.env.example smart-attender-student/.env.local
```

2. Populate both `.env.local` files with your Firebase keys. Required fields:

- Frontend: `NEXT_PUBLIC_FIREBASE_*` plus `GEMINI_API_KEY`
- Student app: `EXPO_PUBLIC_FIREBASE_*`, `EXPO_PUBLIC_TEACHER_API_BASE_URL`, optional `EXPO_PUBLIC_STUDENT_TASKS_ENDPOINT`

3. Create a Gemini API key in Google AI Studio for live task generation (falls back to static ideas if omitted).

### Teacher Web Portal (Next.js)

```bash
cd frontend
bun install
bun run dev
```

Visit `http://localhost:3000` to authenticate and manage sessions. Without Firebase credentials the portal runs in mock mode.

### Student Mobile App (Expo)

```bash
cd smart-attender-student
bun install
bun run android
```

Scan the QR code with an Expo Go client or run on a connected device/emulator. Device registration, proximity checks, and attendance logging require the same Firebase project configured above.

## Special Notes

- Device trust sticks to the first student who registers; anyone else on that hardware is blocked with a clear message.
- We subtract the combined GPS accuracy margin from the distance check so noisy signals don’t trigger false flags.
- Seed scripts depend on Firestore rules being deployed with `firebase deploy --only firestore:rules`.
- Gemini task calls consume your Google Cloud quota—cache or throttle them in production.
- Update the team name, ID, and member table with your final submission details.
Binary file not shown.
55 changes: 55 additions & 0 deletions submissions/T112_TheUnderground/code/firebase/firestore.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /students/{studentId} {
allow read, write: if request.auth != null && request.auth.uid == studentId;

match /{document=**} {
allow read, write: if request.auth != null && request.auth.uid == studentId;
}
}

match /teachers/{teacherId} {
allow read: if request.auth != null && request.auth.uid == teacherId;
allow create, update: if request.auth != null && request.auth.uid == teacherId;
allow delete: if false;

match /sessions/{sessionId} {
allow read, write: if request.auth != null && request.auth.uid == teacherId;
}
}

match /teacherAnalytics/{teacherId} {
allow read: if request.auth != null && request.auth.uid == teacherId;
allow create, update: if request.auth != null && request.auth.uid == teacherId;
allow delete: if false;
}

match /publicSessions/{sessionToken} {
allow read: if request.auth != null;
allow create: if request.auth != null && request.auth.uid == request.resource.data.teacherId;
allow update: if request.auth != null &&
(request.auth.uid == request.resource.data.teacherId || request.auth.uid == resource.data.teacherId);
allow delete: if false;

match /attendances/{studentId} {
allow read: if request.auth != null &&
(request.auth.uid == studentId || request.auth.uid == resource.data.teacherId);
allow create: if request.auth != null &&
request.auth.uid == studentId && request.resource.data.studentId == studentId;
allow update: if request.auth != null &&
request.auth.uid == studentId && request.resource.data.studentId == studentId;
allow delete: if false;
}
}

match /deviceDirectory/{deviceKey} {
allow read: if request.auth != null;
allow create: if request.auth != null && request.resource.data.studentId == request.auth.uid;
allow update: if request.auth != null &&
resource.data.studentId == request.auth.uid &&
request.resource.data.studentId == request.auth.uid;
allow delete: if false;
}
}
}
9 changes: 9 additions & 0 deletions submissions/T112_TheUnderground/code/frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your-sender-id
NEXT_PUBLIC_FIREBASE_APP_ID=your-app-id
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=your-measurement-id

GEMINI_API_KEY=
3 changes: 3 additions & 0 deletions submissions/T112_TheUnderground/code/frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
34 changes: 34 additions & 0 deletions submissions/T112_TheUnderground/code/frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules

# output
out
dist
*.tgz

# code coverage
coverage
*.lcov

# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# caches
.eslintcache
.cache
*.tsbuildinfo

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
15 changes: 15 additions & 0 deletions submissions/T112_TheUnderground/code/frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# smart-attender-teacher

To install dependencies:

```bash
bun install
```

To run:

```bash
bun run
```

This project was created using `bun init` in bun v1.2.5. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { NextResponse } from 'next/server';

import { requestGeminiTasks } from '@/lib/geminiTasks';

function generatePrompt(queryParams: URLSearchParams) {
const subject = queryParams.get('subject') ?? 'mixed subjects';
const gradeFocus = queryParams.get('gradeLevel') ?? 'Grades 9-12';

return `You are an instructional coach creating quick "free period" tasks for high school teachers.
Keep the response lean and JSON only. Output an array (length 3) of tasks formatted exactly like:
[
{
"id": "task-1",
"title": "Short title",
"description": "One sentence, actionable task aligned with current performance gaps.",
"focusArea": "concept-reinforcement" | "skills-practice" | "career-exposure",
"gradeLevel": "Grade 10",
"duration": "15 min"
}
]
Guidelines:
- Focus on ${subject} content for ${gradeFocus}.
- Duration must be either 10 min, 15 min, or 20 min.
- Titles <= 6 words. Descriptions <= 20 words. Avoid filler language.
- If you cannot comply, return [] (an empty array).
Return JSON only with no commentary.`;
}

export async function GET(request: Request) {
const apiKey = process.env.GEMINI_API_KEY;

if (!apiKey) {
return NextResponse.json(
{ error: 'Gemini API key is not configured. Set GEMINI_API_KEY in your environment.' },
{ status: 500 }
);
}

try {
const url = new URL(request.url);
const prompt = generatePrompt(url.searchParams);
const tasks = await requestGeminiTasks({ prompt, apiKey, maxTasks: 3 });
return NextResponse.json({ tasks });
} catch (error) {
console.error('[Gemini tasks] Failed to generate tasks', error);
return NextResponse.json({ error: 'Unable to generate tasks right now.', tasks: [] }, { status: 502 });
}
}

export const dynamic = 'force-dynamic';
export const revalidate = 0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { NextResponse } from 'next/server';

import { requestGeminiTasks } from '@/lib/geminiTasks';

function generateStudentPrompt(queryParams: URLSearchParams) {
const gradeLevel = queryParams.get('gradeLevel') ?? 'Grades 9-12';
const interest = queryParams.get('interest') ?? 'general interests';
const mood = queryParams.get('mood') ?? 'focused';
const time = queryParams.get('time') ?? '15 minutes';

return `You are a friendly mentor helping high school students use a short free period wisely.
Respond with JSON only: an array (length 3) of tasks. Each task must match this schema:
[
{
"id": "task-1",
"title": "Up to 5 words, energetic",
"description": "No more than 18 words. Give clear, positive steps for a student working solo.",
"focusArea": "concept-reinforcement" | "skills-practice" | "career-exposure",
"gradeLevel": "${gradeLevel}",
"duration": "${time}"
}
]
Guidelines:
- Keep tone encouraging and student-facing, never mention teachers.
- Make tasks realistic for a student with ${time} of free time while feeling ${mood}.
- Tie ideas to ${interest} when possible.
- Avoid homework-style chores; include quick wins or reflection prompts.
- If unsure, respond with [] (empty array).
Return JSON only with no extra words.`;
}

export async function GET(request: Request) {
const apiKey = process.env.GEMINI_API_KEY;

if (!apiKey) {
return NextResponse.json(
{ error: 'Gemini API key is not configured. Set GEMINI_API_KEY in your environment.' },
{ status: 500 }
);
}

try {
const url = new URL(request.url);
const prompt = generateStudentPrompt(url.searchParams);
const tasks = await requestGeminiTasks({ prompt, apiKey, maxTasks: 3 });
return NextResponse.json({ tasks });
} catch (error) {
console.error('[Gemini student tasks] Failed to generate tasks', error);
return NextResponse.json({ error: 'Unable to generate tasks right now.', tasks: [] }, { status: 502 });
}
}

export const dynamic = 'force-dynamic';
export const revalidate = 0;
Loading