@intellectif/lk-core 0.17.0 → 0.19.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 (39) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +3 -2
  3. package/dist/{activity-BmjBjXVJ.d.cts → activity-f0bgZSTP.d.cts} +175 -134
  4. package/dist/{activity-BmjBjXVJ.d.ts → activity-f0bgZSTP.d.ts} +175 -134
  5. package/dist/ai-check.cjs +283 -0
  6. package/dist/ai-check.cjs.map +1 -0
  7. package/dist/ai-check.d.cts +106 -0
  8. package/dist/ai-check.d.ts +106 -0
  9. package/dist/ai-check.js +283 -0
  10. package/dist/ai-check.js.map +1 -0
  11. package/dist/chunk-BIJCO2HM.js +443 -0
  12. package/dist/chunk-BIJCO2HM.js.map +1 -0
  13. package/dist/{chunk-XNMKDFPW.cjs → chunk-COBK2RUX.cjs} +44 -9
  14. package/dist/chunk-COBK2RUX.cjs.map +1 -0
  15. package/dist/chunk-FVZJ4X3Y.cjs +443 -0
  16. package/dist/chunk-FVZJ4X3Y.cjs.map +1 -0
  17. package/dist/{chunk-WXS5EB65.js → chunk-MMEQKFFQ.js} +43 -8
  18. package/dist/chunk-MMEQKFFQ.js.map +1 -0
  19. package/dist/{index-G_KusUcK.d.ts → index-DMCM3e3P.d.ts} +1 -1
  20. package/dist/{index-CvfeD_cU.d.cts → index-bpuDhvXV.d.cts} +1 -1
  21. package/dist/index.cjs +37 -422
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +30 -11
  24. package/dist/index.d.ts +30 -11
  25. package/dist/index.js +37 -422
  26. package/dist/index.js.map +1 -1
  27. package/dist/schemas.d.cts +2 -2
  28. package/dist/schemas.d.ts +2 -2
  29. package/dist/scoring.cjs +2 -2
  30. package/dist/scoring.d.cts +33 -5
  31. package/dist/scoring.d.ts +33 -5
  32. package/dist/scoring.js +1 -1
  33. package/dist/xapi.d.cts +1 -1
  34. package/dist/xapi.d.ts +1 -1
  35. package/package.json +11 -1
  36. package/vectors/README.md +9 -3
  37. package/vectors/scoring.json +1258 -10
  38. package/dist/chunk-WXS5EB65.js.map +0 -1
  39. package/dist/chunk-XNMKDFPW.cjs.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
- import { v as AiSupportedActivityType, g as AiActivityInput, k as AiFeature, Z as LearnerResponse, Y as ItemOutcome, j as AiExplanationRequest, o as AiGrade, p as AiHintRequest, ad as ScoringDetail, q as AiItemFacts, w as AiTextResult, u as AiRefusal, a as ActivityData, ai as ValidationError, f as ActivityType, b as ActivityDataMap, C as CriterionScore, Q as GradeRecord, a5 as NativeControlHint, d as ActivityMedia, af as ScoringResult, z as DeferredScoringPartial, E as DictationData, G as DictationLearnerResponse, J as FillInTheBlanksData, K as FillInTheBlanksLearnerResponse, N as GapSelectData, P as GapSelectLearnerResponse, a1 as MultipleChoiceData, a2 as MultipleChoiceLearnerResponse, a6 as ReadAloudData, a9 as ReadAloudLearnerResponse, ak as WrittenResponseData, al as WrittenResponseLearnerResponse } from './activity-BmjBjXVJ.cjs';
2
- export { A as ActivityAiPermissions, c as ActivityFeedback, e as ActivityResult, h as AiBlankFact, i as AiDictationFacts, l as AiFillInTheBlanksFacts, m as AiGapFact, n as AiGapSelectFacts, r as AiMultipleChoiceFacts, s as AiOptionFact, t as AiProvenance, x as AiVerdict, y as AiWordFact, B as BlankConfig, D as DeferredReason, F as DictationEquivalence, H as DictationSlowMedia, I as DictationTolerance, L as GapSelectBank, M as GapSelectChoice, O as GapSelectGap, R as Grader, S as GraderKind, T as GraderUsage, U as GradingState, V as InlineCorrection, W as InteractionEvent, X as InteractionKind, _ as LearnerResponseMap, $ as MediaPlaybackPolicy, a0 as MediaTrack, a3 as MultipleChoiceOption, a4 as MultipleChoiceOptionMedia, a7 as ReadAloudDimension, a8 as ReadAloudDimensionWeight, aa as ReadAloudSlowMedia, ab as RecordingBounds, ac as RecordingRef, ae as ScoringOutcome, ag as TextMatchPolicy, ah as TextMatchResult, aj as ValidationResult, am as WrittenResponseRubric, an as WrittenResponseRubricCriterion, ao as XAPIActor, ap as XAPIConfig, aq as XAPIContext, ar as XAPIContextActivities, as as XAPIError, at as XAPIObject, au as XAPIResult, av as XAPIScore, aw as XAPIStatement, ax as XAPIVerbObject, ay as levenshteinDistance, az as matchText } from './activity-BmjBjXVJ.cjs';
1
+ import { v as AiSupportedActivityType, g as AiActivityInput, k as AiFeature, Z as LearnerResponse, Y as ItemOutcome, j as AiExplanationRequest, o as AiGrade, p as AiHintRequest, ad as ScoringDetail, q as AiItemFacts, w as AiTextResult, u as AiRefusal, a as ActivityData, ai as ValidationError, f as ActivityType, b as ActivityDataMap, C as CriterionScore, Q as GradeRecord, a5 as NativeControlHint, d as ActivityMedia, af as ScoringResult, z as DeferredScoringPartial, E as DictationData, G as DictationLearnerResponse, J as FillInTheBlanksData, K as FillInTheBlanksLearnerResponse, N as GapSelectData, P as GapSelectLearnerResponse, a1 as MultipleChoiceData, a2 as MultipleChoiceLearnerResponse, a6 as ReadAloudData, a9 as ReadAloudLearnerResponse, ak as WrittenResponseData, al as WrittenResponseLearnerResponse } from './activity-f0bgZSTP.cjs';
2
+ export { A as ActivityAiPermissions, c as ActivityFeedback, e as ActivityResult, h as AiBlankFact, i as AiDictationFacts, l as AiFillInTheBlanksFacts, m as AiGapFact, n as AiGapSelectFacts, r as AiMultipleChoiceFacts, s as AiOptionFact, t as AiProvenance, x as AiVerdict, y as AiWordFact, B as BlankConfig, D as DeferredReason, F as DictationEquivalence, H as DictationSlowMedia, I as DictationTolerance, L as GapSelectBank, M as GapSelectChoice, O as GapSelectGap, R as Grader, S as GraderKind, T as GraderUsage, U as GradingState, V as InlineCorrection, W as InteractionEvent, X as InteractionKind, _ as LearnerResponseMap, $ as MediaPlaybackPolicy, a0 as MediaTrack, a3 as MultipleChoiceOption, a4 as MultipleChoiceOptionMedia, a7 as ReadAloudDimension, a8 as ReadAloudDimensionWeight, aa as ReadAloudSlowMedia, ab as RecordingBounds, ac as RecordingRef, ae as ScoringOutcome, ag as TextMatchPolicy, ah as TextMatchResult, aj as ValidationResult, am as WrittenResponseRubric, an as WrittenResponseRubricCriterion, ao as XAPIActor, ap as XAPIConfig, aq as XAPIContext, ar as XAPIContextActivities, as as XAPIError, at as XAPIObject, au as XAPIResult, av as XAPIScore, aw as XAPIStatement, ax as XAPIVerbObject, ay as levenshteinDistance, az as matchText } from './activity-f0bgZSTP.cjs';
3
3
  import { ScoredItem, RoundingPolicy, CompositionPolicy, AssessmentScore } from './scoring.cjs';
