Skip to content
Merged
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
89 changes: 49 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@
[![Playwright](https://img.shields.io/badge/Playwright-1.49-45ba4b?style=flat-square&logo=playwright)](https://playwright.dev/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)

**Roll Book** is an attendance tracker and planning engine designed with a strict principle: **never assume a lecture happened just because the timetable says it should have.**
**Roll Book** is an intelligent attendance tracker, forecasting lab, and academic trajectory management suite designed with a strict principle: **never assume a lecture happened just because the timetable says it should have.**

---

## 🌟 Core Highlights

1. **Authentic Actual Mode**: Attendance statistics are derived *strictly* from verified portal snapshots and confirmed manual logs. Unlogged dates remain unloggedβ€”never silently assumed or blended into statistics.
2. **Predictive Planning Mode**: Driven by your recurring weekly timetable, allowing you to simulate *Plan to Attend* and *Plan to Skip* choices into the future and visualize your projected percentage trajectory (rendered with dashed indicators and trend curves) without corrupting your verified history.
3. **Playful Geometric Design System (Light + Dark Mode)**: Neo-brutalist sticker styling with chunky 2px borders, hard offset shadows, bouncy Framer Motion micro-interactions, Outfit display font, and instant theme switching via `next-themes`.
4. **AI Attendance Advisor (Google Gemini Free Tier)**: Built-in intelligent chatbot powered by `@google/genai` (Gemini 2.5 Flash) with deterministic function/tool calling directly into SQLite dataβ€”never hallucinating numbers.
5. **1-Click Batch Actions**: Fast bulk marking for today's lectures, past backlog audit items, and future planning days with a single tap.
6. **Holiday & Exam Declaration Engine**: Built-in calendar registry for official university recesses and examination days with automatic exclusion from past unlogged audits and future class simulations.
7. **Deterministic Math Engine**: Computes exact skippable buffers (how many classes you can afford to miss) or mandatory recovery streaks (how many consecutive attendances you need to restore compliance).
8. **1-Click Section Onboarding**: Automatically imports all subjects and weekly timetable schedules for 22 MIT Bengaluru CSE Stream sections (`C01`–`C22`).
9. **MAHE SLCM 2.0 Bridge**: A Playwright-based Salesforce response interceptor that synchronizes verified attendance counts directly from MAHE's portal without hardcoding volatile tokens.
3. **1-Click Live SLCM Attendance Sync**: Native local scraper bridge (`scraper/agent.js` on `http://localhost:4747`). Click "Sync Now" in the UI and Roll Book talks to the local agent, executes the Playwright session, and reconciles courses directly without manual file picking.
4. **Playful Geometric Design System (Dual Light/Dark Mode)**: High-contrast neo-brutalist sticker styling with Electric Teal primary accents (`#0D9488` light / `#2DD4BF` dark), chunky 2px borders, hard offset shadows, bouncy Framer Motion micro-interactions, Outfit display font, and instant theme switching via `next-themes`.
5. **AI Attendance Advisor (Google Gemini Free Tier)**: Built-in intelligent chatbot powered by `@google/genai` (Gemini 2.5 Flash) with deterministic function/tool calling directly into SQLite dataβ€”never hallucinating numbers.
6. **1-Click Batch Actions**: Fast bulk marking for today's lectures, past backlog audit items, and future planning days with a single tap.
7. **Holiday & Exam Declaration Engine**: Built-in calendar registry for official university recesses and examination days with automatic exclusion from past unlogged audits and future class simulations.
8. **Deterministic Math Engine**: Computes exact skippable buffers (how many classes you can afford to miss) or mandatory recovery streaks (how many consecutive attendances you need to restore compliance).
9. **1-Click Section Onboarding**: Automatically imports all subjects and weekly timetable schedules for 22 MIT Bengaluru CSE Stream sections (`C01`–`C22`).
10. **Local Single-Account Auth**: Protected by Web Crypto HMAC-SHA256 session middleware with out-of-the-box local credentials.
11. **Local-First & Portable**: Powered by SQLite via Prisma with zero external cloud dependencies. Full CSV exports and JSON backup/restore built in.

Expand Down Expand Up @@ -81,51 +81,55 @@ DATABASE_URL="file:./dev.db"
APP_USERNAME=admin
APP_PASSWORD=rollbook
APP_SESSION_SECRET=change-this-to-a-long-random-string
GEMINI_API_KEY=your-gemini-api-key-here
```

### 4. Start Development Server
### 4. Start Development Server & Local Sync Agent
In terminal 1 (Next.js App):
```bash
npm run dev
```

In terminal 2 (Local SLCM Sync Bridge):
```bash
npm run scraper:agent
```

Open **[http://localhost:3000](http://localhost:3000)** in your browser and sign in with **`admin`** / **`rollbook`**.

---

## 🧭 First-Time Setup & Onboarding

If your database is empty, the **Onboarding Wizard** will appear automatically:
1. Tap **"Get Started"**.
1. Tap **"Choose My Section"**.
2. Select your section (e.g. **`C06`**).
3. Confirmβ€”your 10 subjects and weekly schedule will be populated instantly.
3. Confirmβ€”your subjects and weekly schedule will be populated instantly.
4. Tap **"Sync Now (1-Click)"** to immediately pull your verified attendance baseline from SLCM.

---

## πŸ€– SLCM Scraper Workflow
## πŸ€– 1-Click SLCM Scraper Workflow

The scraper lives independently in `/scraper`:
The scraper runs as a lightweight local HTTP daemon (`scraper/agent.js`) on port `4747`:

```bash
cd scraper
npm install
npx playwright install chromium
node agent.js
```

### 1. One-Time Login (or when session expires)
```bash
node login.js
```
A browser window will open. Complete your MAHE Microsoft SSO and MFA login. Once redirected to `/s/attendance`, your authenticated session state is saved to `auth.json`.

### 2. Pull Live Attendance Numbers
```bash
node sync.js
```
Headlessly captures the `getCOPList` Apex action, unwraps the nested Salesforce envelope, discards telemetry noise, and writes `sync-output.json`.
### 1. 1-Click Sync (Recommended)
Inside Roll Book, navigate to **Settings** $\rightarrow$ **SLCM Sync Bridge** and click **Sync Now (1-Click)**.
- If it's your first time or your session expired, a visible browser will open automatically for Microsoft SSO / MFA.
- Once authenticated, it headlessly intercepts the `getCOPList` Apex payload, reconciles courses with your database, and presents the diff modal.

### 3. Import into Roll Book
- Open Roll Book $\rightarrow$ **Settings & Sync** $\rightarrow$ **SLCM Sync Bridge**.
- Click **Load Synced Data (JSON)** and select `scraper/sync-output.json`.
- Review the reconciliation diff and choose your course merge mappings before confirming.
### 2. Manual CLI Fallback
If you prefer running manual commands:
- Login: `cd scraper && node login.js`
- Sync: `node sync.js`
- Upload: In Settings, click *"Advanced: upload sync-output.json manually"*.

---

Expand All @@ -138,39 +142,44 @@ RollBook/
β”‚ β”œβ”€β”€ FORMULAS.md # Mathematical models and boundary proofs
β”‚ └── SLCM_SCRAPER_GUIDE.md # Salesforce Aura interception guide
β”œβ”€β”€ prisma/
β”‚ β”œβ”€β”€ schema.prisma # Course, TimetableSlot, AttendanceRecord models
β”‚ β”œβ”€β”€ schema.prisma # Course, TimetableSlot, AttendanceRecord, Holiday models
β”‚ └── seed.js # Sample semester seed data
β”œβ”€β”€ scraper/
β”‚ β”œβ”€β”€ login.js # Interactive SSO/MFA Playwright login
β”‚ β”œβ”€β”€ sync.js # Headless getCOPList response listener
β”‚ β”œβ”€β”€ agent.js # Local HTTP bridge server (http://localhost:4747)
β”‚ β”œβ”€β”€ login.js # Modular SSO/MFA Playwright login
β”‚ β”œβ”€β”€ sync.js # Modular headless getCOPList response listener
β”‚ β”œβ”€β”€ package.json # Scraper dependencies
β”‚ └── README.md # Scraper quickstart
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ app/
β”‚ β”‚ β”œβ”€β”€ api/
β”‚ β”‚ β”‚ β”œβ”€β”€ attendance/ # Attendance record CRUD
β”‚ β”‚ β”‚ β”œβ”€β”€ attendance/ # Attendance record CRUD & bulk logger
β”‚ β”‚ β”‚ β”œβ”€β”€ auth/ # Login, logout, session check
β”‚ β”‚ β”‚ β”œβ”€β”€ chat/ # Google Gemini AI Attendance Advisor
β”‚ β”‚ β”‚ β”œβ”€β”€ courses/ # Subject management
β”‚ β”‚ β”‚ β”œβ”€β”€ export/ # CSV and JSON backup/restore
β”‚ β”‚ β”‚ β”œβ”€β”€ holidays/ # Holiday and exam-day registry
β”‚ β”‚ β”‚ β”œβ”€β”€ reset/ # Full database atomic reset
β”‚ β”‚ β”‚ β”œβ”€β”€ sections/ # Official department section schedules
β”‚ β”‚ β”‚ β”œβ”€β”€ sync/ # SLCM reconciliation diff engine
β”‚ β”‚ β”‚ └── timetable/ # Weekly slot management
β”‚ β”‚ β”œβ”€β”€ login/ # Dark-theme login page
β”‚ β”‚ β”œβ”€β”€ globals.css # Design tokens & theme
β”‚ β”‚ β”œβ”€β”€ layout.tsx # Shell
β”‚ β”‚ β”œβ”€β”€ login/ # High-contrast login page
β”‚ β”‚ β”œβ”€β”€ globals.css # Playful Geometric design tokens & CSS variables
β”‚ β”‚ β”œβ”€β”€ layout.tsx # Shell with Outfit and Plus Jakarta Sans fonts
β”‚ β”‚ └── page.tsx # App orchestrator
β”‚ β”œβ”€β”€ components/
β”‚ β”‚ β”œβ”€β”€ Navigation.tsx # Header, stats badge, and sign-out
β”‚ β”‚ β”œβ”€β”€ HomeView.tsx # Dashboard with Today's Quick Logger
β”‚ β”‚ β”œβ”€β”€ Navigation.tsx # Header, stats badge, theme switcher, and sign-out
β”‚ β”‚ β”œβ”€β”€ HomeView.tsx # Dashboard with Today's schedule and past audit
β”‚ β”‚ β”œβ”€β”€ SubjectsView.tsx # Cards, sparklines & audit trail
β”‚ β”‚ β”œβ”€β”€ CalendarView.tsx # Month matrix & live planning simulator
β”‚ β”‚ β”œβ”€β”€ SettingsView.tsx # Timetable editor, sync bridge, reset
β”‚ β”‚ β”œβ”€β”€ OnboardingWizard.tsx # 1-click section setup flow
β”‚ β”‚ β”œβ”€β”€ SettingsView.tsx # Timetable editor, 1-click sync bridge, reset
β”‚ β”‚ β”œβ”€β”€ ChatWidget.tsx # Google Gemini AI Attendance Advisor widget
β”‚ β”‚ β”œβ”€β”€ OnboardingWizard.tsx # 1-click section setup flow with SLCM sync prompt
β”‚ β”‚ β”œβ”€β”€ CourseModal.tsx
β”‚ β”‚ β”œβ”€β”€ SlotModal.tsx
β”‚ β”‚ β”œβ”€β”€ SectionImportModal.tsx # Department timetable selector
β”‚ β”‚ └── SyncModal.tsx # SLCM diff & merge selector
β”‚ β”‚ β”œβ”€β”€ SyncModal.tsx # SLCM diff & merge selector
β”‚ β”‚ └── ThemeProvider.tsx # next-themes client provider
β”‚ β”œβ”€β”€ lib/
β”‚ β”‚ β”œβ”€β”€ attendance.ts # Deterministic math engine
β”‚ β”‚ β”œβ”€β”€ auth.ts # HMAC-SHA256 Web Crypto session token helpers
Expand Down
7 changes: 6 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,12 @@ When launching with an empty database, Roll Book automatically presents an onboa
### 9. AI Attendance Advisor (`/api/chat`)
- `POST /api/chat` β€” Google Gemini (`@google/genai`) AI endpoint with native function/tool calling against Prisma database (`get_attendance_summary`, `get_course_detail`, `get_upcoming_classes`, `get_unlogged_sessions`).

### 10. Data Portability (`/api/export`)
### 10. SLCM Scraper Agent Bridge (`http://localhost:4747`)
- `GET /status` β€” Checks if `auth.json` is present and valid.
- `POST /sync` β€” Headlessly captures portal attendance via Playwright (prompts SSO browser if unauthenticated) and directly returns `{ courses, syncedAt, capturedVia }`.
- `POST /login` β€” Launches interactive browser for Microsoft SSO & MFA authentication.

### 11. Data Portability (`/api/export`)
- `GET /api/export?format=csv` β€” Downloads complete attendance audit trail as spreadsheet CSV.
- `GET /api/export?format=json` β€” Generates a full database backup snapshot.
- `POST /api/export` β€” Restores database state from a backup JSON file.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"lint": "next lint",
"db:push": "prisma db push",
"db:seed": "node prisma/seed.js",
"db:studio": "prisma studio"
"db:studio": "prisma studio",
"scraper:agent": "node scraper/agent.js"
},
"dependencies": {
"@google/genai": "^2.23.0",
Expand Down
104 changes: 104 additions & 0 deletions scraper/agent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// agent.js β€” Local scraper bridge server on http://localhost:4747
const http = require('http');
const fs = require('fs');
const path = require('path');
const { runLogin } = require('./login');
const { runScrape } = require('./sync');

const PORT = 4747;
const AUTH_PATH = path.resolve(__dirname, 'auth.json');

function setCorsHeaders(res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
}
Comment on lines +11 to +15

const server = http.createServer(async (req, res) => {
setCorsHeaders(res);

if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}

const url = new URL(req.url, `http://${req.headers.host || 'localhost:4747'}`);

// Endpoint: GET /status
if (req.method === 'GET' && url.pathname === '/status') {
const hasSession = fs.existsSync(AUTH_PATH) && fs.statSync(AUTH_PATH).size > 10;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({
status: 'online',
hasSession,
port: PORT,
})
);
return;
}

// Endpoint: POST /login
if (req.method === 'POST' && url.pathname === '/login') {
try {
console.log('[Agent] Triggering interactive login...');
const result = await runLogin(AUTH_PATH);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, message: 'Login complete', result }));
} catch (err) {
console.error('[Agent] Login failed:', err);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, error: err.message }));
}
return;
}

