@pie-players/pie-assessment-toolkit 0.3.44 → 0.3.45

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 (191) hide show
  1. package/README.md +54 -69
  2. package/dist/components/ItemToolBar.custom-element.js +479 -324
  3. package/dist/components/PieAssessmentToolkit.custom-element.js +19498 -3590
  4. package/dist/components/SectionToolBar.custom-element.js +761 -507
  5. package/dist/index.d.ts +3 -6
  6. package/dist/index.js +1 -3
  7. package/dist/index.js.map +1 -1
  8. package/dist/policy/core/ToolPolicyEngine.d.ts +1 -2
  9. package/dist/policy/core/ToolPolicyEngine.js +1 -2
  10. package/dist/policy/core/ToolPolicyEngine.js.map +1 -1
  11. package/dist/policy/core/pnp-policy-inputs.d.ts +2 -3
  12. package/dist/policy/core/pnp-policy-inputs.js +2 -3
  13. package/dist/policy/core/pnp-policy-inputs.js.map +1 -1
  14. package/dist/policy/core/provenance.d.ts +7 -34
  15. package/dist/policy/core/provenance.js +4 -21
  16. package/dist/policy/core/provenance.js.map +1 -1
  17. package/dist/policy/sources/PnpPolicySource.d.ts +6 -19
  18. package/dist/policy/sources/PnpPolicySource.js +6 -19
  19. package/dist/policy/sources/PnpPolicySource.js.map +1 -1
  20. package/dist/runtime/SectionRuntimeEngine.d.ts +13 -28
  21. package/dist/runtime/SectionRuntimeEngine.js +13 -27
  22. package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
  23. package/dist/runtime/adapter/SectionEngineAdapter.d.ts +2 -2
  24. package/dist/runtime/adapter/SectionEngineAdapter.js +2 -2
  25. package/dist/runtime/adapter/SectionEngineAdapter.js.map +1 -1
  26. package/dist/runtime/adapter/coordinator-bridge.d.ts +1 -1
  27. package/dist/runtime/adapter/coordinator-bridge.js +1 -1
  28. package/dist/runtime/adapter/coordinator-bridge.js.map +1 -1
  29. package/dist/runtime/adapter/dom-event-bridge.d.ts +1 -1
  30. package/dist/runtime/adapter/dom-event-bridge.js +1 -1
  31. package/dist/runtime/adapter/dom-event-bridge.js.map +1 -1
  32. package/dist/runtime/catalog-registration.d.ts +12 -0
  33. package/dist/runtime/catalog-registration.js +54 -0
  34. package/dist/runtime/catalog-registration.js.map +1 -0
  35. package/dist/runtime/core/engine-output.d.ts +2 -2
  36. package/dist/runtime/core/engine-output.js +2 -2
  37. package/dist/runtime/core/engine-output.js.map +1 -1
  38. package/dist/runtime/core/engine-readiness.d.ts +2 -2
  39. package/dist/runtime/core/engine-readiness.js.map +1 -1
  40. package/dist/runtime/core/engine-resolver.d.ts +13 -41
  41. package/dist/runtime/core/engine-resolver.js +19 -76
  42. package/dist/runtime/core/engine-resolver.js.map +1 -1
  43. package/dist/runtime/core/engine-transition.d.ts +1 -1
  44. package/dist/runtime/core/engine-transition.js +1 -1
  45. package/dist/runtime/core/engine-transition.js.map +1 -1
  46. package/dist/runtime/engine.d.ts +1 -1
  47. package/dist/runtime/engine.js.map +1 -1
  48. package/dist/runtime/section-runtime-engine-host-context.d.ts +22 -15
  49. package/dist/runtime/section-runtime-engine-host-context.js +10 -10
  50. package/dist/runtime/section-runtime-engine-host-context.js.map +1 -1
  51. package/dist/runtime/stage-emit-gate.d.ts +4 -4
  52. package/dist/runtime/stage-emit-gate.js +3 -3
  53. package/dist/runtime/stage-emit-gate.js.map +1 -1
  54. package/dist/services/AccessibilityCatalogResolver.d.ts +23 -0
  55. package/dist/services/AccessibilityCatalogResolver.js +200 -28
  56. package/dist/services/AccessibilityCatalogResolver.js.map +1 -1
  57. package/dist/services/HighlightCoordinator.d.ts +25 -0
  58. package/dist/services/HighlightCoordinator.js +125 -0
  59. package/dist/services/HighlightCoordinator.js.map +1 -1
  60. package/dist/services/SSMLExtractor.d.ts +16 -1
  61. package/dist/services/SSMLExtractor.js +86 -7
  62. package/dist/services/SSMLExtractor.js.map +1 -1
  63. package/dist/services/TTSService.d.ts +30 -4
  64. package/dist/services/TTSService.js +652 -159
  65. package/dist/services/TTSService.js.map +1 -1
  66. package/dist/services/ToolRegistry.d.ts +2 -2
  67. package/dist/services/ToolRegistry.js +1 -1
  68. package/dist/services/ToolRegistry.js.map +1 -1
  69. package/dist/services/ToolkitCoordinator.d.ts +16 -73
  70. package/dist/services/ToolkitCoordinator.js +34 -113
  71. package/dist/services/ToolkitCoordinator.js.map +1 -1
  72. package/dist/services/interfaces.d.ts +20 -2
  73. package/dist/services/interfaces.js.map +1 -1
  74. package/dist/services/tool-config-validation.d.ts +1 -1
  75. package/dist/services/tool-config-validation.js +7 -7
  76. package/dist/services/tool-config-validation.js.map +1 -1
  77. package/dist/services/tool-instance-id.js +1 -1
  78. package/dist/services/tool-instance-id.js.map +1 -1
  79. package/dist/services/tool-providers/TTSToolProvider.js +3 -3
  80. package/dist/services/tool-providers/TTSToolProvider.js.map +1 -1
  81. package/dist/services/tools-config-normalizer.d.ts +13 -2
  82. package/dist/services/tools-config-normalizer.js +3 -8
  83. package/dist/services/tools-config-normalizer.js.map +1 -1
  84. package/dist/services/tts/browser-provider.js +2 -0
  85. package/dist/services/tts/browser-provider.js.map +1 -1
  86. package/dist/services/tts/catalog-span-alignment.d.ts +39 -0
  87. package/dist/services/tts/catalog-span-alignment.js +300 -0
  88. package/dist/services/tts/catalog-span-alignment.js.map +1 -0
  89. package/dist/services/tts/generated-speech/assemble-plan.d.ts +33 -0
  90. package/dist/services/tts/generated-speech/assemble-plan.js +111 -0
  91. package/dist/services/tts/generated-speech/assemble-plan.js.map +1 -0
  92. package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +25 -0
  93. package/dist/services/tts/generated-speech/dom/build-dom-plan.js +66 -0
  94. package/dist/services/tts/generated-speech/dom/build-dom-plan.js.map +1 -0
  95. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.d.ts +19 -0
  96. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.js +71 -0
  97. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.js.map +1 -0
  98. package/dist/services/tts/generated-speech/dom/types.d.ts +37 -0
  99. package/dist/services/tts/generated-speech/dom/types.js +2 -0
  100. package/dist/services/tts/generated-speech/dom/types.js.map +1 -0
  101. package/dist/services/tts/generated-speech/index.d.ts +17 -0
  102. package/dist/services/tts/generated-speech/index.js +17 -0
  103. package/dist/services/tts/generated-speech/index.js.map +1 -0
  104. package/dist/services/tts/generated-speech/math-speech-cache.d.ts +15 -0
  105. package/dist/services/tts/generated-speech/math-speech-cache.js +78 -0
  106. package/dist/services/tts/generated-speech/math-speech-cache.js.map +1 -0
  107. package/dist/services/tts/generated-speech/types.d.ts +104 -0
  108. package/dist/services/tts/generated-speech/types.js +18 -0
  109. package/dist/services/tts/generated-speech/types.js.map +1 -0
  110. package/dist/services/tts/highlight-pipeline/boundary-normalizer.d.ts +2 -0
  111. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +106 -0
  112. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js.map +1 -0
  113. package/dist/services/tts/highlight-pipeline/create-highlight-plan.d.ts +6 -0
  114. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +217 -0
  115. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js.map +1 -0
  116. package/dist/services/tts/highlight-pipeline/highlight-policy.d.ts +2 -0
  117. package/dist/services/tts/highlight-pipeline/highlight-policy.js +32 -0
  118. package/dist/services/tts/highlight-pipeline/highlight-policy.js.map +1 -0
  119. package/dist/services/tts/highlight-pipeline/index.d.ts +9 -0
  120. package/dist/services/tts/highlight-pipeline/index.js +9 -0
  121. package/dist/services/tts/highlight-pipeline/index.js.map +1 -0
  122. package/dist/services/tts/highlight-pipeline/math-semantic-resolver.d.ts +3 -0
  123. package/dist/services/tts/highlight-pipeline/math-semantic-resolver.js +32 -0
  124. package/dist/services/tts/highlight-pipeline/math-semantic-resolver.js.map +1 -0
  125. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.d.ts +29 -0
  126. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +44 -0
  127. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js.map +1 -0
  128. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.d.ts +2 -0
  129. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +20 -0
  130. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js.map +1 -0
  131. package/dist/services/tts/highlight-pipeline/readable-region.d.ts +1 -0
  132. package/dist/services/tts/highlight-pipeline/readable-region.js +19 -0
  133. package/dist/services/tts/highlight-pipeline/readable-region.js.map +1 -0
  134. package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.d.ts +6 -0
  135. package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.js +198 -0
  136. package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.js.map +1 -0
  137. package/dist/services/tts/highlight-pipeline/types.d.ts +72 -0
  138. package/dist/services/tts/highlight-pipeline/types.js +2 -0
  139. package/dist/services/tts/highlight-pipeline/types.js.map +1 -0
  140. package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +2 -0
  141. package/dist/services/tts/highlight-pipeline/visible-map-range.js +22 -0
  142. package/dist/services/tts/highlight-pipeline/visible-map-range.js.map +1 -0
  143. package/dist/services/tts/math-alignment/index.d.ts +30 -0
  144. package/dist/services/tts/math-alignment/index.js +66 -0
  145. package/dist/services/tts/math-alignment/index.js.map +1 -0
  146. package/dist/services/tts/math-alignment/mathml-tokenizer.d.ts +2 -0
  147. package/dist/services/tts/math-alignment/mathml-tokenizer.js +317 -0
  148. package/dist/services/tts/math-alignment/mathml-tokenizer.js.map +1 -0
  149. package/dist/services/tts/math-alignment/range-resolver.d.ts +10 -0
  150. package/dist/services/tts/math-alignment/range-resolver.js +13 -0
  151. package/dist/services/tts/math-alignment/range-resolver.js.map +1 -0
  152. package/dist/services/tts/math-alignment/sequence-aligner.d.ts +7 -0
  153. package/dist/services/tts/math-alignment/sequence-aligner.js +119 -0
  154. package/dist/services/tts/math-alignment/sequence-aligner.js.map +1 -0
  155. package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +31 -0
  156. package/dist/services/tts/math-alignment/speech-tokenizer.js +143 -0
  157. package/dist/services/tts/math-alignment/speech-tokenizer.js.map +1 -0
  158. package/dist/services/tts/math-alignment/types.d.ts +58 -0
  159. package/dist/services/tts/math-alignment/types.js +2 -0
  160. package/dist/services/tts/math-alignment/types.js.map +1 -0
  161. package/dist/services/tts/math-aware-text-processing.d.ts +18 -0
  162. package/dist/services/tts/math-aware-text-processing.js +348 -0
  163. package/dist/services/tts/math-aware-text-processing.js.map +1 -0
  164. package/dist/services/tts/math-speech.d.ts +44 -0
  165. package/dist/services/tts/math-speech.js +147 -0
  166. package/dist/services/tts/math-speech.js.map +1 -0
  167. package/dist/services/tts/mathml-sanitization.d.ts +1 -0
  168. package/dist/services/tts/mathml-sanitization.js +170 -0
  169. package/dist/services/tts/mathml-sanitization.js.map +1 -0
  170. package/dist/services/tts/ssml/spoken-text.d.ts +33 -0
  171. package/dist/services/tts/ssml/spoken-text.js +159 -0
  172. package/dist/services/tts/ssml/spoken-text.js.map +1 -0
  173. package/dist/services/tts/text-processing.d.ts +3 -0
  174. package/dist/services/tts/text-processing.js +30 -4
  175. package/dist/services/tts/text-processing.js.map +1 -1
  176. package/dist/services/tts-runtime-config.d.ts +17 -1
  177. package/dist/services/tts-runtime-config.js +11 -1
  178. package/dist/services/tts-runtime-config.js.map +1 -1
  179. package/dist/tools/registrations/accessibility-tools.js +0 -1
  180. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  181. package/dist/tools/registrations/calculator.js +41 -2
  182. package/dist/tools/registrations/calculator.js.map +1 -1
  183. package/dist/tools/tool-tag-map.js +0 -1
  184. package/dist/tools/tool-tag-map.js.map +1 -1
  185. package/package.json +8 -11
  186. package/dist/services/PNPToolResolver.d.ts +0 -280
  187. package/dist/services/PNPToolResolver.js +0 -514
  188. package/dist/services/PNPToolResolver.js.map +0 -1
  189. package/dist/services/pnp-provenance.d.ts +0 -166
  190. package/dist/services/pnp-provenance.js +0 -231
  191. package/dist/services/pnp-provenance.js.map +0 -1
