Files
classroomio/prd/exercise-question-types [DONE]/README.md
T

435 lines
16 KiB
Markdown
Raw Normal View History

2026-03-09 11:18:22 +01:00
# Exercise Question Types Expansion PRD
## Status
- In progress (remaining question-type work; exercise question sections and video recording are tracked as separate shipped PRDs — see PRD-TRACKER DONE)
2026-03-09 11:18:22 +01:00
## Date
- February 24, 2026
## Implementation Plan
- See [implementation-plan.md](./implementation-plan.md) for ticket breakdown and delivery sequencing.
## Purpose
Expand ClassroomIO exercise questions beyond `single answer`, `multiple answers`, and `paragraph`, so instructors can run stronger assessments, reduce manual grading where possible, and collect richer assignment submissions inside the LMS.
## Problem Statement
Current exercise functionality is constrained by three legacy question types and a grading/answer model tuned for those types. This creates four product issues:
1. Limited assessment design for instructors.
2. Lower auto-grading coverage.
3. Weak first-class support for assignment-style submissions.
4. Increased instructor grading overhead and external-tool leakage.
## Current-State Audit
| Area | Current State | Gaps |
| --- | --- | --- |
| Question types | `RADIO`, `CHECKBOX`, `TEXTAREA` only | No true/false, numeric, fill-blank, matching, ordering, file-upload, hotspot |
| DB `question_type` | Seeded from `packages/db/src/utils/seed/questionType.ts` | Typename coverage too narrow |
| Validation constants | `QUESTION_TYPE` in `packages/utils/src/validation/constants.ts` contains only 3 numeric IDs | Hardcoded numeric assumptions are fragile as type set grows |
| Exercise validation | `packages/utils/src/validation/exercise/exercise.ts` has option rules for radio/checkbox + textarea | No schema-per-type authoring or submission validation |
| Exercise authoring UI | `edit-mode.svelte` renders only radio, checkbox, textarea | No renderer registry or extensibility contract |
| Exercise taking UI | `view-mode.svelte` answer flow assumes options or open text | No type-specific interaction patterns for new formats |
| Submission model | `question_answer` stores `answers[]` + `open_answer` | No structured payload for richer responses (fill blanks, ordering, matching, uploads) |
| Attempt controls | No max-attempt rule or attempt scoring strategy on `exercise` | Cannot configure highest/latest/average grading behavior |
| Test controls | No persisted timer/randomization/availability settings on exercise | Missing in-schema support for test-mode controls |
| File response support | No exercise-question-level upload config | Cannot do per-question file constraints |
## Data Sources Checked
- `packages/db/src/schema.ts`
- `packages/db/src/utils/seed/questionType.ts`
- `packages/db/src/queries/exercise/exercise.ts`
- `packages/utils/src/validation/constants.ts`
- `packages/utils/src/validation/exercise/exercise.ts`
- `apps/api/src/routes/course/exercise.ts`
- `apps/api/src/services/exercise/exercise.ts`
- `apps/api/src/services/exercise/utils.ts`
- `apps/api/src/services/submission/submission.ts`
- `apps/api/src/services/course/clone.ts`
- `apps/dashboard/src/lib/features/ui/question/constants.ts`
- `apps/dashboard/src/lib/features/course/components/exercise/edit-mode.svelte`
- `apps/dashboard/src/lib/features/course/components/exercise/view-mode.svelte`
- `apps/dashboard/src/lib/features/course/components/exercise/functions.ts`
- `apps/dashboard/src/lib/features/course/components/exercise/store.ts`
- `apps/dashboard/src/lib/features/course/api/exercise.svelte.ts`
## Primary Users
- Instructors creating tests and assignments.
- Students answering exercises on desktop and mobile.
- Admins and tutors grading and reviewing progress.
## Goals
1. Support additional objective and subjective question types.
2. Increase auto-grading coverage for objective formats.
3. Add first-class assignment upload support at question level.
4. Keep authoring UX consistent across question types.
5. Add explicit exercise-level controls for attempts, scoring rule, answer reveal, and test behavior.
## Non-Goals (v1)
- Online proctoring.
- AI-generated grading feedback.
- Code execution/judging sandboxes.
- LTI/QTI export.
- Negative marking.
- Audio/video response question type.
## Confirmed Decisions
1. Code execution is out of scope.
2. Upload size limits are plan-based: Free `2MB`, Paid `10MB`.
3. Resubmission policy is exercise-level, not per question.
4. Rollout phases remain `P0`, `P1`, `P2`.
5. If an exercise contains `short answer` or `paragraph`, final grading is manual.
6. Numeric-answer grading mode is configurable.
7. Fill-in-the-blank does not support multiple accepted answers per blank in v1.
8. Partial credit is enabled by default where supported.
9. Partial credit settings are question-level with exercise-level override.
10. File upload configuration is per question.
11. Exercise settings include max attempts.
12. Correct-answer visibility is exercise-level configurable.
13. Test-only controls are in scope.
14. Allowed upload file types in v1: `PDF`, `DOCX`, `PPTX`, `XLSX`, `ZIP`, and images.
15. Attempt grading rule is exercise-level configurable: `highest`, `latest`, `average`.
16. Upload question accepts exactly one file in v1.
## Proposed Question Types
| Type | Auto-gradable | Partial Credit | Assignment Fit | Test Fit | Phase |
| --- | --- | --- | --- | --- | --- |
| True/False | Yes | No | Medium | High | P0 |
| Short Answer (single-line) | No | N/A | High | High | P0 |
| Numeric Answer | Yes | Optional | Medium | High | P0 |
| Fill in the Blank | Yes | Yes | Medium | High | P0 |
| File Upload | No | N/A | High | Low | P0 |
| Matching | Yes | Yes | Medium | High | P1 |
| Ordering/Sequence | Yes | Yes | Medium | High | P1 |
| Hotspot/Image Labeling | Yes | Optional | Medium | Medium | P2 |
## Functional Requirements
### Authoring
- Instructor can choose question type from a type picker.
- Instructor can configure:
- prompt
- points
- required/optional
- per-question instructions
- type-specific settings
- Instructor can define answer key for auto-graded types.
- Instructor can configure scoring mode (`all_or_nothing` or `partial`) where supported.
- Instructor can preview question behavior before publish.
### Student Experience
- Students can answer all released types on desktop and mobile.
- Answers autosave while taking exercise.
- One exercise can contain mixed question types.
- File upload question enforces allowed MIME/extensions and plan-based limits.
- File upload question accepts one file in v1.
### Grading
- Objective types auto-grade immediately unless exercise is in manual-only mode.
- Manual types route to grading queue with teacher feedback.
- Instructors can override auto-graded scores.
- Gradebook stores per-question points and final exercise score.
- Exercise-level `auto-grade` toggle can only be enabled when all active question types are auto-gradable; otherwise it must be disabled with a hint message.
### Attempts and Test Controls
- Exercise-level settings:
- `maxAttempts`
- `attemptScoringRule` (`highest`, `latest`, `average`)
- `showCorrectAnswers` policy
- `timerSeconds` (optional)
- `randomizeQuestionOrder`
- `randomizeOptionOrder`
- availability window (`availableFrom`, `availableTo`)
## Technical Design
### 1. Canonical Question Type Registry
Keep one canonical source for question type metadata used by API and dashboard.
Target package:
- `packages/question-types/src/` (new, published as `@cio/question-types`)
Registry includes:
- stable key (`TRUE_FALSE`, `SHORT_ANSWER`, etc.)
- DB typename
- DB id (resolved at startup/seed, not hardcoded in scattered files)
- auto-gradable flag
- partial-credit support
- submission payload schema key
- rollout phase
### 1.1 Canonical Renderer Components
Keep question renderer components in `@cio/ui` and keep all shared logic/contracts in `@cio/question-types`.
Target package:
- `packages/ui/src/custom/exercise-question/` (new)
- exports via `packages/ui/src/index.ts`
Rules:
- renderer registry/contracts live in `@cio/question-types`.
- answer serialization and shared form-state utilities live in `@cio/question-types`.
- render contract object lives in `@cio/question-types` (`ExerciseQuestionRenderContract` / `ExerciseQuestionListRenderContract`) and is deterministic for the same inputs.
- question type renderer components live in `@cio/ui/custom/exercise-question`.
- `@cio/ui/custom/exercise-question` is components-only and does not own shared exercise logic.
- dashboard screens consume type keys/contracts from `@cio/question-types` and renderers from `@cio/ui/custom/exercise-question`.
### 1.2 Cross-App File Contract
- The complete individual file list for reusable shared packages is defined in `implementation-plan.md` under `Shared Package File Contract (Cross-App Reuse)`.
- Shared package code must stay app-agnostic so any app can consume it to build an exercise/forms product.
### 2. Data Model Changes
#### `question` table
Add:
- `settings` (`jsonb`, default `{}`, not null)
- `scoring_mode` (`varchar`, default `all_or_nothing`)
- `manual_grading_required` (`boolean`, default `false`)
`settings` examples:
- numeric: `{ "mode": "exact" | "tolerance_abs" | "tolerance_pct", "tolerance": number }`
- upload: `{ "maxFiles": 1, "allowedTypes": [...], "maxSizeMb": 2|10 }`
- matching: `{ "shuffleLeft": true, "shuffleRight": true }`
#### `question_answer` table
Add:
- `payload` (`jsonb`, default `{}`, not null)
Keep existing `answers[]` and `open_answer` for backward compatibility during migration; new types write to `payload`.
#### `exercise` table
Add:
- `max_attempts` (`integer`, default `1`)
- `attempt_scoring_rule` (`varchar`, default `latest`)
- `show_correct_answers` (`varchar`, default `after_grading`)
- `timer_seconds` (`integer`, nullable)
- `randomize_question_order` (`boolean`, default `false`)
- `randomize_option_order` (`boolean`, default `false`)
- `available_from` (`timestamptz`, nullable)
- `available_to` (`timestamptz`, nullable)
#### `submission` table
Add:
- `attempt_number` (`integer`, default `1`)
- `finalized_score` (`double precision`, nullable)
- `grading_mode` (`varchar`, default `auto_or_manual`)
### 3. Validation Layer
Update/expand `packages/utils/src/validation/exercise/`:
- `question-base.ts` (shared prompt/points/rules)
- `question-true-false.ts`
- `question-short-answer.ts`
- `question-numeric.ts`
- `question-fill-blank.ts`
- `question-file-upload.ts`
- `question-matching.ts`
- `question-ordering.ts`
- `question-hotspot.ts`
- compose into:
- `ZExerciseCreate`
- `ZExerciseUpdate`
- `ZExerciseSubmissionCreate`
Validation must run:
- in API routes
- in dashboard before submit/update
- in grading service for defensive checks
### 4. API Layer Changes
Primary touchpoints:
- `apps/api/src/routes/course/exercise.ts`
- `apps/api/src/services/exercise/exercise.ts`
- `apps/api/src/services/exercise/utils.ts`
- `apps/api/src/services/submission/submission.ts`
- `packages/db/src/queries/exercise/exercise.ts`
Key changes:
1. Accept/store question `settings` and `scoringMode`.
2. Support typed answer payload writes to `question_answer.payload`.
3. Enforce attempt limits before creating a submission.
4. Compute final grade using `attemptScoringRule`.
5. Auto/manual grading decision based on question mix.
6. Support file-response references through existing assets model.
### 5. Dashboard Layer Changes
Primary touchpoints:
- `packages/ui/src/custom/exercise-question/*` (new shared UI component set; components only)
- `packages/ui/src/index.ts`
- `apps/dashboard/src/lib/features/course/components/exercise/edit-mode.svelte`
- `apps/dashboard/src/lib/features/course/components/exercise/view-mode.svelte`
- `apps/dashboard/src/lib/features/course/components/exercise/functions.ts`
- `apps/dashboard/src/lib/features/course/components/exercise/store.ts`
- `apps/dashboard/src/lib/features/course/api/exercise.svelte.ts`
- `apps/dashboard/src/lib/features/course/utils/types.ts`
- translations in `apps/dashboard/src/lib/utils/translations/en.json`
Refactor plan:
1. Introduce registry/contracts in `@cio/question-types` and map question type keys to shared UI renderers.
2. Keep existing mode structure (`edit`, `view`, `preview`) but remove hardcoded `if RADIO/CHECKBOX/TEXTAREA` branching.
3. Add type-specific renderer components under `packages/ui/src/custom/exercise-question`.
4. Add translation keys for new types and setting labels.
5. Add exercise-level `auto-grade` toggle with eligibility guard and hint message when blocked by manual-only question types.
### 6. Legacy Compatibility and Migration
1. Existing exercises keep working without migration rewrite of question data.
2. New columns default safely (`{}` / false / null / default enums).
3. `question_type` seed extends with new typenames.
4. Replace hardcoded numeric assumptions where present.
Known cleanup item discovered during audit:
- `apps/api/src/services/course/clone.ts` uses `QUESTION_TYPE_TEXTAREA = 2`, while shared constant maps `TEXTAREA = 3`.
- This must be corrected before rollout to avoid option-cloning regressions for paragraph questions.
## Progress Update (Feb 26, 2026)
Completed in branch:
1. Shared package contracts/registry implemented in `@cio/question-types`.
2. Dashboard renderer logic centralized via shared render contracts and shared `@cio/ui/custom/exercise-question` components.
3. Shared question renderer component files implemented for legacy and expanded question types.
4. Clone service question-type ID mismatch fixed to use shared constants.
Still pending for grading engine milestone:
1. End-to-end server auto-grading engine across non-legacy types.
2. Exercise-level `auto-grade` toggle + disabled-state hint behavior.
3. Full per-type validation/attempt-rule enforcement rollout in API.
## Rollout Plan
### Phase 1 (P0)
Question types:
- True/False
- Short Answer
- Numeric
- Fill in the Blank
- File Upload
Required deliverables:
1. DB columns for settings/payload/attempt controls.
2. Validation and API support for P0 types.
3. Dashboard renderer registry + P0 components.
4. Attempt scoring rule support.
5. Migration + seed updates.
### Phase 2 (P1)
Question types:
- Matching
- Ordering/Sequence
Required deliverables:
1. Partial-credit grading helpers.
2. Enhanced analytics aggregation for pair/order correctness.
3. UX polish for drag/drop and accessibility.
### Phase 3 (P2)
Question types:
- Hotspot/Image Labeling
Required deliverables:
1. Image coordinate mapping schema.
2. Responsive touch/mobile interaction hardening.
3. Additional grading explainability in teacher UI.
## Testing Requirements
### API
- Schema validation tests for each type (create/update/submit).
- Grading tests:
- all-or-nothing
- partial-credit
- manual-required fallback
- Attempt rule tests (`highest`, `latest`, `average`).
- Upload constraint tests by plan limit.
### Dashboard
- Component tests for each type renderer in edit/take modes.
- Store tests for autosave and answer serialization.
- Integration tests for submission payload generation.
### Regression
- Existing three types must pass unchanged behavior tests.
- Exercise cloning must preserve all supported question types correctly.
## Build and Verification Commands
1. `pnpm --filter @cio/db build`
2. `pnpm --filter @cio/utils build`
3. `pnpm --filter @cio/api build`
4. `pnpm --filter @cio/dashboard build`
## Success Metrics (90 Days Post-Launch)
1. 40% of newly created exercises include at least one new type.
2. 25% reduction in average grading time per exercise.
3. 20% increase in completion for assignment-heavy exercises.
4. 30% increase in auto-graded question share in test exercises.
## Risks and Mitigations
1. Hardcoded question-type IDs may cause logic drift.
- Mitigation: canonical registry + typename-based mapping.
2. Rich payload types can increase complexity in submission parsing.
- Mitigation: strict per-type schema and shared serializer/deserializer utilities.
3. File uploads can introduce abuse/security risk.
- Mitigation: MIME/size validation, one-file v1 cap, server-side checks, existing asset safeguards.
## Open Questions
1. Should `short answer` support optional case-insensitive exact-match auto-grading in v1.1?
2. For fill-in-the-blank, should punctuation normalization be strict or relaxed by default?
3. Should hotspot remain P2 or shift post-P2 depending on demand?