// Endpoint: POST /sync
if (req.method === 'POST' && url.pathname === '/sync') {
try {
console.log('[Agent] 1-Click Sync requested.');

// If no session exists, run interactive login first
if (!fs.existsSync(AUTH_PATH)) {
console.log('[Agent] auth.json missing. Opening login browser...');
await runLogin(AUTH_PATH);
}

let payload;
try {
payload = await runScrape(AUTH_PATH);
} catch (scrapeErr) {
if (scrapeErr.message === 'SESSION_EXPIRED' || scrapeErr.message === 'DID_NOT_CAPTURE_COURSES') {
console.log('[Agent] Session expired or stale. Re-launching login...');
await runLogin(AUTH_PATH);
payload = await runScrape(AUTH_PATH);
} else {
throw scrapeErr;
}
}

res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(payload));
} catch (err) {
console.error('[Agent] Sync error:', err);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({
error: err.message || 'Scrape failed. Please check portal credentials or rerun login.',
})
);
}
return;
}

res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Endpoint not found' }));
});

server.listen(PORT, () => {
console.log(`\n========================================`);
console.log(`πŸš€ SLCM Scraper Agent listening on http://localhost:${PORT}`);
console.log(`Ready for 1-Click Sync requests from Roll Book.`);
console.log(`========================================\n`);
});
60 changes: 35 additions & 25 deletions scraper/login.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
// login.js β€” run this once, and again whenever your session expires.
// Opens a real, visible browser so you can log in through Microsoft SSO
// (including MFA) yourself. Once you land back on the attendance page,
// it saves the authenticated session to auth.json for sync.js to reuse.
//
// Run: node login.js

