Skip to content

feat: Daily streak score - incentivise consistent attendance with Mon-Sat streak logic - #111

Open
23f3001369 wants to merge 1 commit into
vicharanashala:mainfrom
23f3001369:feat/streak-score
Open

feat: Daily streak score - incentivise consistent attendance with Mon-Sat streak logic#111
23f3001369 wants to merge 1 commit into
vicharanashala:mainfrom
23f3001369:feat/streak-score

Conversation

@23f3001369

@23f3001369 23f3001369 commented Jul 28, 2026

Copy link
Copy Markdown

Summary

This PR introduces a daily streak scoring system that rewards students for consistent session attendance. The feature tracks consecutive qualifying days (Mon-Sat), awards escalating bonus SP at milestones, and provides a hearts-based safety net so a single missed day doesn't destroy weeks of progress.

Sundays are explicitly excluded — they neither count as streak days nor break streaks — making the system naturally aligned with the Mon-Sat session schedule.


Motivation

Spurti currently awards attendance and poll SP per-session, but there is no incentive for day-over-day consistency. A student who attends 5 out of 6 days gets almost the same total as one who attends all 6. The streak feature changes this by compounding rewards for unbroken chains of attendance, which:

  1. Drives daily engagement — students show up every day, not just most days.
  2. Builds habits — consecutive-day tracking creates a psychological commitment loop.
  3. Rewards loyalty — long-running streaks earn meaningfully more SP (up to 2x daily rate after day 30).

How It Works

Qualification (per day)

A student qualifies for a streak day if, on at least one session that calendar date:

  • attendancePercentage >= 85% (from attendancerecords)
  • poll participation >= 85% (from pollrecords)

If no attendance records exist (dev / pre-pipeline), the system falls back to sptransactions: appliedDelta >= 10 (which implies >= 90%) satisfies the 85% threshold.

Sunday logic

  • Sundays are skipped entirelynextWeekday() advances from Friday to Monday, so a Friday-Saturday-Monday chain is consecutive.
  • The system uses isConsecutiveGap(lastQualifyingDate, candidateDate) which internally calls nextWeekday() instead of naive addDays(..., 1).

Eligibility cutoff

Only students whose internshipStartDate >= 2026-07-16 are eligible. Students who started before this date receive no streak processing. This is enforced both at the service level and in the cron script.

SP rewards

Streak day SP earned
Days 1-9 1 SP/day
Day 10 (milestone) 5 SP
Days 11-19 1 SP/day
Day 20 (milestone) 7 SP
Days 21-29 1 SP/day
Day 30 (milestone) 9 SP
Days 31-39 2 SP/day
Day 40 (milestone) 11 SP
Every 10th day after 30 +2 more than previous milestone

Milestone formula: 3 + (streakDay / 10) * 2

Hearts (streak savers)

  • Each student starts with 2 hearts.
  • If a qualifying day is missed, a heart is consumed to preserve the streak instead of resetting to 0.
  • Hearts are never used during backfill — only during live daily processing or manual claim.
  • When a heart is used, lastQualifyingDate is updated to the heart-saved date, resetting the gap counter so consecutive hearts work correctly.
  • Losing both hearts resets the streak to 0.

What Changed

New files

File Purpose
server/models/Streak.js Mongoose schema for streaks collection
server/services/streakService.js Core business logic
server/cron/streakDaily.js Standalone Node script for daily cron execution
server/services/__tests__/streakService.test.js 37 unit tests covering all edge cases

Modified files

File Change
server/server.js +streak imports, streak in profile, 4 new API routes
server/config.js STREAK_CUTOFF_DATE, threshold constants
server/models/SPTransaction.js Added 'streak' to category enum
client/src/main.jsx StreakCard + StreakDetail components, Streak tab
client/src/styles.css 75 lines of streak CSS
CONTEXT.md Full documentation
package.json Added vitest devDependency + test scripts

API Endpoints

Method Route Auth Description
GET /api/streak Student Returns streak status
POST /api/streak/claim Student Manually claim streak SP
GET /api/admin/streaks Admin List all streak documents
POST /api/admin/streak/process-all Admin Trigger daily processing

Testing

  • 37 unit tests in server/services/__tests__/streakService.test.js
  • npx vitest run37/37 passing
  • npm run build (client Vite build) — passes

Post-Merge Deployment

1. Pull and build on production

cd /home/sakshi/spurti
git pull origin main
npm install
npm --prefix client install && npm run build

2. Restart the Express server

# Restart however the server is managed (pm2, systemd, etc.)
pm2 restart spurti

No manual DB migration needed — the streaks collection is auto-created by MongoDB/Mongoose on first write.

3. One-time backfill (historical streak data)

Preview first (dry run):

BACKFILL=1 DRY_RUN=1 node server/cron/streakDaily.js

Then run for real:

BACKFILL=1 node server/cron/streakDaily.js

This processes every active student with internshipStartDate >= 2026-07-16 from their start date through yesterday. Students who started before the cutoff date are automatically skipped.

4. Set up daily cron job

Add to crontab (crontab -e) to run after the main SP pipeline each day:

# Streak processing — runs daily at 23:59 IST (18:59 UTC) after pipeline
59 18 * * * cd /home/sakshi/spurti && node server/cron/streakDaily.js >> /home/sakshi/spurti/logs/streak.log 2>&1

5. Verify

Check Command / URL
Student dashboard Open /spurti as a student — streak card + "Streak" tab should appear
Admin: list streaks GET /api/admin/streaks — should return scored students
Admin: manual trigger POST /api/admin/streak/process-all with { "date": "2026-07-17" }
Cron logs tail -f /home/sakshi/spurti/logs/streak.log

No other action needed

  • No .env changes required — the cron script uses existing MONGO_URI
  • No schema migration — Mongoose auto-creates the streaks collection
  • Frontend is built into client/dist and served as static files

Re-opens #104 (accidentally closed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant