quizwitz-validator 0.1.1 → 0.2.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/dist/evaluate.js +4 -1
- package/dist/rules.d.ts +8 -1
- package/dist/rules.js +63 -0
- package/dist/types.d.ts +9 -2
- package/dist/types.js +1 -1
- package/docs/design.md +1 -0
- package/fixtures/findings-fixtures.json +821 -14
- package/package.json +1 -1
package/dist/evaluate.js
CHANGED
|
@@ -2,7 +2,7 @@ import { TypeCatalog } from './catalog.js';
|
|
|
2
2
|
import { questionSetting, questionTypeToken, questionsOf, normalizedGroupIds, roundName, roundTypeToken, roundsOf } from './aggregate.js';
|
|
3
3
|
import { ignoredBy } from './ignore.js';
|
|
4
4
|
import { questionWarningId } from './ignore.js';
|
|
5
|
-
import { COMPLETENESS_MESSAGES, evaluateConsistency, evaluateExplanationPair, evaluateGroupSize, evaluateOddOneOut, evaluateOptionCountMajority, evaluateOutlier, evaluateRequire, matchesLegacyWarning, matchesOptionCountOver, matchesSettingEquals, matchesTypeSetting, passesCheck, render } from './rules.js';
|
|
5
|
+
import { COMPLETENESS_MESSAGES, evaluateConsistency, evaluateExplanationPair, evaluateGroupPosition, evaluateGroupSize, evaluateOddOneOut, evaluateOptionCountMajority, evaluateOutlier, evaluateRequire, matchesLegacyWarning, matchesOptionCountOver, matchesSettingEquals, matchesTypeSetting, passesCheck, render } from './rules.js';
|
|
6
6
|
import { localizedText } from './text.js';
|
|
7
7
|
import { COMPARATIVE_KINDS } from './types.js';
|
|
8
8
|
export const ROUND_INCOMPLETE_MESSAGE = '{round}: scoring settings incomplete ({fields})';
|
|
@@ -220,6 +220,9 @@ function evaluateRound(catalog, scope, round, roundIndex) {
|
|
|
220
220
|
case 'groupSize':
|
|
221
221
|
evaluateGroupSize(rule, ctx).forEach(add);
|
|
222
222
|
break;
|
|
223
|
+
case 'groupPosition':
|
|
224
|
+
evaluateGroupPosition(rule, ctx).forEach(add);
|
|
225
|
+
break;
|
|
223
226
|
case 'consistency':
|
|
224
227
|
evaluateConsistency(rule, ctx, quiet).forEach(add);
|
|
225
228
|
break;
|
package/dist/rules.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompletenessRule, ConsistencyRule, ExplanationPairRule, Finding, GroupSizeRule, LegacyWarning, LocalizedText, OddOneOutRule, OptionCountMajorityRule, OptionCountOverRule, OutlierRule, QuestionAggregate, RequireCheck, RequireRule, SettingEqualsRule, SettingsField, TypeSettingRule } from './types.js';
|
|
1
|
+
import type { CompletenessRule, ConsistencyRule, ExplanationPairRule, Finding, GroupPositionRule, GroupSizeRule, LegacyWarning, LocalizedText, OddOneOutRule, OptionCountMajorityRule, OptionCountOverRule, OutlierRule, QuestionAggregate, RequireCheck, RequireRule, SettingEqualsRule, SettingsField, TypeSettingRule } from './types.js';
|
|
2
2
|
export interface QuestionContext {
|
|
3
3
|
question: QuestionAggregate;
|
|
4
4
|
uuid: string;
|
|
@@ -25,6 +25,13 @@ export declare function evaluateOutlier(rule: OutlierRule, ctx: RoundContext): F
|
|
|
25
25
|
export declare function evaluateOddOneOut(rule: OddOneOutRule, ctx: RoundContext): Finding[];
|
|
26
26
|
export declare function evaluateOptionCountMajority(rule: OptionCountMajorityRule, ctx: RoundContext): Finding[];
|
|
27
27
|
export declare function evaluateGroupSize(rule: GroupSizeRule, ctx: RoundContext): Finding[];
|
|
28
|
+
/**
|
|
29
|
+
* The nth question of every group compared across the groups: where a strict
|
|
30
|
+
* majority (but not all) of the groups agree on the value, the nth questions
|
|
31
|
+
* that differ. Questions without a value (no positive setting, empty text)
|
|
32
|
+
* are left out of the count.
|
|
33
|
+
*/
|
|
34
|
+
export declare function evaluateGroupPosition(rule: GroupPositionRule, ctx: RoundContext): Finding[];
|
|
28
35
|
/** Explanation swaps; `quiet` names the `questionUuid:field` gaps the swap explains, for the consistency rules. */
|
|
29
36
|
export declare function evaluateExplanationPair(rule: ExplanationPairRule, ctx: RoundContext, quiet: Set<string>): Finding[];
|
|
30
37
|
export declare function evaluateConsistency(rule: ConsistencyRule, ctx: RoundContext, quiet: Set<string>): Finding[];
|
package/dist/rules.js
CHANGED
|
@@ -144,6 +144,69 @@ export function evaluateGroupSize(rule, ctx) {
|
|
|
144
144
|
const first = groups.get(odd.id)[0];
|
|
145
145
|
return [finding(ctx, first, rule, odd.count === 1 ? rule.messageSingle : rule.message, { count: odd.count, expected: odd.expected }, groupSizeWarningId(ctx.roundId, odd.id))];
|
|
146
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* The nth question of every group compared across the groups: where a strict
|
|
149
|
+
* majority (but not all) of the groups agree on the value, the nth questions
|
|
150
|
+
* that differ. Questions without a value (no positive setting, empty text)
|
|
151
|
+
* are left out of the count.
|
|
152
|
+
*/
|
|
153
|
+
export function evaluateGroupPosition(rule, ctx) {
|
|
154
|
+
const order = [];
|
|
155
|
+
const groups = new Map();
|
|
156
|
+
ctx.questions.forEach((q, i) => {
|
|
157
|
+
const id = ctx.groupIds[i];
|
|
158
|
+
if (!groups.has(id)) {
|
|
159
|
+
groups.set(id, []);
|
|
160
|
+
order.push(id);
|
|
161
|
+
}
|
|
162
|
+
groups.get(id).push(q);
|
|
163
|
+
});
|
|
164
|
+
const valueOf = (q) => {
|
|
165
|
+
if (rule.setting !== undefined) {
|
|
166
|
+
const value = questionSetting(q.question, rule.setting);
|
|
167
|
+
return typeof value === 'number' && value > 0 ? { key: String(value), value } : null;
|
|
168
|
+
}
|
|
169
|
+
const text = resourceText(q.question, rule.field);
|
|
170
|
+
return text !== '' ? { key: text.toLowerCase(), value: text } : null;
|
|
171
|
+
};
|
|
172
|
+
const longest = Math.max(0, ...order.map(id => groups.get(id).length));
|
|
173
|
+
const out = [];
|
|
174
|
+
for (let position = 0; position < longest; position++) {
|
|
175
|
+
const carriers = [];
|
|
176
|
+
for (const id of order) {
|
|
177
|
+
const q = groups.get(id)[position];
|
|
178
|
+
const value = q ? valueOf(q) : null;
|
|
179
|
+
if (q && value) {
|
|
180
|
+
carriers.push({ q, ...value });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (carriers.length < (rule.minPopulation ?? 3)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const tally = new Map();
|
|
187
|
+
for (const c of carriers) {
|
|
188
|
+
tally.set(c.key, (tally.get(c.key) ?? 0) + 1);
|
|
189
|
+
}
|
|
190
|
+
let expected = '';
|
|
191
|
+
let agreeing = 0;
|
|
192
|
+
tally.forEach((count, key) => {
|
|
193
|
+
if (count > agreeing) {
|
|
194
|
+
expected = key;
|
|
195
|
+
agreeing = count;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
if (agreeing === carriers.length || agreeing <= carriers.length * (rule.majority ?? 0.5)) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const shown = carriers.find(c => c.key === expected).value;
|
|
202
|
+
for (const c of carriers) {
|
|
203
|
+
if (c.key !== expected) {
|
|
204
|
+
out.push(finding(ctx, c.q, rule, rule.message, { value: c.value, expected: shown, position: position + 1 }));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
147
210
|
/** Explanation swaps; `quiet` names the `questionUuid:field` gaps the swap explains, for the consistency rules. */
|
|
148
211
|
export function evaluateExplanationPair(rule, ctx, quiet) {
|
|
149
212
|
const fields = rule.fields;
|
package/dist/types.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface SettingsField {
|
|
|
22
22
|
};
|
|
23
23
|
[key: string]: unknown;
|
|
24
24
|
}
|
|
25
|
-
export type RuleKind = 'outlier' | 'oddOneOut' | 'consistency' | 'explanationPair' | 'optionCountMajority' | 'groupSize' | 'require' | 'settingEquals' | 'optionCountOver' | 'typeSetting';
|
|
25
|
+
export type RuleKind = 'outlier' | 'oddOneOut' | 'consistency' | 'explanationPair' | 'optionCountMajority' | 'groupSize' | 'groupPosition' | 'require' | 'settingEquals' | 'optionCountOver' | 'typeSetting';
|
|
26
26
|
export declare const COMPARATIVE_KINDS: readonly RuleKind[];
|
|
27
27
|
export declare const PER_QUESTION_KINDS: readonly RuleKind[];
|
|
28
28
|
export type Severity = 'error' | 'warning';
|
|
@@ -70,6 +70,13 @@ export interface GroupSizeRule extends RuleBase {
|
|
|
70
70
|
rule: 'groupSize';
|
|
71
71
|
messageSingle: LocalizedText;
|
|
72
72
|
}
|
|
73
|
+
export interface GroupPositionRule extends RuleBase {
|
|
74
|
+
rule: 'groupPosition';
|
|
75
|
+
/** A question setting (`points`), or else */
|
|
76
|
+
setting?: string;
|
|
77
|
+
/** a text on the primary resource (`label`), compared trimmed and ignoring case. */
|
|
78
|
+
field?: string;
|
|
79
|
+
}
|
|
73
80
|
export type RequireCheck = 'correctOption' | 'allOptionsFilled' | 'answer' | 'imageMap';
|
|
74
81
|
export interface RequireRule extends RuleBase {
|
|
75
82
|
rule: 'require';
|
|
@@ -98,7 +105,7 @@ export interface TypeSettingRule extends RuleBase {
|
|
|
98
105
|
min: number;
|
|
99
106
|
};
|
|
100
107
|
}
|
|
101
|
-
export type FindingRule = OutlierRule | OddOneOutRule | ConsistencyRule | ExplanationPairRule | OptionCountMajorityRule | GroupSizeRule | RequireRule | SettingEqualsRule | OptionCountOverRule | TypeSettingRule;
|
|
108
|
+
export type FindingRule = OutlierRule | OddOneOutRule | ConsistencyRule | ExplanationPairRule | OptionCountMajorityRule | GroupSizeRule | GroupPositionRule | RequireRule | SettingEqualsRule | OptionCountOverRule | TypeSettingRule;
|
|
102
109
|
/** The existing editor-facing fields the evaluator reads as well (docs/design.md, 3.2). */
|
|
103
110
|
export type CompletenessRule = 'requireText' | 'requireCorrectOption' | 'requireAllOptionsFilled' | 'requireAnswer' | 'requireImageMap';
|
|
104
111
|
export interface OptionCountWarning {
|
package/dist/types.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* The served manifests (the part this package reads), the rule vocabulary
|
|
3
3
|
* and the evaluator's input and output (docs/design.md, 3 and 4).
|
|
4
4
|
*/
|
|
5
|
-
export const COMPARATIVE_KINDS = ['outlier', 'oddOneOut', 'consistency', 'explanationPair', 'optionCountMajority', 'groupSize'];
|
|
5
|
+
export const COMPARATIVE_KINDS = ['outlier', 'oddOneOut', 'consistency', 'explanationPair', 'optionCountMajority', 'groupSize', 'groupPosition'];
|
|
6
6
|
export const PER_QUESTION_KINDS = ['require', 'settingEquals', 'optionCountOver', 'typeSetting'];
|
package/docs/design.md
CHANGED
|
@@ -79,6 +79,7 @@ Messages are **English keys**: the editor translates them with its own catalogue
|
|
|
79
79
|
| `consistency` | `field` (narration field on the primary resource: `long`, `tips`, `explanation`, `longExplanation`) **or** `slot` (question attachment slot: `before`, `during`, ...), `label`, `minPopulation` (4), `majority` (0.75) | Questions missing what at least `majority` of the round (but not all) has. Param `{field}` or `{slot}` = label. `during` counts as filled by an image map. A gap `explanationPair` already explains is skipped. |
|
|
80
80
|
| `explanationPair` | `fields` (`["longExplanation", "explanation"]`), `labels` (`{field: label}`), `minPopulation` (4), `majority` (0.75) | Among questions that fill either field, the ones using the other half than the majority. Params `{expected, actual}` = labels. |
|
|
81
81
|
| `optionCountMajority` | `types` (question type tokens), `minPopulation` (4), `majority` (0.5) | Same-type siblings (types listed) with a different number of filled options than the strict majority. Params `{count, expected}`. |
|
|
82
|
+
| `groupPosition` | exactly one of `setting` (a numeric question setting: `points`) or `field` (a text on the primary resource: `label`, compared trimmed and ignoring case), `minPopulation` (3, groups), `majority` (0.5) | In a grouped round, the nth question of every group is compared with the nth question of the other groups: where a strict majority of the groups (but not all) agree, the nth questions that differ. Questions without a value (setting not > 0, empty text) are left out. Params `{value, expected, position}` (position from 1). |
|
|
82
83
|
| `groupSize` | `minPopulation` (3, groups), `message` (several), `messageSingle` (one question) | In a grouped round, the one group whose size differs when every other group agrees. Id `<roundUuid>:group:<groupId>:group-size`, anchored on the group's first question. Params `{count, expected}`. |
|
|
83
84
|
|
|
84
85
|
**Per-question kinds** — checked on every question in scope:
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"settingsFields": [
|
|
45
45
|
{
|
|
46
46
|
"key": "instantFeedback",
|
|
47
|
-
"label": "
|
|
47
|
+
"label": "Reveal answers instantly",
|
|
48
48
|
"type": "boolean",
|
|
49
49
|
"target": "modeSettings",
|
|
50
50
|
"default": false,
|
|
51
|
-
"hint": "
|
|
51
|
+
"hint": "Instead of saving the results for the end of the round, the correct answer appears on the question as soon as its time is up, and the round moves on. No result screens: explanations and feedback attachments are not shown."
|
|
52
52
|
}
|
|
53
53
|
]
|
|
54
54
|
},
|
|
@@ -71,10 +71,11 @@
|
|
|
71
71
|
{
|
|
72
72
|
"token": "multiquestion",
|
|
73
73
|
"name": "Multiquestion",
|
|
74
|
-
"guidance": "Questions come in groups of at most 4. A group is shown at once; each player picks one question of the group by its label and answers only that one, within one shared timer (the longest duration in the group). Give every question a short, distinct label and every group the same number of questions.",
|
|
74
|
+
"guidance": "Questions come in groups of at most 4. A group is shown at once; each player picks one question of the group by its label and answers only that one, within one shared timer (the longest duration in the group). Give every question a short, distinct label and every group the same number of questions. The group shares one screen: the pictures and videos of all its questions (before and during) are shown side by side before it, and only sounds play while it runs.",
|
|
75
75
|
"description": "Players choose from a selection of multiple questions, each worth different point values.",
|
|
76
76
|
"category": "Interactive",
|
|
77
77
|
"questionList": "grouped",
|
|
78
|
+
"sharedQuestionScreen": true,
|
|
78
79
|
"questionLabel": {
|
|
79
80
|
"label": "Label",
|
|
80
81
|
"hint": "Players tap this label to pick this question"
|
|
@@ -84,6 +85,75 @@
|
|
|
84
85
|
"defaultScoringStrategy": "default",
|
|
85
86
|
"limiters": [
|
|
86
87
|
"all"
|
|
88
|
+
],
|
|
89
|
+
"findings": [
|
|
90
|
+
{
|
|
91
|
+
"id": "group-size",
|
|
92
|
+
"rule": "groupSize",
|
|
93
|
+
"minPopulation": 3,
|
|
94
|
+
"message": "This group has {count} questions while the other groups have {expected}",
|
|
95
|
+
"messageSingle": "This group has 1 question while the other groups have {expected}",
|
|
96
|
+
"description": "Question group with more or fewer questions than the other groups"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "group-position:points",
|
|
100
|
+
"rule": "groupPosition",
|
|
101
|
+
"setting": "points",
|
|
102
|
+
"minPopulation": 3,
|
|
103
|
+
"majority": 0.5,
|
|
104
|
+
"message": "Worth {value} points while question {position} of most other groups is worth {expected}",
|
|
105
|
+
"description": "Question worth different points than the same question in the other groups"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "group-position:label",
|
|
109
|
+
"rule": "groupPosition",
|
|
110
|
+
"field": "label",
|
|
111
|
+
"minPopulation": 3,
|
|
112
|
+
"majority": 0.5,
|
|
113
|
+
"message": "Labelled \"{value}\" while question {position} of most other groups is labelled \"{expected}\"",
|
|
114
|
+
"description": "Question with a different label than the same question in the other groups"
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"token": "questiongroups",
|
|
120
|
+
"name": "Question groups",
|
|
121
|
+
"guidance": "Questions come in groups, asked one after another: first the group's attachment (a video or a sound clip, in the 'Show before the question group' slot of the group's first question) plays once, then every question of the group is asked as a normal question, in order. Use it for several questions about one clip. A group is drawn whole when the round asks a random selection. The option 'Play as a lightning round' postpones every result to the end of the round and keeps the countdown music running within a group.",
|
|
122
|
+
"description": "Questions come in groups: a group's attachment plays once, then its questions are asked one after another.",
|
|
123
|
+
"category": "Regular",
|
|
124
|
+
"questionList": "grouped",
|
|
125
|
+
"groupAttachments": true,
|
|
126
|
+
"allowedQuestionTypes": "*",
|
|
127
|
+
"allowedScoringStrategies": "*",
|
|
128
|
+
"defaultScoringStrategy": "default",
|
|
129
|
+
"limiters": [
|
|
130
|
+
"all",
|
|
131
|
+
"no-private-screens",
|
|
132
|
+
"no-screens"
|
|
133
|
+
],
|
|
134
|
+
"settingsFields": [
|
|
135
|
+
{
|
|
136
|
+
"key": "lightning",
|
|
137
|
+
"label": "Play as a lightning round",
|
|
138
|
+
"type": "boolean",
|
|
139
|
+
"target": "modeSettings",
|
|
140
|
+
"default": false,
|
|
141
|
+
"postponesFeedback": true,
|
|
142
|
+
"hint": "Every result is shown at the end of the round, and the countdown music keeps running from one question of a group to the next; it stops when the group is done."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"key": "instantFeedback",
|
|
146
|
+
"label": "Reveal answers instantly",
|
|
147
|
+
"type": "boolean",
|
|
148
|
+
"target": "modeSettings",
|
|
149
|
+
"default": false,
|
|
150
|
+
"postponesFeedback": true,
|
|
151
|
+
"showWhen": {
|
|
152
|
+
"key": "lightning",
|
|
153
|
+
"equals": true
|
|
154
|
+
},
|
|
155
|
+
"hint": "Instead of saving the results for the end of the round, the correct answer appears on the question as soon as its time is up, and the round moves on. No result screens: explanations and feedback attachments are not shown."
|
|
156
|
+
}
|
|
87
157
|
]
|
|
88
158
|
},
|
|
89
159
|
{
|
|
@@ -744,7 +814,7 @@
|
|
|
744
814
|
{
|
|
745
815
|
"token": "imagemapquestion",
|
|
746
816
|
"name": "Image Map Question",
|
|
747
|
-
"guidance": "Players tap spots on an image; taps inside a target zone score. The image and its zones are set in the image-map editor;
|
|
817
|
+
"guidance": "Players tap spots on an image; taps inside a target zone score. The image and its zones are set in the image-map editor; the option texts only name the zones there and players never see them, so the question needs no answering instructions or coordinates. A map with only neutral zones can never be answered correctly. Give the players a picture large enough to tell the zones apart.",
|
|
748
818
|
"description": "Players are presented with an image and must click on the correct position.",
|
|
749
819
|
"icon": "ads_click",
|
|
750
820
|
"addToRounds": "*",
|
|
@@ -992,7 +1062,8 @@
|
|
|
992
1062
|
{
|
|
993
1063
|
"token": "strike",
|
|
994
1064
|
"name": "Strike",
|
|
995
|
-
"guidance": "Each correct answer in a row climbs a level and adds a bonus per level; a wrong answer drops levels (see the strike round type).",
|
|
1065
|
+
"guidance": "Each correct answer in a row climbs a level and adds a bonus per level; a wrong answer drops levels (see the strike round type). The level ladder moves with every result, so a round scored this way shows each result right after its question and cannot play as a lightning round. In a question groups round the levels can start over with every group.",
|
|
1066
|
+
"immediateFeedback": true,
|
|
996
1067
|
"settingsFields": [
|
|
997
1068
|
{
|
|
998
1069
|
"key": "bonusPerLevel",
|
|
@@ -1017,6 +1088,16 @@
|
|
|
1017
1088
|
"target": "scoringStrategy",
|
|
1018
1089
|
"default": 5,
|
|
1019
1090
|
"min": 0
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
"key": "resetLevelsPerGroup",
|
|
1094
|
+
"label": "Start every question group at level 1",
|
|
1095
|
+
"type": "boolean",
|
|
1096
|
+
"target": "scoringStrategy",
|
|
1097
|
+
"roundTypes": [
|
|
1098
|
+
"questiongroups"
|
|
1099
|
+
],
|
|
1100
|
+
"hint": "Every player is back on level 1 when a new group of questions starts, so each group is a ladder of its own."
|
|
1020
1101
|
}
|
|
1021
1102
|
]
|
|
1022
1103
|
},
|
|
@@ -1221,15 +1302,6 @@
|
|
|
1221
1302
|
"message": "Has {count} answer options while most of this round has {expected}",
|
|
1222
1303
|
"description": "Different number of answer options than the rest of the round"
|
|
1223
1304
|
},
|
|
1224
|
-
{
|
|
1225
|
-
"id": "group-size",
|
|
1226
|
-
"rule": "groupSize",
|
|
1227
|
-
"minPopulation": 3,
|
|
1228
|
-
"questionList": "grouped",
|
|
1229
|
-
"message": "This group has {count} questions while the other groups have {expected}",
|
|
1230
|
-
"messageSingle": "This group has 1 question while the other groups have {expected}",
|
|
1231
|
-
"description": "Question group with more or fewer questions than the other groups"
|
|
1232
|
-
},
|
|
1233
1305
|
{
|
|
1234
1306
|
"id": "explanation-pair",
|
|
1235
1307
|
"rule": "explanationPair",
|
|
@@ -3080,6 +3152,741 @@
|
|
|
3080
3152
|
]
|
|
3081
3153
|
}
|
|
3082
3154
|
},
|
|
3155
|
+
{
|
|
3156
|
+
"name": "group-position-label",
|
|
3157
|
+
"input": {
|
|
3158
|
+
"round": {
|
|
3159
|
+
"uuid": "r1",
|
|
3160
|
+
"original_language": "en",
|
|
3161
|
+
"mode": {
|
|
3162
|
+
"token": "multiquestion"
|
|
3163
|
+
},
|
|
3164
|
+
"scoringStrategy": {
|
|
3165
|
+
"token": "default"
|
|
3166
|
+
},
|
|
3167
|
+
"resources": [
|
|
3168
|
+
{
|
|
3169
|
+
"language": "en",
|
|
3170
|
+
"name": "Round one"
|
|
3171
|
+
}
|
|
3172
|
+
],
|
|
3173
|
+
"questions": {
|
|
3174
|
+
"items": [
|
|
3175
|
+
{
|
|
3176
|
+
"uuid": "q1a",
|
|
3177
|
+
"original_language": "en",
|
|
3178
|
+
"mode": {
|
|
3179
|
+
"token": "openquestion"
|
|
3180
|
+
},
|
|
3181
|
+
"points": 1000,
|
|
3182
|
+
"duration": 30,
|
|
3183
|
+
"group": "1",
|
|
3184
|
+
"resources": [
|
|
3185
|
+
{
|
|
3186
|
+
"language": "en",
|
|
3187
|
+
"name": "Question q1a",
|
|
3188
|
+
"label": "Multiple choice",
|
|
3189
|
+
"options": {
|
|
3190
|
+
"items": [
|
|
3191
|
+
{
|
|
3192
|
+
"option": 1,
|
|
3193
|
+
"text": "Answer",
|
|
3194
|
+
"correct": true
|
|
3195
|
+
}
|
|
3196
|
+
]
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
]
|
|
3200
|
+
},
|
|
3201
|
+
{
|
|
3202
|
+
"uuid": "q1b",
|
|
3203
|
+
"original_language": "en",
|
|
3204
|
+
"mode": {
|
|
3205
|
+
"token": "openquestion"
|
|
3206
|
+
},
|
|
3207
|
+
"points": 1000,
|
|
3208
|
+
"duration": 30,
|
|
3209
|
+
"group": "1",
|
|
3210
|
+
"resources": [
|
|
3211
|
+
{
|
|
3212
|
+
"language": "en",
|
|
3213
|
+
"name": "Question q1b",
|
|
3214
|
+
"label": "Open question",
|
|
3215
|
+
"options": {
|
|
3216
|
+
"items": [
|
|
3217
|
+
{
|
|
3218
|
+
"option": 1,
|
|
3219
|
+
"text": "Answer",
|
|
3220
|
+
"correct": true
|
|
3221
|
+
}
|
|
3222
|
+
]
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
]
|
|
3226
|
+
},
|
|
3227
|
+
{
|
|
3228
|
+
"uuid": "q2a",
|
|
3229
|
+
"original_language": "en",
|
|
3230
|
+
"mode": {
|
|
3231
|
+
"token": "openquestion"
|
|
3232
|
+
},
|
|
3233
|
+
"points": 1000,
|
|
3234
|
+
"duration": 30,
|
|
3235
|
+
"group": "2",
|
|
3236
|
+
"resources": [
|
|
3237
|
+
{
|
|
3238
|
+
"language": "en",
|
|
3239
|
+
"name": "Question q2a",
|
|
3240
|
+
"label": "Open question",
|
|
3241
|
+
"options": {
|
|
3242
|
+
"items": [
|
|
3243
|
+
{
|
|
3244
|
+
"option": 1,
|
|
3245
|
+
"text": "Answer",
|
|
3246
|
+
"correct": true
|
|
3247
|
+
}
|
|
3248
|
+
]
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
]
|
|
3252
|
+
},
|
|
3253
|
+
{
|
|
3254
|
+
"uuid": "q2b",
|
|
3255
|
+
"original_language": "en",
|
|
3256
|
+
"mode": {
|
|
3257
|
+
"token": "openquestion"
|
|
3258
|
+
},
|
|
3259
|
+
"points": 1000,
|
|
3260
|
+
"duration": 30,
|
|
3261
|
+
"group": "2",
|
|
3262
|
+
"resources": [
|
|
3263
|
+
{
|
|
3264
|
+
"language": "en",
|
|
3265
|
+
"name": "Question q2b",
|
|
3266
|
+
"label": "Open question",
|
|
3267
|
+
"options": {
|
|
3268
|
+
"items": [
|
|
3269
|
+
{
|
|
3270
|
+
"option": 1,
|
|
3271
|
+
"text": "Answer",
|
|
3272
|
+
"correct": true
|
|
3273
|
+
}
|
|
3274
|
+
]
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
]
|
|
3278
|
+
},
|
|
3279
|
+
{
|
|
3280
|
+
"uuid": "q3a",
|
|
3281
|
+
"original_language": "en",
|
|
3282
|
+
"mode": {
|
|
3283
|
+
"token": "openquestion"
|
|
3284
|
+
},
|
|
3285
|
+
"points": 1000,
|
|
3286
|
+
"duration": 30,
|
|
3287
|
+
"group": "3",
|
|
3288
|
+
"resources": [
|
|
3289
|
+
{
|
|
3290
|
+
"language": "en",
|
|
3291
|
+
"name": "Question q3a",
|
|
3292
|
+
"label": "multiple choice ",
|
|
3293
|
+
"options": {
|
|
3294
|
+
"items": [
|
|
3295
|
+
{
|
|
3296
|
+
"option": 1,
|
|
3297
|
+
"text": "Answer",
|
|
3298
|
+
"correct": true
|
|
3299
|
+
}
|
|
3300
|
+
]
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
]
|
|
3304
|
+
},
|
|
3305
|
+
{
|
|
3306
|
+
"uuid": "q3b",
|
|
3307
|
+
"original_language": "en",
|
|
3308
|
+
"mode": {
|
|
3309
|
+
"token": "openquestion"
|
|
3310
|
+
},
|
|
3311
|
+
"points": 1000,
|
|
3312
|
+
"duration": 30,
|
|
3313
|
+
"group": "3",
|
|
3314
|
+
"resources": [
|
|
3315
|
+
{
|
|
3316
|
+
"language": "en",
|
|
3317
|
+
"name": "Question q3b",
|
|
3318
|
+
"label": "Open question",
|
|
3319
|
+
"options": {
|
|
3320
|
+
"items": [
|
|
3321
|
+
{
|
|
3322
|
+
"option": 1,
|
|
3323
|
+
"text": "Answer",
|
|
3324
|
+
"correct": true
|
|
3325
|
+
}
|
|
3326
|
+
]
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
]
|
|
3330
|
+
},
|
|
3331
|
+
{
|
|
3332
|
+
"uuid": "q4a",
|
|
3333
|
+
"original_language": "en",
|
|
3334
|
+
"mode": {
|
|
3335
|
+
"token": "openquestion"
|
|
3336
|
+
},
|
|
3337
|
+
"points": 1000,
|
|
3338
|
+
"duration": 30,
|
|
3339
|
+
"group": "4",
|
|
3340
|
+
"resources": [
|
|
3341
|
+
{
|
|
3342
|
+
"language": "en",
|
|
3343
|
+
"name": "Question q4a",
|
|
3344
|
+
"label": "Multiple choice",
|
|
3345
|
+
"options": {
|
|
3346
|
+
"items": [
|
|
3347
|
+
{
|
|
3348
|
+
"option": 1,
|
|
3349
|
+
"text": "Answer",
|
|
3350
|
+
"correct": true
|
|
3351
|
+
}
|
|
3352
|
+
]
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
]
|
|
3356
|
+
},
|
|
3357
|
+
{
|
|
3358
|
+
"uuid": "q4b",
|
|
3359
|
+
"original_language": "en",
|
|
3360
|
+
"mode": {
|
|
3361
|
+
"token": "openquestion"
|
|
3362
|
+
},
|
|
3363
|
+
"points": 1000,
|
|
3364
|
+
"duration": 30,
|
|
3365
|
+
"group": "4",
|
|
3366
|
+
"resources": [
|
|
3367
|
+
{
|
|
3368
|
+
"language": "en",
|
|
3369
|
+
"name": "Question q4b",
|
|
3370
|
+
"label": "Open question",
|
|
3371
|
+
"options": {
|
|
3372
|
+
"items": [
|
|
3373
|
+
{
|
|
3374
|
+
"option": 1,
|
|
3375
|
+
"text": "Answer",
|
|
3376
|
+
"correct": true
|
|
3377
|
+
}
|
|
3378
|
+
]
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
]
|
|
3382
|
+
}
|
|
3383
|
+
]
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
},
|
|
3387
|
+
"expected": {
|
|
3388
|
+
"errors": [],
|
|
3389
|
+
"warnings": [
|
|
3390
|
+
{
|
|
3391
|
+
"id": "q2a:group-position:label",
|
|
3392
|
+
"ruleId": "group-position:label",
|
|
3393
|
+
"roundId": "r1",
|
|
3394
|
+
"questionId": "q2a",
|
|
3395
|
+
"message": "Labelled \"Open question\" while question 1 of most other groups is labelled \"Multiple choice\"",
|
|
3396
|
+
"messageKey": "Labelled \"{value}\" while question {position} of most other groups is labelled \"{expected}\"",
|
|
3397
|
+
"messageParams": {
|
|
3398
|
+
"value": "Open question",
|
|
3399
|
+
"expected": "Multiple choice",
|
|
3400
|
+
"position": 1
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
]
|
|
3404
|
+
}
|
|
3405
|
+
},
|
|
3406
|
+
{
|
|
3407
|
+
"name": "group-position-points",
|
|
3408
|
+
"input": {
|
|
3409
|
+
"round": {
|
|
3410
|
+
"uuid": "r1",
|
|
3411
|
+
"original_language": "en",
|
|
3412
|
+
"mode": {
|
|
3413
|
+
"token": "multiquestion"
|
|
3414
|
+
},
|
|
3415
|
+
"scoringStrategy": {
|
|
3416
|
+
"token": "default"
|
|
3417
|
+
},
|
|
3418
|
+
"resources": [
|
|
3419
|
+
{
|
|
3420
|
+
"language": "en",
|
|
3421
|
+
"name": "Round one"
|
|
3422
|
+
}
|
|
3423
|
+
],
|
|
3424
|
+
"questions": {
|
|
3425
|
+
"items": [
|
|
3426
|
+
{
|
|
3427
|
+
"uuid": "q1a",
|
|
3428
|
+
"original_language": "en",
|
|
3429
|
+
"mode": {
|
|
3430
|
+
"token": "openquestion"
|
|
3431
|
+
},
|
|
3432
|
+
"points": 750,
|
|
3433
|
+
"duration": 30,
|
|
3434
|
+
"group": "1",
|
|
3435
|
+
"resources": [
|
|
3436
|
+
{
|
|
3437
|
+
"language": "en",
|
|
3438
|
+
"name": "Question q1a",
|
|
3439
|
+
"label": "Multiple choice",
|
|
3440
|
+
"options": {
|
|
3441
|
+
"items": [
|
|
3442
|
+
{
|
|
3443
|
+
"option": 1,
|
|
3444
|
+
"text": "Answer",
|
|
3445
|
+
"correct": true
|
|
3446
|
+
}
|
|
3447
|
+
]
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
]
|
|
3451
|
+
},
|
|
3452
|
+
{
|
|
3453
|
+
"uuid": "q1b",
|
|
3454
|
+
"original_language": "en",
|
|
3455
|
+
"mode": {
|
|
3456
|
+
"token": "openquestion"
|
|
3457
|
+
},
|
|
3458
|
+
"points": 1500,
|
|
3459
|
+
"duration": 30,
|
|
3460
|
+
"group": "1",
|
|
3461
|
+
"resources": [
|
|
3462
|
+
{
|
|
3463
|
+
"language": "en",
|
|
3464
|
+
"name": "Question q1b",
|
|
3465
|
+
"label": "Open question",
|
|
3466
|
+
"options": {
|
|
3467
|
+
"items": [
|
|
3468
|
+
{
|
|
3469
|
+
"option": 1,
|
|
3470
|
+
"text": "Answer",
|
|
3471
|
+
"correct": true
|
|
3472
|
+
}
|
|
3473
|
+
]
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
]
|
|
3477
|
+
},
|
|
3478
|
+
{
|
|
3479
|
+
"uuid": "q2a",
|
|
3480
|
+
"original_language": "en",
|
|
3481
|
+
"mode": {
|
|
3482
|
+
"token": "openquestion"
|
|
3483
|
+
},
|
|
3484
|
+
"points": 750,
|
|
3485
|
+
"duration": 30,
|
|
3486
|
+
"group": "2",
|
|
3487
|
+
"resources": [
|
|
3488
|
+
{
|
|
3489
|
+
"language": "en",
|
|
3490
|
+
"name": "Question q2a",
|
|
3491
|
+
"label": "Multiple choice",
|
|
3492
|
+
"options": {
|
|
3493
|
+
"items": [
|
|
3494
|
+
{
|
|
3495
|
+
"option": 1,
|
|
3496
|
+
"text": "Answer",
|
|
3497
|
+
"correct": true
|
|
3498
|
+
}
|
|
3499
|
+
]
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
]
|
|
3503
|
+
},
|
|
3504
|
+
{
|
|
3505
|
+
"uuid": "q2b",
|
|
3506
|
+
"original_language": "en",
|
|
3507
|
+
"mode": {
|
|
3508
|
+
"token": "openquestion"
|
|
3509
|
+
},
|
|
3510
|
+
"points": 1500,
|
|
3511
|
+
"duration": 30,
|
|
3512
|
+
"group": "2",
|
|
3513
|
+
"resources": [
|
|
3514
|
+
{
|
|
3515
|
+
"language": "en",
|
|
3516
|
+
"name": "Question q2b",
|
|
3517
|
+
"label": "Open question",
|
|
3518
|
+
"options": {
|
|
3519
|
+
"items": [
|
|
3520
|
+
{
|
|
3521
|
+
"option": 1,
|
|
3522
|
+
"text": "Answer",
|
|
3523
|
+
"correct": true
|
|
3524
|
+
}
|
|
3525
|
+
]
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
]
|
|
3529
|
+
},
|
|
3530
|
+
{
|
|
3531
|
+
"uuid": "q3a",
|
|
3532
|
+
"original_language": "en",
|
|
3533
|
+
"mode": {
|
|
3534
|
+
"token": "openquestion"
|
|
3535
|
+
},
|
|
3536
|
+
"points": 750,
|
|
3537
|
+
"duration": 30,
|
|
3538
|
+
"group": "3",
|
|
3539
|
+
"resources": [
|
|
3540
|
+
{
|
|
3541
|
+
"language": "en",
|
|
3542
|
+
"name": "Question q3a",
|
|
3543
|
+
"label": "Multiple choice",
|
|
3544
|
+
"options": {
|
|
3545
|
+
"items": [
|
|
3546
|
+
{
|
|
3547
|
+
"option": 1,
|
|
3548
|
+
"text": "Answer",
|
|
3549
|
+
"correct": true
|
|
3550
|
+
}
|
|
3551
|
+
]
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
]
|
|
3555
|
+
},
|
|
3556
|
+
{
|
|
3557
|
+
"uuid": "q3b",
|
|
3558
|
+
"original_language": "en",
|
|
3559
|
+
"mode": {
|
|
3560
|
+
"token": "openquestion"
|
|
3561
|
+
},
|
|
3562
|
+
"points": 1000,
|
|
3563
|
+
"duration": 30,
|
|
3564
|
+
"group": "3",
|
|
3565
|
+
"resources": [
|
|
3566
|
+
{
|
|
3567
|
+
"language": "en",
|
|
3568
|
+
"name": "Question q3b",
|
|
3569
|
+
"label": "Open question",
|
|
3570
|
+
"options": {
|
|
3571
|
+
"items": [
|
|
3572
|
+
{
|
|
3573
|
+
"option": 1,
|
|
3574
|
+
"text": "Answer",
|
|
3575
|
+
"correct": true
|
|
3576
|
+
}
|
|
3577
|
+
]
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
]
|
|
3581
|
+
},
|
|
3582
|
+
{
|
|
3583
|
+
"uuid": "q4a",
|
|
3584
|
+
"original_language": "en",
|
|
3585
|
+
"mode": {
|
|
3586
|
+
"token": "openquestion"
|
|
3587
|
+
},
|
|
3588
|
+
"points": 750,
|
|
3589
|
+
"duration": 30,
|
|
3590
|
+
"group": "4",
|
|
3591
|
+
"resources": [
|
|
3592
|
+
{
|
|
3593
|
+
"language": "en",
|
|
3594
|
+
"name": "Question q4a",
|
|
3595
|
+
"label": "Multiple choice",
|
|
3596
|
+
"options": {
|
|
3597
|
+
"items": [
|
|
3598
|
+
{
|
|
3599
|
+
"option": 1,
|
|
3600
|
+
"text": "Answer",
|
|
3601
|
+
"correct": true
|
|
3602
|
+
}
|
|
3603
|
+
]
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
]
|
|
3607
|
+
},
|
|
3608
|
+
{
|
|
3609
|
+
"uuid": "q4b",
|
|
3610
|
+
"original_language": "en",
|
|
3611
|
+
"mode": {
|
|
3612
|
+
"token": "openquestion"
|
|
3613
|
+
},
|
|
3614
|
+
"points": 1500,
|
|
3615
|
+
"duration": 30,
|
|
3616
|
+
"group": "4",
|
|
3617
|
+
"resources": [
|
|
3618
|
+
{
|
|
3619
|
+
"language": "en",
|
|
3620
|
+
"name": "Question q4b",
|
|
3621
|
+
"label": "Open question",
|
|
3622
|
+
"options": {
|
|
3623
|
+
"items": [
|
|
3624
|
+
{
|
|
3625
|
+
"option": 1,
|
|
3626
|
+
"text": "Answer",
|
|
3627
|
+
"correct": true
|
|
3628
|
+
}
|
|
3629
|
+
]
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
]
|
|
3633
|
+
}
|
|
3634
|
+
]
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
},
|
|
3638
|
+
"expected": {
|
|
3639
|
+
"errors": [],
|
|
3640
|
+
"warnings": [
|
|
3641
|
+
{
|
|
3642
|
+
"id": "q3b:group-position:points",
|
|
3643
|
+
"ruleId": "group-position:points",
|
|
3644
|
+
"roundId": "r1",
|
|
3645
|
+
"questionId": "q3b",
|
|
3646
|
+
"message": "Worth 1000 points while question 2 of most other groups is worth 1500",
|
|
3647
|
+
"messageKey": "Worth {value} points while question {position} of most other groups is worth {expected}",
|
|
3648
|
+
"messageParams": {
|
|
3649
|
+
"value": 1000,
|
|
3650
|
+
"expected": 1500,
|
|
3651
|
+
"position": 2
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
]
|
|
3655
|
+
}
|
|
3656
|
+
},
|
|
3657
|
+
{
|
|
3658
|
+
"name": "group-size-not-in-question-groups",
|
|
3659
|
+
"input": {
|
|
3660
|
+
"round": {
|
|
3661
|
+
"uuid": "r1",
|
|
3662
|
+
"original_language": "en",
|
|
3663
|
+
"mode": {
|
|
3664
|
+
"token": "questiongroups"
|
|
3665
|
+
},
|
|
3666
|
+
"modeSettings": {
|
|
3667
|
+
"lightning": false,
|
|
3668
|
+
"instantFeedback": false
|
|
3669
|
+
},
|
|
3670
|
+
"scoringStrategy": {
|
|
3671
|
+
"token": "default"
|
|
3672
|
+
},
|
|
3673
|
+
"resources": [
|
|
3674
|
+
{
|
|
3675
|
+
"language": "en",
|
|
3676
|
+
"name": "Round one"
|
|
3677
|
+
}
|
|
3678
|
+
],
|
|
3679
|
+
"questions": {
|
|
3680
|
+
"items": [
|
|
3681
|
+
{
|
|
3682
|
+
"uuid": "q1",
|
|
3683
|
+
"original_language": "en",
|
|
3684
|
+
"mode": {
|
|
3685
|
+
"token": "multiplechoice"
|
|
3686
|
+
},
|
|
3687
|
+
"points": 1000,
|
|
3688
|
+
"duration": 30,
|
|
3689
|
+
"group": "1",
|
|
3690
|
+
"resources": [
|
|
3691
|
+
{
|
|
3692
|
+
"language": "en",
|
|
3693
|
+
"name": "What is the capital of France?",
|
|
3694
|
+
"options": {
|
|
3695
|
+
"items": [
|
|
3696
|
+
{
|
|
3697
|
+
"option": 1,
|
|
3698
|
+
"text": "Paris",
|
|
3699
|
+
"correct": true
|
|
3700
|
+
},
|
|
3701
|
+
{
|
|
3702
|
+
"option": 2,
|
|
3703
|
+
"text": "Rome",
|
|
3704
|
+
"correct": false
|
|
3705
|
+
},
|
|
3706
|
+
{
|
|
3707
|
+
"option": 3,
|
|
3708
|
+
"text": "Berlin",
|
|
3709
|
+
"correct": false
|
|
3710
|
+
},
|
|
3711
|
+
{
|
|
3712
|
+
"option": 4,
|
|
3713
|
+
"text": "Madrid",
|
|
3714
|
+
"correct": false
|
|
3715
|
+
}
|
|
3716
|
+
]
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
]
|
|
3720
|
+
},
|
|
3721
|
+
{
|
|
3722
|
+
"uuid": "q2",
|
|
3723
|
+
"original_language": "en",
|
|
3724
|
+
"mode": {
|
|
3725
|
+
"token": "multiplechoice"
|
|
3726
|
+
},
|
|
3727
|
+
"points": 1000,
|
|
3728
|
+
"duration": 30,
|
|
3729
|
+
"group": "1",
|
|
3730
|
+
"resources": [
|
|
3731
|
+
{
|
|
3732
|
+
"language": "en",
|
|
3733
|
+
"name": "What is the capital of France?",
|
|
3734
|
+
"options": {
|
|
3735
|
+
"items": [
|
|
3736
|
+
{
|
|
3737
|
+
"option": 1,
|
|
3738
|
+
"text": "Paris",
|
|
3739
|
+
"correct": true
|
|
3740
|
+
},
|
|
3741
|
+
{
|
|
3742
|
+
"option": 2,
|
|
3743
|
+
"text": "Rome",
|
|
3744
|
+
"correct": false
|
|
3745
|
+
},
|
|
3746
|
+
{
|
|
3747
|
+
"option": 3,
|
|
3748
|
+
"text": "Berlin",
|
|
3749
|
+
"correct": false
|
|
3750
|
+
},
|
|
3751
|
+
{
|
|
3752
|
+
"option": 4,
|
|
3753
|
+
"text": "Madrid",
|
|
3754
|
+
"correct": false
|
|
3755
|
+
}
|
|
3756
|
+
]
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
]
|
|
3760
|
+
},
|
|
3761
|
+
{
|
|
3762
|
+
"uuid": "q3",
|
|
3763
|
+
"original_language": "en",
|
|
3764
|
+
"mode": {
|
|
3765
|
+
"token": "multiplechoice"
|
|
3766
|
+
},
|
|
3767
|
+
"points": 1000,
|
|
3768
|
+
"duration": 30,
|
|
3769
|
+
"group": "2",
|
|
3770
|
+
"resources": [
|
|
3771
|
+
{
|
|
3772
|
+
"language": "en",
|
|
3773
|
+
"name": "What is the capital of France?",
|
|
3774
|
+
"options": {
|
|
3775
|
+
"items": [
|
|
3776
|
+
{
|
|
3777
|
+
"option": 1,
|
|
3778
|
+
"text": "Paris",
|
|
3779
|
+
"correct": true
|
|
3780
|
+
},
|
|
3781
|
+
{
|
|
3782
|
+
"option": 2,
|
|
3783
|
+
"text": "Rome",
|
|
3784
|
+
"correct": false
|
|
3785
|
+
},
|
|
3786
|
+
{
|
|
3787
|
+
"option": 3,
|
|
3788
|
+
"text": "Berlin",
|
|
3789
|
+
"correct": false
|
|
3790
|
+
},
|
|
3791
|
+
{
|
|
3792
|
+
"option": 4,
|
|
3793
|
+
"text": "Madrid",
|
|
3794
|
+
"correct": false
|
|
3795
|
+
}
|
|
3796
|
+
]
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
]
|
|
3800
|
+
},
|
|
3801
|
+
{
|
|
3802
|
+
"uuid": "q4",
|
|
3803
|
+
"original_language": "en",
|
|
3804
|
+
"mode": {
|
|
3805
|
+
"token": "multiplechoice"
|
|
3806
|
+
},
|
|
3807
|
+
"points": 1000,
|
|
3808
|
+
"duration": 30,
|
|
3809
|
+
"group": "2",
|
|
3810
|
+
"resources": [
|
|
3811
|
+
{
|
|
3812
|
+
"language": "en",
|
|
3813
|
+
"name": "What is the capital of France?",
|
|
3814
|
+
"options": {
|
|
3815
|
+
"items": [
|
|
3816
|
+
{
|
|
3817
|
+
"option": 1,
|
|
3818
|
+
"text": "Paris",
|
|
3819
|
+
"correct": true
|
|
3820
|
+
},
|
|
3821
|
+
{
|
|
3822
|
+
"option": 2,
|
|
3823
|
+
"text": "Rome",
|
|
3824
|
+
"correct": false
|
|
3825
|
+
},
|
|
3826
|
+
{
|
|
3827
|
+
"option": 3,
|
|
3828
|
+
"text": "Berlin",
|
|
3829
|
+
"correct": false
|
|
3830
|
+
},
|
|
3831
|
+
{
|
|
3832
|
+
"option": 4,
|
|
3833
|
+
"text": "Madrid",
|
|
3834
|
+
"correct": false
|
|
3835
|
+
}
|
|
3836
|
+
]
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
]
|
|
3840
|
+
},
|
|
3841
|
+
{
|
|
3842
|
+
"uuid": "q5",
|
|
3843
|
+
"original_language": "en",
|
|
3844
|
+
"mode": {
|
|
3845
|
+
"token": "multiplechoice"
|
|
3846
|
+
},
|
|
3847
|
+
"points": 1000,
|
|
3848
|
+
"duration": 30,
|
|
3849
|
+
"group": "3",
|
|
3850
|
+
"resources": [
|
|
3851
|
+
{
|
|
3852
|
+
"language": "en",
|
|
3853
|
+
"name": "What is the capital of France?",
|
|
3854
|
+
"options": {
|
|
3855
|
+
"items": [
|
|
3856
|
+
{
|
|
3857
|
+
"option": 1,
|
|
3858
|
+
"text": "Paris",
|
|
3859
|
+
"correct": true
|
|
3860
|
+
},
|
|
3861
|
+
{
|
|
3862
|
+
"option": 2,
|
|
3863
|
+
"text": "Rome",
|
|
3864
|
+
"correct": false
|
|
3865
|
+
},
|
|
3866
|
+
{
|
|
3867
|
+
"option": 3,
|
|
3868
|
+
"text": "Berlin",
|
|
3869
|
+
"correct": false
|
|
3870
|
+
},
|
|
3871
|
+
{
|
|
3872
|
+
"option": 4,
|
|
3873
|
+
"text": "Madrid",
|
|
3874
|
+
"correct": false
|
|
3875
|
+
}
|
|
3876
|
+
]
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
]
|
|
3880
|
+
}
|
|
3881
|
+
]
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
},
|
|
3885
|
+
"expected": {
|
|
3886
|
+
"errors": [],
|
|
3887
|
+
"warnings": []
|
|
3888
|
+
}
|
|
3889
|
+
},
|
|
3083
3890
|
{
|
|
3084
3891
|
"name": "group-size-ungrouped-run",
|
|
3085
3892
|
"input": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quizwitz-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Evaluates the findings (validation issues and surprising-settings warnings) the QuizWitz game client's type manifests declare, for the quiz editor and the MCP server.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|