# Project: a training exercise and answer-record system

Canonical page: https://yishto.com/en/learn/project-labs/lab-training-assessments/

Author: [Yishto](https://yishto.com/en/about/)

Freeze question and grading versions and design drafts, final submissions, and result access in a training system with retry and answer-protection checks.

Published: 2026-11-10
Updated: 2026-11-10

A training exercise system lets learners answer questions, save responses, and receive explainable scores. Its initial scope excludes proctoring and treating AI feedback as objective certification. The engineering goals are consistent grading for one question version, access limited to authorized records, and one final submission despite retries.

Start with single-choice questions before adding open answers, hints, or human comments. Evaluating an open response and marking a fixed answer require different rules; combining them into one correctness rate hides that distinction.

## Define an attempt's lifecycle

A learner creates an attempt, saves draft answers, submits them, and reads the result. Define whether a retake creates a new attempt instead of mutating graded answers. Trainers maintain questions and rules, but knowing a record ID does not authorize access to every tenant.

Specify failures for missing questions, closed attempts, invalid fields, forbidden access, and repeated submissions. The interface waits for server confirmation before declaring completion. After a timeout, query the same attempt's state.

If there is a deadline, the server evaluates it using an explicit time policy. A browser countdown is a reminder rather than authority to extend the window. Define whether offline drafts can arrive after the deadline instead of letting clients decide independently.

## Freeze questions and grading rules

A question has a stable ID, while its text, options, and answer belong to a version. An attempt binds a question-version list and grading-rule version. Updating the question bank must not change an active attempt or silently reinterpret historical results.

Store courses, question versions, attempts, drafts, final submissions, and results. A final submission preserves the answers and grading outcome for that attempt version. A uniqueness constraint on final submission per attempt helps enforce one final result at the database boundary.

Regrading should create an explicit record identifying its rule and reason. Do not overwrite history so the revised score appears original. Complete records do not establish question quality, learning benefit, or assessment validity; those need separate evaluation.

## Keep answers and grading authority on the server

The frontend receives question text and necessary options; the server retains answer keys. A submission carries allowed question IDs and choices. The server calculates the result using frozen rules. Client scores, correctness flags, and role claims are untrusted inputs.

Validate membership of questions in the attempt, valid option values, answer count, and request size. Specify whether missing answers count as unanswered or cause rejection. Reject unknown questions instead of silently dropping them while reporting success.

Authorize using verified identity and resource relationships. Result retrieval needs permission too. Limit trainer reports to their scope and exclude unnecessary identity details from exports and logs.

## Organize the Express submission path

The HTTP adapter handles identity, parameters, and responses. An application service coordinates state and submission, a grading function applies defined rules, and storage code handles transactions. Verify pure grading before connecting database and HTTP boundaries.

Coordinate state checks, unique submission, result storage, and closing the attempt in a transaction. An in-memory submitting flag cannot deduplicate concurrent requests across services. A repeated logical request returns its existing outcome. Detect conflicting payloads under the same request key rather than accepting whichever arrives later.

AI hints can explain concepts during permitted stages. They must not access protected answers or alter authoritative scores. Store generated open-answer comments separately, identifying their generation and review requirements.

## Check the core grading contract offline

Two synthetic questions award one point per correct answer. Missing answers receive zero; unknown questions fail. This verifies the teaching rule rather than a question service or submission transaction.

```python
key = {"Q-1": "B", "Q-2": "A"}
def grade(answers):
    if not set(answers) <= set(key):
        raise ValueError("unknown question")
    return sum(answers.get(q) == expected for q, expected in key.items())
assert grade({"Q-1": "B", "Q-2": "C"}) == 1
assert grade({}) == 0
try:
    grade({"Q-3": "A"})
    raise AssertionError("unknown question accepted")
except ValueError:
    pass
print(grade({"Q-1": "B", "Q-2": "A"}))
```

The output is `2`. One incorrect answer produces one point, and an empty response produces zero. The function does not validate available options, identity, deadlines, or database state. A production boundary must check these before grading. Exposing this function over HTTP does not establish reliable submission.

## Make interface states understandable

Show unanswered, selected, draft-saving, and save-failed states. Preserve local input on failure and distinguish retrying draft storage from final submission. Disable repeated submission clicks while waiting, while retaining server-side concurrency and deduplication guarantees.

Results explain rules, score, unanswered count, and permitted feedback. Pending processing is not a zero score. Forbidden responses must not display another user's cached result. Restore attempt state from the server when navigating or reopening.

Implement question-version import, attempt creation, drafts, final grading, and result retrieval before the full interface. Test inputs, authority, and state with synthetic data at each boundary before adding batches or reports.

Drafts need revision checks too. Two browsers must not blindly overwrite newer answers. Return the current revision on conflict and preserve recoverable local content. Final submission binds an answer snapshot and closes draft writes. Randomized question or option order belongs to the attempt snapshot so refresh does not change interpretation. A result export rechecks ownership and permitted fields instead of copying everything available to a trainer view.

## Exercise: retries and question updates

Change a question's answer after an attempt starts. The attempt must still use its bound version. A second attempt uses an explicitly selected new version. Historical results should identify their grading rule.

Send two concurrent final submissions and simulate a timeout before receiving confirmation. Verify one submission and one result, a retrievable stable outcome, and denied access for another identity. Add invalid choices, unknown questions, late submissions, and hints that attempt to expose answers, with a defined response for each.

## Sources & further reading

- [Express routing](https://expressjs.com/en/guide/routing.html)
- [SQLite transaction documentation](https://www.sqlite.org/lang_transaction.html)
- [API security risks](https://owasp.org/API-Security/editions/2023/en/0x00-header/)

Sources checked on 2026-11-10. Examples are teaching data, not production measurements.

## Related guides

- [LLM evaluation: compare quality, latency, and cost with a task set](https://yishto.com/en/learn/models/model-evaluation/)
- [Use database constraints to protect business correctness](https://yishto.com/en/learn/service-data/database-indexes-and-transactions/)
