@intellectif/lk-core 0.14.0 → 0.15.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -0
  3. package/dist/{activity-BVkg9VDo.d.cts → activity-C7cR8U9n.d.cts} +35 -2
  4. package/dist/{activity-BVkg9VDo.d.ts → activity-C7cR8U9n.d.ts} +35 -2
  5. package/dist/{chunk-R7PV3XIC.js → chunk-54XYQ3CS.js} +7 -4
  6. package/dist/chunk-54XYQ3CS.js.map +1 -0
  7. package/dist/{chunk-J5OIRIOW.cjs → chunk-5KWF34LJ.cjs} +31 -31
  8. package/dist/{chunk-J5OIRIOW.cjs.map → chunk-5KWF34LJ.cjs.map} +1 -1
  9. package/dist/{chunk-CZRUMHKW.cjs → chunk-7ETU6EXQ.cjs} +13 -10
  10. package/dist/chunk-7ETU6EXQ.cjs.map +1 -0
  11. package/dist/{chunk-6RND47QE.js → chunk-DPHX4E7X.js} +62 -3
  12. package/dist/chunk-DPHX4E7X.js.map +1 -0
  13. package/dist/{chunk-NBADQ3JU.js → chunk-EEMVWBPP.js} +195 -4
  14. package/dist/chunk-EEMVWBPP.js.map +1 -0
  15. package/dist/{chunk-RTIR6R2U.js → chunk-L6YBQXSB.js} +2 -2
  16. package/dist/{chunk-VCXIPLEW.cjs → chunk-QFWEF5ST.cjs} +63 -4
  17. package/dist/chunk-QFWEF5ST.cjs.map +1 -0
  18. package/dist/chunk-W7AQP6AV.cjs +418 -0
  19. package/dist/chunk-W7AQP6AV.cjs.map +1 -0
  20. package/dist/{index-v2_-Fyn3.d.ts → index-BWyMDlUS.d.cts} +367 -2
  21. package/dist/{index-BE1bM2Wi.d.cts → index-D10NaztZ.d.ts} +367 -2
  22. package/dist/index.cjs +397 -96
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +103 -7
  25. package/dist/index.d.ts +103 -7
  26. package/dist/index.js +310 -9
  27. package/dist/index.js.map +1 -1
  28. package/dist/schemas.cjs +11 -3
  29. package/dist/schemas.cjs.map +1 -1
  30. package/dist/schemas.d.cts +2 -2
  31. package/dist/schemas.d.ts +2 -2
  32. package/dist/schemas.js +10 -2
  33. package/dist/scoring.cjs +3 -3
  34. package/dist/scoring.d.cts +2 -2
  35. package/dist/scoring.d.ts +2 -2
  36. package/dist/scoring.js +2 -2
  37. package/dist/xapi.cjs +3 -3
  38. package/dist/xapi.d.cts +1 -1
  39. package/dist/xapi.d.ts +1 -1
  40. package/dist/xapi.js +2 -2
  41. package/package.json +1 -1
  42. package/dist/chunk-2NIALQEH.cjs +0 -227
  43. package/dist/chunk-2NIALQEH.cjs.map +0 -1
  44. package/dist/chunk-6RND47QE.js.map +0 -1
  45. package/dist/chunk-CZRUMHKW.cjs.map +0 -1
  46. package/dist/chunk-NBADQ3JU.js.map +0 -1
  47. package/dist/chunk-R7PV3XIC.js.map +0 -1
  48. package/dist/chunk-VCXIPLEW.cjs.map +0 -1
  49. /package/dist/{chunk-RTIR6R2U.js.map → chunk-L6YBQXSB.js.map} +0 -0
