fix(question-types): realign registry ids with the question_type table

The registry hardcoded numeric ids that drifted from the database
`question_type` table: MATCHING and HOTSPOT were inserted mid-list
(ids 9 and 11) but never seeded, shifting ORDERING/LINK/WORD_BANK/
STAR/VIDEO_RECORDING one or two ids past their real rows. Every id
emitter inherited the wrong values — the exercise editor, the AI
question generator, and the grading/scoring gate — so e.g. an ORDERING
question was stored as id 10 (LINK in the DB), came back as a manual
type, and never auto-graded.

- Move MATCHING/HOTSPOT to the end (ids 14/15) and flag them
  `disabled`, restoring the append-only id convention and realigning
  ORDERING=9, LINK=10, WORD_BANK=11, STAR=12, VIDEO_RECORDING=13 with
  the DB.
- Add a registry test asserting every enabled type's id matches the
  canonical `question_type` rows and disabled types stay past the live
  range, so ids can't silently drift again.
- Constrain the agent tool schema to enabled, DB-backed ids so the
  model can't emit a disabled id with no row.
- Drop disabled types from the teacher prompt's type list and guidance.
- Centralize the dashboard question-type picker on the `disabled` flag.

This also fixes video-recording detection, which compared against the
stale id 15 instead of the real 13.
This commit is contained in:
Rotimi Best
2026-06-29 01:07:02 +01:00
parent eb4cd22ac5
commit 55abf982ed
6 changed files with 131 additions and 72 deletions
@@ -74,29 +74,11 @@ export const QUESTION_TYPE_REGISTRY: readonly QuestionTypeMetadata[] = [
supportsPartialCredit: false,
manualGradingRequired: true
},
{
key: QUESTION_TYPE_KEY.MATCHING,
typename: 'MATCHING',
label: 'Matching',
id: 9,
autoGradable: true,
supportsPartialCredit: true,
manualGradingRequired: false
},
{
key: QUESTION_TYPE_KEY.ORDERING,
typename: 'ORDERING',
label: 'Ordering',
id: 10,
autoGradable: true,
supportsPartialCredit: true,
manualGradingRequired: false
},
{
key: QUESTION_TYPE_KEY.HOTSPOT,
typename: 'HOTSPOT',
label: 'Hotspot',
id: 11,
id: 9,
autoGradable: true,
supportsPartialCredit: true,
manualGradingRequired: false
@@ -105,7 +87,7 @@ export const QUESTION_TYPE_REGISTRY: readonly QuestionTypeMetadata[] = [
key: QUESTION_TYPE_KEY.LINK,
typename: 'LINK',
label: 'Links',
id: 12,
id: 10,
autoGradable: false,
supportsPartialCredit: false,
manualGradingRequired: true
@@ -114,7 +96,7 @@ export const QUESTION_TYPE_REGISTRY: readonly QuestionTypeMetadata[] = [
key: QUESTION_TYPE_KEY.WORD_BANK,
typename: 'WORD_BANK',
label: 'Word bank',
id: 13,
id: 11,
autoGradable: true,
supportsPartialCredit: true,
manualGradingRequired: false
@@ -123,7 +105,7 @@ export const QUESTION_TYPE_REGISTRY: readonly QuestionTypeMetadata[] = [
key: QUESTION_TYPE_KEY.STAR,
typename: 'STAR',
label: 'Star rating',
id: 14,
id: 12,
autoGradable: true,
supportsPartialCredit: false,
manualGradingRequired: false
@@ -132,13 +114,40 @@ export const QUESTION_TYPE_REGISTRY: readonly QuestionTypeMetadata[] = [
key: QUESTION_TYPE_KEY.VIDEO_RECORDING,
typename: 'VIDEO_RECORDING',
label: 'Video recording',
id: 15,
id: 13,
autoGradable: false,
supportsPartialCredit: false,
manualGradingRequired: true
},
// Built but not offered in the product. Kept at the END of the id sequence
// (append-only) so they never shift the ids of types that exist in the DB.
// They have no `question_type` row, so `disabled` keeps them out of pickers,
// the AI tool enum, and validation.
{
key: QUESTION_TYPE_KEY.MATCHING,
typename: 'MATCHING',
label: 'Matching',
id: 14,
autoGradable: true,
supportsPartialCredit: true,
manualGradingRequired: false,
disabled: true
},
{
key: QUESTION_TYPE_KEY.HOTSPOT,
typename: 'HOTSPOT',
label: 'Hotspot',
id: 15,
autoGradable: true,
supportsPartialCredit: true,
manualGradingRequired: false,
disabled: true
}
] as const;
/** Types actually offered in the product (excludes `disabled` ones with no DB row). */
export const ENABLED_QUESTION_TYPE_REGISTRY = QUESTION_TYPE_REGISTRY.filter((type) => !type.disabled);
export const QUESTION_TYPE_BY_KEY = Object.freeze(
QUESTION_TYPE_REGISTRY.reduce(
(acc, type) => {
@@ -8,4 +8,6 @@ export interface QuestionTypeMetadata {
autoGradable: boolean;
supportsPartialCredit: boolean;
manualGradingRequired: boolean;
/** Built in code but not offered in the product (no `question_type` row). */
disabled?: boolean;
}