Project

General

Profile

Actions

Task #193

open

Harshit's task list

Added by Ayush Yadav on 09/10/2026 11:16 AM. Updated on 09/23/2026 12:31 PM.

Status:
New
Priority:
Normal
Assignee:
Start date:
09/10/2026
Due date:
% Done:

15%

Estimated time:

Description

Harshit's task list — Gantt W11 to W14 (21 Sep to 16 Oct 2026)

Read this first: your lane has changed

Until now you worked on the React frontend. From 21 September 2026 you own the Spring Boot backend and the admin panel. Nikita has taken over the student-facing React work on issue #194.

Everything you built in W9 and W10 stays — the Plans page, the FREE / PREMIUM badges, the payment page. Do not delete any of it. Nikita will build on top of it.

Tickets you will comment on: #95 (M4), #96 (M5), #97 (M6), #98 (M7), #99 (M8), #100 (M9), #101 (M10), #102 (M11), #103 (M12), #93 (M2), #94 (M3), #112 (QB-1). Do not create new tickets.

How to read every task below

Each task always has the same four parts:

  • Goal — one sentence saying what the finished thing does.
  • Why — the reason it matters, so you understand the task instead of just copying it.
  • Steps — what to actually change, with the real file paths from our repository.
  • Done when — how you prove to yourself it works before you say it is finished.

The calendar (no Saturdays, no Sundays)

Week Working days
W11 Mon 21, Tue 22, Wed 23, Thu 24, Fri 25 September
W12 Mon 28, Tue 29, Wed 30 September, Thu 1, Fri 2 October — Phase 1 MVP must be finished by this Friday
W13 Mon 5, Tue 6, Wed 7, Thu 8, Fri 9 October — Phase 2 opens, JEE Main bank track opens
W14 Mon 12, Tue 13, Wed 14, Thu 15, Fri 16 October

That is 20 working days and 20 tasks.

Ground rules for all your work

  • Backend runs on http://localhost:8081/api. Frontend runs on http://localhost:5173. Never use port 8080.
  • In a controller write @RequestMapping("/tests"), not @RequestMapping("/api/tests"). The /api part is added automatically by the context path.
  • Constructor injection everywhere. Validate request bodies with @Valid DTOs. Return errors as {"error": "..."} through GlobalExceptionHandler.
  • Never commit a .env file, a password, an API key, or a webhook URL.
  • There are only two question stores and they must never be mixed: the shared catalog at /admin/questions, and the six per-exam banks at /exam-questions.

Already built — do not rebuild these

A full code audit on 21 September found that several things our old notes call "pending" are actually finished. Skip them:

  • Bonus marks, negative marks, partial marking for multiple-correct, and numerical tolerance are all live in AssessmentService.gradeObjective.
  • The AttemptAuditLog entity and GET /attempts/{id}/audit both exist.
  • Admin already has Faculty vs Candidate tabs and a subscription-plan dropdown.
  • PlanRules.isPremium exists on the backend.
  • Bulk candidate upload by CSV and Excel works.

The biggest problem we are fixing, and why W11 looks like it does

Our backend has a complete, working exam engine. It starts attempts, saves answers, submits, scores with negative and bonus marks, and calculates rank and percentile. But no student screen calls it. The only code that touches /attempts is an admin testing harness in TestAuthoringPanel.tsx. Nikita connects the student screens this week. Your job in W11 is to make that engine trustworthy: enforce the clock, enforce the navigation rules, and stop giving away premium accounts for free.

Week 11 — Mon 21 to Fri 25 September

Theme: make the exam engine trustworthy, and close the two Gantt modules that end this week — M5 auto evaluation and M7 institution management.