@@ -1,514 +0,0 @@
1
- /**
2
- * PNP Tool Resolver
3
- *
4
- * Resolves tool availability from QTI 3.0 Personal Needs Profile (PNP) and
5
- * assessment settings. Implements a precedence hierarchy for tool configuration
6
- * based on common assessment platform governance patterns.
7
- *
8
- * PRECEDENCE HIERARCHY (highest to lowest):
9
- * ==========================================
10
- *
11
- * Standards-Based (QTI 3.0):
12
- * - PNP supports (#6): Student's documented accessibility needs (accessibilityInfo.accessFeature)
13
- * - Item-level settings (#3, #4): Per-item accessibility requirements/restrictions
14
- *
15
- * Implementation-Specific (Common Practice):
16
- * - District policy (#1, #5): Institutional governance and legal compliance
17
- * - Test administration (#2): Session-level operational control
18
- *
19
- * The hierarchy itself is NOT defined by QTI 3.0 standards but follows common
20
- * patterns in K-12 assessment platforms, aligning with US IEP/504 accommodation
21
- * hierarchies where legal requirements and institutional policies take precedence.
22
- *
23
- * Precedence Order:
24
- * 1. District block (absolute veto) - Legal/policy requirements
25
- * 2. Test administration override - Proctor/administrator operational control
26
- * 3. Item restriction (per-item block) - Content author can disable for specific items
27
- * 4. Item requirement (forces enable) - Required by IEP/504 or content needs
28
- * 5. District requirement - Institutional accessibility requirements
29
- * 6. PNP supports (student needs) - QTI 3.0 standard student preferences
30
- *
31
- * Governance Context:
32
- * - Institutional veto (district) trumps individual preferences (legal compliance)
33
- * - Session control (test admin) enables operational flexibility
34
- * - Content restrictions (item) prevent tools that invalidate assessment
35
- * - Required accommodations (IEP/504) ensure legal compliance
36
- * - Student preferences (PNP) are honored when not overridden
37
- *
38
- * References:
39
- * - QTI 3.0: https://www.imsglobal.org/spec/qti/v3p0
40
- * - IMS AfA 3.0: https://www.imsglobal.org/spec/afa/v3p0
41
- * - Common assessment platform governance patterns (not standardized)
42
- *
43
- * Part of PIE Assessment Toolkit.
44
- */
45
- import { PnpProvenanceBuilder, } from "./pnp-provenance.js";
46
- /**
47
- * Resolves tool availability from QTI 3.0 assessment structure
48
- */
49
- export class PnpToolResolver {
50
- toolRegistry;
51
- enableProvenance;
52
- /**
53
- * Create a PnpToolResolver
54
- *
55
- * @param toolRegistry Tool registry for PNP support mapping
56
- * @param enableProvenance Enable detailed provenance tracking (default: true)
57
- */
58
- constructor(toolRegistry, enableProvenance = true) {
59
- this.toolRegistry = toolRegistry;
60
- this.enableProvenance = enableProvenance;
61
- }
62
- /**
63
- * Resolve all tools from QTI 3.0 assessment + optional item context
64
- *
65
- * @param assessment QTI 3.0 assessment with personalNeedsProfile
66
- * @param currentItemRef Current item for item-specific requirements (optional)
67
- * @returns Array of resolved tool configurations
68
- *
69
- * @example
70
- * const resolver = new PnpToolResolver(registry);
71
- * const tools = resolver.resolveTools(assessment, itemRef);
72
- * tools.forEach(tool => {
73
- * console.log(`${tool.id}: ${tool.enabled ? 'enabled' : 'disabled'}`);
74
- * });
75
- */
76
- resolveTools(assessment, currentItemRef) {
77
- const result = this.resolveToolsWithProvenance(assessment, currentItemRef);
78
- return result.tools;
79
- }
80
- /**
81
- * Resolve all tools with full provenance tracking
82
- *
83
- * Returns both resolved tools and complete provenance trail showing:
84
- * - All configuration sources consulted
85
- * - Decision steps at each precedence level
86
- * - Reasons for enabling/blocking each feature
87
- * - Human-readable explanations
88
- *
89
- * @param assessment QTI 3.0 assessment with personalNeedsProfile
90
- * @param currentItemRef Current item for item-specific requirements (optional)
91
- * @returns Resolution result with tools and provenance
92
- *
93
- * @example
94
- * const resolver = new PnpToolResolver(registry);
95
- * const result = resolver.resolveToolsWithProvenance(assessment, itemRef);
96
- *
97
- * // Use resolved tools
98
- * result.tools.forEach(tool => console.log(tool.id, tool.enabled));
99
- *
100
- * // Display provenance to user
101
- * console.log(result.provenance.summary);
102
- * result.provenance.features.forEach((trail, featureId) => {
103
- * console.log(`${featureId}: ${trail.explanation}`);
104
- * });
105
- */
106
- resolveToolsWithProvenance(assessment, currentItemRef) {
107
- const pnp = assessment.personalNeedsProfile;
108
- const settings = assessment.settings;
109
- const itemSettings = currentItemRef?.settings;
110
- // Initialize provenance builder
111
- const contextId = `assessment-${assessment.id || "unknown"}${currentItemRef ? `-item-${currentItemRef.identifier}` : ""}`;
112
- const provenanceBuilder = this.enableProvenance
113
- ? new PnpProvenanceBuilder(contextId)
114
- : null;
115
- // Record configuration sources
116
- if (provenanceBuilder) {
117
- if (settings?.districtPolicy || settings?.testAdministration) {
118
- provenanceBuilder.addSource("assessment", {
119
- id: assessment.id || "unknown",
120
- name: assessment.name || assessment.id || "Assessment",
121
- config: settings,
122
- });
123
- }
124
- if (pnp) {
125
- provenanceBuilder.addSource("student", {
126
- id: "student", // Would come from session context
127
- name: "Student PNP Profile",
128
- config: pnp,
129
- });
130
- }
131
- if (currentItemRef && itemSettings) {
132
- provenanceBuilder.addSource("item", {
133
- id: currentItemRef.identifier,
134
- name: currentItemRef.identifier,
135
- config: itemSettings,
136
- });
137
- }
138
- }
139
- // Collect all PNP support IDs mentioned anywhere
140
- const allSupports = new Set();
141
- pnp?.supports?.forEach((s) => allSupports.add(s));
142
- pnp?.prohibitedSupports?.forEach((s) => allSupports.add(s));
143
- settings?.districtPolicy?.blockedTools?.forEach((s) => allSupports.add(s));
144
- settings?.districtPolicy?.requiredTools?.forEach((s) => allSupports.add(s));
145
- itemSettings?.requiredTools?.forEach((s) => allSupports.add(s));
146
- itemSettings?.restrictedTools?.forEach((s) => allSupports.add(s));
147
- const resolved = [];
148
- const context = {
149
- pnp,
150
- districtPolicy: settings?.districtPolicy,
151
- testAdmin: settings?.testAdministration,
152
- itemSettings,
153
- toolConfigs: settings?.toolConfigs,
154
- };
155
- for (const supportId of allSupports) {
156
- const config = this.resolveSupport(supportId, context, provenanceBuilder);
157
- if (config) {
158
- resolved.push(config);
159
- }
160
- }
161
- return {
162
- tools: resolved,
163
- provenance: provenanceBuilder
164
- ? provenanceBuilder.build()
165
- : this.createEmptyProvenance(contextId),
166
- };
167
- }
168
- /**
169
- * Resolve availability of a single PNP support
170
- *
171
- * Applies precedence rules to determine if support should be enabled.
172
- *
173
- * @param supportId PNP support identifier
174
- * @param context Resolution context with all configuration sources
175
- * @param provenanceBuilder Provenance tracking (optional)
176
- * @returns Resolved config or null if blocked/disabled
177
- */
178
- resolveSupport(supportId, context, provenanceBuilder) {
179
- // Precedence (highest to lowest):
180
- // 1. District block (absolute veto)
181
- if (context.districtPolicy?.blockedTools?.includes(supportId)) {
182
- provenanceBuilder?.addDecision({
183
- precedence: 1,
184
- rule: "district-block",
185
- featureId: supportId,
186
- action: "block",
187
- sourceType: "assessment",
188
- reason: `District policy blocks "${supportId}" for all assessments`,
189
- value: context.districtPolicy.blockedTools,
190
- });
191
- return null; // Blocked
192
- }
193
- // 2. Test administration override
194
- if (context.testAdmin?.toolOverrides?.[supportId] === false) {
195
- provenanceBuilder?.addDecision({
196
- precedence: 2,
197
- rule: "test-admin-override",
198
- featureId: supportId,
199
- action: "block",
200
- sourceType: "assessment",
201
- reason: `Test administrator disabled "${supportId}" for this session`,
202
- value: context.testAdmin.toolOverrides,
203
- });
204
- return null; // Blocked
205
- }
206
- // 3. Item restriction
207
- if (context.itemSettings?.restrictedTools?.includes(supportId)) {
208
- provenanceBuilder?.addDecision({
209
- precedence: 3,
210
- rule: "item-restriction",
211
- featureId: supportId,
212
- action: "block",
213
- sourceType: "item",
214
- reason: `Item restricts "${supportId}" (e.g., mental math question blocks calculator)`,
215
- value: context.itemSettings.restrictedTools,
216
- });
217
- return null; // Blocked for this item
218
- }
219
- // 4. Item requirement (forces enable)
220
- if (context.itemSettings?.requiredTools?.includes(supportId)) {
221
- provenanceBuilder?.addDecision({
222
- precedence: 4,
223
- rule: "item-requirement",
224
- featureId: supportId,
225
- action: "enable",
226
- sourceType: "item",
227
- reason: `Item requires "${supportId}" for this question`,
228
- value: context.itemSettings.requiredTools,
229
- });
230
- return this.buildToolConfig(supportId, context, {
231
- required: true,
232
- source: "item",
233
- });
234
- }
235
- // 5. District requirement
236
- if (context.districtPolicy?.requiredTools?.includes(supportId)) {
237
- provenanceBuilder?.addDecision({
238
- precedence: 5,
239
- rule: "district-requirement",
240
- featureId: supportId,
241
- action: "enable",
242
- sourceType: "assessment",
243
- reason: `District policy requires "${supportId}" for all assessments`,
244
- value: context.districtPolicy.requiredTools,
245
- });
246
- return this.buildToolConfig(supportId, context, {
247
- required: true,
248
- source: "district",
249
- });
250
- }
251
- // 6. PNP prohibitions and supports (student needs)
252
- if (context.pnp?.prohibitedSupports?.includes(supportId)) {
253
- provenanceBuilder?.addDecision({
254
- precedence: 6,
255
- rule: "pnp-prohibited",
256
- featureId: supportId,
257
- action: "block",
258
- sourceType: "student",
259
- reason: `Student PNP profile prohibits "${supportId}"`,
260
- value: context.pnp.prohibitedSupports,
261
- });
262
- return null;
263
- }
264
- if (context.pnp?.supports?.includes(supportId)) {
265
- provenanceBuilder?.addDecision({
266
- precedence: 6,
267
- rule: "pnp-support",
268
- featureId: supportId,
269
- action: "enable",
270
- sourceType: "student",
271
- reason: `Student PNP profile requests "${supportId}"`,
272
- value: context.pnp.supports,
273
- });
274
- return this.buildToolConfig(supportId, context, {
275
- alwaysAvailable: true,
276
- source: "pnp",
277
- });
278
- }
279
- // Not enabled - log as skipped
280
- provenanceBuilder?.addDecision({
281
- precedence: 6,
282
- rule: "pnp-support",
283
- featureId: supportId,
284
- action: "skip",
285
- sourceType: "system",
286
- reason: `Feature "${supportId}" not configured at any level`,
287
- });
288
- return null;
289
- }
290
- /**
291
- * Create empty provenance when tracking is disabled
292
- */
293
- createEmptyProvenance(contextId) {
294
- return {
295
- contextId,
296
- resolvedAt: new Date(),
297
- sources: {},
298
- features: new Map(),
299
- decisionLog: [],
300
- summary: {
301
- totalFeatures: 0,
302
- enabled: 0,
303
- blocked: 0,
304
- notConfigured: 0,
305
- bySource: {},
306
- byRule: {},
307
- },
308
- };
309
- }
310
- /**
311
- * Build resolved tool configuration
312
- *
313
- * Maps PNP support ID to PIE tool ID and merges tool-specific settings.
314
- *
315
- * @param supportId PNP support identifier
316
- * @param context Resolution context for settings lookup
317
- * @param flags Availability flags (required, alwaysAvailable, source)
318
- * @returns Complete resolved tool configuration
319
- */
320
- buildToolConfig(supportId, context, flags) {
321
- // Map PNP support ID to PIE tool ID via registry
322
- const toolIds = this.toolRegistry.getToolsByPNPSupport(supportId);
323
- // Use first matching tool (usually only one per PNP support)
324
- // If no mapping exists, use the support ID directly as tool ID
325
- const pieToolId = toolIds.size > 0 ? Array.from(toolIds)[0] : supportId;
326
- // Get tool-specific config from settings (item takes precedence)
327
- const toolConfig = context.itemSettings?.toolParameters?.[supportId] ||
328
- context.toolConfigs?.[supportId];
329
- return {
330
- id: pieToolId,
331
- enabled: true,
332
- required: flags.required || false,
333
- alwaysAvailable: flags.alwaysAvailable || false,
334
- settings: toolConfig,
335
- source: flags.source,
336
- };
337
- }
338
- /**
339
- * Check if a specific tool is enabled
340
- *
341
- * @param toolId Toolkit tool identifier (e.g., 'calculator')
342
- * @param assessment Assessment with PNP
343
- * @param itemRef Optional item context
344
- * @returns true if tool is enabled
345
- */
346
- isToolEnabled(toolId, assessment, itemRef) {
347
- const resolved = this.resolveTools(assessment, itemRef);
348
- return resolved.some((t) => t.id === toolId && t.enabled);
349
- }
350
- /**
351
- * Check if a tool is required (cannot be disabled)
352
- *
353
- * @param toolId Toolkit tool identifier
354
- * @param assessment Assessment with PNP
355
- * @param itemRef Optional item context
356
- * @returns true if tool is required or always available
357
- */
358
- isToolRequired(toolId, assessment, itemRef) {
359
- const resolved = this.resolveTools(assessment, itemRef);
360
- const tool = resolved.find((t) => t.id === toolId);
361
- return tool?.required || tool?.alwaysAvailable || false;
362
- }
363
- /**
364
- * Get list of auto-activate tools from PNP
365
- *
366
- * Returns PIE tool IDs that should be automatically opened/activated
367
- * when the assessment starts, based on PNP activateAtInit field.
368
- *
369
- * @param assessment Assessment with PNP
370
- * @returns Array of PIE tool IDs to auto-activate
371
- *
372
- * @example
373
- * const autoActivate = resolver.getAutoActivateTools(assessment);
374
- * // Returns: ['textToSpeech', 'lineReader']
375
- */
376
- getAutoActivateTools(assessment) {
377
- const pnp = assessment.personalNeedsProfile;
378
- if (!pnp?.activateAtInit)
379
- return [];
380
- return pnp.activateAtInit
381
- .map((supportId) => {
382
- const toolIds = this.toolRegistry.getToolsByPNPSupport(supportId);
383
- return toolIds.size > 0 ? Array.from(toolIds)[0] : null;
384
- })
385
- .filter(Boolean);
386
- }
387
- /**
388
- * Get all enabled tools
389
- *
390
- * @param assessment Assessment with PNP
391
- * @param itemRef Optional item context
392
- * @returns Array of PIE tool IDs that are enabled
393
- */
394
- getEnabledTools(assessment, itemRef) {
395
- const resolved = this.resolveTools(assessment, itemRef);
396
- return Array.from(new Set(resolved
397
- .filter((t) => t.enabled)
398
- .map((t) => this.toolRegistry.normalizeToolId(t.id))
399
- .filter(Boolean)));
400
- }
401
- /**
402
- * Get allowed tool IDs (Pass 1 of two-pass visibility model)
403
- *
404
- * Returns tool IDs that pass orchestrator-level checks (PNP + policies + settings).
405
- * This is the first gate - tools still need to pass Pass 2 (relevance check).
406
- *
407
- * This is the primary method to use with ToolRegistry.filterVisibleInContext()
408
- *
409
- * @param assessment Assessment with PNP
410
- * @param itemRef Optional item context
411
- * @returns Array of allowed tool IDs (Pass 1)
412
- *
413
- * @example
414
- * // Two-pass visibility model
415
- * const allowedToolIds = resolver.getAllowedToolIds(assessment, itemRef);
416
- * const visibleTools = toolRegistry.filterVisibleInContext(allowedToolIds, context);
417
- */
418
- getAllowedToolIds(assessment, itemRef) {
419
- return this.getEnabledTools(assessment, itemRef);
420
- }
421
- /**
422
- * Resolve tools with a test/override PNP profile
423
- *
424
- * FOR TESTING/DEVELOPMENT ONLY
425
- *
426
- * Allows direct injection of a PNP profile for testing accessibility configurations
427
- * without modifying the assessment entity. The override profile replaces the
428
- * assessment's personalNeedsProfile entirely.
429
- *
430
- * Use cases:
431
- * - Development/testing UI for trying different profiles
432
- * - Automated testing with various accessibility configurations
433
- * - Preview tools with profile simulation
434
- * - Accessibility team testing and validation
435
- *
436
- * @param assessment Base assessment (settings, policies, etc.)
437
- * @param overrideProfile PNP profile to inject (replaces assessment.personalNeedsProfile)
438
- * @param itemRef Optional item context
439
- * @returns Resolution result with tools and provenance
440
- *
441
- * @example
442
- * // Test with low vision profile
443
- * const testProfile = {
444
- * supports: ['magnification', 'textToSpeech', 'highContrastDisplay']
445
- * };
446
- * const result = resolver.resolveWithOverride(assessment, testProfile);
447
- *
448
- * @example
449
- * // Test with null profile (no accessibility features)
450
- * const result = resolver.resolveWithOverride(assessment, null);
451
- */
452
- resolveWithOverride(assessment, overrideProfile, itemRef) {
453
- // Create assessment clone with overridden PNP
454
- const testAssessment = {
455
- ...assessment,
456
- personalNeedsProfile: overrideProfile || undefined,
457
- };
458
- return this.resolveToolsWithProvenance(testAssessment, itemRef);
459
- }
460
- /**
461
- * Get allowed tool IDs with full provenance tracking
462
- *
463
- * Returns both tool IDs and complete resolution provenance for debugging,
464
- * auditing, and displaying to users.
465
- *
466
- * @param assessment Assessment with PNP
467
- * @param itemRef Optional item context
468
- * @returns Object with tool IDs and provenance
469
- *
470
- * @example
471
- * const result = resolver.getAllowedToolIdsWithProvenance(assessment, itemRef);
472
- *
473
- * // Use tool IDs
474
- * const visibleTools = toolRegistry.filterVisibleInContext(result.toolIds, context);
475
- *
476
- * // Display provenance to user
477
- * console.log('Resolution Summary:', result.provenance.summary);
478
- * console.log('Explanation for calculator:', result.provenance.features.get('calculator')?.explanation);
479
- */
480
- getAllowedToolIdsWithProvenance(assessment, itemRef) {
481
- const result = this.resolveToolsWithProvenance(assessment, itemRef);
482
- return {
483
- toolIds: result.tools.map((t) => t.id),
484
- provenance: result.provenance,
485
- };
486
- }
487
- /**
488
- * Get all required tools
489
- *
490
- * @param assessment Assessment with PNP
491
- * @param itemRef Optional item context
492
- * @returns Array of PIE tool IDs that are required
493
- */
494
- getRequiredTools(assessment, itemRef) {
495
- const resolved = this.resolveTools(assessment, itemRef);
496
- return resolved
497
- .filter((t) => t.required || t.alwaysAvailable)
498
- .map((t) => t.id);
499
- }
500
- /**
501
- * Get tool configuration settings
502
- *
503
- * @param toolId Toolkit tool identifier
504
- * @param assessment Assessment with PNP
505
- * @param itemRef Optional item context
506
- * @returns Tool-specific settings or null if not found
507
- */
508
- getToolSettings(toolId, assessment, itemRef) {
509
- const resolved = this.resolveTools(assessment, itemRef);
510
- const tool = resolved.find((t) => t.id === toolId);
511
- return tool?.settings || null;
512
- }
513
- }
514
- //# sourceMappingURL=PNPToolResolver.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PNPToolResolver.js","sourceRoot":"","sources":["../../src/services/PNPToolResolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PNP Tool Resolver\n *\n * Resolves tool availability from QTI 3.0 Personal Needs Profile (PNP) and\n * assessment settings. Implements a precedence hierarchy for tool configuration\n * based on common assessment platform governance patterns.\n *\n * PRECEDENCE HIERARCHY (highest to lowest):\n * ==========================================\n *\n * Standards-Based (QTI 3.0):\n * - PNP supports (#6): Student's documented accessibility needs (accessibilityInfo.accessFeature)\n * - Item-level settings (#3, #4): Per-item accessibility requirements/restrictions\n *\n * Implementation-Specific (Common Practice):\n * - District policy (#1, #5): Institutional governance and legal compliance\n * - Test administration (#2): Session-level operational control\n *\n * The hierarchy itself is NOT defined by QTI 3.0 standards but follows common\n * patterns in K-12 assessment platforms, aligning with US IEP/504 accommodation\n * hierarchies where legal requirements and institutional policies take precedence.\n *\n * Precedence Order:\n * 1. District block (absolute veto) - Legal/policy requirements\n * 2. Test administration override - Proctor/administrator operational control\n * 3. Item restriction (per-item block) - Content author can disable for specific items\n * 4. Item requirement (forces enable) - Required by IEP/504 or content needs\n * 5. District requirement - Institutional accessibility requirements\n * 6. PNP supports (student needs) - QTI 3.0 standard student preferences\n *\n * Governance Context:\n * - Institutional veto (district) trumps individual preferences (legal compliance)\n * - Session control (test admin) enables operational flexibility\n * - Content restrictions (item) prevent tools that invalidate assessment\n * - Required accommodations (IEP/504) ensure legal compliance\n * - Student preferences (PNP) are honored when not overridden\n *\n * References:\n * - QTI 3.0: https://www.imsglobal.org/spec/qti/v3p0\n * - IMS AfA 3.0: https://www.imsglobal.org/spec/afa/v3p0\n * - Common assessment platform governance patterns (not standardized)\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type {\n\tAssessmentEntity,\n\tAssessmentItemRef,\n\tAssessmentSettings,\n\tItemSettings,\n\tPersonalNeedsProfile,\n} from \"@pie-players/pie-players-shared/types\";\nimport type { ToolRegistry } from \"./ToolRegistry.js\";\nimport {\n\tPnpProvenanceBuilder,\n\ttype PnpResolutionProvenance,\n} from \"./pnp-provenance.js\";\n\n/**\n * Resolved tool configuration with source tracking\n */\nexport interface ResolvedToolConfig {\n\tid: string; // PIE tool ID\n\tenabled: boolean;\n\trequired?: boolean; // Must be available (item/district requirement)\n\talwaysAvailable?: boolean; // PNP support (can't be toggled off)\n\tsettings?: any; // Tool-specific configuration\n\tsource: \"district\" | \"item\" | \"pnp\" | \"settings\"; // Configuration source\n}\n\n/**\n * Resolution result with provenance tracking\n */\nexport interface ToolResolutionResult {\n\t/** Resolved tool configurations */\n\ttools: ResolvedToolConfig[];\n\n\t/** Complete provenance trail (for debugging/display) */\n\tprovenance: PnpResolutionProvenance;\n}\n\n/**\n * Internal context for tool resolution\n */\ninterface ResolutionContext {\n\tpnp?: PersonalNeedsProfile;\n\tdistrictPolicy?: AssessmentSettings[\"districtPolicy\"];\n\ttestAdmin?: AssessmentSettings[\"testAdministration\"];\n\titemSettings?: ItemSettings;\n\ttoolConfigs?: AssessmentSettings[\"toolConfigs\"];\n}\n\n/**\n * Resolves tool availability from QTI 3.0 assessment structure\n */\nexport class PnpToolResolver {\n\tprivate toolRegistry: ToolRegistry;\n\tprivate enableProvenance: boolean;\n\n\t/**\n\t * Create a PnpToolResolver\n\t *\n\t * @param toolRegistry Tool registry for PNP support mapping\n\t * @param enableProvenance Enable detailed provenance tracking (default: true)\n\t */\n\tconstructor(toolRegistry: ToolRegistry, enableProvenance = true) {\n\t\tthis.toolRegistry = toolRegistry;\n\t\tthis.enableProvenance = enableProvenance;\n\t}\n\n\t/**\n\t * Resolve all tools from QTI 3.0 assessment + optional item context\n\t *\n\t * @param assessment QTI 3.0 assessment with personalNeedsProfile\n\t * @param currentItemRef Current item for item-specific requirements (optional)\n\t * @returns Array of resolved tool configurations\n\t *\n\t * @example\n\t * const resolver = new PnpToolResolver(registry);\n\t * const tools = resolver.resolveTools(assessment, itemRef);\n\t * tools.forEach(tool => {\n\t * console.log(`${tool.id}: ${tool.enabled ? 'enabled' : 'disabled'}`);\n\t * });\n\t */\n\tresolveTools(\n\t\tassessment: AssessmentEntity,\n\t\tcurrentItemRef?: AssessmentItemRef,\n\t): ResolvedToolConfig[] {\n\t\tconst result = this.resolveToolsWithProvenance(assessment, currentItemRef);\n\t\treturn result.tools;\n\t}\n\n\t/**\n\t * Resolve all tools with full provenance tracking\n\t *\n\t * Returns both resolved tools and complete provenance trail showing:\n\t * - All configuration sources consulted\n\t * - Decision steps at each precedence level\n\t * - Reasons for enabling/blocking each feature\n\t * - Human-readable explanations\n\t *\n\t * @param assessment QTI 3.0 assessment with personalNeedsProfile\n\t * @param currentItemRef Current item for item-specific requirements (optional)\n\t * @returns Resolution result with tools and provenance\n\t *\n\t * @example\n\t * const resolver = new PnpToolResolver(registry);\n\t * const result = resolver.resolveToolsWithProvenance(assessment, itemRef);\n\t *\n\t * // Use resolved tools\n\t * result.tools.forEach(tool => console.log(tool.id, tool.enabled));\n\t *\n\t * // Display provenance to user\n\t * console.log(result.provenance.summary);\n\t * result.provenance.features.forEach((trail, featureId) => {\n\t * console.log(`${featureId}: ${trail.explanation}`);\n\t * });\n\t */\n\tresolveToolsWithProvenance(\n\t\tassessment: AssessmentEntity,\n\t\tcurrentItemRef?: AssessmentItemRef,\n\t): ToolResolutionResult {\n\t\tconst pnp = assessment.personalNeedsProfile;\n\t\tconst settings = assessment.settings as AssessmentSettings;\n\t\tconst itemSettings = currentItemRef?.settings as ItemSettings;\n\n\t\t// Initialize provenance builder\n\t\tconst contextId = `assessment-${assessment.id || \"unknown\"}${currentItemRef ? `-item-${currentItemRef.identifier}` : \"\"}`;\n\t\tconst provenanceBuilder = this.enableProvenance\n\t\t\t? new PnpProvenanceBuilder(contextId)\n\t\t\t: null;\n\n\t\t// Record configuration sources\n\t\tif (provenanceBuilder) {\n\t\t\tif (settings?.districtPolicy || settings?.testAdministration) {\n\t\t\t\tprovenanceBuilder.addSource(\"assessment\", {\n\t\t\t\t\tid: assessment.id || \"unknown\",\n\t\t\t\t\tname: assessment.name || assessment.id || \"Assessment\",\n\t\t\t\t\tconfig: settings,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (pnp) {\n\t\t\t\tprovenanceBuilder.addSource(\"student\", {\n\t\t\t\t\tid: \"student\", // Would come from session context\n\t\t\t\t\tname: \"Student PNP Profile\",\n\t\t\t\t\tconfig: pnp,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (currentItemRef && itemSettings) {\n\t\t\t\tprovenanceBuilder.addSource(\"item\", {\n\t\t\t\t\tid: currentItemRef.identifier,\n\t\t\t\t\tname: currentItemRef.identifier,\n\t\t\t\t\tconfig: itemSettings,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Collect all PNP support IDs mentioned anywhere\n\t\tconst allSupports = new Set<string>();\n\t\tpnp?.supports?.forEach((s) => allSupports.add(s));\n\t\tpnp?.prohibitedSupports?.forEach((s) => allSupports.add(s));\n\t\tsettings?.districtPolicy?.blockedTools?.forEach((s) => allSupports.add(s));\n\t\tsettings?.districtPolicy?.requiredTools?.forEach((s) => allSupports.add(s));\n\t\titemSettings?.requiredTools?.forEach((s) => allSupports.add(s));\n\t\titemSettings?.restrictedTools?.forEach((s) => allSupports.add(s));\n\n\t\tconst resolved: ResolvedToolConfig[] = [];\n\t\tconst context: ResolutionContext = {\n\t\t\tpnp,\n\t\t\tdistrictPolicy: settings?.districtPolicy,\n\t\t\ttestAdmin: settings?.testAdministration,\n\t\t\titemSettings,\n\t\t\ttoolConfigs: settings?.toolConfigs,\n\t\t};\n\n\t\tfor (const supportId of allSupports) {\n\t\t\tconst config = this.resolveSupport(supportId, context, provenanceBuilder);\n\n\t\t\tif (config) {\n\t\t\t\tresolved.push(config);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\ttools: resolved,\n\t\t\tprovenance: provenanceBuilder\n\t\t\t\t? provenanceBuilder.build()\n\t\t\t\t: this.createEmptyProvenance(contextId),\n\t\t};\n\t}\n\n\t/**\n\t * Resolve availability of a single PNP support\n\t *\n\t * Applies precedence rules to determine if support should be enabled.\n\t *\n\t * @param supportId PNP support identifier\n\t * @param context Resolution context with all configuration sources\n\t * @param provenanceBuilder Provenance tracking (optional)\n\t * @returns Resolved config or null if blocked/disabled\n\t */\n\tprivate resolveSupport(\n\t\tsupportId: string,\n\t\tcontext: ResolutionContext,\n\t\tprovenanceBuilder: PnpProvenanceBuilder | null,\n\t): ResolvedToolConfig | null {\n\t\t// Precedence (highest to lowest):\n\n\t\t// 1. District block (absolute veto)\n\t\tif (context.districtPolicy?.blockedTools?.includes(supportId)) {\n\t\t\tprovenanceBuilder?.addDecision({\n\t\t\t\tprecedence: 1,\n\t\t\t\trule: \"district-block\",\n\t\t\t\tfeatureId: supportId,\n\t\t\t\taction: \"block\",\n\t\t\t\tsourceType: \"assessment\",\n\t\t\t\treason: `District policy blocks \"${supportId}\" for all assessments`,\n\t\t\t\tvalue: context.districtPolicy.blockedTools,\n\t\t\t});\n\t\t\treturn null; // Blocked\n\t\t}\n\n\t\t// 2. Test administration override\n\t\tif (context.testAdmin?.toolOverrides?.[supportId] === false) {\n\t\t\tprovenanceBuilder?.addDecision({\n\t\t\t\tprecedence: 2,\n\t\t\t\trule: \"test-admin-override\",\n\t\t\t\tfeatureId: supportId,\n\t\t\t\taction: \"block\",\n\t\t\t\tsourceType: \"assessment\",\n\t\t\t\treason: `Test administrator disabled \"${supportId}\" for this session`,\n\t\t\t\tvalue: context.testAdmin.toolOverrides,\n\t\t\t});\n\t\t\treturn null; // Blocked\n\t\t}\n\n\t\t// 3. Item restriction\n\t\tif (context.itemSettings?.restrictedTools?.includes(supportId)) {\n\t\t\tprovenanceBuilder?.addDecision({\n\t\t\t\tprecedence: 3,\n\t\t\t\trule: \"item-restriction\",\n\t\t\t\tfeatureId: supportId,\n\t\t\t\taction: \"block\",\n\t\t\t\tsourceType: \"item\",\n\t\t\t\treason: `Item restricts \"${supportId}\" (e.g., mental math question blocks calculator)`,\n\t\t\t\tvalue: context.itemSettings.restrictedTools,\n\t\t\t});\n\t\t\treturn null; // Blocked for this item\n\t\t}\n\n\t\t// 4. Item requirement (forces enable)\n\t\tif (context.itemSettings?.requiredTools?.includes(supportId)) {\n\t\t\tprovenanceBuilder?.addDecision({\n\t\t\t\tprecedence: 4,\n\t\t\t\trule: \"item-requirement\",\n\t\t\t\tfeatureId: supportId,\n\t\t\t\taction: \"enable\",\n\t\t\t\tsourceType: \"item\",\n\t\t\t\treason: `Item requires \"${supportId}\" for this question`,\n\t\t\t\tvalue: context.itemSettings.requiredTools,\n\t\t\t});\n\t\t\treturn this.buildToolConfig(supportId, context, {\n\t\t\t\trequired: true,\n\t\t\t\tsource: \"item\",\n\t\t\t});\n\t\t}\n\n\t\t// 5. District requirement\n\t\tif (context.districtPolicy?.requiredTools?.includes(supportId)) {\n\t\t\tprovenanceBuilder?.addDecision({\n\t\t\t\tprecedence: 5,\n\t\t\t\trule: \"district-requirement\",\n\t\t\t\tfeatureId: supportId,\n\t\t\t\taction: \"enable\",\n\t\t\t\tsourceType: \"assessment\",\n\t\t\t\treason: `District policy requires \"${supportId}\" for all assessments`,\n\t\t\t\tvalue: context.districtPolicy.requiredTools,\n\t\t\t});\n\t\t\treturn this.buildToolConfig(supportId, context, {\n\t\t\t\trequired: true,\n\t\t\t\tsource: \"district\",\n\t\t\t});\n\t\t}\n\n\t\t// 6. PNP prohibitions and supports (student needs)\n\t\tif (context.pnp?.prohibitedSupports?.includes(supportId)) {\n\t\t\tprovenanceBuilder?.addDecision({\n\t\t\t\tprecedence: 6,\n\t\t\t\trule: \"pnp-prohibited\",\n\t\t\t\tfeatureId: supportId,\n\t\t\t\taction: \"block\",\n\t\t\t\tsourceType: \"student\",\n\t\t\t\treason: `Student PNP profile prohibits \"${supportId}\"`,\n\t\t\t\tvalue: context.pnp.prohibitedSupports,\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\n\t\tif (context.pnp?.supports?.includes(supportId)) {\n\t\t\tprovenanceBuilder?.addDecision({\n\t\t\t\tprecedence: 6,\n\t\t\t\trule: \"pnp-support\",\n\t\t\t\tfeatureId: supportId,\n\t\t\t\taction: \"enable\",\n\t\t\t\tsourceType: \"student\",\n\t\t\t\treason: `Student PNP profile requests \"${supportId}\"`,\n\t\t\t\tvalue: context.pnp.supports,\n\t\t\t});\n\n\t\t\treturn this.buildToolConfig(supportId, context, {\n\t\t\t\talwaysAvailable: true,\n\t\t\t\tsource: \"pnp\",\n\t\t\t});\n\t\t}\n\n\t\t// Not enabled - log as skipped\n\t\tprovenanceBuilder?.addDecision({\n\t\t\tprecedence: 6,\n\t\t\trule: \"pnp-support\",\n\t\t\tfeatureId: supportId,\n\t\t\taction: \"skip\",\n\t\t\tsourceType: \"system\",\n\t\t\treason: `Feature \"${supportId}\" not configured at any level`,\n\t\t});\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Create empty provenance when tracking is disabled\n\t */\n\tprivate createEmptyProvenance(contextId: string): PnpResolutionProvenance {\n\t\treturn {\n\t\t\tcontextId,\n\t\t\tresolvedAt: new Date(),\n\t\t\tsources: {},\n\t\t\tfeatures: new Map(),\n\t\t\tdecisionLog: [],\n\t\t\tsummary: {\n\t\t\t\ttotalFeatures: 0,\n\t\t\t\tenabled: 0,\n\t\t\t\tblocked: 0,\n\t\t\t\tnotConfigured: 0,\n\t\t\t\tbySource: {},\n\t\t\t\tbyRule: {},\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Build resolved tool configuration\n\t *\n\t * Maps PNP support ID to PIE tool ID and merges tool-specific settings.\n\t *\n\t * @param supportId PNP support identifier\n\t * @param context Resolution context for settings lookup\n\t * @param flags Availability flags (required, alwaysAvailable, source)\n\t * @returns Complete resolved tool configuration\n\t */\n\tprivate buildToolConfig(\n\t\tsupportId: string,\n\t\tcontext: ResolutionContext,\n\t\tflags: {\n\t\t\trequired?: boolean;\n\t\t\talwaysAvailable?: boolean;\n\t\t\tsource: string;\n\t\t},\n\t): ResolvedToolConfig {\n\t\t// Map PNP support ID to PIE tool ID via registry\n\t\tconst toolIds = this.toolRegistry.getToolsByPNPSupport(supportId);\n\n\t\t// Use first matching tool (usually only one per PNP support)\n\t\t// If no mapping exists, use the support ID directly as tool ID\n\t\tconst pieToolId = toolIds.size > 0 ? Array.from(toolIds)[0] : supportId;\n\n\t\t// Get tool-specific config from settings (item takes precedence)\n\t\tconst toolConfig =\n\t\t\tcontext.itemSettings?.toolParameters?.[supportId] ||\n\t\t\tcontext.toolConfigs?.[supportId];\n\n\t\treturn {\n\t\t\tid: pieToolId,\n\t\t\tenabled: true,\n\t\t\trequired: flags.required || false,\n\t\t\talwaysAvailable: flags.alwaysAvailable || false,\n\t\t\tsettings: toolConfig,\n\t\t\tsource: flags.source as any,\n\t\t};\n\t}\n\n\t/**\n\t * Check if a specific tool is enabled\n\t *\n\t * @param toolId Toolkit tool identifier (e.g., 'calculator')\n\t * @param assessment Assessment with PNP\n\t * @param itemRef Optional item context\n\t * @returns true if tool is enabled\n\t */\n\tisToolEnabled(\n\t\ttoolId: string,\n\t\tassessment: AssessmentEntity,\n\t\titemRef?: AssessmentItemRef,\n\t): boolean {\n\t\tconst resolved = this.resolveTools(assessment, itemRef);\n\t\treturn resolved.some((t) => t.id === toolId && t.enabled);\n\t}\n\n\t/**\n\t * Check if a tool is required (cannot be disabled)\n\t *\n\t * @param toolId Toolkit tool identifier\n\t * @param assessment Assessment with PNP\n\t * @param itemRef Optional item context\n\t * @returns true if tool is required or always available\n\t */\n\tisToolRequired(\n\t\ttoolId: string,\n\t\tassessment: AssessmentEntity,\n\t\titemRef?: AssessmentItemRef,\n\t): boolean {\n\t\tconst resolved = this.resolveTools(assessment, itemRef);\n\t\tconst tool = resolved.find((t) => t.id === toolId);\n\t\treturn tool?.required || tool?.alwaysAvailable || false;\n\t}\n\n\t/**\n\t * Get list of auto-activate tools from PNP\n\t *\n\t * Returns PIE tool IDs that should be automatically opened/activated\n\t * when the assessment starts, based on PNP activateAtInit field.\n\t *\n\t * @param assessment Assessment with PNP\n\t * @returns Array of PIE tool IDs to auto-activate\n\t *\n\t * @example\n\t * const autoActivate = resolver.getAutoActivateTools(assessment);\n\t * // Returns: ['textToSpeech', 'lineReader']\n\t */\n\tgetAutoActivateTools(assessment: AssessmentEntity): string[] {\n\t\tconst pnp = assessment.personalNeedsProfile;\n\t\tif (!pnp?.activateAtInit) return [];\n\n\t\treturn pnp.activateAtInit\n\t\t\t.map((supportId) => {\n\t\t\t\tconst toolIds = this.toolRegistry.getToolsByPNPSupport(supportId);\n\t\t\t\treturn toolIds.size > 0 ? Array.from(toolIds)[0] : null;\n\t\t\t})\n\t\t\t.filter(Boolean) as string[];\n\t}\n\n\t/**\n\t * Get all enabled tools\n\t *\n\t * @param assessment Assessment with PNP\n\t * @param itemRef Optional item context\n\t * @returns Array of PIE tool IDs that are enabled\n\t */\n\tgetEnabledTools(\n\t\tassessment: AssessmentEntity,\n\t\titemRef?: AssessmentItemRef,\n\t): string[] {\n\t\tconst resolved = this.resolveTools(assessment, itemRef);\n\t\treturn Array.from(\n\t\t\tnew Set(\n\t\t\t\tresolved\n\t\t\t\t\t.filter((t) => t.enabled)\n\t\t\t\t\t.map((t) => this.toolRegistry.normalizeToolId(t.id))\n\t\t\t\t\t.filter(Boolean),\n\t\t\t),\n\t\t);\n\t}\n\n\t/**\n\t * Get allowed tool IDs (Pass 1 of two-pass visibility model)\n\t *\n\t * Returns tool IDs that pass orchestrator-level checks (PNP + policies + settings).\n\t * This is the first gate - tools still need to pass Pass 2 (relevance check).\n\t *\n\t * This is the primary method to use with ToolRegistry.filterVisibleInContext()\n\t *\n\t * @param assessment Assessment with PNP\n\t * @param itemRef Optional item context\n\t * @returns Array of allowed tool IDs (Pass 1)\n\t *\n\t * @example\n\t * // Two-pass visibility model\n\t * const allowedToolIds = resolver.getAllowedToolIds(assessment, itemRef);\n\t * const visibleTools = toolRegistry.filterVisibleInContext(allowedToolIds, context);\n\t */\n\tgetAllowedToolIds(\n\t\tassessment: AssessmentEntity,\n\t\titemRef?: AssessmentItemRef,\n\t): string[] {\n\t\treturn this.getEnabledTools(assessment, itemRef);\n\t}\n\n\t/**\n\t * Resolve tools with a test/override PNP profile\n\t *\n\t * FOR TESTING/DEVELOPMENT ONLY\n\t *\n\t * Allows direct injection of a PNP profile for testing accessibility configurations\n\t * without modifying the assessment entity. The override profile replaces the\n\t * assessment's personalNeedsProfile entirely.\n\t *\n\t * Use cases:\n\t * - Development/testing UI for trying different profiles\n\t * - Automated testing with various accessibility configurations\n\t * - Preview tools with profile simulation\n\t * - Accessibility team testing and validation\n\t *\n\t * @param assessment Base assessment (settings, policies, etc.)\n\t * @param overrideProfile PNP profile to inject (replaces assessment.personalNeedsProfile)\n\t * @param itemRef Optional item context\n\t * @returns Resolution result with tools and provenance\n\t *\n\t * @example\n\t * // Test with low vision profile\n\t * const testProfile = {\n\t * supports: ['magnification', 'textToSpeech', 'highContrastDisplay']\n\t * };\n\t * const result = resolver.resolveWithOverride(assessment, testProfile);\n\t *\n\t * @example\n\t * // Test with null profile (no accessibility features)\n\t * const result = resolver.resolveWithOverride(assessment, null);\n\t */\n\tresolveWithOverride(\n\t\tassessment: AssessmentEntity,\n\t\toverrideProfile: PersonalNeedsProfile | null,\n\t\titemRef?: AssessmentItemRef,\n\t): ToolResolutionResult {\n\t\t// Create assessment clone with overridden PNP\n\t\tconst testAssessment: AssessmentEntity = {\n\t\t\t...assessment,\n\t\t\tpersonalNeedsProfile: overrideProfile || undefined,\n\t\t};\n\n\t\treturn this.resolveToolsWithProvenance(testAssessment, itemRef);\n\t}\n\n\t/**\n\t * Get allowed tool IDs with full provenance tracking\n\t *\n\t * Returns both tool IDs and complete resolution provenance for debugging,\n\t * auditing, and displaying to users.\n\t *\n\t * @param assessment Assessment with PNP\n\t * @param itemRef Optional item context\n\t * @returns Object with tool IDs and provenance\n\t *\n\t * @example\n\t * const result = resolver.getAllowedToolIdsWithProvenance(assessment, itemRef);\n\t *\n\t * // Use tool IDs\n\t * const visibleTools = toolRegistry.filterVisibleInContext(result.toolIds, context);\n\t *\n\t * // Display provenance to user\n\t * console.log('Resolution Summary:', result.provenance.summary);\n\t * console.log('Explanation for calculator:', result.provenance.features.get('calculator')?.explanation);\n\t */\n\tgetAllowedToolIdsWithProvenance(\n\t\tassessment: AssessmentEntity,\n\t\titemRef?: AssessmentItemRef,\n\t): { toolIds: string[]; provenance: PnpResolutionProvenance } {\n\t\tconst result = this.resolveToolsWithProvenance(assessment, itemRef);\n\t\treturn {\n\t\t\ttoolIds: result.tools.map((t) => t.id),\n\t\t\tprovenance: result.provenance,\n\t\t};\n\t}\n\n\t/**\n\t * Get all required tools\n\t *\n\t * @param assessment Assessment with PNP\n\t * @param itemRef Optional item context\n\t * @returns Array of PIE tool IDs that are required\n\t */\n\tgetRequiredTools(\n\t\tassessment: AssessmentEntity,\n\t\titemRef?: AssessmentItemRef,\n\t): string[] {\n\t\tconst resolved = this.resolveTools(assessment, itemRef);\n\t\treturn resolved\n\t\t\t.filter((t) => t.required || t.alwaysAvailable)\n\t\t\t.map((t) => t.id);\n\t}\n\n\t/**\n\t * Get tool configuration settings\n\t *\n\t * @param toolId Toolkit tool identifier\n\t * @param assessment Assessment with PNP\n\t * @param itemRef Optional item context\n\t * @returns Tool-specific settings or null if not found\n\t */\n\tgetToolSettings(\n\t\ttoolId: string,\n\t\tassessment: AssessmentEntity,\n\t\titemRef?: AssessmentItemRef,\n\t): any | null {\n\t\tconst resolved = this.resolveTools(assessment, itemRef);\n\t\tconst tool = resolved.find((t) => t.id === toolId);\n\t\treturn tool?.settings || null;\n\t}\n}\n"]}