From 1e79ad8576439b1adede9e89adcf18fc467a3ff2 Mon Sep 17 00:00:00 2001 From: Udaykiran Velagada Date: Fri, 17 Jul 2026 19:32:16 +0530 Subject: [PATCH] fix: add 'yet to onboard' status to student schema and update pipeline reconciliation --- pipeline/sp-rubric-build-mirror.cjs | 11 +++++++++-- pipeline/sp-rubric-build.js | 9 +++++++-- server/models/Student.js | 2 +- server/scripts/addStudents.js | 6 ++++-- server/scripts/syncStudents.js | 6 ++++-- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/pipeline/sp-rubric-build-mirror.cjs b/pipeline/sp-rubric-build-mirror.cjs index 3685335..8e93bf1 100644 --- a/pipeline/sp-rubric-build-mirror.cjs +++ b/pipeline/sp-rubric-build-mirror.cjs @@ -239,7 +239,14 @@ const dayLabel = (topic) => { const m = String(topic).match(/Day\s+([IVXLC0-9]+) for (const e of await Tx.distinct('email')) if (!keep.has(e)) staleSet.add(e); const staleEmails = [...staleSet]; let zdel = 0; for (let i = 0; i < staleEmails.length; i += 500) { const r = await Tx.deleteMany({ email: { $in: staleEmails.slice(i, i + 500) } }); zdel += r.deletedCount; } - for (let i = 0; i < staleEmails.length; i += 1000) await Students.bulkWrite(staleEmails.slice(i, i + 1000).map((email) => ({ updateOne: { filter: { email }, update: { $set: { totalSp: 0 } } } })), { ordered: false }); - console.log(`RECONCILED -> ${staleEmails.length} students not in new ledger cleared (incl ${zeroOut.length} future-start; ghost txns deleted ${zdel}, totalSp=0)`); + const zeroOutSet = new Set(zeroOut); + const staleBulk = staleEmails.map((email) => { + const isExcused = excusedSet.has(email); + const isFuture = zeroOutSet.has(email); + const status = isExcused ? 'excused' : (isFuture ? 'yet to onboard' : 'active'); + return { updateOne: { filter: { email }, update: { $set: { totalSp: 0, status } } } }; + }); + for (let i = 0; i < staleBulk.length; i += 1000) await Students.bulkWrite(staleBulk.slice(i, i + 1000), { ordered: false }); + console.log(`RECONCILED -> ${staleEmails.length} students not in new ledger cleared (incl ${zeroOut.length} future-start set to 'yet to onboard'; ghost txns deleted ${zdel}, totalSp=0)`); await conn.close(); })().catch((e) => { console.error('FATAL', e.message); process.exit(1); }); diff --git a/pipeline/sp-rubric-build.js b/pipeline/sp-rubric-build.js index 43e70f5..5e7977e 100644 --- a/pipeline/sp-rubric-build.js +++ b/pipeline/sp-rubric-build.js @@ -233,8 +233,13 @@ async function participants(uuid) { // zero students who attended but are future-start or yet-to-onboard (meter not started) if (zeroOut.length) { let zdel = 0; for (let i = 0; i < zeroOut.length; i += 500) { const r = await Tx.deleteMany({ email: { $in: zeroOut.slice(i, i + 500) } }); zdel += r.deletedCount; } - for (let i = 0; i < zeroOut.length; i += 1000) await Students.bulkWrite(zeroOut.slice(i, i + 1000).map((email) => ({ updateOne: { filter: { email }, update: { $set: { totalSp: 0 } } } })), { ordered: false }); - console.log(`ZEROED -> ${zeroOut.length} future-start/yet-to-onboard students (txns deleted ${zdel}, totalSp=0)`); + const zBulk = zeroOut.map((email) => { + const isExcused = excusedSet.has(email); + const status = isExcused ? 'excused' : 'yet to onboard'; + return { updateOne: { filter: { email }, update: { $set: { totalSp: 0, status } } } }; + }); + for (let i = 0; i < zBulk.length; i += 1000) await Students.bulkWrite(zBulk.slice(i, i + 1000), { ordered: false }); + console.log(`ZEROED -> ${zeroOut.length} future-start/yet-to-onboard students set to 'yet to onboard' (txns deleted ${zdel}, totalSp=0)`); } await wconn.close(); })().catch((e) => { console.error('FATAL', e.message); process.exit(1); }); diff --git a/server/models/Student.js b/server/models/Student.js index 1b3e7f2..aca905c 100644 --- a/server/models/Student.js +++ b/server/models/Student.js @@ -6,7 +6,7 @@ const studentSchema = new mongoose.Schema({ alternateEmail: { type: String, lowercase: true, trim: true, default: '', index: true }, internshipStartDate: { type: Date, required: true, index: true }, internshipEndDate: { type: Date, default: null }, - status: { type: String, enum: ['active', 'excused'], default: 'active', index: true }, + status: { type: String, enum: ['active', 'excused', 'yet to onboard'], default: 'active', index: true }, excusedAt: { type: Date, default: null }, excusedReason: { type: String, default: '' }, totalSp: { type: Number, default: 100, index: true }, diff --git a/server/scripts/addStudents.js b/server/scripts/addStudents.js index d4f5a50..a55e43c 100644 --- a/server/scripts/addStudents.js +++ b/server/scripts/addStudents.js @@ -135,7 +135,8 @@ async function run() { existing.alternateEmail = row.alternateEmail || existing.alternateEmail; existing.internshipStartDate = row.internshipStartDate || existing.internshipStartDate; existing.internshipEndDate = row.internshipEndDate || existing.internshipEndDate; - existing.status = 'active'; + const existingStart = existing.internshipStartDate ? new Date(existing.internshipStartDate) : null; + existing.status = (existingStart && existingStart > new Date()) ? 'yet to onboard' : 'active'; existing.excusedAt = null; existing.excusedReason = ''; await existing.save(); @@ -143,13 +144,14 @@ async function run() { continue; } + const start = row.internshipStartDate ? new Date(row.internshipStartDate) : null; const student = await Student.create({ name: row.name || row.email, email: row.email, alternateEmail: row.alternateEmail, internshipStartDate: row.internshipStartDate, internshipEndDate: row.internshipEndDate, - status: 'active', + status: (start && start > new Date()) ? 'yet to onboard' : 'active', excusedAt: null, excusedReason: '', totalSp: 100 diff --git a/server/scripts/syncStudents.js b/server/scripts/syncStudents.js index ac1b1e2..91f7318 100644 --- a/server/scripts/syncStudents.js +++ b/server/scripts/syncStudents.js @@ -72,7 +72,8 @@ async function run() { existing.internshipStartDate = row.internshipStartDate || existing.internshipStartDate; existing.internshipEndDate = row.internshipEndDate || existing.internshipEndDate; if (existing.status === 'excused') stats.reactivated++; - existing.status = 'active'; + const existingStart = existing.internshipStartDate ? new Date(existing.internshipStartDate) : null; + existing.status = (existingStart && existingStart > new Date()) ? 'yet to onboard' : 'active'; existing.excusedAt = null; existing.excusedReason = ''; await existing.save(); @@ -81,13 +82,14 @@ async function run() { continue; } + const start = row.internshipStartDate ? new Date(row.internshipStartDate) : null; const student = await Student.create({ name: row.name || row.email, email: normalizeEmail(row.email), alternateEmail: normalizeEmail(row.alternateEmail), internshipStartDate: row.internshipStartDate, internshipEndDate: row.internshipEndDate, - status: 'active', + status: (start && start > new Date()) ? 'yet to onboard' : 'active', excusedAt: null, excusedReason: '', totalSp: 100