Sign up with a college address, get approved, and watch your class's GitHub activity
sync itself into eight role-based dashboards — no spreadsheet surgery.
Features · Architecture · Tech stack · Preview · Get Started · Installation · Troubleshooting · Status · Contributing · License
Live deployment: https://student-analytics-iota.vercel.app
- Email/password sign-up restricted to college addresses, plus Continue with Google when OAuth credentials are configured; GitHub and LinkedIn sign-in are wired for linking a profile to an existing session.
- Three roles — student / faculty / admin — enforced by a route-level auth gate, not by hiding UI. Sessions are HMAC-signed cookies.
- Domain allowlist lives in
ALLOWED_OAUTH_DOMAINS(defaultmitwpu.edu.in) and is re-checked server-side on every signup and OAuth callback.
- Students submit PRN, degree, division, practical batch (lab section), semester, and main batch from the Onboarding page; faculty/admin approve, reject, edit, or remove entries from a pending ledger.
- Approval promotes the student's linked GitHub handle into their account identity — that handle is what every later sync reads.
- Nightly Vercel cron against
/sync/accounts, a Sunday/sync/weeklyannouncement run, and a GitHub Actions Heavy Sync daily at 00:00 UTC (deep commits/PRs/team repos, one student at a time, resumable and rate-limit aware). - Students also self-sync on login, and admins can force a fleet refresh from
/debug/force_sync_all. - Rate-limit aware: GitHub token rotation (
GITHUB_TOKEN+ extraGITHUB_TOKEN_*vars), waits for the published reset epoch, and surfaces friendly errors instead of tracebacks.
- Navigation includes Overview, Onboarding, Students, Repositories, AutoCAD, Leaderboards, Support, Settings, and My Profile — plus the same profile panel behind the sidebar avatar at
/me. Admins also have the certificate Verification page. - AutoCAD currently accepts student
.dwguploads up to 4 MB and gives faculty/admin a recent-submissions download inbox. Browser previews, assignment grouping, grades, and remarks are planned follow-up work; seedocumentation/AutoCAD Submissions.md. - Pages render from the synced account fleet — no upload required — and the Students and Repositories tables export CSV or XLSX.
- Repos and followers are also reported per year of account age (
Repos_Per_Account_Year,Followers_Per_Account_Year) so newer accounts are not punished.
- Six boards — most active repositories, most commits, most stars, top starred repositories, HackerRank problems solved, and top practice scores — across a filtered cohort (division, batch, semester, time window), with a GitHub/HackerRank platform switcher.
- Faculty/admin can blacklist students or hide repositories from a board; the moderation state persists for the whole fleet.
- Students raise tickets with optional image attachments (validated by magic bytes, not just the file extension); staff reply and move them through Open → In Progress → Follow up → Resolved.
- Server-sent events bell at
/api/notifications/stream— students see their own issue alerts, staff see ticket alerts, every role gets the Sunday top-committer announcement, each with a deep link.
- Lazy profile tab backed by a vendored, unauthenticated HackerRank client (fresh fetch per open, display-only): tier-colored hex skill badges, stars, solved counts, last-active, and a paginated solved-questions list.
- Two leaderboard boards — Most Problems Solved and Top Practice Scores — rank snapshots stored in Postgres
hackerrank_snapshots(in-memory fallback); unsynced profiles show as awaiting sync, and both boards support the admin blacklist like every other board. - Leaderboard data refreshes daily at midnight (IST) via
.github/workflows/hackerrank-sync.yml: the GitHub Actions runner (not Vercel) pulls the stale-first queue fromGET /api/hackerrank/handles, fetches profiles at a polite pace, and posts results toPOST /api/hackerrank/snapshot. Profile opens never sync leaderboards — the daily run owns freshness. Needs the sameAPP_URL+CRON_SECRETrepo secrets asheavy-sync.yml. - Manual backfill still exists: admins/faculty hit Sync HackerRank on the Leaderboards page (or
POST /sync/hackerrank), which syncs a few of the stalest profiles per call on a light sequential path (3 requests/profile, 1s pacing, aborts at the first 429).
DATABASE_URL→ Neon Postgres (schema idempotently initialised at startup); noDATABASE_URL→ the bundled SQLite files, so local dev needs zero setup.- GitHub responses cached for 1 h in Upstash Redis when configured, in-process otherwise.
Server-rendered, deliberately thin: FastAPI routes in app/main.py handle HTTP, page payload builders live in app/views.py, and everything that talks to the internet or does math stays in app/services.py + app/sync.py. Jinja2 templates render HTML; ECharts option objects are composed server-side and drawn client-side (Overview only); live updates arrive over Server-Sent Events.
sign-up / OAuth → onboarding approval → sync (cron + Actions + on login)
→ account snapshots (Postgres or SQLite) → views.py payloads → 8 pages
- Feature inventory:
documentation/Features List.md - Roadmap and bug log:
documentation/Taskflow.md·documentation/Bug Tracker.md
app/ FastAPI application
main.py routes, auth gate, cron endpoints
services.py GitHub API + aggregation (ported, logic frozen)
sync.py snapshot compute + fleet sweep
views.py page payload builders
charts.py legacy Plotly builders (unused — charts are ECharts)
auth.py sessions, roles, domain gate
db.py Postgres query layer (SQLite fallback)
templates/ Jinja2 pages + partials
static/ CSS, JS, logo (Favicon.png)
tests/ pytest suite (two-mode parity harness)
scripts/ perf_loop.py (page-timing harness)
documentation/ Bug Tracker, Features List, Taskflow, ANALYSIS, AutoCAD Submissions
vercel.json ASGI entry + cron schedule
| Layer | Stack |
|---|---|
| Language | |
| Web framework | |
| Templating | |
| Interactivity | |
| Charts | |
| Data | |
| OAuth | |
| Cache | |
| Database | |
| CI | |
| Tests | |
| Hosting |
Badges carry the app palette instead of per-brand colors: label eee9df (the --bg paper cream from static/theme.css) and message be342b (the --red behind every .btn-primary — the same vermilion family as the logo's wordmark), with dark ink icons (142235, the --text color) on the cream label and white text on the red message. The CodeQL badge is GitHub-rendered and keeps its own colors; logo= is omitted where simple-icons has no slug (uvicorn, openpyxl).
No screenshots yet — add captures under docs/screenshots/ following the comment above.
git clone https://github.com/Horrid-12/Student-Analytics.git
cd Student-Analyticspython3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtWindows (PowerShell)
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txtEverything goes in .env.local (gitignored, auto-loaded at startup by app/env.py, and the file vercel env pull writes — shell environment variables always win over it). There is no .env.example; the variables below are exactly what the code reads.
Verified: import app.main succeeds with an empty environment and no .env.local — nothing here is required for the server to start. The Required column says what degrades.
| Variable | Required | What it controls |
|---|---|---|
GITHUB_TOKEN |
Recommended | 5,000 GitHub requests/hour instead of 60; without it the nightly sync rate-limits on any real fleet. Extra GITHUB_TOKEN_* vars are picked up as rotation tokens. |
AUTH_SECRET |
Recommended | HMAC key for session cookies; absent → server warns and derives a per-process secret, so logins drop on every restart. |
GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET |
Optional | Shows Continue with Google; both absent → button hidden, email/password still works. |
GITHUB_OAUTH_CLIENT_ID + GITHUB_OAUTH_CLIENT_SECRET |
Optional | GitHub sign-in used to link a profile to an existing session. |
LINKEDIN_CLIENT_ID + LINKEDIN_CLIENT_SECRET |
Optional | LinkedIn profile linking. |
ALLOWED_OAUTH_DOMAINS |
Optional (default mitwpu.edu.in) |
College-domain allowlist for signup and OAuth, comma- or space-separated. |
OAUTH_REDIRECT_BASE_URL |
Optional (defaults to the request origin) | Canonical origin for OAuth callbacks — set it to the deployed origin in production. |
CRON_SECRET |
Required for automation | Authorizes POST /sync/accounts, /sync/weekly, POST /api/sync/single/{email}, and GET /api/users/approved when no admin session is present; absent → 403 for Vercel Cron and the GitHub Action. |
DATABASE_URL |
Optional (aliases POSTGRES_URL, TEST_DATABASE_URL) |
Neon Postgres storage; absent → bundled SQLite files (analytics_history.db, users.db, …). |
DATABASE_URL_UNPOOLED / UNPOOLED |
Optional | Direct (non-pooled) connection used for schema DDL. |
UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN |
Optional | Cross-instance GitHub response cache; absent → in-process cache. |
ADMIN_EMAILS, FACULTY_EMAILS, ADMIN_PASSWORD_HASH, ADMIN_NAME |
Optional | Staff roles for password logins without a pre-seeded database row. |
FACULTY_INVITE_EMAIL + FACULTY_INVITE_PASSWORD_HASH (or FACULTY_INVITE_PASSWORD) |
Optional | One-time pre-saved faculty login — first sign-in forces /faculty-setup (welcome popup → new email + password), then the invite stops working. DB invites via python -m app.seed_users EMAIL PASS faculty --invite. |
SYNC_TTL_SECONDS |
Optional (default 3600) |
Snapshot freshness window — younger snapshots are skipped during a sweep. |
APP_URL |
GitHub Actions secret | Base URL the Heavy Sync workflow calls; not read by the app itself. |
TEST_DATABASE_URL |
Test only | Enables tests/test_db_layer.py. |
Minimal .env.local to get real data flowing:
GITHUB_TOKEN="ghp_pasteYourTokenHere"
AUTH_SECRET="a-long-random-string"
ALLOWED_OAUTH_DOMAINS="your-college.edu"
CRON_SECRET="another-long-random-string"A free classic token from https://github.com/settings/tokens with no extra scopes is enough — only public data is read.
uvicorn app.main:app --port 8001 --reloadWindows (PowerShell)
.\.venv\Scripts\python.exe -m uvicorn app.main:app --port 8001 --reloadOr just .\run.bat (macOS/Linux: ./run.sh) — both pick a usable interpreter, pick port 8001, and open the browser.
Open http://localhost:8001.
- Create an account with a college address (or sign in with Google).
- Submit the onboarding form (PRN, degree, division).
- A faculty/admin account approves it — bootstrap staff once with:
.\.venv\Scripts\python.exe -m app.seed_users admin@your-college.edu "secret" admin
- Data arrives on its own: nightly cron, the 6-hourly Heavy Sync Action, and self-sync on login. Admins can force
/debug/force_sync_all. - Explore the sidebar pages; export any table to CSV/XLSX.
.\.venv\Scripts\python.exe -m app.init_db # create tables (idempotent)
.\.venv\Scripts\python.exe -m app.seed_users admin@col.edu "secret" adminWithout DATABASE_URL everything transparently falls back to SQLite — skip this section for local dev.
| Platform | Prerequisites | Install | Run |
|---|---|---|---|
| Windows (PowerShell) | Python 3.11+ on PATH | python -m venv .venv then .\.venv\Scripts\python.exe -m pip install -r requirements.txt |
.\run.bat |
| macOS | Python 3.11+ (brew install python) |
python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt |
./run.sh |
| Linux | Python 3.11+ via your package manager | python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt |
./run.sh |
Every platform serves the same thing: uvicorn app.main:app --port 8001. Prefer manual control? Use the raw uvicorn command from Get Started → 4. Run.
| Symptom | Fix |
|---|---|
ModuleNotFoundError: No module named 'pandas' |
Activate the venv first, then pip install -r requirements.txt |
| "GitHub API rate limit reached" | Set GITHUB_TOKEN in .env.local and restart, or wait for the reset time shown in the error |
| "Continue with Google" button missing | GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET are absent — add them to .env.local and restart |
| Cron or Heavy Sync returns 403 | CRON_SECRET is unset or differs between Vercel env and the GitHub Actions secret |
| Pages show a placeholder screen | Approve at least one onboarding submission and let a sync run (or hit /debug/force_sync_all as admin) |
| Works locally but not on Vercel | Push the same values as Vercel project env vars — secret values download as empty stubs, so vercel env pull alone never restores them |
| Port 8001 already in use | uvicorn app.main:app --port 8002 |
| App breaks after pulling changes | Re-run pip install -r requirements.txt — dependencies may have changed |
Run from the repo root. The suite exercises both the frozen legacy services.py and the ported app/services.py:
# All tests vs the frozen legacy module
.\.venv\Scripts\python.exe -m pytest tests\ -q
# Same suite vs the port
$env:MODULE_UNDER_TEST="app.services"; .\.venv\Scripts\python.exe -m pytest tests\ -q; Remove-Item Env:\MODULE_UNDER_TEST
# Focused runs
.\.venv\Scripts\python.exe -m pytest tests\test_github_client.py -q # transport/cache, no network
.\.venv\Scripts\python.exe -m pytest tests\test_pages_36.py -q # every page renders/exports
# Postgres path (skipped unless set)
$env:TEST_DATABASE_URL="postgresql://..."; .\.venv\Scripts\python.exe -m pytest tests\test_db_layer.py -q; Remove-Item Env:\TEST_DATABASE_URLThe app runs on Vercel with the Python runtime — vercel.json points builds/routes at app/main.py directly (no Mangum wrapper):
- Push to
main; the linked Vercel project deploys on every push. - Set project env vars: at minimum
GITHUB_TOKEN,AUTH_SECRET,CRON_SECRET,ALLOWED_OAUTH_DOMAINS, andOAUTH_REDIRECT_BASE_URL(the canonical deployment origin, no trailing path). - Register
…/auth/google/callbackand…/auth/github/callbackon that origin with each OAuth provider. - Optional:
DATABASE_URL(then runpython -m app.init_dbonce),UPSTASH_REDIS_REST_URL+UPSTASH_REDIS_REST_TOKEN, and the Google/GitHub/LinkedIn client pairs. - Cron is already wired:
vercel.jsonschedules/sync/accountsnightly and/sync/weeklyon Sundays — they 403 untilCRON_SECRETmatches. - For the Heavy Sync GitHub Action, add repository secrets
APP_URL(production origin) andCRON_SECRET(same value as Vercel).
| Area | State |
|---|---|
| Production | Live at https://student-analytics-iota.vercel.app |
| CI | CodeQL on push/PR to main plus a weekly run; Dependabot weekly for pip and GitHub Actions |
| Scheduled jobs | Vercel crons (nightly accounts + weekly announcement), GitHub Actions Heavy Sync daily (00:00 UTC) and HackerRank sync daily (00:00 IST) |
| Test suite | Green — 734 passed / 10 skipped (default mode), 729 passed / 15 skipped (MODULE_UNDER_TEST=app.services), 2026-10-22. The Postgres layer (tests/test_db_layer.py) stays skipped without TEST_DATABASE_URL. |
- Pick an open item from
documentation/Bug Tracker.md, or an unbuilt row indocumentation/Features List.md, and claim it. - Work on a branch, not straight on
main. - After every change: keep
documentation/Features List.mdin sync with what shipped, and log any fixed bug indocumentation/Bug Tracker.md. - Never commit student data files, tokens, or
.streamlit/secrets.toml— rosters (*.xlsx) and env files are gitignored on purpose. - Report security issues per
SECURITY.md— not through public issues.
No license file is published yet — all rights reserved by the authors. If you want to use or fork this beyond the terms your institution grants, open an issue to request a license.