4
4
  export { AssessmentSectionInput, Band, DEFAULT_PASS_THRESHOLD, DICTATION_MAX_ACCEPTED_TRANSCRIPTS, DICTATION_MAX_EQUIVALENCES, DICTATION_MAX_EQUIVALENCE_LENGTH, DICTATION_MAX_TEXT_LENGTH, DICTATION_MAX_TRANSCRIPT_LENGTH, DictationAlignment, DictationCharOp, DictationReference, DictationWordAlignment, GradeReadAloudOptions, PassFailureReason, READ_ALOUD_MAX_DIMENSION_WEIGHT, READ_ALOUD_MAX_REFERENCE_LENGTH, READ_ALOUD_MAX_SECONDS, READ_ALOUD_MAX_TAKES, ReadAloudWordAlignment, ReadAloudWordState, RoundingMode, SPEECH_ASSESSMENT_MAX_WORDS, ScoringOptions, SectionScore, SpeechAssessment, SpeechMeasurement, SpeechPhoneme, SpeechPhonemeCandidate, SpeechPlausibilityPolicy, SpeechSyllable, SpeechUnscorable, SpeechUnscorableCode, SpeechWord, SpeechWordError, WavInspection, WavInspectionPolicy, alignDictation, alignReadAloud, classifyBand, composeAssessmentScore, computePassThreshold, dictationReferenceWords, diffDictationChars, evaluate, gradeReadAloud, gte, inspectWav, outcomeFromUnscorable, roundGrade, score, validateSpeechAssessment } from './scoring.cjs';
5
- import { a5 as SequenceSlot, a4 as SequenceEntry, I as ItemGroup, i as MediaProgress } from './index-CvfeD_cU.cjs';
6
- export { A as AiPermissionsSchema, B as BlankConfigSchema, D as DictationDataSchema, a as DictationEquivalenceSchema, b as DictationSlowMediaSchema, c as DictationToleranceSchema, F as FeedbackSchema, d as FillInTheBlanksDataSchema, G as GapSelectBankSchema, e as GapSelectChoiceSchema, f as GapSelectDataSchema, g as GapSelectGapSchema, h as ItemGroupSchema, M as MediaPlaybackSchema, j as MediaSchema, k as MediaTimeline, l as MediaTimelineSchema, m as MediaTrackSchema, n as MediaUrlSchema, o as MultipleChoiceDataSchema, p as MultipleChoiceOptionMediaSchema, q as MultipleChoiceOptionSchema, N as NativeControlHintSchema, R as ReadAloudDataSchema, r as ReadAloudSlowMediaSchema, s as RedactedActivity, t as RedactedBlankConfig, u as RedactedBlankConfigSchema, v as RedactedDictationData, w as RedactedDictationDataSchema, x as RedactedDictationSlowMedia, y as RedactedDictationSlowMediaSchema, z as RedactedFillInTheBlanksData, C as RedactedFillInTheBlanksDataSchema, E as RedactedGapSelectBank, H as RedactedGapSelectBankSchema, J as RedactedGapSelectChoice, K as RedactedGapSelectChoiceSchema, L as RedactedGapSelectData, O as RedactedGapSelectDataSchema, P as RedactedGapSelectGap, Q as RedactedGapSelectGapSchema, S as RedactedItemGroupSchema, T as RedactedMediaSchema, U as RedactedMultipleChoiceData, V as RedactedMultipleChoiceDataSchema, W as RedactedMultipleChoiceOption, X as RedactedMultipleChoiceOptionMedia, Y as RedactedMultipleChoiceOptionMediaSchema, Z as RedactedMultipleChoiceOptionSchema, _ as RedactedReadAloudData, $ as RedactedReadAloudDataSchema, a0 as RedactedStimulus, a1 as RedactedStimulusSchema, a2 as RedactedWrittenResponseData, a3 as RedactedWrittenResponseDataSchema, a6 as SequenceSlotGroup, a7 as Stimulus, a8 as StimulusKind, a9 as StimulusSchema, aa as TextMatchPolicySchema, ab as TimelineChapter, ac as TimelineChapterSchema, ad as TimelineCue, ae as TimelineCueSchema, af as WrittenResponseDataSchema, ag as WrittenResponseRubricCriterionSchema, ah as WrittenResponseRubricSchema, ai as dictationJsonSchema, aj as fillInTheBlanksJsonSchema, ak as gapSelectJsonSchema, al as itemGroupJsonSchema, am as jsonSchemaFor, an as multipleChoiceJsonSchema, ao as readAloudJsonSchema, ap as stimulusJsonSchema, aq as validateActivity, ar as validateItemGroup, as as writtenResponseJsonSchema } from './index-CvfeD_cU.cjs';
5
+ import { a5 as SequenceSlot, a4 as SequenceEntry, I as ItemGroup, i as MediaProgress } from './index-bpuDhvXV.cjs';
6
+ export { A as AiPermissionsSchema, B as BlankConfigSchema, D as DictationDataSchema, a as DictationEquivalenceSchema, b as DictationSlowMediaSchema, c as DictationToleranceSchema, F as FeedbackSchema, d as FillInTheBlanksDataSchema, G as GapSelectBankSchema, e as GapSelectChoiceSchema, f as GapSelectDataSchema, g as GapSelectGapSchema, h as ItemGroupSchema, M as MediaPlaybackSchema, j as MediaSchema, k as MediaTimeline, l as MediaTimelineSchema, m as MediaTrackSchema, n as MediaUrlSchema, o as MultipleChoiceDataSchema, p as MultipleChoiceOptionMediaSchema, q as MultipleChoiceOptionSchema, N as NativeControlHintSchema, R as ReadAloudDataSchema, r as ReadAloudSlowMediaSchema, s as RedactedActivity, t as RedactedBlankConfig, u as RedactedBlankConfigSchema, v as RedactedDictationData, w as RedactedDictationDataSchema, x as RedactedDictationSlowMedia, y as RedactedDictationSlowMediaSchema, z as RedactedFillInTheBlanksData, C as RedactedFillInTheBlanksDataSchema, E as RedactedGapSelectBank, H as RedactedGapSelectBankSchema, J as RedactedGapSelectChoice, K as RedactedGapSelectChoiceSchema, L as RedactedGapSelectData, O as RedactedGapSelectDataSchema, P as RedactedGapSelectGap, Q as RedactedGapSelectGapSchema, S as RedactedItemGroupSchema, T as RedactedMediaSchema, U as RedactedMultipleChoiceData, V as RedactedMultipleChoiceDataSchema, W as RedactedMultipleChoiceOption, X as RedactedMultipleChoiceOptionMedia, Y as RedactedMultipleChoiceOptionMediaSchema, Z as RedactedMultipleChoiceOptionSchema, _ as RedactedReadAloudData, $ as RedactedReadAloudDataSchema, a0 as RedactedStimulus, a1 as RedactedStimulusSchema, a2 as RedactedWrittenResponseData, a3 as RedactedWrittenResponseDataSchema, a6 as SequenceSlotGroup, a7 as Stimulus, a8 as StimulusKind, a9 as StimulusSchema, aa as TextMatchPolicySchema, ab as TimelineChapter, ac as TimelineChapterSchema, ad as TimelineCue, ae as TimelineCueSchema, af as WrittenResponseDataSchema, ag as WrittenResponseRubricCriterionSchema, ah as WrittenResponseRubricSchema, ai as dictationJsonSchema, aj as fillInTheBlanksJsonSchema, ak as gapSelectJsonSchema, al as itemGroupJsonSchema, am as jsonSchemaFor, an as multipleChoiceJsonSchema, ao as readAloudJsonSchema, ap as stimulusJsonSchema, aq as validateActivity, ar as validateItemGroup, as as writtenResponseJsonSchema } from './index-bpuDhvXV.cjs';
7
7
  import { z } from 'zod/v4';