// login.js β€” interactive Microsoft SSO + MFA login module
const { chromium } = require('playwright');
const path = require('path');

(async () => {
async function runLogin(authPath = path.resolve(__dirname, 'auth.json')) {
console.log('[Login] Launching interactive browser for MAHE Microsoft SSO...');
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();

await page.goto('https://maheslcmtech.manipal.edu/s/attendance');

console.log('A browser window has opened.');
console.log('Log in with your MAHE Microsoft account, complete MFA if prompted.');
console.log('Waiting for you to land back on the attendance page (up to 5 minutes)...');

// Wait until SSO finishes redirecting us back to the attendance page
await page.waitForURL('**/s/attendance**', { timeout: 5 * 60 * 1000 });

// Give the single-page app a moment to finish its own internal loading
await page.waitForTimeout(5000);

await context.storageState({ path: 'auth.json' });
console.log('Session saved to auth.json. You can now run: node sync.js');

await browser.close();
})();
try {
await page.goto('https://maheslcmtech.manipal.edu/s/attendance', {
waitUntil: 'domcontentloaded',
timeout: 60000,
});

console.log('[Login] A browser window has opened.');
console.log('[Login] Log in with your MAHE Microsoft account, complete MFA if prompted.');
console.log('[Login] Waiting for redirect back to attendance page (up to 5 minutes)...');

await page.waitForURL('**/s/attendance**', { timeout: 5 * 60 * 1000 });
await page.waitForTimeout(5000);

await context.storageState({ path: authPath });
console.log(`[Login] Session saved to ${authPath}`);
return { success: true, authPath };
} finally {
await browser.close();
}
}

if (require.main === module) {
runLogin()
.then(() => console.log('Login complete. You can now run: node sync.js'))
.catch((err) => {
console.error('Login error:', err);
process.exit(1);
});
}

module.exports = { runLogin };
Loading
Loading