H1 — Monday 21 September: Make Razorpay payment verification actually verify something (M9, #100)

Goal. When someone finishes a payment, we confirm the payment really came from Razorpay before we give them a premium plan.

Why. Right now PaymentService.verify marks the order as PAID and upgrades the user to MONTHLY without checking anything at all. Anyone who knows the URL can call that endpoint and get a free premium account. This is a security hole, so it goes first.

Steps.

  1. Open backend/src/main/java/com/teamtalks/backend/services/PaymentService.java.
  2. Razorpay sends back three values: razorpay_order_id, razorpay_payment_id and razorpay_signature. Join the first two with a | between them, for example order_abc|pay_xyz.
  3. Run HMAC-SHA256 over that joined string using your Razorpay key secret as the key. Java has this built in: Mac.getInstance("HmacSHA256"). Convert the result to lowercase hexadecimal.
  4. Compare your hex string to the signature Razorpay sent. Compare the bytes using MessageDigest.isEqual, not String.equals — that avoids a timing attack.
  5. If they do not match, throw an error so the client gets {"error": "Payment verification failed"}. Do not mark the order paid.
  6. Stop hardcoding MONTHLY. Store which plan the user was buying on the PaymentOrder row when the order is created, and set that plan on success.
  7. Keep the stub behaviour for local development: if RAZORPAY_KEY_ID is not set, skip the signature check but return "stub": true so it is obvious we are not in real mode.

Done when. Calling POST /api/payments/verify with a made-up signature returns an error and the user's plan does not change. With the correct signature the order becomes PAID and the plan becomes whatever was ordered. Never print the key secret in a log line.

H2 — Tuesday 22 September: Stop students submitting after time is up (M4, #95)

Goal. The server refuses to save or submit an attempt once the allowed time has passed, and closes off abandoned attempts by itself.

Why. Today the countdown clock only exists in the browser. A student can pause the JavaScript, reopen the page hours later, or call the API directly, and we will happily accept the answers. The clock must be enforced on the server, because the server is the only thing a student cannot edit.

Steps.

  1. Open backend/src/main/java/com/teamtalks/backend/services/AssessmentService.java.
  2. Write one small private helper, for example assertNotExpired(AssessmentAttempt attempt). It takes startedAt, adds the test's durationMinutes, and compares that deadline to the current time.
  3. Call it at the top of save and at the top of submit.
  4. When the deadline has passed do not simply reject the call. Mark the attempt submitted, run the normal evaluate scoring on whatever answers were already saved, write an audit row with action AUTO_SUBMIT, and then return a clear error saying the time expired.
  5. Allow about 30 seconds of grace, so a student whose answer is in flight when the clock hits zero does not lose it.
  6. Also check the test's own startAt and endAt window. An attempt cannot begin before the test opens or after it closes. There is already an assertWindow method — reuse it, do not write a second one.

Done when. Create a test with a 1 minute duration, start it, wait two minutes, then try to save. You get an error, the attempt shows SUBMITTED with a score, and GET /api/attempts/{id}/audit shows the AUTO_SUBMIT row.

H3 — Wednesday 23 September: Enforce navigation modes and record tab switches (M4, #95)

Goal. The three navigation modes we already store actually restrict what a student can do, and switching browser tabs during an exam leaves a permanent record.

Why. AssessmentTest has a navMode column holding FREE, SEQUENTIAL or SECTION_LOCK. We send it to the browser and then ignore it completely. A real exam engine has to stop a student jumping back into a section they already closed. Separately, the audit code has a comment saying it supports a TAB_SWITCH action, but nothing in the codebase ever writes one, so that feature does not really exist yet.

Steps.

  1. Still in AssessmentService.java, inside save, look at the test's navMode before accepting an answer.
  2. SEQUENTIAL: only accept an answer for the current question or one already reached. Track the furthest question index reached on the attempt row so you have something to compare against.
  3. SECTION_LOCK: once a student moves from section 1 to section 2, refuse any further answers for section 1. Add a small completedSectionIds field on AssessmentAttempt and update it when the section changes.
  4. FREE: accept everything, which is what happens today.
  5. When a rule is broken return a clear message such as {"error": "This section is already closed"}. Do not silently ignore the answer.
  6. The save request body already carries tabSwitchCount. When that number is higher than what we stored, call auditService.log(attemptId, userId, "TAB_SWITCH", ...) once per new switch, then update the stored count.

Done when. On a SECTION_LOCK test, moving to section 2 and then answering a section 1 question returns an error. Switching tabs three times produces three TAB_SWITCH rows in the audit endpoint.

H4 — Thursday 24 September: Range-based numerical answers (M5, #96) — this closes M5

Goal. A numerical question can accept any answer inside a range, not only one exact number.

Why. JEE and NEET regularly publish answers as a band, for example "any value between 9.78 and 9.82". Today TestQuestion only has a single tolerance field, which means plus-or-minus the same amount on both sides of one number. That cannot express a band that is not symmetric. M5 ends in W11, so this is the last chance.

Steps.

  1. Open backend/src/main/java/com/teamtalks/backend/entity/TestQuestion.java and add two nullable Double columns: answerMin and answerMax.
  2. Add the same two fields to the request DTO used when creating or updating a test, with validation: if one is given the other must be too, and answerMin must not be greater than answerMax.
  3. In gradeObjective, in the NUMERICAL branch: when both are set, mark the answer correct if the student's number falls inside the band, including both ends. Otherwise fall back to the existing exact-value-plus-tolerance logic. Do not delete the old logic — existing questions depend on it.
  4. Parse the student's typed answer safely. If they typed letters, treat it as wrong rather than letting a NumberFormatException crash the request.

Done when. A question with answerMin 9.78 and answerMax 9.82 marks 9.8 correct and 9.9 wrong. An old question that only has tolerance still scores exactly as it did before.

H5 — Friday 25 September: Institution, Department, Class and Batch (M7, #98) — this closes M7

Goal. A real Institution record in the database, with departments, classes and batches underneath it, so a coaching centre can organise its students.

Why. Today "institution" is just a text field copied onto every user, plus an institutionCode string on tests. There is no Institution table. That means we cannot list a school's students reliably, cannot group them into batches, and cannot produce per-batch reports. The Gantt asks for department, class and batch management and this is the last week for M7.

Steps.

  1. Create entity/Institution.java with id, code (unique), name, address, contactEmail, active, createdAt. The code must match the institutionCode strings already stored on users and tests, so existing data keeps working.
  2. Create entity/Batch.java with id, a @ManyToOne link to Institution, name, department, classLevel, academicYear, active.
  3. Add a nullable @ManyToOne batch field on Users. Leave the existing text fields alone so nothing breaks.
  4. Create repositories for both, in the same style as the other repositories under repository/.
  5. Create InstitutionService and add the endpoints to the existing AdminController, not a new controller: GET /admin/institutions, POST /admin/institutions, GET /admin/institutions/{id}/batches, POST /admin/institutions/{id}/batches, PUT /admin/users/{userId}/batch.
  6. Write a small one-time migration that reads every distinct non-empty institution name already on Users and creates an Institution row for it, so we do not lose the data we already have.
  7. Restrict these endpoints to Super Admin and Institution Admin, and make sure an Institution Admin can only ever see their own institution.

Done when. You can create an institution, create two batches under it, move a student into a batch, and list only that batch's students. Logged in as Institution Admin for institution A, you cannot see institution B.

Week 12 — Mon 28 September to Fri 2 October

Theme: finish Phase 1. Everything on the Gantt for M6, M8, M9 and M10 ends this Friday.

H6 — Monday 28 September: Let students upload answer files (M6, #97)

Goal. A student answering a descriptive question can attach a PDF, a Word file, or a photo of their handwritten answer.

Why. Descriptive questions today only accept typed text in AttemptAnswer.responseText. For long answers, essays and case studies, students need to attach work. This is the first of three descriptive tasks and the other two depend on it.

Steps.

  1. Create entity/AnswerAttachment.java: id, link to AttemptAnswer, fileName, contentType, sizeBytes, storagePath, uploadedAt.
  2. Add POST /attempts/{attemptId}/answers/{testQuestionId}/attachment on AttemptController, taking a MultipartFile.
  3. Validate hard before saving anything. Allow only PDF, DOCX, PNG and JPEG. Cap the size at 10 MB. Check the real content type, not just the file extension — an extension is trivially faked.
  4. Never store the file under the name the student gave it. Generate a UUID filename. A name like ../../application.properties would otherwise let someone write outside our folder.
  5. Save files under a configurable folder. Add app.upload.dir to application.properties with a sensible default, and create the folder at startup if it is missing.
  6. Add a download endpoint. Only the student who owns the attempt, the test's author, and admins may download.
  7. Reject uploads once the attempt is submitted.

Done when. Upload a 2 MB PDF against a descriptive question and download it back unchanged. A 20 MB file is rejected. A .exe renamed to .pdf is rejected. A different student requesting the file gets an error.

H7 — Tuesday 29 September: Rubric-based marking (M6, #97)

Goal. An examiner marks a long answer against a list of named criteria instead of guessing one overall number.

Why. "Give this essay a mark out of 20" produces wildly different marks from different examiners. A rubric — structure 5, content 8, grammar 4, examples 3 — makes marking consistent and lets us explain the mark to the student.

Steps.

  1. Create entity/Rubric.java (id, name, description, createdBy) and entity/RubricCriterion.java (id, link to rubric, name, description, maxMarks, displayOrder).
  2. Add a nullable link from TestQuestion to a Rubric, so a descriptive question can carry one.
  3. Create entity/RubricScore.java: link to AttemptAnswer, link to RubricCriterion, marksAwarded, comment, evaluatorId.
  4. Add admin endpoints GET /admin/rubrics, POST /admin/rubrics, PUT /admin/rubrics/{id}.
  5. Extend the existing PUT /attempts/{id}/manual-score so it can accept a list of per-criterion marks. Add the criteria up to get the total and write that into the answer's marksAwarded.
  6. Validate that no criterion mark is negative or above its maxMarks, and that the criteria actually belong to the rubric attached to that question.
  7. After saving, call evaluate again so the attempt total, rank and percentile all refresh.

Done when. Create a rubric with four criteria totalling 20, attach it to a descriptive question, mark a student's answer criterion by criterion, and confirm the attempt total went up by exactly the sum you entered.

H8 — Wednesday 30 September: Two independent examiners and a moderator (M6, #97) — this closes M6

Goal. Two examiners mark the same answer without seeing each other's marks, and if they disagree too much a senior reviewer decides the final mark.

Why. This is standard practice for high-stakes descriptive papers and it is the last piece of M6. Double marking catches examiner bias; moderation resolves genuine disagreement.

Steps.

  1. Change RubricScore so several evaluators can score the same answer. The pair of answer plus evaluator must be unique — one examiner, one set of marks.
  2. Add to AttemptAnswer: evaluationStatus (PENDING, ONE_DONE, TWO_DONE, MODERATION_NEEDED, FINAL), finalMarks, moderatedBy.
  3. When the second examiner submits, compare the two totals. If they differ by more than a configurable threshold — start with 15 percent of the question's maximum — set MODERATION_NEEDED. If they agree, set finalMarks to the average and the status to FINAL.
  4. An examiner must never see the other examiner's marks while still marking. Filter them out of the API response by evaluator id.
  5. Add GET /admin/evaluations/moderation-queue and PUT /admin/evaluations/{answerId}/moderate, where a Super Admin or Reviewer sets the final mark and writes a short reason.
  6. Only once the status is FINAL should the mark count towards the attempt total.

Done when. Two examiners mark 12 and 13 out of 20 — the answer goes FINAL at 12.5 automatically. Two examiners mark 8 and 18 — it lands in the moderation queue and the attempt total does not move until a reviewer settles it.

H9 — Thursday 1 October: Fix invite-only notifications and add exam reminders (M10, #101) — this closes M10 for Phase 1

Goal. Students invited to a private test actually receive the email, and everyone gets a reminder before the exam starts.

Why. There is a real bug here: findAssignedCandidates returns an empty list for tests whose availability is INVITE, so invite-only tests email precisely nobody. Separately the Gantt asks for reminders and we only send mail at publish time and result time.

Steps.

  1. Create entity/TestInvitation.java: link to AssessmentTest, link to Users, invitedAt, notifiedAt. Add POST /tests/{id}/invitations and GET /tests/{id}/invitations.
  2. Fix findAssignedCandidates in AssessmentService: for INVITE tests return the invited users instead of an empty list. Leave PUBLIC and PRIVATE behaving as they do now.
  3. Create ReminderScheduler using Spring's @Scheduled, running every 15 minutes. Add @EnableScheduling to the application class.
  4. On each run, find published tests starting within the next 24 hours, and again within the next hour, and email each assigned candidate who has not had that particular reminder. Record what was sent on the invitation row so nobody gets the same reminder twice.
  5. Reuse AssessmentMailService.send. Do not build a second mail path. The console fallback when SMTP is missing must keep working so local development does not break.
  6. Keep the subject and body text in one place so they are easy to change later.
  7. Do not build SMS, WhatsApp or push notifications. Those stay deferred.

Done when. Create an invite-only test, invite two students, publish, and both receive the email. Set a start time 50 minutes away and confirm the one-hour reminder goes out on the next run and does not repeat.

H10 — Friday 2 October: Institution and batch reports, then Phase 1 sign-off (M8, #99) — this closes M8 and Phase 1

Goal. An institution admin can see how a whole batch performed on a test and compare batches against each other.

Why. Everything we report today is about one student's single attempt. A coaching centre needs to know which batch is behind and which topic the whole class is failing. This is the last Phase 1 row on the Gantt.

Steps.

  1. Add GET /reports/tests/{testId}/summary: number of candidates who attempted, average score, highest and lowest, average time, and topic-wise accuracy across everyone.
  2. Add GET /reports/batches/{batchId}/summary using the Batch entity from H5, plus a ranked list of students.
  3. Add GET /reports/institutions/{institutionId}/comparison returning one row per batch so batches sit side by side.
  4. Do the arithmetic in the database with JPQL aggregate queries. Do not load every attempt into Java memory and loop — that will not survive real data volumes.
  5. Enforce access strictly. Institution Admin reads only their own institution. Super Admin reads anything. A Candidate reads none of these.
  6. Spend the rest of the day on Phase 1 sign-off. Run every endpoint you built in W11 and W12 once, then write a short comment on #95, #96, #97, #98, #99, #100 and #101 saying exactly what shipped and what is still deferred.

Done when. Two batches take the same test and the comparison endpoint shows two rows with averages you can verify by hand from the raw attempt rows.

Week 13 — Mon 5 to Fri 9 October

Phase 2 opens. Two Gantt tracks start: M11 Language Assessment, and QB-1 the JEE Main question bank.

Read this before you start. The audit found that CAT has about 10,092 questions across 36 chapter files in backend/data/cat_questions_json/, but JEE Main has only 9 sample questions in backend/data/jee_questions_json/. The Gantt wants 24,000 unit-wise JEE Main questions. Our only import script, backend/scripts/convert_and_import_cat_questions.py, is hardcoded to CAT. So this week is about building the machinery, not typing questions. Writing the actual content stays with Ayush Sharma on the Gantt.

H11 — Monday 5 October: One import script that works for every exam (QB-1, #112)

Goal. A single script that imports questions into any of our six exam banks, from CSV or JSON.

Why. The CAT script has the CAT folder path, CAT chapter names and CAT subject mapping baked in. Copying it five more times would leave six scripts to maintain and six places for the same bug. We need one script driven by a config file.

Steps.

  1. Create backend/scripts/import_questions.py, starting from the existing CAT script so the working parts are preserved.
  2. Take arguments: --exam (JEE_MAINS, JEE_ADVANCED, NEET, CAT, JIPMAT, CUET), --input (file or folder), --mapping (config file), --dry-run, --import.
  3. Move the filename-to-chapter and subject mapping into a JSON config per exam under backend/scripts/mappings/. Adding a new exam then means adding a config file, not editing code.
  4. Support both formats. CSV gets a column mapping in the config. JSON is expected in the {exam, questions} shape the import endpoint already accepts.
  5. Validate every row before sending anything: content not empty, at least two options, correct answer must be one of the options, marks and negative marks must be numbers. Collect all the problems and print a report. Do not stop at the first bad row.
  6. --dry-run must be the default. Nobody should write 4,000 rows into the database by accident.
  7. Send in batches of 500 to POST /api/exam-questions/import, with a progress line per batch. One request with 24,000 questions will time out.
  8. Skip duplicates — the repositories already have existsByContentIgnoreCase. Report how many were skipped.
  9. Write a summary file next to the input, the way the CAT script writes _import_summary.json.

Done when. Running the script against backend/data/jee_questions_json/ in dry-run reports 9 valid questions and no errors. Running with --import loads them, and running it again skips all 9 as duplicates.

H12 — Tuesday 6 October: Maths formulas and images on questions (M2, #93)

Goal. A question can contain a properly typeset mathematical formula and a diagram.

Why. We cannot build a serious JEE or NEET bank without this. Physics and Maths questions are full of integrals, fractions and vectors, and many refer to a circuit diagram or a graph. Today content is plain text and there is no image field anywhere, so those questions simply cannot be stored.

Steps.

  1. Add to ExamQuestionBase: contentFormat (PLAIN or LATEX, default PLAIN), imageUrl, solutionImageUrl.
  2. Add a latex flag and an image URL to each option too, since options often contain formulas. Options are stored as a JSON list, so extend that structure rather than adding columns.
  3. Add the same fields to ExamQuestionItemRequest so the importer can populate them.
  4. Add POST /admin/questions/image, reusing the safe file handling from H6: whitelist content types, UUID filename, size cap.
  5. Store LaTeX exactly as given. Do not parse or clean it on the server — the browser library renders it, and touching it here will only corrupt formulas.
  6. Update ExamQuestionResponse so the new fields reach the frontend.
  7. Because ddl-auto=update adds columns automatically, confirm existing rows still work with contentFormat defaulting to PLAIN.

Done when. Save a question with contentFormat LATEX and content like \int_0^1 x^2 dx, attach a diagram, read it back through GET /api/exam-questions, and everything is intact.

H13 — Wednesday 7 October: Generate many practice tests from a question pool (M3 and QB-1, #94 and #112)

Goal. One API call produces many different practice papers from an exam bank, following a blueprint.

Why. The Gantt asks for 100 practice tests and 100 mock tests per exam. Nobody will assemble 200 papers by hand in the admin panel. What we have today is ExamQuestionService.sample, which shuffles and takes N — that ignores subject balance and difficulty, so one paper might be all easy Chemistry. A real paper needs a blueprint.

Steps.

  1. Create services/TestGenerationService.java.
  2. Add POST /tests/generate taking: the exam bank, how many tests, a name pattern like JEE Main Practice Test {n}, the duration, and a blueprint.
  3. The blueprint is a list of rules. Each rule says subject, optional chapter, difficulty, and how many questions. For example 25 Physics medium, 25 Chemistry easy, 25 Maths hard.
  4. Before generating anything, check there are enough questions for every rule. If Physics-hard has 20 and the blueprint wants 25, fail immediately naming the shortfall. Do not generate half a paper.
  5. Make the papers genuinely different. Track which questions each paper used and cap how much any two papers may overlap — start at 20 percent.
  6. Create real AssessmentTest, TestSection and TestQuestion rows, so generated papers behave exactly like hand-made ones everywhere else.
  7. Accept an optional random seed so a run can be reproduced when debugging.
  8. Run the whole thing in one transaction and return a summary: how many papers, their ids, and any warnings.
  9. Restrict to Super Admin and Test Admin. It creates a lot of data.

Done when. Generating 10 practice tests from the CAT bank with a VARC / DILR / QA blueprint produces 10 tests, each matching the blueprint exactly, with no two sharing more than a fifth of their questions.

H14 — Thursday 8 October: Audio for listening questions (M11, #102)

Goal. A question can have an audio clip attached, and the server controls how many times a student may play it.

Why. This is the first half of M11 Language Assessment, which starts this week. The play-count rule matters: in real language exams the audio plays a fixed number of times, and if we enforce that only in the browser a student can just reload the page.

Steps.

  1. Add to ExamQuestionBase: audioUrl, audioDurationSeconds, maxPlayCount (null meaning unlimited).
  2. Add POST /admin/questions/audio accepting MP3 and M4A up to 20 MB, with the same safe-filename and content-type checks as H6.
  3. Serve audio through GET /content/audio/{id}. Support HTTP range requests so the browser can stream and seek, otherwise large files will not play reliably.
  4. Track plays server-side. Add a PlaybackLog recording attempt, question and time of each play. Add POST /attempts/{id}/questions/{qid}/play which the player calls before each play; it returns how many plays remain, or an error at the limit.
  5. Return the remaining play count in the attempt response so the player can show it from the start.
  6. Only allow audio access to a student with an active attempt containing that question. Do not leave exam audio publicly downloadable.

Done when. Upload a clip, set maxPlayCount to 2, and the third play request is rejected by the server. Refreshing the page does not reset the counter.

H15 — Friday 9 October: Recording answers for speaking tests (M11, #102)

Goal. A student can record a spoken answer and have it stored for an examiner to mark.

Why. This pairs with the listening work and completes the listening-and-speaking pair the Gantt schedules for W13 to W14. Nikita builds the microphone recorder the same week, so agree the audio format and field names with her first thing in the morning.

Steps.

  1. Add a SPEAKING value to the question type list on ExamQuestionBase, plus maxRecordingSeconds and preparationSeconds.
  2. Extend AnswerAttachment from H6 to accept audio/webm and audio/mp4, which is what browser recording produces. Cap at 25 MB.
  3. Add POST /attempts/{attemptId}/answers/{testQuestionId}/recording to receive the audio plus its length in seconds.
  4. Reject recordings longer than maxRecordingSeconds plus a small tolerance.
  5. Only one recording per question. A second upload replaces the first and deletes the old file, so nobody is confused about which one counts.
  6. Add GET /admin/evaluations/{answerId}/recording so an examiner can listen while marking. Reuse the rubric scoring from H7 — speaking is marked against criteria like fluency and pronunciation, exactly like written work.
  7. Do not attempt automatic speech scoring. That is M17 in Phase 3 and is out of scope.

Done when. Nikita's recorder uploads a 30-second clip, it appears in the examiner panel, plays back cleanly, and an examiner can score it against a rubric.

Week 14 — Mon 12 to Fri 16 October

Theme: M12 Adaptive Testing starts on the Gantt, M11 continues, and the first real JEE Main practice papers get generated. Friday is a quality day, because the whole backend currently has only two test files.

H16 — Monday 12 October: Groundwork for adaptive tests (M12, #103)

Goal. The system understands a new kind of test where the next question depends on how the student is doing.

Why. M12 runs W14 to W18 and this is its first week. An adaptive test has no fixed question list, which breaks several of our assumptions — so lay the foundations carefully before writing the algorithm tomorrow.

Steps.

  1. Add ADAPTIVE to the testType values on AssessmentTest, alongside PRACTICE, MOCK, EXAM and ASSIGNMENT.
  2. Add settings on the test: adaptiveQuestionBank, adaptiveTotalQuestions, adaptiveStartDifficulty, adaptiveStopOnConfidence.
  3. Add to AssessmentAttempt: abilityEstimate (decimal, starts at 0), abilityStandardError, currentDifficulty.
  4. Make sure every difficulty value in the six exam banks maps to a number. Check what strings are actually in the data first with a query, then map them, for example easy to -1, medium to 0, hard to 1. Do not assume the data is clean.
  5. For an adaptive attempt, do not create all the TestQuestion rows at the start. Create them one at a time as questions are served, because we do not know in advance which questions the student will see.
  6. Confirm scoring, the audit log and the reports all still work when the question list grows during the attempt. This is the part most likely to break.

Done when. You can create an adaptive test and start an attempt. Only one question exists at the start and the ability estimate is 0. Nothing about normal fixed tests changes.

H17 — Tuesday 13 October: Choosing the next question (M12, #103)

Goal. After each answer the system picks a harder or easier question based on how the student is performing.

Why. This is the core of adaptive testing. The principle is simple: a right answer means the student is probably stronger than we thought, so ask something harder; a wrong answer means ask something easier. Done well this finds a student's true level in far fewer questions than a fixed paper.

Steps.

  1. Create services/AdaptiveTestService.java.
  2. Add GET /attempts/{id}/next-question. It scores the previous answer, updates the ability estimate, picks the next question, creates its TestQuestion row, and returns it.
  3. Start simple and correct. Do not attempt Item Response Theory — that is M14 in a later phase. Use a step rule: correct moves the target difficulty up one step, wrong moves it down one step, and the steps get smaller as the estimate settles.
  4. Pick the question whose difficulty is closest to the target, from the configured bank, excluding anything already seen in this attempt. If several tie, choose randomly so two students do not get identical papers.
  5. If the bank runs out at the target difficulty, widen the search rather than failing. If it runs out entirely, end the test early and record why.
  6. Stop when either the question count is reached, or the standard error drops below the confidence threshold.
  7. Never send the correct answer or the solution in the next-question response. This endpoint is called from the browser and a student can read the response.
  8. Write an audit row for every question served, with the difficulty and the ability estimate at that moment, so we can debug why a student got the paper they got.

Done when. Answer everything correctly and the difficulty climbs steadily. Answer everything wrongly and it falls. Answer randomly and it settles near the middle. No question repeats within one attempt.

H18 — Wednesday 14 October: Turning the ability estimate into a score (M12, #103)

Goal. An adaptive attempt produces a meaningful, comparable score.

Why. Adaptive scores cannot be "marks out of total". Two students answering 20 questions each may have faced completely different papers, so 15 out of 20 on a hard path is a much better performance than 18 out of 20 on an easy one. The score has to reflect the difficulty of the questions faced.

Steps.

  1. In AssessmentService.evaluate, branch for ADAPTIVE tests.
  2. Convert the final ability estimate into a scaled score on a fixed range, for example 0 to 100, so all adaptive attempts are comparable regardless of path.
  3. Weight each question by its difficulty when computing its contribution, so a correct hard answer is worth more than a correct easy one.
  4. Store both numbers: the scaled score in score, and the raw correct count separately, so reports can show both.
  5. Make recomputeRanks work for adaptive attempts by ranking on the scaled score.
  6. Add the ability estimate and its standard error to the report response, so the result page can show a confidence band rather than a single misleadingly precise number.
  7. Write the formula and the reasoning as a comment in the service. The next person to read this needs to understand why the arithmetic is what it is.

Done when. Two students each answer 15 of 20 correctly, one on a hard path and one on an easy path, and the hard-path student scores measurably higher. Ranks compute without error.

H19 — Thursday 15 October: Generate the first real JEE Main practice papers (QB-1, #112)

Goal. Use the importer from H11 and the generator from H13 together and produce genuine practice papers.

Why. This is the first end-to-end proof that our question-bank pipeline works. It will also expose whatever is wrong with both tools while they are still fresh in your mind, which is far cheaper than finding out during a 24,000-question import.

Steps.

  1. Talk to Ayush Sharma first and get whatever JEE Main Physics Class XI content exists. Do not invent questions yourself.
  2. Write the JEE Main mapping config under backend/scripts/mappings/, following the CAT config as a model. Map unit names to chapters and to Physics, Chemistry and Mathematics.
  3. Run the importer in dry-run first and fix every validation problem it reports. Expect problems — real content is always messier than sample data.
  4. Import for real, then verify by querying jee_mains_questions directly: count rows, check the subject and chapter spread, and read ten questions end to end to confirm nothing was mangled.
  5. Build a practice blueprint matching the real JEE Main pattern: 25 questions per subject with a realistic easy / medium / hard mix. Look the pattern up rather than guessing.
  6. Generate 10 practice tests, not 100. Start small so mistakes are cheap.
  7. Take one of the generated papers yourself, all the way to the result page. This is the real test of whether the chain works.
  8. Write what you found, with counts and any problems, as a comment on #112. We need this before scaling to 24,000 questions.

Done when. 10 JEE Main practice tests exist, each matching the blueprint, and you have personally completed one and seen a correct result page.

H20 — Friday 16 October: Automated tests for the exam engine (M4 and M5, #95 and #96)

Goal. Automated tests that will catch it if someone breaks scoring or attempt handling.

Why. The whole backend has exactly two test files: BackendApplicationTests.java and rules/TopicAccessRulesTest.java. Scoring is the most important code we have — if it silently breaks, students get wrong marks and we may not notice for weeks. You have changed scoring, timing, navigation and payments over four weeks. Lock that behaviour down.

Steps.

  1. Create AssessmentServiceTest covering gradeObjective thoroughly: correct single MCQ, wrong one with negative marks, fully correct multi-correct with bonus, partially correct multi-correct, multi-correct with a wrong option selected, numerical within tolerance, numerical outside it, and numerical inside the new range from H4.
  2. Add tests for the time limit from H2: saving before the deadline works, saving after it auto-submits and errors.
  3. Add tests for the navigation rules from H3: sequential rejects jumping ahead, section-locked rejects going back.
  4. Add a test for the payment signature check from H1: valid signature passes, invalid one is rejected, and the plan does not change on rejection. Use a fixed test key, never a real one.
  5. Use an in-memory H2 database so tests run without MySQL and anyone can run them.
  6. Confirm mvn test passes and add the command to the README.
  7. Aim for meaningful coverage of AssessmentService, not a coverage percentage. Test the paths that would hurt if they broke.

Done when. mvn test runs green, and deliberately breaking the negative-marking line makes a test fail with a clear message.

Do not do these

Say no if they come up. They are either scheduled for a later Gantt phase or explicitly deferred in our project rules.

  • Google and Microsoft sign-in, and hardware device binding.
  • Live PayU, Stripe and PayPal. Razorpay stays in stub mode locally; only the signature check becomes real.
  • SMS, WhatsApp and push notifications (M10). Email only.
  • Camera-based proctoring.
  • Item Response Theory and the question analytics dashboard (M14, W15 to W19).
  • White-label branding and custom domains (M15, W17 to W20).
  • AI-assisted marking (M17, Phase 3).
  • Multi-tenant architecture and the 10,000 concurrent user load test (M18 and M19, Phase 3).
  • The coding assessment module (M20, optional).
  • Writing the bulk question content itself. Per the Gantt the question bank tracks belong to Ayush Sharma — you build the tooling.
Actions #1

Updated by Ayush Yadav on 09/10/2026 12:45 PM

  • % Done changed from 0 to 15

Today's Work (10 Sep 2026)

Worked on the project/task assigned by Aayush Yadav.
Practised Spring Boot, focusing on its commonly used annotations.
Started learning React and covered the basic concepts.

Assigned-task progress:
- Task H1 (Plans page at /pricing) completed: Free vs Premium cards, Plans link in the header after Practice, and Your plan from subscriptionPlan. No live payment gateway.

Actions #2

Updated by Ayush Yadav on 09/11/2026 12:27 PM

Today's Work - 11 September 2026
Harshit Singh

I spent today on the TeamTalks work assigned by Aayush Yadav, and on learning how we run this project with Docker.

Assigned work (Aayush Yadav - Harshit's task list)
. Continued the TeamTalks task assigned to me, with focus on the membership / Plans flow and making the app usable after login.
. Built and checked the Plans page at /pricing: Free and Premium cards, and plan status that appears only after a user is signed in.
. For a Premium user the UI now shows "Premium user". For everyone else it shows the actual plan (Free).
. Set up my sign-in account so I can open the app, use the Admin panel, and verify the work end to end.
. Brought the stack up with Docker Compose and rebuilt the frontend/backend images after each change so localhost:5173 stays in sync with the code.

Learning - Docker
. Practised the daily Docker flow we use on TeamTalks: images, containers, and docker compose up --build.
. Learned why a code change does not show on localhost:5173 until the frontend image is rebuilt (nginx serves a built copy, not live source).
. Learned the service map: frontend on 5173, backend API on 8081, MySQL on 3307, and the AI sidecar on 3001.
. Practised starting Docker Desktop, waiting for containers to become healthy, and checking logs when a rebuild is needed.

I will keep using Docker for local runs and apply the same approach on the next assigned tasks.

Actions #3

Updated by Ayush Yadav on 09/14/2026 12:46 PM

Today's Work - 14 September 2026
Harshit Singh

I worked on the TeamTalks tasks assigned by Aayush Yadav.

Assigned work
- Made a separate payment page for Premium
- Made that page fit on one screen, so there is no need to scroll
- Added ways to pay: UPI, credit card, debit card, and net banking
- This is only a practice payment page. Real money is not taken.

Learning
- Docker - learnt various topics

I will keep applying this on the next assigned TeamTalks tasks.

Actions #4

Updated by Ayush Yadav on 09/15/2026 12:39 PM

Today's Work - 15 September 2026
Harshit Singh

I completed the TeamTalks task assigned by Aayush Yadav.

I also fixed some issues I was facing in the project and improved its structure.

Along with this, I learnt Docker and revised Java so I can use both better on the next tasks.

Actions #5

Updated by Ayush Yadav on 09/16/2026 12:47 PM

Today's Work - 16 September 2026
Harshit Singh

I completed the TeamTalks task assigned by Aayush Yadav.

I also spent some time revising Java. I went over the basics again so I can apply them more confidently on the next tasks.

Actions #6

Updated by Ayush Yadav on 09/21/2026 12:55 PM

  • Description updated (diff)

Task list replaced on 21 September 2026 for Gantt W11 to W14 (21 September to 16 October 2026).

Lanes have been swapped. Harshit now owns the Spring Boot backend and the admin panel. Nikita now owns the student-facing React frontend. Nothing already built is being thrown away.

The new list has 20 tasks, one per working day. Saturdays and Sundays are holidays and carry no tasks. Each task states its goal, why it matters, the exact steps with real file paths, and how to check it is finished.

A full code audit was run before writing this. Items already shipped have been removed from the list: bonus marks, the attempt audit log and its endpoint, Faculty and Candidate tabs, the admin plan dropdown, the premium plan helpers, the FREE and PREMIUM segment badges, and bulk candidate upload.

Actions #7

Updated by Ayush Yadav on 09/22/2026 12:22 PM

Today's Work - 22 September 2026
Harshit Singh

I spent time studying Java to strengthen my basics.

I also created 40 test cases on Cometa to cover the main flows and check that they work as expected.

Actions #8

Updated by Ayush Yadav on 09/23/2026 12:31 PM

Today's Work - 23 September 2026
Harshit Singh

I completed the assigned task.

I also studied Java and fixed some errors I faced today while working on the project.

Actions

Also available in: Atom PDF