@conform-ed/qti-react 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.d.ts +25 -0
- package/dist/headless.js +4804 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +160 -96
- package/dist/item-capability.d.ts +43 -0
- package/dist/item-score.d.ts +17 -0
- package/package.json +10 -5
- package/src/headless.ts +58 -0
- package/src/index.ts +4 -0
- package/src/item-capability.ts +211 -0
- package/src/item-score.ts +40 -0
- package/src/runtime.ts +17 -123
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export declare const qtiReactPackageName = "@conform-ed/qti-react";
|
|
2
2
|
export { v0ContentModel, v0InteractionKinds, isAllowedFlowElement, isInteractionKind, sanitizeAttributes, type ContentModel, type V0InteractionKind, } from "./content-model";
|
|
3
3
|
export { foldString, mapResponse, matchCorrect, mapResponsePoint, scoreResponse } from "./response-processing";
|
|
4
|
+
export { effectiveItemScore, type EffectiveItemScore } from "./item-score";
|
|
4
5
|
export { assessmentItemViewFromNormalized, assessmentTestViewFromNormalized, stimulusContentFromNormalized, } from "./normalized-item";
|
|
6
|
+
export { referenceInteractionKinds, reportItemCapability, type ItemCapabilityOptions } from "./item-capability";
|
|
5
7
|
export { formatPoint, parseCoords, parsePoint, pointInShape, type Point, type QtiShape } from "./graphic";
|
|
6
8
|
export { applyCorrectResponseOverrides, collectRpIssues, collectTemplateIssues, executeResponseProcessing, executeTemplateProcessing, mulberry32, resolveTemplate, } from "./rp";
|
|
7
9
|
export type { CustomOperatorImplementation, InterpolationTableEntryView, InterpolationTableView, MatchTableEntryView, MatchTableView, MaybeRpValue, OutcomeDeclarationView, OutcomeValue, ResponseNormalization, ResponseProcessingContext, ResponseProcessingResult, ResponseProcessingView, RpConditionBranch, RpExpressionView, RpRecordField, RpRuleView, RpScalar, RpValue, TemplateConditionBranch, TemplateDeclarationView, TemplateProcessingContext, TemplateProcessingResult, TemplateProcessingView, TemplateRuleView, } from "./rp";
|
package/dist/index.js
CHANGED
|
@@ -387,6 +387,23 @@ function scoreResponse(declaration, response, normalize) {
|
|
|
387
387
|
correct
|
|
388
388
|
};
|
|
389
389
|
}
|
|
390
|
+
// src/item-score.ts
|
|
391
|
+
function numericOutcome(value) {
|
|
392
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
393
|
+
}
|
|
394
|
+
function effectiveItemScore(scores, outcomes) {
|
|
395
|
+
const scoreOutcome = numericOutcome(outcomes["SCORE"]);
|
|
396
|
+
const maxOutcome = numericOutcome(outcomes["MAXSCORE"]);
|
|
397
|
+
const summedMax = scores.reduce((total, score) => total + score.maxScore, 0);
|
|
398
|
+
if (scoreOutcome !== null) {
|
|
399
|
+
return { raw: scoreOutcome, max: maxOutcome ?? summedMax, fromOutcomes: true };
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
raw: scores.reduce((total, score) => total + score.score, 0),
|
|
403
|
+
max: maxOutcome ?? summedMax,
|
|
404
|
+
fromOutcomes: false
|
|
405
|
+
};
|
|
406
|
+
}
|
|
390
407
|
// src/normalized-item.ts
|
|
391
408
|
function isRecord(value) {
|
|
392
409
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -2930,6 +2947,127 @@ function collectTemplateIssues(view, options) {
|
|
|
2930
2947
|
walkRules(view.rules);
|
|
2931
2948
|
return issues;
|
|
2932
2949
|
}
|
|
2950
|
+
// src/item-capability.ts
|
|
2951
|
+
var feedbackKinds = new Set(["feedbackInline", "feedbackBlock"]);
|
|
2952
|
+
var templateContentKinds = new Set(["templateInline", "templateBlock"]);
|
|
2953
|
+
var intrinsicLeafKinds = new Set(["text", "printedVariable"]);
|
|
2954
|
+
function isFeedbackNode(node) {
|
|
2955
|
+
return feedbackKinds.has(node.kind);
|
|
2956
|
+
}
|
|
2957
|
+
function isTemplateContentNode(node) {
|
|
2958
|
+
return templateContentKinds.has(node.kind);
|
|
2959
|
+
}
|
|
2960
|
+
function isInteractionNode(node) {
|
|
2961
|
+
return node.kind !== "xml" && typeof node.responseIdentifier === "string";
|
|
2962
|
+
}
|
|
2963
|
+
function reportItemCapability(item, options) {
|
|
2964
|
+
const model = options.model ?? v0ContentModel;
|
|
2965
|
+
const customOperatorClasses = options.customOperatorClasses ?? new Set;
|
|
2966
|
+
const issues = [];
|
|
2967
|
+
const seen = new Set;
|
|
2968
|
+
function report(issue) {
|
|
2969
|
+
const dedupeKey = `${issue.type}:${issue.name}:${issue.responseIdentifier ?? ""}`;
|
|
2970
|
+
if (!seen.has(dedupeKey)) {
|
|
2971
|
+
seen.add(dedupeKey);
|
|
2972
|
+
issues.push(issue);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
function walk(node) {
|
|
2976
|
+
if (isFeedbackNode(node) || isTemplateContentNode(node) || node.kind === "rubricBlock") {
|
|
2977
|
+
for (const child of node.content ?? []) {
|
|
2978
|
+
walk(child);
|
|
2979
|
+
}
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
if (isInteractionNode(node)) {
|
|
2983
|
+
if (!options.supportedInteractions.has(node.kind)) {
|
|
2984
|
+
report({ type: "unsupported-interaction", name: node.kind, responseIdentifier: node.responseIdentifier });
|
|
2985
|
+
return;
|
|
2986
|
+
}
|
|
2987
|
+
const schema = options.interactionSchemas?.get(node.kind);
|
|
2988
|
+
if (schema) {
|
|
2989
|
+
const parsed = schema.safeParse(node);
|
|
2990
|
+
if (!parsed.success) {
|
|
2991
|
+
const detail = parsed.error.issues[0]?.message;
|
|
2992
|
+
report({
|
|
2993
|
+
type: "invalid-interaction",
|
|
2994
|
+
name: node.kind,
|
|
2995
|
+
responseIdentifier: node.responseIdentifier,
|
|
2996
|
+
...detail !== undefined ? { detail } : {}
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
if (node.kind === "xml") {
|
|
3003
|
+
const xmlNode = node;
|
|
3004
|
+
if (xmlNode.name === model.mathRoot) {
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
if (!isAllowedFlowElement(model, xmlNode.name)) {
|
|
3008
|
+
report({ type: "unsupported-element", name: xmlNode.name });
|
|
3009
|
+
}
|
|
3010
|
+
for (const child of xmlNode.children ?? []) {
|
|
3011
|
+
walk(child);
|
|
3012
|
+
}
|
|
3013
|
+
return;
|
|
3014
|
+
}
|
|
3015
|
+
if (intrinsicLeafKinds.has(node.kind)) {
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
report({ type: "unsupported-element", name: node.kind });
|
|
3019
|
+
}
|
|
3020
|
+
for (const node of item.itemBody.content ?? []) {
|
|
3021
|
+
walk(node);
|
|
3022
|
+
}
|
|
3023
|
+
for (const ref of item.assessmentStimulusRefs ?? []) {
|
|
3024
|
+
const stimulus = options.resolveStimulus?.(ref) ?? null;
|
|
3025
|
+
if (stimulus === null) {
|
|
3026
|
+
report({ type: "unsupported-element", name: "assessmentStimulusRef", detail: ref.href });
|
|
3027
|
+
continue;
|
|
3028
|
+
}
|
|
3029
|
+
for (const node of stimulus.content) {
|
|
3030
|
+
walk(node);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
for (const feedback of item.modalFeedbacks ?? []) {
|
|
3034
|
+
for (const child of feedback.content ?? []) {
|
|
3035
|
+
walk(child);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
for (const issue of collectRpIssues(item.responseProcessing, {
|
|
3039
|
+
customOperatorClasses,
|
|
3040
|
+
outcomeDeclarations: item.outcomeDeclarations ?? []
|
|
3041
|
+
})) {
|
|
3042
|
+
report(issue);
|
|
3043
|
+
}
|
|
3044
|
+
for (const issue of collectTemplateIssues(item.templateProcessing, { customOperatorClasses })) {
|
|
3045
|
+
report(issue);
|
|
3046
|
+
}
|
|
3047
|
+
return { deliverable: issues.length === 0, issues };
|
|
3048
|
+
}
|
|
3049
|
+
var referenceInteractionKinds = [
|
|
3050
|
+
"associateInteraction",
|
|
3051
|
+
"choiceInteraction",
|
|
3052
|
+
"drawingInteraction",
|
|
3053
|
+
"endAttemptInteraction",
|
|
3054
|
+
"extendedTextInteraction",
|
|
3055
|
+
"gapMatchInteraction",
|
|
3056
|
+
"graphicAssociateInteraction",
|
|
3057
|
+
"graphicGapMatchInteraction",
|
|
3058
|
+
"graphicOrderInteraction",
|
|
3059
|
+
"hotspotInteraction",
|
|
3060
|
+
"hottextInteraction",
|
|
3061
|
+
"inlineChoiceInteraction",
|
|
3062
|
+
"matchInteraction",
|
|
3063
|
+
"mediaInteraction",
|
|
3064
|
+
"orderInteraction",
|
|
3065
|
+
"positionObjectStage",
|
|
3066
|
+
"selectPointInteraction",
|
|
3067
|
+
"sliderInteraction",
|
|
3068
|
+
"textEntryInteraction",
|
|
3069
|
+
"uploadInteraction"
|
|
3070
|
+
];
|
|
2933
3071
|
// src/response-validity.ts
|
|
2934
3072
|
var countConstraintKinds = ["minChoices", "maxChoices", "minAssociations", "maxAssociations", "minStrings"];
|
|
2935
3073
|
function collectInteractionConstraints(content) {
|
|
@@ -4669,22 +4807,21 @@ function responseIncludes(value, optionIdentifier) {
|
|
|
4669
4807
|
function isCorrectOption(declaration, optionIdentifier) {
|
|
4670
4808
|
return Boolean(declaration?.correctResponse?.values.some((entry) => entry.value === optionIdentifier));
|
|
4671
4809
|
}
|
|
4672
|
-
function
|
|
4810
|
+
function isInteractionNode2(node) {
|
|
4673
4811
|
return node.kind !== "xml" && typeof node.responseIdentifier === "string";
|
|
4674
4812
|
}
|
|
4675
|
-
var
|
|
4676
|
-
function
|
|
4677
|
-
return
|
|
4813
|
+
var feedbackKinds2 = new Set(["feedbackInline", "feedbackBlock"]);
|
|
4814
|
+
function isFeedbackNode2(node) {
|
|
4815
|
+
return feedbackKinds2.has(node.kind);
|
|
4678
4816
|
}
|
|
4679
|
-
var
|
|
4680
|
-
function
|
|
4681
|
-
return
|
|
4817
|
+
var templateContentKinds2 = new Set(["templateInline", "templateBlock"]);
|
|
4818
|
+
function isTemplateContentNode2(node) {
|
|
4819
|
+
return templateContentKinds2.has(node.kind);
|
|
4682
4820
|
}
|
|
4683
4821
|
function templateVisible(value, view) {
|
|
4684
4822
|
const matched = Array.isArray(value) ? value.includes(view.identifier) : value === view.identifier;
|
|
4685
4823
|
return matched !== (view.showHide === "hide");
|
|
4686
4824
|
}
|
|
4687
|
-
var intrinsicLeafKinds = new Set(["text", "printedVariable"]);
|
|
4688
4825
|
function createStaticStore(outcomes) {
|
|
4689
4826
|
const snapshot = {
|
|
4690
4827
|
responses: {},
|
|
@@ -4782,13 +4919,13 @@ function createQtiRuntime(config) {
|
|
|
4782
4919
|
if (override) {
|
|
4783
4920
|
return createElement(Fragment, { key }, override(node, key));
|
|
4784
4921
|
}
|
|
4785
|
-
if (
|
|
4922
|
+
if (isInteractionNode2(node)) {
|
|
4786
4923
|
if (descriptorsByKind.has(node.kind) && config.skin[node.kind]) {
|
|
4787
4924
|
return createElement(InteractionHost, { key, node });
|
|
4788
4925
|
}
|
|
4789
4926
|
return renderUnsupported(node, key);
|
|
4790
4927
|
}
|
|
4791
|
-
if (
|
|
4928
|
+
if (isFeedbackNode2(node)) {
|
|
4792
4929
|
return createElement(FeedbackHost, {
|
|
4793
4930
|
key,
|
|
4794
4931
|
feedback: node,
|
|
@@ -4796,7 +4933,7 @@ function createQtiRuntime(config) {
|
|
|
4796
4933
|
overrides
|
|
4797
4934
|
});
|
|
4798
4935
|
}
|
|
4799
|
-
if (
|
|
4936
|
+
if (isTemplateContentNode2(node)) {
|
|
4800
4937
|
return createElement(TemplateContentHost, {
|
|
4801
4938
|
key,
|
|
4802
4939
|
view: node,
|
|
@@ -5039,91 +5176,15 @@ function createQtiRuntime(config) {
|
|
|
5039
5176
|
return catalogIdref !== undefined ? catalogSupports.get(catalogIdref) ?? noSupports : noSupports;
|
|
5040
5177
|
}
|
|
5041
5178
|
function canDeliver(item) {
|
|
5042
|
-
const
|
|
5043
|
-
const
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
}
|
|
5050
|
-
}
|
|
5051
|
-
function walk(node) {
|
|
5052
|
-
if (isFeedbackNode(node) || isTemplateContentNode(node) || node.kind === "rubricBlock") {
|
|
5053
|
-
for (const child of node.content ?? []) {
|
|
5054
|
-
walk(child);
|
|
5055
|
-
}
|
|
5056
|
-
return;
|
|
5057
|
-
}
|
|
5058
|
-
if (isInteractionNode(node)) {
|
|
5059
|
-
const descriptor = descriptorsByKind.get(node.kind);
|
|
5060
|
-
if (!descriptor || !config.skin[node.kind]) {
|
|
5061
|
-
report({
|
|
5062
|
-
type: "unsupported-interaction",
|
|
5063
|
-
name: node.kind,
|
|
5064
|
-
responseIdentifier: node.responseIdentifier
|
|
5065
|
-
});
|
|
5066
|
-
return;
|
|
5067
|
-
}
|
|
5068
|
-
const parsed = descriptor.schema.safeParse(node);
|
|
5069
|
-
if (!parsed.success) {
|
|
5070
|
-
const detail = parsed.error.issues[0]?.message;
|
|
5071
|
-
report({
|
|
5072
|
-
type: "invalid-interaction",
|
|
5073
|
-
name: node.kind,
|
|
5074
|
-
responseIdentifier: node.responseIdentifier,
|
|
5075
|
-
...detail !== undefined ? { detail } : {}
|
|
5076
|
-
});
|
|
5077
|
-
}
|
|
5078
|
-
return;
|
|
5079
|
-
}
|
|
5080
|
-
if (node.kind === "xml") {
|
|
5081
|
-
const xmlNode = node;
|
|
5082
|
-
if (xmlNode.name === model.mathRoot) {
|
|
5083
|
-
return;
|
|
5084
|
-
}
|
|
5085
|
-
if (!isAllowedFlowElement(model, xmlNode.name)) {
|
|
5086
|
-
report({ type: "unsupported-element", name: xmlNode.name });
|
|
5087
|
-
}
|
|
5088
|
-
for (const child of xmlNode.children ?? []) {
|
|
5089
|
-
walk(child);
|
|
5090
|
-
}
|
|
5091
|
-
return;
|
|
5092
|
-
}
|
|
5093
|
-
if (intrinsicLeafKinds.has(node.kind)) {
|
|
5094
|
-
return;
|
|
5095
|
-
}
|
|
5096
|
-
report({ type: "unsupported-element", name: node.kind });
|
|
5097
|
-
}
|
|
5098
|
-
for (const node of item.itemBody.content ?? []) {
|
|
5099
|
-
walk(node);
|
|
5100
|
-
}
|
|
5101
|
-
for (const ref of item.assessmentStimulusRefs ?? []) {
|
|
5102
|
-
const stimulus = config.resolveStimulus?.(ref) ?? null;
|
|
5103
|
-
if (stimulus === null) {
|
|
5104
|
-
report({ type: "unsupported-element", name: "assessmentStimulusRef", detail: ref.href });
|
|
5105
|
-
continue;
|
|
5106
|
-
}
|
|
5107
|
-
for (const node of stimulus.content) {
|
|
5108
|
-
walk(node);
|
|
5109
|
-
}
|
|
5110
|
-
}
|
|
5111
|
-
for (const feedback of item.modalFeedbacks ?? []) {
|
|
5112
|
-
for (const child of feedback.content ?? []) {
|
|
5113
|
-
walk(child);
|
|
5114
|
-
}
|
|
5115
|
-
}
|
|
5116
|
-
const customOperatorClasses = new Set(Object.keys(config.customOperators ?? {}));
|
|
5117
|
-
for (const issue of collectRpIssues(item.responseProcessing, {
|
|
5118
|
-
customOperatorClasses,
|
|
5119
|
-
outcomeDeclarations: item.outcomeDeclarations ?? []
|
|
5120
|
-
})) {
|
|
5121
|
-
report(issue);
|
|
5122
|
-
}
|
|
5123
|
-
for (const issue of collectTemplateIssues(item.templateProcessing, { customOperatorClasses })) {
|
|
5124
|
-
report(issue);
|
|
5125
|
-
}
|
|
5126
|
-
return { deliverable: issues.length === 0, issues };
|
|
5179
|
+
const supportedInteractions = new Set([...descriptorsByKind.keys()].filter((kind) => Boolean(config.skin[kind])));
|
|
5180
|
+
const interactionSchemas = new Map([...descriptorsByKind].map(([kind, descriptor]) => [kind, descriptor.schema]));
|
|
5181
|
+
return reportItemCapability(item, {
|
|
5182
|
+
supportedInteractions,
|
|
5183
|
+
interactionSchemas,
|
|
5184
|
+
model,
|
|
5185
|
+
customOperatorClasses: new Set(Object.keys(config.customOperators ?? {})),
|
|
5186
|
+
...config.resolveStimulus !== undefined ? { resolveStimulus: config.resolveStimulus } : {}
|
|
5187
|
+
});
|
|
5127
5188
|
}
|
|
5128
5189
|
return { ItemRenderer, ContentRenderer, useAttempt, useCatalogSupports, canDeliver };
|
|
5129
5190
|
}
|
|
@@ -6758,7 +6819,9 @@ export {
|
|
|
6758
6819
|
resolveTemplate,
|
|
6759
6820
|
resolvePnpActivation,
|
|
6760
6821
|
resolveCatalogSupports,
|
|
6822
|
+
reportItemCapability,
|
|
6761
6823
|
referenceSkin,
|
|
6824
|
+
referenceInteractionKinds,
|
|
6762
6825
|
qtiReactPackageName,
|
|
6763
6826
|
qtiCoreInteractions,
|
|
6764
6827
|
positionObjectStage,
|
|
@@ -6790,6 +6853,7 @@ export {
|
|
|
6790
6853
|
executeTemplateProcessing,
|
|
6791
6854
|
executeResponseProcessing,
|
|
6792
6855
|
endAttemptInteraction,
|
|
6856
|
+
effectiveItemScore,
|
|
6793
6857
|
drawingInteraction,
|
|
6794
6858
|
defineInteraction,
|
|
6795
6859
|
createTestSessionStore,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless capability gate (ADR-0003): "can a runtime that supports `supportedInteractions`
|
|
3
|
+
* deliver this item, and if not, why" — extracted from the React runtime's `canDeliver`
|
|
4
|
+
* so server-side callers (e.g. an ingest pipeline) can reach the *same* decision without
|
|
5
|
+
* importing React. The React runtime delegates to this, passing the interaction set its
|
|
6
|
+
* descriptors + skins cover; a headless caller passes the set its delivery supports.
|
|
7
|
+
*
|
|
8
|
+
* This module is React-free by construction (content-model + RP collectors only; the view
|
|
9
|
+
* shapes are type-only imports), so it ships through the `@conform-ed/qti-react/headless`
|
|
10
|
+
* entry alongside the normalize → view adapters.
|
|
11
|
+
*/
|
|
12
|
+
import type { ZodType } from "zod";
|
|
13
|
+
import type { CapabilityReport } from "./capability";
|
|
14
|
+
import { type ContentModel } from "./content-model";
|
|
15
|
+
import type { AssessmentItemView, AssessmentStimulusRefView, StimulusContentView } from "./runtime";
|
|
16
|
+
export interface ItemCapabilityOptions {
|
|
17
|
+
/** Interaction kinds the target runtime can render (descriptor + skin both present). */
|
|
18
|
+
readonly supportedInteractions: ReadonlySet<string>;
|
|
19
|
+
/** Content model deciding the flow-element allowlist + math root; defaults to v0. */
|
|
20
|
+
readonly model?: ContentModel;
|
|
21
|
+
/** Custom-operator classes the target runtime registers (for RP capability). */
|
|
22
|
+
readonly customOperatorClasses?: ReadonlySet<string>;
|
|
23
|
+
/** Resolver for shared-stimulus refs; unresolved refs are not deliverable. */
|
|
24
|
+
readonly resolveStimulus?: (ref: AssessmentStimulusRefView) => StimulusContentView | null;
|
|
25
|
+
/**
|
|
26
|
+
* Optional per-kind schemas for the stricter `invalid-interaction` check. The React
|
|
27
|
+
* runtime supplies its descriptor schemas; a headless caller that has already validated
|
|
28
|
+
* structure (e.g. against the qti-xml contracts schema) can omit them.
|
|
29
|
+
*/
|
|
30
|
+
readonly interactionSchemas?: ReadonlyMap<string, ZodType>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Report whether `item` can be delivered by a runtime with the given capabilities, and
|
|
34
|
+
* every reason it cannot. Pure and React-free; the React runtime's `canDeliver` is a thin
|
|
35
|
+
* wrapper over this.
|
|
36
|
+
*/
|
|
37
|
+
export declare function reportItemCapability(item: AssessmentItemView, options: ItemCapabilityOptions): CapabilityReport;
|
|
38
|
+
/**
|
|
39
|
+
* Interaction kinds the bundled reference skin renders — the default "supported set" for
|
|
40
|
+
* callers that deliver with the reference skin. Kept in parity with the reference skin by
|
|
41
|
+
* a test; pass an explicit set to `reportItemCapability` for a custom delivery surface.
|
|
42
|
+
*/
|
|
43
|
+
export declare const referenceInteractionKinds: readonly string[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ScoreResult } from "./types";
|
|
2
|
+
export interface EffectiveItemScore {
|
|
3
|
+
readonly raw: number;
|
|
4
|
+
readonly max: number;
|
|
5
|
+
/** True when SCORE came from the RP outcomes of record rather than per-variable scoring. */
|
|
6
|
+
readonly fromOutcomes: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The item score of record (QTI): a numeric SCORE outcome from response processing is
|
|
10
|
+
* authoritative — PCI/RP-scored items (e.g. math-entry) have no per-variable
|
|
11
|
+
* correctResponse basis, so their standard scores read 0. Summed per-variable standard
|
|
12
|
+
* scoring is the fallback for items without RP. MAXSCORE follows the same precedence.
|
|
13
|
+
*
|
|
14
|
+
* Pure and framework-light: client and server (authoritative finalize) share it so the
|
|
15
|
+
* grade of record is derived identically on both sides.
|
|
16
|
+
*/
|
|
17
|
+
export declare function effectiveItemScore(scores: readonly ScoreResult[], outcomes: Readonly<Record<string, unknown>>): EffectiveItemScore;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conform-ed/qti-react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"files": [
|
|
5
5
|
"src",
|
|
6
6
|
"dist"
|
|
@@ -12,10 +12,15 @@
|
|
|
12
12
|
"development": "./src/index.ts",
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"import": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./headless": {
|
|
17
|
+
"development": "./src/headless.ts",
|
|
18
|
+
"types": "./dist/headless.d.ts",
|
|
19
|
+
"import": "./dist/headless.js"
|
|
15
20
|
}
|
|
16
21
|
},
|
|
17
22
|
"scripts": {
|
|
18
|
-
"build": "bun build ./src/index.ts --outdir dist --format esm --target browser --external react --external react-dom --external zod && tsgo -p tsconfig.build.json",
|
|
23
|
+
"build": "bun build ./src/index.ts ./src/headless.ts --outdir dist --format esm --target browser --external react --external react-dom --external zod && tsgo -p tsconfig.build.json",
|
|
19
24
|
"format": "oxfmt --config ../../.oxfmtrc.jsonc --check .",
|
|
20
25
|
"lint": "oxlint --config ../../.oxlintrc.jsonc .",
|
|
21
26
|
"test": "bun test",
|
|
@@ -25,11 +30,11 @@
|
|
|
25
30
|
"xspattern": "^3.1.0"
|
|
26
31
|
},
|
|
27
32
|
"devDependencies": {
|
|
28
|
-
"@conform-ed/contracts": "0.0.
|
|
29
|
-
"@conform-ed/qti-xml": "0.0.
|
|
33
|
+
"@conform-ed/contracts": "0.0.19",
|
|
34
|
+
"@conform-ed/qti-xml": "0.0.19",
|
|
30
35
|
"@types/react": "^19.2.17",
|
|
31
36
|
"@types/react-dom": "^19",
|
|
32
|
-
"happy-dom": "^20.10.
|
|
37
|
+
"happy-dom": "^20.10.3",
|
|
33
38
|
"react": "^19.2.7",
|
|
34
39
|
"react-dom": "^19"
|
|
35
40
|
},
|
package/src/headless.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless (React-free) surface of @conform-ed/qti-react: the normalize → view adapters,
|
|
3
|
+
* the capability gate, and the **scoring engine** (Response Processing interpreter, the
|
|
4
|
+
* standard per-response scoring templates, the item-score aggregator, and the test-level
|
|
5
|
+
* outcome-processing controller). Importable on a server (e.g. a QTI ingest pipeline or an
|
|
6
|
+
* authoritative grade finalize) without pulling React. Exposed at
|
|
7
|
+
* `@conform-ed/qti-react/headless`; everything here is also re-exported from the package
|
|
8
|
+
* root for React consumers.
|
|
9
|
+
*
|
|
10
|
+
* Keep this entry free of React-coupled imports — every module re-exported here
|
|
11
|
+
* (./normalized-item, ./item-capability, ./response-processing, ./rp, ./item-score,
|
|
12
|
+
* ./store, ./test, ./response-validity) is verified framework-light.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
assessmentItemViewFromNormalized,
|
|
17
|
+
assessmentTestViewFromNormalized,
|
|
18
|
+
stimulusContentFromNormalized,
|
|
19
|
+
} from "./normalized-item";
|
|
20
|
+
export { referenceInteractionKinds, reportItemCapability, type ItemCapabilityOptions } from "./item-capability";
|
|
21
|
+
|
|
22
|
+
// The scoring engine, headless. Running these server-side re-derives the grade of record
|
|
23
|
+
// from the learner's raw responses + the (server-held) answer keys — see emergent ADR-0019.
|
|
24
|
+
export { foldString, mapResponse, matchCorrect, mapResponsePoint, scoreResponse } from "./response-processing";
|
|
25
|
+
export { effectiveItemScore, type EffectiveItemScore } from "./item-score";
|
|
26
|
+
export {
|
|
27
|
+
applyCorrectResponseOverrides,
|
|
28
|
+
collectRpIssues,
|
|
29
|
+
collectTemplateIssues,
|
|
30
|
+
executeResponseProcessing,
|
|
31
|
+
executeTemplateProcessing,
|
|
32
|
+
mulberry32,
|
|
33
|
+
resolveTemplate,
|
|
34
|
+
} from "./rp";
|
|
35
|
+
export type {
|
|
36
|
+
CustomOperatorImplementation,
|
|
37
|
+
OutcomeDeclarationView,
|
|
38
|
+
OutcomeValue,
|
|
39
|
+
ResponseNormalization,
|
|
40
|
+
ResponseProcessingContext,
|
|
41
|
+
ResponseProcessingResult,
|
|
42
|
+
ResponseProcessingView,
|
|
43
|
+
TemplateDeclarationView,
|
|
44
|
+
} from "./rp";
|
|
45
|
+
export { createAttemptStore, type AttemptSnapshot, type AttemptStore, type AttemptStoreOptions } from "./store";
|
|
46
|
+
export { createTestController, type TestController, type TestSessionState } from "./test";
|
|
47
|
+
|
|
48
|
+
export type { CapabilityIssue, CapabilityIssueType, CapabilityReport } from "./capability";
|
|
49
|
+
export type {
|
|
50
|
+
AssessmentItemView,
|
|
51
|
+
AssessmentStimulusRefView,
|
|
52
|
+
BodyNode,
|
|
53
|
+
InteractionNode,
|
|
54
|
+
StimulusContentView,
|
|
55
|
+
XmlContentNode,
|
|
56
|
+
} from "./runtime";
|
|
57
|
+
export type { AssessmentTestView } from "./test";
|
|
58
|
+
export type { Cardinality, ResponseDeclarationView, ResponseValue, ScoreResult } from "./types";
|
package/src/index.ts
CHANGED
|
@@ -14,12 +14,16 @@ export {
|
|
|
14
14
|
|
|
15
15
|
export { foldString, mapResponse, matchCorrect, mapResponsePoint, scoreResponse } from "./response-processing";
|
|
16
16
|
|
|
17
|
+
export { effectiveItemScore, type EffectiveItemScore } from "./item-score";
|
|
18
|
+
|
|
17
19
|
export {
|
|
18
20
|
assessmentItemViewFromNormalized,
|
|
19
21
|
assessmentTestViewFromNormalized,
|
|
20
22
|
stimulusContentFromNormalized,
|
|
21
23
|
} from "./normalized-item";
|
|
22
24
|
|
|
25
|
+
export { referenceInteractionKinds, reportItemCapability, type ItemCapabilityOptions } from "./item-capability";
|
|
26
|
+
|
|
23
27
|
export { formatPoint, parseCoords, parsePoint, pointInShape, type Point, type QtiShape } from "./graphic";
|
|
24
28
|
|
|
25
29
|
export {
|