From 247feb00d5464c4602f5fbdf3a9693fe295f6840 Mon Sep 17 00:00:00 2001 From: Rishi Desai <71404284+rdesai14@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:28:01 -0400 Subject: [PATCH] Add AI scoring fields to Application and Review models, and update grading logic --- .../registration/src/models/application.ts | 6 ++++ services/registration/src/models/review.ts | 8 +++++ services/registration/src/routes/grading.ts | 29 +++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/services/registration/src/models/application.ts b/services/registration/src/models/application.ts index e5b5b7c4..c03e3fc8 100644 --- a/services/registration/src/models/application.ts +++ b/services/registration/src/models/application.ts @@ -88,6 +88,7 @@ export interface Application extends mongoose.Document { createdAt: Date; updatedAt: Date; finalScore: number; + finalAiScore?: number; } const applicationSchema = new Schema( @@ -309,6 +310,11 @@ const applicationSchema = new Schema( finalScore: { type: Number, }, + // Average of the graders' AI likelihood ratings across this application's reviews. + // Recorded for data collection only; nothing currently acts on it. + finalAiScore: { + type: Number, + }, }, { timestamps: true, diff --git a/services/registration/src/models/review.ts b/services/registration/src/models/review.ts index ad38e37a..e9f141b3 100644 --- a/services/registration/src/models/review.ts +++ b/services/registration/src/models/review.ts @@ -8,6 +8,7 @@ export interface Review extends mongoose.Document { essayId: Types.ObjectId; score: number; adjustedScore: number; + aiScore?: number; timestamp: Date; } @@ -40,6 +41,13 @@ const reviewSchema = new Schema({ type: Number, required: true, }, + // Grader's rating of how likely the essay was written by AI. Optional so that reviews + // submitted before this field existed (and any client not yet sending it) remain valid. + aiScore: { + type: Number, + min: 1, + max: 3, + }, timestamp: { type: Date, required: true, diff --git a/services/registration/src/routes/grading.ts b/services/registration/src/routes/grading.ts index ad0c3766..6b2c9725 100644 --- a/services/registration/src/routes/grading.ts +++ b/services/registration/src/routes/grading.ts @@ -15,6 +15,11 @@ import { getCalibrationMapping } from "../common/adjustScores"; const MAX_REVIEWS_PER_ESSAY = 2; // NOTE: No. of essays for each application. As such, will need to be updated whenever we add/remove essays. const ESSAY_COUNT = 4; +// Graders rate how likely an essay was written by AI alongside the normal score, so that they can +// grade the essay on its merits and record the suspicion separately. +// 1 = no chance of AI, 2 = some hints and sounds kinda like AI, 3 = fairly confident this is AI. +const AI_SCORE_MIN = 1; +const AI_SCORE_MAX = 3; type AggregatedEssay = { applicationId: string; @@ -224,6 +229,18 @@ gradingRouter.route("/actions/submit-review").post( throw new BadRequestError("One or more of the method body arguments is missing."); } + // Optional so clients that haven't been updated yet can still submit reviews. + const { aiScore } = req.body; + if ( + aiScore !== undefined && + aiScore !== null && + (!Number.isInteger(aiScore) || aiScore < AI_SCORE_MIN || aiScore > AI_SCORE_MAX) + ) { + throw new BadRequestError( + `AI score must be an integer between ${AI_SCORE_MIN} and ${AI_SCORE_MAX}` + ); + } + const gradingGroup = req.body.gradingGroup as GradingGroupType; let criteriaScores = grader.calibrationScores.find( @@ -294,6 +311,7 @@ gradingRouter.route("/actions/submit-review").post( score: req.body.score, timestamp: new Date(), adjustedScore, + aiScore, }); const allEssayReviews = await ReviewModel.find({ @@ -305,6 +323,17 @@ gradingRouter.route("/actions/submit-review").post( application.gradingComplete = true; const sumScores = allEssayReviews.reduce((prev, review) => prev + review.adjustedScore, 0); application.finalScore = sumScores / allEssayReviews.length; + + // Average only over the reviews that actually submitted an AI score, so reviews from + // before this field existed don't pull the average toward zero. + const aiScores = allEssayReviews + .map(review => review.aiScore) + .filter((score): score is number => score !== undefined && score !== null); + if (aiScores.length > 0) { + application.finalAiScore = + aiScores.reduce((prev, score) => prev + score, 0) / aiScores.length; + } + await application.save(); }