@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,166 +0,0 @@
1
- /**
2
- * PNP Resolution Provenance Tracking
3
- *
4
- * Provides detailed tracking of how PNP profiles are resolved, including:
5
- * - Decision steps at each precedence level
6
- * - Sources of configuration (district, assessment, item, student)
7
- * - Reasons for tool enablement/blocking
8
- * - Merge operations and overrides
9
- *
10
- * This enables:
11
- * - Visual explanation components for users
12
- * - Debugging tool resolution issues
13
- * - Compliance auditing and reporting
14
- * - Developer troubleshooting
15
- *
16
- * Part of PIE Assessment Toolkit.
17
- */
18
- /**
19
- * Decision made during PNP resolution
20
- */
21
- export interface ResolutionDecision {
22
- /** Unique step identifier */
23
- step: number;
24
- /** Precedence level (1-6, lower = higher priority) */
25
- precedence: 1 | 2 | 3 | 4 | 5 | 6;
26
- /** Human-readable rule name */
27
- rule: "district-block" | "test-admin-override" | "item-restriction" | "item-requirement" | "district-requirement" | "pnp-support" | "pnp-prohibited" | "system-default";
28
- /** Tool or feature ID being resolved */
29
- featureId: string;
30
- /** Decision outcome */
31
- action: "block" | "enable" | "skip";
32
- /** Where the decision came from */
33
- source: {
34
- type: "organization" | "assessment" | "section" | "item" | "student" | "system";
35
- id?: string;
36
- name?: string;
37
- };
38
- /** Why this decision was made */
39
- reason: string;
40
- /** Configuration value that triggered this decision */
41
- value?: any;
42
- /** Timestamp of decision */
43
- timestamp: Date;
44
- }
45
- /**
46
- * Complete resolution trail for a single feature
47
- */
48
- export interface FeatureResolutionTrail {
49
- /** Feature ID (QTI 3.0 or tool ID) */
50
- featureId: string;
51
- /** Final decision */
52
- finalState: "enabled" | "blocked" | "not-configured";
53
- /** Winning decision (highest precedence) */
54
- winningDecision?: ResolutionDecision;
55
- /** All decisions considered (in precedence order) */
56
- allDecisions: ResolutionDecision[];
57
- /** Human-readable explanation */
58
- explanation: string;
59
- }
60
- /**
61
- * Complete provenance for an entire PNP resolution
62
- */
63
- export interface PnpResolutionProvenance {
64
- /** Session or resolution context ID */
65
- contextId: string;
66
- /** When resolution occurred */
67
- resolvedAt: Date;
68
- /** All configuration sources consulted */
69
- sources: {
70
- organization?: {
71
- id: string;
72
- name: string;
73
- pnpDefaults?: any;
74
- };
75
- assessment?: {
76
- id: string;
77
- name: string;
78
- settings?: any;
79
- };
80
- section?: {
81
- id: string;
82
- title?: string;
83
- settings?: any;
84
- };
85
- item?: {
86
- id: string;
87
- name?: string;
88
- settings?: any;
89
- };
90
- student?: {
91
- id: string;
92
- pnpProfile?: any;
93
- };
94
- };
95
- /** Resolution trail for each feature */
96
- features: Map<string, FeatureResolutionTrail>;
97
- /** All decisions in chronological order */
98
- decisionLog: ResolutionDecision[];
99
- /** Summary statistics */
100
- summary: {
101
- totalFeatures: number;
102
- enabled: number;
103
- blocked: number;
104
- notConfigured: number;
105
- bySource: Record<string, number>;
106
- byRule: Record<string, number>;
107
- };
108
- }
109
- /**
110
- * Provenance builder for tracking PNP resolution
111
- */
112
- export declare class PnpProvenanceBuilder {
113
- private provenance;
114
- private stepCounter;
115
- constructor(contextId: string);
116
- /**
117
- * Add a configuration source
118
- */
119
- addSource(type: "organization" | "assessment" | "section" | "item" | "student", data: {
120
- id: string;
121
- name?: string;
122
- config?: any;
123
- }): this;
124
- /**
125
- * Record a decision during resolution
126
- */
127
- addDecision(params: {
128
- precedence: 1 | 2 | 3 | 4 | 5 | 6;
129
- rule: ResolutionDecision["rule"];
130
- featureId: string;
131
- action: "block" | "enable" | "skip";
132
- sourceType: ResolutionDecision["source"]["type"];
133
- sourceId?: string;
134
- sourceName?: string;
135
- reason: string;
136
- value?: any;
137
- }): this;
138
- /**
139
- * Build final provenance with explanations
140
- */
141
- build(): PnpResolutionProvenance;
142
- /**
143
- * Generate human-readable explanation for a feature resolution
144
- */
145
- private generateExplanation;
146
- /**
147
- * Get human-readable rule name
148
- */
149
- private getRuleName;
150
- /**
151
- * Get current provenance (without explanations)
152
- */
153
- getCurrent(): PnpResolutionProvenance;
154
- }
155
- /**
156
- * Format provenance as markdown for display
157
- */
158
- export declare function formatProvenanceAsMarkdown(provenance: PnpResolutionProvenance): string;
159
- /**
160
- * Format provenance as JSON (for APIs)
161
- */
162
- export declare function formatProvenanceAsJSON(provenance: PnpResolutionProvenance): string;
163
- /**
164
- * Get simple explanation for a specific feature
165
- */
166
- export declare function getFeatureExplanation(provenance: PnpResolutionProvenance, featureId: string): string | null;
@@ -1,231 +0,0 @@
1
- /**
2
- * PNP Resolution Provenance Tracking
3
- *
4
- * Provides detailed tracking of how PNP profiles are resolved, including:
5
- * - Decision steps at each precedence level
6
- * - Sources of configuration (district, assessment, item, student)
7
- * - Reasons for tool enablement/blocking
8
- * - Merge operations and overrides
9
- *
10
- * This enables:
11
- * - Visual explanation components for users
12
- * - Debugging tool resolution issues
13
- * - Compliance auditing and reporting
14
- * - Developer troubleshooting
15
- *
16
- * Part of PIE Assessment Toolkit.
17
- */
18
- /**
19
- * Provenance builder for tracking PNP resolution
20
- */
21
- export class PnpProvenanceBuilder {
22
- provenance;
23
- stepCounter = 0;
24
- constructor(contextId) {
25
- this.provenance = {
26
- contextId,
27
- resolvedAt: new Date(),
28
- sources: {},
29
- features: new Map(),
30
- decisionLog: [],
31
- summary: {
32
- totalFeatures: 0,
33
- enabled: 0,
34
- blocked: 0,
35
- notConfigured: 0,
36
- bySource: {},
37
- byRule: {},
38
- },
39
- };
40
- }
41
- /**
42
- * Add a configuration source
43
- */
44
- addSource(type, data) {
45
- this.provenance.sources[type] = {
46
- id: data.id,
47
- name: data.name || data.id,
48
- ...(type === "organization" && { pnpDefaults: data.config }),
49
- ...(type === "assessment" && { settings: data.config }),
50
- ...(type === "section" && { settings: data.config }),
51
- ...(type === "item" && { settings: data.config }),
52
- ...(type === "student" && { pnpProfile: data.config }),
53
- };
54
- return this;
55
- }
56
- /**
57
- * Record a decision during resolution
58
- */
59
- addDecision(params) {
60
- const decision = {
61
- step: ++this.stepCounter,
62
- precedence: params.precedence,
63
- rule: params.rule,
64
- featureId: params.featureId,
65
- action: params.action,
66
- source: {
67
- type: params.sourceType,
68
- id: params.sourceId,
69
- name: params.sourceName,
70
- },
71
- reason: params.reason,
72
- value: params.value,
73
- timestamp: new Date(),
74
- };
75
- // Add to decision log
76
- this.provenance.decisionLog.push(decision);
77
- // Update feature trail
78
- if (!this.provenance.features.has(params.featureId)) {
79
- this.provenance.features.set(params.featureId, {
80
- featureId: params.featureId,
81
- finalState: "not-configured",
82
- allDecisions: [],
83
- explanation: "",
84
- });
85
- }
86
- const trail = this.provenance.features.get(params.featureId);
87
- trail.allDecisions.push(decision);
88
- // Update winning decision if this has higher precedence
89
- if (params.action !== "skip" &&
90
- (!trail.winningDecision ||
91
- decision.precedence < trail.winningDecision.precedence)) {
92
- trail.winningDecision = decision;
93
- trail.finalState = decision.action === "enable" ? "enabled" : "blocked";
94
- }
95
- // Update summary
96
- this.provenance.summary.bySource[params.sourceType] =
97
- (this.provenance.summary.bySource[params.sourceType] || 0) + 1;
98
- this.provenance.summary.byRule[params.rule] =
99
- (this.provenance.summary.byRule[params.rule] || 0) + 1;
100
- return this;
101
- }
102
- /**
103
- * Build final provenance with explanations
104
- */
105
- build() {
106
- // Generate explanations for each feature
107
- for (const [featureId, trail] of this.provenance.features.entries()) {
108
- trail.explanation = this.generateExplanation(trail);
109
- }
110
- // Update summary counts
111
- for (const trail of this.provenance.features.values()) {
112
- this.provenance.summary.totalFeatures++;
113
- if (trail.finalState === "enabled") {
114
- this.provenance.summary.enabled++;
115
- }
116
- else if (trail.finalState === "blocked") {
117
- this.provenance.summary.blocked++;
118
- }
119
- else {
120
- this.provenance.summary.notConfigured++;
121
- }
122
- }
123
- return this.provenance;
124
- }
125
- /**
126
- * Generate human-readable explanation for a feature resolution
127
- */
128
- generateExplanation(trail) {
129
- if (!trail.winningDecision) {
130
- return `Feature "${trail.featureId}" was not configured at any level.`;
131
- }
132
- const decision = trail.winningDecision;
133
- const source = decision.source;
134
- const sourceName = source.name || source.type;
135
- let explanation = `Feature "${trail.featureId}" is ${trail.finalState}.\n\n`;
136
- // Explain winning decision
137
- explanation += `**Primary Reason** (${this.getRuleName(decision.rule)}):\n`;
138
- explanation += `${decision.reason}\n`;
139
- explanation += `Source: ${sourceName} (${source.type})\n\n`;
140
- // Show overridden decisions if any
141
- const overridden = trail.allDecisions.filter((d) => d.step !== decision.step &&
142
- d.action !== "skip" &&
143
- d.precedence > decision.precedence);
144
- if (overridden.length > 0) {
145
- explanation += `**Overridden Rules**:\n`;
146
- overridden.forEach((d) => {
147
- explanation += `- ${this.getRuleName(d.rule)}: ${d.reason} (overridden by higher precedence)\n`;
148
- });
149
- }
150
- return explanation;
151
- }
152
- /**
153
- * Get human-readable rule name
154
- */
155
- getRuleName(rule) {
156
- const names = {
157
- "district-block": "District Block",
158
- "test-admin-override": "Test Administrator Override",
159
- "item-restriction": "Item Restriction",
160
- "item-requirement": "Item Requirement",
161
- "district-requirement": "District Requirement",
162
- "pnp-support": "Student PNP Support",
163
- "pnp-prohibited": "Student PNP Prohibition",
164
- "system-default": "System Default",
165
- };
166
- return names[rule];
167
- }
168
- /**
169
- * Get current provenance (without explanations)
170
- */
171
- getCurrent() {
172
- return this.provenance;
173
- }
174
- }
175
- /**
176
- * Format provenance as markdown for display
177
- */
178
- export function formatProvenanceAsMarkdown(provenance) {
179
- let md = `# PNP Resolution Report\n\n`;
180
- md += `**Context**: ${provenance.contextId}\n`;
181
- md += `**Resolved At**: ${provenance.resolvedAt.toISOString()}\n\n`;
182
- // Summary
183
- md += `## Summary\n\n`;
184
- md += `- Total Features: ${provenance.summary.totalFeatures}\n`;
185
- md += `- Enabled: ${provenance.summary.enabled}\n`;
186
- md += `- Blocked: ${provenance.summary.blocked}\n`;
187
- md += `- Not Configured: ${provenance.summary.notConfigured}\n\n`;
188
- // Sources
189
- md += `## Configuration Sources\n\n`;
190
- Object.entries(provenance.sources).forEach(([type, source]) => {
191
- const sourceName = source.name || source.title || source.id;
192
- md += `- **${type}**: ${sourceName} (${source.id})\n`;
193
- });
194
- md += `\n`;
195
- // Features
196
- md += `## Feature Resolution\n\n`;
197
- for (const trail of provenance.features.values()) {
198
- md += `### ${trail.featureId}\n\n`;
199
- md += `**Status**: ${trail.finalState}\n\n`;
200
- md += trail.explanation + `\n\n`;
201
- }
202
- // Decision log
203
- md += `## Decision Log\n\n`;
204
- provenance.decisionLog.forEach((decision) => {
205
- md += `${decision.step}. **${decision.rule}** (precedence ${decision.precedence})\n`;
206
- md += ` - Feature: ${decision.featureId}\n`;
207
- md += ` - Action: ${decision.action}\n`;
208
- md += ` - Source: ${decision.source.name || decision.source.type}\n`;
209
- md += ` - Reason: ${decision.reason}\n\n`;
210
- });
211
- return md;
212
- }
213
- /**
214
- * Format provenance as JSON (for APIs)
215
- */
216
- export function formatProvenanceAsJSON(provenance) {
217
- // Convert Map to object for JSON serialization
218
- const serializable = {
219
- ...provenance,
220
- features: Array.from(provenance.features.entries()).map(([_featureId, trail]) => trail),
221
- };
222
- return JSON.stringify(serializable, null, 2);
223
- }
224
- /**
225
- * Get simple explanation for a specific feature
226
- */
227
- export function getFeatureExplanation(provenance, featureId) {
228
- const trail = provenance.features.get(featureId);
229
- return trail?.explanation || null;
230
- }
231
- //# sourceMappingURL=pnp-provenance.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pnp-provenance.js","sourceRoot":"","sources":["../../src/services/pnp-provenance.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PNP Resolution Provenance Tracking\n *\n * Provides detailed tracking of how PNP profiles are resolved, including:\n * - Decision steps at each precedence level\n * - Sources of configuration (district, assessment, item, student)\n * - Reasons for tool enablement/blocking\n * - Merge operations and overrides\n *\n * This enables:\n * - Visual explanation components for users\n * - Debugging tool resolution issues\n * - Compliance auditing and reporting\n * - Developer troubleshooting\n *\n * Part of PIE Assessment Toolkit.\n */\n\n/**\n * Decision made during PNP resolution\n */\nexport interface ResolutionDecision {\n\t/** Unique step identifier */\n\tstep: number;\n\n\t/** Precedence level (1-6, lower = higher priority) */\n\tprecedence: 1 | 2 | 3 | 4 | 5 | 6;\n\n\t/** Human-readable rule name */\n\trule:\n\t\t| \"district-block\"\n\t\t| \"test-admin-override\"\n\t\t| \"item-restriction\"\n\t\t| \"item-requirement\"\n\t\t| \"district-requirement\"\n\t\t| \"pnp-support\"\n\t\t| \"pnp-prohibited\"\n\t\t| \"system-default\";\n\n\t/** Tool or feature ID being resolved */\n\tfeatureId: string;\n\n\t/** Decision outcome */\n\taction: \"block\" | \"enable\" | \"skip\";\n\n\t/** Where the decision came from */\n\tsource: {\n\t\ttype:\n\t\t\t| \"organization\"\n\t\t\t| \"assessment\"\n\t\t\t| \"section\"\n\t\t\t| \"item\"\n\t\t\t| \"student\"\n\t\t\t| \"system\";\n\t\tid?: string; // Entity ID (e.g., org ID, assessment ID)\n\t\tname?: string; // Human-readable name\n\t};\n\n\t/** Why this decision was made */\n\treason: string;\n\n\t/** Configuration value that triggered this decision */\n\tvalue?: any;\n\n\t/** Timestamp of decision */\n\ttimestamp: Date;\n}\n\n/**\n * Complete resolution trail for a single feature\n */\nexport interface FeatureResolutionTrail {\n\t/** Feature ID (QTI 3.0 or tool ID) */\n\tfeatureId: string;\n\n\t/** Final decision */\n\tfinalState: \"enabled\" | \"blocked\" | \"not-configured\";\n\n\t/** Winning decision (highest precedence) */\n\twinningDecision?: ResolutionDecision;\n\n\t/** All decisions considered (in precedence order) */\n\tallDecisions: ResolutionDecision[];\n\n\t/** Human-readable explanation */\n\texplanation: string;\n}\n\n/**\n * Complete provenance for an entire PNP resolution\n */\nexport interface PnpResolutionProvenance {\n\t/** Session or resolution context ID */\n\tcontextId: string;\n\n\t/** When resolution occurred */\n\tresolvedAt: Date;\n\n\t/** All configuration sources consulted */\n\tsources: {\n\t\torganization?: {\n\t\t\tid: string;\n\t\t\tname: string;\n\t\t\tpnpDefaults?: any;\n\t\t};\n\t\tassessment?: {\n\t\t\tid: string;\n\t\t\tname: string;\n\t\t\tsettings?: any;\n\t\t};\n\t\tsection?: {\n\t\t\tid: string;\n\t\t\ttitle?: string;\n\t\t\tsettings?: any;\n\t\t};\n\t\titem?: {\n\t\t\tid: string;\n\t\t\tname?: string;\n\t\t\tsettings?: any;\n\t\t};\n\t\tstudent?: {\n\t\t\tid: string;\n\t\t\tpnpProfile?: any;\n\t\t};\n\t};\n\n\t/** Resolution trail for each feature */\n\tfeatures: Map<string, FeatureResolutionTrail>;\n\n\t/** All decisions in chronological order */\n\tdecisionLog: ResolutionDecision[];\n\n\t/** Summary statistics */\n\tsummary: {\n\t\ttotalFeatures: number;\n\t\tenabled: number;\n\t\tblocked: number;\n\t\tnotConfigured: number;\n\t\tbySource: Record<string, number>;\n\t\tbyRule: Record<string, number>;\n\t};\n}\n\n/**\n * Provenance builder for tracking PNP resolution\n */\nexport class PnpProvenanceBuilder {\n\tprivate provenance: PnpResolutionProvenance;\n\tprivate stepCounter = 0;\n\n\tconstructor(contextId: string) {\n\t\tthis.provenance = {\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 * Add a configuration source\n\t */\n\taddSource(\n\t\ttype: \"organization\" | \"assessment\" | \"section\" | \"item\" | \"student\",\n\t\tdata: { id: string; name?: string; config?: any },\n\t): this {\n\t\tthis.provenance.sources[type] = {\n\t\t\tid: data.id,\n\t\t\tname: data.name || data.id,\n\t\t\t...(type === \"organization\" && { pnpDefaults: data.config }),\n\t\t\t...(type === \"assessment\" && { settings: data.config }),\n\t\t\t...(type === \"section\" && { settings: data.config }),\n\t\t\t...(type === \"item\" && { settings: data.config }),\n\t\t\t...(type === \"student\" && { pnpProfile: data.config }),\n\t\t} as any;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Record a decision during resolution\n\t */\n\taddDecision(params: {\n\t\tprecedence: 1 | 2 | 3 | 4 | 5 | 6;\n\t\trule: ResolutionDecision[\"rule\"];\n\t\tfeatureId: string;\n\t\taction: \"block\" | \"enable\" | \"skip\";\n\t\tsourceType: ResolutionDecision[\"source\"][\"type\"];\n\t\tsourceId?: string;\n\t\tsourceName?: string;\n\t\treason: string;\n\t\tvalue?: any;\n\t}): this {\n\t\tconst decision: ResolutionDecision = {\n\t\t\tstep: ++this.stepCounter,\n\t\t\tprecedence: params.precedence,\n\t\t\trule: params.rule,\n\t\t\tfeatureId: params.featureId,\n\t\t\taction: params.action,\n\t\t\tsource: {\n\t\t\t\ttype: params.sourceType,\n\t\t\t\tid: params.sourceId,\n\t\t\t\tname: params.sourceName,\n\t\t\t},\n\t\t\treason: params.reason,\n\t\t\tvalue: params.value,\n\t\t\ttimestamp: new Date(),\n\t\t};\n\n\t\t// Add to decision log\n\t\tthis.provenance.decisionLog.push(decision);\n\n\t\t// Update feature trail\n\t\tif (!this.provenance.features.has(params.featureId)) {\n\t\t\tthis.provenance.features.set(params.featureId, {\n\t\t\t\tfeatureId: params.featureId,\n\t\t\t\tfinalState: \"not-configured\",\n\t\t\t\tallDecisions: [],\n\t\t\t\texplanation: \"\",\n\t\t\t});\n\t\t}\n\n\t\tconst trail = this.provenance.features.get(params.featureId)!;\n\t\ttrail.allDecisions.push(decision);\n\n\t\t// Update winning decision if this has higher precedence\n\t\tif (\n\t\t\tparams.action !== \"skip\" &&\n\t\t\t(!trail.winningDecision ||\n\t\t\t\tdecision.precedence < trail.winningDecision.precedence)\n\t\t) {\n\t\t\ttrail.winningDecision = decision;\n\t\t\ttrail.finalState = decision.action === \"enable\" ? \"enabled\" : \"blocked\";\n\t\t}\n\n\t\t// Update summary\n\t\tthis.provenance.summary.bySource[params.sourceType] =\n\t\t\t(this.provenance.summary.bySource[params.sourceType] || 0) + 1;\n\t\tthis.provenance.summary.byRule[params.rule] =\n\t\t\t(this.provenance.summary.byRule[params.rule] || 0) + 1;\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Build final provenance with explanations\n\t */\n\tbuild(): PnpResolutionProvenance {\n\t\t// Generate explanations for each feature\n\t\tfor (const [featureId, trail] of this.provenance.features.entries()) {\n\t\t\ttrail.explanation = this.generateExplanation(trail);\n\t\t}\n\n\t\t// Update summary counts\n\t\tfor (const trail of this.provenance.features.values()) {\n\t\t\tthis.provenance.summary.totalFeatures++;\n\t\t\tif (trail.finalState === \"enabled\") {\n\t\t\t\tthis.provenance.summary.enabled++;\n\t\t\t} else if (trail.finalState === \"blocked\") {\n\t\t\t\tthis.provenance.summary.blocked++;\n\t\t\t} else {\n\t\t\t\tthis.provenance.summary.notConfigured++;\n\t\t\t}\n\t\t}\n\n\t\treturn this.provenance;\n\t}\n\n\t/**\n\t * Generate human-readable explanation for a feature resolution\n\t */\n\tprivate generateExplanation(trail: FeatureResolutionTrail): string {\n\t\tif (!trail.winningDecision) {\n\t\t\treturn `Feature \"${trail.featureId}\" was not configured at any level.`;\n\t\t}\n\n\t\tconst decision = trail.winningDecision;\n\t\tconst source = decision.source;\n\t\tconst sourceName = source.name || source.type;\n\n\t\tlet explanation = `Feature \"${trail.featureId}\" is ${trail.finalState}.\\n\\n`;\n\n\t\t// Explain winning decision\n\t\texplanation += `**Primary Reason** (${this.getRuleName(decision.rule)}):\\n`;\n\t\texplanation += `${decision.reason}\\n`;\n\t\texplanation += `Source: ${sourceName} (${source.type})\\n\\n`;\n\n\t\t// Show overridden decisions if any\n\t\tconst overridden = trail.allDecisions.filter(\n\t\t\t(d) =>\n\t\t\t\td.step !== decision.step &&\n\t\t\t\td.action !== \"skip\" &&\n\t\t\t\td.precedence > decision.precedence,\n\t\t);\n\n\t\tif (overridden.length > 0) {\n\t\t\texplanation += `**Overridden Rules**:\\n`;\n\t\t\toverridden.forEach((d) => {\n\t\t\t\texplanation += `- ${this.getRuleName(d.rule)}: ${d.reason} (overridden by higher precedence)\\n`;\n\t\t\t});\n\t\t}\n\n\t\treturn explanation;\n\t}\n\n\t/**\n\t * Get human-readable rule name\n\t */\n\tprivate getRuleName(rule: ResolutionDecision[\"rule\"]): string {\n\t\tconst names: Record<ResolutionDecision[\"rule\"], string> = {\n\t\t\t\"district-block\": \"District Block\",\n\t\t\t\"test-admin-override\": \"Test Administrator Override\",\n\t\t\t\"item-restriction\": \"Item Restriction\",\n\t\t\t\"item-requirement\": \"Item Requirement\",\n\t\t\t\"district-requirement\": \"District Requirement\",\n\t\t\t\"pnp-support\": \"Student PNP Support\",\n\t\t\t\"pnp-prohibited\": \"Student PNP Prohibition\",\n\t\t\t\"system-default\": \"System Default\",\n\t\t};\n\t\treturn names[rule];\n\t}\n\n\t/**\n\t * Get current provenance (without explanations)\n\t */\n\tgetCurrent(): PnpResolutionProvenance {\n\t\treturn this.provenance;\n\t}\n}\n\n/**\n * Format provenance as markdown for display\n */\nexport function formatProvenanceAsMarkdown(\n\tprovenance: PnpResolutionProvenance,\n): string {\n\tlet md = `# PNP Resolution Report\\n\\n`;\n\tmd += `**Context**: ${provenance.contextId}\\n`;\n\tmd += `**Resolved At**: ${provenance.resolvedAt.toISOString()}\\n\\n`;\n\n\t// Summary\n\tmd += `## Summary\\n\\n`;\n\tmd += `- Total Features: ${provenance.summary.totalFeatures}\\n`;\n\tmd += `- Enabled: ${provenance.summary.enabled}\\n`;\n\tmd += `- Blocked: ${provenance.summary.blocked}\\n`;\n\tmd += `- Not Configured: ${provenance.summary.notConfigured}\\n\\n`;\n\n\t// Sources\n\tmd += `## Configuration Sources\\n\\n`;\n\tObject.entries(provenance.sources).forEach(([type, source]) => {\n\t\tconst sourceName =\n\t\t\t(source as any).name || (source as any).title || source.id;\n\t\tmd += `- **${type}**: ${sourceName} (${source.id})\\n`;\n\t});\n\tmd += `\\n`;\n\n\t// Features\n\tmd += `## Feature Resolution\\n\\n`;\n\tfor (const trail of provenance.features.values()) {\n\t\tmd += `### ${trail.featureId}\\n\\n`;\n\t\tmd += `**Status**: ${trail.finalState}\\n\\n`;\n\t\tmd += trail.explanation + `\\n\\n`;\n\t}\n\n\t// Decision log\n\tmd += `## Decision Log\\n\\n`;\n\tprovenance.decisionLog.forEach((decision) => {\n\t\tmd += `${decision.step}. **${decision.rule}** (precedence ${decision.precedence})\\n`;\n\t\tmd += ` - Feature: ${decision.featureId}\\n`;\n\t\tmd += ` - Action: ${decision.action}\\n`;\n\t\tmd += ` - Source: ${decision.source.name || decision.source.type}\\n`;\n\t\tmd += ` - Reason: ${decision.reason}\\n\\n`;\n\t});\n\n\treturn md;\n}\n\n/**\n * Format provenance as JSON (for APIs)\n */\nexport function formatProvenanceAsJSON(\n\tprovenance: PnpResolutionProvenance,\n): string {\n\t// Convert Map to object for JSON serialization\n\tconst serializable = {\n\t\t...provenance,\n\t\tfeatures: Array.from(provenance.features.entries()).map(\n\t\t\t([_featureId, trail]) => trail,\n\t\t),\n\t};\n\treturn JSON.stringify(serializable, null, 2);\n}\n\n/**\n * Get simple explanation for a specific feature\n */\nexport function getFeatureExplanation(\n\tprovenance: PnpResolutionProvenance,\n\tfeatureId: string,\n): string | null {\n\tconst trail = provenance.features.get(featureId);\n\treturn trail?.explanation || null;\n}\n"]}