@intellectif/lk-core 0.12.0 → 0.13.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/CHANGELOG.md +24 -0
- package/README.md +2 -1
- package/dist/{activity-B2QxK15j.d.cts → activity-DfAmJ1sl.d.cts} +134 -7
- package/dist/{activity-B2QxK15j.d.ts → activity-DfAmJ1sl.d.ts} +134 -7
- package/dist/{chunk-VYE6BYON.cjs → chunk-DQKJUMUW.cjs} +38 -15
- package/dist/chunk-DQKJUMUW.cjs.map +1 -0
- package/dist/{chunk-RK2NVTHZ.js → chunk-FGN4LLSP.js} +50 -6
- package/dist/chunk-FGN4LLSP.js.map +1 -0
- package/dist/{chunk-O2IVKTHV.cjs → chunk-FT7SAC3X.cjs} +4 -4
- package/dist/{chunk-O2IVKTHV.cjs.map → chunk-FT7SAC3X.cjs.map} +1 -1
- package/dist/{chunk-UQPNJP2O.js → chunk-PYP3HCRQ.js} +2 -2
- package/dist/{chunk-4OHWLCI6.cjs → chunk-UHQVIC3P.cjs} +58 -14
- package/dist/chunk-UHQVIC3P.cjs.map +1 -0
- package/dist/{chunk-FVN37GIH.js → chunk-XKKKWKRH.js} +1944 -177
- package/dist/chunk-XKKKWKRH.js.map +1 -0
- package/dist/{chunk-J6POE4HL.cjs → chunk-XONKWL6D.cjs} +1785 -18
- package/dist/chunk-XONKWL6D.cjs.map +1 -0
- package/dist/{chunk-7JMMPE7O.js → chunk-Y36S3X3L.js} +26 -3
- package/dist/chunk-Y36S3X3L.js.map +1 -0
- package/dist/{index-B_scInLM.d.cts → index-Bz9wUigA.d.cts} +181 -4
- package/dist/{index-CRKtWP5G.d.ts → index-CJbbVE9o.d.ts} +181 -4
- package/dist/index.cjs +128 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -6
- package/dist/index.d.ts +14 -6
- package/dist/index.js +60 -5
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +17 -3
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +16 -2
- package/dist/scoring.cjs +19 -3
- package/dist/scoring.cjs.map +1 -1
- package/dist/scoring.d.cts +150 -5
- package/dist/scoring.d.ts +150 -5
- package/dist/scoring.js +18 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +1 -1
- package/vectors/README.md +9 -1
- package/vectors/scoring.json +12023 -1
- package/dist/chunk-4OHWLCI6.cjs.map +0 -1
- package/dist/chunk-7JMMPE7O.js.map +0 -1
- package/dist/chunk-FVN37GIH.js.map +0 -1
- package/dist/chunk-J6POE4HL.cjs.map +0 -1
- package/dist/chunk-RK2NVTHZ.js.map +0 -1
- package/dist/chunk-VYE6BYON.cjs.map +0 -1
- /package/dist/{chunk-UQPNJP2O.js.map → chunk-PYP3HCRQ.js.map} +0 -0
|
@@ -38,6 +38,876 @@ var DeferredScoringError = class extends Error {
|
|
|
38
38
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
// src/validation-scope.ts
|
|
42
|
+
var active = null;
|
|
43
|
+
function withValidationScope(run) {
|
|
44
|
+
if (active !== null) {
|
|
45
|
+
return run();
|
|
46
|
+
}
|
|
47
|
+
active = /* @__PURE__ */ new Map();
|
|
48
|
+
try {
|
|
49
|
+
return run();
|
|
50
|
+
} finally {
|
|
51
|
+
active = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function scopeCache(key, create) {
|
|
55
|
+
if (active === null) {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
let value = active.get(key);
|
|
59
|
+
if (value === void 0) {
|
|
60
|
+
value = create();
|
|
61
|
+
active.set(key, value);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/scoring/dictation/normalize.ts
|
|
67
|
+
var DICTATION_MAX_TRANSCRIPT_LENGTH = 2e3;
|
|
68
|
+
var DICTATION_MAX_TEXT_LENGTH = 8e3;
|
|
69
|
+
var DICTATION_MAX_EQUIVALENCE_LENGTH = 200;
|
|
70
|
+
var DICTATION_MAX_EQUIVALENCES = 100;
|
|
71
|
+
var DICTATION_MAX_ACCEPTED_TRANSCRIPTS = 10;
|
|
72
|
+
var WORKING_LENGTH = 2 * DICTATION_MAX_TEXT_LENGTH;
|
|
73
|
+
var UNSPACED_REVEAL_MIN_CHARACTERS = 4;
|
|
74
|
+
var cp = (...points) => String.fromCodePoint(...points);
|
|
75
|
+
var NEXT_LINE = cp(133);
|
|
76
|
+
var MIDDLE_DOT = cp(183);
|
|
77
|
+
var TIBETAN_TSHEG = cp(3851);
|
|
78
|
+
var COMBINING_KEYCAP = cp(8419);
|
|
79
|
+
var WAVING_BLACK_FLAG = 127988;
|
|
80
|
+
var ZWNJ = 8204;
|
|
81
|
+
var ZWJ = 8205;
|
|
82
|
+
var MONGOLIAN_VOWEL_SEPARATOR = 6158;
|
|
83
|
+
var JOINERS = cp(ZWNJ, ZWJ, MONGOLIAN_VOWEL_SEPARATOR);
|
|
84
|
+
var MONGOLIAN_SELECTORS = cp(6155, 6156, 6157, 6159);
|
|
85
|
+
var TAGS = `${cp(917536)}-${cp(917631)}`;
|
|
86
|
+
var STRETCHERS = cp(1600, 2042);
|
|
87
|
+
var COMPATIBILITY_RE = new RegExp(
|
|
88
|
+
`[${[
|
|
89
|
+
cp(170),
|
|
90
|
+
cp(178, 179, 181, 185, 186),
|
|
91
|
+
`${cp(188)}-${cp(190)}`,
|
|
92
|
+
`${cp(306)}-${cp(307)}`,
|
|
93
|
+
`${cp(319)}-${cp(320)}`,
|
|
94
|
+
cp(329),
|
|
95
|
+
`${cp(452)}-${cp(460)}`,
|
|
96
|
+
`${cp(497)}-${cp(499)}`,
|
|
97
|
+
`${cp(8304)}-${cp(8351)}`,
|
|
98
|
+
`${cp(8448)}-${cp(8591)}`,
|
|
99
|
+
`${cp(9312)}-${cp(9471)}`,
|
|
100
|
+
`${cp(11904)}-${cp(12255)}`,
|
|
101
|
+
`${cp(12881)}-${cp(12895)}`,
|
|
102
|
+
`${cp(12977)}-${cp(12991)}`,
|
|
103
|
+
`${cp(64256)}-${cp(65023)}`,
|
|
104
|
+
`${cp(65040)}-${cp(65049)}`,
|
|
105
|
+
`${cp(65072)}-${cp(65135)}`,
|
|
106
|
+
`${cp(65136)}-${cp(65278)}`,
|
|
107
|
+
`${cp(65281)}-${cp(65373)}`,
|
|
108
|
+
`${cp(65375)}-${cp(65506)}`,
|
|
109
|
+
`${cp(65508)}-${cp(65518)}`
|
|
110
|
+
].join("")}]`,
|
|
111
|
+
"gu"
|
|
112
|
+
);
|
|
113
|
+
var COMPATIBILITY_JAMO = new Map(
|
|
114
|
+
Array.from({ length: 12686 - 12593 + 1 }, (_, index) => {
|
|
115
|
+
const jamo = cp(12593 + index);
|
|
116
|
+
return [jamo.normalize("NFKC"), jamo];
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
var SEQUENCE_FOLDS = /* @__PURE__ */ new Map([
|
|
120
|
+
[cp(3363, 3405, ZWJ), cp(3450)],
|
|
121
|
+
[cp(3368, 3405, ZWJ), cp(3451)],
|
|
122
|
+
[cp(3376, 3405, ZWJ), cp(3452)],
|
|
123
|
+
[cp(3378, 3405, ZWJ), cp(3453)],
|
|
124
|
+
[cp(3379, 3405, ZWJ), cp(3454)],
|
|
125
|
+
[cp(3349, 3405, ZWJ), cp(3455)],
|
|
126
|
+
[cp(3374, 3405, ZWJ), cp(3412)],
|
|
127
|
+
[cp(3375, 3405, ZWJ), cp(3413)],
|
|
128
|
+
[cp(3380, 3405, ZWJ), cp(3414)],
|
|
129
|
+
[cp(2468, 2509, ZWJ), cp(2510)],
|
|
130
|
+
[cp(3368, 3405, 3377), cp(3451, 3405, 3377)],
|
|
131
|
+
[cp(2352, 2381, ZWJ), cp(2353, 2381)],
|
|
132
|
+
[cp(2353, 2381, ZWJ), cp(2353, 2381)]
|
|
133
|
+
]);
|
|
134
|
+
var SEQUENCE_FOLDS_RE = new RegExp([...SEQUENCE_FOLDS.keys()].join("|"), "gu");
|
|
135
|
+
var THAI_SARA_AM_PARTS_RE = new RegExp(
|
|
136
|
+
`${cp(3661)}([${cp(3656)}-${cp(3659)}]*)${cp(3634)}`,
|
|
137
|
+
"gu"
|
|
138
|
+
);
|
|
139
|
+
var LAO_SARA_AM_PARTS_RE = new RegExp(
|
|
140
|
+
`${cp(3789)}([${cp(3784)}-${cp(3787)}]*)${cp(3762)}`,
|
|
141
|
+
"gu"
|
|
142
|
+
);
|
|
143
|
+
function foldSequences(text) {
|
|
144
|
+
return text.replace(SEQUENCE_FOLDS_RE, (sequence) => SEQUENCE_FOLDS.get(sequence)).replace(THAI_SARA_AM_PARTS_RE, `$1${cp(3635)}`).replace(LAO_SARA_AM_PARTS_RE, `$1${cp(3763)}`);
|
|
145
|
+
}
|
|
146
|
+
var FOLDED = new Map(
|
|
147
|
+
[
|
|
148
|
+
[8216, "'"],
|
|
149
|
+
// LEFT SINGLE QUOTATION MARK
|
|
150
|
+
[8217, "'"],
|
|
151
|
+
// RIGHT SINGLE QUOTATION MARK (the smart apostrophe)
|
|
152
|
+
[8218, "'"],
|
|
153
|
+
// SINGLE LOW-9 QUOTATION MARK
|
|
154
|
+
[8219, "'"],
|
|
155
|
+
// SINGLE HIGH-REVERSED-9 QUOTATION MARK
|
|
156
|
+
[8242, "'"],
|
|
157
|
+
// PRIME
|
|
158
|
+
[700, "'"],
|
|
159
|
+
// MODIFIER LETTER APOSTROPHE
|
|
160
|
+
[180, "'"],
|
|
161
|
+
// ACUTE ACCENT, used as an apostrophe
|
|
162
|
+
[96, "'"],
|
|
163
|
+
// GRAVE ACCENT, used as an apostrophe
|
|
164
|
+
[1523, "'"],
|
|
165
|
+
// HEBREW PUNCTUATION GERESH
|
|
166
|
+
[8220, '"'],
|
|
167
|
+
// LEFT DOUBLE QUOTATION MARK
|
|
168
|
+
[8221, '"'],
|
|
169
|
+
// RIGHT DOUBLE QUOTATION MARK
|
|
170
|
+
[8222, '"'],
|
|
171
|
+
// DOUBLE LOW-9 QUOTATION MARK
|
|
172
|
+
[8243, '"'],
|
|
173
|
+
// DOUBLE PRIME
|
|
174
|
+
[1524, '"'],
|
|
175
|
+
// HEBREW PUNCTUATION GERSHAYIM
|
|
176
|
+
[8208, "-"],
|
|
177
|
+
// HYPHEN
|
|
178
|
+
[8209, "-"],
|
|
179
|
+
// NON-BREAKING HYPHEN
|
|
180
|
+
[1470, "-"],
|
|
181
|
+
// HEBREW PUNCTUATION MAQAF
|
|
182
|
+
[1418, "-"],
|
|
183
|
+
// ARMENIAN HYPHEN
|
|
184
|
+
[3852, TIBETAN_TSHEG],
|
|
185
|
+
// TIBETAN MARK DELIMITER TSHEG BSTAR
|
|
186
|
+
[65374, cp(12316)]
|
|
187
|
+
// FULLWIDTH TILDE, which a Japanese keyboard types for the WAVE DASH
|
|
188
|
+
].map(([point, folded]) => [cp(point), folded])
|
|
189
|
+
);
|
|
190
|
+
var FOLDED_RE = new RegExp(`[${[...FOLDED.keys()].join("")}]`, "gu");
|
|
191
|
+
var DRAWN_FORMAT_CHARACTERS = `${cp(1536)}-${cp(1541)}${cp(1757, 1807, 2192, 2193, 2274, 69821, 69837)}`;
|
|
192
|
+
var INVISIBLE_RE = new RegExp(
|
|
193
|
+
`(?![\\t\\n\\v\\f\\r${NEXT_LINE}${JOINERS}${MONGOLIAN_SELECTORS}${TAGS}${DRAWN_FORMAT_CHARACTERS}])[\\p{Cc}\\p{Cf}\\p{Default_Ignorable_Code_Point}${STRETCHERS}]`,
|
|
194
|
+
"gu"
|
|
195
|
+
);
|
|
196
|
+
var REPEATED_JOINER_RE = new RegExp(`([${JOINERS}])\\1+`, "gu");
|
|
197
|
+
var LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
198
|
+
var REPLACEMENT_CHARACTER = cp(65533);
|
|
199
|
+
var CONTEXTUAL_RE = new RegExp(`[${JOINERS}${MONGOLIAN_SELECTORS}${TAGS}]`, "u");
|
|
200
|
+
var WHITESPACE_RE = new RegExp(`[\\s${NEXT_LINE}]+`, "gu");
|
|
201
|
+
var UNSPACED_CLASS = "\\p{Script_Extensions=Han}\\p{Script_Extensions=Hiragana}\\p{Script_Extensions=Katakana}\\p{Script_Extensions=Thai}\\p{Script_Extensions=Lao}\\p{Script_Extensions=Khmer}\\p{Script_Extensions=Myanmar}";
|
|
202
|
+
var UNSPACED_CHARACTER = `(?!\\p{Nd})[${UNSPACED_CLASS}]`;
|
|
203
|
+
var IN_WORD_PUNCTUATION = `'\\-${MIDDLE_DOT}${TIBETAN_TSHEG}`;
|
|
204
|
+
var PUNCTUATION_RE = new RegExp(
|
|
205
|
+
[
|
|
206
|
+
`[${IN_WORD_PUNCTUATION}](?<![\\p{L}\\p{M}\\p{N}][${IN_WORD_PUNCTUATION}])`,
|
|
207
|
+
`[${IN_WORD_PUNCTUATION}](?![\\p{L}\\p{M}\\p{N}])`,
|
|
208
|
+
`${MIDDLE_DOT}(?<=${UNSPACED_CHARACTER}${MIDDLE_DOT})`,
|
|
209
|
+
`${MIDDLE_DOT}(?=${UNSPACED_CHARACTER})`,
|
|
210
|
+
`(?![#*]${COMBINING_KEYCAP})[^\\P{P}${IN_WORD_PUNCTUATION}]`
|
|
211
|
+
].join("|"),
|
|
212
|
+
"gu"
|
|
213
|
+
);
|
|
214
|
+
var ALL_PUNCTUATION_RE = new RegExp(`(?![#*]${COMBINING_KEYCAP})\\p{P}`, "gu");
|
|
215
|
+
var DIGIT_SEPARATOR_RE = new RegExp(`(?<=\\p{N})[.,'${cp(1643, 1644)}](?=\\p{N})`, "gu");
|
|
216
|
+
var UNSPACED_LETTERS_RE = new RegExp(`(?![\\p{M}\\p{Nd}])[${UNSPACED_CLASS}]`, "gu");
|
|
217
|
+
var SPACE_BESIDE_UNSPACED_RE = new RegExp(
|
|
218
|
+
`(?<=${UNSPACED_CHARACTER}) (?=[\\p{L}\\p{M}\\p{N}])|(?<=[\\p{L}\\p{M}\\p{N}]) (?=${UNSPACED_CHARACTER})`,
|
|
219
|
+
"gu"
|
|
220
|
+
);
|
|
221
|
+
var WORD_RE = new RegExp(`^[\\p{L}\\p{M}\\p{N}${JOINERS}]$`, "u");
|
|
222
|
+
var UNSPACED_CHARACTER_RE = new RegExp(`^${UNSPACED_CHARACTER}$`, "u");
|
|
223
|
+
var EXTENDING_RE = new RegExp(`^[\\p{M}${JOINERS}${TAGS}]$`, "u");
|
|
224
|
+
var WORD = 1;
|
|
225
|
+
var UNSPACED = 2;
|
|
226
|
+
var EXTENDING = 4;
|
|
227
|
+
var KNOWN = 8;
|
|
228
|
+
var FLAGS = [];
|
|
229
|
+
function flagsOfCharacter(character) {
|
|
230
|
+
return KNOWN | (WORD_RE.test(character) ? WORD : 0) | (UNSPACED_CHARACTER_RE.test(character) ? UNSPACED : 0) | (EXTENDING_RE.test(character) ? EXTENDING : 0);
|
|
231
|
+
}
|
|
232
|
+
function flagsAt(text, index) {
|
|
233
|
+
const point = text.codePointAt(index);
|
|
234
|
+
let plane = FLAGS[point >> 16];
|
|
235
|
+
if (plane === void 0) {
|
|
236
|
+
plane = new Uint8Array(65536);
|
|
237
|
+
FLAGS[point >> 16] = plane;
|
|
238
|
+
}
|
|
239
|
+
const offset = point & 65535;
|
|
240
|
+
let flags = plane[offset];
|
|
241
|
+
if (flags === 0) {
|
|
242
|
+
flags = flagsOfCharacter(String.fromCodePoint(point));
|
|
243
|
+
plane[offset] = flags;
|
|
244
|
+
}
|
|
245
|
+
return flags;
|
|
246
|
+
}
|
|
247
|
+
var VIRAMAS = /* @__PURE__ */ new Set([
|
|
248
|
+
2381,
|
|
249
|
+
2509,
|
|
250
|
+
2637,
|
|
251
|
+
2765,
|
|
252
|
+
2893,
|
|
253
|
+
3021,
|
|
254
|
+
3149,
|
|
255
|
+
3277,
|
|
256
|
+
3387,
|
|
257
|
+
3388,
|
|
258
|
+
3405,
|
|
259
|
+
3530,
|
|
260
|
+
3642,
|
|
261
|
+
3770,
|
|
262
|
+
3972,
|
|
263
|
+
4153,
|
|
264
|
+
4154,
|
|
265
|
+
5908,
|
|
266
|
+
5909,
|
|
267
|
+
5940,
|
|
268
|
+
6098,
|
|
269
|
+
6752,
|
|
270
|
+
6980,
|
|
271
|
+
7082,
|
|
272
|
+
7083,
|
|
273
|
+
7154,
|
|
274
|
+
7155,
|
|
275
|
+
11647,
|
|
276
|
+
43014,
|
|
277
|
+
43052,
|
|
278
|
+
43204,
|
|
279
|
+
43347,
|
|
280
|
+
43456,
|
|
281
|
+
43766,
|
|
282
|
+
44013,
|
|
283
|
+
68159,
|
|
284
|
+
69702,
|
|
285
|
+
69744,
|
|
286
|
+
69759,
|
|
287
|
+
69817,
|
|
288
|
+
69939,
|
|
289
|
+
69940,
|
|
290
|
+
70080,
|
|
291
|
+
70197,
|
|
292
|
+
70378,
|
|
293
|
+
70477,
|
|
294
|
+
70606,
|
|
295
|
+
70607,
|
|
296
|
+
70608,
|
|
297
|
+
70722,
|
|
298
|
+
70850,
|
|
299
|
+
71103,
|
|
300
|
+
71231,
|
|
301
|
+
71350,
|
|
302
|
+
71467,
|
|
303
|
+
71737,
|
|
304
|
+
71997,
|
|
305
|
+
71998,
|
|
306
|
+
72160,
|
|
307
|
+
72244,
|
|
308
|
+
72263,
|
|
309
|
+
72345,
|
|
310
|
+
72767,
|
|
311
|
+
73028,
|
|
312
|
+
73029,
|
|
313
|
+
73111,
|
|
314
|
+
73537,
|
|
315
|
+
73538,
|
|
316
|
+
90415
|
|
317
|
+
]);
|
|
318
|
+
var JOINS_FORWARD = [
|
|
319
|
+
[1568, 1568],
|
|
320
|
+
[1574, 1574],
|
|
321
|
+
[1576, 1576],
|
|
322
|
+
[1578, 1582],
|
|
323
|
+
[1587, 1599],
|
|
324
|
+
[1601, 1607],
|
|
325
|
+
[1609, 1610],
|
|
326
|
+
[1646, 1647],
|
|
327
|
+
[1656, 1671],
|
|
328
|
+
[1690, 1727],
|
|
329
|
+
[1729, 1730],
|
|
330
|
+
[1740, 1740],
|
|
331
|
+
[1742, 1742],
|
|
332
|
+
[1744, 1745],
|
|
333
|
+
[1786, 1788],
|
|
334
|
+
[1791, 1791],
|
|
335
|
+
[1810, 1812],
|
|
336
|
+
[1818, 1821],
|
|
337
|
+
[1823, 1831],
|
|
338
|
+
[1833, 1833],
|
|
339
|
+
[1835, 1835],
|
|
340
|
+
[1837, 1838],
|
|
341
|
+
[1870, 1880],
|
|
342
|
+
[1884, 1898],
|
|
343
|
+
[1901, 1904],
|
|
344
|
+
[1906, 1906],
|
|
345
|
+
[1909, 1911],
|
|
346
|
+
[1914, 1919],
|
|
347
|
+
[1994, 2026],
|
|
348
|
+
[2113, 2117],
|
|
349
|
+
[2120, 2120],
|
|
350
|
+
[2122, 2131],
|
|
351
|
+
[2133, 2133],
|
|
352
|
+
[2144, 2144],
|
|
353
|
+
[2146, 2149],
|
|
354
|
+
[2152, 2152],
|
|
355
|
+
[2182, 2182],
|
|
356
|
+
[2185, 2189],
|
|
357
|
+
[2208, 2217],
|
|
358
|
+
[2223, 2224],
|
|
359
|
+
[2227, 2232],
|
|
360
|
+
[2234, 2248],
|
|
361
|
+
[6151, 6151],
|
|
362
|
+
[6176, 6264],
|
|
363
|
+
[6279, 6312],
|
|
364
|
+
[6314, 6314],
|
|
365
|
+
[43072, 43122],
|
|
366
|
+
[68288, 68292],
|
|
367
|
+
[68301, 68301],
|
|
368
|
+
[68307, 68316],
|
|
369
|
+
[68318, 68320],
|
|
370
|
+
[68331, 68334],
|
|
371
|
+
[68480, 68480],
|
|
372
|
+
[68482, 68482],
|
|
373
|
+
[68486, 68488],
|
|
374
|
+
[68490, 68491],
|
|
375
|
+
[68493, 68493],
|
|
376
|
+
[68496, 68496],
|
|
377
|
+
[68525, 68526],
|
|
378
|
+
[68864, 68897],
|
|
379
|
+
[68899, 68899],
|
|
380
|
+
[69315, 69316],
|
|
381
|
+
[69424, 69426],
|
|
382
|
+
[69428, 69444],
|
|
383
|
+
[69457, 69459],
|
|
384
|
+
[69488, 69491],
|
|
385
|
+
[69494, 69505],
|
|
386
|
+
[69552, 69552],
|
|
387
|
+
[69554, 69555],
|
|
388
|
+
[69560, 69560],
|
|
389
|
+
[69563, 69564],
|
|
390
|
+
[69566, 69567],
|
|
391
|
+
[69569, 69569],
|
|
392
|
+
[69572, 69572],
|
|
393
|
+
[69578, 69579],
|
|
394
|
+
[125184, 125251]
|
|
395
|
+
];
|
|
396
|
+
var JOINS_BACKWARD = [
|
|
397
|
+
[1568, 1568],
|
|
398
|
+
[1570, 1599],
|
|
399
|
+
[1601, 1610],
|
|
400
|
+
[1646, 1647],
|
|
401
|
+
[1649, 1651],
|
|
402
|
+
[1653, 1747],
|
|
403
|
+
[1749, 1749],
|
|
404
|
+
[1774, 1775],
|
|
405
|
+
[1786, 1788],
|
|
406
|
+
[1791, 1791],
|
|
407
|
+
[1808, 1808],
|
|
408
|
+
[1810, 1839],
|
|
409
|
+
[1869, 1919],
|
|
410
|
+
[1994, 2026],
|
|
411
|
+
[2112, 2136],
|
|
412
|
+
[2144, 2144],
|
|
413
|
+
[2146, 2149],
|
|
414
|
+
[2151, 2154],
|
|
415
|
+
[2160, 2178],
|
|
416
|
+
[2182, 2182],
|
|
417
|
+
[2185, 2190],
|
|
418
|
+
[2208, 2220],
|
|
419
|
+
[2222, 2248],
|
|
420
|
+
[6151, 6151],
|
|
421
|
+
[6176, 6264],
|
|
422
|
+
[6279, 6312],
|
|
423
|
+
[6314, 6314],
|
|
424
|
+
[43072, 43121],
|
|
425
|
+
[68288, 68293],
|
|
426
|
+
[68295, 68295],
|
|
427
|
+
[68297, 68298],
|
|
428
|
+
[68302, 68310],
|
|
429
|
+
[68312, 68321],
|
|
430
|
+
[68324, 68324],
|
|
431
|
+
[68331, 68335],
|
|
432
|
+
[68480, 68497],
|
|
433
|
+
[68521, 68526],
|
|
434
|
+
[68865, 68899],
|
|
435
|
+
[69314, 69316],
|
|
436
|
+
[69424, 69444],
|
|
437
|
+
[69457, 69460],
|
|
438
|
+
[69488, 69505],
|
|
439
|
+
[69552, 69552],
|
|
440
|
+
[69554, 69558],
|
|
441
|
+
[69560, 69567],
|
|
442
|
+
[69569, 69572],
|
|
443
|
+
[69577, 69578],
|
|
444
|
+
[125184, 125251]
|
|
445
|
+
];
|
|
446
|
+
var TRANSPARENT_RE = new RegExp(`^[\\p{Mn}\\p{Me}${cp(125259)}${TAGS}]$`, "u");
|
|
447
|
+
function collapseWhitespace(text) {
|
|
448
|
+
return text.replace(WHITESPACE_RE, " ").trim();
|
|
449
|
+
}
|
|
450
|
+
function codePointLength(text) {
|
|
451
|
+
let length = 0;
|
|
452
|
+
for (const _ of text) {
|
|
453
|
+
length += 1;
|
|
454
|
+
}
|
|
455
|
+
return length;
|
|
456
|
+
}
|
|
457
|
+
function truncateCodePoints(text, max) {
|
|
458
|
+
if (text.length <= max) {
|
|
459
|
+
return text;
|
|
460
|
+
}
|
|
461
|
+
let points = 0;
|
|
462
|
+
let units = 0;
|
|
463
|
+
for (const character of text) {
|
|
464
|
+
if (points === max) {
|
|
465
|
+
return text.slice(0, units);
|
|
466
|
+
}
|
|
467
|
+
points += 1;
|
|
468
|
+
units += character.length;
|
|
469
|
+
}
|
|
470
|
+
return text;
|
|
471
|
+
}
|
|
472
|
+
function isLongerThan(text, max) {
|
|
473
|
+
if (text.length <= max) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
return text.length > 2 * max || truncateCodePoints(text, max).length < text.length;
|
|
477
|
+
}
|
|
478
|
+
function inRanges(point, ranges) {
|
|
479
|
+
return ranges.some(([first, last]) => point >= first && point <= last);
|
|
480
|
+
}
|
|
481
|
+
function isMongolianLetterOrMark(point) {
|
|
482
|
+
return point !== void 0 && point >= 6176 && point <= 6314;
|
|
483
|
+
}
|
|
484
|
+
function isMongolianSelector(point) {
|
|
485
|
+
return point !== void 0 && (point >= 6155 && point <= 6157 || point === 6159);
|
|
486
|
+
}
|
|
487
|
+
function letterBefore(points, index) {
|
|
488
|
+
for (let at = index - 1; at >= 0; at -= 1) {
|
|
489
|
+
const point = points[at];
|
|
490
|
+
if (!TRANSPARENT_RE.test(point)) {
|
|
491
|
+
return point.codePointAt(0);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return void 0;
|
|
495
|
+
}
|
|
496
|
+
function letterAfter(points, index) {
|
|
497
|
+
for (let at = index + 1; at < points.length; at += 1) {
|
|
498
|
+
const point = points[at];
|
|
499
|
+
if (!TRANSPARENT_RE.test(point)) {
|
|
500
|
+
return point.codePointAt(0);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return void 0;
|
|
504
|
+
}
|
|
505
|
+
var LETTER_OR_MARK_RE = /^[\p{L}\p{M}]$/u;
|
|
506
|
+
function keepsInContext(points, index) {
|
|
507
|
+
const point = points[index].codePointAt(0);
|
|
508
|
+
const previous = points[index - 1];
|
|
509
|
+
const afterVirama = previous !== void 0 && VIRAMAS.has(previous.codePointAt(0));
|
|
510
|
+
if (point === ZWJ) {
|
|
511
|
+
const next = points[index + 1];
|
|
512
|
+
return afterVirama || previous !== void 0 && LETTER_OR_MARK_RE.test(previous) && next !== void 0 && VIRAMAS.has(next.codePointAt(0));
|
|
513
|
+
}
|
|
514
|
+
if (point === ZWNJ) {
|
|
515
|
+
if (afterVirama) {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
const before = letterBefore(points, index);
|
|
519
|
+
const after = letterAfter(points, index);
|
|
520
|
+
return before !== void 0 && after !== void 0 && inRanges(before, JOINS_FORWARD) && inRanges(after, JOINS_BACKWARD);
|
|
521
|
+
}
|
|
522
|
+
const previousPoint = _optionalChain([previous, 'optionalAccess', _2 => _2.codePointAt, 'call', _3 => _3(0)]);
|
|
523
|
+
if (point === MONGOLIAN_VOWEL_SEPARATOR) {
|
|
524
|
+
return (isMongolianLetterOrMark(previousPoint) || isMongolianSelector(previousPoint)) && isMongolianLetterOrMark(_optionalChain([points, 'access', _4 => _4[index + 1], 'optionalAccess', _5 => _5.codePointAt, 'call', _6 => _6(0)]));
|
|
525
|
+
}
|
|
526
|
+
return isMongolianLetterOrMark(previousPoint);
|
|
527
|
+
}
|
|
528
|
+
function settle(text) {
|
|
529
|
+
let current = text;
|
|
530
|
+
for (; ; ) {
|
|
531
|
+
let next = foldSequences(current);
|
|
532
|
+
if (CONTEXTUAL_RE.test(next)) {
|
|
533
|
+
const points = Array.from(next.replace(REPEATED_JOINER_RE, "$1"));
|
|
534
|
+
let inFlag = false;
|
|
535
|
+
next = points.filter((character, index) => {
|
|
536
|
+
const point = character.codePointAt(0);
|
|
537
|
+
if (point >= 917536 && point <= 917631) {
|
|
538
|
+
return inFlag;
|
|
539
|
+
}
|
|
540
|
+
inFlag = point === WAVING_BLACK_FLAG;
|
|
541
|
+
return !CONTEXTUAL_RE.test(character) || keepsInContext(points, index);
|
|
542
|
+
}).join("");
|
|
543
|
+
}
|
|
544
|
+
next = collapseWhitespace(next).normalize("NFC");
|
|
545
|
+
if (next === current) {
|
|
546
|
+
return current;
|
|
547
|
+
}
|
|
548
|
+
current = next;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
var CARRIED_MARKS_RE = new RegExp(`^[ ${cp(1600)}]?(\\p{M}+)$`, "u");
|
|
552
|
+
var SPACING_CHARACTER_RE = new RegExp(`[\\s${NEXT_LINE}]`, "u");
|
|
553
|
+
var PUNCTUATION_CHARACTER_RE = /^\p{P}$/u;
|
|
554
|
+
function foldCompatibility(character, offset, text) {
|
|
555
|
+
const folded = character.normalize("NFKC");
|
|
556
|
+
const carried = CARRIED_MARKS_RE.exec(folded);
|
|
557
|
+
if (carried === null) {
|
|
558
|
+
return _nullishCoalesce(COMPATIBILITY_JAMO.get(folded), () => ( folded));
|
|
559
|
+
}
|
|
560
|
+
return PUNCTUATION_CHARACTER_RE.test(character) || offset === 0 || SPACING_CHARACTER_RE.test(text.charAt(offset - 1)) ? character : carried[1];
|
|
561
|
+
}
|
|
562
|
+
function preStripNormalize(text) {
|
|
563
|
+
return settle(
|
|
564
|
+
collapseWhitespace(
|
|
565
|
+
text.replace(LONE_SURROGATE_RE, REPLACEMENT_CHARACTER).normalize("NFC").replace(COMPATIBILITY_RE, foldCompatibility).replace(INVISIBLE_RE, "").replace(FOLDED_RE, (character) => FOLDED.get(character)).toLowerCase()
|
|
566
|
+
).normalize("NFC")
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
function settleWithout(text, pattern, replacement) {
|
|
570
|
+
let current = text;
|
|
571
|
+
for (; ; ) {
|
|
572
|
+
const next = settle(current.replace(pattern, replacement));
|
|
573
|
+
if (next === current) {
|
|
574
|
+
return current;
|
|
575
|
+
}
|
|
576
|
+
current = next;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function stripPunctuation(text) {
|
|
580
|
+
return settleWithout(text, PUNCTUATION_RE, "");
|
|
581
|
+
}
|
|
582
|
+
function spacePunctuation(text) {
|
|
583
|
+
return settleWithout(text.replace(DIGIT_SEPARATOR_RE, ""), ALL_PUNCTUATION_RE, " ");
|
|
584
|
+
}
|
|
585
|
+
function isHighSurrogate(unit) {
|
|
586
|
+
return unit >= 55296 && unit <= 56319;
|
|
587
|
+
}
|
|
588
|
+
function isLowSurrogate(unit) {
|
|
589
|
+
return unit >= 56320 && unit <= 57343;
|
|
590
|
+
}
|
|
591
|
+
function startOfCodePointBefore(text, index) {
|
|
592
|
+
return index >= 2 && isLowSurrogate(text.charCodeAt(index - 1)) && isHighSurrogate(text.charCodeAt(index - 2)) ? index - 2 : index - 1;
|
|
593
|
+
}
|
|
594
|
+
function lastCodePointAfter(previous, appended) {
|
|
595
|
+
return appended === "" ? previous : appended.slice(startOfCodePointBefore(appended, appended.length));
|
|
596
|
+
}
|
|
597
|
+
function isAsciiWordUnit(unit) {
|
|
598
|
+
return unit >= 48 && unit <= 57 || unit >= 97 && unit <= 122;
|
|
599
|
+
}
|
|
600
|
+
function isWordAt(text, index) {
|
|
601
|
+
const unit = text.charCodeAt(index);
|
|
602
|
+
return unit < 128 ? isAsciiWordUnit(unit) : (flagsAt(text, index) & WORD) !== 0;
|
|
603
|
+
}
|
|
604
|
+
function isSpacedWordAt(text, index) {
|
|
605
|
+
const unit = text.charCodeAt(index);
|
|
606
|
+
return unit < 128 ? isAsciiWordUnit(unit) : (flagsAt(text, index) & (WORD | UNSPACED)) === WORD;
|
|
607
|
+
}
|
|
608
|
+
function isExtendingAt(text, index) {
|
|
609
|
+
return text.charCodeAt(index) >= 128 && (flagsAt(text, index) & EXTENDING) !== 0;
|
|
610
|
+
}
|
|
611
|
+
function edgeOf(text, index, unspaced) {
|
|
612
|
+
if (!isWordAt(text, index)) {
|
|
613
|
+
return "none";
|
|
614
|
+
}
|
|
615
|
+
return isSpacedWordAt(text, index) ? "spaced" : unspaced;
|
|
616
|
+
}
|
|
617
|
+
function phraseOf(text, unspaced) {
|
|
618
|
+
return {
|
|
619
|
+
text,
|
|
620
|
+
before: edgeOf(text, 0, unspaced),
|
|
621
|
+
after: edgeOf(text, startOfCodePointBefore(text, text.length), unspaced)
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
function blocks(edge, text, neighbour) {
|
|
625
|
+
if (edge === "none") {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
return edge === "spaced" ? isSpacedWordAt(text, neighbour) : isWordAt(text, neighbour);
|
|
629
|
+
}
|
|
630
|
+
function isMatchAt(text, phrase, at) {
|
|
631
|
+
const end = at + phrase.text.length;
|
|
632
|
+
if (end < text.length && (isExtendingAt(text, end) || blocks(phrase.after, text, end))) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
return !(at > 0 && blocks(phrase.before, text, startOfCodePointBefore(text, at)));
|
|
636
|
+
}
|
|
637
|
+
function indexOfPhrase(text, phrase, start) {
|
|
638
|
+
const needle = phrase.text;
|
|
639
|
+
let at = text.indexOf(needle, start);
|
|
640
|
+
while (at !== -1) {
|
|
641
|
+
if (phrase.before === "spaced" && at > 0 && isSpacedWordAt(text, startOfCodePointBefore(text, at))) {
|
|
642
|
+
let next = at;
|
|
643
|
+
while (next < text.length && isSpacedWordAt(text, next)) {
|
|
644
|
+
next += isHighSurrogate(text.charCodeAt(next)) && isLowSurrogate(text.charCodeAt(next + 1)) ? 2 : 1;
|
|
645
|
+
}
|
|
646
|
+
at = text.indexOf(needle, next);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (isMatchAt(text, phrase, at)) {
|
|
650
|
+
return at;
|
|
651
|
+
}
|
|
652
|
+
at = text.indexOf(needle, at + 1);
|
|
653
|
+
}
|
|
654
|
+
return -1;
|
|
655
|
+
}
|
|
656
|
+
function containsPhrase(text, phrase) {
|
|
657
|
+
const needle = phrase.text;
|
|
658
|
+
const length = needle.length;
|
|
659
|
+
if (length === 0 || length > text.length) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
const fallback = new Int32Array(length);
|
|
663
|
+
for (let index = 1, matched = 0; index < length; index += 1) {
|
|
664
|
+
while (matched > 0 && needle.charCodeAt(index) !== needle.charCodeAt(matched)) {
|
|
665
|
+
matched = fallback[matched - 1];
|
|
666
|
+
}
|
|
667
|
+
if (needle.charCodeAt(index) === needle.charCodeAt(matched)) {
|
|
668
|
+
matched += 1;
|
|
669
|
+
}
|
|
670
|
+
fallback[index] = matched;
|
|
671
|
+
}
|
|
672
|
+
for (let index = 0, matched = 0; index < text.length; index += 1) {
|
|
673
|
+
while (matched > 0 && text.charCodeAt(index) !== needle.charCodeAt(matched)) {
|
|
674
|
+
matched = fallback[matched - 1];
|
|
675
|
+
}
|
|
676
|
+
if (text.charCodeAt(index) === needle.charCodeAt(matched)) {
|
|
677
|
+
matched += 1;
|
|
678
|
+
}
|
|
679
|
+
if (matched === length) {
|
|
680
|
+
if (isMatchAt(text, phrase, index - length + 1)) {
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
matched = fallback[matched - 1];
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
function compileRules(tolerance) {
|
|
689
|
+
const equivalences = _optionalChain([tolerance, 'optionalAccess', _7 => _7.equivalences]);
|
|
690
|
+
if (!Array.isArray(equivalences)) {
|
|
691
|
+
return { rules: [], signature: "[]" };
|
|
692
|
+
}
|
|
693
|
+
const rules = [];
|
|
694
|
+
for (const rule of equivalences.slice(0, DICTATION_MAX_EQUIVALENCES)) {
|
|
695
|
+
const rawFrom = _optionalChain([rule, 'optionalAccess', _8 => _8.from]);
|
|
696
|
+
const rawTo = _optionalChain([rule, 'optionalAccess', _9 => _9.to]);
|
|
697
|
+
if (typeof rawFrom !== "string" || typeof rawTo !== "string" || isLongerThan(rawFrom, DICTATION_MAX_EQUIVALENCE_LENGTH) || isLongerThan(rawTo, DICTATION_MAX_EQUIVALENCE_LENGTH)) {
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
const from = preStripNormalize(rawFrom);
|
|
701
|
+
const to = stripPunctuation(preStripNormalize(rawTo));
|
|
702
|
+
if (from === "" || to === "") {
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
rules.push({
|
|
706
|
+
from: phraseOf(from, "none"),
|
|
707
|
+
to,
|
|
708
|
+
spaceBefore: isSpacedWordAt(to, 0),
|
|
709
|
+
spaceAfter: isSpacedWordAt(to, startOfCodePointBefore(to, to.length))
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
rules,
|
|
714
|
+
signature: JSON.stringify(rules.map((rule) => [rule.from.text, rule.to]))
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function bounded(text) {
|
|
718
|
+
return isLongerThan(text, WORKING_LENGTH) ? { text: truncateCodePoints(text, WORKING_LENGTH), cut: true } : { text, cut: false };
|
|
719
|
+
}
|
|
720
|
+
function rewrite(text, rule) {
|
|
721
|
+
let output = "";
|
|
722
|
+
let consumed = 0;
|
|
723
|
+
let last = "";
|
|
724
|
+
for (let at = indexOfPhrase(text, rule.from, 0); at !== -1; at = indexOfPhrase(text, rule.from, consumed)) {
|
|
725
|
+
const between = text.slice(consumed, at);
|
|
726
|
+
if (rule.spaceAfter && consumed > 0 && between !== "" && isSpacedWordAt(between, 0)) {
|
|
727
|
+
output += " ";
|
|
728
|
+
}
|
|
729
|
+
output += between;
|
|
730
|
+
last = lastCodePointAfter(last, between);
|
|
731
|
+
if (rule.spaceBefore && last !== "" && isSpacedWordAt(last, 0)) {
|
|
732
|
+
output += " ";
|
|
733
|
+
last = " ";
|
|
734
|
+
}
|
|
735
|
+
output += rule.to;
|
|
736
|
+
last = lastCodePointAfter(last, rule.to);
|
|
737
|
+
consumed = at + rule.from.text.length;
|
|
738
|
+
if (output.length > 2 * WORKING_LENGTH) {
|
|
739
|
+
return { text: truncateCodePoints(output, WORKING_LENGTH), cut: true };
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const rest = text.slice(consumed);
|
|
743
|
+
const apart = rule.spaceAfter && consumed > 0 && rest !== "" && isSpacedWordAt(rest, 0);
|
|
744
|
+
return bounded(apart ? `${output} ${rest}` : output + rest);
|
|
745
|
+
}
|
|
746
|
+
var WORKING_TEXTS = /* @__PURE__ */ Symbol("dictation working texts");
|
|
747
|
+
var MEASURED_TRANSCRIPTS = /* @__PURE__ */ Symbol("dictation measured transcripts");
|
|
748
|
+
var SHOWN_TEXTS = /* @__PURE__ */ Symbol("dictation shown texts");
|
|
749
|
+
var REVEALS = /* @__PURE__ */ Symbol("dictation reveal answers");
|
|
750
|
+
function scopedMap(key, signature) {
|
|
751
|
+
const bySignature = scopeCache(key, () => /* @__PURE__ */ new Map());
|
|
752
|
+
if (bySignature === void 0) {
|
|
753
|
+
return /* @__PURE__ */ new Map();
|
|
754
|
+
}
|
|
755
|
+
let map = bySignature.get(signature);
|
|
756
|
+
if (map === void 0) {
|
|
757
|
+
map = /* @__PURE__ */ new Map();
|
|
758
|
+
bySignature.set(signature, map);
|
|
759
|
+
}
|
|
760
|
+
return map;
|
|
761
|
+
}
|
|
762
|
+
var PRE_STRIP_TEXTS = /* @__PURE__ */ Symbol("dictation pre-strip texts");
|
|
763
|
+
function boundedPreStrip(text) {
|
|
764
|
+
const memo = scopeCache(PRE_STRIP_TEXTS, () => /* @__PURE__ */ new Map());
|
|
765
|
+
let result = _optionalChain([memo, 'optionalAccess', _10 => _10.get, 'call', _11 => _11(text)]);
|
|
766
|
+
if (result === void 0) {
|
|
767
|
+
result = bounded(preStripNormalize(text));
|
|
768
|
+
_optionalChain([memo, 'optionalAccess', _12 => _12.set, 'call', _13 => _13(text, result)]);
|
|
769
|
+
}
|
|
770
|
+
return result;
|
|
771
|
+
}
|
|
772
|
+
function isBlankBeforeRules(text) {
|
|
773
|
+
return boundedPreStrip(text).text === "";
|
|
774
|
+
}
|
|
775
|
+
function workingText(text, compiled, preStripped = boundedPreStrip(text)) {
|
|
776
|
+
if (compiled.rules.length === 0) {
|
|
777
|
+
return preStripped;
|
|
778
|
+
}
|
|
779
|
+
const memo = scopeCache(WORKING_TEXTS, () => /* @__PURE__ */ new Map());
|
|
780
|
+
let known = _optionalChain([memo, 'optionalAccess', _14 => _14.get, 'call', _15 => _15(compiled.signature)]);
|
|
781
|
+
const remembered = _optionalChain([known, 'optionalAccess', _16 => _16.get, 'call', _17 => _17(text)]);
|
|
782
|
+
if (remembered !== void 0) {
|
|
783
|
+
return remembered;
|
|
784
|
+
}
|
|
785
|
+
let working = preStripped;
|
|
786
|
+
let cut = working.cut;
|
|
787
|
+
for (const rule of compiled.rules) {
|
|
788
|
+
working = rewrite(working.text, rule);
|
|
789
|
+
cut ||= working.cut;
|
|
790
|
+
}
|
|
791
|
+
const result = { text: working.text, cut };
|
|
792
|
+
if (memo !== void 0) {
|
|
793
|
+
if (known === void 0) {
|
|
794
|
+
known = /* @__PURE__ */ new Map();
|
|
795
|
+
memo.set(compiled.signature, known);
|
|
796
|
+
}
|
|
797
|
+
known.set(text, result);
|
|
798
|
+
}
|
|
799
|
+
return result;
|
|
800
|
+
}
|
|
801
|
+
function normalizeDictationTextBounded(text, tolerance) {
|
|
802
|
+
const working = workingText(text, compileRules(tolerance));
|
|
803
|
+
return { text: stripPunctuation(working.text), cut: working.cut };
|
|
804
|
+
}
|
|
805
|
+
function normalizeDictationText(text, tolerance) {
|
|
806
|
+
return normalizeDictationTextBounded(text, tolerance).text;
|
|
807
|
+
}
|
|
808
|
+
var NOTHING_TO_REVEAL = { forms: [], long: false, before: "none", after: "none" };
|
|
809
|
+
function stripForReveal(text) {
|
|
810
|
+
return stripPunctuation(text.replace(DIGIT_SEPARATOR_RE, ""));
|
|
811
|
+
}
|
|
812
|
+
function readingOf(written, working) {
|
|
813
|
+
const writtenStripped = stripForReveal(written);
|
|
814
|
+
const workingStripped = stripForReveal(working);
|
|
815
|
+
const source = writtenStripped === "" ? workingStripped : writtenStripped;
|
|
816
|
+
if (source === "") {
|
|
817
|
+
return NOTHING_TO_REVEAL;
|
|
818
|
+
}
|
|
819
|
+
const long = (_nullishCoalesce(_optionalChain([source, 'access', _18 => _18.match, 'call', _19 => _19(UNSPACED_LETTERS_RE), 'optionalAccess', _20 => _20.length]), () => ( 0))) >= UNSPACED_REVEAL_MIN_CHARACTERS;
|
|
820
|
+
const { before, after } = phraseOf(source, long ? "none" : "visible");
|
|
821
|
+
const forms = [
|
|
822
|
+
writtenStripped,
|
|
823
|
+
spacePunctuation(written),
|
|
824
|
+
workingStripped,
|
|
825
|
+
spacePunctuation(working)
|
|
826
|
+
].map((form) => long ? form.replace(SPACE_BESIDE_UNSPACED_RE, "") : form);
|
|
827
|
+
return { forms: [...new Set(forms)].filter((form) => form !== ""), long, before, after };
|
|
828
|
+
}
|
|
829
|
+
function dictationNormalizer(tolerance) {
|
|
830
|
+
const compiled = compileRules(tolerance);
|
|
831
|
+
const measured = scopedMap(MEASURED_TRANSCRIPTS, compiled.signature);
|
|
832
|
+
const shownTexts = scopedMap(SHOWN_TEXTS, compiled.signature);
|
|
833
|
+
return {
|
|
834
|
+
measure(text) {
|
|
835
|
+
let result = measured.get(text);
|
|
836
|
+
if (result === void 0) {
|
|
837
|
+
if (isLongerThan(text, DICTATION_MAX_TRANSCRIPT_LENGTH)) {
|
|
838
|
+
result = { tooLong: true, normalized: null, reveal: NOTHING_TO_REVEAL };
|
|
839
|
+
} else {
|
|
840
|
+
const written = boundedPreStrip(text);
|
|
841
|
+
const working = workingText(text, compiled, written);
|
|
842
|
+
const normalized = stripPunctuation(working.text);
|
|
843
|
+
result = {
|
|
844
|
+
tooLong: working.cut || isLongerThan(normalized, DICTATION_MAX_TRANSCRIPT_LENGTH),
|
|
845
|
+
normalized,
|
|
846
|
+
reveal: readingOf(written.text, working.text)
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
measured.set(text, result);
|
|
850
|
+
}
|
|
851
|
+
return result;
|
|
852
|
+
},
|
|
853
|
+
shown(text) {
|
|
854
|
+
let result = shownTexts.get(text);
|
|
855
|
+
if (result === void 0) {
|
|
856
|
+
const plainText = isLongerThan(text, WORKING_LENGTH) ? null : boundedPreStrip(text);
|
|
857
|
+
const working = plainText === null ? null : workingText(text, compiled, plainText);
|
|
858
|
+
if (plainText === null || working === null || working.cut) {
|
|
859
|
+
result = { tooLong: true, forms: [] };
|
|
860
|
+
} else {
|
|
861
|
+
const plain = plainText.text;
|
|
862
|
+
result = {
|
|
863
|
+
tooLong: false,
|
|
864
|
+
forms: [
|
|
865
|
+
.../* @__PURE__ */ new Set([
|
|
866
|
+
stripForReveal(working.text),
|
|
867
|
+
spacePunctuation(working.text),
|
|
868
|
+
stripForReveal(plain),
|
|
869
|
+
spacePunctuation(plain)
|
|
870
|
+
])
|
|
871
|
+
]
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
shownTexts.set(text, result);
|
|
875
|
+
}
|
|
876
|
+
return result;
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
function revealsCandidate(forms, candidate) {
|
|
881
|
+
const memo = scopeCache(REVEALS, () => /* @__PURE__ */ new WeakMap());
|
|
882
|
+
const known = _optionalChain([memo, 'optionalAccess', _21 => _21.get, 'call', _22 => _22(forms), 'optionalAccess', _23 => _23.get, 'call', _24 => _24(candidate)]);
|
|
883
|
+
if (known !== void 0) {
|
|
884
|
+
return known;
|
|
885
|
+
}
|
|
886
|
+
const answer = searchReveal(forms, candidate);
|
|
887
|
+
if (memo !== void 0) {
|
|
888
|
+
const byCandidate = _nullishCoalesce(memo.get(forms), () => ( /* @__PURE__ */ new WeakMap()));
|
|
889
|
+
byCandidate.set(candidate, answer);
|
|
890
|
+
memo.set(forms, byCandidate);
|
|
891
|
+
}
|
|
892
|
+
return answer;
|
|
893
|
+
}
|
|
894
|
+
var COMPACTED_FORMS = /* @__PURE__ */ new WeakMap();
|
|
895
|
+
function searchReveal(forms, candidate) {
|
|
896
|
+
const { reveal } = candidate;
|
|
897
|
+
if (candidate.tooLong) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
let fields = forms;
|
|
901
|
+
if (reveal.long) {
|
|
902
|
+
fields = _nullishCoalesce(COMPACTED_FORMS.get(forms), () => ( forms.map((form) => form.replace(SPACE_BESIDE_UNSPACED_RE, ""))));
|
|
903
|
+
COMPACTED_FORMS.set(forms, fields);
|
|
904
|
+
}
|
|
905
|
+
return reveal.forms.some((text) => {
|
|
906
|
+
const phrase = { text, before: reveal.before, after: reveal.after };
|
|
907
|
+
return fields.some((field) => containsPhrase(field, phrase));
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
41
911
|
// src/schemas/feedback.ts
|
|
42
912
|
var _v4 = require('zod/v4');
|
|
43
913
|
var FeedbackSchema = _v4.z.looseObject({
|
|
@@ -98,22 +968,22 @@ var MediaSchema = _v4.z.looseObject({
|
|
|
98
968
|
error: "playback policy is supported on audio media only. An embed is a provider iframe the SDK cannot control at all (it has no reliable JS API for a third-party player); an image has nothing to play; video is deliberately deferred, because a video transport must also own fullscreen and Picture-in-Picture and the SDK will not pretend to govern those yet.",
|
|
99
969
|
path: ["playback"]
|
|
100
970
|
}).refine(
|
|
101
|
-
(m) => _optionalChain([m, 'access',
|
|
971
|
+
(m) => _optionalChain([m, 'access', _25 => _25.playback, 'optionalAccess', _26 => _26.controls]) !== "native" || m.playback.maxPlays === void 0 && m.playback.seek !== "none" && m.playback.rate !== "fixed",
|
|
102
972
|
{
|
|
103
973
|
error: 'controls: "native" cannot carry maxPlays, seek: "none" or rate: "fixed". The browser\'s own bar keeps its play button enabled after a budget is spent, and keeps a scrubber that would move and then silently snap back \u2014 a control that looks operable and does nothing (WCAG 3.2.2 / 4.1.3). Omit `controls` and the SDK transport is used automatically, or use nativeControlHints for a cosmetic hint.',
|
|
104
974
|
path: ["playback", "controls"]
|
|
105
975
|
}
|
|
106
|
-
).refine((m) => _optionalChain([m, 'access',
|
|
976
|
+
).refine((m) => _optionalChain([m, 'access', _27 => _27.playback, 'optionalAccess', _28 => _28.maxPlays]) === void 0 || m.playback.seek !== "allow", {
|
|
107
977
|
error: 'maxPlays cannot be combined with seek: "allow". A play is consumed when playback starts from somewhere other than where it stopped, so scrubbing back mid-play would replay the whole recording without spending anything. Omit `seek` \u2014 it resolves to "none" under a budget.',
|
|
108
978
|
path: ["playback", "seek"]
|
|
109
979
|
}).refine(
|
|
110
|
-
(m) => _optionalChain([m, 'access',
|
|
980
|
+
(m) => _optionalChain([m, 'access', _29 => _29.playback, 'optionalAccess', _30 => _30.controls]) !== "minimal" || m.playback.maxPlays !== void 0 || m.playback.seek === "none" || m.playback.rate === "fixed",
|
|
111
981
|
{
|
|
112
982
|
error: 'controls: "minimal" with nothing to enforce trades the browser\'s localized, familiar control bar for the SDK\'s, and buys nothing. Set maxPlays, seek: "none" or rate: "fixed", or omit `controls`.',
|
|
113
983
|
path: ["playback", "controls"]
|
|
114
984
|
}
|
|
115
985
|
).refine(
|
|
116
|
-
(m) => _optionalChain([m, 'access',
|
|
986
|
+
(m) => _optionalChain([m, 'access', _31 => _31.playback, 'optionalAccess', _32 => _32.nativeControlHints]) === void 0 || m.playback.controls !== "minimal" && m.playback.maxPlays === void 0 && m.playback.seek !== "none" && m.playback.rate !== "fixed",
|
|
117
987
|
{
|
|
118
988
|
error: "nativeControlHints only affects the browser's own control bar, and an enforcing policy replaces that bar with the SDK transport \u2014 so the hints would be silently inert. Use them on an otherwise unrestricted recording, or drop them.",
|
|
119
989
|
path: ["playback", "nativeControlHints"]
|
|
@@ -136,6 +1006,223 @@ var RedactedMediaSchema = _v4.z.strictObject({
|
|
|
136
1006
|
path: ["url"]
|
|
137
1007
|
});
|
|
138
1008
|
|
|
1009
|
+
// src/schemas/dictation.ts
|
|
1010
|
+
|
|
1011
|
+
var DictationSlowMediaSchema = _v4.z.looseObject({
|
|
1012
|
+
type: _v4.z.literal("audio"),
|
|
1013
|
+
url: MediaUrlSchema,
|
|
1014
|
+
alt: _v4.z.string().min(1).optional()
|
|
1015
|
+
});
|
|
1016
|
+
var RuleTextSchema = _v4.z.string().min(1).meta({ maxLength: DICTATION_MAX_EQUIVALENCE_LENGTH });
|
|
1017
|
+
var TranscriptTextSchema = _v4.z.string().min(1).meta({ maxLength: DICTATION_MAX_TRANSCRIPT_LENGTH });
|
|
1018
|
+
var DictationEquivalenceSchema = _v4.z.looseObject({
|
|
1019
|
+
from: RuleTextSchema,
|
|
1020
|
+
to: RuleTextSchema
|
|
1021
|
+
}).refine((rule) => codePointLength(rule.from) <= DICTATION_MAX_EQUIVALENCE_LENGTH, {
|
|
1022
|
+
error: `A rule rewrites at most ${DICTATION_MAX_EQUIVALENCE_LENGTH} characters.`,
|
|
1023
|
+
path: ["from"]
|
|
1024
|
+
}).refine((rule) => codePointLength(rule.to) <= DICTATION_MAX_EQUIVALENCE_LENGTH, {
|
|
1025
|
+
error: `A rewrite is at most ${DICTATION_MAX_EQUIVALENCE_LENGTH} characters.`,
|
|
1026
|
+
path: ["to"]
|
|
1027
|
+
});
|
|
1028
|
+
var DictationToleranceSchema = _v4.z.looseObject({
|
|
1029
|
+
equivalences: _v4.z.array(DictationEquivalenceSchema).max(DICTATION_MAX_EQUIVALENCES, {
|
|
1030
|
+
error: `A dictation carries at most ${DICTATION_MAX_EQUIVALENCES} equivalence rules.`
|
|
1031
|
+
}).optional()
|
|
1032
|
+
});
|
|
1033
|
+
var normalizers = /* @__PURE__ */ new WeakMap();
|
|
1034
|
+
function normalizerFor(data) {
|
|
1035
|
+
let normalizer = normalizers.get(data);
|
|
1036
|
+
if (normalizer === void 0) {
|
|
1037
|
+
normalizer = dictationNormalizer(data.tolerance);
|
|
1038
|
+
normalizers.set(data, normalizer);
|
|
1039
|
+
}
|
|
1040
|
+
return normalizer;
|
|
1041
|
+
}
|
|
1042
|
+
function measure(data, text) {
|
|
1043
|
+
return normalizerFor(data).measure(text);
|
|
1044
|
+
}
|
|
1045
|
+
function candidatesOf(data) {
|
|
1046
|
+
return [data.transcript, ..._nullishCoalesce(data.acceptedTranscripts, () => ( []))];
|
|
1047
|
+
}
|
|
1048
|
+
function isEmpty(measured) {
|
|
1049
|
+
return measured.normalized === "";
|
|
1050
|
+
}
|
|
1051
|
+
function checkEvenAfterIssues(check) {
|
|
1052
|
+
const guard = new _v4.z.core.$ZodCheck({ check: "custom", when: () => true });
|
|
1053
|
+
guard._zod.check = check;
|
|
1054
|
+
return guard;
|
|
1055
|
+
}
|
|
1056
|
+
var DictationDataShape = _v4.z.looseObject({
|
|
1057
|
+
schemaVersion: _v4.z.literal("1.0"),
|
|
1058
|
+
type: _v4.z.literal("dictation"),
|
|
1059
|
+
id: _v4.z.string().min(1),
|
|
1060
|
+
title: _v4.z.string().min(1),
|
|
1061
|
+
transcript: TranscriptTextSchema,
|
|
1062
|
+
acceptedTranscripts: _v4.z.array(TranscriptTextSchema).max(DICTATION_MAX_ACCEPTED_TRANSCRIPTS).optional(),
|
|
1063
|
+
media: MediaSchema.optional(),
|
|
1064
|
+
slowMedia: DictationSlowMediaSchema.optional(),
|
|
1065
|
+
hints: _v4.z.looseObject({ mode: _v4.z.literal("progressive-words") }).optional(),
|
|
1066
|
+
tolerance: DictationToleranceSchema.optional(),
|
|
1067
|
+
feedback: FeedbackSchema.optional(),
|
|
1068
|
+
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
1069
|
+
locale: _v4.z.string().optional(),
|
|
1070
|
+
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
1071
|
+
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional()
|
|
1072
|
+
});
|
|
1073
|
+
var DictationDataSchema = DictationDataShape.check(
|
|
1074
|
+
checkEvenAfterIssues((ctx) => {
|
|
1075
|
+
if (ctx.issues.some((issue2) => (_nullishCoalesce(_optionalChain([issue2, 'access', _33 => _33.path, 'optionalAccess', _34 => _34.length]), () => ( 0))) === 0)) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const data = ctx.value;
|
|
1079
|
+
const refused = new Set(ctx.issues.map((issue2) => String(_optionalChain([issue2, 'access', _35 => _35.path, 'optionalAccess', _36 => _36[0]]))));
|
|
1080
|
+
const report = (path, input, message) => {
|
|
1081
|
+
ctx.issues.push({ code: "custom", input, message, path: [...path] });
|
|
1082
|
+
};
|
|
1083
|
+
const rulesParse = !refused.has("tolerance");
|
|
1084
|
+
const transcriptParses = !refused.has("transcript");
|
|
1085
|
+
if (rulesParse && transcriptParses) {
|
|
1086
|
+
const transcript = measure(data, data.transcript);
|
|
1087
|
+
if (isEmpty(transcript)) {
|
|
1088
|
+
report(
|
|
1089
|
+
["transcript"],
|
|
1090
|
+
data.transcript,
|
|
1091
|
+
"The transcript must contain something to type: after ignoring case, punctuation and spacing nothing is left."
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
if (transcript.tooLong) {
|
|
1095
|
+
report(
|
|
1096
|
+
["transcript"],
|
|
1097
|
+
data.transcript,
|
|
1098
|
+
`A transcript is at most ${DICTATION_MAX_TRANSCRIPT_LENGTH} characters, before and after its equivalences are applied, and its equivalences may not grow it past ${WORKING_LENGTH} characters on the way.`
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
if (!refused.has("acceptedTranscripts")) {
|
|
1102
|
+
const accepted = _nullishCoalesce(data.acceptedTranscripts, () => ( []));
|
|
1103
|
+
if (accepted.some((entry) => {
|
|
1104
|
+
const measured = measure(data, entry);
|
|
1105
|
+
return measured.tooLong || isEmpty(measured);
|
|
1106
|
+
})) {
|
|
1107
|
+
report(
|
|
1108
|
+
["acceptedTranscripts"],
|
|
1109
|
+
data.acceptedTranscripts,
|
|
1110
|
+
`An accepted transcript must contain something to type and be at most ${DICTATION_MAX_TRANSCRIPT_LENGTH} characters, before and after its equivalences are applied, and its equivalences may not grow it past ${WORKING_LENGTH} characters on the way.`
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
const normalized = candidatesOf(data).map((entry) => measure(data, entry).normalized).filter((entry) => entry !== null);
|
|
1114
|
+
if (new Set(normalized).size !== normalized.length) {
|
|
1115
|
+
report(
|
|
1116
|
+
["acceptedTranscripts"],
|
|
1117
|
+
data.acceptedTranscripts,
|
|
1118
|
+
"An accepted transcript must differ from the transcript and from the other accepted transcripts once case, punctuation and spacing are ignored."
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
const mediaParses = !refused.has("media");
|
|
1124
|
+
const slowParses = !refused.has("slowMedia");
|
|
1125
|
+
if (mediaParses && data.media !== void 0 && data.media.type !== "audio") {
|
|
1126
|
+
report(
|
|
1127
|
+
["media", "type"],
|
|
1128
|
+
data.media.type,
|
|
1129
|
+
"A dictation is dictated: its recording must be audio."
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
if (slowParses && _optionalChain([data, 'access', _37 => _37.slowMedia, 'optionalAccess', _38 => _38.playback]) !== void 0) {
|
|
1133
|
+
report(
|
|
1134
|
+
["slowMedia", "playback"],
|
|
1135
|
+
data.slowMedia.playback,
|
|
1136
|
+
"The slow recording carries no playback policy of its own; put the policy on `media` \u2014 the slow recording follows it (except the play budget)."
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
if (mediaParses && slowParses && data.slowMedia !== void 0) {
|
|
1140
|
+
if (_optionalChain([data, 'access', _39 => _39.media, 'optionalAccess', _40 => _40.playback, 'optionalAccess', _41 => _41.maxPlays]) !== void 0) {
|
|
1141
|
+
report(
|
|
1142
|
+
["slowMedia"],
|
|
1143
|
+
data.slowMedia,
|
|
1144
|
+
"A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content. Drop `slowMedia`, or drop `maxPlays` (a budgeted recording can still offer the transport's 0.75\xD7 speed when `rate` is `allow`)."
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
if (typeof data.slowMedia.url === "string" && typeof _optionalChain([data, 'access', _42 => _42.media, 'optionalAccess', _43 => _43.url]) === "string" && data.slowMedia.url === data.media.url) {
|
|
1148
|
+
report(
|
|
1149
|
+
["slowMedia", "url"],
|
|
1150
|
+
data.slowMedia.url,
|
|
1151
|
+
"The slow recording must be a different file from the recording."
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
if (data.media === void 0) {
|
|
1155
|
+
report(
|
|
1156
|
+
["media"],
|
|
1157
|
+
data.media,
|
|
1158
|
+
"A slow recording accompanies a recording: add `media` first."
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (rulesParse && !(_nullishCoalesce(_optionalChain([data, 'access', _44 => _44.tolerance, 'optionalAccess', _45 => _45.equivalences]), () => ( []))).every(
|
|
1163
|
+
(rule) => preStripNormalize(rule.from) !== "" && normalizeDictationText(rule.to) !== ""
|
|
1164
|
+
)) {
|
|
1165
|
+
report(
|
|
1166
|
+
["tolerance", "equivalences"],
|
|
1167
|
+
_optionalChain([data, 'access', _46 => _46.tolerance, 'optionalAccess', _47 => _47.equivalences]),
|
|
1168
|
+
"An equivalence must name a word, phrase or symbol to rewrite, and rewrite it to something that survives normalisation (not only punctuation or spaces)."
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
for (const field of ["media", "slowMedia"]) {
|
|
1172
|
+
const recording = data[field];
|
|
1173
|
+
if (_optionalChain([recording, 'optionalAccess', _48 => _48.captionsUrl]) !== void 0) {
|
|
1174
|
+
report(
|
|
1175
|
+
[field, "captionsUrl"],
|
|
1176
|
+
recording.captionsUrl,
|
|
1177
|
+
"A dictation recording cannot carry captions: the captions are the answer. Offer an accessible alternative as a different item."
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
if (!rulesParse || !transcriptParses) {
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const normalizer = normalizerFor(data);
|
|
1185
|
+
const candidates = candidatesOf(data).map((entry) => normalizer.measure(entry));
|
|
1186
|
+
const shown = [
|
|
1187
|
+
[["title"], refused.has("title") ? void 0 : data.title],
|
|
1188
|
+
[["media", "alt"], _optionalChain([data, 'access', _49 => _49.media, 'optionalAccess', _50 => _50.alt])],
|
|
1189
|
+
[["slowMedia", "alt"], _optionalChain([data, 'access', _51 => _51.slowMedia, 'optionalAccess', _52 => _52.alt])]
|
|
1190
|
+
];
|
|
1191
|
+
for (const [path, value] of shown) {
|
|
1192
|
+
if (value === void 0) {
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
const { tooLong, forms } = normalizer.shown(value);
|
|
1196
|
+
if (tooLong) {
|
|
1197
|
+
report(
|
|
1198
|
+
path,
|
|
1199
|
+
value,
|
|
1200
|
+
`The title and the recording descriptions are searched for the transcript before they are shown: each is at most ${WORKING_LENGTH} characters, before and after the equivalences are applied, and the equivalences may not grow it past that on the way.`
|
|
1201
|
+
);
|
|
1202
|
+
} else if (candidates.some((candidate) => revealsCandidate(forms, candidate))) {
|
|
1203
|
+
report(
|
|
1204
|
+
path,
|
|
1205
|
+
value,
|
|
1206
|
+
'The title and the recording descriptions are shown to the learner before they type: they must not contain the transcript. Describe the recording ("Recording, normal speed"), never transcribe it.'
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
})
|
|
1211
|
+
);
|
|
1212
|
+
function groupCaptionsRevealDictation(group, hasCaptions = (captionsUrl) => captionsUrl !== void 0) {
|
|
1213
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1214
|
+
if (!isObject(group) || !isObject(group.stimulus) || !isObject(group.stimulus.media)) {
|
|
1215
|
+
return false;
|
|
1216
|
+
}
|
|
1217
|
+
if (!hasCaptions(group.stimulus.media.captionsUrl) || !Array.isArray(group.items)) {
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
return group.items.some(
|
|
1221
|
+
(item) => isObject(item) && item.type === "dictation" && item.media === void 0
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
var GROUP_CAPTIONS_REVEAL_DICTATION = "The stimulus recording carries captions, and a dictation in this group plays that recording: the captions are the answer. Remove the captions, or give the dictation its own recording.";
|
|
1225
|
+
|
|
139
1226
|
// src/schemas/fill-in-the-blanks.ts
|
|
140
1227
|
|
|
141
1228
|
var PLACEHOLDER_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
@@ -290,7 +1377,7 @@ function resolveChoices(gap, banks) {
|
|
|
290
1377
|
if (gap.bankId === void 0) {
|
|
291
1378
|
return void 0;
|
|
292
1379
|
}
|
|
293
|
-
return _optionalChain([banks, 'access',
|
|
1380
|
+
return _optionalChain([banks, 'access', _53 => _53.find, 'call', _54 => _54((bank) => bank.id === gap.bankId), 'optionalAccess', _55 => _55.choices]);
|
|
294
1381
|
}
|
|
295
1382
|
function placeholderCounts(passage) {
|
|
296
1383
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -492,10 +1579,238 @@ var RedactedWrittenResponseDataSchema = _v4.z.strictObject({
|
|
|
492
1579
|
rubric: RedactedWrittenResponseRubricSchema.optional(),
|
|
493
1580
|
languageTarget: _v4.z.string().optional()
|
|
494
1581
|
});
|
|
1582
|
+
var RedactedDictationSlowMediaSchema = _v4.z.strictObject({
|
|
1583
|
+
type: _v4.z.literal("audio"),
|
|
1584
|
+
url: MediaUrlSchema,
|
|
1585
|
+
alt: _v4.z.string().min(1).optional()
|
|
1586
|
+
});
|
|
1587
|
+
var RedactedDictationDataSchema = _v4.z.strictObject({
|
|
1588
|
+
...redactedBase,
|
|
1589
|
+
type: _v4.z.literal("dictation"),
|
|
1590
|
+
slowMedia: RedactedDictationSlowMediaSchema.optional(),
|
|
1591
|
+
hints: _v4.z.strictObject({ mode: _v4.z.literal("progressive-words") }).optional()
|
|
1592
|
+
}).check((ctx) => {
|
|
1593
|
+
const data = ctx.value;
|
|
1594
|
+
if (_optionalChain([data, 'access', _56 => _56.media, 'optionalAccess', _57 => _57.captionsUrl]) !== void 0) {
|
|
1595
|
+
ctx.issues.push({
|
|
1596
|
+
code: "custom",
|
|
1597
|
+
input: data.media.captionsUrl,
|
|
1598
|
+
message: "A dictation recording cannot carry captions: the captions are the answer, so this payload is not learner-safe.",
|
|
1599
|
+
path: ["media", "captionsUrl"]
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
if (data.slowMedia !== void 0 && _optionalChain([data, 'access', _58 => _58.media, 'optionalAccess', _59 => _59.playback, 'optionalAccess', _60 => _60.maxPlays]) !== void 0) {
|
|
1603
|
+
ctx.issues.push({
|
|
1604
|
+
code: "custom",
|
|
1605
|
+
input: data.slowMedia,
|
|
1606
|
+
message: "A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content.",
|
|
1607
|
+
path: ["slowMedia"]
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
if (data.slowMedia !== void 0 && data.media === void 0) {
|
|
1611
|
+
ctx.issues.push({
|
|
1612
|
+
code: "custom",
|
|
1613
|
+
input: data.slowMedia,
|
|
1614
|
+
message: "A slow recording accompanies a recording: this payload has no `media`.",
|
|
1615
|
+
path: ["media"]
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
|
|
1620
|
+
// src/scoring/dictation/edit-distance.ts
|
|
1621
|
+
function codePoints(text) {
|
|
1622
|
+
return Array.from(text, (character) => character.codePointAt(0));
|
|
1623
|
+
}
|
|
1624
|
+
function editDistance(a, b) {
|
|
1625
|
+
if (a === b) {
|
|
1626
|
+
return 0;
|
|
1627
|
+
}
|
|
1628
|
+
let left = codePoints(a);
|
|
1629
|
+
let right = codePoints(b);
|
|
1630
|
+
if (left.length < right.length) {
|
|
1631
|
+
[left, right] = [right, left];
|
|
1632
|
+
}
|
|
1633
|
+
if (right.length === 0) {
|
|
1634
|
+
return left.length;
|
|
1635
|
+
}
|
|
1636
|
+
let previous = new Uint32Array(right.length + 1);
|
|
1637
|
+
let current = new Uint32Array(right.length + 1);
|
|
1638
|
+
for (let j = 0; j <= right.length; j += 1) {
|
|
1639
|
+
previous[j] = j;
|
|
1640
|
+
}
|
|
1641
|
+
for (let i = 1; i <= left.length; i += 1) {
|
|
1642
|
+
current[0] = i;
|
|
1643
|
+
const leftPoint = left[i - 1];
|
|
1644
|
+
for (let j = 1; j <= right.length; j += 1) {
|
|
1645
|
+
const deletion = previous[j] + 1;
|
|
1646
|
+
const insertion = current[j - 1] + 1;
|
|
1647
|
+
const substitution = previous[j - 1] + (leftPoint === right[j - 1] ? 0 : 1);
|
|
1648
|
+
current[j] = Math.min(deletion, insertion, substitution);
|
|
1649
|
+
}
|
|
1650
|
+
[previous, current] = [current, previous];
|
|
1651
|
+
}
|
|
1652
|
+
return previous[right.length];
|
|
1653
|
+
}
|
|
1654
|
+
function similarity(a, b) {
|
|
1655
|
+
const max = Math.max(codePoints(a).length, codePoints(b).length);
|
|
1656
|
+
return max === 0 ? 0 : (max - editDistance(a, b)) / max;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// src/scoring/dictation/align.ts
|
|
1660
|
+
function dictationReferenceWords(data) {
|
|
1661
|
+
return rawCandidates(data).map(
|
|
1662
|
+
(candidate) => tokensOf(normalizeCandidate(candidate, data)).map((word, index) => ({
|
|
1663
|
+
itemId: `w${index + 1}`,
|
|
1664
|
+
word
|
|
1665
|
+
}))
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
function alignDictation(data, text) {
|
|
1669
|
+
const raw = typeof text === "string" ? text : "";
|
|
1670
|
+
const capped = truncateCodePoints(raw, DICTATION_MAX_TEXT_LENGTH);
|
|
1671
|
+
const { text: normalized, cut } = normalizeDictationTextBounded(capped, data.tolerance);
|
|
1672
|
+
const attempt = cutAt(normalized, DICTATION_MAX_TEXT_LENGTH);
|
|
1673
|
+
const truncated = cut || capped.length < raw.length || attempt.length < normalized.length;
|
|
1674
|
+
const candidates = [];
|
|
1675
|
+
rawCandidates(data).forEach((candidate, index) => {
|
|
1676
|
+
const candidateText = normalizeCandidate(candidate, data);
|
|
1677
|
+
if (candidateText !== "") {
|
|
1678
|
+
candidates.push({ index, text: candidateText });
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
if (candidates.length === 0) {
|
|
1682
|
+
return { candidateIndex: -1, reference: "", attempt, truncated, similarity: 0, words: [] };
|
|
1683
|
+
}
|
|
1684
|
+
let chosen = candidates[0];
|
|
1685
|
+
let best = similarity(chosen.text, attempt);
|
|
1686
|
+
for (const candidate of candidates.slice(1)) {
|
|
1687
|
+
const candidateSimilarity = similarity(candidate.text, attempt);
|
|
1688
|
+
if (candidateSimilarity > best) {
|
|
1689
|
+
best = candidateSimilarity;
|
|
1690
|
+
chosen = candidate;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return {
|
|
1694
|
+
candidateIndex: chosen.index,
|
|
1695
|
+
reference: chosen.text,
|
|
1696
|
+
attempt,
|
|
1697
|
+
truncated,
|
|
1698
|
+
similarity: best,
|
|
1699
|
+
words: alignWords(tokensOf(chosen.text), tokensOf(attempt))
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function rawCandidates(data) {
|
|
1703
|
+
return [
|
|
1704
|
+
data.transcript,
|
|
1705
|
+
...Array.isArray(data.acceptedTranscripts) ? data.acceptedTranscripts.slice(0, DICTATION_MAX_ACCEPTED_TRANSCRIPTS) : []
|
|
1706
|
+
];
|
|
1707
|
+
}
|
|
1708
|
+
function normalizeCandidate(candidate, data) {
|
|
1709
|
+
return typeof candidate === "string" ? cutAt(
|
|
1710
|
+
normalizeDictationText(
|
|
1711
|
+
truncateCodePoints(candidate, DICTATION_MAX_TRANSCRIPT_LENGTH),
|
|
1712
|
+
data.tolerance
|
|
1713
|
+
),
|
|
1714
|
+
DICTATION_MAX_TRANSCRIPT_LENGTH
|
|
1715
|
+
) : "";
|
|
1716
|
+
}
|
|
1717
|
+
function cutAt(normalized, max) {
|
|
1718
|
+
const cut = truncateCodePoints(normalized, max);
|
|
1719
|
+
return cut.endsWith(" ") ? cut.slice(0, -1) : cut;
|
|
1720
|
+
}
|
|
1721
|
+
function tokensOf(normalized) {
|
|
1722
|
+
return normalized === "" ? [] : normalized.split(" ");
|
|
1723
|
+
}
|
|
1724
|
+
var STEP = {
|
|
1725
|
+
/** A reference element paired with an attempt element — equal or substituted. */
|
|
1726
|
+
pair: 0,
|
|
1727
|
+
/** A reference element the attempt lacks. */
|
|
1728
|
+
missing: 1,
|
|
1729
|
+
/** An attempt element the reference lacks. */
|
|
1730
|
+
extra: 2
|
|
1731
|
+
};
|
|
1732
|
+
function alignSequences(reference, attempt) {
|
|
1733
|
+
const rows = reference.length;
|
|
1734
|
+
const columns = attempt.length;
|
|
1735
|
+
const width = columns + 1;
|
|
1736
|
+
const steps = new Uint8Array((rows + 1) * width);
|
|
1737
|
+
let previous = new Uint32Array(width);
|
|
1738
|
+
let current = new Uint32Array(width);
|
|
1739
|
+
for (let j2 = 0; j2 <= columns; j2 += 1) {
|
|
1740
|
+
previous[j2] = j2;
|
|
1741
|
+
steps[j2] = STEP.extra;
|
|
1742
|
+
}
|
|
1743
|
+
for (let i2 = 1; i2 <= rows; i2 += 1) {
|
|
1744
|
+
current[0] = i2;
|
|
1745
|
+
steps[i2 * width] = STEP.missing;
|
|
1746
|
+
const referenceItem = reference[i2 - 1];
|
|
1747
|
+
for (let j2 = 1; j2 <= columns; j2 += 1) {
|
|
1748
|
+
const paired = previous[j2 - 1] + (referenceItem === attempt[j2 - 1] ? 0 : 1);
|
|
1749
|
+
const missing = previous[j2] + 1;
|
|
1750
|
+
const extra = current[j2 - 1] + 1;
|
|
1751
|
+
const cost = Math.min(paired, missing, extra);
|
|
1752
|
+
current[j2] = cost;
|
|
1753
|
+
steps[i2 * width + j2] = paired === cost ? STEP.pair : missing === cost ? STEP.missing : STEP.extra;
|
|
1754
|
+
}
|
|
1755
|
+
[previous, current] = [current, previous];
|
|
1756
|
+
}
|
|
1757
|
+
const path = [];
|
|
1758
|
+
let i = rows;
|
|
1759
|
+
let j = columns;
|
|
1760
|
+
while (i > 0 || j > 0) {
|
|
1761
|
+
const step = steps[i * width + j];
|
|
1762
|
+
if (step === STEP.pair) {
|
|
1763
|
+
i -= 1;
|
|
1764
|
+
j -= 1;
|
|
1765
|
+
} else if (step === STEP.missing) {
|
|
1766
|
+
i -= 1;
|
|
1767
|
+
} else {
|
|
1768
|
+
j -= 1;
|
|
1769
|
+
}
|
|
1770
|
+
path.push({ step, referenceIndex: i, attemptIndex: j });
|
|
1771
|
+
}
|
|
1772
|
+
return path.reverse();
|
|
1773
|
+
}
|
|
1774
|
+
function alignWords(reference, attempt) {
|
|
1775
|
+
const words = [];
|
|
1776
|
+
let ordinal = 0;
|
|
1777
|
+
for (const { step, referenceIndex, attemptIndex } of alignSequences(reference, attempt)) {
|
|
1778
|
+
if (step === STEP.extra) {
|
|
1779
|
+
words.push({
|
|
1780
|
+
reference: "",
|
|
1781
|
+
attempt: attempt[attemptIndex],
|
|
1782
|
+
similarity: 0,
|
|
1783
|
+
status: "extra"
|
|
1784
|
+
});
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
ordinal += 1;
|
|
1788
|
+
const referenceWord = reference[referenceIndex];
|
|
1789
|
+
if (step === STEP.missing) {
|
|
1790
|
+
words.push({
|
|
1791
|
+
itemId: `w${ordinal}`,
|
|
1792
|
+
reference: referenceWord,
|
|
1793
|
+
attempt: "",
|
|
1794
|
+
similarity: 0,
|
|
1795
|
+
status: "missing"
|
|
1796
|
+
});
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
const attemptWord = attempt[attemptIndex];
|
|
1800
|
+
words.push({
|
|
1801
|
+
itemId: `w${ordinal}`,
|
|
1802
|
+
reference: referenceWord,
|
|
1803
|
+
attempt: attemptWord,
|
|
1804
|
+
similarity: similarity(referenceWord, attemptWord),
|
|
1805
|
+
status: referenceWord === attemptWord ? "correct" : "incorrect"
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
return words;
|
|
1809
|
+
}
|
|
495
1810
|
|
|
496
1811
|
// src/scoring/text-match.ts
|
|
497
1812
|
var COMBINING_MARKS_RE = /\p{M}/gu;
|
|
498
|
-
var
|
|
1813
|
+
var PUNCTUATION_RE2 = /\p{P}/gu;
|
|
499
1814
|
function applyBaseline(value, policy) {
|
|
500
1815
|
let result = value;
|
|
501
1816
|
if (policy.trim !== false) {
|
|
@@ -512,7 +1827,7 @@ function applyNormalization(value, policy) {
|
|
|
512
1827
|
result = result.normalize(policy.normalize);
|
|
513
1828
|
}
|
|
514
1829
|
if (policy.ignorePunctuation === true) {
|
|
515
|
-
result = result.replace(
|
|
1830
|
+
result = result.replace(PUNCTUATION_RE2, "");
|
|
516
1831
|
}
|
|
517
1832
|
if (policy.collapseInnerWhitespace === true) {
|
|
518
1833
|
result = result.replace(/(?<=\S)\s+(?=\S)/g, " ");
|
|
@@ -700,7 +2015,29 @@ var DRAFT_ISSUE_SEVERITY = {
|
|
|
700
2015
|
wr_criterion_weight_required: "incomplete",
|
|
701
2016
|
wr_criterion_weight_invalid: "invalid",
|
|
702
2017
|
wr_rubric_weights_zero: "incomplete",
|
|
703
|
-
wr_rubric_weights_too_large: "invalid"
|
|
2018
|
+
wr_rubric_weights_too_large: "invalid",
|
|
2019
|
+
// dictation
|
|
2020
|
+
dc_transcript_required: "incomplete",
|
|
2021
|
+
dc_transcript_unscorable: "invalid",
|
|
2022
|
+
dc_transcript_too_long: "invalid",
|
|
2023
|
+
dc_accepted_transcript_empty: "incomplete",
|
|
2024
|
+
dc_accepted_transcript_too_long: "invalid",
|
|
2025
|
+
dc_accepted_transcript_duplicate: "invalid",
|
|
2026
|
+
dc_accepted_transcripts_too_many: "invalid",
|
|
2027
|
+
dc_media_kind: "invalid",
|
|
2028
|
+
dc_captions_not_allowed: "invalid",
|
|
2029
|
+
dc_slow_media_kind: "invalid",
|
|
2030
|
+
dc_slow_media_playback: "invalid",
|
|
2031
|
+
dc_slow_media_unbudgeted: "invalid",
|
|
2032
|
+
dc_slow_media_same_url: "invalid",
|
|
2033
|
+
dc_slow_media_without_media: "incomplete",
|
|
2034
|
+
dc_transcript_revealed: "invalid",
|
|
2035
|
+
dc_shown_text_too_long: "invalid",
|
|
2036
|
+
dc_hints_mode_required: "incomplete",
|
|
2037
|
+
dc_hints_mode_invalid: "invalid",
|
|
2038
|
+
dc_equivalence_from_required: "incomplete",
|
|
2039
|
+
dc_equivalence_to_required: "incomplete",
|
|
2040
|
+
dc_tolerance_invalid: "invalid"
|
|
704
2041
|
};
|
|
705
2042
|
function issue(code, path, message) {
|
|
706
2043
|
return { path: path.map(String), message, code, severity: DRAFT_ISSUE_SEVERITY[code] };
|
|
@@ -879,6 +2216,355 @@ function covers(reported, failed) {
|
|
|
879
2216
|
}
|
|
880
2217
|
return failed.every((segment, index) => reported[index] === segment);
|
|
881
2218
|
}
|
|
2219
|
+
function pathKey(path) {
|
|
2220
|
+
return path.length === 0 ? "" : JSON.stringify(path.map(String));
|
|
2221
|
+
}
|
|
2222
|
+
function coveredPathsOf(reported) {
|
|
2223
|
+
const covered = /* @__PURE__ */ new Set();
|
|
2224
|
+
for (const known of reported) {
|
|
2225
|
+
if (known.path.length === 0) {
|
|
2226
|
+
covered.add("");
|
|
2227
|
+
}
|
|
2228
|
+
for (let length = 1; length <= known.path.length; length += 1) {
|
|
2229
|
+
covered.add(pathKey(known.path.slice(0, length)));
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
return covered;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// src/authoring/dictation.ts
|
|
2236
|
+
var dictationAuthoring = {
|
|
2237
|
+
createDraft: ({ newId }) => ({
|
|
2238
|
+
schemaVersion: "1.0",
|
|
2239
|
+
type: "dictation",
|
|
2240
|
+
id: newId(),
|
|
2241
|
+
title: "",
|
|
2242
|
+
transcript: ""
|
|
2243
|
+
}),
|
|
2244
|
+
checkDraft: checkDictationDraft
|
|
2245
|
+
};
|
|
2246
|
+
function checkDictationDraft(draft) {
|
|
2247
|
+
const issues = checkIdentity(draft, "dictation");
|
|
2248
|
+
const tolerance = isRecord(draft.tolerance) ? draft.tolerance : void 0;
|
|
2249
|
+
const normalizer = dictationNormalizer(tolerance);
|
|
2250
|
+
issues.push(...checkTranscript(draft.transcript, normalizer));
|
|
2251
|
+
issues.push(...checkAcceptedTranscripts(draft, normalizer));
|
|
2252
|
+
issues.push(...checkRecording(draft.media));
|
|
2253
|
+
issues.push(...checkSlowRecording(draft));
|
|
2254
|
+
issues.push(...checkRevealed(draft, normalizer));
|
|
2255
|
+
issues.push(...checkHints(draft.hints));
|
|
2256
|
+
issues.push(...checkTolerance(draft.tolerance));
|
|
2257
|
+
issues.push(...checkSharedOptional(draft));
|
|
2258
|
+
return issues;
|
|
2259
|
+
}
|
|
2260
|
+
function isUnwrittenTranscript(value) {
|
|
2261
|
+
if (typeof value === "string" && isLongerThan(value, DICTATION_MAX_TRANSCRIPT_LENGTH)) {
|
|
2262
|
+
return false;
|
|
2263
|
+
}
|
|
2264
|
+
return isUnwritten(value) || typeof value === "string" && isBlankBeforeRules(value);
|
|
2265
|
+
}
|
|
2266
|
+
function acceptedTranscriptsRead(accepted) {
|
|
2267
|
+
return accepted.slice(0, DICTATION_MAX_ACCEPTED_TRANSCRIPTS);
|
|
2268
|
+
}
|
|
2269
|
+
function checkTranscript(transcript, normalizer) {
|
|
2270
|
+
if (isUnwrittenTranscript(transcript)) {
|
|
2271
|
+
return [issue("dc_transcript_required", ["transcript"], "Write the transcript.")];
|
|
2272
|
+
}
|
|
2273
|
+
if (typeof transcript !== "string") {
|
|
2274
|
+
return [];
|
|
2275
|
+
}
|
|
2276
|
+
const measured = normalizer.measure(transcript);
|
|
2277
|
+
if (measured.tooLong) {
|
|
2278
|
+
return [
|
|
2279
|
+
issue(
|
|
2280
|
+
"dc_transcript_too_long",
|
|
2281
|
+
["transcript"],
|
|
2282
|
+
`The transcript is longer than ${DICTATION_MAX_TRANSCRIPT_LENGTH} characters before or after its equivalences are applied, or they grow it past ${WORKING_LENGTH} characters on the way. A dictation is a sentence or two.`
|
|
2283
|
+
)
|
|
2284
|
+
];
|
|
2285
|
+
}
|
|
2286
|
+
if (measured.normalized === "") {
|
|
2287
|
+
return [
|
|
2288
|
+
issue(
|
|
2289
|
+
"dc_transcript_unscorable",
|
|
2290
|
+
["transcript"],
|
|
2291
|
+
"The transcript has nothing to type: once case, punctuation and spacing are ignored, nothing is left."
|
|
2292
|
+
)
|
|
2293
|
+
];
|
|
2294
|
+
}
|
|
2295
|
+
return [];
|
|
2296
|
+
}
|
|
2297
|
+
function checkAcceptedTranscripts(draft, normalizer) {
|
|
2298
|
+
const accepted = draft.acceptedTranscripts;
|
|
2299
|
+
if (!Array.isArray(accepted)) {
|
|
2300
|
+
return [];
|
|
2301
|
+
}
|
|
2302
|
+
const issues = [];
|
|
2303
|
+
if (accepted.length > DICTATION_MAX_ACCEPTED_TRANSCRIPTS) {
|
|
2304
|
+
issues.push(
|
|
2305
|
+
issue(
|
|
2306
|
+
"dc_accepted_transcripts_too_many",
|
|
2307
|
+
["acceptedTranscripts"],
|
|
2308
|
+
`At most ${DICTATION_MAX_ACCEPTED_TRANSCRIPTS} accepted transcripts. Prefer an equivalence for a word that varies.`
|
|
2309
|
+
)
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
for (const [index, text] of accepted.entries()) {
|
|
2313
|
+
if (typeof text !== "string") {
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2316
|
+
if (index >= DICTATION_MAX_ACCEPTED_TRANSCRIPTS) {
|
|
2317
|
+
if (text === "") {
|
|
2318
|
+
issues.push(acceptedTranscriptEmpty(index));
|
|
2319
|
+
}
|
|
2320
|
+
continue;
|
|
2321
|
+
}
|
|
2322
|
+
const measured = normalizer.measure(text);
|
|
2323
|
+
if (measured.tooLong) {
|
|
2324
|
+
issues.push(
|
|
2325
|
+
issue(
|
|
2326
|
+
"dc_accepted_transcript_too_long",
|
|
2327
|
+
["acceptedTranscripts", index],
|
|
2328
|
+
`Accepted transcript ${index + 1} is longer than ${DICTATION_MAX_TRANSCRIPT_LENGTH} characters before or after its equivalences are applied, or they grow it past ${WORKING_LENGTH} characters on the way.`
|
|
2329
|
+
)
|
|
2330
|
+
);
|
|
2331
|
+
} else if (measured.normalized === "") {
|
|
2332
|
+
issues.push(acceptedTranscriptEmpty(index));
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
const candidates = [draft.transcript, ...acceptedTranscriptsRead(accepted)].filter((text) => typeof text === "string").map((text) => normalizer.measure(text).normalized).filter((text) => text !== null && text !== "");
|
|
2336
|
+
if (new Set(candidates).size !== candidates.length) {
|
|
2337
|
+
issues.push(
|
|
2338
|
+
issue(
|
|
2339
|
+
"dc_accepted_transcript_duplicate",
|
|
2340
|
+
["acceptedTranscripts"],
|
|
2341
|
+
"An accepted transcript repeats the transcript, or another accepted transcript, once case, punctuation and spacing are ignored. Remove it."
|
|
2342
|
+
)
|
|
2343
|
+
);
|
|
2344
|
+
}
|
|
2345
|
+
return issues;
|
|
2346
|
+
}
|
|
2347
|
+
function acceptedTranscriptEmpty(index) {
|
|
2348
|
+
return issue(
|
|
2349
|
+
"dc_accepted_transcript_empty",
|
|
2350
|
+
["acceptedTranscripts", index],
|
|
2351
|
+
`Accepted transcript ${index + 1} has nothing to type. Fill it in or remove it.`
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
function checkRecording(media) {
|
|
2355
|
+
if (!isRecord(media)) {
|
|
2356
|
+
return [];
|
|
2357
|
+
}
|
|
2358
|
+
const issues = [];
|
|
2359
|
+
const kind = media.type;
|
|
2360
|
+
if (!isUnwritten(kind) && kind !== "audio") {
|
|
2361
|
+
issues.push(
|
|
2362
|
+
issue(
|
|
2363
|
+
"dc_media_kind",
|
|
2364
|
+
["media", "type"],
|
|
2365
|
+
"A dictation is dictated: its recording must be audio."
|
|
2366
|
+
)
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
if (!isUnset(media.captionsUrl)) {
|
|
2370
|
+
issues.push(
|
|
2371
|
+
issue("dc_captions_not_allowed", ["media", "captionsUrl"], CAPTIONS_ARE_THE_ANSWER)
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
return issues;
|
|
2375
|
+
}
|
|
2376
|
+
var CAPTIONS_ARE_THE_ANSWER = "A dictation recording cannot carry captions: the captions are the answer. Remove them, and offer an accessible alternative as a different item.";
|
|
2377
|
+
function checkSlowRecording(draft) {
|
|
2378
|
+
const slow = draft.slowMedia;
|
|
2379
|
+
if (!isRecord(slow)) {
|
|
2380
|
+
return [];
|
|
2381
|
+
}
|
|
2382
|
+
const issues = [];
|
|
2383
|
+
const kind = slow.type;
|
|
2384
|
+
const wrongKind = !isUnwritten(kind) && kind !== "audio";
|
|
2385
|
+
if (wrongKind) {
|
|
2386
|
+
issues.push(
|
|
2387
|
+
issue("dc_slow_media_kind", ["slowMedia", "type"], "The slow recording must be audio.")
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
issues.push(
|
|
2391
|
+
...checkMedia(
|
|
2392
|
+
wrongKind ? { ...slow, type: "audio" } : slow,
|
|
2393
|
+
["slowMedia"],
|
|
2394
|
+
DictationSlowMediaSchema
|
|
2395
|
+
)
|
|
2396
|
+
);
|
|
2397
|
+
if (!isUnset(slow.captionsUrl)) {
|
|
2398
|
+
issues.push(
|
|
2399
|
+
issue("dc_captions_not_allowed", ["slowMedia", "captionsUrl"], CAPTIONS_ARE_THE_ANSWER)
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2402
|
+
if (slow.playback !== void 0) {
|
|
2403
|
+
issues.push(
|
|
2404
|
+
issue(
|
|
2405
|
+
"dc_slow_media_playback",
|
|
2406
|
+
["slowMedia", "playback"],
|
|
2407
|
+
"The slow recording has no playback policy of its own. Put the policy on the recording; the slow recording follows it, except the play budget."
|
|
2408
|
+
)
|
|
2409
|
+
);
|
|
2410
|
+
}
|
|
2411
|
+
const media = draft.media;
|
|
2412
|
+
if (isUnset(media)) {
|
|
2413
|
+
issues.push(
|
|
2414
|
+
issue(
|
|
2415
|
+
"dc_slow_media_without_media",
|
|
2416
|
+
["media"],
|
|
2417
|
+
"A slow recording accompanies a recording. Add the recording first."
|
|
2418
|
+
)
|
|
2419
|
+
);
|
|
2420
|
+
return issues;
|
|
2421
|
+
}
|
|
2422
|
+
if (!isRecord(media)) {
|
|
2423
|
+
return issues;
|
|
2424
|
+
}
|
|
2425
|
+
if (isRecord(media.playback) && media.playback.maxPlays !== void 0) {
|
|
2426
|
+
issues.push(
|
|
2427
|
+
issue(
|
|
2428
|
+
"dc_slow_media_unbudgeted",
|
|
2429
|
+
["slowMedia"],
|
|
2430
|
+
"A play budget on the recording cannot coexist with a slow recording that has none. Remove the slow recording, or remove the play limit."
|
|
2431
|
+
)
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
if (typeof slow.url === "string" && slow.url !== "" && slow.url === media.url) {
|
|
2435
|
+
issues.push(
|
|
2436
|
+
issue(
|
|
2437
|
+
"dc_slow_media_same_url",
|
|
2438
|
+
["slowMedia", "url"],
|
|
2439
|
+
"The slow recording is the same file as the recording. Point it at the slower render."
|
|
2440
|
+
)
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
return issues;
|
|
2444
|
+
}
|
|
2445
|
+
function checkRevealed(draft, normalizer) {
|
|
2446
|
+
const candidates = [
|
|
2447
|
+
draft.transcript,
|
|
2448
|
+
...Array.isArray(draft.acceptedTranscripts) ? acceptedTranscriptsRead(draft.acceptedTranscripts) : []
|
|
2449
|
+
].filter((text) => typeof text === "string").map((text) => normalizer.measure(text));
|
|
2450
|
+
const shown = [
|
|
2451
|
+
[["title"], draft.title],
|
|
2452
|
+
[["media", "alt"], isRecord(draft.media) ? draft.media.alt : void 0],
|
|
2453
|
+
[["slowMedia", "alt"], isRecord(draft.slowMedia) ? draft.slowMedia.alt : void 0]
|
|
2454
|
+
];
|
|
2455
|
+
const issues = [];
|
|
2456
|
+
for (const [path, text] of shown) {
|
|
2457
|
+
if (typeof text !== "string") {
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
const { tooLong, forms } = normalizer.shown(text);
|
|
2461
|
+
if (tooLong) {
|
|
2462
|
+
issues.push(
|
|
2463
|
+
issue(
|
|
2464
|
+
"dc_shown_text_too_long",
|
|
2465
|
+
path,
|
|
2466
|
+
`${path[0] === "title" ? "The title" : "The description"} is too long to be checked for the transcript: at most ${WORKING_LENGTH} characters before and after the equivalences are applied, and they may not grow it past that on the way.`
|
|
2467
|
+
)
|
|
2468
|
+
);
|
|
2469
|
+
} else if (candidates.some((candidate) => revealsCandidate(forms, candidate))) {
|
|
2470
|
+
issues.push(
|
|
2471
|
+
issue(
|
|
2472
|
+
"dc_transcript_revealed",
|
|
2473
|
+
path,
|
|
2474
|
+
path[0] === "title" ? 'The title gives the answer away: it contains the transcript. Name the exercise instead ("Listen and type the sentence").' : 'The description gives the answer away: it contains the transcript. Describe the recording ("Recording, normal speed"), never transcribe it.'
|
|
2475
|
+
)
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
return issues;
|
|
2480
|
+
}
|
|
2481
|
+
function checkHints(hints) {
|
|
2482
|
+
if (!isRecord(hints)) {
|
|
2483
|
+
return [];
|
|
2484
|
+
}
|
|
2485
|
+
const mode = hints.mode;
|
|
2486
|
+
if (isUnwritten(mode)) {
|
|
2487
|
+
return [issue("dc_hints_mode_required", ["hints", "mode"], "Choose how hints are given.")];
|
|
2488
|
+
}
|
|
2489
|
+
if (mode !== "progressive-words") {
|
|
2490
|
+
return [
|
|
2491
|
+
issue(
|
|
2492
|
+
"dc_hints_mode_invalid",
|
|
2493
|
+
["hints", "mode"],
|
|
2494
|
+
'The only hint mode is "progressive-words". Leave hints out unless you mean to offer them.'
|
|
2495
|
+
)
|
|
2496
|
+
];
|
|
2497
|
+
}
|
|
2498
|
+
return [];
|
|
2499
|
+
}
|
|
2500
|
+
function checkTolerance(tolerance) {
|
|
2501
|
+
if (isUnset(tolerance)) {
|
|
2502
|
+
return [];
|
|
2503
|
+
}
|
|
2504
|
+
if (!isRecord(tolerance)) {
|
|
2505
|
+
return [
|
|
2506
|
+
issue(
|
|
2507
|
+
"dc_tolerance_invalid",
|
|
2508
|
+
["tolerance"],
|
|
2509
|
+
"The tolerances are an object: `{ equivalences: [{ from, to }] }`."
|
|
2510
|
+
)
|
|
2511
|
+
];
|
|
2512
|
+
}
|
|
2513
|
+
const issues = [];
|
|
2514
|
+
const rules = tolerance.equivalences;
|
|
2515
|
+
if (Array.isArray(rules)) {
|
|
2516
|
+
if (rules.length > DICTATION_MAX_EQUIVALENCES) {
|
|
2517
|
+
issues.push(
|
|
2518
|
+
issue(
|
|
2519
|
+
"dc_tolerance_invalid",
|
|
2520
|
+
["tolerance", "equivalences"],
|
|
2521
|
+
`A dictation carries at most ${DICTATION_MAX_EQUIVALENCES} equivalence rules.`
|
|
2522
|
+
)
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
for (const [index, rule] of rules.entries()) {
|
|
2526
|
+
if (!isRecord(rule)) {
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
const path = ["tolerance", "equivalences", index];
|
|
2530
|
+
const from = rule.from;
|
|
2531
|
+
if (isUnwritten(from) || typeof from === "string" && preStripNormalize(from) === "") {
|
|
2532
|
+
issues.push(
|
|
2533
|
+
issue(
|
|
2534
|
+
"dc_equivalence_from_required",
|
|
2535
|
+
[...path, "from"],
|
|
2536
|
+
`Say what rule ${index + 1} rewrites: a word, a phrase or a symbol.`
|
|
2537
|
+
)
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
const to = rule.to;
|
|
2541
|
+
if (isUnwritten(to) || typeof to === "string" && normalizeDictationText(to) === "") {
|
|
2542
|
+
issues.push(
|
|
2543
|
+
issue(
|
|
2544
|
+
"dc_equivalence_to_required",
|
|
2545
|
+
[...path, "to"],
|
|
2546
|
+
`Say what rule ${index + 1} rewrites to. A rule cannot delete a word or leave only punctuation.`
|
|
2547
|
+
)
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
const parsed = DictationToleranceSchema.safeParse(tolerance, { reportInput: true });
|
|
2553
|
+
if (!parsed.success) {
|
|
2554
|
+
const covered = coveredPathsOf(issues);
|
|
2555
|
+
for (const schemaIssue of parsed.error.issues) {
|
|
2556
|
+
const path = ["tolerance", ...schemaIssue.path.map(String)];
|
|
2557
|
+
if (refusesEmpty(tolerance, schemaIssue) || covered.has(pathKey(path))) {
|
|
2558
|
+
continue;
|
|
2559
|
+
}
|
|
2560
|
+
issues.push(issue("dc_tolerance_invalid", path, schemaIssue.message));
|
|
2561
|
+
for (const key of coveredPathsOf([{ path }])) {
|
|
2562
|
+
covered.add(key);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
return issues;
|
|
2567
|
+
}
|
|
882
2568
|
|
|
883
2569
|
// src/authoring/fill-in-the-blanks.ts
|
|
884
2570
|
var fillInTheBlanksAuthoring = {
|
|
@@ -1633,6 +3319,27 @@ function checkCriteria(criteria) {
|
|
|
1633
3319
|
return issues;
|
|
1634
3320
|
}
|
|
1635
3321
|
|
|
3322
|
+
// src/scoring/activity-scorers/dictation.ts
|
|
3323
|
+
function scoreDictation(data, response) {
|
|
3324
|
+
const alignment = alignDictation(data, typeof _optionalChain([response, 'optionalAccess', _61 => _61.text]) === "string" ? response.text : "");
|
|
3325
|
+
const details = [];
|
|
3326
|
+
for (const word of alignment.words) {
|
|
3327
|
+
if (word.status === "extra") {
|
|
3328
|
+
continue;
|
|
3329
|
+
}
|
|
3330
|
+
details.push({
|
|
3331
|
+
itemId: word.itemId,
|
|
3332
|
+
correct: word.status === "correct",
|
|
3333
|
+
outcome: word.status === "correct" ? "correct" : word.status === "incorrect" ? "incorrect" : "incorrect-omission",
|
|
3334
|
+
learnerResponse: word.attempt,
|
|
3335
|
+
correctResponse: word.reference,
|
|
3336
|
+
score: word.similarity,
|
|
3337
|
+
weight: 1
|
|
3338
|
+
});
|
|
3339
|
+
}
|
|
3340
|
+
return { score: alignment.similarity, maxScore: 1, feedback: null, details };
|
|
3341
|
+
}
|
|
3342
|
+
|
|
1636
3343
|
// src/scoring/strategies/all-or-nothing.ts
|
|
1637
3344
|
function allOrNothingStrategy(correctItems) {
|
|
1638
3345
|
return correctItems.every((isCorrect) => isCorrect) ? 1 : 0;
|
|
@@ -1683,7 +3390,7 @@ function choicesFor(data, gap) {
|
|
|
1683
3390
|
if (gap.choices !== void 0) {
|
|
1684
3391
|
return gap.choices;
|
|
1685
3392
|
}
|
|
1686
|
-
return _nullishCoalesce(_optionalChain([data, 'access',
|
|
3393
|
+
return _nullishCoalesce(_optionalChain([data, 'access', _62 => _62.banks, 'optionalAccess', _63 => _63.find, 'call', _64 => _64((bank) => bank.id === gap.bankId), 'optionalAccess', _65 => _65.choices]), () => ( []));
|
|
1687
3394
|
}
|
|
1688
3395
|
function scoreGapSelect(data, response) {
|
|
1689
3396
|
const details = [];
|
|
@@ -1719,7 +3426,7 @@ function scoreMultipleChoice(data, response) {
|
|
|
1719
3426
|
let scoreValue;
|
|
1720
3427
|
if (data.scoringStrategy === "all-or-nothing") {
|
|
1721
3428
|
if (data.mode === "single") {
|
|
1722
|
-
scoreValue = response.selectedOptionIds.length === 1 && _optionalChain([optionById, 'access',
|
|
3429
|
+
scoreValue = response.selectedOptionIds.length === 1 && _optionalChain([optionById, 'access', _66 => _66.get, 'call', _67 => _67(response.selectedOptionIds[0]), 'optionalAccess', _68 => _68.isCorrect]) === true ? 1 : 0;
|
|
1723
3430
|
} else {
|
|
1724
3431
|
const correctIds = data.options.filter((o) => o.isCorrect).map((o) => o.id);
|
|
1725
3432
|
const allCorrectSelected = correctIds.every((id) => selected.has(id));
|
|
@@ -1730,7 +3437,7 @@ function scoreMultipleChoice(data, response) {
|
|
|
1730
3437
|
let incorrectSelected = 0;
|
|
1731
3438
|
for (const id of selected) {
|
|
1732
3439
|
const option = optionById.get(id);
|
|
1733
|
-
if (_optionalChain([option, 'optionalAccess',
|
|
3440
|
+
if (_optionalChain([option, 'optionalAccess', _69 => _69.isCorrect])) {
|
|
1734
3441
|
correctSelected += 1;
|
|
1735
3442
|
} else {
|
|
1736
3443
|
incorrectSelected += 1;
|
|
@@ -1860,6 +3567,15 @@ var FILL_IN_THE_BLANKS_FIELD_POLICY = {
|
|
|
1860
3567
|
feedback: "answer-key"
|
|
1861
3568
|
}
|
|
1862
3569
|
};
|
|
3570
|
+
var DICTATION_FIELD_POLICY = {
|
|
3571
|
+
...SHARED_PUBLIC_FIELDS,
|
|
3572
|
+
transcript: "answer-key",
|
|
3573
|
+
acceptedTranscripts: "answer-key",
|
|
3574
|
+
slowMedia: { type: "public", url: "public", alt: "public" },
|
|
3575
|
+
hints: { mode: "public" },
|
|
3576
|
+
tolerance: "answer-key",
|
|
3577
|
+
feedback: FEEDBACK_FIELD_POLICY
|
|
3578
|
+
};
|
|
1863
3579
|
var RUBRIC_FIELD_POLICY = {
|
|
1864
3580
|
label: "public",
|
|
1865
3581
|
// Applies to every element of the array.
|
|
@@ -1892,7 +3608,7 @@ var multipleChoiceType = defineActivityType({
|
|
|
1892
3608
|
// exact optionals. Structurally identical at runtime — cast is type-level only.
|
|
1893
3609
|
schema: MultipleChoiceDataSchema,
|
|
1894
3610
|
scoring: { kind: "sync", score: scoreMultipleChoice },
|
|
1895
|
-
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
3611
|
+
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _70 => _70.selectedOptionIds, 'access', _71 => _71.length]), () => ( 0))) > 0,
|
|
1896
3612
|
fieldPolicy: MULTIPLE_CHOICE_FIELD_POLICY,
|
|
1897
3613
|
redactedSchema: RedactedMultipleChoiceDataSchema,
|
|
1898
3614
|
interop: {
|
|
@@ -1909,7 +3625,7 @@ var fillInTheBlanksType = defineActivityType({
|
|
|
1909
3625
|
type: "fill-in-the-blanks",
|
|
1910
3626
|
schema: FillInTheBlanksDataSchema,
|
|
1911
3627
|
scoring: { kind: "sync", score: scoreFillInTheBlanks },
|
|
1912
|
-
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
3628
|
+
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _72 => _72.answers]), () => ( {}))).some((answer) => answer.trim().length > 0),
|
|
1913
3629
|
fieldPolicy: FILL_IN_THE_BLANKS_FIELD_POLICY,
|
|
1914
3630
|
redactedSchema: RedactedFillInTheBlanksDataSchema,
|
|
1915
3631
|
interop: {
|
|
@@ -1932,14 +3648,14 @@ var writtenResponseType = defineActivityType({
|
|
|
1932
3648
|
kind: "deferred",
|
|
1933
3649
|
reason: "requires_async_grading",
|
|
1934
3650
|
partial: (data, response) => {
|
|
1935
|
-
const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
3651
|
+
const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _73 => _73.text]), () => ( "")));
|
|
1936
3652
|
return {
|
|
1937
3653
|
withinWordBounds: response !== void 0 && wordCount >= data.minWords && wordCount <= data.maxWords,
|
|
1938
3654
|
wordCount
|
|
1939
3655
|
};
|
|
1940
3656
|
}
|
|
1941
3657
|
},
|
|
1942
|
-
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
3658
|
+
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _74 => _74.text, 'access', _75 => _75.trim, 'call', _76 => _76(), 'access', _77 => _77.length]), () => ( 0))) > 0,
|
|
1943
3659
|
fieldPolicy: WRITTEN_RESPONSE_FIELD_POLICY,
|
|
1944
3660
|
redactedSchema: RedactedWrittenResponseDataSchema,
|
|
1945
3661
|
interop: {
|
|
@@ -1954,7 +3670,7 @@ var gapSelectType = defineActivityType({
|
|
|
1954
3670
|
type: "gap-select",
|
|
1955
3671
|
schema: GapSelectDataSchema,
|
|
1956
3672
|
scoring: { kind: "sync", score: scoreGapSelect },
|
|
1957
|
-
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
3673
|
+
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _78 => _78.selections]), () => ( {}))).some((choiceId) => choiceId !== ""),
|
|
1958
3674
|
fieldPolicy: GAP_SELECT_FIELD_POLICY,
|
|
1959
3675
|
redactedSchema: RedactedGapSelectDataSchema,
|
|
1960
3676
|
interop: {
|
|
@@ -1973,10 +3689,61 @@ var gapSelectType = defineActivityType({
|
|
|
1973
3689
|
interactions: ["gap-selected", "submitted"],
|
|
1974
3690
|
authoring: gapSelectAuthoring
|
|
1975
3691
|
});
|
|
3692
|
+
var dictationType = defineActivityType({
|
|
3693
|
+
type: "dictation",
|
|
3694
|
+
schema: DictationDataSchema,
|
|
3695
|
+
scoring: { kind: "sync", score: scoreDictation },
|
|
3696
|
+
// Whitespace-only or punctuation-only text is no answer. Without the
|
|
3697
|
+
// tolerance, which this signature cannot see: the one gap is an attempt made
|
|
3698
|
+
// ONLY of a symbol an equivalence names, which reads as unanswered here and
|
|
3699
|
+
// is scored as the rewritten word — documented in the authoring guide. The
|
|
3700
|
+
// reverse cannot happen: a rule inserts words, never nothing, and the text is
|
|
3701
|
+
// cut where the scorer cuts, so text past the cap never counts as an answer.
|
|
3702
|
+
isAnswered: (response) => normalizeDictationText(
|
|
3703
|
+
truncateCodePoints(
|
|
3704
|
+
typeof _optionalChain([response, 'optionalAccess', _79 => _79.text]) === "string" ? response.text : "",
|
|
3705
|
+
DICTATION_MAX_TEXT_LENGTH
|
|
3706
|
+
)
|
|
3707
|
+
) !== "",
|
|
3708
|
+
fieldPolicy: DICTATION_FIELD_POLICY,
|
|
3709
|
+
redactedSchema: RedactedDictationDataSchema,
|
|
3710
|
+
interop: {
|
|
3711
|
+
xapiActivityTypeIri: "http://adlnet.gov/expapi/activities/cmi.interaction",
|
|
3712
|
+
// `fill-in`, as for fill-in-the-blanks: a typed answer with a key pattern.
|
|
3713
|
+
// `long-fill-in` is what written-response uses for free text nothing scores.
|
|
3714
|
+
xapiInteractionType: "fill-in",
|
|
3715
|
+
correctResponsesPattern: (data) => [data.transcript, ..._nullishCoalesce(data.acceptedTranscripts, () => ( []))]
|
|
3716
|
+
},
|
|
3717
|
+
interactions: ["text-changed", "hint-requested", "submitted"],
|
|
3718
|
+
authoring: dictationAuthoring
|
|
3719
|
+
});
|
|
1976
3720
|
registerActivityType(multipleChoiceType);
|
|
1977
3721
|
registerActivityType(fillInTheBlanksType);
|
|
1978
3722
|
registerActivityType(writtenResponseType);
|
|
1979
3723
|
registerActivityType(gapSelectType);
|
|
3724
|
+
registerActivityType(dictationType);
|
|
3725
|
+
|
|
3726
|
+
|
|
3727
|
+
|
|
3728
|
+
|
|
3729
|
+
|
|
3730
|
+
|
|
3731
|
+
|
|
3732
|
+
|
|
3733
|
+
|
|
3734
|
+
|
|
3735
|
+
|
|
3736
|
+
|
|
3737
|
+
|
|
3738
|
+
|
|
3739
|
+
|
|
3740
|
+
|
|
3741
|
+
|
|
3742
|
+
|
|
3743
|
+
|
|
3744
|
+
|
|
3745
|
+
|
|
3746
|
+
|
|
1980
3747
|
|
|
1981
3748
|
|
|
1982
3749
|
|
|
@@ -2032,5 +3799,5 @@ registerActivityType(gapSelectType);
|
|
|
2032
3799
|
|
|
2033
3800
|
|
|
2034
3801
|
|
|
2035
|
-
exports.ActivitySchemaError = ActivitySchemaError; exports.UnknownActivityTypeError = UnknownActivityTypeError; exports.RedactedScoringError = RedactedScoringError; exports.DeferredScoringError = DeferredScoringError; exports.FeedbackSchema = FeedbackSchema; exports.MediaUrlSchema = MediaUrlSchema; exports.NativeControlHintSchema = NativeControlHintSchema; exports.MediaPlaybackSchema = MediaPlaybackSchema; exports.MediaSchema = MediaSchema; exports.RedactedMediaSchema = RedactedMediaSchema; exports.
|
|
2036
|
-
//# sourceMappingURL=chunk-
|
|
3802
|
+
exports.ActivitySchemaError = ActivitySchemaError; exports.UnknownActivityTypeError = UnknownActivityTypeError; exports.RedactedScoringError = RedactedScoringError; exports.DeferredScoringError = DeferredScoringError; exports.withValidationScope = withValidationScope; exports.DICTATION_MAX_TRANSCRIPT_LENGTH = DICTATION_MAX_TRANSCRIPT_LENGTH; exports.DICTATION_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH; exports.DICTATION_MAX_EQUIVALENCE_LENGTH = DICTATION_MAX_EQUIVALENCE_LENGTH; exports.DICTATION_MAX_EQUIVALENCES = DICTATION_MAX_EQUIVALENCES; exports.DICTATION_MAX_ACCEPTED_TRANSCRIPTS = DICTATION_MAX_ACCEPTED_TRANSCRIPTS; exports.FeedbackSchema = FeedbackSchema; exports.MediaUrlSchema = MediaUrlSchema; exports.NativeControlHintSchema = NativeControlHintSchema; exports.MediaPlaybackSchema = MediaPlaybackSchema; exports.MediaSchema = MediaSchema; exports.RedactedMediaSchema = RedactedMediaSchema; exports.DictationSlowMediaSchema = DictationSlowMediaSchema; exports.DictationEquivalenceSchema = DictationEquivalenceSchema; exports.DictationToleranceSchema = DictationToleranceSchema; exports.DictationDataSchema = DictationDataSchema; exports.groupCaptionsRevealDictation = groupCaptionsRevealDictation; exports.GROUP_CAPTIONS_REVEAL_DICTATION = GROUP_CAPTIONS_REVEAL_DICTATION; exports.DRAFT_ISSUE_SEVERITY = DRAFT_ISSUE_SEVERITY; exports.issue = issue; exports.isRecord = isRecord; exports.refusesEmpty = refusesEmpty; exports.isUnset = isUnset; exports.isUnwritten = isUnwritten; exports.isMissingId = isMissingId; exports.checkMedia = checkMedia; exports.covers = covers; exports.pathKey = pathKey; exports.coveredPathsOf = coveredPathsOf; exports.TextMatchPolicySchema = TextMatchPolicySchema; exports.BlankConfigSchema = BlankConfigSchema; exports.FillInTheBlanksDataSchema = FillInTheBlanksDataSchema; exports.MultipleChoiceOptionMediaSchema = MultipleChoiceOptionMediaSchema; exports.MultipleChoiceOptionSchema = MultipleChoiceOptionSchema; exports.MultipleChoiceDataSchema = MultipleChoiceDataSchema; exports.countWords = countWords; exports.GapSelectChoiceSchema = GapSelectChoiceSchema; exports.GapSelectBankSchema = GapSelectBankSchema; exports.GapSelectGapSchema = GapSelectGapSchema; exports.GapSelectDataSchema = GapSelectDataSchema; exports.WrittenResponseRubricCriterionSchema = WrittenResponseRubricCriterionSchema; exports.WrittenResponseRubricSchema = WrittenResponseRubricSchema; exports.WrittenResponseDataSchema = WrittenResponseDataSchema; exports.RedactedMultipleChoiceOptionMediaSchema = RedactedMultipleChoiceOptionMediaSchema; exports.RedactedMultipleChoiceOptionSchema = RedactedMultipleChoiceOptionSchema; exports.RedactedMultipleChoiceDataSchema = RedactedMultipleChoiceDataSchema; exports.RedactedBlankConfigSchema = RedactedBlankConfigSchema; exports.RedactedFillInTheBlanksDataSchema = RedactedFillInTheBlanksDataSchema; exports.RedactedGapSelectChoiceSchema = RedactedGapSelectChoiceSchema; exports.RedactedGapSelectBankSchema = RedactedGapSelectBankSchema; exports.RedactedGapSelectGapSchema = RedactedGapSelectGapSchema; exports.RedactedGapSelectDataSchema = RedactedGapSelectDataSchema; exports.RedactedWrittenResponseDataSchema = RedactedWrittenResponseDataSchema; exports.RedactedDictationSlowMediaSchema = RedactedDictationSlowMediaSchema; exports.RedactedDictationDataSchema = RedactedDictationDataSchema; exports.dictationReferenceWords = dictationReferenceWords; exports.alignDictation = alignDictation; exports.STEP = STEP; exports.alignSequences = alignSequences; exports.levenshteinDistance = levenshteinDistance; exports.matchText = matchText; exports.defineActivityType = defineActivityType; exports.registerActivityType = registerActivityType; exports.getActivityTypeDescriptor = getActivityTypeDescriptor; exports.registeredActivityTypes = registeredActivityTypes; exports.MEDIA_FIELD_POLICY = MEDIA_FIELD_POLICY; exports.multipleChoiceType = multipleChoiceType; exports.fillInTheBlanksType = fillInTheBlanksType; exports.writtenResponseType = writtenResponseType; exports.gapSelectType = gapSelectType; exports.dictationType = dictationType;
|
|
3803
|
+
//# sourceMappingURL=chunk-XONKWL6D.cjs.map
|