@kensai/quiz-core 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 +99 -0
- package/dist/evaluate.d.ts +47 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +149 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/events.d.ts +10 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +26 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/load.d.ts +22 -0
- package/dist/load.d.ts.map +1 -0
- package/dist/load.js +31 -0
- package/dist/load.js.map +1 -0
- package/dist/resolve.d.ts +9 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +22 -0
- package/dist/resolve.js.map +1 -0
- package/dist/runtime.d.ts +118 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +245 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema.generated.d.ts +503 -0
- package/dist/schema.generated.d.ts.map +1 -0
- package/dist/schema.generated.js +626 -0
- package/dist/schema.generated.js.map +1 -0
- package/dist/settings.d.ts +19 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +32 -0
- package/dist/settings.js.map +1 -0
- package/dist/stats.d.ts +33 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +79 -0
- package/dist/stats.js.map +1 -0
- package/dist/text.d.ts +5 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +11 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +28 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +170 -0
- package/dist/validate.js.map +1 -0
- package/package.json +60 -0
package/dist/settings.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Defaults applied when `settings` (or a given field) is omitted. */
|
|
2
|
+
export const DEFAULT_SETTINGS = {
|
|
3
|
+
navigation: "all",
|
|
4
|
+
order: "fixed",
|
|
5
|
+
feedback: "on_finish",
|
|
6
|
+
time_limit: null,
|
|
7
|
+
shuffle_options: false,
|
|
8
|
+
passing_score: null,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Merge authored settings and optional per-attempt overrides over the documented
|
|
12
|
+
* defaults. `undefined` fields are ignored (so partial overrides are safe);
|
|
13
|
+
* explicit `null` is kept (e.g. to clear a time limit).
|
|
14
|
+
*/
|
|
15
|
+
export function resolveSettings(authored, overrides) {
|
|
16
|
+
return {
|
|
17
|
+
...DEFAULT_SETTINGS,
|
|
18
|
+
...omitUndefined(authored),
|
|
19
|
+
...omitUndefined(overrides),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function omitUndefined(obj) {
|
|
23
|
+
if (!obj)
|
|
24
|
+
return {};
|
|
25
|
+
const out = {};
|
|
26
|
+
for (const key of Object.keys(obj)) {
|
|
27
|
+
if (obj[key] !== undefined)
|
|
28
|
+
out[key] = obj[key];
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAiBA,sEAAsE;AACtE,MAAM,CAAC,MAAM,gBAAgB,GAAqB;IAChD,UAAU,EAAE,KAAK;IACjB,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,WAAW;IACrB,UAAU,EAAE,IAAI;IAChB,eAAe,EAAE,KAAK;IACtB,aAAa,EAAE,IAAI;CACpB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAuB,EACvB,SAAqC;IAErC,OAAO;QACL,GAAG,gBAAgB;QACnB,GAAG,aAAa,CAAC,QAAQ,CAAC;QAC1B,GAAG,aAAa,CAAC,SAAS,CAAC;KAC5B,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAmB,GAAO;IAC9C,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAgB,EAAE,CAAC;QAClD,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/stats.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Quiz } from "./types.js";
|
|
2
|
+
import type { QuestionResult } from "./evaluate.js";
|
|
3
|
+
export interface CategoryStat {
|
|
4
|
+
/** Declared/free-form category id, or `null` for the uncategorized bucket. */
|
|
5
|
+
categoryId: string | null;
|
|
6
|
+
/** Human label from the categories registry, when available. */
|
|
7
|
+
label?: string;
|
|
8
|
+
total: number;
|
|
9
|
+
answered: number;
|
|
10
|
+
correct: number;
|
|
11
|
+
incorrect: number;
|
|
12
|
+
score: number;
|
|
13
|
+
maxScore: number;
|
|
14
|
+
/** correct / total (0 when the bucket is empty). */
|
|
15
|
+
accuracy: number;
|
|
16
|
+
}
|
|
17
|
+
export interface OverallStat {
|
|
18
|
+
total: number;
|
|
19
|
+
answered: number;
|
|
20
|
+
correct: number;
|
|
21
|
+
incorrect: number;
|
|
22
|
+
score: number;
|
|
23
|
+
maxScore: number;
|
|
24
|
+
accuracy: number;
|
|
25
|
+
}
|
|
26
|
+
export interface QuizStats {
|
|
27
|
+
overall: OverallStat;
|
|
28
|
+
/** Declared categories first (in declaration order), then any free-form, uncategorized last. */
|
|
29
|
+
byCategory: CategoryStat[];
|
|
30
|
+
}
|
|
31
|
+
/** Aggregate per-question results into overall and per-category statistics. */
|
|
32
|
+
export declare function computeStats(quiz: Quiz, results: QuestionResult[]): QuizStats;
|
|
33
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,WAAW,CAAC;IACrB,gGAAgG;IAChG,UAAU,EAAE,YAAY,EAAE,CAAC;CAC5B;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,SAAS,CA8E7E"}
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Aggregate per-question results into overall and per-category statistics. */
|
|
2
|
+
export function computeStats(quiz, results) {
|
|
3
|
+
const labels = new Map();
|
|
4
|
+
for (const category of quiz.categories ?? [])
|
|
5
|
+
labels.set(category.id, category.label);
|
|
6
|
+
const buckets = new Map();
|
|
7
|
+
const bucketFor = (key) => {
|
|
8
|
+
let bucket = buckets.get(key);
|
|
9
|
+
if (!bucket) {
|
|
10
|
+
bucket = {
|
|
11
|
+
categoryId: key,
|
|
12
|
+
label: key !== null ? labels.get(key) : undefined,
|
|
13
|
+
total: 0,
|
|
14
|
+
answered: 0,
|
|
15
|
+
correct: 0,
|
|
16
|
+
incorrect: 0,
|
|
17
|
+
score: 0,
|
|
18
|
+
maxScore: 0,
|
|
19
|
+
accuracy: 0,
|
|
20
|
+
};
|
|
21
|
+
buckets.set(key, bucket);
|
|
22
|
+
}
|
|
23
|
+
return bucket;
|
|
24
|
+
};
|
|
25
|
+
const overall = {
|
|
26
|
+
total: 0,
|
|
27
|
+
answered: 0,
|
|
28
|
+
correct: 0,
|
|
29
|
+
incorrect: 0,
|
|
30
|
+
score: 0,
|
|
31
|
+
maxScore: 0,
|
|
32
|
+
accuracy: 0,
|
|
33
|
+
};
|
|
34
|
+
for (const result of results) {
|
|
35
|
+
const bucket = bucketFor(result.category ?? null);
|
|
36
|
+
bucket.total++;
|
|
37
|
+
overall.total++;
|
|
38
|
+
if (result.answered) {
|
|
39
|
+
bucket.answered++;
|
|
40
|
+
overall.answered++;
|
|
41
|
+
}
|
|
42
|
+
if (result.correct) {
|
|
43
|
+
bucket.correct++;
|
|
44
|
+
overall.correct++;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
bucket.incorrect++;
|
|
48
|
+
overall.incorrect++;
|
|
49
|
+
}
|
|
50
|
+
bucket.score += result.score;
|
|
51
|
+
overall.score += result.score;
|
|
52
|
+
bucket.maxScore += result.maxScore;
|
|
53
|
+
overall.maxScore += result.maxScore;
|
|
54
|
+
}
|
|
55
|
+
for (const bucket of buckets.values()) {
|
|
56
|
+
bucket.accuracy = bucket.total > 0 ? bucket.correct / bucket.total : 0;
|
|
57
|
+
}
|
|
58
|
+
overall.accuracy = overall.total > 0 ? overall.correct / overall.total : 0;
|
|
59
|
+
const byCategory = [];
|
|
60
|
+
// Declared categories first, in declaration order.
|
|
61
|
+
for (const category of quiz.categories ?? []) {
|
|
62
|
+
const bucket = buckets.get(category.id);
|
|
63
|
+
if (bucket) {
|
|
64
|
+
byCategory.push(bucket);
|
|
65
|
+
buckets.delete(category.id);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Any remaining free-form (non-null) categories.
|
|
69
|
+
for (const [key, bucket] of buckets) {
|
|
70
|
+
if (key !== null)
|
|
71
|
+
byCategory.push(bucket);
|
|
72
|
+
}
|
|
73
|
+
// Uncategorized bucket last.
|
|
74
|
+
const uncategorized = buckets.get(null);
|
|
75
|
+
if (uncategorized)
|
|
76
|
+
byCategory.push(uncategorized);
|
|
77
|
+
return { overall, byCategory };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"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"}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Normalize free-text for comparison: trim, collapse whitespace, optional lowercase. */
|
|
2
|
+
export declare function normalizeText(value: string, caseSensitive?: boolean): string;
|
|
3
|
+
/** True if `given` matches any accepted answer under the same normalization. */
|
|
4
|
+
export declare function textMatches(given: string, accept: readonly string[], caseSensitive?: boolean): boolean;
|
|
5
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,UAAQ,GAAG,MAAM,CAG1E;AAED,gFAAgF;AAChF,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,aAAa,UAAQ,GACpB,OAAO,CAGT"}
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Normalize free-text for comparison: trim, collapse whitespace, optional lowercase. */
|
|
2
|
+
export function normalizeText(value, caseSensitive = false) {
|
|
3
|
+
const trimmed = value.trim().replace(/\s+/g, " ");
|
|
4
|
+
return caseSensitive ? trimmed : trimmed.toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
/** True if `given` matches any accepted answer under the same normalization. */
|
|
7
|
+
export function textMatches(given, accept, caseSensitive = false) {
|
|
8
|
+
const normalized = normalizeText(given, caseSensitive);
|
|
9
|
+
return accept.some((a) => normalizeText(a, caseSensitive) === normalized);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,aAAa,GAAG,KAAK;IAChE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;AACzD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,WAAW,CACzB,KAAa,EACb,MAAyB,EACzB,aAAa,GAAG,KAAK;IAErB,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IACvD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,aAAa,CAAC,KAAK,UAAU,CAAC,CAAC;AAC5E,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Kensai Quiz format (schema version 0.1).
|
|
3
|
+
*
|
|
4
|
+
* These types mirror `spec/quiz.schema.json`, which remains the authoritative
|
|
5
|
+
* definition used for runtime validation. When the schema changes, keep these
|
|
6
|
+
* types in sync (and re-run `pnpm sync:schema`).
|
|
7
|
+
*/
|
|
8
|
+
export type SchemaVersion = "0.1";
|
|
9
|
+
export type QuestionType = "choice" | "true_false" | "fill_blank" | "classify" | "matching" | "ordering" | "short_answer";
|
|
10
|
+
export interface QuizMetadata {
|
|
11
|
+
id?: string;
|
|
12
|
+
title: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
language?: string;
|
|
16
|
+
author?: string;
|
|
17
|
+
tags?: string[];
|
|
18
|
+
}
|
|
19
|
+
export type NavigationMode = "all" | "sequential";
|
|
20
|
+
export type OrderMode = "fixed" | "random";
|
|
21
|
+
export type FeedbackMode = "immediate" | "on_finish";
|
|
22
|
+
export interface QuizSettings {
|
|
23
|
+
navigation?: NavigationMode;
|
|
24
|
+
order?: OrderMode;
|
|
25
|
+
feedback?: FeedbackMode;
|
|
26
|
+
/** Seconds allowed for the whole attempt. `null`/omitted = unlimited. */
|
|
27
|
+
time_limit?: number | null;
|
|
28
|
+
shuffle_options?: boolean;
|
|
29
|
+
/** Pass threshold as a fraction 0..1. */
|
|
30
|
+
passing_score?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface Category {
|
|
33
|
+
id: string;
|
|
34
|
+
label: string;
|
|
35
|
+
}
|
|
36
|
+
export interface Option {
|
|
37
|
+
id: string;
|
|
38
|
+
text: string;
|
|
39
|
+
}
|
|
40
|
+
export interface Group {
|
|
41
|
+
id: string;
|
|
42
|
+
label: string;
|
|
43
|
+
}
|
|
44
|
+
export interface BaseQuestion {
|
|
45
|
+
id?: string;
|
|
46
|
+
category?: string;
|
|
47
|
+
points?: number;
|
|
48
|
+
prompt: string;
|
|
49
|
+
explanation?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface ChoiceQuestion extends BaseQuestion {
|
|
52
|
+
type: "choice";
|
|
53
|
+
select?: "single" | "multiple";
|
|
54
|
+
options?: Option[];
|
|
55
|
+
/** Name of an entry in `option_groups`. Mutually exclusive with `options`. */
|
|
56
|
+
options_from?: string;
|
|
57
|
+
answer: string | string[];
|
|
58
|
+
/**
|
|
59
|
+
* Whether these options may be reordered when the quiz enables option shuffling.
|
|
60
|
+
* Default `true`; set `false` to lock the order (e.g. an "All of the above" option
|
|
61
|
+
* or options that reference position like "Both A and B").
|
|
62
|
+
*/
|
|
63
|
+
shuffle_options?: boolean;
|
|
64
|
+
}
|
|
65
|
+
export interface TrueFalseQuestion extends BaseQuestion {
|
|
66
|
+
type: "true_false";
|
|
67
|
+
answer: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** A blank is either free-text (`accept`) or a per-blank choice (`options` + `answer`). */
|
|
70
|
+
export interface Blank {
|
|
71
|
+
accept?: string[];
|
|
72
|
+
case_sensitive?: boolean;
|
|
73
|
+
options?: Option[];
|
|
74
|
+
answer?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface FillBlankQuestion extends BaseQuestion {
|
|
77
|
+
type: "fill_blank";
|
|
78
|
+
/** Map of placeholder id ({{id}} in prompt) to a blank definition. */
|
|
79
|
+
blanks: Record<string, Blank>;
|
|
80
|
+
}
|
|
81
|
+
export interface ClassifyItem {
|
|
82
|
+
id: string;
|
|
83
|
+
text: string;
|
|
84
|
+
/** Target group id. */
|
|
85
|
+
answer: string;
|
|
86
|
+
}
|
|
87
|
+
export interface ClassifyQuestion extends BaseQuestion {
|
|
88
|
+
type: "classify";
|
|
89
|
+
groups?: Group[];
|
|
90
|
+
/** Name of an entry in `option_groups` used as buckets. Mutually exclusive with `groups`. */
|
|
91
|
+
groups_from?: string;
|
|
92
|
+
items: ClassifyItem[];
|
|
93
|
+
}
|
|
94
|
+
export interface MatchingQuestion extends BaseQuestion {
|
|
95
|
+
type: "matching";
|
|
96
|
+
left: Option[];
|
|
97
|
+
right: Option[];
|
|
98
|
+
/** Map of left id -> right id. */
|
|
99
|
+
answer: Record<string, string>;
|
|
100
|
+
}
|
|
101
|
+
export interface OrderingQuestion extends BaseQuestion {
|
|
102
|
+
type: "ordering";
|
|
103
|
+
items: Option[];
|
|
104
|
+
/** Item ids in the correct order. */
|
|
105
|
+
answer: string[];
|
|
106
|
+
}
|
|
107
|
+
export interface ShortAnswerQuestion extends BaseQuestion {
|
|
108
|
+
type: "short_answer";
|
|
109
|
+
accept: string[];
|
|
110
|
+
case_sensitive?: boolean;
|
|
111
|
+
}
|
|
112
|
+
export type Question = ChoiceQuestion | TrueFalseQuestion | FillBlankQuestion | ClassifyQuestion | MatchingQuestion | OrderingQuestion | ShortAnswerQuestion;
|
|
113
|
+
export interface Quiz {
|
|
114
|
+
schema_version: SchemaVersion;
|
|
115
|
+
metadata: QuizMetadata;
|
|
116
|
+
settings?: QuizSettings;
|
|
117
|
+
/** Registry of categories used for end-of-quiz statistics. */
|
|
118
|
+
categories?: Category[];
|
|
119
|
+
/** Reusable named option sets, referenced via `options_from` / `groups_from`. */
|
|
120
|
+
option_groups?: Record<string, Option[]>;
|
|
121
|
+
questions: Question[];
|
|
122
|
+
}
|
|
123
|
+
/** One option id (single-select) or a list of ids (multiple-select). */
|
|
124
|
+
export type ChoiceAnswer = string | string[];
|
|
125
|
+
export type TrueFalseAnswer = boolean;
|
|
126
|
+
/** Map of blank id -> submitted text (free-text) or selected option id. */
|
|
127
|
+
export type FillBlankAnswer = Record<string, string>;
|
|
128
|
+
/** Map of item id -> chosen group id. */
|
|
129
|
+
export type ClassifyAnswer = Record<string, string>;
|
|
130
|
+
/** Map of left id -> chosen right id. */
|
|
131
|
+
export type MatchingAnswer = Record<string, string>;
|
|
132
|
+
/** Item ids in the taker's chosen order. */
|
|
133
|
+
export type OrderingAnswer = string[];
|
|
134
|
+
export type ShortTextAnswer = string;
|
|
135
|
+
export type AnswerInput = ChoiceAnswer | TrueFalseAnswer | FillBlankAnswer | ClassifyAnswer | MatchingAnswer | OrderingAnswer | ShortTextAnswer;
|
|
136
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Kensai Quiz format (schema version 0.1).
|
|
3
|
+
*
|
|
4
|
+
* These types mirror `spec/quiz.schema.json`, which remains the authoritative
|
|
5
|
+
* definition used for runtime validation. When the schema changes, keep these
|
|
6
|
+
* types in sync (and re-run `pnpm sync:schema`).
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Quiz } from "./types.js";
|
|
2
|
+
export type IssueSeverity = "error" | "warning";
|
|
3
|
+
export type IssueKind = "schema" | "reference";
|
|
4
|
+
export interface ValidationIssue {
|
|
5
|
+
severity: IssueSeverity;
|
|
6
|
+
kind: IssueKind;
|
|
7
|
+
/** JSON-pointer-ish path to the offending node (e.g. `/questions/2/answer`). */
|
|
8
|
+
path: string;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ValidationResult {
|
|
12
|
+
/** True when there are no error-severity issues (warnings are allowed). */
|
|
13
|
+
valid: boolean;
|
|
14
|
+
issues: ValidationIssue[];
|
|
15
|
+
errors: ValidationIssue[];
|
|
16
|
+
warnings: ValidationIssue[];
|
|
17
|
+
}
|
|
18
|
+
/** Structural validation against the authoritative JSON Schema. */
|
|
19
|
+
export declare function validateSchema(data: unknown): ValidationIssue[];
|
|
20
|
+
/**
|
|
21
|
+
* Referential-integrity checks that a JSON Schema cannot express: answers point at
|
|
22
|
+
* existing ids, `*_from` names resolve, placeholders match blanks, ids are unique,
|
|
23
|
+
* etc. Assumes `quiz` already passed schema validation.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateReferences(quiz: Quiz): ValidationIssue[];
|
|
26
|
+
/** Full validation: schema first, then referential integrity (only if the shape is valid). */
|
|
27
|
+
export declare function validateQuiz(data: unknown): ValidationResult;
|
|
28
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAWvC,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAChD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE/C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAWD,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,EAAE,CAI/D;AAgBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,eAAe,EAAE,CAiJhE;AAED,8FAA8F;AAC9F,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,CAQ5D"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import AjvModule from "ajv/dist/2020.js";
|
|
2
|
+
import { quizSchema } from "./schema.generated.js";
|
|
3
|
+
import { resolveChoiceOptions, resolveGroups } from "./resolve.js";
|
|
4
|
+
// ajv is published as CommonJS; under NodeNext the interop default can be the
|
|
5
|
+
// module namespace, so unwrap a possible nested `default` and construct via a
|
|
6
|
+
// typed alias exposing just the `compile` we use.
|
|
7
|
+
const Ajv2020 = (AjvModule.default ??
|
|
8
|
+
AjvModule);
|
|
9
|
+
// Ajv accepts unknown vocabulary keywords (`description`, `default`) with strict off.
|
|
10
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
11
|
+
let compiled = null;
|
|
12
|
+
function getValidator() {
|
|
13
|
+
if (!compiled)
|
|
14
|
+
compiled = ajv.compile(quizSchema);
|
|
15
|
+
return compiled;
|
|
16
|
+
}
|
|
17
|
+
/** Structural validation against the authoritative JSON Schema. */
|
|
18
|
+
export function validateSchema(data) {
|
|
19
|
+
const validate = getValidator();
|
|
20
|
+
if (validate(data))
|
|
21
|
+
return [];
|
|
22
|
+
return (validate.errors ?? []).map(fromAjvError);
|
|
23
|
+
}
|
|
24
|
+
function fromAjvError(error) {
|
|
25
|
+
const path = error.instancePath || "/";
|
|
26
|
+
const params = error.params && Object.keys(error.params).length
|
|
27
|
+
? " " + JSON.stringify(error.params)
|
|
28
|
+
: "";
|
|
29
|
+
return {
|
|
30
|
+
severity: "error",
|
|
31
|
+
kind: "schema",
|
|
32
|
+
path,
|
|
33
|
+
message: `${error.message ?? "invalid"}${params}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Referential-integrity checks that a JSON Schema cannot express: answers point at
|
|
38
|
+
* existing ids, `*_from` names resolve, placeholders match blanks, ids are unique,
|
|
39
|
+
* etc. Assumes `quiz` already passed schema validation.
|
|
40
|
+
*/
|
|
41
|
+
export function validateReferences(quiz) {
|
|
42
|
+
const issues = [];
|
|
43
|
+
const add = (severity, path, message) => issues.push({ severity, kind: "reference", path, message });
|
|
44
|
+
const declaredCategories = new Set((quiz.categories ?? []).map((c) => c.id));
|
|
45
|
+
const seenQuestionIds = new Set();
|
|
46
|
+
quiz.questions.forEach((question, i) => {
|
|
47
|
+
const base = `/questions/${i}`;
|
|
48
|
+
if (question.id) {
|
|
49
|
+
if (seenQuestionIds.has(question.id)) {
|
|
50
|
+
add("error", `${base}/id`, `Duplicate question id "${question.id}".`);
|
|
51
|
+
}
|
|
52
|
+
seenQuestionIds.add(question.id);
|
|
53
|
+
}
|
|
54
|
+
if (question.category && quiz.categories && !declaredCategories.has(question.category)) {
|
|
55
|
+
add("warning", `${base}/category`, `Category "${question.category}" is not declared in categories[].`);
|
|
56
|
+
}
|
|
57
|
+
switch (question.type) {
|
|
58
|
+
case "choice": {
|
|
59
|
+
if (question.options_from &&
|
|
60
|
+
!(quiz.option_groups && question.options_from in quiz.option_groups)) {
|
|
61
|
+
add("error", `${base}/options_from`, `option_groups has no entry "${question.options_from}".`);
|
|
62
|
+
}
|
|
63
|
+
const options = resolveChoiceOptions(quiz, question);
|
|
64
|
+
reportDuplicateIds(options.map((o) => o.id), `${base}/options`, add);
|
|
65
|
+
const optionIds = new Set(options.map((o) => o.id));
|
|
66
|
+
const answers = Array.isArray(question.answer) ? question.answer : [question.answer];
|
|
67
|
+
for (const answerId of answers) {
|
|
68
|
+
if (!optionIds.has(answerId)) {
|
|
69
|
+
add("error", `${base}/answer`, `Answer id "${answerId}" is not among the options.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if ((question.select ?? "single") === "single" && answers.length !== 1) {
|
|
73
|
+
add("error", `${base}/answer`, `A single-select choice must have exactly one answer.`);
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "fill_blank": {
|
|
78
|
+
const placeholders = extractPlaceholders(question.prompt);
|
|
79
|
+
for (const [key, blank] of Object.entries(question.blanks)) {
|
|
80
|
+
if (!placeholders.has(key)) {
|
|
81
|
+
add("warning", `${base}/blanks/${key}`, `Blank "${key}" has no {{${key}}} placeholder in the prompt.`);
|
|
82
|
+
}
|
|
83
|
+
if (blank.options && blank.answer !== undefined) {
|
|
84
|
+
if (!blank.options.some((o) => o.id === blank.answer)) {
|
|
85
|
+
add("error", `${base}/blanks/${key}/answer`, `Answer "${blank.answer}" is not among the blank options.`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const placeholder of placeholders) {
|
|
90
|
+
if (!(placeholder in question.blanks)) {
|
|
91
|
+
add("warning", `${base}/prompt`, `Placeholder {{${placeholder}}} has no matching blank definition.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "classify": {
|
|
97
|
+
if (question.groups_from &&
|
|
98
|
+
!(quiz.option_groups && question.groups_from in quiz.option_groups)) {
|
|
99
|
+
add("error", `${base}/groups_from`, `option_groups has no entry "${question.groups_from}".`);
|
|
100
|
+
}
|
|
101
|
+
const groupIds = new Set(resolveGroups(quiz, question).map((g) => g.id));
|
|
102
|
+
reportDuplicateIds(question.items.map((it) => it.id), `${base}/items`, add);
|
|
103
|
+
question.items.forEach((item, j) => {
|
|
104
|
+
if (!groupIds.has(item.answer)) {
|
|
105
|
+
add("error", `${base}/items/${j}/answer`, `Item answer "${item.answer}" is not a declared group.`);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "matching": {
|
|
111
|
+
const leftIds = new Set(question.left.map((o) => o.id));
|
|
112
|
+
const rightIds = new Set(question.right.map((o) => o.id));
|
|
113
|
+
for (const [leftId, rightId] of Object.entries(question.answer)) {
|
|
114
|
+
if (!leftIds.has(leftId)) {
|
|
115
|
+
add("error", `${base}/answer`, `Answer key "${leftId}" is not a left id.`);
|
|
116
|
+
}
|
|
117
|
+
if (!rightIds.has(rightId)) {
|
|
118
|
+
add("error", `${base}/answer`, `Answer value "${rightId}" is not a right id.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "ordering": {
|
|
124
|
+
const itemIds = question.items.map((o) => o.id);
|
|
125
|
+
reportDuplicateIds(itemIds, `${base}/items`, add);
|
|
126
|
+
const itemIdSet = new Set(itemIds);
|
|
127
|
+
const answerSet = new Set(question.answer);
|
|
128
|
+
const isPermutation = question.answer.length === itemIds.length &&
|
|
129
|
+
itemIds.every((id) => answerSet.has(id)) &&
|
|
130
|
+
question.answer.every((id) => itemIdSet.has(id));
|
|
131
|
+
if (!isPermutation) {
|
|
132
|
+
add("error", `${base}/answer`, "Ordering answer must be a permutation of all item ids.");
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
default:
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return issues;
|
|
141
|
+
}
|
|
142
|
+
/** Full validation: schema first, then referential integrity (only if the shape is valid). */
|
|
143
|
+
export function validateQuiz(data) {
|
|
144
|
+
let issues = validateSchema(data);
|
|
145
|
+
if (issues.length === 0) {
|
|
146
|
+
issues = issues.concat(validateReferences(data));
|
|
147
|
+
}
|
|
148
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
149
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
150
|
+
return { valid: errors.length === 0, issues, errors, warnings };
|
|
151
|
+
}
|
|
152
|
+
const PLACEHOLDER_RE = /\{\{\s*([^}\s]+)\s*\}\}/g;
|
|
153
|
+
function extractPlaceholders(prompt) {
|
|
154
|
+
const found = new Set();
|
|
155
|
+
let match;
|
|
156
|
+
PLACEHOLDER_RE.lastIndex = 0;
|
|
157
|
+
while ((match = PLACEHOLDER_RE.exec(prompt)) !== null) {
|
|
158
|
+
found.add(match[1]);
|
|
159
|
+
}
|
|
160
|
+
return found;
|
|
161
|
+
}
|
|
162
|
+
function reportDuplicateIds(ids, path, add) {
|
|
163
|
+
const seen = new Set();
|
|
164
|
+
for (const id of ids) {
|
|
165
|
+
if (seen.has(id))
|
|
166
|
+
add("error", path, `Duplicate id "${id}".`);
|
|
167
|
+
seen.add(id);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kensai/quiz-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless, UI-agnostic engine for the Kensai Quiz format: load, validate, evaluate answers, run an attempt, and compute per-category statistics.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"quiz",
|
|
9
|
+
"assessment",
|
|
10
|
+
"education",
|
|
11
|
+
"yaml",
|
|
12
|
+
"json-schema",
|
|
13
|
+
"headless"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/EFRA-GM/kensai-quiz#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/EFRA-GM/kensai-quiz/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/EFRA-GM/kensai-quiz.git",
|
|
22
|
+
"directory": "packages/core"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"ajv": "^8.17.1",
|
|
42
|
+
"js-yaml": "^4.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/js-yaml": "^4.0.9",
|
|
46
|
+
"@types/node": "^22.10.0",
|
|
47
|
+
"typescript": "^5.7.0",
|
|
48
|
+
"vitest": "^2.1.8"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"sync:schema": "node scripts/sync-schema.mjs",
|
|
52
|
+
"prebuild": "node scripts/sync-schema.mjs",
|
|
53
|
+
"build": "tsc -p tsconfig.json",
|
|
54
|
+
"pretest": "node scripts/sync-schema.mjs",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"pretypecheck": "node scripts/sync-schema.mjs",
|
|
58
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
59
|
+
}
|
|
60
|
+
}
|