@@ -15,6 +15,11 @@ import { getCalibrationMapping } from "../common/adjustScores";
1515const MAX_REVIEWS_PER_ESSAY = 2 ;
1616// NOTE: No. of essays for each application. As such, will need to be updated whenever we add/remove essays.
1717const ESSAY_COUNT = 4 ;
18+ // Graders rate how likely an essay was written by AI alongside the normal score, so that they can
19+ // grade the essay on its merits and record the suspicion separately.
20+ // 1 = no chance of AI, 2 = some hints and sounds kinda like AI, 3 = fairly confident this is AI.
21+ const AI_SCORE_MIN = 1 ;
22+ const AI_SCORE_MAX = 3 ;
1823
1924type AggregatedEssay = {
2025 applicationId : string ;
@@ -224,6 +229,18 @@ gradingRouter.route("/actions/submit-review").post(
224229 throw new BadRequestError ( "One or more of the method body arguments is missing." ) ;
225230 }
226231
232+ // Optional so clients that haven't been updated yet can still submit reviews.
233+ const { aiScore } = req . body ;
234+ if (
235+ aiScore !== undefined &&
236+ aiScore !== null &&
237+ ( ! Number . isInteger ( aiScore ) || aiScore < AI_SCORE_MIN || aiScore > AI_SCORE_MAX )
238+ ) {
239+ throw new BadRequestError (
240+ `AI score must be an integer between ${ AI_SCORE_MIN } and ${ AI_SCORE_MAX } `
241+ ) ;
242+ }
243+
227244 const gradingGroup = req . body . gradingGroup as GradingGroupType ;
228245
229246 let criteriaScores = grader . calibrationScores . find (
@@ -294,6 +311,7 @@ gradingRouter.route("/actions/submit-review").post(
294311 score : req . body . score ,
295312 timestamp : new Date ( ) ,
296313 adjustedScore,
314+ aiScore,
297315 } ) ;
298316
299317 const allEssayReviews = await ReviewModel . find ( {
@@ -305,6 +323,17 @@ gradingRouter.route("/actions/submit-review").post(
305323 application . gradingComplete = true ;
306324 const sumScores = allEssayReviews . reduce ( ( prev , review ) => prev + review . adjustedScore , 0 ) ;
307325 application . finalScore = sumScores / allEssayReviews . length ;
326+
327+ // Average only over the reviews that actually submitted an AI score, so reviews from
328+ // before this field existed don't pull the average toward zero.
329+ const aiScores = allEssayReviews
330+ . map ( review => review . aiScore )
331+ . filter ( ( score ) : score is number => score !== undefined && score !== null ) ;
332+ if ( aiScores . length > 0 ) {
333+ application . finalAiScore =
334+ aiScores . reduce ( ( prev , score ) => prev + score , 0 ) / aiScores . length ;
335+ }
336+
308337 await application . save ( ) ;
309338 }
310339
0 commit comments