@memizy/oqse 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/SPECIFICATION.md +2444 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +845 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/manifest.d.ts +429 -0
- package/dist/src/manifestValidation.d.ts +167 -0
- package/dist/src/manifestValidation.test.d.ts +1 -0
- package/dist/src/oqse.d.ts +1121 -0
- package/dist/src/oqseValidation.d.ts +6120 -0
- package/dist/src/oqseValidation.test.d.ts +1 -0
- package/dist/src/progress.d.ts +42 -0
- package/dist/src/progressValidation.d.ts +9 -0
- package/dist/src/progressValidation.test.d.ts +1 -0
- package/dist/src/utils.d.ts +3 -0
- package/package.json +40 -0
- package/schemas/README.md +1 -0
- package/schemas/oqse-manifest-v0.1.json +256 -0
- package/schemas/oqse-v0.1.json +7573 -0
- package/schemas/oqsep-v0.1.json +172 -0
|
@@ -0,0 +1,1121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OQSE v0.1 Type Definitions
|
|
3
|
+
* (Open Quiz & Study Exchange)
|
|
4
|
+
*
|
|
5
|
+
* Type-safe TypeScript definitions for the OQSE specification.
|
|
6
|
+
* Uses Discriminated Unions to make invalid states unrepresentable.
|
|
7
|
+
*
|
|
8
|
+
* @see /SPECIFICATION.md
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* BCP 47 language code (e.g., "en", "en-US", "cs", "zh-Hans")
|
|
12
|
+
*/
|
|
13
|
+
export type LanguageCode = string;
|
|
14
|
+
/**
|
|
15
|
+
* SPDX license identifier (e.g., "CC-BY-SA-4.0", "CC0-1.0")
|
|
16
|
+
* @see https://spdx.org/licenses/
|
|
17
|
+
*/
|
|
18
|
+
export type SPDXLicense = string;
|
|
19
|
+
/**
|
|
20
|
+
* ISO 8601 date/time string in RFC 3339 format.
|
|
21
|
+
* Examples: "2025-11-21", "2025-11-21T14:30:00Z", "2025-11-21T15:30:00+01:00"
|
|
22
|
+
*/
|
|
23
|
+
export type ISO8601DateTime = string;
|
|
24
|
+
/**
|
|
25
|
+
* Media object type
|
|
26
|
+
*/
|
|
27
|
+
export type MediaType = 'image' | 'audio' | 'video' | 'model';
|
|
28
|
+
/**
|
|
29
|
+
* Media object for embedding media in items or set metadata.
|
|
30
|
+
* Used in `meta.assets` and `item.assets`.
|
|
31
|
+
*/
|
|
32
|
+
export interface MediaObject {
|
|
33
|
+
/** Media type */
|
|
34
|
+
type: MediaType;
|
|
35
|
+
/**
|
|
36
|
+
* URI of the resource.
|
|
37
|
+
* Can be absolute URL (https://...) or relative path in package (assets/...)
|
|
38
|
+
*/
|
|
39
|
+
value: string;
|
|
40
|
+
/** MIME type (e.g., "image/png", "audio/mpeg", "video/mp4") */
|
|
41
|
+
mimeType?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Alternative text for accessibility.
|
|
44
|
+
* REQUIRED for images, optional for audio/video.
|
|
45
|
+
* MUST be plain text without formatting.
|
|
46
|
+
*/
|
|
47
|
+
altText?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Verbatim transcript of spoken word (for audio/video).
|
|
50
|
+
* Rich Content (Markdown, LaTeX).
|
|
51
|
+
*/
|
|
52
|
+
transcript?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Media caption displayed below/next to media.
|
|
55
|
+
* Rich Content (Markdown, LaTeX).
|
|
56
|
+
*/
|
|
57
|
+
caption?: string;
|
|
58
|
+
/** Preferred width in pixels (rendering hint) */
|
|
59
|
+
width?: number;
|
|
60
|
+
/** Preferred height in pixels (rendering hint) */
|
|
61
|
+
height?: number;
|
|
62
|
+
/** Time in seconds where media playback should start (for audio/video) */
|
|
63
|
+
start?: number;
|
|
64
|
+
/** Time in seconds where media playback should end (for audio/video) */
|
|
65
|
+
end?: number;
|
|
66
|
+
/** Whether media should loop (for audio/video). Default: false */
|
|
67
|
+
loop?: boolean;
|
|
68
|
+
/** Subtitles for audio/video */
|
|
69
|
+
subtitles?: SubtitleTrack[];
|
|
70
|
+
/** SPDX license for this specific media (if different from set) */
|
|
71
|
+
license?: SPDXLicense;
|
|
72
|
+
/** Media author/source attribution (e.g., "Photo: NASA") */
|
|
73
|
+
attribution?: string;
|
|
74
|
+
/**
|
|
75
|
+
* File integrity checksums.
|
|
76
|
+
* Key is algorithm name ("sha256", "sha512", "md5"), value is hex hash.
|
|
77
|
+
*/
|
|
78
|
+
checksums?: Record<string, string>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Subtitle track for audio/video media
|
|
82
|
+
*/
|
|
83
|
+
export interface SubtitleTrack {
|
|
84
|
+
/** Language code (BCP 47) */
|
|
85
|
+
lang: LanguageCode;
|
|
86
|
+
/** URI to .vtt or .srt file (absolute URL or relative path) */
|
|
87
|
+
value: string;
|
|
88
|
+
/** Display name (e.g., "Czech", "English") */
|
|
89
|
+
label?: string;
|
|
90
|
+
/** Subtitle type per WebVTT specification. Default: "subtitles" */
|
|
91
|
+
kind?: 'captions' | 'subtitles' | 'descriptions';
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Dictionary of media objects.
|
|
95
|
+
* Key is lowercase alphanumeric identifier (a-z, 0-9, _, -)
|
|
96
|
+
*/
|
|
97
|
+
export type AssetDictionary = Record<string, MediaObject>;
|
|
98
|
+
/**
|
|
99
|
+
* Person object for identifying authors and contributors
|
|
100
|
+
*/
|
|
101
|
+
export interface PersonObject {
|
|
102
|
+
/** Full name of the person */
|
|
103
|
+
name: string;
|
|
104
|
+
/** Role in the project (e.g., "Editor", "Translator", "Biology Expert") */
|
|
105
|
+
role?: string;
|
|
106
|
+
/** Contact email */
|
|
107
|
+
email?: string;
|
|
108
|
+
/** Link to website or author profile */
|
|
109
|
+
url?: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Source material type
|
|
113
|
+
*/
|
|
114
|
+
export type SourceMaterialType = 'url' | 'doi' | 'isbn' | 'pdf' | 'textbook' | 'video' | 'audio' | 'image' | 'model' | 'other';
|
|
115
|
+
/**
|
|
116
|
+
* Source material object describing sources from which the set draws
|
|
117
|
+
*/
|
|
118
|
+
export interface SourceMaterial {
|
|
119
|
+
/**
|
|
120
|
+
* Unique ID within the file (not global UUID).
|
|
121
|
+
* Can be alphanumeric string (e.g., "src-1", "book-physics")
|
|
122
|
+
*/
|
|
123
|
+
id: string;
|
|
124
|
+
/** Source type */
|
|
125
|
+
type: SourceMaterialType;
|
|
126
|
+
/**
|
|
127
|
+
* Source value.
|
|
128
|
+
* For 'url', 'pdf', 'video', 'audio', 'image': absolute URL
|
|
129
|
+
* For 'doi', 'isbn', 'textbook', 'other': identifier string
|
|
130
|
+
*/
|
|
131
|
+
value: string;
|
|
132
|
+
/** Source title */
|
|
133
|
+
title: string;
|
|
134
|
+
/** Additional source description */
|
|
135
|
+
description?: string;
|
|
136
|
+
/** Source authors */
|
|
137
|
+
authors?: string[];
|
|
138
|
+
/** Source publication date (ISO 8601) */
|
|
139
|
+
publishedDate?: ISO8601DateTime;
|
|
140
|
+
/** Date when the source was used (ISO 8601) */
|
|
141
|
+
retrievedAt?: ISO8601DateTime;
|
|
142
|
+
/** License of the original source (SPDX ID) */
|
|
143
|
+
license?: SPDXLicense;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Reference to a source material from an item
|
|
147
|
+
*/
|
|
148
|
+
export interface SourceReference {
|
|
149
|
+
/** ID of source defined in meta.sourceMaterials */
|
|
150
|
+
id: string;
|
|
151
|
+
/** Specification of place in source (e.g., "page 42", "time 12:30") */
|
|
152
|
+
location?: string;
|
|
153
|
+
/**
|
|
154
|
+
* Exact citation or text segment from source.
|
|
155
|
+
* Rich Content (Markdown, LaTeX).
|
|
156
|
+
*/
|
|
157
|
+
quote?: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Tag definition for semantic linking
|
|
161
|
+
*/
|
|
162
|
+
export interface TagDefinition {
|
|
163
|
+
/** "Q" identifier from Wikidata for semantic linking */
|
|
164
|
+
wikidataId?: string;
|
|
165
|
+
/** More detailed tag description */
|
|
166
|
+
description?: string;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Dictionary of tag definitions.
|
|
170
|
+
* Key is the tag string, value is TagDefinition.
|
|
171
|
+
*/
|
|
172
|
+
export type TagDefinitionDictionary = Record<string, TagDefinition>;
|
|
173
|
+
/**
|
|
174
|
+
* FeatureProfile is a base structure shared between:
|
|
175
|
+
* - Application Manifest `capabilities` object (§2.1.2)
|
|
176
|
+
* - Study Set `meta.requirements` object (§3)
|
|
177
|
+
*
|
|
178
|
+
* It declares which features, LaTeX packages, and item/meta properties
|
|
179
|
+
* an application supports (in Manifest) or requires (in Study Set).
|
|
180
|
+
*
|
|
181
|
+
* @see /SPECIFICATION.md#22-feature-profile--official-registry
|
|
182
|
+
*/
|
|
183
|
+
export interface FeatureProfile {
|
|
184
|
+
/**
|
|
185
|
+
* Array of feature flags from the Official Feature Registry or `x-` prefixed extensions.
|
|
186
|
+
* Example: `["math", "markdown", "x-memizy-3d-voxel"]`
|
|
187
|
+
*/
|
|
188
|
+
features?: string[];
|
|
189
|
+
/**
|
|
190
|
+
* Array of supported/required LaTeX packages.
|
|
191
|
+
* Only meaningful when `features` includes `"math"`.
|
|
192
|
+
* Example: `["mhchem", "amsmath"]`
|
|
193
|
+
*/
|
|
194
|
+
latexPackages?: string[];
|
|
195
|
+
/**
|
|
196
|
+
* Array of item-level properties supported/required.
|
|
197
|
+
* Example: `["hints", "explanation", "sources"]`
|
|
198
|
+
*/
|
|
199
|
+
itemProperties?: string[];
|
|
200
|
+
/**
|
|
201
|
+
* Array of set-level metadata properties supported/required.
|
|
202
|
+
* Example: `["license", "contributors", "tags"]`
|
|
203
|
+
*/
|
|
204
|
+
metaProperties?: string[];
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Feature requirement type
|
|
208
|
+
* @deprecated Use FeatureProfile instead for spec-compliant requirements.
|
|
209
|
+
*/
|
|
210
|
+
export type FeatureType = 'official' | 'experimental' | 'proprietary';
|
|
211
|
+
/**
|
|
212
|
+
* Official feature names
|
|
213
|
+
*/
|
|
214
|
+
export type OfficialFeatureName = 'math' | 'media-image' | 'media-audio' | 'media-video' | 'hotspots' | 'complex-pairing' | 'open-text';
|
|
215
|
+
/**
|
|
216
|
+
* Feature requirement object
|
|
217
|
+
*/
|
|
218
|
+
export interface FeatureRequirement {
|
|
219
|
+
/** Feature name */
|
|
220
|
+
name: string;
|
|
221
|
+
/** Feature type */
|
|
222
|
+
type: FeatureType;
|
|
223
|
+
/**
|
|
224
|
+
* For proprietary features: Domain name of the provider (e.g., "memizy.com").
|
|
225
|
+
* MUST be present for type: "proprietary", ignored for other types.
|
|
226
|
+
*/
|
|
227
|
+
vendor?: string;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Translation object defining reference to translated version of this set
|
|
231
|
+
*/
|
|
232
|
+
export interface TranslationObject {
|
|
233
|
+
/** Language code (BCP 47) of translated set */
|
|
234
|
+
lang: LanguageCode;
|
|
235
|
+
/** Unique UUID (meta.id) of translated set */
|
|
236
|
+
id: string;
|
|
237
|
+
/** Name of translated set */
|
|
238
|
+
title: string;
|
|
239
|
+
/** Link to download translated set */
|
|
240
|
+
downloadUrl?: string;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Linked set object for prerequisites and related sets
|
|
244
|
+
*/
|
|
245
|
+
export interface LinkedSetObject {
|
|
246
|
+
/** UUID of OQSE set being referenced */
|
|
247
|
+
id: string;
|
|
248
|
+
/** Name of referenced set */
|
|
249
|
+
title: string;
|
|
250
|
+
/** Absolute address for downloading set */
|
|
251
|
+
downloadUrl?: string;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Bloom's taxonomy level (revised)
|
|
255
|
+
*/
|
|
256
|
+
export type BloomLevel = 'remember' | 'understand' | 'apply' | 'analyze' | 'evaluate' | 'create';
|
|
257
|
+
/**
|
|
258
|
+
* Cognitive load level
|
|
259
|
+
*/
|
|
260
|
+
export type CognitiveLoad = 'low' | 'medium' | 'high';
|
|
261
|
+
/**
|
|
262
|
+
* Pedagogy object for advanced data for adaptive learning and psychometrics
|
|
263
|
+
*/
|
|
264
|
+
export interface Pedagogy {
|
|
265
|
+
/** Level according to revised Bloom's taxonomy */
|
|
266
|
+
bloomLevel?: BloomLevel;
|
|
267
|
+
/** IRT Parameter b (Difficulty). Typically range -3.0 to +3.0 */
|
|
268
|
+
irtDifficulty?: number;
|
|
269
|
+
/** IRT Parameter a (Discrimination) */
|
|
270
|
+
irtDiscrimination?: number;
|
|
271
|
+
/** IRT Parameter c (Pseudo-guessing). Probability of guessing answer by chance */
|
|
272
|
+
irtGuessing?: number;
|
|
273
|
+
/** Average time (in seconds) students actually need to solve */
|
|
274
|
+
avgTime?: number;
|
|
275
|
+
/** Subjective cognitive load */
|
|
276
|
+
cognitiveLoad?: CognitiveLoad;
|
|
277
|
+
/** Whether item supports partial scoring */
|
|
278
|
+
partialCredit?: boolean;
|
|
279
|
+
/** Penalty for each wrong choice (0.0 - 1.0) */
|
|
280
|
+
penaltyPerWrong?: number;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Math renderer type
|
|
284
|
+
*/
|
|
285
|
+
export type MathRenderer = 'katex' | 'mathjax';
|
|
286
|
+
/**
|
|
287
|
+
* Math settings for rendering mathematical formulas
|
|
288
|
+
*/
|
|
289
|
+
export interface MathSettings {
|
|
290
|
+
/** Preferred renderer */
|
|
291
|
+
renderer?: MathRenderer;
|
|
292
|
+
/** Array of LaTeX package names required for proper formula display */
|
|
293
|
+
packages?: string[];
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Metadata describing the entire set
|
|
297
|
+
*/
|
|
298
|
+
export interface OQSEMeta {
|
|
299
|
+
/** Unique UUID of the set */
|
|
300
|
+
id: string;
|
|
301
|
+
/**
|
|
302
|
+
* Origin UUID of the creator.
|
|
303
|
+
* Used to verify ownership when syncing between devices or cloud.
|
|
304
|
+
* If created anonymously, this is the local device install ID.
|
|
305
|
+
*/
|
|
306
|
+
originUid?: string;
|
|
307
|
+
/** Language code of the set (BCP 47) */
|
|
308
|
+
language: LanguageCode;
|
|
309
|
+
/** Title of the study set (Plain Text) */
|
|
310
|
+
title: string;
|
|
311
|
+
/** Short description of the set (Rich Content) */
|
|
312
|
+
description?: string;
|
|
313
|
+
/** Key from meta.assets that defines the cover image */
|
|
314
|
+
thumbnail?: string;
|
|
315
|
+
/** Dictionary of media for the entire set */
|
|
316
|
+
assets?: AssetDictionary;
|
|
317
|
+
/** Minimum recommended age (0 = preschool, recommended >= 3) */
|
|
318
|
+
ageMin?: number;
|
|
319
|
+
/** Maximum recommended age */
|
|
320
|
+
ageMax?: number;
|
|
321
|
+
/** Subject/field (e.g., "mathematics", "biology", "history") */
|
|
322
|
+
subject?: string;
|
|
323
|
+
/** Date and time of creation (ISO 8601) */
|
|
324
|
+
createdAt: ISO8601DateTime;
|
|
325
|
+
/** Date and time of last modification (ISO 8601) */
|
|
326
|
+
updatedAt: ISO8601DateTime;
|
|
327
|
+
/** Information about the main author of the set */
|
|
328
|
+
author?: PersonObject;
|
|
329
|
+
/** Array of information about other contributors */
|
|
330
|
+
contributors?: PersonObject[];
|
|
331
|
+
/** SPDX license identifier */
|
|
332
|
+
license?: SPDXLicense;
|
|
333
|
+
/** Link to the full text of the license */
|
|
334
|
+
licenseUrl?: string;
|
|
335
|
+
/**
|
|
336
|
+
* A FeatureProfile object describing set's explicit requirements.
|
|
337
|
+
* Declares which features, LaTeX packages, and item/meta properties are required.
|
|
338
|
+
* Note: Required item types and assets are implicit and MUST NOT be listed here.
|
|
339
|
+
*
|
|
340
|
+
* @see FeatureProfile
|
|
341
|
+
*/
|
|
342
|
+
requirements?: FeatureProfile;
|
|
343
|
+
/**
|
|
344
|
+
* @deprecated Use `requirements` (FeatureProfile) instead.
|
|
345
|
+
* This field used a non-spec-compliant structure.
|
|
346
|
+
*/
|
|
347
|
+
requiredFeatures?: FeatureRequirement[];
|
|
348
|
+
/** Array of text labels (tags) for the entire set */
|
|
349
|
+
tags?: string[];
|
|
350
|
+
/** Dictionary of tag definitions */
|
|
351
|
+
tagDefinitions?: TagDefinitionDictionary;
|
|
352
|
+
/** Array of references to translations of this set */
|
|
353
|
+
translations?: TranslationObject[];
|
|
354
|
+
/** Array of objects describing sources from which the set draws */
|
|
355
|
+
sourceMaterials?: SourceMaterial[];
|
|
356
|
+
/** Estimated time to complete the set in minutes */
|
|
357
|
+
estimatedTime?: number;
|
|
358
|
+
/** Array of references to sets that should be completed before this set */
|
|
359
|
+
prerequisites?: LinkedSetObject[];
|
|
360
|
+
/** Array of references to related OQSE sets */
|
|
361
|
+
relatedSets?: LinkedSetObject[];
|
|
362
|
+
/** Configuration for rendering mathematical formulas */
|
|
363
|
+
mathSettings?: MathSettings;
|
|
364
|
+
/** Object for metadata determined by the author/creator */
|
|
365
|
+
customData?: Record<string, unknown>;
|
|
366
|
+
/** Object for metadata specific to a particular software application */
|
|
367
|
+
appSpecific?: Record<string, unknown>;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Base properties shared by all item types
|
|
371
|
+
*/
|
|
372
|
+
export interface BaseItem {
|
|
373
|
+
/** Unique UUID of the item */
|
|
374
|
+
id: string;
|
|
375
|
+
/** Item type (discriminator for union) */
|
|
376
|
+
type: string;
|
|
377
|
+
/** Dictionary of media for this item */
|
|
378
|
+
assets?: AssetDictionary;
|
|
379
|
+
/** Language code (BCP 47) for this specific item. Overrides meta.language */
|
|
380
|
+
lang?: LanguageCode;
|
|
381
|
+
/** Array of text labels (tags) for this item */
|
|
382
|
+
tags?: string[];
|
|
383
|
+
/** Numeric difficulty from 1 (easy) to 5 (hard) */
|
|
384
|
+
difficulty?: number;
|
|
385
|
+
/** Recommended time limit in seconds for answering this item */
|
|
386
|
+
timeLimit?: number;
|
|
387
|
+
/** Array of hints (Rich Content) */
|
|
388
|
+
hints?: string[];
|
|
389
|
+
/** Explanation of the correct answer (Rich Content) */
|
|
390
|
+
explanation?: string;
|
|
391
|
+
/**
|
|
392
|
+
* Message displayed after an incorrect answer (Rich Content).
|
|
393
|
+
* Guidance without revealing the solution.
|
|
394
|
+
*/
|
|
395
|
+
incorrectFeedback?: string;
|
|
396
|
+
/** Array of references to sources related to this item */
|
|
397
|
+
sources?: SourceReference[];
|
|
398
|
+
/** Array of IDs of related items in this set */
|
|
399
|
+
relatedItems?: string[];
|
|
400
|
+
/** Array of IDs of items that should logically precede this item */
|
|
401
|
+
dependencyItems?: string[];
|
|
402
|
+
/** Advanced data for adaptive learning and psychometrics */
|
|
403
|
+
pedagogy?: Pedagogy;
|
|
404
|
+
/** Field for static creator metadata */
|
|
405
|
+
customData?: Record<string, unknown>;
|
|
406
|
+
/** Field for application-specific static metadata */
|
|
407
|
+
appSpecific?: Record<string, unknown>;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Select blank object for fill-in-select items
|
|
411
|
+
*/
|
|
412
|
+
export interface SelectBlankObject {
|
|
413
|
+
/** Array of text options to choose from (Rich Content) */
|
|
414
|
+
options: string[];
|
|
415
|
+
/** Index of correct answer in options array (0-based) */
|
|
416
|
+
correctIndex: number;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Hotspot shape type.
|
|
420
|
+
* `rect`, `circle`, `polygon` are for 2D image hotspots (pin-on-image).
|
|
421
|
+
* `mesh` is for 3D model mesh selection (pin-on-model).
|
|
422
|
+
*/
|
|
423
|
+
export type HotspotShape = 'rect' | 'circle' | 'polygon' | 'mesh';
|
|
424
|
+
/**
|
|
425
|
+
* Base hotspot properties
|
|
426
|
+
*/
|
|
427
|
+
interface BaseHotspot {
|
|
428
|
+
/** Zone shape */
|
|
429
|
+
type: HotspotShape;
|
|
430
|
+
/** Zone label (Plain Text) */
|
|
431
|
+
label?: string;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Rectangle hotspot
|
|
435
|
+
*/
|
|
436
|
+
export interface RectHotspot extends BaseHotspot {
|
|
437
|
+
type: 'rect';
|
|
438
|
+
/** Percent X from left edge (0-100) */
|
|
439
|
+
x: number;
|
|
440
|
+
/** Percent Y from top edge (0-100) */
|
|
441
|
+
y: number;
|
|
442
|
+
/** Width in percentages (0-100) */
|
|
443
|
+
width: number;
|
|
444
|
+
/** Height in percentages (0-100) */
|
|
445
|
+
height: number;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Circle hotspot
|
|
449
|
+
*/
|
|
450
|
+
export interface CircleHotspot extends BaseHotspot {
|
|
451
|
+
type: 'circle';
|
|
452
|
+
/** Center X in percentages (0-100) */
|
|
453
|
+
x: number;
|
|
454
|
+
/** Center Y in percentages (0-100) */
|
|
455
|
+
y: number;
|
|
456
|
+
/** Radius in percentages (0-100) */
|
|
457
|
+
radius: number;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Polygon hotspot
|
|
461
|
+
*/
|
|
462
|
+
export interface PolygonHotspot extends BaseHotspot {
|
|
463
|
+
type: 'polygon';
|
|
464
|
+
/** Array of points defining shape */
|
|
465
|
+
points: Array<{
|
|
466
|
+
x: number;
|
|
467
|
+
y: number;
|
|
468
|
+
}>;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Mesh hotspot for 3D model items (`pin-on-model`).
|
|
472
|
+
* Identifies a named mesh/object in a glTF scene by its node name.
|
|
473
|
+
* Coordinates are not used; selection is by object identity in the 3D scene.
|
|
474
|
+
*/
|
|
475
|
+
export interface MeshHotspot extends BaseHotspot {
|
|
476
|
+
type: 'mesh';
|
|
477
|
+
/**
|
|
478
|
+
* Name (or partial name) of the object/mesh in the 3D scene.
|
|
479
|
+
* Corresponds to the glTF node name in the `.glb` / `.gltf` file.
|
|
480
|
+
* Example: `"Femur"`, `"spine_L3"`
|
|
481
|
+
*/
|
|
482
|
+
targetName: string;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Hotspot object (Discriminated Union).
|
|
486
|
+
* Use `RectHotspot | CircleHotspot | PolygonHotspot` for 2D image hotspots.
|
|
487
|
+
* Use `MeshHotspot` for 3D model mesh selection.
|
|
488
|
+
*/
|
|
489
|
+
export type HotspotObject = RectHotspot | CircleHotspot | PolygonHotspot | MeshHotspot;
|
|
490
|
+
/**
|
|
491
|
+
* A 3D point or vector with X, Y, Z coordinates.
|
|
492
|
+
* Used for camera setup in `pin-on-model` items.
|
|
493
|
+
*/
|
|
494
|
+
export interface Vector3 {
|
|
495
|
+
x: number;
|
|
496
|
+
y: number;
|
|
497
|
+
z: number;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Recommended initial camera configuration for `pin-on-model` items.
|
|
501
|
+
* If omitted, the application SHOULD auto-fit the model in view.
|
|
502
|
+
*/
|
|
503
|
+
export interface CameraSetup {
|
|
504
|
+
/** Camera position in 3D world space */
|
|
505
|
+
position?: Vector3;
|
|
506
|
+
/** The point the camera is looking at and orbiting around */
|
|
507
|
+
target?: Vector3;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Categorize item
|
|
511
|
+
*/
|
|
512
|
+
export interface CategorizeItem {
|
|
513
|
+
/**
|
|
514
|
+
* Unique ID within question (not global UUID).
|
|
515
|
+
* Can be alphanumeric string.
|
|
516
|
+
*/
|
|
517
|
+
id: string;
|
|
518
|
+
/** Item text (Rich Content) */
|
|
519
|
+
text: string;
|
|
520
|
+
/** 0-based index into categories array in parent item */
|
|
521
|
+
correctCategoryIndex: number;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Timeline event precision
|
|
525
|
+
*/
|
|
526
|
+
export type TimelinePrecision = 'year' | 'month' | 'day' | 'datetime';
|
|
527
|
+
/**
|
|
528
|
+
* Timeline event
|
|
529
|
+
*/
|
|
530
|
+
export interface TimelineEvent {
|
|
531
|
+
/**
|
|
532
|
+
* Unique ID within question (not global UUID).
|
|
533
|
+
* Can be alphanumeric string.
|
|
534
|
+
*/
|
|
535
|
+
id: string;
|
|
536
|
+
/** Event description (Rich Content) */
|
|
537
|
+
text: string;
|
|
538
|
+
/**
|
|
539
|
+
* Event date in ISO 8601 format.
|
|
540
|
+
* MUST always contain full date, even if precision is only year or month.
|
|
541
|
+
*/
|
|
542
|
+
date: ISO8601DateTime;
|
|
543
|
+
/** Determines display format to user. Default: use full precision from date */
|
|
544
|
+
precision?: TimelinePrecision;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Diagram label zone (extended hotspot with correctLabelIndex)
|
|
548
|
+
*/
|
|
549
|
+
export type DiagramZone = (RectHotspot | CircleHotspot | PolygonHotspot) & {
|
|
550
|
+
/** Index of correct label from labels array (0-based) */
|
|
551
|
+
correctLabelIndex: number;
|
|
552
|
+
};
|
|
553
|
+
/**
|
|
554
|
+
* Rubric criterion for open-ended questions
|
|
555
|
+
*/
|
|
556
|
+
export interface RubricCriterion {
|
|
557
|
+
/** Criterion name (e.g., "Grammar", "Argumentation") */
|
|
558
|
+
label: string;
|
|
559
|
+
/** Criterion weight in percentages (0-100) */
|
|
560
|
+
percentage: number;
|
|
561
|
+
/** Description of what is evaluated in this criterion */
|
|
562
|
+
description?: string;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Rubric for open-ended questions
|
|
566
|
+
*/
|
|
567
|
+
export interface Rubric {
|
|
568
|
+
/** Array of criteria */
|
|
569
|
+
criteria: RubricCriterion[];
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Range object for numeric-input
|
|
573
|
+
*/
|
|
574
|
+
export interface NumericRange {
|
|
575
|
+
/** Minimum value (inclusive) */
|
|
576
|
+
min: number;
|
|
577
|
+
/** Maximum value (inclusive) */
|
|
578
|
+
max: number;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Note item (Study Note)
|
|
582
|
+
*/
|
|
583
|
+
export interface NoteItem extends BaseItem {
|
|
584
|
+
type: 'note';
|
|
585
|
+
/** Note heading (Plain Text) */
|
|
586
|
+
title?: string;
|
|
587
|
+
/** Main educational content (Rich Content) */
|
|
588
|
+
content: string;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Flashcard item
|
|
592
|
+
*/
|
|
593
|
+
export interface FlashcardItem extends BaseItem {
|
|
594
|
+
type: 'flashcard';
|
|
595
|
+
/** Text on front side (Rich Content) */
|
|
596
|
+
front: string;
|
|
597
|
+
/** Text on back side (Rich Content) */
|
|
598
|
+
back: string;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* True/False item
|
|
602
|
+
*/
|
|
603
|
+
export interface TrueFalseItem extends BaseItem {
|
|
604
|
+
type: 'true-false';
|
|
605
|
+
/** Statement to be evaluated (Rich Content) */
|
|
606
|
+
question: string;
|
|
607
|
+
/** Correct answer */
|
|
608
|
+
answer: boolean;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Multiple Choice Question - Single Choice
|
|
612
|
+
*/
|
|
613
|
+
export interface MCQSingleItem extends BaseItem {
|
|
614
|
+
type: 'mcq-single';
|
|
615
|
+
/** Question text (Rich Content) */
|
|
616
|
+
question: string;
|
|
617
|
+
/** Array of text options (Rich Content). Min 2 items. */
|
|
618
|
+
options: string[];
|
|
619
|
+
/** Index (0-based) of correct answer in options array */
|
|
620
|
+
correctIndex: number;
|
|
621
|
+
/** Whether application should shuffle options. Default: true */
|
|
622
|
+
shuffleOptions?: boolean;
|
|
623
|
+
/**
|
|
624
|
+
* Array of explanations specific to each option (Rich Content).
|
|
625
|
+
* Index corresponds to index in options array.
|
|
626
|
+
* Use null for options without specific explanation.
|
|
627
|
+
*/
|
|
628
|
+
optionExplanations?: Array<string | null>;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Multiple Choice Question - Multiple Choice
|
|
632
|
+
*/
|
|
633
|
+
export interface MCQMultiItem extends BaseItem {
|
|
634
|
+
type: 'mcq-multi';
|
|
635
|
+
/** Question text (Rich Content) */
|
|
636
|
+
question: string;
|
|
637
|
+
/** Array of text options (Rich Content). Min 2 items. */
|
|
638
|
+
options: string[];
|
|
639
|
+
/** Array of indices of correct answers. Min 1 index. */
|
|
640
|
+
correctIndices: number[];
|
|
641
|
+
/** Minimum number of answers user must select */
|
|
642
|
+
minSelections?: number;
|
|
643
|
+
/** Maximum number of answers user can select */
|
|
644
|
+
maxSelections?: number;
|
|
645
|
+
/** Whether application should shuffle options. Default: true */
|
|
646
|
+
shuffleOptions?: boolean;
|
|
647
|
+
/**
|
|
648
|
+
* Array of explanations specific to each option (Rich Content).
|
|
649
|
+
* Index corresponds to index in options array.
|
|
650
|
+
* Use null for options without specific explanation.
|
|
651
|
+
*/
|
|
652
|
+
optionExplanations?: Array<string | null>;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Short Text Answer
|
|
656
|
+
*/
|
|
657
|
+
export interface ShortAnswerItem extends BaseItem {
|
|
658
|
+
type: 'short-answer';
|
|
659
|
+
/** Question text (Rich Content) */
|
|
660
|
+
question: string;
|
|
661
|
+
/** Array of acceptable text answers (Plain Text). Min 1 item. */
|
|
662
|
+
answers: string[];
|
|
663
|
+
/** Distinguish letter case. Default: false */
|
|
664
|
+
caseSensitive?: boolean;
|
|
665
|
+
/** Ignore spaces at beginning/end. Default: true */
|
|
666
|
+
trimWhitespace?: boolean;
|
|
667
|
+
/** Accept approximate match (fuzzy matching). Default: false */
|
|
668
|
+
acceptPartial?: boolean;
|
|
669
|
+
/**
|
|
670
|
+
* Remove diacritics before comparison.
|
|
671
|
+
* Default: false
|
|
672
|
+
*/
|
|
673
|
+
ignoreDiacritics?: boolean;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Fill in the Blanks
|
|
677
|
+
*/
|
|
678
|
+
export interface FillInBlanksItem extends BaseItem {
|
|
679
|
+
type: 'fill-in-blanks';
|
|
680
|
+
/** Heading or instructions (Rich Content) */
|
|
681
|
+
question?: string;
|
|
682
|
+
/**
|
|
683
|
+
* Text with blanks marked using blank tags <blank:token />.
|
|
684
|
+
* Rich Content (Markdown, LaTeX, Media Tags).
|
|
685
|
+
*/
|
|
686
|
+
text: string;
|
|
687
|
+
/**
|
|
688
|
+
* Dictionary where key is token and value is array of correct answers (Plain Text).
|
|
689
|
+
* Min 1 token.
|
|
690
|
+
*/
|
|
691
|
+
blanks: Record<string, string[]>;
|
|
692
|
+
/** Distinguish letter case when checking answers. Default: false */
|
|
693
|
+
caseSensitive?: boolean;
|
|
694
|
+
/** Ignore spaces at beginning and end of answer. Default: true */
|
|
695
|
+
trimWhitespace?: boolean;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Fill in with Selection
|
|
699
|
+
*/
|
|
700
|
+
export interface FillInSelectItem extends BaseItem {
|
|
701
|
+
type: 'fill-in-select';
|
|
702
|
+
/** Heading or instructions (Rich Content) */
|
|
703
|
+
question?: string;
|
|
704
|
+
/**
|
|
705
|
+
* Text with blanks marked using blank tags <blank:token />.
|
|
706
|
+
* Rich Content (Markdown, LaTeX, Media Tags).
|
|
707
|
+
*/
|
|
708
|
+
text: string;
|
|
709
|
+
/**
|
|
710
|
+
* Dictionary where key is token and value is SelectBlankObject.
|
|
711
|
+
* Min 1 token.
|
|
712
|
+
*/
|
|
713
|
+
blanks: Record<string, SelectBlankObject>;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Match Pairs
|
|
717
|
+
*/
|
|
718
|
+
export interface MatchPairsItem extends BaseItem {
|
|
719
|
+
type: 'match-pairs';
|
|
720
|
+
/** Instructions (Rich Content) */
|
|
721
|
+
question?: string;
|
|
722
|
+
/** Left side (what is being matched) - array of strings (Rich Content). Min 2 items. */
|
|
723
|
+
prompts: string[];
|
|
724
|
+
/**
|
|
725
|
+
* Right side (target) - array of strings (Rich Content).
|
|
726
|
+
* Must have same length as prompts. Min 2 items.
|
|
727
|
+
* prompts[0] belongs to matches[0], etc.
|
|
728
|
+
*/
|
|
729
|
+
matches: string[];
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Advanced Matching
|
|
733
|
+
*/
|
|
734
|
+
export interface MatchComplexItem extends BaseItem {
|
|
735
|
+
type: 'match-complex';
|
|
736
|
+
/** Instructions (Rich Content) */
|
|
737
|
+
question?: string;
|
|
738
|
+
/** Items on left side (Rich Content). Min 1 item. */
|
|
739
|
+
leftItems: string[];
|
|
740
|
+
/** Items on right side (Rich Content). Min 1 item. */
|
|
741
|
+
rightItems: string[];
|
|
742
|
+
/**
|
|
743
|
+
* Array of index pairs [left_index, right_index] defining correct pairs.
|
|
744
|
+
* Min 1 connection.
|
|
745
|
+
*/
|
|
746
|
+
connections: Array<[number, number]>;
|
|
747
|
+
/** Minimum number of correct connections required for success */
|
|
748
|
+
minCorrect?: number;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Sorting
|
|
752
|
+
*/
|
|
753
|
+
export interface SortItemsItem extends BaseItem {
|
|
754
|
+
type: 'sort-items';
|
|
755
|
+
/** Instructions (Rich Content) */
|
|
756
|
+
question: string;
|
|
757
|
+
/**
|
|
758
|
+
* Array of items in correct order (Rich Content).
|
|
759
|
+
* Min 2 items. Application must shuffle them.
|
|
760
|
+
*/
|
|
761
|
+
items: string[];
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Slider / Numeric Answer
|
|
765
|
+
*/
|
|
766
|
+
export interface SliderItem extends BaseItem {
|
|
767
|
+
type: 'slider';
|
|
768
|
+
/** Question text (Rich Content) */
|
|
769
|
+
question: string;
|
|
770
|
+
/** Minimum value on slider */
|
|
771
|
+
min: number;
|
|
772
|
+
/** Maximum value on slider */
|
|
773
|
+
max: number;
|
|
774
|
+
/** Slider step (e.g., 1, 0.1). Must be > 0 */
|
|
775
|
+
step: number;
|
|
776
|
+
/** Correct value. Must be reachable by slider. */
|
|
777
|
+
correctAnswer: number;
|
|
778
|
+
/** Allowed deviation (can be 0) */
|
|
779
|
+
tolerance: number;
|
|
780
|
+
/** Unit (e.g., "year", "m", "°C") (Plain Text) */
|
|
781
|
+
unit?: string;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Pin on Image
|
|
785
|
+
*/
|
|
786
|
+
export interface PinOnImageItem extends BaseItem {
|
|
787
|
+
type: 'pin-on-image';
|
|
788
|
+
/** Instructions (Rich Content) */
|
|
789
|
+
question: string;
|
|
790
|
+
/** Key from assets, which determines the image to be clicked on */
|
|
791
|
+
targetAsset: string;
|
|
792
|
+
/** Array defining correct areas. Min 1 hotspot. */
|
|
793
|
+
hotspots: HotspotObject[];
|
|
794
|
+
/** Whether user must mark more than one hotspot. Default: false */
|
|
795
|
+
multipleCorrect?: boolean;
|
|
796
|
+
/**
|
|
797
|
+
* If multipleCorrect: true, determines minimum number of correct hotspots.
|
|
798
|
+
* Ignored if multipleCorrect: false
|
|
799
|
+
*/
|
|
800
|
+
minCorrect?: number;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Categorization
|
|
804
|
+
*/
|
|
805
|
+
export interface CategorizeItemType extends BaseItem {
|
|
806
|
+
type: 'categorize';
|
|
807
|
+
/** Instructions (Rich Content) */
|
|
808
|
+
question: string;
|
|
809
|
+
/** Array of category names (Plain Text). Min 2 items. */
|
|
810
|
+
categories: string[];
|
|
811
|
+
/** Items to sort. Min 1 item. */
|
|
812
|
+
items: CategorizeItem[];
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Timeline
|
|
816
|
+
*/
|
|
817
|
+
export interface TimelineItem extends BaseItem {
|
|
818
|
+
type: 'timeline';
|
|
819
|
+
/** Instructions (Rich Content) */
|
|
820
|
+
question: string;
|
|
821
|
+
/** Array of events in correct chronological order. Min 2 events. */
|
|
822
|
+
events: TimelineEvent[];
|
|
823
|
+
/** Whether application should shuffle events. Default: true */
|
|
824
|
+
randomize?: boolean;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Matrix / Table Answer
|
|
828
|
+
*/
|
|
829
|
+
export interface MatrixItem extends BaseItem {
|
|
830
|
+
type: 'matrix';
|
|
831
|
+
/** Instructions (Rich Content) */
|
|
832
|
+
question: string;
|
|
833
|
+
/** Row labels (Plain Text). Min 1 item. */
|
|
834
|
+
rows: string[];
|
|
835
|
+
/** Column labels (Plain Text). Min 1 item. */
|
|
836
|
+
columns: string[];
|
|
837
|
+
/**
|
|
838
|
+
* Array of coordinates of correct answers in format [row, column].
|
|
839
|
+
* Indices are 0-based. Min 1 cell.
|
|
840
|
+
*/
|
|
841
|
+
correctCells: Array<[number, number]>;
|
|
842
|
+
/** Can user select multiple answers in one row? Default: false */
|
|
843
|
+
multiplePerRow?: boolean;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Math Input
|
|
847
|
+
*/
|
|
848
|
+
export interface MathInputItem extends BaseItem {
|
|
849
|
+
type: 'math-input';
|
|
850
|
+
/** Question text (Rich Content) */
|
|
851
|
+
question: string;
|
|
852
|
+
/** Correct answer in LaTeX format (e.g., "$2x + 2$") */
|
|
853
|
+
correctAnswer: string;
|
|
854
|
+
/** Array of other text strings that should also be considered correct */
|
|
855
|
+
alternativeAnswers?: string[];
|
|
856
|
+
/** For purely numeric answers, allowed numeric deviation */
|
|
857
|
+
tolerance?: number;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Diagram Labeling
|
|
861
|
+
*/
|
|
862
|
+
export interface DiagramLabelItem extends BaseItem {
|
|
863
|
+
type: 'diagram-label';
|
|
864
|
+
/** Instructions (Rich Content) */
|
|
865
|
+
question: string;
|
|
866
|
+
/** Key from assets, which determines the diagram image */
|
|
867
|
+
targetAsset: string;
|
|
868
|
+
/**
|
|
869
|
+
* Array of text labels for user to assign (Rich Content).
|
|
870
|
+
* Must contain all correct answers and may contain distractors.
|
|
871
|
+
*/
|
|
872
|
+
labels: string[];
|
|
873
|
+
/** Distinguish letter case when comparing labels. Default: false */
|
|
874
|
+
caseSensitive?: boolean;
|
|
875
|
+
/**
|
|
876
|
+
* If true, application MUST render text fields instead of draggable labels.
|
|
877
|
+
* Default: false
|
|
878
|
+
*/
|
|
879
|
+
requireTyping?: boolean;
|
|
880
|
+
/** Array of zones on image. Min 1 zone. */
|
|
881
|
+
zones: DiagramZone[];
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Open-Ended Answer
|
|
885
|
+
*/
|
|
886
|
+
export interface OpenEndedItem extends BaseItem {
|
|
887
|
+
type: 'open-ended';
|
|
888
|
+
/** Question text (Rich Content) */
|
|
889
|
+
question: string;
|
|
890
|
+
/** Minimum required word count */
|
|
891
|
+
minWords?: number;
|
|
892
|
+
/** Maximum allowed word count */
|
|
893
|
+
maxWords?: number;
|
|
894
|
+
/** Sample (model) answer for evaluators (Rich Content) */
|
|
895
|
+
sampleAnswer?: string;
|
|
896
|
+
/** Structured evaluation criteria */
|
|
897
|
+
rubric?: Rubric;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Numeric Input
|
|
901
|
+
*/
|
|
902
|
+
export interface NumericInputItem extends BaseItem {
|
|
903
|
+
type: 'numeric-input';
|
|
904
|
+
/** Question text (Rich Content) */
|
|
905
|
+
question: string;
|
|
906
|
+
/** Correct numeric value (float) */
|
|
907
|
+
value: number;
|
|
908
|
+
/** Absolute allowed deviation. Default: 0 */
|
|
909
|
+
tolerance?: number;
|
|
910
|
+
/**
|
|
911
|
+
* Alternative to value+tolerance.
|
|
912
|
+
* If specified, takes precedence over value.
|
|
913
|
+
*/
|
|
914
|
+
range?: NumericRange;
|
|
915
|
+
/** Unit displayed after input field (Plain Text) */
|
|
916
|
+
unit?: string;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Pin on 3D Model
|
|
920
|
+
*
|
|
921
|
+
* The user must locate and click on a specific named part (mesh) of a 3D model.
|
|
922
|
+
* Requires the `capabilities.assets.model` to be declared in the application manifest.
|
|
923
|
+
*
|
|
924
|
+
* @see §6.21
|
|
925
|
+
*/
|
|
926
|
+
export interface PinOnModelItem extends BaseItem {
|
|
927
|
+
type: 'pin-on-model';
|
|
928
|
+
/** Instructions (Rich Content) */
|
|
929
|
+
question: string;
|
|
930
|
+
/**
|
|
931
|
+
* Key from `assets` dictionary pointing to the 3D model.
|
|
932
|
+
* The referenced asset MUST have `type: "model"` and use glTF/GLB format.
|
|
933
|
+
* Example key: `"model_lower_limb"`
|
|
934
|
+
*/
|
|
935
|
+
targetAsset: string;
|
|
936
|
+
/**
|
|
937
|
+
* Array of correct mesh hotspots. At least 1 hotspot is required.
|
|
938
|
+
* For 3D models, use `MeshHotspot` (`type: "mesh"`).
|
|
939
|
+
*/
|
|
940
|
+
hotspots: MeshHotspot[];
|
|
941
|
+
/**
|
|
942
|
+
* Whether the user must find more than one target mesh.
|
|
943
|
+
* Default: `false`
|
|
944
|
+
*/
|
|
945
|
+
multipleCorrect?: boolean;
|
|
946
|
+
/**
|
|
947
|
+
* Minimum number of correct meshes the user must find.
|
|
948
|
+
* Only applies when `multipleCorrect: true`.
|
|
949
|
+
*/
|
|
950
|
+
minCorrect?: number;
|
|
951
|
+
/**
|
|
952
|
+
* Recommended initial camera setup.
|
|
953
|
+
* If omitted, the application SHOULD auto-fit the entire model in view.
|
|
954
|
+
*/
|
|
955
|
+
camera?: CameraSetup;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Chess Puzzle
|
|
959
|
+
*
|
|
960
|
+
* An interactive chess puzzle where the user finds the correct move sequence
|
|
961
|
+
* on a given board position. Applications SHOULD render an interactive chessboard.
|
|
962
|
+
*
|
|
963
|
+
* @see §6.22
|
|
964
|
+
*/
|
|
965
|
+
export interface ChessPuzzleItem extends BaseItem {
|
|
966
|
+
type: 'chess-puzzle';
|
|
967
|
+
/**
|
|
968
|
+
* Instructions or puzzle description (Rich Content).
|
|
969
|
+
* Example: `"White to move and checkmate in 2."`
|
|
970
|
+
*/
|
|
971
|
+
question: string;
|
|
972
|
+
/**
|
|
973
|
+
* Board position in Forsyth–Edwards Notation (FEN).
|
|
974
|
+
* Example: `"r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4"`
|
|
975
|
+
*/
|
|
976
|
+
fen: string;
|
|
977
|
+
/**
|
|
978
|
+
* Array of valid solution move sequences.
|
|
979
|
+
* Each sequence is an array of moves in Standard Algebraic Notation (SAN).
|
|
980
|
+
* Multiple sequences allow declaring alternative correct solutions.
|
|
981
|
+
* Example: `[["Ng5"]]` or `[["e4", "e5"]]`
|
|
982
|
+
*/
|
|
983
|
+
answers: string[][];
|
|
984
|
+
/**
|
|
985
|
+
* ELO difficulty rating of the puzzle.
|
|
986
|
+
* Optional. Allows adaptive selection of puzzles by difficulty.
|
|
987
|
+
*/
|
|
988
|
+
elo?: number;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* OQSE Item (Discriminated Union)
|
|
992
|
+
*
|
|
993
|
+
* This is the power of TypeScript's Discriminated Unions:
|
|
994
|
+
* TypeScript will narrow the type based on the `type` field,
|
|
995
|
+
* making invalid states unrepresentable.
|
|
996
|
+
*/
|
|
997
|
+
export type OQSEItem = NoteItem | FlashcardItem | TrueFalseItem | MCQSingleItem | MCQMultiItem | ShortAnswerItem | FillInBlanksItem | FillInSelectItem | MatchPairsItem | MatchComplexItem | SortItemsItem | SliderItem | PinOnImageItem | PinOnModelItem | CategorizeItemType | TimelineItem | MatrixItem | MathInputItem | DiagramLabelItem | OpenEndedItem | NumericInputItem | ChessPuzzleItem;
|
|
998
|
+
/**
|
|
999
|
+
* OQSE File (Root Structure)
|
|
1000
|
+
*
|
|
1001
|
+
* This is the top-level structure of an OQSE file.
|
|
1002
|
+
*/
|
|
1003
|
+
export interface OQSEFile {
|
|
1004
|
+
/** URL reference to the JSON Schema specification */
|
|
1005
|
+
$schema?: string;
|
|
1006
|
+
/** Version of the OQSE specification (e.g., "0.1") */
|
|
1007
|
+
version: string;
|
|
1008
|
+
/** Metadata about the entire set */
|
|
1009
|
+
meta: OQSEMeta;
|
|
1010
|
+
/** Array containing individual study items */
|
|
1011
|
+
items: OQSEItem[];
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Type guard for NoteItem
|
|
1015
|
+
*/
|
|
1016
|
+
export declare function isNote(item: OQSEItem): item is NoteItem;
|
|
1017
|
+
/**
|
|
1018
|
+
* Type guard for FlashcardItem
|
|
1019
|
+
*/
|
|
1020
|
+
export declare function isFlashcard(item: OQSEItem): item is FlashcardItem;
|
|
1021
|
+
/**
|
|
1022
|
+
* Type guard for TrueFalseItem
|
|
1023
|
+
*/
|
|
1024
|
+
export declare function isTrueFalse(item: OQSEItem): item is TrueFalseItem;
|
|
1025
|
+
/**
|
|
1026
|
+
* Type guard for MCQSingleItem
|
|
1027
|
+
*/
|
|
1028
|
+
export declare function isMCQSingle(item: OQSEItem): item is MCQSingleItem;
|
|
1029
|
+
/**
|
|
1030
|
+
* Type guard for MCQMultiItem
|
|
1031
|
+
*/
|
|
1032
|
+
export declare function isMCQMulti(item: OQSEItem): item is MCQMultiItem;
|
|
1033
|
+
/**
|
|
1034
|
+
* Type guard for ShortAnswerItem
|
|
1035
|
+
*/
|
|
1036
|
+
export declare function isShortAnswer(item: OQSEItem): item is ShortAnswerItem;
|
|
1037
|
+
/**
|
|
1038
|
+
* Type guard for FillInBlanksItem
|
|
1039
|
+
*/
|
|
1040
|
+
export declare function isFillInBlanks(item: OQSEItem): item is FillInBlanksItem;
|
|
1041
|
+
/**
|
|
1042
|
+
* Type guard for FillInSelectItem
|
|
1043
|
+
*/
|
|
1044
|
+
export declare function isFillInSelect(item: OQSEItem): item is FillInSelectItem;
|
|
1045
|
+
/**
|
|
1046
|
+
* Type guard for MatchPairsItem
|
|
1047
|
+
*/
|
|
1048
|
+
export declare function isMatchPairs(item: OQSEItem): item is MatchPairsItem;
|
|
1049
|
+
/**
|
|
1050
|
+
* Type guard for MatchComplexItem
|
|
1051
|
+
*/
|
|
1052
|
+
export declare function isMatchComplex(item: OQSEItem): item is MatchComplexItem;
|
|
1053
|
+
/**
|
|
1054
|
+
* Type guard for SortItemsItem
|
|
1055
|
+
*/
|
|
1056
|
+
export declare function isSortItems(item: OQSEItem): item is SortItemsItem;
|
|
1057
|
+
/**
|
|
1058
|
+
* Type guard for SliderItem
|
|
1059
|
+
*/
|
|
1060
|
+
export declare function isSlider(item: OQSEItem): item is SliderItem;
|
|
1061
|
+
/**
|
|
1062
|
+
* Type guard for PinOnImageItem
|
|
1063
|
+
*/
|
|
1064
|
+
export declare function isPinOnImage(item: OQSEItem): item is PinOnImageItem;
|
|
1065
|
+
/**
|
|
1066
|
+
* Type guard for CategorizeItemType
|
|
1067
|
+
*/
|
|
1068
|
+
export declare function isCategorize(item: OQSEItem): item is CategorizeItemType;
|
|
1069
|
+
/**
|
|
1070
|
+
* Type guard for TimelineItem
|
|
1071
|
+
*/
|
|
1072
|
+
export declare function isTimeline(item: OQSEItem): item is TimelineItem;
|
|
1073
|
+
/**
|
|
1074
|
+
* Type guard for MatrixItem
|
|
1075
|
+
*/
|
|
1076
|
+
export declare function isMatrix(item: OQSEItem): item is MatrixItem;
|
|
1077
|
+
/**
|
|
1078
|
+
* Type guard for MathInputItem
|
|
1079
|
+
*/
|
|
1080
|
+
export declare function isMathInput(item: OQSEItem): item is MathInputItem;
|
|
1081
|
+
/**
|
|
1082
|
+
* Type guard for DiagramLabelItem
|
|
1083
|
+
*/
|
|
1084
|
+
export declare function isDiagramLabel(item: OQSEItem): item is DiagramLabelItem;
|
|
1085
|
+
/**
|
|
1086
|
+
* Type guard for OpenEndedItem
|
|
1087
|
+
*/
|
|
1088
|
+
export declare function isOpenEnded(item: OQSEItem): item is OpenEndedItem;
|
|
1089
|
+
/**
|
|
1090
|
+
* Type guard for NumericInputItem
|
|
1091
|
+
*/
|
|
1092
|
+
export declare function isNumericInput(item: OQSEItem): item is NumericInputItem;
|
|
1093
|
+
/**
|
|
1094
|
+
* Type guard for PinOnModelItem
|
|
1095
|
+
*/
|
|
1096
|
+
export declare function isPinOnModel(item: OQSEItem): item is PinOnModelItem;
|
|
1097
|
+
/**
|
|
1098
|
+
* Type guard for ChessPuzzleItem
|
|
1099
|
+
*/
|
|
1100
|
+
export declare function isChessPuzzle(item: OQSEItem): item is ChessPuzzleItem;
|
|
1101
|
+
/**
|
|
1102
|
+
* Core Item Types (Level 1: Core Consumer)
|
|
1103
|
+
*
|
|
1104
|
+
* These are the absolute minimum types that every implementation must support.
|
|
1105
|
+
*/
|
|
1106
|
+
export type CoreItemType = 'note' | 'flashcard' | 'mcq-single' | 'short-answer';
|
|
1107
|
+
/**
|
|
1108
|
+
* Extended Item Types
|
|
1109
|
+
*
|
|
1110
|
+
* All other types beyond the core.
|
|
1111
|
+
*/
|
|
1112
|
+
export type ExtendedItemType = Exclude<OQSEItem['type'], CoreItemType>;
|
|
1113
|
+
/**
|
|
1114
|
+
* Type guard for Core Item
|
|
1115
|
+
*/
|
|
1116
|
+
export declare function isCoreItem(item: OQSEItem): boolean;
|
|
1117
|
+
/**
|
|
1118
|
+
* Type guard for Extended Item
|
|
1119
|
+
*/
|
|
1120
|
+
export declare function isExtendedItem(item: OQSEItem): boolean;
|
|
1121
|
+
export {};
|