package/dist/index.js CHANGED
@@ -16,22 +16,31 @@ import {
16
16
  roundGrade,
17
17
  score,
18
18
  validateSpeechAssessment
19
- } from "./chunk-RTIR6R2U.js";
19
+ } from "./chunk-L6YBQXSB.js";
20
20
  import {
21
21
  XAPIVerb,
22
22
  XAPI_VERB_DISPLAY,
23
23
  validateXAPIStatement,
24
24
  xAPIBuilder,
25
25
  xapiDefinitionFor
26
- } from "./chunk-R7PV3XIC.js";
26
+ } from "./chunk-54XYQ3CS.js";
27
27
  import {
28
+ INTERACTIVE_VIDEO_ITEM_TYPES,
28
29
  ItemGroupSchema,
30
+ MediaTimelineSchema,
29
31
  RedactedItemGroupSchema,
30
32
  RedactedStimulusSchema,
31
33
  StimulusSchema,
34
+ TIMELINE_MAX_CHAPTERS,
35
+ TIMELINE_MAX_ITEMS,
36
+ TIMELINE_MAX_QUIZZES,
37
+ TIMELINE_MAX_TITLE_LENGTH,
38
+ TimelineChapterSchema,
39
+ TimelineCueSchema,
32
40
  dictationJsonSchema,
33
41
  fillInTheBlanksJsonSchema,
34
42
  gapSelectJsonSchema,
43
+ isInteractiveVideoItemType,
35
44
  itemGroupJsonSchema,
36
45
  jsonSchemaFor,
37
46
  multipleChoiceJsonSchema,
@@ -40,7 +49,7 @@ import {
40
49
  validateActivity,
41
50
  validateItemGroup,
42
51
  writtenResponseJsonSchema
43
- } from "./chunk-NBADQ3JU.js";
52
+ } from "./chunk-EEMVWBPP.js";
44
53
  import {
45
54
  ActivitySchemaError,
46
55
  BlankConfigSchema,
@@ -65,6 +74,7 @@ import {
65
74
  MEDIA_FIELD_POLICY,
66
75
  MediaPlaybackSchema,
67
76
  MediaSchema,
77
+ MediaTrackSchema,
68
78
  MediaUrlSchema,
69
79
  MultipleChoiceDataSchema,
70
80
  MultipleChoiceOptionMediaSchema,
@@ -124,7 +134,7 @@ import {
124
134
  registeredActivityTypes,
125
135
  withValidationScope,
126
136
  writtenResponseType
127
- } from "./chunk-6RND47QE.js";
137
+ } from "./chunk-DPHX4E7X.js";
128
138
 
129
139
  // src/content-hash.ts
130
140
  function canonicalJson(value, seen = /* @__PURE__ */ new Set()) {
@@ -226,6 +236,20 @@ function keyOf(entry) {
226
236
  }
227
237
  return key;
228
238
  }
239
+ function inQuizOrder(items, timeline) {
240
+ const placement = /* @__PURE__ */ new Map();
241
+ timeline.cues.forEach((cue, cueIndex) => {
242
+ cue.itemIds.forEach((itemId, position) => {
243
+ if (!placement.has(itemId)) {
244
+ placement.set(itemId, { at: cue.at, cue: cueIndex, position });
245
+ }
246
+ });
247
+ });
248
+ const unplaced = { at: Number.POSITIVE_INFINITY, cue: Number.POSITIVE_INFINITY, position: 0 };
249
+ return items.map((entry) => ({ entry, place: placement.get(entry.item.id) ?? unplaced })).sort(
250
+ (a, b) => a.place.at - b.place.at || a.place.cue - b.place.cue || a.place.position - b.place.position || a.entry.itemIndex - b.entry.itemIndex
251
+ ).map(({ entry }) => entry);
252
+ }
229
253
  function flattenSequence(entries, options = {}) {
230
254
  const shuffleEntries = options.shuffleEntries === true;
231
255
  const needsSeed = shuffleEntries || entries.some((entry) => isItemGroup(entry) && entry.shuffle === "within-group");
@@ -249,11 +273,20 @@ function flattenSequence(entries, options = {}) {
249
273
  `flattenSequence: item group "${entry.id}" has no items. An empty group would silently remove its stimulus and its questions from the presented sequence.`
250
274
  );
251
275
  }
276
+ const timeline = entry.timeline;
277
+ if (timeline !== void 0 && entry.shuffle === "within-group") {
278
+ throw new Error(
279
+ `flattenSequence: item group "${entry.id}" has a timeline and shuffle: "within-group". An interactive video presents its questions in the order its quizzes open.`
280
+ );
281
+ }
252
282
  const items = entry.items.map((item, itemIndex) => ({ item, itemIndex }));
253
- const presented = entry.shuffle === "within-group" ? seededShuffle(items, `${seed}:group:${entry.id}`) : items;
283
+ const presented = timeline !== void 0 ? inQuizOrder(items, timeline) : entry.shuffle === "within-group" ? seededShuffle(items, `${seed}:group:${entry.id}`) : items;
254
284
  const size = presented.length;
255
285
  presented.forEach(({ item, itemIndex }, position) => {
286
+ const cue = timeline?.cues.find((candidate) => candidate.itemIds.includes(item.id));
256
287
  slots.push({
288
+ // Slot ids stay AUTHORED — the item's index or its key — whatever the
289
+ // presented order: moving a quiz must never re-map a stored answer.
257
290
  slotId: `${entryKey}.${keyOf(item) ?? String(itemIndex)}`,
258
291
  index: slots.length,
259
292
  activity: item,
@@ -262,7 +295,9 @@ function flattenSequence(entries, options = {}) {
262
295
  ...entry.title !== void 0 ? { title: entry.title } : {},
263
296
  stimulus: entry.stimulus,
264
297
  position,
265
- size
298
+ size,
299
+ ...timeline !== void 0 ? { timeline } : {},
300
+ ...cue !== void 0 ? { cue } : {}
266
301
  }
267
302
  });
268
303
  });
@@ -445,7 +480,14 @@ function planSlot(slot, points) {
445
480
  group: {
446
481
  id: slot.group.id,
447
482
  ...slot.group.title !== void 0 ? { title: slot.group.title } : {},
448
- stimulusHash: contentHash(slot.group.stimulus)
483
+ stimulusHash: contentHash(slot.group.stimulus),
484
+ ...slot.group.cue !== void 0 ? {
485
+ cue: {
486
+ id: slot.group.cue.id,
487
+ at: slot.group.cue.at,
488
+ ...slot.group.cue.required !== void 0 ? { required: slot.group.cue.required } : {}
489
+ }
490
+ } : {}
449
491
  }
450
492
  } : {},
451
493
  ...mediaBudgets !== void 0 ? { mediaBudgets } : {}
@@ -479,6 +521,7 @@ function verifyAttemptPlan(plan, current) {
479
521
  const addedSlotIds = current.slots.filter((s) => !before.has(s.slotId)).map((s) => s.slotId);
480
522
  const changedSlotIds = [];
481
523
  const changedStimulusSlotIds = [];
524
+ const changedCueSlotIds = [];
482
525
  const changedPointsSlotIds = [];
483
526
  const reorderedSlotIds = [];
484
527
  for (const slot of plan.slots) {
@@ -492,6 +535,9 @@ function verifyAttemptPlan(plan, current) {
492
535
  if (now.group?.stimulusHash !== slot.group?.stimulusHash) {
493
536
  changedStimulusSlotIds.push(slot.slotId);
494
537
  }
538
+ if (contentHash(now.group?.cue ?? null) !== contentHash(slot.group?.cue ?? null)) {
539
+ changedCueSlotIds.push(slot.slotId);
540
+ }
495
541
  if (now.points !== slot.points) {
496
542
  changedPointsSlotIds.push(slot.slotId);
497
543
  }
@@ -500,11 +546,12 @@ function verifyAttemptPlan(plan, current) {
500
546
  }
501
547
  }
502
548
  return {
503
- matches: missingSlotIds.length === 0 && addedSlotIds.length === 0 && changedSlotIds.length === 0 && changedStimulusSlotIds.length === 0 && changedPointsSlotIds.length === 0 && reorderedSlotIds.length === 0,
549
+ matches: missingSlotIds.length === 0 && addedSlotIds.length === 0 && changedSlotIds.length === 0 && changedStimulusSlotIds.length === 0 && changedCueSlotIds.length === 0 && changedPointsSlotIds.length === 0 && reorderedSlotIds.length === 0,
504
550
  missingSlotIds,
505
551
  addedSlotIds,
506
552
  changedSlotIds,
507
553
  changedStimulusSlotIds,
554
+ changedCueSlotIds,
508
555
  changedPointsSlotIds,
509
556
  reorderedSlotIds
510
557
  };
@@ -652,6 +699,14 @@ function createItemGroupDraft(context) {
652
699
  items: []
653
700
  };
654
701
  }
702
+ function createInteractiveVideoDraft(context) {
703
+ const draft = createItemGroupDraft(context);
704
+ return {
705
+ ...draft,
706
+ stimulus: { id: draft.stimulus.id, kind: "video", media: { type: "video", url: "" } },
707
+ timeline: { cues: [] }
708
+ };
709
+ }
655
710
  function validateItemGroupDraft(draft) {
656
711
  return withValidationScope(() => validateItemGroupDraftInScope(draft));
657
712
  }
@@ -676,6 +731,9 @@ function validateItemGroupDraftInScope(draft) {
676
731
  );
677
732
  }
678
733
  issues.push(...checkItems(draft));
734
+ if (draft.timeline !== void 0 && draft.timeline !== null) {
735
+ issues.push(...checkTimeline(draft));
736
+ }
679
737
  if (groupCaptionsRevealDictation(draft, (captionsUrl) => !isUnwritten(captionsUrl))) {
680
738
  issues.push(
681
739
  issue(
@@ -722,6 +780,183 @@ function validateItemGroupDraftInScope(draft) {
722
780
  const invalid = issues.some((found) => found.severity !== "incomplete");
723
781
  return { status: invalid ? "invalid" : "incomplete", issues };
724
782
  }
783
+ function checkTimeline(draft) {
784
+ const issues = [];
785
+ const timeline = draft.timeline;
786
+ if (!isRecord(timeline)) {
787
+ return issues;
788
+ }
789
+ const stimulus = isRecord(draft.stimulus) ? draft.stimulus : void 0;
790
+ if (stimulus !== void 0 && !isUnwritten(stimulus.kind) && stimulus.kind !== "video") {
791
+ issues.push(
792
+ issue(
793
+ "ig_timeline_stimulus_kind",
794
+ ["stimulus", "kind"],
795
+ "An interactive video needs a video: quizzes open at moments of it."
796
+ )
797
+ );
798
+ } else if (stimulus !== void 0 && isRecord(stimulus.media) && !isUnwritten(stimulus.media.type) && stimulus.media.type !== "video") {
799
+ issues.push(
800
+ issue(
801
+ "ig_timeline_stimulus_kind",
802
+ ["stimulus", "media", "type"],
803
+ "Upload a video file. An embedded player from another site cannot be paused when a quiz opens."
804
+ )
805
+ );
806
+ }
807
+ if (draft.shuffle === "within-group") {
808
+ issues.push(
809
+ issue(
810
+ "ig_timeline_shuffle",
811
+ ["shuffle"],
812
+ "The questions of an interactive video appear in the order their quizzes open, so they cannot be shuffled."
813
+ )
814
+ );
815
+ }
816
+ const items = Array.isArray(draft.items) ? draft.items : [];
817
+ const itemIndexById = /* @__PURE__ */ new Map();
818
+ items.forEach((item, index) => {
819
+ if (!isRecord(item)) {
820
+ return;
821
+ }
822
+ if (typeof item.id === "string" && item.id !== "") {
823
+ itemIndexById.set(item.id, index);
824
+ }
825
+ const type = item.type;
826
+ if (typeof type === "string" && type !== "" && type !== "item-group" && !isInteractiveVideoItemType(type) && getActivityTypeDescriptor(type) !== void 0) {
827
+ issues.push(
828
+ issue(
829
+ "ig_timeline_item_type",
830
+ ["items", index, "type"],
831
+ `An interactive video can hold ${INTERACTIVE_VIDEO_ITEM_TYPES.join(", ")} questions only.`
832
+ )
833
+ );
834
+ }
835
+ if (type === "dictation" && isUnset(item.media)) {
836
+ issues.push(
837
+ issue(
838
+ "ig_timeline_dictation_media",
839
+ ["items", index, "media"],
840
+ "Add the recording this dictation plays: inside a video it cannot play the video."
841
+ )
842
+ );
843
+ }
844
+ });
845
+ const cues = Array.isArray(timeline.cues) ? timeline.cues : [];
846
+ const quizIds = /* @__PURE__ */ new Set();
847
+ const placed = /* @__PURE__ */ new Set();
848
+ cues.forEach((cue, cueIndex) => {
849
+ if (!isRecord(cue)) {
850
+ return;
851
+ }
852
+ const at = (field, ...rest) => [
853
+ "timeline",
854
+ "cues",
855
+ cueIndex,
856
+ field,
857
+ ...rest
858
+ ];
859
+ if (isMissingId(cue.id)) {
860
+ issues.push(issue("ig_timeline_quiz_id_required", at("id"), "This quiz has no id."));
861
+ } else if (typeof cue.id === "string") {
862
+ if (quizIds.has(cue.id)) {
863
+ issues.push(
864
+ issue("ig_timeline_quiz_id_duplicate", at("id"), `Two quizzes share the id "${cue.id}".`)
865
+ );
866
+ }
867
+ quizIds.add(cue.id);
868
+ }
869
+ if (isUnset(cue.at)) {
870
+ issues.push(
871
+ issue(
872
+ "ig_timeline_quiz_time_required",
873
+ at("at"),
874
+ "Choose when in the video this quiz opens."
875
+ )
876
+ );
877
+ } else if (typeof cue.at !== "number" || !Number.isFinite(cue.at) || cue.at < 0) {
878
+ issues.push(
879
+ issue(
880
+ "ig_timeline_quiz_time_invalid",
881
+ at("at"),
882
+ "A quiz opens at a number of seconds, 0 or more."
883
+ )
884
+ );
885
+ }
886
+ if (isUnset(cue.itemIds) || Array.isArray(cue.itemIds) && cue.itemIds.length === 0) {
887
+ issues.push(
888
+ issue("ig_timeline_quiz_empty", at("itemIds"), "Add at least one question to this quiz.")
889
+ );
890
+ return;
891
+ }
892
+ if (!Array.isArray(cue.itemIds)) {
893
+ return;
894
+ }
895
+ cue.itemIds.forEach((itemId, position) => {
896
+ if (typeof itemId !== "string") {
897
+ return;
898
+ }
899
+ if (!itemIndexById.has(itemId)) {
900
+ issues.push(
901
+ issue(
902
+ "ig_timeline_quiz_unknown_item",
903
+ at("itemIds", position),
904
+ `This quiz names a question that is not in the video ("${itemId}").`
905
+ )
906
+ );
907
+ } else if (placed.has(itemId)) {
908
+ issues.push(
909
+ issue(
910
+ "ig_timeline_item_duplicate",
911
+ at("itemIds", position),
912
+ "This question is already in a quiz: each question belongs to one quiz."
913
+ )
914
+ );
915
+ }
916
+ placed.add(itemId);
917
+ });
918
+ });
919
+ for (const [itemId, index] of itemIndexById) {
920
+ if (!placed.has(itemId)) {
921
+ issues.push(
922
+ issue(
923
+ "ig_timeline_item_unplaced",
924
+ ["items", index],
925
+ "Add this question to a quiz, or the video never shows it."
926
+ )
927
+ );
928
+ }
929
+ }
930
+ const chapters = Array.isArray(timeline.chapters) ? timeline.chapters : [];
931
+ let previousAt;
932
+ chapters.forEach((chapter, index) => {
933
+ if (!isRecord(chapter)) {
934
+ return;
935
+ }
936
+ if (isUnwritten(chapter.title)) {
937
+ issues.push(
938
+ issue(
939
+ "ig_timeline_chapter_title",
940
+ ["timeline", "chapters", index, "title"],
941
+ "Give this chapter a title."
942
+ )
943
+ );
944
+ }
945
+ if (typeof chapter.at === "number" && Number.isFinite(chapter.at)) {
946
+ if (previousAt !== void 0 && chapter.at <= previousAt) {
947
+ issues.push(
948
+ issue(
949
+ "ig_timeline_chapter_order",
950
+ ["timeline", "chapters", index, "at"],
951
+ "Chapters must start in order, each after the one before it."
952
+ )
953
+ );
954
+ }
955
+ previousAt = chapter.at;
956
+ }
957
+ });
958
+ return issues;
959
+ }
725
960
  function checkGroupIdentity(draft) {
726
961
  const issues = [];
727
962
  if (draft.schemaVersion !== "1.0") {
@@ -1127,7 +1362,25 @@ var ITEM_GROUP_FIELD_POLICY = {
1127
1362
  // and identity has to cross the redaction boundary intact.
1128
1363
  slotKey: "public",
1129
1364
  shuffle: "public",
1130
- stimulus: STIMULUS_FIELD_POLICY
1365
+ stimulus: STIMULUS_FIELD_POLICY,
1366
+ // Public, and classified key by key rather than as one leaf: the learner's
1367
+ // player needs every part of it, none of it is a key — and a leaf would hand
1368
+ // over the author's object by reference, with whatever else was parked in it.
1369
+ timeline: {
1370
+ // Applies to every element of the array.
1371
+ cues: {
1372
+ id: "public",
1373
+ at: "public",
1374
+ itemIds: "public",
1375
+ title: "public",
1376
+ required: "public"
1377
+ },
1378
+ chapters: {
1379
+ at: "public",
1380
+ title: "public"
1381
+ },
1382
+ navigation: "public"
1383
+ }
1131
1384
  };
1132
1385
  function redactItemGroup(group, options = {}) {
1133
1386
  const reveal = options.reveal ?? "none";
@@ -1197,6 +1450,41 @@ function assertRedactedItemGroup(data) {
1197
1450
  }
1198
1451
  });
1199
1452
  }
1453
+
1454
+ // src/timeline.ts
1455
+ function readMediaProgress(value, duration) {
1456
+ let version;
1457
+ let at;
1458
+ let furthest;
1459
+ try {
1460
+ if (typeof value !== "object" || value === null) {
1461
+ return null;
1462
+ }
1463
+ ({ progressVersion: version, at, furthest } = value);
1464
+ } catch {
1465
+ return null;
1466
+ }
1467
+ if (version !== "1.0" || typeof at !== "number" || !Number.isFinite(at)) {
1468
+ return null;
1469
+ }
1470
+ const end = typeof duration === "number" && Number.isFinite(duration) && duration >= 0 ? duration : Number.POSITIVE_INFINITY;
1471
+ const clampedAt = Math.min(Math.max(0, at), end);
1472
+ const reached = typeof furthest === "number" && Number.isFinite(furthest) ? furthest : clampedAt;
1473
+ return {
1474
+ progressVersion: "1.0",
1475
+ at: clampedAt,
1476
+ furthest: Math.min(Math.max(clampedAt, reached), end)
1477
+ };
1478
+ }
1479
+ function composeTimelineScore(entryKey, items, policy) {
1480
+ const own = items.filter((item) => entryKeyOf(item.slotId) === entryKey);
1481
+ if (own.length === 0) {
1482
+ throw new Error(
1483
+ `composeTimelineScore: no scored item belongs to entry "${entryKey}". Pass the video's slotKey (or its position in the sequence), and the items of the attempt that holds it.`
1484
+ );
1485
+ }
1486
+ return composeAssessmentScore([{ id: entryKey, weight: 1, items: own }], policy);
1487
+ }
1200
1488
  export {
1201
1489
  ActivitySchemaError,
1202
1490
  BlankConfigSchema,
@@ -1217,9 +1505,12 @@ export {
1217
1505
  GapSelectChoiceSchema,
1218
1506
  GapSelectDataSchema,
1219
1507
  GapSelectGapSchema,
1508
+ INTERACTIVE_VIDEO_ITEM_TYPES,
1220
1509
  ItemGroupSchema,
1221
1510
  MediaPlaybackSchema,
1222
1511
  MediaSchema,
1512
+ MediaTimelineSchema,
1513
+ MediaTrackSchema,
1223
1514
  MediaUrlSchema,
1224
1515
  MultipleChoiceDataSchema,
1225
1516
  MultipleChoiceOptionMediaSchema,
@@ -1250,7 +1541,13 @@ export {
1250
1541
  RedactedWrittenResponseDataSchema,
1251
1542
  SPEECH_ASSESSMENT_MAX_WORDS,
1252
1543
  StimulusSchema,
1544
+ TIMELINE_MAX_CHAPTERS,
1545
+ TIMELINE_MAX_ITEMS,
1546
+ TIMELINE_MAX_QUIZZES,
1547
+ TIMELINE_MAX_TITLE_LENGTH,
1253
1548
  TextMatchPolicySchema,
1549
+ TimelineChapterSchema,
1550
+ TimelineCueSchema,
1254
1551
  UnknownActivityTypeError,
1255
1552
  WrittenResponseDataSchema,
1256
1553
  WrittenResponseRubricCriterionSchema,
@@ -1264,10 +1561,12 @@ export {
1264
1561
  canonicalJson,
1265
1562
  classifyBand,
1266
1563
  composeAssessmentScore,
1564
+ composeTimelineScore,
1267
1565
  computePassThreshold,
1268
1566
  contentHash,
1269
1567
  countWords,
1270
1568
  createDraft,
1569
+ createInteractiveVideoDraft,
1271
1570
  createItemGroupDraft,
1272
1571
  defineActivityType,
1273
1572
  dictationJsonSchema,
@@ -1290,6 +1589,7 @@ export {
1290
1589
  hasGrade,
1291
1590
  hashSeed,
1292
1591
  inspectWav,
1592
+ isInteractiveVideoItemType,
1293
1593
  isItemGroup,
1294
1594
  itemGroupJsonSchema,
1295
1595
  jsonSchemaFor,
@@ -1303,6 +1603,7 @@ export {
1303
1603
  planMediaBudgets,
1304
1604
  readAloudJsonSchema,
1305
1605
  readAloudType,
1606
+ readMediaProgress,
1306
1607
  redact,
1307
1608
  redactItemGroup,
1308
1609
  registerActivityType,