8
8
  export { AnsweredStatementParams, CompletedStatementParams, SubmittedStatementParams, XAPIObjectParams, XAPIStatementParams, XAPIVerb, XAPIVerbKey, XAPI_VERB_DISPLAY, validateXAPIStatement, xAPIBuilder, xapiDefinitionFor } from './xapi.cjs';
9
9
 
@@ -45,11 +45,15 @@ declare function aiGradeOf(result: {
45
45
  declare function buildAiFacts(data: AiActivityInput, response: LearnerResponse | null, details: readonly ScoringDetail[] | null): AiItemFacts | null;
46
46
  /**
47
47
  * The request for an explanation of `response`, or `null` when there is none
48
- * to give: an unsupported type, an author who switched explanations off, or
49
- * nothing graded to explain.
48
+ * to give: an unsupported type, an author who switched explanations off,
49
+ * nothing graded to explain, or a grade of record that cannot be a grade.
50
50
  *
51
51
  * A scored `outcome` — the grade of record a review shows — wins over grading
52
- * locally, so the explanation speaks to the grade on the screen.
52
+ * locally, so the explanation speaks to the grade on the screen. One whose
53
+ * numbers cannot be a grade (the rule `outcomeFromGrade` applies: `maxScore`
54
+ * positive and finite, `score` finite from 0 to it) gets no explanation at all:
55
+ * explaining 85 "out of 1" would tell the model a wrong answer was correct, and
56
+ * grading locally instead would speak to a grade the screen does not show.
53
57
  */
54
58
  declare function aiExplanationRequest(input: {
55
59
  data: AiActivityInput;
@@ -743,10 +747,12 @@ interface GradeFromRubricOptions {
743
747
  * total a pure function of the judgements, so a grade can be recomputed and
744
748
  * audited years later.
745
749
  *
746
- * Weights are normalised by their sum, so they need not add to 1. Criteria
747
- * marked `notApplicable`, and those carrying no numeric `score` (a purely
748
- * banded judgement), are excluded from both numerator and denominator. When
749
- * nothing scoreable remains the result is `unscorable`, never a zero.
750
+ * Weights are normalised by their sum, so they need not add to 1; each must be
751
+ * zero or more, and their sum a positive, finite number, or the result is
752
+ * `unscorable`. Criteria marked `notApplicable`, and those carrying no numeric
753
+ * `score` (a purely banded judgement), are excluded from both numerator and
754
+ * denominator, weight included. When nothing scoreable remains the result is
755
+ * `unscorable`, never a zero.
750
756
  *
751
757
  * Scores need not be in [0,1]: set `maxScore` on a criterion to declare what
752
758
  * its score is out of, and each is normalised before weighting. A grader
@@ -771,6 +777,19 @@ declare function gradeFromRubric(criteria: readonly CriterionScore[], activityDa
771
777
  * a grade that arrived asynchronously renders through the same path as a
772
778
  * synchronously scored item. The score/passed/feedback fields are mirrored
773
779
  * onto the outcome for uniform reads; `grade` carries the full record.
780
+ *
781
+ * A record whose numbers cannot be a grade is refused, as `evaluate` refuses a
782
+ * non-finite score and {@link gradeFromRubric} one outside [0,1]. `maxScore`
783
+ * must be a positive, finite number and `score` a finite number from 0 to it (a
784
+ * hair above, one part in a billion, is float noise and passes as it is).
785
+ * Anything else — NaN, 85 "out of 1", a score out of 0 — comes back as
786
+ * `{ status: 'deferred', reason: 'grade_rejected', maxScore: 1, rejectedGrade }`
787
+ * with the record kept verbatim. Not `graded`, or its `passed` would be read as
788
+ * a verdict; not `unscorable`, which `composeAssessmentScore` drops from the
789
+ * denominator while letting the attempt go final — so a failing grade on the
790
+ * wrong scale would vanish and the rest would be recorded as a pass. Deferred
791
+ * is what it is: the slot is still owed a real grade, so a composed attempt
792
+ * stays `provisional` and names the slot in `rejectedSlotIds`.
774
793
  */
775
794
  declare function outcomeFromGrade(grade: GradeRecord): ItemOutcome;
776
795
  /**
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { v as AiSupportedActivityType, g as AiActivityInput, k as AiFeature, Z as LearnerResponse, Y as ItemOutcome, j as AiExplanationRequest, o as AiGrade, p as AiHintRequest, ad as ScoringDetail, q as AiItemFacts, w as AiTextResult, u as AiRefusal, a as ActivityData, ai as ValidationError, f as ActivityType, b as ActivityDataMap, C as CriterionScore, Q as GradeRecord, a5 as NativeControlHint, d as ActivityMedia, af as ScoringResult, z as DeferredScoringPartial, E as DictationData, G as DictationLearnerResponse, J as FillInTheBlanksData, K as FillInTheBlanksLearnerResponse, N as GapSelectData, P as GapSelectLearnerResponse, a1 as MultipleChoiceData, a2 as MultipleChoiceLearnerResponse, a6 as ReadAloudData, a9 as ReadAloudLearnerResponse, ak as WrittenResponseData, al as WrittenResponseLearnerResponse } from './activity-BmjBjXVJ.js';
2
- export { A as ActivityAiPermissions, c as ActivityFeedback, e as ActivityResult, h as AiBlankFact, i as AiDictationFacts, l as AiFillInTheBlanksFacts, m as AiGapFact, n as AiGapSelectFacts, r as AiMultipleChoiceFacts, s as AiOptionFact, t as AiProvenance, x as AiVerdict, y as AiWordFact, B as BlankConfig, D as DeferredReason, F as DictationEquivalence, H as DictationSlowMedia, I as DictationTolerance, L as GapSelectBank, M as GapSelectChoice, O as GapSelectGap, R as Grader, S as GraderKind, T as GraderUsage, U as GradingState, V as InlineCorrection, W as InteractionEvent, X as InteractionKind, _ as LearnerResponseMap, $ as MediaPlaybackPolicy, a0 as MediaTrack, a3 as MultipleChoiceOption, a4 as MultipleChoiceOptionMedia, a7 as ReadAloudDimension, a8 as ReadAloudDimensionWeight, aa as ReadAloudSlowMedia, ab as RecordingBounds, ac as RecordingRef, ae as ScoringOutcome, ag as TextMatchPolicy, ah as TextMatchResult, aj as ValidationResult, am as WrittenResponseRubric, an as WrittenResponseRubricCriterion, ao as XAPIActor, ap as XAPIConfig, aq as XAPIContext, ar as XAPIContextActivities, as as XAPIError, at as XAPIObject, au as XAPIResult, av as XAPIScore, aw as XAPIStatement, ax as XAPIVerbObject, ay as levenshteinDistance, az as matchText } from './activity-BmjBjXVJ.js';
1
+ import { v as AiSupportedActivityType, g as AiActivityInput, k as AiFeature, Z as LearnerResponse, Y as ItemOutcome, j as AiExplanationRequest, o as AiGrade, p as AiHintRequest, ad as ScoringDetail, q as AiItemFacts, w as AiTextResult, u as AiRefusal, a as ActivityData, ai as ValidationError, f as ActivityType, b as ActivityDataMap, C as CriterionScore, Q as GradeRecord, a5 as NativeControlHint, d as ActivityMedia, af as ScoringResult, z as DeferredScoringPartial, E as DictationData, G as DictationLearnerResponse, J as FillInTheBlanksData, K as FillInTheBlanksLearnerResponse, N as GapSelectData, P as GapSelectLearnerResponse, a1 as MultipleChoiceData, a2 as MultipleChoiceLearnerResponse, a6 as ReadAloudData, a9 as ReadAloudLearnerResponse, ak as WrittenResponseData, al as WrittenResponseLearnerResponse } from './activity-f0bgZSTP.js';
2
+ export { A as ActivityAiPermissions, c as ActivityFeedback, e as ActivityResult, h as AiBlankFact, i as AiDictationFacts, l as AiFillInTheBlanksFacts, m as AiGapFact, n as AiGapSelectFacts, r as AiMultipleChoiceFacts, s as AiOptionFact, t as AiProvenance, x as AiVerdict, y as AiWordFact, B as BlankConfig, D as DeferredReason, F as DictationEquivalence, H as DictationSlowMedia, I as DictationTolerance, L as GapSelectBank, M as GapSelectChoice, O as GapSelectGap, R as Grader, S as GraderKind, T as GraderUsage, U as GradingState, V as InlineCorrection, W as InteractionEvent, X as InteractionKind, _ as LearnerResponseMap, $ as MediaPlaybackPolicy, a0 as MediaTrack, a3 as MultipleChoiceOption, a4 as MultipleChoiceOptionMedia, a7 as ReadAloudDimension, a8 as ReadAloudDimensionWeight, aa as ReadAloudSlowMedia, ab as RecordingBounds, ac as RecordingRef, ae as ScoringOutcome, ag as TextMatchPolicy, ah as TextMatchResult, aj as ValidationResult, am as WrittenResponseRubric, an as WrittenResponseRubricCriterion, ao as XAPIActor, ap as XAPIConfig, aq as XAPIContext, ar as XAPIContextActivities, as as XAPIError, at as XAPIObject, au as XAPIResult, av as XAPIScore, aw as XAPIStatement, ax as XAPIVerbObject, ay as levenshteinDistance, az as matchText } from './activity-f0bgZSTP.js';
3
3
  import { ScoredItem, RoundingPolicy, CompositionPolicy, AssessmentScore } from './scoring.js';
4
4
  export { AssessmentSectionInput, Band, DEFAULT_PASS_THRESHOLD, DICTATION_MAX_ACCEPTED_TRANSCRIPTS, DICTATION_MAX_EQUIVALENCES, DICTATION_MAX_EQUIVALENCE_LENGTH, DICTATION_MAX_TEXT_LENGTH, DICTATION_MAX_TRANSCRIPT_LENGTH, DictationAlignment, DictationCharOp, DictationReference, DictationWordAlignment, GradeReadAloudOptions, PassFailureReason, READ_ALOUD_MAX_DIMENSION_WEIGHT, READ_ALOUD_MAX_REFERENCE_LENGTH, READ_ALOUD_MAX_SECONDS, READ_ALOUD_MAX_TAKES, ReadAloudWordAlignment, ReadAloudWordState, RoundingMode, SPEECH_ASSESSMENT_MAX_WORDS, ScoringOptions, SectionScore, SpeechAssessment, SpeechMeasurement, SpeechPhoneme, SpeechPhonemeCandidate, SpeechPlausibilityPolicy, SpeechSyllable, SpeechUnscorable, SpeechUnscorableCode, SpeechWord, SpeechWordError, WavInspection, WavInspectionPolicy, alignDictation, alignReadAloud, classifyBand, composeAssessmentScore, computePassThreshold, dictationReferenceWords, diffDictationChars, evaluate, gradeReadAloud, gte, inspectWav, outcomeFromUnscorable, roundGrade, score, validateSpeechAssessment } from './scoring.js';
5
- import { a5 as SequenceSlot, a4 as SequenceEntry, I as ItemGroup, i as MediaProgress } from './index-G_KusUcK.js';
6
- export { A as AiPermissionsSchema, B as BlankConfigSchema, D as DictationDataSchema, a as DictationEquivalenceSchema, b as DictationSlowMediaSchema, c as DictationToleranceSchema, F as FeedbackSchema, d as FillInTheBlanksDataSchema, G as GapSelectBankSchema, e as GapSelectChoiceSchema, f as GapSelectDataSchema, g as GapSelectGapSchema, h as ItemGroupSchema, M as MediaPlaybackSchema, j as MediaSchema, k as MediaTimeline, l as MediaTimelineSchema, m as MediaTrackSchema, n as MediaUrlSchema, o as MultipleChoiceDataSchema, p as MultipleChoiceOptionMediaSchema, q as MultipleChoiceOptionSchema, N as NativeControlHintSchema, R as ReadAloudDataSchema, r as ReadAloudSlowMediaSchema, s as RedactedActivity, t as RedactedBlankConfig, u as RedactedBlankConfigSchema, v as RedactedDictationData, w as RedactedDictationDataSchema, x as RedactedDictationSlowMedia, y as RedactedDictationSlowMediaSchema, z as RedactedFillInTheBlanksData, C as RedactedFillInTheBlanksDataSchema, E as RedactedGapSelectBank, H as RedactedGapSelectBankSchema, J as RedactedGapSelectChoice, K as RedactedGapSelectChoiceSchema, L as RedactedGapSelectData, O as RedactedGapSelectDataSchema, P as RedactedGapSelectGap, Q as RedactedGapSelectGapSchema, S as RedactedItemGroupSchema, T as RedactedMediaSchema, U as RedactedMultipleChoiceData, V as RedactedMultipleChoiceDataSchema, W as RedactedMultipleChoiceOption, X as RedactedMultipleChoiceOptionMedia, Y as RedactedMultipleChoiceOptionMediaSchema, Z as RedactedMultipleChoiceOptionSchema, _ as RedactedReadAloudData, $ as RedactedReadAloudDataSchema, a0 as RedactedStimulus, a1 as RedactedStimulusSchema, a2 as RedactedWrittenResponseData, a3 as RedactedWrittenResponseDataSchema, a6 as SequenceSlotGroup, a7 as Stimulus, a8 as StimulusKind, a9 as StimulusSchema, aa as TextMatchPolicySchema, ab as TimelineChapter, ac as TimelineChapterSchema, ad as TimelineCue, ae as TimelineCueSchema, af as WrittenResponseDataSchema, ag as WrittenResponseRubricCriterionSchema, ah as WrittenResponseRubricSchema, ai as dictationJsonSchema, aj as fillInTheBlanksJsonSchema, ak as gapSelectJsonSchema, al as itemGroupJsonSchema, am as jsonSchemaFor, an as multipleChoiceJsonSchema, ao as readAloudJsonSchema, ap as stimulusJsonSchema, aq as validateActivity, ar as validateItemGroup, as as writtenResponseJsonSchema } from './index-G_KusUcK.js';
5
+ import { a5 as SequenceSlot, a4 as SequenceEntry, I as ItemGroup, i as MediaProgress } from './index-DMCM3e3P.js';
6
+ export { A as AiPermissionsSchema, B as BlankConfigSchema, D as DictationDataSchema, a as DictationEquivalenceSchema, b as DictationSlowMediaSchema, c as DictationToleranceSchema, F as FeedbackSchema, d as FillInTheBlanksDataSchema, G as GapSelectBankSchema, e as GapSelectChoiceSchema, f as GapSelectDataSchema, g as GapSelectGapSchema, h as ItemGroupSchema, M as MediaPlaybackSchema, j as MediaSchema, k as MediaTimeline, l as MediaTimelineSchema, m as MediaTrackSchema, n as MediaUrlSchema, o as MultipleChoiceDataSchema, p as MultipleChoiceOptionMediaSchema, q as MultipleChoiceOptionSchema, N as NativeControlHintSchema, R as ReadAloudDataSchema, r as ReadAloudSlowMediaSchema, s as RedactedActivity, t as RedactedBlankConfig, u as RedactedBlankConfigSchema, v as RedactedDictationData, w as RedactedDictationDataSchema, x as RedactedDictationSlowMedia, y as RedactedDictationSlowMediaSchema, z as RedactedFillInTheBlanksData, C as RedactedFillInTheBlanksDataSchema, E as RedactedGapSelectBank, H as RedactedGapSelectBankSchema, J as RedactedGapSelectChoice, K as RedactedGapSelectChoiceSchema, L as RedactedGapSelectData, O as RedactedGapSelectDataSchema, P as RedactedGapSelectGap, Q as RedactedGapSelectGapSchema, S as RedactedItemGroupSchema, T as RedactedMediaSchema, U as RedactedMultipleChoiceData, V as RedactedMultipleChoiceDataSchema, W as RedactedMultipleChoiceOption, X as RedactedMultipleChoiceOptionMedia, Y as RedactedMultipleChoiceOptionMediaSchema, Z as RedactedMultipleChoiceOptionSchema, _ as RedactedReadAloudData, $ as RedactedReadAloudDataSchema, a0 as RedactedStimulus, a1 as RedactedStimulusSchema, a2 as RedactedWrittenResponseData, a3 as RedactedWrittenResponseDataSchema, a6 as SequenceSlotGroup, a7 as Stimulus, a8 as StimulusKind, a9 as StimulusSchema, aa as TextMatchPolicySchema, ab as TimelineChapter, ac as TimelineChapterSchema, ad as TimelineCue, ae as TimelineCueSchema, af as WrittenResponseDataSchema, ag as WrittenResponseRubricCriterionSchema, ah as WrittenResponseRubricSchema, ai as dictationJsonSchema, aj as fillInTheBlanksJsonSchema, ak as gapSelectJsonSchema, al as itemGroupJsonSchema, am as jsonSchemaFor, an as multipleChoiceJsonSchema, ao as readAloudJsonSchema, ap as stimulusJsonSchema, aq as validateActivity, ar as validateItemGroup, as as writtenResponseJsonSchema } from './index-DMCM3e3P.js';
7
7
  import { z } from 'zod/v4';
8
8
  export { AnsweredStatementParams, CompletedStatementParams, SubmittedStatementParams, XAPIObjectParams, XAPIStatementParams, XAPIVerb, XAPIVerbKey, XAPI_VERB_DISPLAY, validateXAPIStatement, xAPIBuilder, xapiDefinitionFor } from './xapi.js';
9
9
 
@@ -45,11 +45,15 @@ declare function aiGradeOf(result: {
45
45
  declare function buildAiFacts(data: AiActivityInput, response: LearnerResponse | null, details: readonly ScoringDetail[] | null): AiItemFacts | null;
46
46
  /**
47
47
  * The request for an explanation of `response`, or `null` when there is none
48
- * to give: an unsupported type, an author who switched explanations off, or
49
- * nothing graded to explain.
48
+ * to give: an unsupported type, an author who switched explanations off,
49
+ * nothing graded to explain, or a grade of record that cannot be a grade.
50
50
  *
51
51
  * A scored `outcome` — the grade of record a review shows — wins over grading
52
- * locally, so the explanation speaks to the grade on the screen.
52
+ * locally, so the explanation speaks to the grade on the screen. One whose
53
+ * numbers cannot be a grade (the rule `outcomeFromGrade` applies: `maxScore`
54
+ * positive and finite, `score` finite from 0 to it) gets no explanation at all:
55
+ * explaining 85 "out of 1" would tell the model a wrong answer was correct, and
56
+ * grading locally instead would speak to a grade the screen does not show.
53
57
  */
54
58
  declare function aiExplanationRequest(input: {
55
59
  data: AiActivityInput;
@@ -743,10 +747,12 @@ interface GradeFromRubricOptions {
743
747
  * total a pure function of the judgements, so a grade can be recomputed and
744
748
  * audited years later.
745
749
  *
746
- * Weights are normalised by their sum, so they need not add to 1. Criteria
747
- * marked `notApplicable`, and those carrying no numeric `score` (a purely
748
- * banded judgement), are excluded from both numerator and denominator. When
749
- * nothing scoreable remains the result is `unscorable`, never a zero.
750
+ * Weights are normalised by their sum, so they need not add to 1; each must be
751
+ * zero or more, and their sum a positive, finite number, or the result is
752
+ * `unscorable`. Criteria marked `notApplicable`, and those carrying no numeric
753
+ * `score` (a purely banded judgement), are excluded from both numerator and
754
+ * denominator, weight included. When nothing scoreable remains the result is
755
+ * `unscorable`, never a zero.
750
756
  *
751
757
  * Scores need not be in [0,1]: set `maxScore` on a criterion to declare what
752
758
  * its score is out of, and each is normalised before weighting. A grader
@@ -771,6 +777,19 @@ declare function gradeFromRubric(criteria: readonly CriterionScore[], activityDa
771
777
  * a grade that arrived asynchronously renders through the same path as a
772
778
  * synchronously scored item. The score/passed/feedback fields are mirrored
773
779
  * onto the outcome for uniform reads; `grade` carries the full record.
780
+ *
781
+ * A record whose numbers cannot be a grade is refused, as `evaluate` refuses a
782
+ * non-finite score and {@link gradeFromRubric} one outside [0,1]. `maxScore`
783
+ * must be a positive, finite number and `score` a finite number from 0 to it (a
784
+ * hair above, one part in a billion, is float noise and passes as it is).
785
+ * Anything else — NaN, 85 "out of 1", a score out of 0 — comes back as
786
+ * `{ status: 'deferred', reason: 'grade_rejected', maxScore: 1, rejectedGrade }`
787
+ * with the record kept verbatim. Not `graded`, or its `passed` would be read as
788
+ * a verdict; not `unscorable`, which `composeAssessmentScore` drops from the
789
+ * denominator while letting the attempt go final — so a failing grade on the
790
+ * wrong scale would vanish and the rest would be recorded as a pass. Deferred
791
+ * is what it is: the slot is still owed a real grade, so a composed attempt
792
+ * stays `provisional` and names the slot in `rejectedSlotIds`.
774
793
  */
775
794
  declare function outcomeFromGrade(grade: GradeRecord): ItemOutcome;
776
795
  /**
package/dist/index.js CHANGED
@@ -1,22 +1,3 @@
1
- import {
2
- DEFAULT_PASS_THRESHOLD,
3
- alignReadAloud,
4
- classifyBand,
5
- composeAssessmentScore,
6
- computePassThreshold,
7
- diffDictationChars,
8
- evaluate,
9
- gradeFromRubric,
10
- gradeReadAloud,
11
- gte,
12
- hasGrade,
13
- inspectWav,
14
- outcomeFromGrade,
15
- outcomeFromUnscorable,
16
- roundGrade,
17
- score,
18
- validateSpeechAssessment
19
- } from "./chunk-WXS5EB65.js";
20
1
  import {
21
2
  XAPIVerb,
22
3
  XAPI_VERB_DISPLAY,
@@ -50,6 +31,40 @@ import {
50
31
  validateItemGroup,
51
32
  writtenResponseJsonSchema
52
33
  } from "./chunk-Z7QQZ53L.js";
34
+ import {
35
+ AI_EXPLANATION_TYPES,
36
+ AI_HINT_TYPES,
37
+ AI_TEXT_MAX_LENGTH,
38
+ aiAllowedByContent,
39
+ aiExplanationRequest,
40
+ aiGradeOf,
41
+ aiHintRequest,
42
+ aiSupports,
43
+ buildAiFacts,
44
+ checkAiExplanation,
45
+ checkAiHint,
46
+ hintRevealsAnswer,
47
+ readAiTextResult
48
+ } from "./chunk-BIJCO2HM.js";
49
+ import {
50
+ DEFAULT_PASS_THRESHOLD,
51
+ alignReadAloud,
52
+ classifyBand,
53
+ composeAssessmentScore,
54
+ computePassThreshold,
55
+ diffDictationChars,
56
+ evaluate,
57
+ gradeFromRubric,
58
+ gradeReadAloud,
59
+ gte,
60
+ hasGrade,
61
+ inspectWav,
62
+ outcomeFromGrade,
63
+ outcomeFromUnscorable,
64
+ roundGrade,
65
+ score,
66
+ validateSpeechAssessment
67
+ } from "./chunk-MMEQKFFQ.js";
53
68
  import {
54
69
  ActivitySchemaError,
55
70
  AiPermissionsSchema,
@@ -81,7 +96,6 @@ import {
81
96
  MultipleChoiceOptionMediaSchema,
82
97
  MultipleChoiceOptionSchema,
83
98
  NativeControlHintSchema,
84
- PLACEHOLDER_RE,
85
99
  READ_ALOUD_MAX_DIMENSION_WEIGHT,
86
100
  READ_ALOUD_MAX_REFERENCE_LENGTH,
87
101
  READ_ALOUD_MAX_SECONDS,
@@ -138,405 +152,6 @@ import {
138
152
  writtenResponseType
139
153
  } from "./chunk-D3GP5MET.js";
140
154
 
141
- // src/ai.ts
142
- var AI_EXPLANATION_TYPES = [
143
- "multiple-choice",
144
- "fill-in-the-blanks",
145
- "gap-select",
146
- "dictation"
147
- ];
148
- var AI_HINT_TYPES = [
149
- "multiple-choice",
150
- "fill-in-the-blanks",
151
- "gap-select"
152
- ];
153
- var AI_TEXT_MAX_LENGTH = 2e3;
154
- function aiSupports(activityType, feature) {
155
- const types = feature === "hint" ? AI_HINT_TYPES : AI_EXPLANATION_TYPES;
156
- return types.includes(activityType);
157
- }
158
- function aiAllowedByContent(data, feature) {
159
- const permissions = data.ai;
160
- if (typeof permissions !== "object" || permissions === null) {
161
- return true;
162
- }
163
- const key = feature === "hint" ? "hints" : "explanations";
164
- return permissions[key] !== false;
165
- }
166
- function aiGradeOf(result) {
167
- const ratio = result.maxScore > 0 ? result.score / result.maxScore : 0;
168
- let category = "partly-correct";
169
- if (ratio >= 1 - 1e-9) {
170
- category = "correct";
171
- } else if (ratio <= 1e-9) {
172
- category = "incorrect";
173
- }
174
- return { score: result.score, maxScore: result.maxScore, passed: result.passed, category };
175
- }
176
- function placeholderOrder(passage) {
177
- return [...passage.matchAll(PLACEHOLDER_RE)].map((match) => match[1]);
178
- }
179
- function numberedPassage(passage) {
180
- let position = 0;
181
- return passage.replace(PLACEHOLDER_RE, () => {
182
- position += 1;
183
- return `[${position}]`;
184
- });
185
- }
186
- function isAnswerOption(detail, chosen) {
187
- switch (detail.outcome) {
188
- case "correct":
189
- case "incorrect-omission":
190
- return true;
191
- case "incorrect":
192
- case "correct-omission":
193
- return false;
194
- default:
195
- return chosen ? detail.correct : !detail.correct;
196
- }
197
- }
198
- var firstString = (value) => Array.isArray(value) ? value[0] : value;
199
- var listOf = (value) => value === void 0 ? null : Array.isArray(value) ? [...value] : [value];
200
- function emptyResponse(type) {
201
- switch (type) {
202
- case "multiple-choice":
203
- return { type: "multiple-choice", selectedOptionIds: [] };
204
- case "fill-in-the-blanks":
205
- return { type: "fill-in-the-blanks", answers: {} };
206
- case "gap-select":
207
- return { type: "gap-select", selections: {} };
208
- case "dictation":
209
- return { type: "dictation", text: "" };
210
- default:
211
- return null;
212
- }
213
- }
214
- var base = (data) => ({
215
- activityId: data.id,
216
- title: data.title,
217
- ...typeof data.locale === "string" ? { locale: data.locale } : {}
218
- });
219
- function buildAiFacts(data, response, details) {
220
- const byId = new Map((details ?? []).map((detail) => [detail.itemId, detail]));
221
- const full = data.redacted !== true;
222
- switch (data.type) {
223
- case "multiple-choice": {
224
- const item = data;
225
- const chosen = new Set(
226
- response?.type === "multiple-choice" ? response.selectedOptionIds : []
227
- );
228
- return {
229
- ...base(data),
230
- activityType: "multiple-choice",
231
- question: item.question,
232
- mode: item.mode,
233
- options: item.options.map((option) => {
234
- const detail = byId.get(option.id);
235
- const wasChosen = chosen.has(option.id);
236
- let correct = null;
237
- if (full && typeof option.isCorrect === "boolean") {
238
- correct = option.isCorrect;
239
- } else if (detail !== void 0) {
240
- correct = isAnswerOption(detail, wasChosen);
241
- }
242
- return {
243
- id: option.id,
244
- text: option.text,
245
- chosen: wasChosen,
246
- correct,
247
- ...typeof option.feedback === "string" ? { feedback: option.feedback } : {}
248
- };
249
- })
250
- };
251
- }
252
- case "fill-in-the-blanks": {
253
- const item = data;
254
- const answers = response?.type === "fill-in-the-blanks" ? response.answers : {};
255
- const blanks = new Map(item.blanks.map((blank) => [blank.id, blank]));
256
- return {
257
- ...base(data),
258
- activityType: "fill-in-the-blanks",
259
- passage: numberedPassage(item.passage),
260
- blanks: placeholderOrder(item.passage).map((id, index) => {
261
- const blank = blanks.get(id);
262
- const detail = byId.get(id);
263
- const accepted = full && Array.isArray(blank?.acceptedAnswers) ? [...blank.acceptedAnswers] : listOf(detail?.correctResponse);
264
- return {
265
- id,
266
- position: index + 1,
267
- typed: typeof answers[id] === "string" ? answers[id] : "",
268
- accepted,
269
- correct: detail === void 0 ? null : detail.correct,
270
- ...typeof blank?.hint === "string" ? { hint: blank.hint } : {},
271
- ...typeof blank?.feedback === "string" ? { feedback: blank.feedback } : {}
272
- };
273
- })
274
- };
275
- }
276
- case "gap-select": {
277
- const item = data;
278
- const selections = response?.type === "gap-select" ? response.selections : {};
279
- const gaps = new Map(item.gaps.map((gap) => [gap.id, gap]));
280
- const banks = new Map((item.banks ?? []).map((bank) => [bank.id, bank]));
281
- return {
282
- ...base(data),
283
- activityType: "gap-select",
284
- passage: numberedPassage(item.passage),
285
- gaps: placeholderOrder(item.passage).map((id, index) => {
286
- const gap = gaps.get(id);
287
- const choices = gap?.choices ?? (gap?.bankId === void 0 ? [] : banks.get(gap.bankId)?.choices ?? []);
288
- const textOf = (choiceId) => choiceId === void 0 ? null : choices.find((choice) => choice.id === choiceId)?.text ?? null;
289
- const detail = byId.get(id);
290
- const answerId = full && typeof gap?.correctChoiceId === "string" ? gap.correctChoiceId : firstString(detail?.correctResponse);
291
- return {
292
- id,
293
- position: index + 1,
294
- choices: choices.map((choice) => choice.text),
295
- chosen: textOf(selections[id]),
296
- answer: textOf(answerId),
297
- correct: detail === void 0 ? null : detail.correct,
298
- ...typeof gap?.feedback === "string" ? { feedback: gap.feedback } : {}
299
- };
300
- })
301
- };
302
- }
303
- case "dictation": {
304
- const item = data;
305
- const typed = response?.type === "dictation" ? response.text : "";
306
- const hasKey = full && typeof item.transcript === "string";
307
- let words = null;
308
- if (hasKey) {
309
- words = alignDictation(item, typed).words.map((word) => ({
310
- expected: word.reference,
311
- typed: word.attempt,
312
- status: word.status
313
- }));
314
- } else if (details !== null && details.length > 0) {
315
- words = details.map((detail) => {
316
- const expected = firstString(detail.correctResponse) ?? "";
317
- const attempt = firstString(detail.learnerResponse) ?? "";
318
- let status = "incorrect";
319
- if (detail.correct) {
320
- status = "correct";
321
- } else if (attempt === "") {
322
- status = "missing";
323
- } else if (expected === "") {
324
- status = "extra";
325
- }
326
- return { expected, typed: attempt, status };
327
- });
328
- }
329
- return {
330
- ...base(data),
331
- activityType: "dictation",
332
- transcript: hasKey ? item.transcript : null,
333
- typed,
334
- words
335
- };
336
- }
337
- default:
338
- return null;
339
- }
340
- }
341
- function gradeLocally(data, response) {
342
- if (data.redacted === true) {
343
- return null;
344
- }
345
- try {
346
- const result = score(data.type, data, response);
347
- return { details: result.details, grade: aiGradeOf(result) };
348
- } catch {
349
- return null;
350
- }
351
- }
352
- function aiExplanationRequest(input) {
353
- const { data, response, outcome, learnerLocale } = input;
354
- if (!aiSupports(data.type, "explanation") || !aiAllowedByContent(data, "explanation")) {
355
- return null;
356
- }
357
- let details = null;
358
- let grade = null;
359
- if (outcome?.status === "scored") {
360
- details = outcome.details;
361
- grade = aiGradeOf(outcome);
362
- } else if (response !== null) {
363
- const local = gradeLocally(data, response);
364
- if (local !== null) {
365
- details = local.details;
366
- grade = local.grade;
367
- }
368
- }
369
- if (grade === null) {
370
- return null;
371
- }
372
- const facts = buildAiFacts(data, response, details);
373
- if (facts === null) {
374
- return null;
375
- }
376
- return {
377
- feature: "explanation",
378
- facts,
379
- grade,
380
- ...learnerLocale !== void 0 ? { learnerLocale } : {}
381
- };
382
- }
383
- function aiHintRequest(input) {
384
- const { data, previousHints, learnerLocale } = input;
385
- if (!aiSupports(data.type, "hint") || !aiAllowedByContent(data, "hint") || data.redacted === true) {
386
- return null;
387
- }
388
- const response = input.response ?? emptyResponse(data.type);
389
- const local = response === null ? null : gradeLocally(data, response);
390
- const facts = buildAiFacts(data, response, local?.details ?? null);
391
- if (facts === null) {
392
- return null;
393
- }
394
- return {
395
- feature: "hint",
396
- facts,
397
- hintNumber: previousHints.length + 1,
398
- previousHints: [...previousHints],
399
- ...learnerLocale !== void 0 ? { learnerLocale } : {}
400
- };
401
- }
402
- var VERDICTS = ["correct", "partly-correct", "incorrect"];
403
- function provenanceOf(raw) {
404
- if (typeof raw !== "object" || raw === null) {
405
- return void 0;
406
- }
407
- const kept = {};
408
- for (const key of ["model", "promptHash", "generatedAt"]) {
409
- const value = raw[key];
410
- if (typeof value === "string" && value.length > 0 && value.length <= 200) {
411
- kept[key] = value;
412
- }
413
- }
414
- return Object.keys(kept).length > 0 ? kept : void 0;
415
- }
416
- function cleanText(text) {
417
- return text.replace(/\r\n?/g, "\n").replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F\u2028\u2029]/g, "").replace(/\n{3,}/g, "\n\n").trim();
418
- }
419
- function readAiTextResult(raw) {
420
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
421
- return { ok: false, refusal: "malformed" };
422
- }
423
- const record = raw;
424
- if (typeof record.text !== "string") {
425
- return { ok: false, refusal: "malformed" };
426
- }
427
- if (record.verdict !== void 0 && !VERDICTS.includes(record.verdict)) {
428
- return { ok: false, refusal: "malformed" };
429
- }
430
- const text = cleanText(record.text);
431
- if (text === "") {
432
- return { ok: false, refusal: "empty" };
433
- }
434
- if ([...text].length > AI_TEXT_MAX_LENGTH) {
435
- return { ok: false, refusal: "too-long" };
436
- }
437
- const provenance = provenanceOf(record.provenance);
438
- return {
439
- ok: true,
440
- result: {
441
- text,
442
- ...record.verdict !== void 0 ? { verdict: record.verdict } : {},
443
- ...provenance !== void 0 ? { provenance } : {}
444
- }
445
- };
446
- }
447
- function checkAiExplanation(raw, request) {
448
- const read = readAiTextResult(raw);
449
- if (!read.ok) {
450
- return read;
451
- }
452
- if (read.result.verdict !== void 0 && read.result.verdict !== request.grade.category) {
453
- return { ok: false, refusal: "contradicts-grade" };
454
- }
455
- return read;
456
- }
457
- function checkAiHint(raw, request) {
458
- const read = readAiTextResult(raw);
459
- if (!read.ok) {
460
- return read;
461
- }
462
- if (hintRevealsAnswer(request.facts, read.result.text)) {
463
- return { ok: false, refusal: "reveals-answer" };
464
- }
465
- return read;
466
- }
467
- function fold(text) {
468
- const folded = text.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
469
- return folded === "" ? [] : folded.split(" ");
470
- }
471
- function containsWords(haystack, needle) {
472
- if (needle.length === 0 || needle.length > haystack.length) {
473
- return false;
474
- }
475
- for (let start = 0; start + needle.length <= haystack.length; start += 1) {
476
- let match = true;
477
- for (let offset = 0; offset < needle.length; offset += 1) {
478
- if (haystack[start + offset] !== needle[offset]) {
479
- match = false;
480
- break;
481
- }
482
- }
483
- if (match) {
484
- return true;
485
- }
486
- }
487
- return false;
488
- }
489
- var isShort = (words) => words.length === 1 && [...words[0]].length < 4;
490
- function neighbours(passage, position) {
491
- const marker = `[${position}]`;
492
- const at = passage.indexOf(marker);
493
- if (at === -1) {
494
- return {};
495
- }
496
- const strip = (text) => text.replace(/\[\d+\]/g, " ");
497
- const left = fold(strip(passage.slice(0, at)));
498
- const right = fold(strip(passage.slice(at + marker.length)));
499
- const before = left[left.length - 1];
500
- const after = right[0];
501
- return {
502
- ...before !== void 0 ? { before } : {},
503
- ...after !== void 0 ? { after } : {}
504
- };
505
- }
506
- function hintRevealsAnswer(facts, hint) {
507
- const words = fold(hint);
508
- const reveals = (answer, passage, position) => {
509
- const needle = fold(answer);
510
- if (needle.length === 0) {
511
- return false;
512
- }
513
- if (!isShort(needle) || passage === void 0 || position === void 0) {
514
- return containsWords(words, needle);
515
- }
516
- const { before, after } = neighbours(passage, position);
517
- if (before === void 0 && after === void 0) {
518
- return containsWords(words, needle);
519
- }
520
- return before !== void 0 && containsWords(words, [before, ...needle]) || after !== void 0 && containsWords(words, [...needle, after]);
521
- };
522
- switch (facts.activityType) {
523
- case "multiple-choice":
524
- return facts.options.some((option) => option.correct === true && reveals(option.text));
525
- case "fill-in-the-blanks":
526
- return facts.blanks.some(
527
- (blank) => (blank.accepted ?? []).some((answer) => reveals(answer, facts.passage, blank.position))
528
- );
529
- case "gap-select":
530
- return facts.gaps.some(
531
- (gap) => gap.answer !== null && reveals(gap.answer, facts.passage, gap.position)
532
- );
533
- case "dictation":
534
- return false;
535
- default:
536
- return false;
537
- }
538
- }
539
-
540
155
  // src/content-hash.ts
541
156
  function canonicalJson(value, seen = /* @__PURE__ */ new Set()) {
542
157
  if (value === null) {
@@ -1620,11 +1235,11 @@ function normalise(found) {
1620
1235
  }
1621
1236
 
1622
1237
  // src/redact.ts
1623
- function mergePolicy(base2, overrides) {
1238
+ function mergePolicy(base, overrides) {
1624
1239
  if (overrides === void 0) {
1625
- return base2;
1240
+ return base;
1626
1241
  }
1627
- const merged = { ...base2 };
1242
+ const merged = { ...base };
1628
1243
  for (const [key, override] of Object.entries(overrides)) {
1629
1244
  const current = merged[key];
1630
1245
  merged[key] = typeof override === "object" && typeof current === "object" ? { ...current, ...override } : override;